diff --git a/.claude/commands/dedupe.md b/.claude/commands/dedupe.md deleted file mode 100644 index 2711e981a..000000000 --- a/.claude/commands/dedupe.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -allowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(./scripts/comment-on-duplicates.sh:*) -description: Find duplicate GitHub issues ---- - -Find up to 3 likely duplicate issues for a given GitHub issue. - -To do this, follow these steps precisely: - -1. Use an agent to check if the Github issue (a) is closed, (b) does not need to be deduped (eg. because it is broad product feedback without a specific solution, or positive feedback), or (c) already has a duplicates comment that you made earlier. If so, do not proceed. -2. Use an agent to view a Github issue, and ask the agent to return a summary of the issue -3. Then, launch 5 parallel agents to search Github for duplicates of this issue, using diverse keywords and search approaches, using the summary from #2 -4. Next, feed the results from #2 and #3 into another agent, so that it can filter out false positives, that are likely not actually duplicates of the original issue. If there are no duplicates remaining, do not proceed. -5. Finally, use the comment script to post duplicates: - ``` - ./scripts/comment-on-duplicates.sh --base-issue --potential-duplicates - ``` - -Notes (be sure to tell this to your agents, too): - -- Use `gh` to interact with Github, rather than web fetch -- Do not use other tools, beyond `gh` and the comment script (eg. don't use other MCP servers, file edit, etc.) -- Make a todo list first diff --git a/.custom-gcl.yml b/.custom-gcl.yml new file mode 100644 index 000000000..6ca10094a --- /dev/null +++ b/.custom-gcl.yml @@ -0,0 +1,4 @@ +version: v1.57.0 +plugins: + - module: 'github.com/lightningnetwork/lnd/tools/linters' + path: ./tools/linters \ No newline at end of file diff --git a/.github/actions/setup-go/action.yml b/.github/actions/setup-go/action.yml index 5d7275f55..09f47d769 100644 --- a/.github/actions/setup-go/action.yml +++ b/.github/actions/setup-go/action.yml @@ -52,8 +52,8 @@ runs: # The key is used to create and later look up the cache. It's made of # four parts: # - The base part is made from the OS name, Go version and a - # job-specified key prefix. Example: `linux-go-1.26.4-unit-test-`. - # It ensures that a job running on Linux with Go 1.26 only looks for + # job-specified key prefix. Example: `linux-go-1.25.5-unit-test-`. + # It ensures that a job running on Linux with Go 1.25 only looks for # caches from the same environment. # - The unique part is the `hashFiles('**/go.sum')`, which calculates a # hash (a fingerprint) of the go.sum file. diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml deleted file mode 100644 index c691bcf10..000000000 --- a/.github/workflows/backport.yml +++ /dev/null @@ -1,124 +0,0 @@ -name: Backport - -on: - pull_request_target: - types: [closed, labeled] - -permissions: - contents: write - pull-requests: write - issues: read - -jobs: - backport: - name: Backport PR - runs-on: ubuntu-latest - # Only run on merged PRs with backport labels. - # Labels must match pattern: backport-v* (e.g., backport-v0.20.x-branch). - # This excludes labels like "backport candidate" or "backport-candidate". - if: | - github.event.pull_request.merged == true && - contains(join(github.event.pull_request.labels.*.name, ','), 'backport-v') - - steps: - - name: Checkout repository - uses: actions/checkout@v5 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.base.ref }} - - - name: Validate target branches exist - id: validate - shell: bash - run: | - # Extract all backport labels - labels='${{ toJSON(github.event.pull_request.labels.*.name) }}' - echo "All labels: $labels" - - # Parse labels and extract branch names - # Only match labels starting with "backport-v" to exclude labels like - # "backport candidate" or "backport-candidate" - backport_labels=$(echo "$labels" | jq -r '.[] | select(startswith("backport-v"))') - - if [ -z "$backport_labels" ]; then - echo "::error::No valid backport labels found (must start with 'backport-v')" - exit 1 - fi - - echo "Found backport labels:" - echo "$backport_labels" - - # Check each target branch exists - missing_branches=() - valid_branches=() - while IFS= read -r label; do - # Extract branch name (everything after "backport-") - branch_name="${label#backport-}" - echo "Checking if branch exists: $branch_name" - - # Check if branch exists in remote - if ! git ls-remote --heads origin "$branch_name" | grep -q "$branch_name"; then - echo "::warning::Target branch '$branch_name' does not exist (from label '$label')" - missing_branches+=("$branch_name") - else - echo "✓ Branch '$branch_name' exists" - valid_branches+=("$branch_name") - fi - done <<< "$backport_labels" - - # Report validation results - if [ ${#missing_branches[@]} -gt 0 ]; then - echo "::warning::The following target branches do not exist and will be skipped: ${missing_branches[*]}" - echo "::warning::Please check the branch names or create the branches before retrying" - fi - - # Only fail if ALL branches are invalid - if [ ${#valid_branches[@]} -eq 0 ]; then - echo "::error::No valid target branches found. All backport labels reference non-existent branches." - exit 1 - fi - - echo "✓ Found ${#valid_branches[@]} valid target branch(es): ${valid_branches[*]}" - if [ ${#missing_branches[@]} -gt 0 ]; then - echo "⚠ Skipping ${#missing_branches[@]} invalid branch(es): ${missing_branches[*]}" - fi - - - name: Create backport PRs - # Uses version v3.4, we pin to a hash here. For more details to - # available versions, see: - # https://github.com/korthout/backport-action/releases. - uses: korthout/backport-action@d07416681cab29bf2661702f925f020aaa962997 - with: - # Automatically detect target branches from labels. - # Labels must be in format: backport-v0.20.x-branch (must start - # with "backport-v"). This excludes labels like "backport candidate" - # or "backport-candidate". The pattern extracts everything after - # "backport-" as the branch name. - label_pattern: '^backport-(v.+)$' - - # GitHub token for creating PRs. - github_token: ${{ secrets.GITHUB_TOKEN }} - - # PR title format - shows it's a backport with original PR number. - pull_title: '[${target_branch}] Backport #${pull_number}: ${pull_title}' - - # PR description template - links back to original PR. - pull_description: |- - Backport of #${pull_number} - - --- - - ${pull_description} - - # Automatically add labels to backport PRs. - # The 'no-changelog' label skips the release notes check in CI. - add_labels: no-changelog - - # Copy milestone from original PR to backport PR. - copy_milestone: true - - # Merge strategy - skip merge commits, use cherry-pick only. - merge_commits: skip - - # If conflicts occur, create a draft PR with conflict markers. - experimental: '{"conflict_resolution": "draft_commit_conflicts"}' diff --git a/.github/workflows/claude-dedupe-issues.yml b/.github/workflows/claude-dedupe-issues.yml deleted file mode 100644 index 93c052e3b..000000000 --- a/.github/workflows/claude-dedupe-issues.yml +++ /dev/null @@ -1,176 +0,0 @@ -name: Claude Issue Dedupe -description: Automatically dedupe GitHub issues using Claude Code -on: - issues: - types: [opened] - workflow_dispatch: - inputs: - issue_number: - description: 'Issue number to process for duplicate detection' - required: true - type: string - -# Default to read-only. The find-duplicates job reads untrusted issue text with -# the model, so it must not hold a write token; post-comment takes issues: write -# but runs no model and only shells out to scripts/comment-on-duplicates.sh, -# which re-validates every issue number it is handed. -permissions: - contents: read - -# Serialize runs for the same issue so an `issues: opened` event and a -# workflow_dispatch for the same number can't both read "no prior comment" and -# double-post. Mirrors pr-severity.yml. -concurrency: - group: claude-dedupe-${{ github.event.issue.number || inputs.issue_number }} - cancel-in-progress: true - -jobs: - find-duplicates: - runs-on: ubuntu-latest - timeout-minutes: 10 - # Read-only: the model inspects the issue and searches for duplicates, then - # records the candidate issue numbers to a file. - permissions: - contents: read - issues: read - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - persist-credentials: false - - - name: Find duplicate issues with Claude - # Pinned to a full commit SHA rather than the mutable @v1 tag: this step - # feeds untrusted issue text to the model with CLAUDE_CODE_OAUTH_TOKEN in - # process, so a repointed tag would run attacker-controlled action code - # with that secret present. Bump deliberately when updating. - uses: anthropics/claude-code-action@ba0aafd4308cbba7165f9f2cdb0cfbed5a3c99ce # v1 - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - - # Accept any issue author: this job holds only read scope and merely - # records candidate issue numbers to a file; the comment is posted by - # a separate, model-free job. "*" is safe ONLY while this job stays - # read-only. Before granting this job a write token or a mutating tool - # (a write-capable gh subcommand, a Bash mutation), replace "*" with - # an explicit allowlist — otherwise any fork author's issue text would - # steer a privileged model. - allowed_non_write_users: "*" - model: claude-haiku-4-5-20251001 - - # Read-only gh tools plus Write to record the result. No comment or - # edit tools, and no access to the duplicate-comment script. - claude_args: >- - --allowedTools - "Bash(gh issue view:*)" - "Bash(gh search:*)" - "Bash(gh issue list:*)" - "Write" - - prompt: | - Find up to 3 likely duplicate issues for issue - #${{ github.event.issue.number || inputs.issue_number }} in the - ${{ github.repository }} repository. Follow these steps precisely: - - 1. View the issue and check whether it (a) is closed, (b) does not - need deduping (e.g. broad product feedback without a specific - solution, or positive feedback), or (c) already has a duplicates - comment. If any of these hold, write an empty `duplicates.txt` - (create the file with no content) and stop. - - 2. Summarize the issue. - - 3. Search GitHub for duplicates of this issue using several diverse - keyword searches and search approaches, based on the summary. - - 4. Filter out false positives that are likely not actually - duplicates of the original issue. If no plausible duplicates - remain, write an empty `duplicates.txt` and stop. - - 5. Otherwise, write the chosen duplicate issue numbers to a file - named `duplicates.txt` in the current working directory: digits - only, one issue number per line, at most 3 lines. Do not include - `#`, URLs, or any other text. - - Notes: - - Use `gh` to interact with GitHub, not web fetch. - - Do NOT use any tools beyond `gh issue view`, `gh search`, - `gh issue list`, and `Write`. You do NOT post comments; a separate - step does that from the file you write. - - Make a todo list first. - - - name: Upload duplicate candidates - uses: actions/upload-artifact@v4 - with: - name: dedupe-result - path: duplicates.txt - if-no-files-found: warn - retention-days: 1 - - post-comment: - runs-on: ubuntu-latest - needs: find-duplicates - timeout-minutes: 5 - # Write scope lives here, in a job that runs no model. The base issue number - # comes from the trusted event payload, and comment-on-duplicates.sh - # re-validates every candidate issue number before posting. - permissions: - contents: read - issues: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - persist-credentials: false - - - name: Download duplicate candidates - uses: actions/download-artifact@v4 - # find-duplicates uploads with if-no-files-found: warn, so when the - # model writes no file at all (timeout, refusal) no artifact exists and - # download-artifact would otherwise hard-fail the job. Tolerate a - # missing artifact so the no-op guard in the next step is reachable. - continue-on-error: true - with: - name: dedupe-result - path: result - - - name: Post duplicate comment - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_REPOSITORY: ${{ github.repository }} - BASE_ISSUE: ${{ github.event.issue.number || inputs.issue_number }} - run: | - set -euo pipefail - - # Distinguish a genuine "no duplicates" verdict from a find-duplicates - # run that produced no artifact at all (model crash/timeout, or a - # tolerated missing-artifact download). The latter gets a warning so a - # broken run doesn't read as a healthy no-op, mirroring the pr-severity - # apply step; the present-but-empty case stays a silent no-op. - if [[ ! -f result/duplicates.txt ]]; then - echo "::warning::dedupe find-duplicates produced no result; nothing posted." - exit 0 - fi - if [[ ! -s result/duplicates.txt ]]; then - echo "No duplicate candidates; nothing to post." - exit 0 - fi - - # Extract up to 3 purely-numeric issue ids. comment-on-duplicates.sh - # re-validates these and the base issue (numeric, existing, at most 3) - # before posting. - mapfile -t DUPS < <(grep -oE '^[0-9]+$' result/duplicates.txt | head -n 3) - - if [[ ${#DUPS[@]} -eq 0 ]]; then - echo "No valid numeric duplicate ids; nothing to post." - exit 0 - fi - - ./scripts/comment-on-duplicates.sh \ - --base-issue "$BASE_ISSUE" \ - --potential-duplicates "${DUPS[@]}" diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml deleted file mode 100644 index d211eb03d..000000000 --- a/.github/workflows/claude.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Claude Code - -on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - issues: - types: [opened, assigned] - pull_request_review: - types: [submitted] - -jobs: - claude: - if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - issues: read - id-token: write - actions: read # Required for Claude to read CI results on PRs - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Checkout PR branch (handles fork PRs) - if: github.event.issue.pull_request || github.event_name == 'pull_request_review_comment' || github.event_name == 'pull_request_review' - env: - GH_TOKEN: ${{ github.token }} - run: | - if [ "${{ github.event_name }}" = "issue_comment" ]; then - PR_NUMBER=${{ github.event.issue.number }} - else - PR_NUMBER=${{ github.event.pull_request.number }} - fi - gh pr checkout "$PR_NUMBER" - - - name: Run Claude Code - id: claude - uses: anthropics/claude-code-action@v1 - with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - - # This is an optional setting that allows Claude to read CI results on PRs - additional_permissions: | - actions: read - - # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. - # prompt: 'Update the pull request description to include a summary of changes.' - - # Optional: Add claude_args to customize behavior and configuration - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - # or https://code.claude.com/docs/en/cli-reference for available options - # claude_args: '--allowed-tools Bash(gh pr:*)' - diff --git a/.github/workflows/gateway.yml b/.github/workflows/gateway.yml deleted file mode 100644 index 0b8bf31fc..000000000 --- a/.github/workflows/gateway.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: gateway - -# Opt-in code-review bot. Triggered by a `/gateway ` comment on a PR -# (e.g. `/gateway review`); review/approve commands are gated to maintainers. -# Comment-commands only — no pull_request triggers — so fork PRs (which receive -# no secrets) never spawn failing runs. v0.5.0 added the -# pull_request_review_comment trigger: /gateway dismiss, promote, and explain -# now also work as replies on a finding's inline thread (finding id inferred -# from the thread when omitted). Also a comment event — same fork-PR safety -# profile as issue_comment. -# -# Thin shim: the public lightninglabs/gateway-action mints an App token and -# checks out the private gateway runtime at execution time. The runtime stays -# private; only this entry point is public. - -on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - -permissions: - # The action mints an App installation token internally; the GITHUB_TOKEN - # handed to this shim is unused, so we minimise it. - contents: read - -jobs: - review: - # issue_comment fires for all issues and every PR comment. Filter to PR - # comments that look like a /gateway command so unrelated comments don't - # spin up a no-op runner. `contains` (not `startsWith`) because the runtime - # accepts the command at column 0 of any line, including multi-line bodies. - if: >- - ${{ - (github.event_name == 'issue_comment' - && github.event.issue.pull_request != null - && contains(github.event.comment.body, '/gateway')) || - (github.event_name == 'pull_request_review_comment' - && contains(github.event.comment.body, '/gateway')) - }} - runs-on: ubuntu-latest - timeout-minutes: 15 - env: - GATEWAY_REVIEW_MODE: multi - steps: - - uses: lightninglabs/gateway-action@334a8455ee316e40668ae3ac85249150c62704ec # v0.6.0 - with: - # Pin the private runtime to an immutable commit (matches the action - # SHA-pin above) so runtime upgrades go through an lnd PR, not a moved - # tag. Without this, runtime_ref defaults to the v0.6.0 tag. - runtime_ref: 75f6e67deac362bdcfc10d10629ddcf69c0e2615 # gateway v0.6.0 - event_name: ${{ github.event_name }} - event_action: ${{ github.event.action }} - repo: ${{ github.repository }} - pr_number: ${{ github.event.issue.number || github.event.pull_request.number }} - actor: ${{ github.event.sender.login }} - comment_body: ${{ github.event.comment.body }} - comment_id: ${{ github.event.comment.id }} - comment_in_reply_to: ${{ github.event.comment.in_reply_to_id }} - # installation_id intentionally omitted: as of gateway v0.4.4 the - # runtime resolves the App installation covering this repo from - # app_id/private_key, so a hardcoded (and easily wrong-org) id is no - # longer needed. - app_id: ${{ secrets.GATEWAY_APP_ID }} - private_key: ${{ secrets.GATEWAY_PRIVATE_KEY }} - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml deleted file mode 100644 index 75f659761..000000000 --- a/.github/workflows/govulncheck.yml +++ /dev/null @@ -1,109 +0,0 @@ -name: Vulnerability scan - -on: - workflow_dispatch: - schedule: - # Run weekly to catch newly published vulnerabilities even when the code - # does not change. - - cron: "0 9 * * 1" - pull_request: - paths: - - ".github/workflows/govulncheck.yml" - - ".github/actions/setup-go/action.yml" - - "Makefile" - - "make/release_flags.mk" - - "**/*.go" - - "**/go.mod" - - "**/go.sum" - push: - branches: - - "master" - paths: - - ".github/workflows/govulncheck.yml" - - ".github/actions/setup-go/action.yml" - - "Makefile" - - "make/release_flags.mk" - - "**/*.go" - - "**/go.mod" - - "**/go.sum" - merge_group: - branches: - - "master" - -permissions: - contents: read - -defaults: - run: - shell: bash - -env: - # If you change this please also update GO_VERSION in Makefile (then run - # `make lint` to see where else it needs to be updated as well). - GO_VERSION: 1.26.4 - -jobs: - govulncheck: - name: Scan release binaries - runs-on: ubuntu-latest - steps: - - name: Git checkout - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - - name: Setup Go ${{ env.GO_VERSION }} - uses: ./.github/actions/setup-go - with: - go-version: '${{ env.GO_VERSION }}' - key-prefix: govulncheck - use-build-cache: 'no' - - - name: Install govulncheck - run: go install golang.org/x/vuln/cmd/govulncheck@v1.3.0 - - - name: Build release binaries - run: make release-install - - - name: Run govulncheck - run: | - set +e - - gopath="$(go env GOPATH)" - final_exit_code=0 - advisory_findings=0 - - for binary in lnd lncli; do - output="govulncheck-${binary}.txt" - "${gopath}/bin/govulncheck" \ - -mode=binary \ - "${gopath}/bin/${binary}" 2>&1 | tee "${output}" - exit_code=${PIPESTATUS[0]} - - { - echo "### govulncheck ${binary}" - echo - echo '```' - sed -n '1,200p' "${output}" - echo '```' - } >> "$GITHUB_STEP_SUMMARY" - - if [ "$exit_code" -eq 3 ]; then - advisory_findings=1 - continue - fi - - if [ "$exit_code" -ne 0 ] && [ "$final_exit_code" -eq 0 ]; then - final_exit_code="$exit_code" - fi - done - - if [ "$advisory_findings" -eq 1 ]; then - echo "::warning title=govulncheck findings::govulncheck found vulnerabilities; see the job summary for details." - { - echo - echo "> govulncheck exited with code 3 for one or more release binaries. This job is advisory while the existing vulnerability baseline is remediated." - } >> "$GITHUB_STEP_SUMMARY" - fi - - exit "$final_exit_code" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 85553fca2..f7ec9f8e2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,8 +27,7 @@ defaults: shell: bash env: - # Accepts either a major image tag like "30" or a patch tag like "29.1". - BITCOIN_VERSION: "31" + BITCOIN_VERSION: "29" # TRANCHES defines the number of tranches used in the itests. TRANCHES: 16 @@ -41,7 +40,7 @@ env: # If you change this please also update GO_VERSION in Makefile (then run # `make lint` to see where else it needs to be updated as well). - GO_VERSION: 1.26.4 + GO_VERSION: 1.25.5 jobs: static-checks: @@ -177,7 +176,7 @@ jobs: - name: amd64 sys: darwin-amd64 freebsd-amd64 linux-amd64 netbsd-amd64 openbsd-amd64 windows-amd64 - name: arm - sys: darwin-arm64 freebsd-arm linux-armv6 linux-armv7 linux-arm64 windows-arm64 + sys: darwin-arm64 freebsd-arm linux-armv6 linux-armv7 linux-arm64 windows-arm steps: - name: Git checkout uses: actions/checkout@v5 @@ -213,8 +212,6 @@ jobs: - unit tags="test_db_sqlite" - unit tags="test_db_postgres" - unit-race - - unit-race tags="test_db_sqlite" - - unit-race tags="test_db_postgres" - unit-module steps: @@ -281,8 +278,6 @@ jobs: args: backend=btcd cover=1 - name: bitcoind args: backend=bitcoind cover=1 - - name: bitcoind-miner - args: backend=bitcoind minerbackend=bitcoind cover=1 - name: bitcoind-notxindex args: backend="bitcoind notxindex" - name: neutrino @@ -551,7 +546,7 @@ jobs: fail-fast: false matrix: pinned_dep: - - google.golang.org/grpc v1.79.3 + - google.golang.org/grpc v1.59.0 - github.com/golang/protobuf v1.5.4 steps: @@ -605,14 +600,6 @@ jobs: - name: 🛡️ Backwards compatibility test run: make backwards-compat-test - - name: 📋 Upload node logs on failure - if: failure() - uses: actions/upload-artifact@v4 - with: - name: bw-compat-logs - path: scripts/bw-compatibility-test/logs/ - retention-days: 7 - ######################################### # Auto Cache Cleanup on Pull Requests ######################################### diff --git a/.github/workflows/pr-severity.yml b/.github/workflows/pr-severity.yml deleted file mode 100644 index e4c14ff22..000000000 --- a/.github/workflows/pr-severity.yml +++ /dev/null @@ -1,313 +0,0 @@ -name: PR Severity Classification - -on: - # Use pull_request_target so the workflow runs on fork PRs with the base - # repository's workflow definition. The classify job below reads PR metadata - # with a read-only token and never checks out or executes PR code; the write - # scope needed to apply the label lives in a separate, model-free job. - pull_request_target: - types: [opened, synchronize, labeled] - -# Default the whole workflow to read-only. Each job opts into exactly the scope -# it needs: classify stays read-only (untrusted PR metadata reaches the model, -# so it must not hold a write token), apply takes pull-requests: write but runs -# no model. -permissions: - contents: read - -concurrency: - group: pr-severity-${{ github.event.pull_request.number }} - cancel-in-progress: true - -jobs: - classify: - name: Classify PR Severity - runs-on: ubuntu-latest - # Cap the model run: it fires on every pull_request_target synchronize with - # attacker-controllable input, so bound its runner-minute/token cost rather - # than inheriting GitHub's 6h default. Mirrors the dedupe workflow. - timeout-minutes: 10 - # Read-only: the classifier only inspects PR metadata via the GitHub API. - permissions: - contents: read - pull-requests: read - # Skip if PR has skip-severity-check label. - # For labeled events, only run if 'reclassify' label was added. - if: | - !contains(github.event.pull_request.labels.*.name, 'skip-severity-check') && - (github.event.action != 'labeled' || github.event.label.name == 'reclassify') - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - # Don't leave the job token in .git/config: nothing here needs a - # persisted git credential, and the classifier runs on untrusted - # fork-PR input. - persist-credentials: false - - - name: Classify PR with Claude - # Pinned to a full commit SHA rather than the mutable @v1 tag: this step - # runs on pull_request_target with CLAUDE_CODE_OAUTH_TOKEN and - # GITHUB_TOKEN in-process and is reachable by any fork author, so a - # repointed tag would run attacker-controlled action code with those - # secrets present. Bump deliberately when updating. - uses: anthropics/claude-code-action@ba0aafd4308cbba7165f9f2cdb0cfbed5a3c99ce # v1 - env: - # gh (invoked by the Bash(gh pr view) tool) authenticates from - # GH_TOKEN; set it explicitly so classification doesn't depend on the - # action propagating its github_token input into the tool environment, - # matching the dedupe find-duplicates step. - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - github_token: ${{ secrets.GITHUB_TOKEN }} - - # Accept any PR author: this job holds only a read-only token, reads - # PR metadata via the API, and writes its verdict to a file. The - # privileged label/comment step runs separately without the model. - # "*" is safe ONLY while this job stays read-only. Before granting it - # a write token or a mutating tool (a write-capable gh subcommand, a - # Bash mutation), replace "*" with an explicit allowlist — otherwise - # any fork author's PR text would steer a privileged model. - allowed_non_write_users: "*" - - # Pin the model so the severity decision (which gates the label the - # apply job applies) is reproducible from the workflow file and does - # not drift on an action-default change. - model: claude-sonnet-5 - - # The classifier only needs to read PR data and record its verdict to - # the workspace. It has no write-capable gh tools. - # - # Security note: because this runs on pull_request_target, the OAuth - # token above is present in-process while the model reads untrusted - # fork-PR text. The read-only github_token bounds what the model can - # WRITE via the API, but the in-process token's protection rests on - # this allowlist staying minimal — read-only `gh pr view` plus - # `Write`, with no network- or shell-mutating tool a prompt injection - # could use to exfiltrate it. Keep it that way: do not add `Bash` - # verbs beyond `gh pr view`, and never add a tool that can make - # outbound requests. (A stricter design would drop Bash entirely and - # pre-fetch PR metadata via the API in a separate step.) - claude_args: --allowedTools "Bash(gh pr view:*)" "Write" - - prompt: | - You are a PR severity classifier for the lnd (Lightning Network Daemon) repository. - - ## Tool Constraints - - You ONLY have access to: - - `gh pr view` - to read PR metadata - - `Write` - to record your verdict to files - - You do NOT have access to `gh api`, `gh label`, `gh pr edit`, - `gh pr comment`, or any other command. Do not attempt to use them. - You do NOT apply labels or post comments yourself. A separate, - deterministic step reads the files you write and applies the label - and comment. Your job is only to classify and record the result. - - ## Your Task - - Analyze PR #${{ github.event.pull_request.number }} and: - 1. Determine its severity level based on the files changed - 2. Record the severity, whether a comment should be posted, and the - comment body, to files (see "Output" below). - - ## Severity Levels - - **CRITICAL** (severity-critical) - Requires expert review: - - lnwallet/* - Wallet operations, channel funding, signing, commitment transactions - - htlcswitch/* - HTLC forwarding, payment routing state machine - - contractcourt/* - On-chain dispute resolution, breach handling - - sweep/* - Output sweeping, fund recovery, fee bumping - - peer/*, brontide/* - Encrypted peer connections, Noise protocol - - keychain/* - Private key derivation and management - - input/* - Script signing, witness generation, MuSig2 - - channeldb/* - Channel state persistence, database migrations - - funding/* - Channel funding workflow coordination - - lnwire/* - Lightning wire protocol messages - - server.go, rpcserver.go - Core server coordination - - **HIGH** (severity-high) - Requires knowledgeable engineer: - - routing/* - Payment pathfinding algorithms - - invoices/* - Invoice management and settlement - - discovery/* - Gossip protocol - - graph/* - Network graph maintenance - - watchtower/* - Breach remediation - - feature/* - Feature bit management - - lnrpc/* - RPC/API definitions - - macaroons/*, walletunlocker/*, cert/* - Auth/security - - chainntnfs/*, chanacceptor/*, protofsm/*, sqldb/* - - **MEDIUM** (severity-medium) - Focused review: - - cmd/* - CLI client commands (do NOT inherit severity from server-side packages with similar names) - - payments/*, autopilot/*, lncfg/*, chanfitness/* - - netann/*, kvdb/*, chanbackup/*, aezeed/*, tor/* - - zpay32/*, tlv/*, fn/*, record/*, amp/* - - *.proto files (API changes) - - Other Go files not categorized above - - **LOW** (severity-low) - Best-effort review: - - docs/*, release-notes/*, *.md files - - scripts/*, tools/*, contrib/*, make/*, docker/* - - itest/*, lntest/*, *_test.go (test-only changes) - - .github/* (CI/CD configuration) - - ## Classification Rules - - 1. The HIGHEST severity file determines the PR severity - 2. Classify files by their actual package path, NOT by filename keywords. - Files under cmd/* are CLI client code and should always be MEDIUM, - even if the filename contains a server-side package name (e.g. - cmd/commands/cmd_walletunlocker.go is MEDIUM, not HIGH). - 3. Bump severity UP one level if: - - PR touches >20 files (excluding tests and auto-generated files) - - PR has >500 lines changed (excluding tests and auto-generated files) - - PR touches multiple distinct critical packages - 4. Check for override labels first (severity-override-*). If present, respect the override. - 5. Database migrations (channeldb/migration*, sqldb/*, wtdb/*) are always CRITICAL - - ## Files to Exclude from Line/File Counting - When calculating file count and lines changed for severity bumps, exclude: - - Test files: *_test.go, itest/*, lntest/* - - Auto-generated files: *.pb.go, *.pb.gw.go, *.pb.json.go, *.sql.go, *_generated.go - - Mock files: mock_*.go, *_mock.go - - ## Steps - - 1. Read the current labels AND comments to detect overrides and prior - bot activity: - ``` - gh pr view ${{ github.event.pull_request.number }} --json labels,comments - ``` - Note which `severity-*` label (if any) is currently applied. This - is the "previous severity". Look for the HTML marker - `` in comment bodies to tell whether the - bot has commented before. - - 2. If an override label exists (severity-override-*), use that level - and skip classification. - - 3. Get the list of changed files: - ``` - gh pr view ${{ github.event.pull_request.number }} --json files,additions,deletions - ``` - - 4. Classify each file and determine the new overall severity. - - 5. **Decide whether a comment should be posted.** Set should_comment - to "true" only if EITHER: - - The bot has NOT commented before (no existing comment with - ``), OR - - The newly determined severity is DIFFERENT from the previous - severity label. - - Otherwise set should_comment to "false" (the label may still be - updated by the apply step, but no new comment is posted). - - ## Output - - Record your verdict by writing these files in the current working - directory (the repository root). Do NOT apply labels or comment - yourself. - - 1. `severity.txt` - exactly one lowercase word, one of: - `critical`, `high`, `medium`, `low`. Nothing else. - - 2. `should_comment.txt` - exactly `true` or `false`. - - 3. `comment.md` - only if should_comment is `true`. The full comment - markdown, in this format: - - If this is a severity CHANGE (previous label existed but differs), - prepend: `> ⚠️ Severity changed: **** → **** (files changed since last classification)` - - ```markdown - ## PR Severity: **** - - > | files | lines changed - -
- 🔴 Critical (N files) - - - `path/to/file1.go` - reason - - `path/to/file2.go` - reason - -
- - [repeat for other tiers if applicable] - - ### Analysis - - - - --- - To override, add a `severity-override-{critical,high,medium,low}` label. - - ``` - - If should_comment is `false`, do not create `comment.md`. - - Keep the comment concise and factual: it is posted verbatim under - the bot's identity, and the apply step defangs any `@`-mentions - and links, so do not rely on them. - - ## Emoji Mapping - - critical: 🔴 - - high: 🟠 - - medium: 🟡 - - low: 🟢 - - - name: Upload classification result - uses: actions/upload-artifact@v4 - with: - name: pr-severity-result - path: | - severity.txt - should_comment.txt - comment.md - if-no-files-found: warn - retention-days: 1 - - apply: - name: Apply Severity Label - runs-on: ubuntu-latest - needs: classify - timeout-minutes: 5 - # Write scope lives here, in a job that runs no model. The only inputs are - # the PR number from the trusted event payload and the classifier's files, - # which are strictly validated before use. - permissions: - contents: read - pull-requests: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - # Needed for scripts/apply-pr-severity.sh; no persisted git credential - # is required here. - persist-credentials: false - - - name: Download classification result - uses: actions/download-artifact@v4 - # classify uploads with if-no-files-found: warn, so if the model writes - # no verdict at all (timeout, refusal) no artifact exists and - # download-artifact would otherwise hard-fail the job. Tolerate a - # missing artifact so the no-op guard in the next step is reachable. - continue-on-error: true - with: - name: pr-severity-result - path: result - - - name: Apply label and comment - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number }} - REPO: ${{ github.repository }} - # Validate the severity, reconcile the label, and sanitize + post the - # model-authored comment. The logic lives in a checked-in script so the - # untrusted-comment sanitizer is unit-tested - # (scripts/apply-pr-severity_test.sh), mirroring how the dedupe workflow - # delegates to scripts/comment-on-duplicates.sh. - run: ./scripts/apply-pr-severity.sh result diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6791432e7..012a71628 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -12,7 +12,7 @@ defaults: env: # If you change this please also update GO_VERSION in Makefile (then run # `make lint` to see where else it needs to be updated as well). - GO_VERSION: 1.26.4 + GO_VERSION: 1.25.5 jobs: ######################## @@ -40,7 +40,7 @@ jobs: run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - name: build release for all architectures - run: make release tag=${{ env.RELEASE_VERSION }} + run: SKIP_VERSION_CHECK=1 make release tag=${{ env.RELEASE_VERSION }} - name: Create Release uses: lightninglabs/gh-actions/action-gh-release@c7149b6a7818d1c39b36b69e727569897b6f2c5a @@ -81,11 +81,10 @@ jobs: ## Verifying the Release Timestamp - From this new version onwards, in addition to time-stamping the _git tag_ with [OpenTimestamps](https://opentimestamps.org/), we'll also now timestamp the manifest file along with the `roasbeef` release signature. For final releases, and for release candidates when these optional artifacts are uploaded, timestamp proof files are included along with the rest of our release artifacts: `manifest-${{ env.RELEASE_VERSION }}.txt.ots` and `manifest-roasbeef-${{ env.RELEASE_VERSION }}.sig.ots`. + From this new version onwards, in addition time-stamping the _git tag_ with [OpenTimestamps](https://opentimestamps.org/), we'll also now timestamp the manifest file along with its signature. Two new files are now included along with the rest of our release artifacts: ` manifest-roasbeef-${{ env.RELEASE_VERSION }}.txt.asc.ots`. Assuming you have the opentimestamps client installed locally, the timestamps can be verified with the following commands: ``` - ots verify manifest-${{ env.RELEASE_VERSION }}.txt.ots -f manifest-${{ env.RELEASE_VERSION }}.txt ots verify manifest-roasbeef-${{ env.RELEASE_VERSION }}.sig.ots -f manifest-roasbeef-${{ env.RELEASE_VERSION }}.sig ``` diff --git a/.github/workflows/verify-release.yaml b/.github/workflows/verify-release.yaml deleted file mode 100644 index 418dfc0d4..000000000 --- a/.github/workflows/verify-release.yaml +++ /dev/null @@ -1,74 +0,0 @@ -name: Verify release - -on: - release: - types: [published] - workflow_dispatch: - inputs: - version: - description: 'Release version tag (e.g. v0.20.1-beta)' - required: true - -permissions: - contents: write - -jobs: - verify-release: - name: Verify release signatures and binaries - runs-on: ubuntu-latest - steps: - - name: git checkout - uses: actions/checkout@v4 - with: - ref: ${{ inputs.version || github.sha }} - - - name: Check final release OpenTimestamps asset - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - VERSION: ${{ inputs.version || github.event.release.tag_name }} - run: | - set -euo pipefail - - if [[ "${VERSION}" =~ \.rc[0-9]+$ ]]; then - echo "Release candidate ${VERSION}; skipping OpenTimestamps asset check." - exit 0 - fi - - REQUIRED_MANIFEST_OTS="manifest-${VERSION}.txt.ots" - REQUIRED_SIG="manifest-roasbeef-${VERSION}.sig" - REQUIRED_SIG_OTS="${REQUIRED_SIG}.ots" - - ASSETS="$(gh release view "${VERSION}" \ - --repo "${{ github.repository }}" \ - --json assets \ - --jq '.assets[].name')" - - for asset in "${REQUIRED_MANIFEST_OTS}" "${REQUIRED_SIG}" "${REQUIRED_SIG_OTS}"; do - if ! grep -Fxq "${asset}" <<< "${ASSETS}"; then - echo "ERROR: Final release ${VERSION} is missing ${asset}." - exit 1 - fi - done - - echo "Found required release timestamp artifacts:" - echo " ${REQUIRED_MANIFEST_OTS}" - echo " ${REQUIRED_SIG}" - echo " ${REQUIRED_SIG_OTS}" - - - name: Verify release - env: - VERSION: ${{ inputs.version || github.event.release.tag_name }} - run: | - docker run --rm --entrypoint="" \ - lightninglabs/lnd:${VERSION} \ - /verify-install.sh ${VERSION} - - - name: Set release back to draft on failure - if: failure() - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - VERSION: ${{ inputs.version || github.event.release.tag_name }} - run: | - gh release edit ${VERSION} \ - --repo ${{ github.repository }} \ - --draft diff --git a/.gitignore b/.gitignore index 9bfd1442a..11c67fe65 100644 --- a/.gitignore +++ b/.gitignore @@ -40,9 +40,6 @@ itest/btcd-itest itest/.logs-* itest/cover -# Local lntest miner logs (dev artifacts) -lntest/miner/*.log - cmd/cmd *.key *.hex @@ -83,7 +80,6 @@ coverage.txt # Release build directory (to avoid build.vcs.modified Golang build tag to be # set to true by having untracked files in the working directory). /lnd-*/ -/.worktrees/ .aider* diff --git a/.golangci.yml b/.golangci.yml index e1dc06aeb..bcbf5e026 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,9 +1,7 @@ -version: "2" - run: # If you change this please also update GO_VERSION in Makefile (then run # `make lint` to see where else it needs to be updated as well). - go: "1.26.4" + go: "1.25.5" # Abort after 10 minutes. timeout: 10m @@ -23,8 +21,95 @@ run: - kvdb_sqlite - integration +linters-settings: + custom: + ll: + type: "module" + description: "Custom lll linter with 'S' log line exclusion." + settings: + # Max line length, lines longer will be reported. + line-length: 80 + # Tab width in spaces. + tab-width: 8 + # The regex that we will use to detect the start of an `S` log line. + log-regex: "^\\s*.*(L|l)og\\.(Info|Debug|Trace|Warn|Error|Critical)S\\(" + + errorlint: + # Check for incorrect fmt.Errorf error wrapping. + errorf: true + + gofmt: + # simplify code: gofmt with `-s` option, true by default + simplify: true + + tagliatelle: + case: + rules: + json: snake + + whitespace: + multi-func: true + multi-if: true + + gosec: + excludes: + - G402 # Look for bad TLS connection settings. + - G306 # Poor file permissions used when writing to a new file. + - G601 # Implicit memory aliasing in for loop. + - G115 # Integer overflow in conversion. + + staticcheck: + checks: ["-SA1019"] + + funlen: + # Checks the number of lines in a function. + # If lower than 0, disable the check. + lines: 200 + # Checks the number of statements in a function. + statements: 80 + + dupl: + # Tokens count to trigger issue. + threshold: 200 + + nestif: + # Minimal complexity of if statements to report. + min-complexity: 10 + + nlreturn: + # Size of the block (including return statement that is still "OK") + # so no return split required. + block-size: 3 + + gomnd: + # List of numbers to exclude from analysis. + # The numbers should be written as string. + # Values always ignored: "1", "1.0", "0" and "0.0" + # Default: [] + ignored-numbers: + - '0666' + - '0755' + + # List of function patterns to exclude from analysis. + # Values always ignored: `time.Date` + # Default: [] + ignored-functions: + - 'math.*' + - 'strconv.ParseInt' + - 'errors.Wrap' + + gomoddirectives: + replace-local: true + replace-allow-list: + # See go.mod for the explanation why these are needed. + - github.com/ulikunitz/xz + - github.com/gogo/protobuf + - google.golang.org/protobuf + - github.com/lightningnetwork/lnd/sqldb + + linters: - default: all + enable-all: true disable: # We instead use our own custom line length linter called `ll` since # then we can ignore log lines. @@ -46,33 +131,25 @@ linters: # Init functions are used by loggers throughout the codebase. - gochecknoinits - # contextcheck requires threading context.Context through many existing - # function signatures (including test harnesses), so we leave it off for - # now. + # Deprecated linters. See https://golangci-lint.run/usage/linters/. + - bodyclose - contextcheck - - # tparallel requires adding t.Parallel() to a large number of existing - # subtests, which can surface shared-state races. Disabled until we can - # address it carefully. - - tparallel - - # unparam has a sizeable backlog of unused parameters to clean up before it - # can be enabled. - - unparam - - # nilerr is too noisy for our code base: most reports are intentional error - # swallowing (documented with comments) or false positives where a boolean - # check is mistaken for an error check. - nilerr - - # noctx would only flag a couple of interface methods and a test helper that - # have no context to thread through, so it adds little value for now. - noctx + - rowserrcheck + - sqlclosecheck + - tparallel + - unparam + - wastedassign - # Disable whitespace linters as it has conflict rules against our + # Disable gofumpt as it has weird behavior regarding formatting multiple + # lines for a function which is in conflict with our contribution + # guidelines. See https://github.com/mvdan/gofumpt/issues/235. + - gofumpt + + # Disable whitespace linter as it has conflict rules against our # contribution guidelines. - wsl - - wsl_v5 # Allow using default empty values. - exhaustruct @@ -126,6 +203,7 @@ linters: - testifylint - perfsprint - inamedparam + - copyloopvar - tagalign - protogetter - revive @@ -133,216 +211,88 @@ linters: - gosmopolitan - intrange - goconst - - # Disable function order linter because we structure exported and unexported - # functions differently. - - funcorder - - # Disable noinlineerr linter because we use it to inline errors. - - noinlineerr - - # Disable embeddedstructfieldcheck linter because we use it to align - # structs. Because sometimes we have atomic fields that need to be aligned - # with means we need to assure that the field is at the beginning of the - # struct. - - embeddedstructfieldcheck - - settings: - dupl: - # Tokens count to trigger issue. - threshold: 200 - - errorlint: - # Check for incorrect fmt.Errorf error wrapping. - errorf: true - - funlen: - # Checks the number of lines in a function. - # If lower than 0, disable the check. - lines: 200 - # Checks the number of statements in a function. - statements: 80 - - gomoddirectives: - # See project's go.mod for the explanation why these are needed. - replace-allow-list: - - github.com/ulikunitz/xz - - github.com/gogo/protobuf - - google.golang.org/protobuf - - github.com/lightningnetwork/lnd/sqldb - - github.com/lightningnetwork/lightning-onion - replace-local: true - - gosec: - excludes: - - G402 # Look for bad TLS connection settings. - - G306 # Poor file permissions used when writing to a new file. - - G601 # Implicit memory aliasing in for loop. - - G115 # Integer overflow in conversion. - - nestif: - # Minimal complexity of if statements to report. - min-complexity: 10 - - nlreturn: - # Size of the block (including return statement that is still "OK") - # so no return split required. - block-size: 3 - - staticcheck: - checks: - - -SA1019 - - tagliatelle: - case: - rules: - json: snake - - usetesting: - context-background: true - - whitespace: - multi-if: true - multi-func: true - - custom: - ll: - type: module - description: Custom lll linter with 'S' log line exclusion. - settings: - # Max line length, lines longer will be reported. - line-length: 80 - # The regex that we will use to detect the start of an `S` log line. - log-regex: ^\s*.*(L|l)og\.(Info|Debug|Trace|Warn|Error|Critical)S\( - # Tab width in spaces. - tab-width: 8 - - exclusions: - # Mode of the generated files analysis. - # - # - `strict`: sources are excluded by strictly following the Go generated file convention. - # Source files that have lines matching only the following regular expression will be excluded: `^// Code generated .* DO NOT EDIT\.$` - # This line must appear before the first non-comment, non-blank text in the file. - # https://go.dev/s/generatedcode - # - `lax`: sources are excluded if they contain lines like `autogenerated file`, `code generated`, `do not edit`, etc. - # - `disable`: disable the generated files exclusion. - # - # Default: strict - generated: lax - - presets: - - comments - - common-false-positives - - legacy - - std-error-handling - - rules: - - linters: - # Allow duplications in tests so it's easier to follow a single unit - - dupl - - funlen - - gosec - - revive - # Exclude gosec from running for tests so that tests with weak - # randomness (math/rand) will pass the linter. - path: _test\.go - - - linters: - # forcetypeassert is skipped for the mock because the test would fail - # if the returned value doesn't match the type, so there's no need to - # check the convert. - - forcetypeassert - - revive - path: mock* - - - linters: - - funlen - - gosec - path: test* - - # Allow duplicated code and fmt.Printf() in DB migrations. - - linters: - - dupl - - forbidigo - - godot - path: channeldb/migration* - - # Allow duplicated code and fmt.Printf() in DB migration tests. - - linters: - - dupl - - forbidigo - - godot - path: channeldb/migtest - - # Allow fmt.Printf() in commands. - - linters: - - forbidigo - path: cmd/commands/* - - # Allow fmt.Printf() in config parsing. - - linters: - - forbidigo - path: config\.go - - linters: - - forbidigo - path: lnd\.go - - - linters: - # forcetypeassert is skipped for the mock because the test would fail - # if the returned value doesn't match the type, so there's no need to - # check the convert. - - forcetypeassert - path: lnmock/* - - - linters: - # forcetypeassert is skipped for the mock because the test would fail - # if the returned value doesn't match the type, so there's no need to - # check the convert. - - forcetypeassert - path: mock* - - # Skip autogenerated files for mobile and gRPC as well as copied code for - # internal use. - paths: - - third_party$ - - builtin$ - - examples$ - - "mobile\\/.*generated\\.go" - - "\\.pb\\.go$" - - "\\.pb\\.gw\\.go$" - - "internal\\/musig2v040" - - channeldb/migration_01_to_11 - - channeldb/migration/lnwire21 - - payments/db/migration1/lnwire - - payments/db/migration1/record + # Deprecated linters that have been replaced by newer ones. + - tenv issues: # Only show newly introduced problems. new-from-rev: 03eab4db64540aa5f789c617793e4459f4ba9e78 -formatters: - enable: - - gci - - gofmt - - goimports + # Skip autogenerated files for mobile and gRPC as well as copied code for + # internal use. + skip-files: + - "mobile\\/.*generated\\.go" + - "\\.pb\\.go$" + - "\\.pb\\.gw\\.go$" + - "internal\\/musig2v040" - settings: - gofmt: - # simplify code: gofmt with `-s` option, true by default - simplify: true + skip-dirs: + - channeldb/migration_01_to_11 + - channeldb/migration/lnwire21 - exclusions: - generated: lax - # Skip autogenerated files for mobile and gRPC as well as copied code for - # internal use. - paths: - - third_party$ - - builtin$ - - examples$ - - "mobile\\/.*generated\\.go" - - "\\.pb\\.go$" - - "\\.pb\\.gw\\.go$" - - "internal\\/musig2v040" - - channeldb/migration_01_to_11 - - channeldb/migration/lnwire21 + exclude-rules: + # Exclude gosec from running for tests so that tests with weak randomness + # (math/rand) will pass the linter. + - path: _test\.go + linters: + - gosec + - funlen + - revive + # Allow duplications in tests so it's easier to follow a single unit + # test. + - dupl + + - path: mock* + linters: + - revive + # forcetypeassert is skipped for the mock because the test would fail + # if the returned value doesn't match the type, so there's no need to + # check the convert. + - forcetypeassert + + - path: test* + linters: + - gosec + - funlen + + # Allow duplicated code and fmt.Printf() in DB migrations. + - path: channeldb/migration* + linters: + - dupl + - forbidigo + - godot + + # Allow duplicated code and fmt.Printf() in DB migration tests. + - path: channeldb/migtest + linters: + - dupl + - forbidigo + - godot + + # Allow fmt.Printf() in commands. + - path: cmd/commands/* + linters: + - forbidigo + + # Allow fmt.Printf() in config parsing. + - path: config\.go + linters: + - forbidigo + - path: lnd\.go + linters: + - forbidigo + + - path: lnmock/* + linters: + # forcetypeassert is skipped for the mock because the test would fail + # if the returned value doesn't match the type, so there's no need to + # check the convert. + - forcetypeassert + + - path: mock* + linters: + # forcetypeassert is skipped for the mock because the test would fail + # if the returned value doesn't match the type, so there's no need to + # check the convert. + - forcetypeassert diff --git a/Dockerfile b/Dockerfile index cccb2aeb4..a152d9184 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # If you change this please also update GO_VERSION in Makefile (then run # `make lint` to see where else it needs to be updated as well). -FROM golang:1.26.4-alpine as builder +FROM golang:1.25.5-alpine as builder # Force Go to use the cgo based DNS resolver. This is required to ensure DNS # queries required to connect to linked containers succeed. @@ -29,13 +29,13 @@ FROM alpine as final VOLUME /root/.lnd # Add utilities for quality of life and SSL-related reasons. We also require -# wget and gpg for the signature verification script. +# curl and gpg for the signature verification script. RUN apk --no-cache add \ bash \ jq \ ca-certificates \ gnupg \ - wget + curl # Copy the binaries from the builder image. COPY --from=builder /go/bin/lncli /bin/ diff --git a/Makefile b/Makefile index 7dc53d94e..6d0faf00b 100644 --- a/Makefile +++ b/Makefile @@ -1,20 +1,16 @@ PKG := github.com/lightningnetwork/lnd MOBILE_PKG := $(PKG)/mobile TOOLS_DIR := tools -TOOLS_MOD := $(TOOLS_DIR)/go.mod GOCC ?= go PREFIX ?= /usr/local -GOTOOL := GOWORK=off $(GOCC) tool -modfile=$(TOOLS_MOD) - - BTCD_PKG := github.com/btcsuite/btcd GOIMPORTS_PKG := github.com/rinchsan/gosimports/cmd/gosimports -GOLINT_PKG := github.com/golangci/golangci-lint/v2/cmd/golangci-lint GO_BIN := ${GOPATH}/bin BTCD_BIN := $(GO_BIN)/btcd +GOIMPORTS_BIN := $(GO_BIN)/gosimports GOMOBILE_BIN := $(GO_BIN)/gomobile MOBILE_BUILD_DIR :=${GOPATH}/src/$(MOBILE_PKG)/build @@ -22,10 +18,6 @@ IOS_BUILD_DIR := $(MOBILE_BUILD_DIR)/ios IOS_BUILD := $(IOS_BUILD_DIR)/Lndmobile.xcframework ANDROID_BUILD_DIR := $(MOBILE_BUILD_DIR)/android ANDROID_BUILD := $(ANDROID_BUILD_DIR)/Lndmobile.aar -# For Android, set max page size to 16KB to support devices using 16KB memory pages. -# Reference: https://developer.android.com/guide/practices/page-sizes -ANDROID_MAX_PAGE_SIZE := 16384 -ANDROID_EXTLDFLAGS := -extldflags '-Wl,-z,max-page-size=$(ANDROID_MAX_PAGE_SIZE)' COMMIT := $(shell git describe --tags --dirty) @@ -36,7 +28,7 @@ ACTIVE_GO_VERSION_MINOR := $(shell echo $(ACTIVE_GO_VERSION) | cut -d. -f2) # GO_VERSION is the Go version used for the release build, docker files, and # GitHub Actions. This is the reference version for the project. All other Go # versions are checked against this version. -GO_VERSION = 1.26.4 +GO_VERSION = 1.25.5 GOBUILD := $(GOCC) build -v GOINSTALL := $(GOCC) install -v @@ -72,41 +64,12 @@ ifneq ($(workers),) LINT_WORKERS = --concurrency=$(workers) endif -# Docker cache mounting strategy: -# - CI (GitHub Actions): Use bind mounts to host paths that GA caches persist. -# - Local: Use Docker named volumes (much faster on macOS/Windows due to -# avoiding slow host-syncing overhead). -# Paths inside container must match GOCACHE/GOMODCACHE in tools/Dockerfile. -ifdef CI -# CI mode: bind mount to host paths that GitHub Actions caches. -DOCKER_TOOLS_BASE = docker run \ +DOCKER_TOOLS = docker run \ --rm \ - -v $${HOME}/.cache/go-build:/tmp/build/.cache \ - -v $${HOME}/go/pkg/mod:/tmp/build/.modcache \ - -v $${HOME}/.cache/golangci-lint:/root/.cache/golangci-lint \ - -v $$(pwd):/build -DOCKER_TOOLS = $(DOCKER_TOOLS_BASE) lnd-tools -DOCKER_TOOLS_LINT = $(DOCKER_TOOLS) -else -# Local mode: Docker named volumes for fast macOS/Windows performance. -# Detect if we're in a git worktree. Use git rev-parse --git-common-dir to get -# the path to the main git directory for the linter's diff processor to work -# correctly with the new-from-rev setting. -GIT_COMMON_DIR := $(shell \ - common_dir="$$(git rev-parse --git-common-dir 2>/dev/null)"; \ - if [ "$$common_dir" != ".git" ] && [ -n "$$common_dir" ]; then \ - echo "$$common_dir"; \ - fi) -GIT_VOLUME := $(if $(GIT_COMMON_DIR),-v "$(GIT_COMMON_DIR):$(GIT_COMMON_DIR):ro",) -DOCKER_TOOLS_BASE = docker run \ - --rm \ - -v lnd-go-build-cache:/tmp/build/.cache \ - -v lnd-go-mod-cache:/tmp/build/.modcache \ - -v lnd-go-lint-cache:/root/.cache/golangci-lint \ - -v $$(pwd):/build -DOCKER_TOOLS = $(DOCKER_TOOLS_BASE) lnd-tools -DOCKER_TOOLS_LINT = $(DOCKER_TOOLS_BASE) $(GIT_VOLUME) lnd-tools -endif + -v $(shell bash -c "$(GOCC) env GOCACHE || (mkdir -p /tmp/go-cache; echo /tmp/go-cache)"):/tmp/build/.cache \ + -v $(shell bash -c "$(GOCC) env GOMODCACHE || (mkdir -p /tmp/go-modcache; echo /tmp/go-modcache)"):/tmp/build/.modcache \ + -v $(shell bash -c "mkdir -p /tmp/go-lint-cache; echo /tmp/go-lint-cache"):/root/.cache/golangci-lint \ + -v $$(pwd):/build lnd-tools GREEN := "\\033[0;32m" NC := "\\033[0m" @@ -125,6 +88,10 @@ $(BTCD_BIN): @$(call print, "Installing btcd.") cd $(TOOLS_DIR); $(GOCC) install -trimpath $(BTCD_PKG) +$(GOIMPORTS_BIN): + @$(call print, "Installing goimports.") + cd $(TOOLS_DIR); $(GOCC) install -trimpath $(GOIMPORTS_PKG) + # ============ # INSTALLATION # ============ @@ -199,11 +166,7 @@ release: clean-mobile ./scripts/release.sh build-release "$(VERSION_TAG)" "$(BUILD_SYSTEM)" "$(RELEASE_TAGS)" "$(RELEASE_LDFLAGS)" "$(GO_VERSION)" #? docker-release: Same as release but within a docker container to support reproducible builds on BSD/MacOS platforms -docker-release-cache: - $(call check_docker_release_cache,$(DOCKER_RELEASE_GOCACHE)) - $(call check_docker_release_cache,$(DOCKER_RELEASE_GOMODCACHE)) - -docker-release: docker-release-cache +docker-release: @$(call print, "Building release helper docker image.") if [ "$(tag)" = "" ]; then echo "Must specify tag=!"; exit 1; fi @@ -352,9 +315,9 @@ fuzz: # ========= #? fmt: Format source code and fix imports -fmt: +fmt: $(GOIMPORTS_BIN) @$(call print, "Fixing imports.") - $(GOTOOL) $(GOIMPORTS_PKG) -w $(GOFILES_NOVENDOR) + gosimports -w $(GOFILES_NOVENDOR) @$(call print, "Formatting source.") gofmt -l -w -s $(GOFILES_NOVENDOR) @@ -379,28 +342,10 @@ check-go-version: check-go-version-dockerfile check-go-version-yaml #? lint-source: Run static code analysis lint-source: docker-tools @$(call print, "Linting source.") - $(DOCKER_TOOLS_LINT) custom-gcl run -v $(LINT_WORKERS) - -#? lint-config-check: Verify that the lint config is up to date -# We use the official linter here not our custom one because for checking the -# config file it does not matter. -lint-config-check: - @$(call print, "Checking lint config is up to date.") - $(GOTOOL) $(GOLINT_PKG) config verify -v + $(DOCKER_TOOLS) custom-gcl run -v $(LINT_WORKERS) #? lint: Run static code analysis -lint: check-go-version lint-config-check lint-source - -#? build-native-linter: Build the custom golangci-lint binary natively -build-native-linter: - @$(call print, "Building custom linter natively.") - cd tools && CGO_ENABLED=0 $(GOCC) tool $(GOLINT_PKG) custom - -#? lint-native: Run static code analysis without Docker (faster on macOS) -lint-native: check-go-version lint-config-check build-native-linter - @$(call print, "Linting source (native).") - GOWORK=off ./tools/custom-gcl run -v $(LINT_WORKERS) \ - --new-from-rev=$$(git merge-base HEAD master) +lint: check-go-version lint-source #? protolint: Lint proto files using protolint protolint: @@ -502,7 +447,7 @@ macos: mobile-rpc android: mobile-rpc @$(call print, "Building Android library ($(ANDROID_BUILD)).") mkdir -p $(ANDROID_BUILD_DIR) - $(GOMOBILE_BIN) bind -target=android -androidapi 21 -tags="mobile $(DEV_TAGS) $(RPC_TAGS)" -ldflags "$(RELEASE_LDFLAGS) $(ANDROID_EXTLDFLAGS)" -v -o $(ANDROID_BUILD) $(MOBILE_PKG) + $(GOMOBILE_BIN) bind -target=android -androidapi 21 -tags="mobile $(DEV_TAGS) $(RPC_TAGS)" -ldflags "$(RELEASE_LDFLAGS)" -v -o $(ANDROID_BUILD) $(MOBILE_PKG) #? mobile: Build mobile RPC stubs and project templates for iOS and Android mobile: ios android @@ -520,11 +465,6 @@ clean-mobile: $(RM) -r mobile/build $(RM) mobile/*_generated.go -#? clean-docker-volumes: Remove Docker cache volumes used for local development -clean-docker-volumes: - @$(call print, "Removing Docker cache volumes.") - docker volume rm lnd-go-build-cache lnd-go-mod-cache lnd-go-lint-cache 2>/dev/null || true - .PHONY: all \ btcd \ default \ @@ -543,7 +483,6 @@ clean-docker-volumes: flake-unit \ fmt \ lint \ - lint-native \ list \ rpc \ rpc-format \ @@ -554,5 +493,4 @@ clean-docker-volumes: ios \ android \ mobile \ - clean \ - clean-docker-volumes + clean diff --git a/README.md b/README.md index e75f3dd31..091bb827b 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ any issues regarding security or privacy, please disclose the information responsibly by sending an email to security at lightning dot engineering, preferably encrypted using our designated PGP key (`91FE464CD75101DA6B6BAB60555C6465E5BCB3AF`) which can be found -[here](https://gist.githubusercontent.com/Roasbeef/6fb5b52886183239e4aa558f83d085d3/raw/1ecb328bbcf36f76ead67f08008f8db1da07e60e/security@lightning.engineering). +[here](https://gist.githubusercontent.com/Roasbeef/6fb5b52886183239e4aa558f83d085d3/raw/5fa96010af201628bcfa61e9309d9b13d23d220f/security@lightning.engineering). ## Further reading * [Step-by-step send payment guide with docker](https://github.com/lightningnetwork/lnd/tree/master/docker) diff --git a/SECURITY.md b/SECURITY.md index 45453f1a0..ea945bdba 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -8,4 +8,4 @@ The last major lnd release is to be considered the current support version. Give To report security issues, send an email to security@lightning.engineering (this list isn't to be used for support). -The following key can be used to communicate sensitive information: [`91FE 464C D751 01DA 6B6B  AB60 555C 6465 E5BC B3AF`](https://gist.githubusercontent.com/Roasbeef/6fb5b52886183239e4aa558f83d085d3/raw/1ecb328bbcf36f76ead67f08008f8db1da07e60e/security@lightning.engineering). +The following key can be used to communicate sensitive information: `91FE 464C D751 01DA 6B6B  AB60 555C 6465 E5BC B3AF`. diff --git a/actor/README.md b/actor/README.md deleted file mode 100644 index 0d138bb69..000000000 --- a/actor/README.md +++ /dev/null @@ -1,478 +0,0 @@ -# Actor Package - -## Introduction to Actors - -The actor model is a conceptual model for concurrent computation that treats -"actors" as the universal primitives of concurrent computation. Originating from -Carl Hewitt's work in the 1970s and popularized by languages like Erlang and -frameworks like Akka, actors provide a high-level abstraction for building -robust, concurrent, and distributed systems. - -At its core, an actor is an independent unit of computation that encapsulates: -- **State**: An actor can maintain private state that it alone can modify. -- **Behavior**: An actor defines how it reacts to messages it receives. -- **Mailbox**: Each actor has a mailbox to queue incoming messages. - -Actors communicate exclusively through asynchronous message passing. When an -actor receives a message, it can: -1. Send a finite number of messages to other actors. -2. Create a finite number of new actors. -3. Designate the behavior to be used for the next message it receives (which - can be the same behavior). - -Concurrency is managed by the actor system, allowing many actors to execute -concurrently without explicit lock management by the developer for actor state. -This model inherently promotes loose coupling, as actors do not share state and -interact only through messages. - -## Motivation for this Package - -In large, long-lived systems like `lnd`, managing complexity, concurrency, and -component lifecycles becomes increasingly challenging. This `actor` package is -introduced to address several key motivations: - -### Structured Message Passing - -To move away from direct, synchronous method calls between major components, -especially where concurrency or complex state interactions are involved. Message -passing encourages clearer, more auditable interactions and helps manage -concurrent access to component state. - -### Eliminating "God Structs" - -Over time, systems can develop large "god structs" that hold references to -numerous sub-systems. This leads to tight coupling, makes dependency management -difficult, and can obscure the flow of control and data. Actors, by -encapsulating state and behavior and interacting via messages, help break down -these monolithic structures into more manageable, independent units. - -### Decoupled Lifecycles - -Often, the lifecycle of a sub-system is unnecessarily tied to a parent system, -or access to a sub-system requires traversing through a central "manager" -object. Actors can have independent lifecycles managed by an actor system, -allowing for more granular control over starting, stopping, and restarting -components. - -An example of such interaction is when an RPC call needs to go through several -other structs to obtain a reference to a given sub-system, in order to make a -direct method call on that sub-system. - -With the model described in this document, the RPC server just needs to know -about what is effectively an _abstract address_ of that sub-system. It can then -use that to obtain something similar to a mailbox to do the method call. - -This allows for a more decoupled architecture, as the RPC server doesn't need to -know the exact "shape" of the method to call, just which message to send. -Refactors of the sub-system won't break the RPC server, as long as the message -(which can be constructed via a dedicated constructor) is the same. - ---- - -This package provides a foundational actor framework tailored for Go, enabling -developers to build components that are easier to reason about, test, and -maintain in a concurrent environment. - -## Core Concepts - -Let's explore the fundamental building blocks provided by this package. - -### Messages - -Actors communicate by sending and receiving messages. Any type that an actor -needs to process must implement the `actor.Message` interface. A simple way to -do this is by embedding `actor.BaseMessage`: - -```go -package mymodule - -import "github.com/lightningnetwork/lnd/actor" - -// MyRequest is a custom message type. -type MyRequest struct { - // Embed BaseMessage to satisfy the Message interface. - actor.BaseMessage - Data string -} - -// MessageType returns a string identifier for this message type. -func (m *MyRequest) MessageType() string { - return "MyRequest" -} - -// MyResponse might be a corresponding response type. -type MyResponse struct { - actor.BaseMessage - Reply string -} - -func (m *MyResponse) MessageType() string { - return "MyResponse" -} -``` -The `MessageType()` method provides a string representation of the message type, -which can be useful for debugging or routing. - - -### Actor Behavior - -The logic of an actor (how it responds to messages) is defined by its -`ActorBehavior`. This is an interface that you implement: - -```go -package actor - -// ActorBehavior defines the logic for how an actor processes incoming messages. -type ActorBehavior[M Message, R any] interface { - Receive(actorCtx context.Context, msg M) fn.Result[R] -} -``` -The `Receive` method passes in a caller context (useful for shutdown detection) -and the incoming message. It returns an `fn.Result[R]`, which can encapsulate -either a successful response of type `R` or an error. - -For simple cases, you can use `actor.FunctionBehavior` to adapt a Go function -into an `ActorBehavior`: - -```go -import ( - "context" - "fmt" - "github.com/lightningnetwork/lnd/actor" - "github.com/lightningnetwork/lnd/fn/v2" -) - -// myActorLogic defines the processing for MyRequest messages. -func myActorLogic(ctx context.Context, msg *MyRequest) fn.Result[*MyResponse] { - // In a real actor, you might interact with state or other services. - // The actor's context (ctx) can be checked for shutdown signals. - select { - case <-ctx.Done(): - return fn.Err[*MyResponse](errors.New("actor shutting down")) - default: - } - - response := &MyResponse{Reply: fmt.Sprintf("Processed: %s", msg.Data)} - return fn.Ok(response) -} - -// Create a behavior from the function. -behavior := actor.NewFunctionBehavior(myActorLogic) -``` - -For more complex cases, you can implement the `Receive` method on a new struct, -and pass that around directly. - -### Service Keys and Actor References: The Interaction Layer - -Direct interaction with an actor's internal state or its concrete struct is -discouraged. Instead, communication and discovery are managed through two key -abstractions: `ServiceKey` and `ActorRef`. These provide a layer of indirection, -promoting loose coupling and location transparency (though the current -implementation is in-process). - -#### `ServiceKey[M Message, R any]` - -A `ServiceKey` is a type-safe identifier used for registering actors that -provide a particular service and for discovering them later. The generic type -parameters `M` (the type of message the actor handles) and `R` (the type of -response the actor produces for `Ask` operations) ensure that you discover -actors compatible with the interactions you intend to perform. - -```go -// Define a service key for actors that handle MyRequest and produce MyResponse. -myServiceKey := actor.NewServiceKey[*MyRequest, *MyResponse]("my-custom-service") - -// Later, this key would be used with a Receptionist (part of an ActorSystem) -// to find ActorRefs for actors offering this service. -``` - -#### `ActorRef[M Message, R any]` - -An `ActorRef` is a lightweight, shareable reference to an actor. It's the -primary means by which you send messages to an actor. It is also generic over -the message type `M` and response type `R` that the target actor handles. - -You typically obtain an `ActorRef` by looking it up in a `Receptionist` using a -`ServiceKey` (covered later when discussing the `ActorSystem`), or directly from -an actor instance via its `.Ref()` method (e.g., `sampleActor.Ref()` if you have -the `Actor` instance). - -There are two main ways to send messages using an `ActorRef`: - -1. **Tell (Fire-and-Forget)**: Used for sending messages when you don't need a - direct reply. The call returns immediately after attempting to enqueue the - message. - - ```go - // Assuming 'actorRef' is an ActorRef[*MyRequest, *MyResponse] obtained for an actor. - requestMsg := &MyRequest{Data: "A fire-and-forget message"} - actorRef.Tell(context.Background(), requestMsg) - // The message is now in the actor's mailbox (or will be shortly). - ``` - The `context.Context` passed to `Tell` can be used to cancel the send - operation if, for example, the actor's mailbox is full and the send would - block for too long. - -2. **Ask (Request-Response)**: Used when you need a response from the actor. - This returns a `Future[R]`, which represents the eventual reply. - - ```go - // Assuming 'actorRef' is an ActorRef[*MyRequest, *MyResponse]. - askMsg := &MyRequest{Data: "A request needing a response"} - futureResponse := actorRef.Ask(context.Background(), askMsg) - ``` - A `Future[R]` represents a result that will be available at some point. You - can block until it's ready using `Await`: - - ```go - // Await the result. It's good practice to use a context with a timeout. - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - result := futureResponse.Await(ctx) - response, err := result.Unpack() - if err != nil { - fmt.Printf("Ask failed: %v\n", err) - // return or handle error - } else { - fmt.Printf("Received reply: %s\n", response.Reply) - } - ``` - The `Future` interface also offers non-blocking ways to handle results, like - `OnComplete` (for callbacks) and `ThenApply` (for chaining transformations). - A more restricted `TellOnlyRef[M]` is also available if only fire-and-forget - semantics are required (obtained via an actor's `TellRef()` method). - -### Actors - -An `Actor` is the concrete entity that runs a behavior, manages a mailbox, and -has a lifecycle. You create an actor using `actor.NewActor` with an -`ActorConfig`: - -```go -cfg := actor.ActorConfig[*MyRequest, *MyResponse]{ - ID: "my-sample-actor", - Behavior: behavior, - MailboxSize: 10, - // Dead Letter Office (covered later) - DLO: nil, -} -sampleActor, err := actor.NewActor(cfg) -if err != nil { - // Handle invalid config (empty ID, nil behavior). - return err -} -``` - -An actor doesn't start processing messages until its `Start()` method is called. -This launches a dedicated goroutine for the actor. - -```go -sampleActor.Start() -``` -To stop an actor, you call its `Stop()` method. This cancels the actor's -internal context, causing its goroutine to clean up and exit. - -```go -// Sometime later... -sampleActor.Stop() -``` - - -## Visualizing Actor Relationships - -The following diagram illustrates the primary components of the actor package -and their relationships. It provides a high-level overview of how actors are -managed, discovered, and interacted with. - -```mermaid -classDiagram - direction TB - - class ActorSystem { - +Receptionist - +DeadLetters - +Shutdown() - } - - class Receptionist { - +Find(ServiceKey) ActorRef[] - +Register(ServiceKey, ActorRef) - } - - class DeadLetterOffice { - +Receive(undeliverable Message) - } - - class ServiceKey { - +Spawn(ActorSystem, Behavior) ActorRef - } - - class Actor { - -mailbox - -behavior - +Ref() ActorRef - +Start() - +Stop() - } - - class ActorRef { - <> - +Tell(Message) - +Ask(Message) Future - } - - class Message { - <> - } - - class Future { - +Await() Result - } - - class Router { - +Tell(Message) - +Ask(Message) Future - } - - %% Core system relationships - ActorSystem *-- Receptionist : has - ActorSystem *-- DeadLetterOffice : provides - ActorSystem o-- "manages" Actor - - %% Actor and communication - Actor --> ActorRef : provides - Actor ..> Message : processes - ActorRef ..> Message : sends - ActorRef ..> Future : returns for Ask - - %% Service discovery and routing - Receptionist o-- ServiceKey : uses for lookup - ServiceKey ..> Actor : creates - Router --> ActorRef : routes to - Router --> Receptionist : discovers actors via - - note for ActorSystem "Central manager for actor lifecycle and service discovery" - note for Actor "Independent unit with encapsulated state and behavior" - note for ActorRef "Location-transparent handle for sending messages" - note for Message "Data exchanged between actors" - note for ServiceKey "Type-safe identifier for actor registration and discovery" - note for Router "Distributes messages among multiple actors" - note for DeadLetterOffice "Handles messages that cannot be delivered" -``` - -## The Actor System - -While individual actors are useful, they often need to be managed and -coordinated. The `ActorSystem` serves this purpose. - -```go -system := actor.NewActorSystem() -// Ensures all actors in the system are stopped. -defer system.Shutdown() -``` - -### Actor Lifecycle and Registration - -The `ActorSystem` can manage the lifecycle of actors. You can register actors -with the system: - -```go -// Using 'behavior' from earlier and 'myServiceKey' defined in the -// "Service Keys and Actor References" section. - -// RegisterWithSystem creates, starts, and registers the actor. -actorRefFromSystem := actor.RegisterWithSystem( - system, "system-managed-actor", myServiceKey, behavior, -) -``` - -Alternatively, a `ServiceKey` itself provides a `Spawn` method for convenience: -```go -actorRefSpawned := myServiceKey.Spawn(system, "spawned-actor", behavior) -``` - -Actors registered with the system are automatically stopped when -`system.Shutdown()` is called. You can also stop and remove individual actors -using `system.StopAndRemoveActor(actorID)`. - -A `ServiceKey` is essentially the mailbox address of an actor. - -### Receptionist: Service Discovery - -Actors often need to find other actors to communicate with. The `Receptionist` -facilitates this. Actors are registered with the receptionist using a -`ServiceKey`, which is type-safe. - -```go -// Get the system's receptionist. -receptionist := system.Receptionist() - -// Find actors registered for a specific service key. -foundRefs := actor.FindInReceptionist(receptionist, myServiceKey) -if len(foundRefs) > 0 { - targetActor := foundRefs[0] - targetActor.Tell(context.Background(), &MyRequest{Data: "Hello from a discoverer!"}) -} else { - fmt.Println("No actors found for service key:", myServiceKey) -} -``` -When an actor is stopped (e.g., via `ServiceKey.Unregister` or system shutdown), -it should also be unregistered from the receptionist. - -### Dead Letter Office (DLO) - -What happens to messages that cannot be delivered? For example, if an actor is -stopped while messages are still in its mailbox, or if a message is sent to an -actor that doesn't exist (though the current `ActorRef` design makes the latter -less likely for direct sends). - -The `ActorSystem` provides a default `DeadLetterActor`. When an actor is -configured (via `ActorConfig.DLO`), undeliverable messages (e.g., those drained -from its mailbox upon shutdown) can be routed to this DLO. This allows for -logging, auditing, or potential manual intervention for "lost" messages. - -```go -// Actors created via RegisterWithSystem or ServiceKey.Spawn -// are automatically configured to use the system's DLO. -// system.DeadLetters() returns an ActorRef to the system's DLO. -``` - -## Routers: Distributing Work - -Sometimes, you might have multiple actors performing the same kind of task, and -you want to distribute messages among them. A `Router` can do this. It's not an -actor itself but acts as a dispatcher. - -A `Router` uses a `RoutingStrategy` to pick one actor from a group registered -under a `ServiceKey`. - -```go -// Assume 'system' and 'myServiceKey' are set up, and multiple actors -// are registered with 'myServiceKey'. - -// Create a round-robin routing strategy. -roundRobinStrategy := actor.NewRoundRobinStrategy[*MyRequest, *MyResponse]() - -// Create a router for 'myServiceKey' using this strategy. -// Messages sent to this router will be forwarded to one of the actors -// registered under 'myServiceKey'. -// The router also needs a DLO for messages it can't route (e.g., if no actors are available). -serviceRouter := actor.NewRouter( - system.Receptionist(), - myServiceKey, - roundRobinStrategy, - system.DeadLetters(), -) - -// Now, interact with the router as if it were an ActorRef: -serviceRouter.Tell(context.Background(), &MyRequest{Data: "Message via router"}) - -futureReplyFromRouter := serviceRouter.Ask(context.Background(), &MyRequest{Data: "Ask via router"}) -// ... await futureReplyFromRouter ... -``` -If the router cannot find any available actors for the `ServiceKey` (e.g., none -are registered or running), `Tell` operations will typically send the message to -the router's configured DLO, and `Ask` operations will return a `Future` -completed with `ErrNoActorsAvailable`. diff --git a/actor/actor.go b/actor/actor.go deleted file mode 100644 index 6dddab0c0..000000000 --- a/actor/actor.go +++ /dev/null @@ -1,294 +0,0 @@ -package actor - -import ( - "context" - "sync" - - "github.com/lightningnetwork/lnd/fn/v2" -) - -// MailboxFactory is a function type that creates a Mailbox implementation. -// It receives the actor's context and the desired capacity, allowing custom -// mailbox implementations (e.g., BackpressureMailbox) to be injected. -type MailboxFactory[M Message, R any] func(ctx context.Context, - capacity int) Mailbox[M, R] - -// ActorConfig holds the configuration parameters for creating a new Actor. -// It is generic over M (Message type) and R (Response type) to accommodate -// the actor's specific behavior. -type ActorConfig[M Message, R any] struct { - // ID is the unique identifier for the actor. - ID string - - // Behavior defines how the actor responds to messages. - Behavior ActorBehavior[M, R] - - // DLO is a reference to the dead letter office for this actor system. - // If nil, undeliverable messages during shutdown or due to a full - // mailbox (if such logic were added) might be dropped. - DLO ActorRef[Message, any] - - // MailboxSize defines the buffer capacity of the actor's mailbox. - MailboxSize int - - // MailboxFactory is an optional factory for creating the actor's - // mailbox. If nil, a default ChannelMailbox will be used. - MailboxFactory MailboxFactory[M, R] -} - -// envelope wraps a message with its associated promise. This allows the sender -// of an "ask" message to await a response. If the promise is nil, it -// signifies a "tell" operation (fire-and-forget). -type envelope[M Message, R any] struct { - message M - promise Promise[R] -} - -// Actor represents a concrete actor implementation. It encapsulates a behavior, -// manages its internal state implicitly through that behavior, and processes -// messages from its mailbox sequentially in its own goroutine. -type Actor[M Message, R any] struct { - // id is the unique identifier for the actor. - id string - - // behavior defines how the actor responds to messages. - behavior ActorBehavior[M, R] - - // mailbox is the incoming message queue for the actor. - mailbox Mailbox[M, R] - - // ctx is the context governing the actor's lifecycle. - ctx context.Context - - // cancel is the function to cancel the actor's context. - cancel context.CancelFunc - - // dlo is a reference to the dead letter office for this actor system. - dlo ActorRef[Message, any] - - // startOnce ensures the actor's processing loop is started only once. - startOnce sync.Once - - // stopOnce ensures the actor's processing loop is stopped only once. - stopOnce sync.Once - - // ref is the cached ActorRef for this actor. - ref ActorRef[M, R] -} - -// NewActor creates a new actor instance with the given ID and behavior. -// It initializes the actor's internal structures but does not start its -// message processing goroutine. The Start() method must be called to begin -// processing messages. -func NewActor[M Message, R any](cfg ActorConfig[M, R]) (*Actor[M, R], - error) { - - if cfg.ID == "" { - return nil, ErrEmptyActorID - } - - if cfg.Behavior == nil { - return nil, ErrNilBehavior - } - - ctx, cancel := context.WithCancel(context.Background()) - - // Ensure MailboxSize has a sane default if not specified or zero. A - // capacity of 0 would make the channel unbuffered, which is generally - // not desired for actor mailboxes. - mailboxCapacity := cfg.MailboxSize - if mailboxCapacity <= 0 { - // Default to a small capacity if an invalid one is given. This - // could also come from a global constant. - mailboxCapacity = 1 - } - - // Create the mailbox using the factory if provided, otherwise use - // the default ChannelMailbox. - var mailbox Mailbox[M, R] - if cfg.MailboxFactory != nil { - mailbox = cfg.MailboxFactory(ctx, mailboxCapacity) - } else { - mailbox = NewChannelMailbox[M, R](ctx, mailboxCapacity) - } - - actor := &Actor[M, R]{ - id: cfg.ID, - behavior: cfg.Behavior, - mailbox: mailbox, - ctx: ctx, - cancel: cancel, - dlo: cfg.DLO, - } - - // Create and cache the actor's own reference. - actor.ref = &actorRefImpl[M, R]{ - actor: actor, - } - - return actor, nil -} - -// Start initiates the actor's message processing loop in a new goroutine. This -// method should be called once after the actor is created. -func (a *Actor[M, R]) Start() { - a.startOnce.Do(func() { - log.Infof("Actor %s: starting", a.id) - - go a.process() - }) -} - -// process is the main event loop for the actor. It continuously monitors its -// mailbox for incoming messages and its context for cancellation signals. -func (a *Actor[M, R]) process() { - // Use the new iterator pattern for receiving messages. - for env := range a.mailbox.Receive(a.ctx) { - result := a.behavior.Receive(a.ctx, env.message) - - // If a promise was provided (i.e., it was an "ask" - // operation), complete the promise with the result from - // the behavior. - if env.promise != nil { - env.promise.Complete(result) - } - } - - // Context was cancelled or mailbox closed, drain remaining messages. - a.mailbox.Close() - - for env := range a.mailbox.Drain() { - // If a DLO is configured, send the original message there - // for auditing or potential manual reprocessing. - if a.dlo != nil { - a.dlo.Tell(context.Background(), env.message) - } - - // If it was an Ask, complete the promise with an error - // indicating the actor terminated. - if env.promise != nil { - env.promise.Complete(fn.Err[R](ErrActorTerminated)) - } - } -} - -// Stop signals the actor to terminate its processing loop and shut down. -// This is achieved by cancelling the actor's internal context. The actor's -// goroutine will exit once it detects the context cancellation. -func (a *Actor[M, R]) Stop() { - a.stopOnce.Do(func() { - log.Infof("Actor %s: stopping", a.id) - - a.cancel() - }) -} - -// actorRefImpl provides a concrete implementation of the ActorRef interface. It -// holds a reference to the target Actor instance, enabling message sending. -type actorRefImpl[M Message, R any] struct { - actor *Actor[M, R] -} - -// Tell sends a message without waiting for a response. If the context is -// cancelled before the message can be sent to the actor's mailbox, the message -// may be dropped. -// -//nolint:ll -func (ref *actorRefImpl[M, R]) Tell(ctx context.Context, msg M) { - // If the actor's own context is already done, don't try to send. - // Route to DLO if available. - if ref.actor.ctx.Err() != nil { - ref.trySendToDLO(msg) - return - } - - env := envelope[M, R]{message: msg, promise: nil} - - // Use mailbox Send method which internally checks both contexts. - if !ref.actor.mailbox.Send(ctx, env) { - // Failed to send - check if actor terminated. - if ref.actor.ctx.Err() != nil { - ref.trySendToDLO(msg) - } - // Otherwise the message was either dropped by backpressure - // (load shedding) or the caller's context was cancelled. - // Both are intentionally silent — no DLO routing. - } -} - -// Ask sends a message and returns a Future for the response. The Future will be -// completed with the actor's reply or an error if the operation fails (e.g., -// context cancellation before send). -// -//nolint:ll -func (ref *actorRefImpl[M, R]) Ask(ctx context.Context, msg M) Future[R] { - // Create a new promise that will be fulfilled with the actor's response. - promise := NewPromise[R]() - - // If the actor's own context is already done, complete the promise with - // ErrActorTerminated and return immediately. This is the primary guard - // against trying to send to a stopped actor. - if ref.actor.ctx.Err() != nil { - promise.Complete(fn.Err[R](ErrActorTerminated)) - return promise.Future() - } - - // Check if the context is already done before attempting to send. This - // ensures deterministic behavior and prevents a race where the message - // could be enqueued even though the context was already cancelled. - if ctx.Err() != nil { - promise.Complete(fn.Err[R](ctx.Err())) - return promise.Future() - } - - env := envelope[M, R]{message: msg, promise: promise} - - // Use mailbox Send method which internally checks both contexts. - if !ref.actor.mailbox.Send(ctx, env) { - // Determine the error based on what failed. - switch { - case ref.actor.ctx.Err() != nil: - promise.Complete(fn.Err[R](ErrActorTerminated)) - case ctx.Err() != nil: - promise.Complete(fn.Err[R](ctx.Err())) - default: - // Neither context is done — the mailbox's - // backpressure mechanism dropped the message. - promise.Complete(fn.Err[R](ErrMessageDropped)) - } - } - - // Return the future associated with the promise, allowing the caller to - // await the response. - return promise.Future() -} - -// trySendToDLO attempts to send the message to the actor's DLO if configured. -func (ref *actorRefImpl[M, R]) trySendToDLO(msg M) { - if ref.actor.dlo != nil { - // Use context.Background() for sending to DLO as the - // original context might be done or the operation - // should not be bound by it. - // This Tell to DLO is fire-and-forget. - ref.actor.dlo.Tell(context.Background(), msg) - } -} - -// ID returns the unique identifier for this actor. -func (ref *actorRefImpl[M, R]) ID() string { - return ref.actor.id -} - -// Ref returns an ActorRef for this actor. This allows clients to interact with -// the actor (send messages) without having direct access to the Actor struct -// itself, promoting encapsulation and location transparency. -func (a *Actor[M, R]) Ref() ActorRef[M, R] { - return a.ref -} - -// TellRef returns a TellOnlyRef for this actor. This allows clients to send -// messages to the actor using only the "tell" pattern (fire-and-forget), -// without having access to "ask" capabilities. -func (a *Actor[M, R]) TellRef() TellOnlyRef[M] { - return a.ref -} diff --git a/actor/actor_test.go b/actor/actor_test.go deleted file mode 100644 index 3d49aa6eb..000000000 --- a/actor/actor_test.go +++ /dev/null @@ -1,446 +0,0 @@ -package actor - -import ( - "context" - "errors" - "fmt" - "reflect" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/stretchr/testify/require" -) - -// testMsg is a simple message type for testing. It embeds BaseMessage to -// satisfy the actor.Message interface. -type testMsg struct { - BaseMessage - data string - - replyChan chan string -} - -// MessageType returns the type name of the message. -func (m *testMsg) MessageType() string { - return "testMsg" -} - -// newTestMsg creates a new test message. -func newTestMsg(data string) *testMsg { - return &testMsg{data: data} -} - -// newTestMsgWithReply creates a new test message that includes a reply channel. -// This can be used by test behaviors to send data back to the test -// synchronously, especially for Tell operations. -func newTestMsgWithReply(data string, replyChan chan string) *testMsg { - return &testMsg{data: data, replyChan: replyChan} -} - -// echoBehavior is a simple actor behavior that processes *testMsg messages. It -// stores the last message's data and, for Ask, echoes it back. For Tell, if -// replyChan is set in testMsg, it sends data back on it. -type echoBehavior struct { - lastMsgData atomic.Value - processingDelay time.Duration - t *testing.T -} - -// newEchoBehavior creates a new echoBehavior. -func newEchoBehavior(t *testing.T, delay time.Duration) *echoBehavior { - return &echoBehavior{t: t, processingDelay: delay} -} - -// Receive handles incoming messages. It simulates work if processingDelay is -// set, stores the message data, and responds for Ask operations or via -// replyChan for Tell. -func (b *echoBehavior) Receive(_ context.Context, - msg *testMsg) fn.Result[string] { - - if b.processingDelay > 0 { - time.Sleep(b.processingDelay) - } - - b.lastMsgData.Store(msg.data) - - if msg.replyChan != nil { - // Attempt to send the data on the reply channel, but quit if - // it takes longer than 1 second (e.g., channel unbuffered - // and no receiver). - select { - case msg.replyChan <- msg.data: - case <-time.After(time.Second): - b.t.Logf("warning: replyChan send timed out") - } - } - - return fn.Ok(fmt.Sprintf("echo: %s", msg.data)) -} - -// GetLastMsgData retrieves the data from the last message processed. -func (b *echoBehavior) GetLastMsgData() (string, bool) { - val := b.lastMsgData.Load() - if val == nil { - return "", false - } - data, ok := val.(string) - return data, ok -} - -// errorBehavior is an actor behavior that always returns a predefined error -// upon receiving a message. -type errorBehavior struct { - err error -} - -// newErrorBehavior creates a new errorBehavior. -func newErrorBehavior(err error) *errorBehavior { - return &errorBehavior{err: err} -} - -// Receive always returns the configured error. -func (b *errorBehavior) Receive(_ context.Context, - _ *testMsg) fn.Result[string] { - - return fn.Err[string](b.err) -} - -// blockingBehavior is an actor behavior that blocks until its actorCtx is done. -type blockingBehavior struct{} - -// Receive blocks until the actor's context is cancelled, then returns the -// context's error. -func (b *blockingBehavior) Receive(actorCtx context.Context, - _ *testMsg) fn.Result[string] { - - <-actorCtx.Done() - return fn.Err[string](actorCtx.Err()) -} - -// deadLetterTestMsg is a distinct message type used for testing DLO -// interactions. -type deadLetterTestMsg struct { - BaseMessage - id string -} - -// MessageType returns the type name of the message. -func (m *deadLetterTestMsg) MessageType() string { - return "deadLetterTestMsg" -} - -// deadLetterObserverBehavior is a behavior for a test Dead Letter Office actor. -// It records all messages sent to it, allowing tests to verify DLO -// interactions. -type deadLetterObserverBehavior struct { - mu sync.Mutex - receivedMsgs []Message -} - -// newDeadLetterObserverBehavior creates a new deadLetterObserverBehavior. -func newDeadLetterObserverBehavior() *deadLetterObserverBehavior { - return &deadLetterObserverBehavior{ - receivedMsgs: make([]Message, 0), - } -} - -// Receive records the incoming message and returns a successful result. -func (b *deadLetterObserverBehavior) Receive(_ context.Context, - msg Message) fn.Result[any] { - - b.mu.Lock() - b.receivedMsgs = append(b.receivedMsgs, msg) - b.mu.Unlock() - - return fn.Ok[any](nil) -} - -// GetReceivedMsgs returns a copy of all messages received by this DLO. -func (b *deadLetterObserverBehavior) GetReceivedMsgs() []Message { - b.mu.Lock() - defer b.mu.Unlock() - - msgs := make([]Message, len(b.receivedMsgs)) - copy(msgs, b.receivedMsgs) - - return msgs -} - -// actorTestHarness provides helper methods for setting up actors in tests. It -// manages a dedicated DLO for actors created through it. -type actorTestHarness struct { - t *testing.T - dlo *Actor[Message, any] - dloBeh *deadLetterObserverBehavior -} - -// newActorTestHarness sets up a test harness with a dedicated DLO. The DLO is -// automatically stopped when the test cleans up. -func newActorTestHarness(t *testing.T) *actorTestHarness { - t.Helper() - - dloBeh := newDeadLetterObserverBehavior() - dloCfg := ActorConfig[Message, any]{ - ID: "test-dlo-" + t.Name(), - Behavior: dloBeh, - DLO: nil, - MailboxSize: 10, - } - dloActor, err := NewActor[Message, any](dloCfg) - require.NoError(t, err) - dloActor.Start() - - t.Cleanup(dloActor.Stop) - - return &actorTestHarness{ - t: t, - dlo: dloActor, - dloBeh: dloBeh, - } -} - -// newActor creates, starts, and registers a new actor for cleanup. The actor -// will use the harness's DLO. -func (h *actorTestHarness) newActor(id string, - beh ActorBehavior[*testMsg, string], - mailboxSize int) *Actor[*testMsg, string] { - - h.t.Helper() - - cfg := ActorConfig[*testMsg, string]{ - ID: id, - Behavior: beh, - DLO: h.dlo.Ref(), - MailboxSize: mailboxSize, - } - actor, err := NewActor(cfg) - require.NoError(h.t, err) - actor.Start() - - h.t.Cleanup(actor.Stop) - - return actor -} - -// assertDLOMessage checks that the DLO eventually receives a specific message. -func (h *actorTestHarness) assertDLOMessage(expectedMsg Message) { - h.t.Helper() - require.Eventually(h.t, func() bool { - msgs := h.dloBeh.GetReceivedMsgs() - for _, m := range msgs { - if reflect.DeepEqual(m, expectedMsg) { - return true - } - } - return false - }, time.Second, 10*time.Millisecond, - "dLO did not receive expected message: %v", expectedMsg, - ) -} - -// assertNoDLOMessages checks that the DLO has not received any messages. -func (h *actorTestHarness) assertNoDLOMessages() { - h.t.Helper() - - // Allow a very brief moment for any async DLO sends to occur. - time.Sleep(20 * time.Millisecond) - - msgs := h.dloBeh.GetReceivedMsgs() - - require.Empty(h.t, msgs, "dLO received unexpected messages") -} - -// TestActorNewActorIDAndRefs verifies that NewActor correctly initializes an -// actor's ID and provides functional ActorRef and TellOnlyRef instances. -func TestActorNewActorIDAndRefs(t *testing.T) { - t.Parallel() - - h := newActorTestHarness(t) - actorID := "test-actor-1" - beh := newEchoBehavior(t, 0) - actor := h.newActor(actorID, beh, 1) - - require.Equal(t, actorID, actor.Ref().ID(), "actorRef ID mismatch") - require.Equal( - t, actorID, actor.TellRef().ID(), "tellOnlyRef ID mismatch", - ) - require.NotNil(t, actor.Ref(), "actorRef should not be nil") - require.NotNil(t, actor.TellRef(), "tellOnlyRef should not be nil") -} - -// TestActorStartStop verifies the basic lifecycle of an actor: starting, -// processing messages, and stopping. -func TestActorStartStop(t *testing.T) { - t.Parallel() - - h := newActorTestHarness(t) - beh := newEchoBehavior(t, 0) - actor := h.newActor("test-actor-lifecycle", beh, 1) - - // Actor should be running and process a message. - msgData := "hello" - replyChan := make(chan string, 1) - actor.Ref().Tell( - context.Background(), newTestMsgWithReply(msgData, replyChan), - ) - - received, err := fn.RecvOrTimeout(replyChan, 100*time.Millisecond) - require.NoError(t, err, "timed out waiting for actor to process message") - require.Equal( - t, msgData, received, "actor did not process message before stop", - ) - - actor.Stop() - time.Sleep(50 * time.Millisecond) - - // Try sending another message; it should ideally not be processed or go - // to DLO. - msgDataAfterStop := "message-after-stop" - replyChanAfterStop := make(chan string, 1) - actor.Ref().Tell( - context.Background(), - newTestMsgWithReply(msgDataAfterStop, replyChanAfterStop), - ) - - // We expect a timeout here, meaning the message was not processed by - // the echoBehavior's replyChan. - _, err = fn.RecvOrTimeout(replyChanAfterStop, 100*time.Millisecond) - // err == nil would mean a message was received, meaning the actor - // processed it after Stop(). - require.Error(t, err, "actor processed message after Stop()") - require.ErrorContains(t, err, "timeout hit") - - h.assertDLOMessage( - &testMsg{data: msgDataAfterStop, replyChan: replyChanAfterStop}, - ) -} - -// TestActorTellBasic verifies that a message sent via Tell is processed by the -// actor's behavior. -func TestActorTellBasic(t *testing.T) { - t.Parallel() - - h := newActorTestHarness(t) - beh := newEchoBehavior(t, 0) - actor := h.newActor("test-actor-tell", beh, 1) - - msgData := "tell-message" - replyChan := make(chan string, 1) - actor.Ref().Tell( - context.Background(), newTestMsgWithReply(msgData, replyChan), - ) - - receivedTell, errTell := fn.RecvOrTimeout(replyChan, 100*time.Millisecond) - require.NoError(t, errTell, "timed out waiting for Tell message processing") - require.Equal( - t, msgData, receivedTell, "behavior did not receive Tell message data", - ) - - lastData, ok := beh.GetLastMsgData() - require.True(t, ok, "last message data not set in behavior") - require.Equal(t, msgData, lastData, "last message data mismatch") - h.assertNoDLOMessages() -} - -// TestActorAskSuccess verifies that a message sent via Ask is processed, and -// the returned Future is completed with the behavior's successful result. -func TestActorAskSuccess(t *testing.T) { - t.Parallel() - - h := newActorTestHarness(t) - beh := newEchoBehavior(t, 0) - actor := h.newActor("test-actor-ask-success", beh, 1) - - msgData := "ask-message" - future := actor.Ref().Ask(context.Background(), newTestMsg(msgData)) - - result := future.Await(context.Background()) - require.False(t, result.IsErr(), "ask returned an error: %v", result.Err()) - - result.WhenOk(func(val string) { - expectedReply := fmt.Sprintf("echo: %s", msgData) - require.Equal(t, expectedReply, val, "ask response mismatch") - }) - - lastData, ok := beh.GetLastMsgData() - require.True(t, ok, "last message data not set in behavior") - require.Equal(t, msgData, lastData, "last message data mismatch") - h.assertNoDLOMessages() -} - -// TestActorAskErrorBehavior verifies that if an actor's behavior returns an -// error, the Future from an Ask call is completed with that error. -func TestActorAskErrorBehavior(t *testing.T) { - t.Parallel() - - h := newActorTestHarness(t) - expectedErr := errors.New("behavior error") - beh := newErrorBehavior(expectedErr) - actor := h.newActor("test-actor-ask-error", beh, 1) - - future := actor.Ref().Ask( - context.Background(), newTestMsg("ask-error-test"), - ) - - result := future.Await(context.Background()) - require.True(t, result.IsErr(), "ask should have returned an error") - require.ErrorIs(t, result.Err(), expectedErr, "ask error mismatch") - - h.assertNoDLOMessages() -} - -// TestFunctionBehaviorFromSimple verifies that FunctionBehaviorFromSimple -// correctly adapts a simple (msg) -> (result, error) function into an -// ActorBehavior, handling both success and error cases. -func TestFunctionBehaviorFromSimple(t *testing.T) { - t.Parallel() - - t.Run("success", func(t *testing.T) { - t.Parallel() - - h := newActorTestHarness(t) - - beh := FunctionBehaviorFromSimple( - func(msg *testMsg) (string, error) { - return "simple: " + msg.data, nil - }, - ) - actor := h.newActor("test-simple-success", beh, 1) - - future := actor.Ref().Ask( - context.Background(), newTestMsg("hello"), - ) - result := future.Await(context.Background()) - require.False( - t, result.IsErr(), - "expected success, got: %v", result.Err(), - ) - result.WhenOk(func(val string) { - require.Equal(t, "simple: hello", val) - }) - }) - - t.Run("error", func(t *testing.T) { - t.Parallel() - - h := newActorTestHarness(t) - - expectedErr := errors.New("simple behavior error") - beh := FunctionBehaviorFromSimple( - func(msg *testMsg) (string, error) { - return "", expectedErr - }, - ) - actor := h.newActor("test-simple-error", beh, 1) - - future := actor.Ref().Ask( - context.Background(), newTestMsg("hello"), - ) - result := future.Await(context.Background()) - require.True(t, result.IsErr()) - require.ErrorIs(t, result.Err(), expectedErr) - }) -} diff --git a/actor/backpressure_mailbox.go b/actor/backpressure_mailbox.go deleted file mode 100644 index 016b4576d..000000000 --- a/actor/backpressure_mailbox.go +++ /dev/null @@ -1,166 +0,0 @@ -package actor - -import ( - "context" - "iter" - "sync" - "sync/atomic" - - "github.com/lightningnetwork/lnd/queue" -) - -// BackpressureMailbox implements the Mailbox interface using a -// queue.BackpressureQueue as its core buffer. The BackpressureQueue's drop -// predicate is consulted on every Send/TrySend, allowing RED-style load -// shedding before the mailbox is full. -type BackpressureMailbox[M Message, R any] struct { - // queue is the underlying backpressure-aware buffer. - queue *queue.BackpressureQueue[envelope[M, R]] - - // closed tracks whether the mailbox has been closed. - closed atomic.Bool - - // mu protects Send/TrySend operations to prevent send-on-closed-channel - // panics. Close() acquires write lock, Send/TrySend acquire read lock. - mu sync.RWMutex - - // closeOnce ensures Close() executes exactly once. - closeOnce sync.Once - - // actorCtx is the actor's context for lifecycle management. - actorCtx context.Context -} - -// NewBackpressureMailbox creates a new mailbox backed by a BackpressureQueue. -// The shouldDrop function is called with the current queue depth on every send -// attempt; if it returns true the message is silently dropped. -func NewBackpressureMailbox[M Message, R any]( - actorCtx context.Context, - capacity int, - shouldDrop queue.DropCheckFunc, -) *BackpressureMailbox[M, R] { - - if capacity <= 0 { - capacity = 1 - } - - pred := queue.AsDropPredicate[envelope[M, R]](shouldDrop) - - return &BackpressureMailbox[M, R]{ - queue: queue.NewBackpressureQueue(capacity, pred), - actorCtx: actorCtx, - } -} - -// Send attempts to send an envelope to the mailbox. The BackpressureQueue's -// drop predicate is consulted first; if it decides to drop, false is returned -// immediately. Otherwise the send blocks until the envelope is accepted, the -// caller's context is cancelled, or the actor's context is cancelled. -func (m *BackpressureMailbox[M, R]) Send(ctx context.Context, - env envelope[M, R]) bool { - - m.mu.RLock() - defer m.mu.RUnlock() - - if m.IsClosed() { - return false - } - - // Create a context that is cancelled when either the caller's context - // or the actor's context is done, so that the blocking Enqueue - // respects both. - merged, cancel := context.WithCancel(ctx) - stop := context.AfterFunc(m.actorCtx, cancel) - defer stop() - defer cancel() - - err := m.queue.Enqueue(merged, env) - - return err == nil -} - -// TrySend attempts a non-blocking send. Returns false if the drop predicate -// rejects the message, the queue is at capacity, or the mailbox is closed. -func (m *BackpressureMailbox[M, R]) TrySend(env envelope[M, R]) bool { - m.mu.RLock() - defer m.mu.RUnlock() - - if m.IsClosed() { - return false - } - - return m.queue.TryEnqueue(env) -} - -// Receive returns an iterator that yields envelopes from the mailbox until -// the mailbox is closed, the provided context is cancelled, or the actor's -// context is cancelled. -func (m *BackpressureMailbox[M, R]) Receive( - ctx context.Context) iter.Seq[envelope[M, R]] { - - return func(yield func(envelope[M, R]) bool) { - ch := m.queue.ReceiveChan() - for { - select { - case env, ok := <-ch: - if !ok { - return - } - - if !yield(env) { - return - } - - case <-ctx.Done(): - return - - case <-m.actorCtx.Done(): - return - } - } - } -} - -// Close closes the mailbox, preventing new messages from being sent. Any -// remaining messages can still be consumed via Drain. -func (m *BackpressureMailbox[M, R]) Close() { - m.closeOnce.Do(func() { - m.mu.Lock() - defer m.mu.Unlock() - - m.closed.Store(true) - - m.queue.Close() - }) -} - -// IsClosed returns true if the mailbox has been closed. -func (m *BackpressureMailbox[M, R]) IsClosed() bool { - return m.closed.Load() -} - -// Drain returns an iterator that yields all remaining messages in the mailbox -// after it has been closed. -func (m *BackpressureMailbox[M, R]) Drain() iter.Seq[envelope[M, R]] { - return func(yield func(envelope[M, R]) bool) { - if !m.IsClosed() { - return - } - - ch := m.queue.ReceiveChan() - for { - select { - case env, ok := <-ch: - if !ok { - return - } - - if !yield(env) { - return - } - default: - return - } - } - } -} diff --git a/actor/backpressure_mailbox_test.go b/actor/backpressure_mailbox_test.go deleted file mode 100644 index e8ff56007..000000000 --- a/actor/backpressure_mailbox_test.go +++ /dev/null @@ -1,393 +0,0 @@ -package actor - -import ( - "context" - "sync" - "testing" - - "github.com/lightningnetwork/lnd/queue" - "github.com/stretchr/testify/require" -) - -// Compile-time assertion that BackpressureMailbox satisfies the Mailbox -// interface. -var _ Mailbox[TestMessage, int] = (*BackpressureMailbox[TestMessage, int])(nil) - -// TestBackpressureMailboxDropsWhenThresholdReached verifies that -// BackpressureMailbox drops messages when shouldDrop returns true. -func TestBackpressureMailboxDropsWhenThresholdReached(t *testing.T) { - t.Parallel() - - ctx := context.Background() - const capacity = 10 - const dropThreshold = 5 - - shouldDrop := queue.DropCheckFunc(func(queueLen int) bool { - return queueLen >= dropThreshold - }) - - mbox := NewBackpressureMailbox[TestMessage, int]( - ctx, capacity, shouldDrop, - ) - - // Fill up to the drop threshold — these should all succeed. - for i := range dropThreshold { - env := envelope[TestMessage, int]{ - message: TestMessage{Value: i}, - } - ok := mbox.Send(ctx, env) - require.True(t, ok, "message %d should be accepted", i) - } - - // Next message should be dropped by the predicate. - env := envelope[TestMessage, int]{ - message: TestMessage{Value: 99}, - } - ok := mbox.Send(ctx, env) - require.False(t, ok, "message at threshold should be dropped") -} - -// TestBackpressureMailboxTrySendDrops verifies TrySend also respects the drop -// predicate. -func TestBackpressureMailboxTrySendDrops(t *testing.T) { - t.Parallel() - - ctx := context.Background() - const capacity = 10 - const dropThreshold = 3 - - shouldDrop := queue.DropCheckFunc(func(queueLen int) bool { - return queueLen >= dropThreshold - }) - - mbox := NewBackpressureMailbox[TestMessage, int]( - ctx, capacity, shouldDrop, - ) - - // Fill to threshold. - for i := range dropThreshold { - env := envelope[TestMessage, int]{ - message: TestMessage{Value: i}, - } - ok := mbox.TrySend(env) - require.True(t, ok, "message %d should be accepted", i) - } - - // TrySend should now be rejected. - env := envelope[TestMessage, int]{ - message: TestMessage{Value: 99}, - } - ok := mbox.TrySend(env) - require.False(t, ok, "TrySend at threshold should be dropped") -} - -// TestBackpressureMailboxNeverDropPassesThrough verifies that a never-drop -// predicate lets all messages through (up to channel capacity). -func TestBackpressureMailboxNeverDropPassesThrough(t *testing.T) { - t.Parallel() - - ctx := context.Background() - const capacity = 5 - - neverDrop := queue.DropCheckFunc(func(queueLen int) bool { - return false - }) - - mbox := NewBackpressureMailbox[TestMessage, int]( - ctx, capacity, neverDrop, - ) - - // Fill the entire capacity. - for i := range capacity { - env := envelope[TestMessage, int]{ - message: TestMessage{Value: i}, - } - ok := mbox.Send(ctx, env) - require.True(t, ok, "message %d should be accepted", i) - } -} - -// TestBackpressureMailboxDelegatesReceive verifies that Receive yields messages -// from the underlying BackpressureQueue. -func TestBackpressureMailboxDelegatesReceive(t *testing.T) { - t.Parallel() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - const capacity = 5 - neverDrop := queue.DropCheckFunc(func(queueLen int) bool { - return false - }) - mbox := NewBackpressureMailbox[TestMessage, int]( - ctx, capacity, neverDrop, - ) - - // Send two messages. - for i := range 2 { - env := envelope[TestMessage, int]{ - message: TestMessage{Value: i}, - } - mbox.Send(ctx, env) - } - - // Close so Receive iterator terminates after draining. - mbox.Close() - - var count int - for range mbox.Receive(ctx) { - count++ - } - - require.Equal(t, 2, count, "should receive 2 messages") -} - -// TestBackpressureMailboxDelegatesDrain verifies that Drain yields remaining -// messages after close. -func TestBackpressureMailboxDelegatesDrain(t *testing.T) { - t.Parallel() - - ctx := context.Background() - const capacity = 5 - neverDrop := queue.DropCheckFunc(func(queueLen int) bool { - return false - }) - mbox := NewBackpressureMailbox[TestMessage, int]( - ctx, capacity, neverDrop, - ) - - // Send messages and close. - for i := range 3 { - env := envelope[TestMessage, int]{ - message: TestMessage{Value: i}, - } - mbox.Send(ctx, env) - } - mbox.Close() - - require.True(t, mbox.IsClosed()) - - var count int - for range mbox.Drain() { - count++ - } - - require.Equal(t, 3, count, "should drain 3 messages") -} - -// TestBackpressureMailboxSendRespectsActorCtx verifies that Send returns false -// when the actor context is cancelled. -func TestBackpressureMailboxSendRespectsActorCtx(t *testing.T) { - t.Parallel() - - actorCtx, actorCancel := context.WithCancel(context.Background()) - - const capacity = 1 - neverDrop := queue.DropCheckFunc(func(queueLen int) bool { - return false - }) - mbox := NewBackpressureMailbox[TestMessage, int]( - actorCtx, capacity, neverDrop, - ) - - // Fill the mailbox to capacity. - env := envelope[TestMessage, int]{ - message: TestMessage{Value: 1}, - } - ok := mbox.Send(context.Background(), env) - require.True(t, ok) - - // Cancel the actor context. The next blocking send should fail. - actorCancel() - - env2 := envelope[TestMessage, int]{ - message: TestMessage{Value: 2}, - } - ok = mbox.Send(context.Background(), env2) - require.False(t, ok, "send should fail when actor context is cancelled") -} - -// TestBackpressureMailboxReceiveAfterClose verifies that calling Receive after -// Close does not panic and yields no messages (the channel is already drained). -func TestBackpressureMailboxReceiveAfterClose(t *testing.T) { - t.Parallel() - - ctx := context.Background() - const capacity = 5 - neverDrop := queue.DropCheckFunc(func(queueLen int) bool { - return false - }) - mbox := NewBackpressureMailbox[TestMessage, int]( - ctx, capacity, neverDrop, - ) - - mbox.Close() - - // First Receive after close should return immediately (closed channel). - var count int - for range mbox.Receive(ctx) { - count++ - } - require.Equal(t, 0, count, "no messages expected") - - // Second Receive must not panic. - for range mbox.Receive(ctx) { - count++ - } - require.Equal(t, 0, count, "still no messages expected") -} - -// TestBackpressureMailboxDrainAfterDrain verifies that calling Drain twice -// after Close does not panic. -func TestBackpressureMailboxDrainAfterDrain(t *testing.T) { - t.Parallel() - - ctx := context.Background() - const capacity = 5 - neverDrop := queue.DropCheckFunc(func(queueLen int) bool { - return false - }) - mbox := NewBackpressureMailbox[TestMessage, int]( - ctx, capacity, neverDrop, - ) - - // Send one message and close. - env := envelope[TestMessage, int]{ - message: TestMessage{Value: 1}, - } - mbox.Send(ctx, env) - mbox.Close() - - // First drain should yield the message. - var count int - for range mbox.Drain() { - count++ - } - require.Equal(t, 1, count, "should drain 1 message") - - // Second drain must not panic and should yield nothing. - count = 0 - for range mbox.Drain() { - count++ - } - require.Equal(t, 0, count, "second drain should yield nothing") -} - -// TestBackpressureMailboxConcurrentSendClose tests concurrent Send/TrySend and -// Close operations to ensure no race conditions or panics occur. -func TestBackpressureMailboxConcurrentSendClose(t *testing.T) { - t.Parallel() - - const ( - numSenders = 50 - capacity = 20 - ) - - ctx := context.Background() - neverDrop := queue.DropCheckFunc(func(queueLen int) bool { - return false - }) - - mbox := NewBackpressureMailbox[TestMessage, int]( - ctx, capacity, neverDrop, - ) - - var wg sync.WaitGroup - - // Launch many goroutines that continuously call Send/TrySend. - for i := range numSenders { - wg.Add(1) - go func() { - defer wg.Done() - - for j := range 100 { - env := envelope[TestMessage, int]{ - message: TestMessage{ - Value: i*100 + j, - }, - } - // Send must not panic regardless of - // whether Close has been called. - mbox.Send(ctx, env) - } - }() - - // Launch a goroutine that also calls TrySend - // concurrently. - wg.Add(1) - go func() { - defer wg.Done() - - for j := range 500 { - env := envelope[TestMessage, int]{ - message: TestMessage{Value: j}, - } - mbox.TrySend(env) - } - }() - } - - // Drain messages concurrently to free buffer space so Send - // goroutines make progress and don't all block. - wg.Add(1) - go func() { - defer wg.Done() - - ch := mbox.queue.ReceiveChan() - for range ch { - } - }() - - // Close the mailbox while senders are still active. - mbox.Close() - - // Wait for all goroutines to finish. If the RWMutex protocol - // is broken, this test will panic with "send on closed channel" - // or the race detector will flag a data race. - wg.Wait() - - require.True(t, mbox.IsClosed()) - - // After Close, all subsequent sends must return false. - env := envelope[TestMessage, int]{ - message: TestMessage{Value: -1}, - } - require.False(t, mbox.Send(ctx, env)) - require.False(t, mbox.TrySend(env)) -} - -// TestBackpressureMailboxConcurrentMultiClose verifies that calling Close -// from multiple goroutines simultaneously does not panic. -func TestBackpressureMailboxConcurrentMultiClose(t *testing.T) { - t.Parallel() - - ctx := context.Background() - neverDrop := queue.DropCheckFunc(func(queueLen int) bool { - return false - }) - - mbox := NewBackpressureMailbox[TestMessage, int]( - ctx, 10, neverDrop, - ) - - // Send a few messages first. - for i := range 5 { - env := envelope[TestMessage, int]{ - message: TestMessage{Value: i}, - } - mbox.Send(ctx, env) - } - - // Close from many goroutines simultaneously. - var wg sync.WaitGroup - for range 20 { - wg.Add(1) - go func() { - defer wg.Done() - mbox.Close() - }() - } - wg.Wait() - - require.True(t, mbox.IsClosed()) -} diff --git a/actor/example_basic_actor_test.go b/actor/example_basic_actor_test.go deleted file mode 100644 index 960602a91..000000000 --- a/actor/example_basic_actor_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package actor_test - -import ( - "context" - "fmt" - "time" - - "github.com/lightningnetwork/lnd/actor" - "github.com/lightningnetwork/lnd/fn/v2" -) - -// BasicGreetingMsg is a simple message type for the basic actor example. -type BasicGreetingMsg struct { - actor.BaseMessage - Name string -} - -// MessageType implements actor.Message. -func (m BasicGreetingMsg) MessageType() string { return "BasicGreetingMsg" } - -// BasicGreetingResponse is a simple response type. -type BasicGreetingResponse struct { - Greeting string -} - -// ExampleActor demonstrates creating a single actor, sending it a message -// directly using Ask, and then unregistering and stopping it. -func ExampleActor() { - system := actor.NewActorSystem() - defer system.Shutdown() - - //nolint:ll - greeterKey := actor.NewServiceKey[BasicGreetingMsg, BasicGreetingResponse]( - "basic-greeter", - ) - - actorID := "my-greeter" - greeterBehavior := actor.NewFunctionBehavior( - func(ctx context.Context, - msg BasicGreetingMsg) fn.Result[BasicGreetingResponse] { - - return fn.Ok(BasicGreetingResponse{ - Greeting: "Hello, " + msg.Name + " from " + - actorID, - }) - }, - ) - - // Spawn the actor. This registers it with the system and receptionist, - // and starts it. It returns an ActorRef. - greeterRef, err := greeterKey.Spawn(system, actorID, greeterBehavior) - if err != nil { - fmt.Printf("Failed to spawn actor: %v\n", err) - return - } - fmt.Printf("Actor %s spawned.\n", greeterRef.ID()) - - // Send a message directly to the actor's reference. - askCtx, askCancel := context.WithTimeout( - context.Background(), 1*time.Second, - ) - defer askCancel() - futureResponse := greeterRef.Ask( - askCtx, BasicGreetingMsg{Name: "World"}, - ) - - awaitCtx, awaitCancel := context.WithTimeout( - context.Background(), 1*time.Second, - ) - defer awaitCancel() - result := futureResponse.Await(awaitCtx) - - result.WhenErr(func(err error) { - fmt.Printf("Error awaiting response: %v\n", err) - }) - result.WhenOk(func(response BasicGreetingResponse) { - fmt.Printf("Received: %s\n", response.Greeting) - }) - - // Unregister the actor. This also stops the actor. - unregistered := greeterKey.Unregister(system, greeterRef) - if unregistered { - fmt.Printf("Actor %s unregistered and stopped.\n", - greeterRef.ID()) - } else { - fmt.Printf("Failed to unregister actor %s.\n", greeterRef.ID()) - } - - // Verify it's no longer in the receptionist. - refsAfterUnregister := actor.FindInReceptionist( - system.Receptionist(), greeterKey, - ) - fmt.Printf("Actors for key '%s' after unregister: %d\n", - "basic-greeter", len(refsAfterUnregister)) - - // Output: - // Actor my-greeter spawned. - // Received: Hello, World from my-greeter - // Actor my-greeter unregistered and stopped. - // Actors for key 'basic-greeter' after unregister: 0 -} diff --git a/actor/example_router_test.go b/actor/example_router_test.go deleted file mode 100644 index f68cff60a..000000000 --- a/actor/example_router_test.go +++ /dev/null @@ -1,120 +0,0 @@ -package actor_test - -import ( - "context" - "fmt" - "time" - - "github.com/lightningnetwork/lnd/actor" - "github.com/lightningnetwork/lnd/fn/v2" -) - -// RouterGreetingMsg is a message type for the router example. -type RouterGreetingMsg struct { - actor.BaseMessage - Name string -} - -// MessageType implements actor.Message. -func (m RouterGreetingMsg) MessageType() string { return "RouterGreetingMsg" } - -// RouterGreetingResponse is a response type for the router example. -type RouterGreetingResponse struct { - Greeting string - HandlerID string -} - -// ExampleRouter demonstrates creating multiple actors under the same service -// key and using a router to dispatch messages to them. -func ExampleRouter() { - system := actor.NewActorSystem() - defer system.Shutdown() - - //nolint:ll - routerGreeterKey := actor.NewServiceKey[RouterGreetingMsg, RouterGreetingResponse]( - "router-greeter-service", - ) - - // Behavior for the first greeter actor. - actorID1 := "router-greeter-1" - greeterBehavior1 := actor.NewFunctionBehavior( - func(ctx context.Context, - msg RouterGreetingMsg) fn.Result[RouterGreetingResponse] { - - return fn.Ok(RouterGreetingResponse{ - Greeting: "Greetings, " + msg.Name + "!", - HandlerID: actorID1, - }) - }, - ) - _, err := routerGreeterKey.Spawn(system, actorID1, greeterBehavior1) - if err != nil { - fmt.Printf("Failed to spawn actor: %v\n", err) - return - } - fmt.Printf("Actor %s spawned.\n", actorID1) - - // Behavior for the second greeter actor. - actorID2 := "router-greeter-2" - greeterBehavior2 := actor.NewFunctionBehavior( - func(ctx context.Context, - msg RouterGreetingMsg) fn.Result[RouterGreetingResponse] { - - return fn.Ok(RouterGreetingResponse{ - Greeting: "Salutations, " + msg.Name + "!", - HandlerID: actorID2, - }) - }, - ) - _, err = routerGreeterKey.Spawn(system, actorID2, greeterBehavior2) - if err != nil { - fmt.Printf("Failed to spawn actor: %v\n", err) - return - } - fmt.Printf("Actor %s spawned.\n", actorID2) - - // Create a router for the "router-greeter-service". - greeterRouter := actor.NewRouter( - system.Receptionist(), routerGreeterKey, - actor.NewRoundRobinStrategy[RouterGreetingMsg, - RouterGreetingResponse](), - system.DeadLetters(), - ) - fmt.Printf("Router %s created for service key '%s'.\n", - greeterRouter.ID(), "router-greeter-service") - - // Send messages through the router. - names := []string{"Alice", "Bob", "Charlie", "David"} - for _, name := range names { - askCtx, askCancel := context.WithTimeout( - context.Background(), 1*time.Second, - ) - futureResponse := greeterRouter.Ask( - askCtx, RouterGreetingMsg{Name: name}, - ) - - awaitCtx, awaitCancel := context.WithTimeout( - context.Background(), 1*time.Second, - ) - result := futureResponse.Await(awaitCtx) - - result.WhenErr(func(err error) { - fmt.Printf("For %s: Error - %v\n", name, err) - }) - result.WhenOk(func(response RouterGreetingResponse) { - fmt.Printf("For %s: Received '%s' from %s\n", - name, response.Greeting, response.HandlerID) - }) - awaitCancel() - askCancel() - } - - // Output: - // Actor router-greeter-1 spawned. - // Actor router-greeter-2 spawned. - // Router router(router-greeter-service) created for service key 'router-greeter-service'. - // For Alice: Received 'Greetings, Alice!' from router-greeter-1 - // For Bob: Received 'Salutations, Bob!' from router-greeter-2 - // For Charlie: Received 'Greetings, Charlie!' from router-greeter-1 - // For David: Received 'Salutations, David!' from router-greeter-2 -} diff --git a/actor/example_struct_actor_test.go b/actor/example_struct_actor_test.go deleted file mode 100644 index e92e1e1a7..000000000 --- a/actor/example_struct_actor_test.go +++ /dev/null @@ -1,153 +0,0 @@ -package actor_test - -import ( - "context" - "fmt" - "time" - - "github.com/lightningnetwork/lnd/actor" - "github.com/lightningnetwork/lnd/fn/v2" -) - -// CounterMsg is a message type for the stateful counter actor. -// It can be used to increment the counter or get its current value. -type CounterMsg struct { - actor.BaseMessage - Increment int - GetValue bool - Who string -} - -// MessageType implements actor.Message. -func (m CounterMsg) MessageType() string { return "CounterMsg" } - -// CounterResponse is a response type for the counter actor. -type CounterResponse struct { - Value int - Responder string -} - -// StatefulCounterActor demonstrates an actor that maintains internal state (a -// counter) and processes messages to modify or query that state. -type StatefulCounterActor struct { - counter int - actorID string -} - -// NewStatefulCounterActor creates a new counter actor. -func NewStatefulCounterActor(id string) *StatefulCounterActor { - return &StatefulCounterActor{ - actorID: id, - } -} - -// Receive is the message handler for the StatefulCounterActor. -// It implements the actor.ActorBehavior interface implicitly when wrapped. -func (s *StatefulCounterActor) Receive(ctx context.Context, - msg CounterMsg) fn.Result[CounterResponse] { - - if msg.Increment > 0 { - // For increment, we can just acknowledge or return the new - // value. Messages are sent serially, so we don't need to worry - // about a mutex here. - s.counter += msg.Increment - - return fn.Ok(CounterResponse{ - Value: s.counter, - Responder: s.actorID, - }) - } - - if msg.GetValue { - return fn.Ok(CounterResponse{ - Value: s.counter, - Responder: s.actorID, - }) - } - - return fn.Err[CounterResponse](fmt.Errorf("invalid CounterMsg")) -} - -// ExampleActor_stateful demonstrates creating an actor whose behavior is defined -// by a struct with methods, allowing it to maintain internal state. -func ExampleActor_stateful() { - system := actor.NewActorSystem() - defer system.Shutdown() - - counterServiceKey := actor.NewServiceKey[CounterMsg, CounterResponse]( - "struct-counter-service", - ) - - // Create an instance of our stateful actor logic. - actorID := "counter-actor-1" - counterLogic := NewStatefulCounterActor(actorID) - - // Spawn the actor. - // The counterLogic instance itself satisfies the ActorBehavior - // interface because its Receive method matches the required signature. - counterRef, err := counterServiceKey.Spawn( - system, actorID, counterLogic, - ) - if err != nil { - fmt.Printf("Failed to spawn actor: %v\n", err) - return - } - fmt.Printf("Actor %s spawned.\n", counterRef.ID()) - - // Send messages to increment the counter. - for i := 1; i <= 3; i++ { - askCtx, askCancel := context.WithTimeout( - context.Background(), 1*time.Second, - ) - futureResp := counterRef.Ask(askCtx, - CounterMsg{ - Increment: i, - Who: fmt.Sprintf("Incrementer-%d", i), - }, - ) - awaitCtx, awaitCancel := context.WithTimeout( - context.Background(), 1*time.Second, - ) - resp := futureResp.Await(awaitCtx) - - resp.WhenOk(func(r CounterResponse) { - fmt.Printf("Incremented by %d, new value: %d "+ - "(from %s)\n", i, r.Value, r.Responder) - }) - resp.WhenErr(func(e error) { - fmt.Printf("Error incrementing: %v\n", e) - }) - awaitCancel() - askCancel() - } - - // Send a message to get the current value. - askCtx, askCancel := context.WithTimeout( - context.Background(), 1*time.Second, - ) - futureResp := counterRef.Ask( - askCtx, CounterMsg{GetValue: true, Who: "Getter"}, - ) - - awaitCtx, awaitCancel := context.WithTimeout( - context.Background(), 1*time.Second, - ) - - finalValueResp := futureResp.Await(awaitCtx) - finalValueResp.WhenOk(func(r CounterResponse) { - fmt.Printf("Final counter value: %d (from %s)\n", - r.Value, r.Responder) - }) - finalValueResp.WhenErr(func(e error) { - fmt.Printf("Error getting value: %v\n", e) - }) - awaitCancel() - askCancel() - - // Output: - // Actor counter-actor-1 spawned. - // Incremented by 1, new value: 1 (from counter-actor-1) - // Incremented by 2, new value: 3 (from counter-actor-1) - // Incremented by 3, new value: 6 (from counter-actor-1) - // Final counter value: 6 (from counter-actor-1) -} diff --git a/actor/example_tell_only_test.go b/actor/example_tell_only_test.go deleted file mode 100644 index a6371310b..000000000 --- a/actor/example_tell_only_test.go +++ /dev/null @@ -1,137 +0,0 @@ -package actor_test - -import ( - "context" - "fmt" - "strings" - "sync" - "time" - - "github.com/lightningnetwork/lnd/actor" - "github.com/lightningnetwork/lnd/fn/v2" -) - -// LogMsg is a message type for the TellOnly example. -type LogMsg struct { - actor.BaseMessage - Text string -} - -// MessageType implements actor.Message. -func (m LogMsg) MessageType() string { return "LogMsg" } - -// LoggerActorBehavior is a simple actor behavior that logs messages. It doesn't -// produce a meaningful response for Ask, so it's a good candidate for TellOnly -// interactions. -type LoggerActorBehavior struct { - mu sync.Mutex - logs []string - actorID string -} - -func NewLoggerActorBehavior(id string) *LoggerActorBehavior { - return &LoggerActorBehavior{actorID: id} -} - -// Receive processes LogMsg messages by appending them to an internal log. The -// response type is 'any' as it's not typically used with Ask. -func (l *LoggerActorBehavior) Receive(ctx context.Context, - msg actor.Message) fn.Result[any] { - - logMessage, ok := msg.(LogMsg) - if !ok { - return fn.Err[any](fmt.Errorf("unexpected message "+ - "type: %s", msg.MessageType())) - } - - l.mu.Lock() - defer l.mu.Unlock() - - entry := fmt.Sprintf("[%s from %s]: %s", time.Now().Format("15:04:05"), - l.actorID, logMessage.Text) - l.logs = append(l.logs, entry) - - // For Tell, the result is often ignored, but we must return something. - return fn.Ok[any](nil) -} - -func (l *LoggerActorBehavior) GetLogs() []string { - l.mu.Lock() - defer l.mu.Unlock() - - copiedLogs := make([]string, len(l.logs)) - copy(copiedLogs, l.logs) - - return copiedLogs -} - -// ExampleTellOnlyRef demonstrates using a TellOnlyRef for fire-and-forget -// messaging with an actor. -func ExampleTellOnlyRef() { - system := actor.NewActorSystem() - defer system.Shutdown() - - // The logger actor doesn't really have a response type for Ask, so we - // use 'any'. - loggerServiceKey := actor.NewServiceKey[actor.Message, any]( - "tell-only-logger-service", - ) - - actorID := "my-logger" - loggerLogic := NewLoggerActorBehavior(actorID) - - // Spawn the actor. - fullRef, err := loggerServiceKey.Spawn(system, actorID, loggerLogic) - if err != nil { - fmt.Printf("Failed to spawn actor: %v\n", err) - return - } - fmt.Printf("Actor %s spawned.\n", fullRef.ID()) - - // Get a TellOnlyRef for the actor. We can get this from the Actor - // instance itself if we had it, or by type assertion if we know the - // underlying ref supports it. Since fullRef is ActorRef[actor.Message, - // any], it already satisfies TellOnlyRef[actor.Message]. - // - // Or, if we had the *Actor instance: tellOnlyLogger = - // actorInstance.TellRef() - var tellOnlyLogger actor.TellOnlyRef[actor.Message] = fullRef - - fmt.Printf("Obtained TellOnlyRef for %s.\n", tellOnlyLogger.ID()) - - // Send messages using Tell. - tellOnlyLogger.Tell( - context.Background(), LogMsg{Text: "First log entry."}, - ) - tellOnlyLogger.Tell( - context.Background(), LogMsg{Text: "Second log entry."}, - ) - - // Allow some time for messages to be processed. - time.Sleep(10 * time.Millisecond) - - // Retrieve logs directly from the behavior for verification in this - // example. In a real scenario, this might not be possible or desired. - logs := loggerLogic.GetLogs() - fmt.Println("Logged entries:") - for _, entry := range logs { - // Strip the timestamp and actor ID for consistent example - // output. Example entry: "[15:04:05 from my-logger]: Actual log - // text" - parts := strings.SplitN(entry, "]: ", 2) - if len(parts) == 2 { - fmt.Println(parts[1]) - } - } - - // Attempting to Ask using tellOnlyLogger would be a compile-time error: - // tellOnlyLogger.Ask(context.Background(), LogMsg{Text: "This would - // fail"}) - - // Output: - // Actor my-logger spawned. - // Obtained TellOnlyRef for my-logger. - // Logged entries: - // First log entry. - // Second log entry. -} diff --git a/actor/func_actor.go b/actor/func_actor.go deleted file mode 100644 index f1580f03f..000000000 --- a/actor/func_actor.go +++ /dev/null @@ -1,45 +0,0 @@ -package actor - -import ( - "context" - - "github.com/lightningnetwork/lnd/fn/v2" -) - -// ActorFunc is a function type that represents an actor which functions purely -// based on a simple function processor. -type ActorFunc[M Message, R any] func(context.Context, M) fn.Result[R] - -// FunctionBehavior adapts a function to the ActorBehavior interface. -type FunctionBehavior[M Message, R any] struct { - fn ActorFunc[M, R] -} - -// NewFunctionBehavior creates a behavior from a function. -func NewFunctionBehavior[M Message, R any]( - fn ActorFunc[M, R]) *FunctionBehavior[M, R] { - - return &FunctionBehavior[M, R]{fn: fn} -} - -// Receive implements ActorBehavior interface for the function. -// -// TODO(roasbeef): just base it off the function direct instead? -func (b *FunctionBehavior[M, R]) Receive(ctx context.Context, - msg M) fn.Result[R] { - - return b.fn(ctx, msg) -} - -// FunctionBehaviorFromSimple adapts a simpler function to the ActorBehavior -// interface. -func FunctionBehaviorFromSimple[M Message, R any]( - sFunc func(M) (R, error)) *FunctionBehavior[M, R] { - - return NewFunctionBehavior( - func(ctx context.Context, msg M) fn.Result[R] { - val, err := sFunc(msg) - return fn.NewResult(val, err) - }, - ) -} diff --git a/actor/future.go b/actor/future.go deleted file mode 100644 index d9edef015..000000000 --- a/actor/future.go +++ /dev/null @@ -1,174 +0,0 @@ -package actor - -import ( - "context" - "sync" - "sync/atomic" - - "github.com/lightningnetwork/lnd/fn/v2" -) - -// promiseImpl is a structure that can be used to complete a Future. It provides -// methods to set the result of an asynchronous operation and to obtain the -// Future interface for consumers. -// The promiseImpl itself is not typically exposed directly to consumers of the -// future's result; they interact with the Future interface. -type promiseImpl[T any] struct { - fut *futureImpl[T] -} - -// CompleteWith completes a promise with the given value, wrapping it as a -// successful result. This is a convenience wrapper over -// promise.Complete(fn.Ok(val)). Safe to call multiple times; only the first -// call takes effect. -func CompleteWith[T any](p Promise[T], val T) { - p.Complete(fn.Ok(val)) -} - -// AwaitFuture blocks until the future resolves or the context is cancelled. -// On success, it returns the resolved value and a nil error. If the context -// is cancelled before the future resolves, it returns the zero value of T and -// the context cancellation error. -func AwaitFuture[T any](ctx context.Context, f Future[T]) (T, error) { - return f.Await(ctx).Unpack() -} - -// NewPromise creates a new Promise. The associated Future, which consumers can -// use to await the result, can be obtained via the Future() method. The Future -// is completed by calling the Complete() method on this Promise. -func NewPromise[T any]() Promise[T] { - return &promiseImpl[T]{ - fut: &futureImpl[T]{ - // done is a channel that will be closed when the future - // is completed. - done: make(chan struct{}), - }, - } -} - -// Future returns the Future interface associated with this Promise. Consumers -// can use this to Await the result or register callbacks. -func (p *promiseImpl[T]) Future() Future[T] { - return p.fut -} - -// Complete attempts to set the result of the future. It returns true if this -// call successfully set the result (i.e., it was the first to complete it), -// and false if the future had already been completed. This ensures that a -// future can only be completed once. The completion involves storing the result -// and signaling any goroutines waiting on the future's done channel. -func (p *promiseImpl[T]) Complete(result fn.Result[T]) bool { - var success bool - p.fut.completeOnce.Do(func() { - p.fut.resultCache.Store(&result) - close(p.fut.done) - - success = true - }) - - return success -} - -// futureImpl is the concrete implementation of the Future interface. It manages -// the state of an asynchronous computation's result. -type futureImpl[T any] struct { - // resultCache stores the fn.Result[T] after the future is completed. - // It's of type atomic.Pointer to allow lock-free reads after completion - // with improved type safety over atomic.Value. - resultCache atomic.Pointer[fn.Result[T]] - - // done is closed once the future is completed, signaling any waiting - // Await calls. - done chan struct{} - - // completeOnce ensures that the logic to set the result and close the - // done channel is executed only once. - completeOnce sync.Once -} - -// Await blocks until the result is available or the passed context is -// cancelled. If the future is already completed, it returns the result -// immediately. Otherwise, it waits for either the future's completion or the -// context's cancellation. -func (f *futureImpl[T]) Await(ctx context.Context) fn.Result[T] { - // First, try a non-blocking load from the cache. If the future is - // already completed, this will return the result directly. - if resPtr := f.resultCache.Load(); resPtr != nil { - return *resPtr - } - - // Wait for either the future to be done or the context to be cancelled. - select { - case <-f.done: - // The future has been completed. Load the result from the - // cache. It must be present now. Load and dereference. - // This load is safe because the 'done' channel is closed only - // after the resultCache is written (ensured by completeOnce). - resPtr := f.resultCache.Load() - - // resPtr should not be nil here as <-f.done was signaled. - return *resPtr - - case <-ctx.Done(): - // The waiting context was cancelled before the future completed. - return fn.Err[T](ctx.Err()) - } -} - -// ThenApply registers a function to transform the result of a future. The -// original future is not modified; a new Future instance representing the -// transformed result is returned. Once the original future completes -// successfully, the provided transformation function (fApply) is called with -// the result. The transformation is applied asynchronously in a new goroutine. -// If the passed context is cancelled while waiting for the -// original future to complete, the returned future will yield the context's -// error. -func (f *futureImpl[T]) ThenApply(ctx context.Context, - fApply func(T) T) Future[T] { - - // Create a new promise for the transformed result. - transformedPromise := NewPromise[T]() - - go func() { - // Await the original future's result, respecting the passed - // context for cancellation. - originalResult := f.Await(ctx) - - // If the original future completed with an error (or Await was - // cancelled by its context), complete the transformed future - // with the same error. - // This also handles the case where originalResult.Await(ctx) - // itself returned ctx.Err(). - if originalResult.IsErr() { - transformedPromise.Complete(originalResult) - return - } - - // Otherwise, the original future completed successfully. Apply the - // transformation function to its result. - originalResult.WhenOk(func(res T) { - newValue := fApply(res) - transformedPromise.Complete(fn.Ok(newValue)) - }) - }() - - return transformedPromise.Future() -} - -// OnComplete registers a function to be called when the result is ready. If the -// passed context is cancelled before the future completes, the callback -// function (cFunc) will be invoked with the context's error. The callback is -// executed in a new goroutine, so it does not block the completion path of the -// original future. -func (f *futureImpl[T]) OnComplete(ctx context.Context, - cFunc func(fn.Result[T])) { - - go func() { - // Await the original future's result, respecting the passed - // context for cancellation. - result := f.Await(ctx) - - // Call the callback function with the result. - cFunc(result) - }() -} diff --git a/actor/future_test.go b/actor/future_test.go deleted file mode 100644 index 519f0d4b0..000000000 --- a/actor/future_test.go +++ /dev/null @@ -1,525 +0,0 @@ -package actor - -import ( - "context" - "fmt" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/stretchr/testify/require" - "pgregory.net/rapid" -) - -// TestFutureAwaitContextCancellation tests that Await respects context -// cancellation if the context is cancelled before the future resolves. -func TestFutureAwaitContextCancellation(t *testing.T) { - t.Parallel() - - rapid.Check(t, func(t *rapid.T) { - // Test cancellation when the Await context is cancelled via - // context.Cancel. The underlying future will not be completed, allowing - // us to test the cancellation path of Await. - prom1 := NewPromise[int]() - fut1 := prom1.Future() - ctx1, cancel1 := context.WithCancel(context.Background()) - - // We'll cancel the future immediately after creating it. - cancel1() - - result1 := fut1.Await(ctx1) - - require.True(t, result1.IsErr()) - require.ErrorIs( - t, result1.Err(), context.Canceled, - "await with immediate cancel", - ) - - // Test cancellation when the Await context times out. The - // underlying future will also not be completed. - prom2 := NewPromise[int]() - fut2 := prom2.Future() - - // Use a very short timeout that will trigger. - ctx2, cancel2 := context.WithTimeout( - context.Background(), 1*time.Nanosecond, - ) - defer cancel2() - - // Await the future; it should fall through to the timeout - // because the future itself is not completed. - result2 := fut2.Await(ctx2) - - require.True(t, result2.IsErr()) - require.ErrorIs( - t, result2.Err(), context.DeadlineExceeded, - "await with timeout", - ) - }) -} - -// TestFutureAwaitFutureCompletes tests that Await returns the future's -// result if the context is not cancelled before the future resolves. -func TestFutureAwaitFutureCompletes(t *testing.T) { - t.Parallel() - - rapid.Check(t, func(t *rapid.T) { - valToSet := rapid.Int().Draw(t, "valToSet") - - // With a 50% chance, configure the test to complete the future - // with an error instead of a successful value. - var errToSet error - if rapid.Bool().Draw(t, "have_error") { - errToSet = fmt.Errorf("err") - } - - promise := NewPromise[int]() - fut := promise.Future() - - // Use a background context for Await, as we expect the future - // to complete normally. - ctx := context.Background() - - // Complete the future in a separate goroutine to simulate an - // asynchronous operation. - go func() { - if errToSet != nil { - promise.Complete(fn.Err[int](errToSet)) - } else { - promise.Complete(fn.Ok(valToSet)) - } - }() - - // Now we'll wait for the future to complete, then verify below - // that the result (value or error) is as expected. - result := fut.Await(ctx) - - if errToSet != nil { - // If an error was set, verify that Await returns that - // specific error. - require.True(t, result.IsErr()) - require.ErrorIs( - t, result.Err(), errToSet, - "await with error", - ) - } else { - // If no error was set, verify that Await returns the - // correct value. - require.False(t, result.IsErr(), "await with value") - - result.WhenOk(func(val int) { - require.Equal( - t, valToSet, val, "await with value", - ) - }) - } - }) -} - -// TestFutureThenApplyContextCancellation tests that ThenApply respects its -// context, yielding a context error if cancelled before the original future -// completes. -func TestFutureThenApplyContextCancellation(t *testing.T) { - t.Parallel() - - rapid.Check(t, func(t *rapid.T) { - // The original future will not be completed in this test case, - // allowing us to specifically test the cancellation behavior of - // the context passed to ThenApply. - originalPromise := NewPromise[int]() - originalFut := originalPromise.Future() - - // Create a context for ThenApply and cancel it immediately. - ctxApply, cancelApply := context.WithCancel( - context.Background(), - ) - cancelApply() - - var transformCalled atomic.Bool - transform := func(i int) int { - transformCalled.Store(true) - return i * 2 - } - - // Register the transformation. The ThenApply operation itself - // will start a goroutine to await the originalFut. - newFut := originalFut.ThenApply(ctxApply, transform) - - // Await the new (transformed) future. Use a background context - // for this Await to isolate the test to the cancellation of - // ctxApply. - result := newFut.Await(context.Background()) - - require.True(t, result.IsErr()) - require.ErrorIs( - t, result.Err(), context.Canceled, - "ThenApply with cancelled context", - ) - require.False( - t, transformCalled.Load(), - "ThenApply transform function called despite "+ - "context cancellation", - ) - }) -} - -// TestFutureThenApplyOriginalFutureCompletes tests ThenApply's behavior when -// the original future completes (with a value or error) before ThenApply's -// context is cancelled. -func TestFutureThenApplyOriginalFutureCompletes(t *testing.T) { - t.Parallel() - - rapid.Check(t, func(t *rapid.T) { - initialVal := rapid.Int().Draw(t, "initialVal") - - // Configure whether the original future completes with an error - // or a successful value. - var originalErr error - if rapid.Bool().Draw(t, "have_error") { - originalErr = fmt.Errorf("original error") - } - - originalPromise := NewPromise[int]() - originalFut := originalPromise.Future() - - // Create a context for ThenApply that should not cancel before - // the original future completes. - ctxApply, cancelApply := context.WithTimeout( - context.Background(), 50*time.Millisecond, - ) - defer cancelApply() - - var transformCalled atomic.Bool - transform := func(i int) int { - transformCalled.Store(true) - return i * 2 - } - - newFut := originalFut.ThenApply(ctxApply, transform) - - // Complete the original future in a separate goroutine to - // simulate asynchrony. - go func() { - if originalErr != nil { - originalPromise.Complete( - fn.Err[int](originalErr), - ) - } else { - originalPromise.Complete(fn.Ok(initialVal)) - } - }() - - // Await our new future which transforms the original future's - // result. Use a background context for this Await. - result := newFut.Await(context.Background()) - - if originalErr != nil { - // If the original future had an error, the transformed - // future should also yield that same error. - require.True(t, result.IsErr()) - require.ErrorIs( - t, result.Err(), originalErr, - "ThenApply with original error", - ) - require.False( - t, transformCalled.Load(), - "ThenApply transform function called despite "+ - "original future having an error", - ) - } else { - // If the original future completed successfully, the - // transformed future should contain the transformed value. - require.False( - t, result.IsErr(), - "ThenApply with original value", - ) - require.True( - t, transformCalled.Load(), - "ThenApply transform function not called for "+ - "successful original future", - ) - - result.WhenOk(func(val int) { - expectedTransformedVal := initialVal * 2 - require.Equal( - t, expectedTransformedVal, val, - "ThenApply with original value", - ) - }) - } - }) -} - -// TestFutureOnCompleteContextCancellation tests that OnComplete's callback -// receives a context error if its context is cancelled before the future -// completes. -func TestFutureOnCompleteContextCancellation(t *testing.T) { - t.Parallel() - - rapid.Check(t, func(t *rapid.T) { - // The original future will not complete in this test, allowing - // us to focus on the cancellation of OnComplete's context. - originalPromise := NewPromise[int]() - originalFut := originalPromise.Future() - - // Create a context for OnComplete and cancel it immediately to - // simulate a premature cancellation. - ctxComplete, cancelComplete := context.WithCancel( - context.Background(), - ) - cancelComplete() - - var wg sync.WaitGroup - wg.Add(1) - var ( - callbackInvoked atomic.Bool - callbackResultValue fn.Result[int] - - // mu is a mutex to protect callbackResultValue as it's - // written by the callback goroutine and read by the - // test goroutine. - mu sync.Mutex - ) - - // Register an OnComplete callback. The callback itself runs in - // a new goroutine started by OnComplete. - originalFut.OnComplete(ctxComplete, func(res fn.Result[int]) { - mu.Lock() - callbackResultValue = res - mu.Unlock() - - callbackInvoked.Store(true) - wg.Done() - }) - - // Use a wait group and a channel to wait for the callback to - // be invoked. - waitChan := make(chan struct{}) - go func() { - wg.Wait() - close(waitChan) - }() - - select { - // The callback should be invoked, even if with a context error. - case <-waitChan: - case <-time.After(50 * time.Millisecond): - require.Fail( - t, "OnComplete callback timed out waiting "+ - "for execution after context cancel", - ) - } - - require.True( - t, callbackInvoked.Load(), - "OnComplete callback not invoked", - ) - - mu.Lock() - defer mu.Unlock() - - // Verify that the callback received a context.Canceled error - // because its context (ctxComplete) was cancelled. - require.True(t, callbackResultValue.IsErr()) - require.ErrorIs( - t, callbackResultValue.Err(), context.Canceled, - "OnComplete with cancelled context", - ) - }) -} - -// TestFutureOnCompleteFutureCompletes tests OnComplete's behavior when the -// future completes (with value or error) before its context is cancelled. -func TestFutureOnCompleteFutureCompletes(t *testing.T) { - t.Parallel() - - rapid.Check(t, func(t *rapid.T) { - valToSet := rapid.Int().Draw(t, "valToSet") - - // Configure whether the original future completes with an error - // or a successful value. - var originalErr error - if rapid.Bool().Draw(t, "have_error") { - originalErr = fmt.Errorf("original error") - } - - originalPromise := NewPromise[int]() - originalFut := originalPromise.Future() - - // Use a background context for OnComplete, as we expect the - // future to complete normally. - ctxComplete := context.Background() - - var wg sync.WaitGroup - wg.Add(1) - - var ( - callbackInvoked atomic.Bool - callbackResultValue fn.Result[int] - mu sync.Mutex - ) - - // Register an OnComplete callback. This callback will execute - // once the originalFut completes. - originalFut.OnComplete(ctxComplete, func(res fn.Result[int]) { - mu.Lock() - callbackResultValue = res - mu.Unlock() - - callbackInvoked.Store(true) - - wg.Done() - }) - - // Complete the original future in a separate goroutine to - // simulate an asynchronous operation. - go func() { - if originalErr != nil { - originalPromise.Complete( - fn.Err[int](originalErr), - ) - } else { - originalPromise.Complete(fn.Ok(valToSet)) - } - }() - - // Use a wait group and a channel to wait for the callback's - // execution. - waitChan := make(chan struct{}) - go func() { - wg.Wait() - close(waitChan) - }() - - select { - // The callback should be invoked as the future completes. - case <-waitChan: - case <-time.After(50 * time.Millisecond): - require.Fail( - t, "OnComplete callback timed out waiting "+ - "for execution", - ) - } - - require.True(t, callbackInvoked.Load()) - - mu.Lock() - defer mu.Unlock() - - // Verify that the callback received the correct result (either - // the error or the value from the completed future). - if originalErr != nil { - require.True(t, callbackResultValue.IsErr()) - require.ErrorIs( - t, callbackResultValue.Err(), originalErr, - "OnComplete with error", - ) - } else { - require.False( - t, callbackResultValue.IsErr(), - "OnComplete with value", - ) - callbackResultValue.WhenOk(func(val int) { - require.Equal( - t, valToSet, val, - "OnComplete with value", - ) - }) - } - }) -} - -// TestCompleteWith verifies that CompleteWith resolves a promise with the -// supplied value, that the resolution is immediately visible on the Future, and -// that a second call is a safe no-op (idempotency inherited from Complete). -func TestCompleteWith(t *testing.T) { - t.Parallel() - - // Normal completion — value should be visible on the future. - promise := NewPromise[int]() - CompleteWith(promise, 42) - - result := promise.Future().Await(context.Background()) - require.False(t, result.IsErr()) - result.WhenOk(func(v int) { - require.Equal(t, 42, v) - }) - - // Second call must be a no-op; the future must still hold 42. - CompleteWith(promise, 99) - - result2 := promise.Future().Await(context.Background()) - require.False(t, result2.IsErr()) - result2.WhenOk(func(v int) { - require.Equal(t, 42, v, "second CompleteWith must not overwrite") - }) -} - -// TestAwaitFuture verifies that AwaitFuture unpacks a resolved future into a -// (value, nil) pair, that a future completed with fn.Err is reported as a -// (zero, err) pair, and that context cancellation before resolution is -// reported as a (zero, ctx.Err()) pair. -func TestAwaitFuture(t *testing.T) { - t.Parallel() - - // Resolved future — should return the value with a nil error. - promise := NewPromise[string]() - CompleteWith(promise, "hello") - - val, err := AwaitFuture(context.Background(), promise.Future()) - require.NoError(t, err) - require.Equal(t, "hello", val) - - // Future completed with fn.Err — should surface the error as the - // second return value with the zero string value. - sentinel := fmt.Errorf("result-level error") - errPromise := NewPromise[string]() - errPromise.Complete(fn.Err[string](sentinel)) - - val3, err3 := AwaitFuture(context.Background(), errPromise.Future()) - require.ErrorIs(t, err3, sentinel) - require.Equal(t, "", val3, "zero value expected on fn.Err result") - - // Cancelled context — should return the zero value and ctx.Err(). - unresolved := NewPromise[string]() - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - val2, err2 := AwaitFuture(ctx, unresolved.Future()) - require.ErrorIs(t, err2, context.Canceled) - require.Equal(t, "", val2, "zero value expected on cancellation") -} - -func TestPromiseCompleteIdempotency(t *testing.T) { - t.Parallel() - - promise := NewPromise[string]() - future := promise.Future() - - // First completion should succeed. - firstResult := fn.Ok("first-value") - ok := promise.Complete(firstResult) - require.True(t, ok, "first Complete should return true") - - // Second completion with a different value should be ignored. - secondResult := fn.Ok("second-value") - ok = promise.Complete(secondResult) - require.False(t, ok, "second Complete should return false") - - // Third completion with an error should also be ignored. - thirdResult := fn.Err[string](fmt.Errorf("should be ignored")) - ok = promise.Complete(thirdResult) - require.False(t, ok, "third Complete should return false") - - // The future should contain the first value. - result := future.Await(context.Background()) - require.False(t, result.IsErr(), "future should not be an error") - result.WhenOk(func(val string) { - require.Equal( - t, "first-value", val, - "future should contain the first completion value", - ) - }) -} diff --git a/actor/go.mod b/actor/go.mod deleted file mode 100644 index f8ad71a5e..000000000 --- a/actor/go.mod +++ /dev/null @@ -1,27 +0,0 @@ -module github.com/lightningnetwork/lnd/actor - -go 1.25.11 - -require ( - github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084 - github.com/lightningnetwork/lnd/fn/v2 v2.0.8 - github.com/lightningnetwork/lnd/queue v1.1.1 - github.com/stretchr/testify v1.8.1 - pgregory.net/rapid v1.2.0 -) - -require ( - github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/lightningnetwork/lnd/ticker v1.0.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/exp v0.0.0-20231226003508-02704c960a9b // indirect - golang.org/x/sync v0.7.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) - -replace github.com/lightningnetwork/lnd/queue => ../queue - -replace github.com/lightningnetwork/lnd/ticker => ../ticker - -replace github.com/lightningnetwork/lnd/fn/v2 => ../fn diff --git a/actor/go.sum b/actor/go.sum deleted file mode 100644 index be484c5cb..000000000 --- a/actor/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c h1:4HxD1lBUGUddhzgaNgrCPsFWd7cGYNpeFUgd9ZIgyM0= -github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ= -github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084 h1:y3bvkt8ki0KX35eUEU8XShRHusz1S+55QwXUTmxn888= -github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4= -golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= -pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/actor/interface.go b/actor/interface.go deleted file mode 100644 index acc7bf8a0..000000000 --- a/actor/interface.go +++ /dev/null @@ -1,124 +0,0 @@ -package actor - -import ( - "context" - "errors" - "fmt" - - "github.com/lightningnetwork/lnd/fn/v2" -) - -// ErrActorTerminated indicates that an operation failed because the target -// actor was terminated or in the process of shutting down. -var ErrActorTerminated = fmt.Errorf("actor terminated") - -// ErrMessageDropped indicates that a message was dropped by the mailbox's -// backpressure mechanism (e.g., RED-style load shedding). -var ErrMessageDropped = errors.New("message dropped by backpressure") - -// ErrEmptyActorID is returned when an actor is created with an empty ID. -var ErrEmptyActorID = fmt.Errorf("actor ID must not be empty") - -// ErrNilBehavior is returned when an actor is created with a nil behavior. -var ErrNilBehavior = fmt.Errorf("actor behavior must not be nil") - -// ErrDuplicateActorID is returned when attempting to register an actor with an -// ID that is already in use within the actor system. -var ErrDuplicateActorID = fmt.Errorf("actor ID already registered") - -// BaseMessage is a helper struct that can be embedded in message types defined -// outside the actor package to satisfy the Message interface's unexported -// messageMarker method. -type BaseMessage struct{} - -// messageMarker implements the unexported method for the Message interface, -// allowing types that embed BaseMessage to satisfy the Message interface. -func (BaseMessage) messageMarker() {} - -// Message is a sealed interface for actor messages. Actors will receive -// messages conforming to this interface. The interface is "sealed" by the -// unexported messageMarker method, meaning only types that can satisfy it -// (e.g., by embedding BaseMessage or being in the same package) can be -// Messages. -type Message interface { - // messageMarker is a private method that makes this a sealed interface - // (see BaseMessage for embedding). - messageMarker() - - // MessageType returns the type name of the message for - // routing/filtering. - MessageType() string -} - -// Future represents the result of an asynchronous computation. It allows -// consumers to wait for the result (Await), apply transformations upon -// completion (ThenApply), or register a callback to be executed when the -// result is available (OnComplete). -type Future[T any] interface { - // Await blocks until the result is available or the context is - // cancelled, then returns it. - Await(ctx context.Context) fn.Result[T] - - // ThenApply registers a function to transform the result of a future. - // The original future is not modified, a new instance of the future is - // returned. If the passed context is cancelled while waiting for the - // original future to complete, the new future will complete with the - // context's error. - ThenApply(ctx context.Context, fn func(T) T) Future[T] - - // OnComplete registers a function to be called when the result of the - // future is ready. If the passed context is cancelled before the future - // completes, the callback function will be invoked with the context's - // error. - OnComplete(ctx context.Context, fn func(fn.Result[T])) -} - -// Promise is an interface that allows for the completion of an associated -// Future. It provides a way to set the result of an asynchronous operation. -// The producer of an asynchronous result uses a Promise to set the outcome, -// while consumers use the associated Future to retrieve it. -type Promise[T any] interface { - // Future returns the Future interface associated with this Promise. - // Consumers can use this to Await the result or register callbacks. - Future() Future[T] - - // Complete attempts to set the result of the future. It returns true if - // this call successfully set the result (i.e., it was the first to - // complete it), and false if the future had already been completed. - Complete(result fn.Result[T]) bool -} - -// TellOnlyRef is a reference to an actor that only supports "tell" operations. -// This is useful for scenarios where only fire-and-forget message passing is -// needed, or to restrict capabilities. -type TellOnlyRef[M Message] interface { - // Tell sends a message without waiting for a response. If the - // context is cancelled before the message can be sent to the actor's - // mailbox, the message may be dropped. - Tell(ctx context.Context, msg M) - - // ID returns the unique identifier for this actor. - ID() string -} - -// ActorRef is a reference to an actor that supports both "tell" and "ask" -// operations. It embeds TellOnlyRef and adds the Ask method for -// request-response interactions. -type ActorRef[M Message, R any] interface { - TellOnlyRef[M] - - // Ask sends a message and returns a Future for the response. - // The Future will be completed with the actor's reply or an error - // if the operation fails (e.g., context cancellation before send). - Ask(ctx context.Context, msg M) Future[R] -} - -// ActorBehavior defines the logic for how an actor processes incoming messages. -// It is a strategy interface that encapsulates the actor's reaction to -// messages. -type ActorBehavior[M Message, R any] interface { - // Receive processes a message and returns a Result. The provided - // context is the actor's internal context, which can be used to - // detect actor shutdown requests. - Receive(actorCtx context.Context, msg M) fn.Result[R] -} diff --git a/actor/log.go b/actor/log.go deleted file mode 100644 index 77da9070d..000000000 --- a/actor/log.go +++ /dev/null @@ -1,12 +0,0 @@ -package actor - -import "github.com/btcsuite/btclog/v2" - -// log is a logger that is initialized as disabled. This means the package will -// not perform any logging by default until a logger is set. -var log = btclog.Disabled - -// UseLogger uses a specified Logger to output package logging info. -func UseLogger(logger btclog.Logger) { - log = logger -} diff --git a/actor/mailbox.go b/actor/mailbox.go deleted file mode 100644 index 4d15f434c..000000000 --- a/actor/mailbox.go +++ /dev/null @@ -1,176 +0,0 @@ -package actor - -import ( - "context" - "iter" - "sync" - "sync/atomic" -) - -// Mailbox represents the message queue for an actor. It provides methods for -// sending messages and receiving them via an iterator pattern. -type Mailbox[M Message, R any] interface { - // Send attempts to send an envelope to the mailbox with context-based - // cancellation. Returns true if sent successfully, false if the - // context was cancelled or the mailbox is closed. - Send(ctx context.Context, env envelope[M, R]) bool - - // TrySend attempts to send without blocking. Returns true if the - // envelope was sent, false if the mailbox is full or closed. - TrySend(env envelope[M, R]) bool - - // Receive returns an iterator for consuming messages from the mailbox. - // The iterator will yield messages until the mailbox is closed or the - // context is cancelled. - Receive(ctx context.Context) iter.Seq[envelope[M, R]] - - // Close closes the mailbox, preventing new messages from being sent. - // Any remaining messages can still be consumed via Receive. - Close() - - // IsClosed returns true if the mailbox has been closed. - IsClosed() bool - - // Drain returns an iterator that yields all remaining messages in the - // mailbox after it has been closed. This is useful for cleanup. - Drain() iter.Seq[envelope[M, R]] -} - -// ChannelMailbox is a channel-based implementation of the Mailbox interface. -type ChannelMailbox[M Message, R any] struct { - ch chan envelope[M, R] - closed atomic.Bool - - // mu protects Send/TrySend operations to prevent send-on-closed-channel - // panics. Close() acquires write lock, Send/TrySend acquire read lock. - mu sync.RWMutex - - // closeOnce ensures Close() executes exactly once. - closeOnce sync.Once - - // actorCtx is the actor's context for lifecycle management. - actorCtx context.Context -} - -// NewChannelMailbox creates a new channel-based mailbox with the specified -// buffer capacity and actor context. -func NewChannelMailbox[M Message, R any](actorCtx context.Context, - capacity int) *ChannelMailbox[M, R] { - - if capacity <= 0 { - capacity = 1 - } - return &ChannelMailbox[M, R]{ - ch: make(chan envelope[M, R], capacity), - actorCtx: actorCtx, - } -} - -// Send implements Mailbox.Send with context-aware blocking send. -func (m *ChannelMailbox[M, R]) Send(ctx context.Context, - env envelope[M, R]) bool { - - m.mu.RLock() - defer m.mu.RUnlock() - - if m.IsClosed() { - return false - } - - select { - case m.ch <- env: - return true - case <-ctx.Done(): - return false - case <-m.actorCtx.Done(): - // Actor is shutting down. - return false - } -} - -// TrySend implements Mailbox.TrySend with non-blocking send. -func (m *ChannelMailbox[M, R]) TrySend(env envelope[M, R]) bool { - m.mu.RLock() - defer m.mu.RUnlock() - - if m.IsClosed() { - return false - } - - select { - case m.ch <- env: - return true - default: - return false - } -} - -// Receive implements Mailbox.Receive using iter.Seq pattern. -func (m *ChannelMailbox[M, R]) Receive( - ctx context.Context) iter.Seq[envelope[M, R]] { - return func(yield func(envelope[M, R]) bool) { - for { - select { - case env, ok := <-m.ch: - if !ok { - return - } - - if !yield(env) { - return - } - - case <-ctx.Done(): - return - - case <-m.actorCtx.Done(): - return - } - } - } -} - -// Close implements Mailbox.Close. -func (m *ChannelMailbox[M, R]) Close() { - m.closeOnce.Do(func() { - m.mu.Lock() - defer m.mu.Unlock() - - m.closed.Store(true) - - close(m.ch) - }) -} - -// IsClosed implements Mailbox.IsClosed. -func (m *ChannelMailbox[M, R]) IsClosed() bool { - return m.closed.Load() -} - -// Drain implements Mailbox.Drain for cleanup after close. -func (m *ChannelMailbox[M, R]) Drain() iter.Seq[envelope[M, R]] { - return func(yield func(envelope[M, R]) bool) { - // Only drain if closed. - if !m.IsClosed() { - return - } - - // Drain all remaining messages from the channel. - for { - select { - case env, ok := <-m.ch: - // Channel closed, nothing left to drain. - if !ok { - return - } - - if !yield(env) { - return - } - default: - // Channel empty, done draining. - return - } - } - } -} diff --git a/actor/mailbox_test.go b/actor/mailbox_test.go deleted file mode 100644 index b96d5e4de..000000000 --- a/actor/mailbox_test.go +++ /dev/null @@ -1,593 +0,0 @@ -package actor - -import ( - "context" - "sync" - "testing" - - "github.com/stretchr/testify/require" -) - -// TestMessage is a test message type that embeds BaseMessage. -type TestMessage struct { - BaseMessage - Value int -} - -// MessageType returns the type name of the message for routing/filtering. -func (tm TestMessage) MessageType() string { - return "TestMessage" -} - -// TestChannelMailboxSend tests the Send method of ChannelMailbox. -func TestChannelMailboxSend(t *testing.T) { - t.Run("successful send", func(t *testing.T) { - mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 10) - ctx := context.Background() - env := envelope[TestMessage, int]{ - message: TestMessage{Value: 42}, - promise: nil, - } - - sent := mailbox.Send(ctx, env) - require.True(t, sent, "Send should succeed") - }) - - t.Run("send with cancelled context", func(t *testing.T) { - mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 1) - // Fill the mailbox first. - env := envelope[TestMessage, int]{ - message: TestMessage{Value: 42}, - promise: nil, - } - mailbox.TrySend(env) - - ctx, cancel := context.WithCancel(context.Background()) - // Cancel immediately. - cancel() - - env2 := envelope[TestMessage, int]{ - message: TestMessage{Value: 43}, - promise: nil, - } - - sent := mailbox.Send(ctx, env2) - require.False(t, sent, "Send should fail with cancelled context") - }) - - t.Run("send to closed mailbox", func(t *testing.T) { - mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 10) - mailbox.Close() - - ctx := context.Background() - env := envelope[TestMessage, int]{ - message: TestMessage{Value: 42}, - promise: nil, - } - - sent := mailbox.Send(ctx, env) - require.False(t, sent, "Send should fail on closed mailbox") - }) -} - -// TestChannelMailboxTrySend tests the TrySend method of ChannelMailbox. -func TestChannelMailboxTrySend(t *testing.T) { - t.Run("successful try send", func(t *testing.T) { - mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 10) - env := envelope[TestMessage, int]{ - message: TestMessage{Value: 42}, - promise: nil, - } - - sent := mailbox.TrySend(env) - require.True(t, sent, "TrySend should succeed") - }) - - t.Run("try send to full mailbox", func(t *testing.T) { - mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 1) - env := envelope[TestMessage, int]{ - message: TestMessage{Value: 42}, - promise: nil, - } - - // Fill the mailbox. - sent := mailbox.TrySend(env) - require.True(t, sent, "First TrySend should succeed") - - // Try to send again - should fail. - sent = mailbox.TrySend(env) - require.False(t, sent, "TrySend should fail on full mailbox") - }) - - t.Run("try send to closed mailbox", func(t *testing.T) { - mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 10) - mailbox.Close() - - env := envelope[TestMessage, int]{ - message: TestMessage{Value: 42}, - promise: nil, - } - - sent := mailbox.TrySend(env) - require.False(t, sent, "TrySend should fail on closed mailbox") - }) -} - -// TestChannelMailboxReceive tests the Receive method of ChannelMailbox. -func TestChannelMailboxReceive(t *testing.T) { - t.Run("receive messages", func(t *testing.T) { - mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 10) - ctx := context.Background() - - // Send some messages. - for i := 0; i < 3; i++ { - env := envelope[TestMessage, int]{ - message: TestMessage{Value: i}, - promise: nil, - } - mailbox.Send(ctx, env) - } - - // Start receiving in a goroutine. - var received []int - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - for env := range mailbox.Receive(ctx) { - received = append(received, env.message.Value) - } - }() - - // Close the mailbox after sending all messages. - mailbox.Close() - wg.Wait() - - require.Len(t, received, 3, "Should receive 3 messages") - require.Equal(t, []int{0, 1, 2}, received, "Should receive messages in order") - }) - - t.Run("receive with cancelled context", func(t *testing.T) { - mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 10) - ctx, cancel := context.WithCancel(context.Background()) - - // Send a message. - env := envelope[TestMessage, int]{ - message: TestMessage{Value: 42}, - promise: nil, - } - mailbox.Send(context.Background(), env) - - // Start receiving. - var received int - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - for env := range mailbox.Receive(ctx) { - received++ - _ = env - } - }() - - // Cancel the context. - cancel() - wg.Wait() - - // Might receive 0 or 1 message depending on timing. - require.LessOrEqual(t, received, 1, - "Should stop receiving after context cancel") - }) -} - -// TestChannelMailboxClose tests the Close and IsClosed methods. -func TestChannelMailboxClose(t *testing.T) { - mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 10) - - require.False(t, mailbox.IsClosed(), "Mailbox should not be closed initially") - - mailbox.Close() - require.True(t, mailbox.IsClosed(), "Mailbox should be closed after Close()") - - // Closing again should be safe. - mailbox.Close() - require.True(t, mailbox.IsClosed(), "Mailbox should remain closed") -} - -// TestChannelMailboxDrain tests the Drain method of ChannelMailbox. -func TestChannelMailboxDrain(t *testing.T) { - mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 10) - ctx := context.Background() - - // Send some messages. - for i := 0; i < 3; i++ { - env := envelope[TestMessage, int]{ - message: TestMessage{Value: i}, - promise: nil, - } - mailbox.Send(ctx, env) - } - - // Close the mailbox. - mailbox.Close() - - // Drain messages. - var drained []int - for env := range mailbox.Drain() { - drained = append(drained, env.message.Value) - } - - require.Len(t, drained, 3, "Should drain 3 messages") - require.Equal(t, []int{0, 1, 2}, drained, "Should drain messages in order") -} - -// TestChannelMailboxConcurrent tests concurrent operations on ChannelMailbox. -func TestChannelMailboxConcurrent(t *testing.T) { - mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 100) - ctx := context.Background() - - const numSenders = 10 - const messagesPerSender = 100 - - var wg sync.WaitGroup - - // Start multiple senders. - for i := 0; i < numSenders; i++ { - wg.Add(1) - go func(senderID int) { - defer wg.Done() - for j := 0; j < messagesPerSender; j++ { - env := envelope[TestMessage, int]{ - message: TestMessage{Value: senderID*1000 + j}, - promise: nil, - } - mailbox.Send(ctx, env) - } - }(i) - } - - // Start receiver. - received := make([]int, 0, numSenders*messagesPerSender) - var receiverWg sync.WaitGroup - receiverWg.Add(1) - go func() { - defer receiverWg.Done() - for env := range mailbox.Receive(ctx) { - received = append(received, env.message.Value) - } - }() - - // Wait for all senders to complete. - wg.Wait() - - // Close the mailbox now that all sends are complete. - mailbox.Close() - receiverWg.Wait() - - require.Len(t, received, numSenders*messagesPerSender, - "Should receive all messages") -} - -// TestChannelMailboxZeroCapacity tests that zero capacity defaults to 1. -func TestChannelMailboxZeroCapacity(t *testing.T) { - mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 0) - - // Should default to capacity of 1. - env := envelope[TestMessage, int]{ - message: TestMessage{Value: 42}, - promise: nil, - } - - sent := mailbox.TrySend(env) - require.True(t, sent, "Should be able to send one message") - - // Second send should fail (mailbox full). - sent = mailbox.TrySend(env) - require.False(t, sent, "Second send should fail on full mailbox") -} - -// TestChannelMailboxActorContext tests that the mailbox respects the actor's -// context for cancellation. -func TestChannelMailboxActorContext(t *testing.T) { - t.Run("send respects actor context", func(t *testing.T) { - actorCtx, actorCancel := context.WithCancel(context.Background()) - mailbox := NewChannelMailbox[TestMessage, int](actorCtx, 1) - - // Fill the mailbox. - env := envelope[TestMessage, int]{ - message: TestMessage{Value: 42}, - promise: nil, - } - mailbox.TrySend(env) - - // Cancel the actor context. - actorCancel() - - // Try to send with a fresh caller context - should fail due to - // actor context cancellation. - callerCtx := context.Background() - env2 := envelope[TestMessage, int]{ - message: TestMessage{Value: 43}, - promise: nil, - } - - sent := mailbox.Send(callerCtx, env2) - require.False(t, sent, "Send should fail when actor context is cancelled") - }) - - t.Run("receive respects actor context", func(t *testing.T) { - actorCtx, actorCancel := context.WithCancel(context.Background()) - mailbox := NewChannelMailbox[TestMessage, int](actorCtx, 10) - - // Send a message. - env := envelope[TestMessage, int]{ - message: TestMessage{Value: 42}, - promise: nil, - } - mailbox.Send(context.Background(), env) - - // Start receiving with a fresh context. - callerCtx := context.Background() - var received int - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - for env := range mailbox.Receive(callerCtx) { - received++ - _ = env - } - }() - - // Cancel the actor context. - actorCancel() - wg.Wait() - - // Should have stopped receiving due to actor context cancellation. - require.LessOrEqual(t, received, 1, - "Should stop receiving when actor context is cancelled") - }) -} - -// TestMailboxConcurrentSendAndClose tests concurrent Send and Close operations -// to ensure no race conditions or panics occur. -func TestMailboxConcurrentSendAndClose(t *testing.T) { - const numSenders = 20 - const sendsPerSender = 100 - - mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 100) - ctx := context.Background() - - var wg sync.WaitGroup - - // Start receiver to drain messages. - var recvWg sync.WaitGroup - recvWg.Add(1) - go func() { - defer recvWg.Done() - for range mailbox.Receive(ctx) { - // Just drain. - } - }() - - // Start multiple senders. - for i := 0; i < numSenders; i++ { - wg.Add(1) - go func(senderID int) { - defer wg.Done() - for j := 0; j < sendsPerSender; j++ { - env := envelope[TestMessage, int]{ - message: TestMessage{Value: senderID*1000 + j}, - promise: nil, - } - // Send may fail if mailbox closes, that's ok. - mailbox.Send(ctx, env) - } - }(i) - } - - // Concurrently close the mailbox multiple times from different - // goroutines. - for i := 0; i < 5; i++ { - wg.Add(1) - go func() { - defer wg.Done() - mailbox.Close() - }() - } - - wg.Wait() - recvWg.Wait() - - // Mailbox should be closed. - require.True(t, mailbox.IsClosed(), "Mailbox should be closed") - - // Further sends should fail without panic. - env := envelope[TestMessage, int]{ - message: TestMessage{Value: 999}, - promise: nil, - } - sent := mailbox.Send(ctx, env) - require.False(t, sent, "Send should fail on closed mailbox") -} - -// TestMailboxConcurrentTrySendAndClose tests concurrent TrySend and Close -// operations to ensure no race conditions or panics occur. -func TestMailboxConcurrentTrySendAndClose(t *testing.T) { - const numSenders = 20 - const sendsPerSender = 100 - - mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 10) - - var wg sync.WaitGroup - - // Start multiple senders using TrySend. - for i := 0; i < numSenders; i++ { - wg.Add(1) - go func(senderID int) { - defer wg.Done() - for j := 0; j < sendsPerSender; j++ { - env := envelope[TestMessage, int]{ - message: TestMessage{Value: senderID*1000 + j}, - promise: nil, - } - // TrySend may fail if mailbox is full or closed. - mailbox.TrySend(env) - } - }(i) - } - - // Concurrently close the mailbox. - for i := 0; i < 5; i++ { - wg.Add(1) - go func() { - defer wg.Done() - mailbox.Close() - }() - } - - wg.Wait() - - // Mailbox should be closed. - require.True(t, mailbox.IsClosed(), "Mailbox should be closed") - - // Further sends should fail without panic. - env := envelope[TestMessage, int]{ - message: TestMessage{Value: 999}, - promise: nil, - } - sent := mailbox.TrySend(env) - require.False(t, sent, "TrySend should fail on closed mailbox") -} - -// TestMailboxMultipleCloseCallers tests that multiple goroutines calling -// Close() simultaneously don't cause panics or issues. -func TestMailboxMultipleCloseCallers(t *testing.T) { - const numClosers = 100 - - mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 10) - - var wg sync.WaitGroup - - // Start many goroutines all trying to close the mailbox. - for i := 0; i < numClosers; i++ { - wg.Add(1) - go func() { - defer wg.Done() - mailbox.Close() - }() - } - - wg.Wait() - - // Mailbox should be closed exactly once. - require.True(t, mailbox.IsClosed(), "Mailbox should be closed") - - // Calling Close again should be safe. - mailbox.Close() - require.True(t, mailbox.IsClosed(), "Mailbox should remain closed") -} - -// TestMailboxCloseWhileSending tests closing the mailbox while multiple -// senders are actively sending messages. -func TestMailboxCloseWhileSending(t *testing.T) { - const numSenders = 10 - const sendsPerSender = 1000 - - mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 100) - ctx := context.Background() - - var sendWg sync.WaitGroup - - // Start multiple senders. - for i := 0; i < numSenders; i++ { - sendWg.Add(1) - go func(senderID int) { - defer sendWg.Done() - for j := 0; j < sendsPerSender; j++ { - env := envelope[TestMessage, int]{ - message: TestMessage{Value: senderID*1000 + j}, - promise: nil, - } - // Send may fail after close, that's expected. - mailbox.Send(ctx, env) - } - }(i) - } - - // Start receiver to drain messages. - var recvWg sync.WaitGroup - recvWg.Add(1) - receivedCount := 0 - go func() { - defer recvWg.Done() - for range mailbox.Receive(ctx) { - receivedCount++ - } - }() - - // Close mailbox while sends are happening. - mailbox.Close() - - sendWg.Wait() - recvWg.Wait() - - // Should have received at least some messages (exact count depends on - // timing). - t.Logf("Received %d messages before close", receivedCount) - - // Mailbox should be closed. - require.True(t, mailbox.IsClosed(), "Mailbox should be closed") -} - -// TestMailboxStressTest performs a high-concurrency stress test with multiple -// senders, receivers, and close operations. -func TestMailboxStressTest(t *testing.T) { - const numSenders = 50 - const numReceivers = 5 - const sendsPerSender = 200 - - mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 200) - ctx := context.Background() - - var sendWg sync.WaitGroup - - // Start multiple senders. - for i := 0; i < numSenders; i++ { - sendWg.Add(1) - go func(senderID int) { - defer sendWg.Done() - for j := 0; j < sendsPerSender; j++ { - env := envelope[TestMessage, int]{ - message: TestMessage{Value: senderID*1000 + j}, - promise: nil, - } - mailbox.Send(ctx, env) - } - }(i) - } - - // Start multiple receivers. - var recvWg sync.WaitGroup - for i := 0; i < numReceivers; i++ { - recvWg.Add(1) - go func() { - defer recvWg.Done() - for range mailbox.Receive(ctx) { - // Just drain messages. - } - }() - } - - // Wait for all sends to complete. - sendWg.Wait() - - // Close mailbox. - mailbox.Close() - - // Wait for all receivers to finish. - recvWg.Wait() - - // Mailbox should be closed. - require.True(t, mailbox.IsClosed(), "Mailbox should be closed") -} diff --git a/actor/router.go b/actor/router.go deleted file mode 100644 index 87e2dae44..000000000 --- a/actor/router.go +++ /dev/null @@ -1,154 +0,0 @@ -package actor - -import ( - "context" - "errors" - "sync/atomic" - - "github.com/lightningnetwork/lnd/fn/v2" -) - -// ErrNoActorsAvailable is returned when a router cannot find any actors -// registered for its service key to forward a message to. -var ErrNoActorsAvailable = errors.New("no actors available for service key") - -// Compile-time assertion that Router satisfies the ActorRef interface. -var _ ActorRef[Message, any] = (*Router[Message, any])(nil) - -// RoutingStrategy defines the interface for selecting an actor from a list of -// available actors. -// The M (Message) and R (Response) type parameters ensure that the strategy -// is compatible with the types of actors it will be selecting. -type RoutingStrategy[M Message, R any] interface { - // Select chooses an ActorRef from the provided slice. It returns the - // selected actor or an error if no actor can be selected (e.g., if the - // list is empty or another strategy-specific issue occurs). - Select(refs []ActorRef[M, R]) (ActorRef[M, R], error) -} - -// RoundRobinStrategy implements a round-robin selection strategy. It is generic -// over M and R to match the RoutingStrategy interface, though its logic doesn't -// depend on these types directly for the selection mechanism itself. -type RoundRobinStrategy[M Message, R any] struct { - // index is used to pick the next actor in a round-robin fashion. It - // must be accessed atomically to ensure thread-safety if multiple - // goroutines use the same strategy instance (which they will via the - // router). - index uint64 -} - -// NewRoundRobinStrategy creates a new RoundRobinStrategy, initialized for -// round-robin selection. -func NewRoundRobinStrategy[M Message, R any]() *RoundRobinStrategy[M, R] { - return &RoundRobinStrategy[M, R]{} -} - -// Select picks an actor from the list using a round-robin algorithm. -func (s *RoundRobinStrategy[M, R]) Select( - refs []ActorRef[M, R], -) (ActorRef[M, R], error) { - if len(refs) == 0 { - return nil, ErrNoActorsAvailable - } - - // Atomically increment and get the current index for selection. - // We subtract 1 because AddUint64 returns the new value (which is - // 1-based for the first call after initialization to 0), and slice - // indexing is 0-based. - idx := atomic.AddUint64(&s.index, 1) - 1 - selectedRef := refs[idx%uint64(len(refs))] - - return selectedRef, nil -} - -// Router is a message-dispatching component that fronts multiple actors -// registered under a specific ServiceKey. It uses a RoutingStrategy to -// distribute messages to one of the available actors. It is generic over M -// (Message type) and R (Response type) to match the actors it routes to. -type Router[M Message, R any] struct { - receptionist *Receptionist - serviceKey ServiceKey[M, R] - strategy RoutingStrategy[M, R] - dlo ActorRef[Message, any] // Dead Letter Office reference. -} - -// NewRouter creates a new Router for a given service key and strategy. The -// receptionist is used to discover actors registered with the service key. -// The router itself is not an actor but a message dispatcher that behaves like -// an ActorRef from the sender's perspective. -func NewRouter[M Message, R any](receptionist *Receptionist, - key ServiceKey[M, R], strategy RoutingStrategy[M, R], - dlo ActorRef[Message, any]) *Router[M, R] { - - return &Router[M, R]{ - receptionist: receptionist, - serviceKey: key, - strategy: strategy, - dlo: dlo, - } -} - -// getActor dynamically finds available actors for the service key and selects -// one using the configured strategy. This method is called internally by Tell -// and Ask on each invocation to ensure up-to-date actor discovery. -func (r *Router[M, R]) getActor() (ActorRef[M, R], error) { - // Discover available actors from the receptionist. - availableActors := FindInReceptionist(r.receptionist, r.serviceKey) - if len(availableActors) == 0 { - return nil, ErrNoActorsAvailable - } - - // Select one actor using the strategy. - return r.strategy.Select(availableActors) -} - -// Tell sends a message to one of the actors managed by the router, selected by -// the routing strategy. If no actors are available or the send context is -// cancelled before the message can be enqueued in the target actor's mailbox, -// the message may be dropped. Errors during actor selection (e.g., -// ErrNoActorsAvailable) are currently not propagated from Tell, aligning with -// its fire-and-forget nature. Such errors could be logged internally if needed. -func (r *Router[M, R]) Tell(ctx context.Context, msg M) { - selectedActor, err := r.getActor() - if err != nil { - // If no actors are available for the service, and a DLO is - // configured, forward the message there. - if errors.Is(err, ErrNoActorsAvailable) && r.dlo != nil { - r.dlo.Tell(context.Background(), msg) - } else { - log.Warnf("Router(%s): message %s dropped "+ - "(no actors available, no DLO configured)", - r.serviceKey.name, msg.MessageType()) - } - - return - } - - selectedActor.Tell(ctx, msg) -} - -// Ask sends a message to one of the actors managed by the router, selected by -// the routing strategy, and returns a Future for the response. If no actors are -// available (ErrNoActorsAvailable), the Future will be completed with this -// error. If the send context is cancelled before the message can be enqueued in -// the chosen actor's mailbox, the Future will be completed with the context's -// error. -func (r *Router[M, R]) Ask(ctx context.Context, msg M) Future[R] { - selectedActor, err := r.getActor() - if err != nil { - // If no actor could be selected (e.g., none available), - // complete the promise immediately with the selection error. - promise := NewPromise[R]() - promise.Complete(fn.Err[R](err)) - return promise.Future() - } - - return selectedActor.Ask(ctx, msg) -} - -// ID provides an identifier for the router. Since a router isn't an actor -// itself but a dispatcher for a service, its ID can be based on the service -// key. -func (r *Router[M, R]) ID() string { - return "router(" + r.serviceKey.name + ")" -} diff --git a/actor/system.go b/actor/system.go deleted file mode 100644 index 18d8a3862..000000000 --- a/actor/system.go +++ /dev/null @@ -1,447 +0,0 @@ -package actor - -import ( - "context" - "errors" - "fmt" - "sync" - - "github.com/lightningnetwork/lnd/fn/v2" -) - -// stoppable defines an interface for components that can be stopped. -// This is unexported as it's an internal detail of ActorSystem for managing -// actors that need to be shut down. -type stoppable interface { - Stop() -} - -// SystemConfig holds configuration parameters for the ActorSystem. -type SystemConfig struct { - // MailboxCapacity is the default capacity for actor mailboxes. - MailboxCapacity int -} - -// DefaultConfig returns a default configuration for the ActorSystem. -// The default mailbox capacity of 100 means each actor can buffer up to 100 -// pending messages (envelopes). Each envelope holds a message and an optional -// promise pointer, so the memory overhead per actor is roughly proportional to -// the size of the messages being sent multiplied by this capacity. -func DefaultConfig() SystemConfig { - return SystemConfig{ - MailboxCapacity: 100, - } -} - -// ActorSystem manages the lifecycle of actors and provides coordination -// services such as a receptionist for actor discovery and a dead letter office -// for undeliverable messages. It also handles the graceful shutdown of all -// managed actors. -type ActorSystem struct { - // receptionist is used for actor discovery. - receptionist *Receptionist - - // actors stores all actors managed by the system, keyed by their ID. - // This includes the deadLetterActor. - actors map[string]stoppable - - // deadLetterActor handles undeliverable messages. - deadLetterActor ActorRef[Message, any] - - // config holds the system-wide configuration. - config SystemConfig - - // mu protects the 'actors' map. - mu sync.RWMutex - - // ctx is the main context for the actor system. - ctx context.Context - - // cancel cancels the main system context. - cancel context.CancelFunc -} - -// NewActorSystem creates a new actor system using the default configuration. -func NewActorSystem() *ActorSystem { - return NewActorSystemWithConfig(DefaultConfig()) -} - -// NewActorSystemWithConfig creates a new actor system with custom configuration -func NewActorSystemWithConfig(config SystemConfig) *ActorSystem { - ctx, cancel := context.WithCancel(context.Background()) - - // Initialize the core ActorSystem components. - system := &ActorSystem{ - receptionist: newReceptionist(), - config: config, - actors: make(map[string]stoppable), - ctx: ctx, - cancel: cancel, - } - - // Define the behavior for the dead letter actor. It logs undeliverable - // messages and returns an error. - deadLetterBehavior := NewFunctionBehavior( - func(ctx context.Context, msg Message) fn.Result[any] { - log.Warnf("Dead letter received: message type=%s", - msg.MessageType()) - - return fn.Err[any](errors.New( - "message undeliverable: " + msg.MessageType(), - )) - }, - ) - - // Create the raw dead letter actor (*Actor instance). The DLO's own DLO - // reference is nil to prevent loops if messages to the DLO itself fail. - deadLetterActorCfg := ActorConfig[Message, any]{ - ID: "dead-letters", - Behavior: deadLetterBehavior, - DLO: nil, - MailboxSize: config.MailboxCapacity, - } - deadLetterRawActor, err := NewActor[Message, any](deadLetterActorCfg) - if err != nil { - // This should never happen since we control the DLO config. - panic("failed to create dead letter actor: " + err.Error()) - } - deadLetterRawActor.Start() - system.deadLetterActor = deadLetterRawActor.Ref() - - // Add the raw actor to the map of stoppable actors. No lock needed here - // as 'system' is not yet accessible concurrently. - system.actors[deadLetterRawActor.id] = deadLetterRawActor - - // The system is now fully initialized and ready. - return system -} - -// ActorOption is a functional option for customizing actor creation. -type ActorOption[M Message, R any] func(*ActorConfig[M, R]) - -// WithMailboxFactory returns an ActorOption that sets a custom mailbox factory. -func WithMailboxFactory[M Message, R any]( - f MailboxFactory[M, R]) ActorOption[M, R] { - - return func(cfg *ActorConfig[M, R]) { - cfg.MailboxFactory = f - } -} - -// WithMailboxSize returns an ActorOption that overrides the default mailbox -// size. -func WithMailboxSize[M Message, R any](size int) ActorOption[M, R] { - return func(cfg *ActorConfig[M, R]) { - cfg.MailboxSize = size - } -} - -// RegisterWithSystem creates an actor with the given ID, service key, and -// behavior within the specified ActorSystem. It starts the actor, adds it to -// the system's management, registers it with the receptionist using the -// provided key, and returns its ActorRef. -func RegisterWithSystem[M Message, R any](as *ActorSystem, id string, - key ServiceKey[M, R], - behavior ActorBehavior[M, R], - opts ...ActorOption[M, R]) (ActorRef[M, R], error) { - - actorCfg := ActorConfig[M, R]{ - ID: id, - Behavior: behavior, - DLO: as.deadLetterActor, - MailboxSize: as.config.MailboxCapacity, - } - - for _, opt := range opts { - opt(&actorCfg) - } - // Check for duplicate actor ID before creating the actor. - as.mu.Lock() - if _, exists := as.actors[id]; exists { - as.mu.Unlock() - - return nil, fmt.Errorf("%w: %s", ErrDuplicateActorID, id) - } - - actorInstance, err := NewActor(actorCfg) - if err != nil { - as.mu.Unlock() - - return nil, err - } - actorInstance.Start() - - // Add the actor instance to the system's list of stoppable actors. - as.actors[actorInstance.id] = actorInstance - as.mu.Unlock() - - log.Infof("ActorSystem: registered actor %s with service key %s", - id, key.name) - - // Register the actor's reference with the receptionist under the given - // service key, making it discoverable by other parts of the system. - RegisterWithReceptionist(as.receptionist, key, actorInstance.Ref()) - - return actorInstance.Ref(), nil -} - -// Receptionist returns the system's receptionist, which can be used for -// actor service discovery (finding actors by ServiceKey). -func (as *ActorSystem) Receptionist() *Receptionist { - return as.receptionist -} - -// DeadLetters returns a reference to the system's dead letter actor. Messages -// that cannot be delivered to their intended recipient (e.g., if an Ask -// context is cancelled before enqueuing) may be routed here if not otherwise -// handled. -func (as *ActorSystem) DeadLetters() ActorRef[Message, any] { - return as.deadLetterActor -} - -// Shutdown gracefully stops the actor system. It iterates through all managed -// actors, including the dead letter actor, and calls their Stop method. -// After initiating the stop for all actors, it cancels the main system context. -// This method is safe for concurrent use. -func (as *ActorSystem) Shutdown() error { - log.Infof("ActorSystem: initiating shutdown") - - // Create a slice of actors to stop. This avoids holding the lock while - // calling Stop() on each actor, and includes the dead letter actor. - var actorsToStop []stoppable - as.mu.RLock() - for _, actor := range as.actors { - actorsToStop = append(actorsToStop, actor) - } - as.mu.RUnlock() - - // Notify all managed actors to stop. Actor.Stop() is non-blocking. - // Each actor's Stop method will cancel its internal context, leading - // to the termination of its processing goroutine. - for _, actor := range actorsToStop { - actor.Stop() - } - - // Clear the actors map after initiating their shutdown. - as.mu.Lock() - as.actors = nil - as.mu.Unlock() - - // Finally cancel the main context - // This signals to any other components observing the system's context - // that shutdown has been initiated. - as.cancel() - - return nil -} - -// StopAndRemoveActor stops a specific actor by its ID and removes it from the -// ActorSystem's management. It returns true if the actor was found and stopped, -// false otherwise. -func (as *ActorSystem) StopAndRemoveActor(id string) bool { - as.mu.Lock() - defer as.mu.Unlock() - - actorToStop, exists := as.actors[id] - if !exists { - return false - } - - // Stop the actor. This is non-blocking. - actorToStop.Stop() - - // Remove from the system's management. - delete(as.actors, id) - - return true -} - -// UnregisterFromReceptionist removes an actor reference from a service key in -// the given receptionist. It returns true if the reference was found and -// removed, and false otherwise. This is a package-level generic function -// because methods cannot have their own type parameters in Go. -func UnregisterFromReceptionist[M Message, R any](r *Receptionist, - key ServiceKey[M, R], refToRemove ActorRef[M, R]) bool { - - r.mu.Lock() - defer r.mu.Unlock() - - refs, exists := r.registrations[key.name] - if !exists { - return false - } - - found := false - - // Build a new slice containing only the references that are not the one - // to be removed. - newRefs := make([]any, 0, max(0, len(refs)-1)) - for _, itemInSlice := range refs { - // Try to assert the item from the slice to the specific - // ActorRef[M,R] type we are trying to remove. - if specificActorRef, ok := itemInSlice.(ActorRef[M, R]); ok { - // If the type assertion is successful and it's the one - // we want to remove, mark as found and skip adding it - // to newRefs. - if specificActorRef == refToRemove { - found = true - continue - } - } - newRefs = append(newRefs, itemInSlice) - } - - if !found { - return false - } - - // If the new list of references is empty, remove the key from the map. - // Otherwise, update the map with the new slice. - if len(newRefs) == 0 { - delete(r.registrations, key.name) - } else { - r.registrations[key.name] = newRefs - } - - return true -} - -// ServiceKey is a type-safe identifier used for registering and discovering -// actors via the Receptionist. The generic type parameters M (Message) and R -// (Response) ensure that only actors handling compatible message/response types -// are associated with and retrieved for this key. -type ServiceKey[M Message, R any] struct { - name string -} - -// NewServiceKey creates a new service key with the given name. The name is used -// as the lookup key within the Receptionist. -func NewServiceKey[M Message, R any](name string) ServiceKey[M, R] { - return ServiceKey[M, R]{name: name} -} - -// Spawn registers an actor for this service key within the given ActorSystem. -// It's a convenience method that calls RegisterWithSystem, starting the actor -// and registering it with the receptionist. -func (sk ServiceKey[M, R]) Spawn(as *ActorSystem, id string, - behavior ActorBehavior[M, R], - opts ...ActorOption[M, R]) (ActorRef[M, R], error) { - - return RegisterWithSystem(as, id, sk, behavior, opts...) -} - -// Unregister removes an actor reference associated with this service key from -// the ActorSystem's receptionist and also stops the actor. -// It returns true if the actor was successfully unregistered from the -// receptionist AND successfully stopped and removed from the system's -// management. Otherwise, it returns false. -func (sk ServiceKey[M, R]) Unregister(as *ActorSystem, - refToRemove ActorRef[M, R]) bool { - - unregisteredFromReceptionist := UnregisterFromReceptionist( - as.Receptionist(), sk, refToRemove, - ) - - // If not found in receptionist, no need to try stopping. - if !unregisteredFromReceptionist { - return false - } - - // Attempt to stop and remove the actor from the system. - stoppedAndRemoved := as.StopAndRemoveActor(refToRemove.ID()) - - return unregisteredFromReceptionist && stoppedAndRemoved -} - -// UnregisterAll finds all actor references associated with this service key in -// the ActorSystem's receptionist. For each found actor, it attempts to stop it -// and remove it from system management, and also unregisters it from the -// receptionist. -func (sk ServiceKey[M, R]) UnregisterAll(as *ActorSystem) int { - // First find all the refs that match this service key. - refsFound := FindInReceptionist(as.Receptionist(), sk) - - actorsStoppedCount := 0 - for _, ref := range refsFound { - // Attempt to stop and remove the actor from the system's active - // management. This is the primary action to deactivate the - // actor. If StopAndRemoveActor returns true, it means an active - // actor was found in the system's `actors` map and was stopped. - if as.StopAndRemoveActor(ref.ID()) { - actorsStoppedCount++ - } - - // Regardless of whether the actor was actively managed by the - // system (i.e., found in as.actors), attempt to unregister its - // reference from the receptionist. This helps clean up any - // potentially stale entries in the receptionist if an actor was - // removed from the system's management without also being - // unregistered from the receptionist. - UnregisterFromReceptionist(as.Receptionist(), sk, ref) - } - - return actorsStoppedCount -} - -// Receptionist provides service discovery for actors. Actors can be registered -// under a ServiceKey and later discovered by other actors or system components. -type Receptionist struct { - // registrations stores ActorRef instances, keyed by ServiceKey.name. - registrations map[string][]any - - // mu protects access to registrations. - mu sync.RWMutex -} - -// newReceptionist creates a new Receptionist instance. -func newReceptionist() *Receptionist { - return &Receptionist{ - registrations: make(map[string][]any), - } -} - -// RegisterWithReceptionist registers an actor with a service key in the given -// receptionist. This is a package-level generic function because methods -// cannot have their own type parameters in Go (as of the current version). -// It appends the actor reference to the list associated with the key's name. -func RegisterWithReceptionist[M Message, R any](r *Receptionist, - key ServiceKey[M, R], ref ActorRef[M, R]) { - - r.mu.Lock() - defer r.mu.Unlock() - - // Initialize the slice for this key if it's the first registration. - if _, exists := r.registrations[key.name]; !exists { - r.registrations[key.name] = make([]any, 0) - } - - r.registrations[key.name] = append(r.registrations[key.name], ref) -} - -// FindInReceptionist returns all actors registered with a service key in the -// given receptionist. This is a package-level generic function because methods -// cannot have their own type parameters. It performs a type assertion to ensure -// that only ActorRefs matching the ServiceKey's generic types (M, R) are -// returned, providing type safety. -func FindInReceptionist[M Message, R any](r *Receptionist, - key ServiceKey[M, R]) []ActorRef[M, R] { - - r.mu.RLock() - defer r.mu.RUnlock() - - if refs, exists := r.registrations[key.name]; exists { - typedRefs := make([]ActorRef[M, R], 0, len(refs)) - for _, ref := range refs { - // Make sure that the reference is of the correct type. - // This type assertion is crucial for type safety, ensuring - // that the returned ActorRefs match the expected M and R. - if typedRef, ok := ref.(ActorRef[M, R]); ok { - typedRefs = append(typedRefs, typedRef) - } - } - - return typedRefs - } - - return nil -} diff --git a/actor/system_test.go b/actor/system_test.go deleted file mode 100644 index cc1510ac6..000000000 --- a/actor/system_test.go +++ /dev/null @@ -1,958 +0,0 @@ -package actor - -import ( - "context" - "errors" - "fmt" - "sync/atomic" - "testing" - "time" - - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/stretchr/testify/require" -) - -// TestActorSystemNewActorSystem verifies the basic initialization of an -// ActorSystem, including its default DLO. -func TestActorSystemNewActorSystem(t *testing.T) { - t.Parallel() - - as := NewActorSystem() - require.NotNil(t, as, "newActorSystem should not return nil") - require.NotNil(t, as.Receptionist(), "receptionist should not be nil") - require.NotNil(t, as.DeadLetters(), "deadLetters should not be nil") - require.Equal(t, "dead-letters", as.DeadLetters().ID(), "dLO ID mismatch") - - // Test the DLO's behavior (it should return an error for Ask). - testDLOMsg := newTestMsg("to-dlo") - future := as.DeadLetters().Ask(context.Background(), testDLOMsg) - result := future.Await(context.Background()) - - // We should get back an error for asks. - require.True( - t, result.IsErr(), "system DLO should return an error on Ask", - ) - expectedErrStr := "message undeliverable: " + testDLOMsg.MessageType() - require.EqualError( - t, result.Err(), expectedErrStr, "dLO error message mismatch", - ) - - // Shutdown the system to clean up resources. - err := as.Shutdown() - require.NoError(t, err, "actorSystem shutdown failed") -} - -// TestActorSystemRegisterWithSystem verifies actor registration, lifecycle -// management within the system. -func TestActorSystemRegisterWithSystem(t *testing.T) { - t.Parallel() - - as := NewActorSystem() - defer func() { - err := as.Shutdown() - require.NoError(t, err) - }() - - actorID := "test-actor-sys-reg" - serviceKey := NewServiceKey[*testMsg, string]("test-service") - - // Using echoBehavior from actor_test.go (implicitly available) - beh := newEchoBehavior(t, 0) - - // We'll start off by registering the actor. - actorRef, err := RegisterWithSystem(as, actorID, serviceKey, beh) - require.NoError(t, err) - require.NotNil(t, actorRef, "registerWithSystem should return a valid ActorRef") - require.Equal(t, actorID, actorRef.ID(), "registered actor ID mismatch") - - // The actor should be found in the receptionist. - foundActors := FindInReceptionist(as.Receptionist(), serviceKey) - require.Len(t, foundActors, 1, "actor not found in receptionist") - require.Equal(t, actorRef, foundActors[0], "incorrect actor in receptionist") - - // Next, we'll send out a simple tell, using our reply channel to make - // sure it's actually processed. - msgData := "hello-system-actor" - replyChan := make(chan string, 1) - actorRef.Tell(context.Background(), newTestMsgWithReply(msgData, replyChan)) - - received, err := fn.RecvOrTimeout(replyChan, 100*time.Millisecond) - require.NoError(t, err, "timed out waiting for actor to process message") - require.Equal(t, msgData, received, "actor did not process message") - - // Stop the actor through the system. - stopped := as.StopAndRemoveActor(actorID) - require.True(t, stopped, "StopAndRemoveActor failed") - - // Wait for actor to fully stop. - time.Sleep(50 * time.Millisecond) - - // Send a message to the now-stopped actor's ref. This should go to the - // system's DLO. - afterStopMsg := newTestMsg("after-stop-to-dlo") - require.NotPanics(t, func() { - actorRef.Tell(context.Background(), afterStopMsg) - }, "tell to stopped actor should not panic") -} - -// TestActorSystemShutdown verifies that all actors are stopped and the system -// context is cancelled upon shutdown. -func TestActorSystemShutdown(t *testing.T) { - t.Parallel() - - as := NewActorSystem() - - // We'll start by making 3 new actors, each with a unique ID. - numActors := 3 - actorRefs := make([]ActorRef[*testMsg, string], numActors) - for i := 0; i < numActors; i++ { - actorID := fmt.Sprintf("shutdown-test-actor-%d", i) - key := NewServiceKey[*testMsg, string]( - fmt.Sprintf("service-%d", i), - ) - beh := newEchoBehavior(t, 0) - ref, regErr := RegisterWithSystem(as, actorID, key, beh) - require.NoError(t, regErr) - actorRefs[i] = ref - } - - // We'll now send a message to each actor to ensure that they're - // running. - for i, ref := range actorRefs { - future := ref.Ask( - context.Background(), - newTestMsg(fmt.Sprintf("ping-%d", i)), - ) - ctxAwait, cancelAwait := context.WithTimeout( - context.Background(), time.Second, - ) - res := future.Await(ctxAwait) - cancelAwait() - require.False( - t, res.IsErr(), - "actor %d failed to respond before shutdown: %v", - i, res.Err(), - ) - } - - // Next, trigger a shutdown, and assert that the done channel gets - // closed. - err := as.Shutdown() - require.NoError(t, err, "actorSystem shutdown failed") - - // Check if the system context is done using RecvOrTimeout with a zero - // timeout for a non-blocking check. - _, err = fn.RecvOrTimeout(as.ctx.Done(), time.Millisecond*100) - require.NoError(t, err, "actorSystem context not cancelled after shutdown") - - // We'll now try to send a message to each of the actors, this should - // result in an error. - for i, ref := range actorRefs { - future := ref.Ask( - context.Background(), - newTestMsg(fmt.Sprintf("ping-after-shutdown-%d", i)), - ) - res := future.Await(context.Background()) - require.True( - t, res.IsErr(), - "actor %d Ask should fail after shutdown", i, - ) - require.ErrorIs(t, res.Err(), ErrActorTerminated) - } - - as.mu.RLock() - require.Nil(t, as.actors, "actors map should be nil after shutdown") - as.mu.RUnlock() - - // Once shutdown, we shouldn't be able to send to the DLO either. - dloRef := as.DeadLetters() - futureDLO := dloRef.Ask( - context.Background(), newTestMsg("ping-dlo-after-shutdown"), - ) - resDLO := futureDLO.Await(context.Background()) - require.True( - t, resDLO.IsErr(), "DLO Ask should fail after system shutdown", - ) - require.ErrorIs( - t, resDLO.Err(), ErrActorTerminated, - ) -} - -// TestActorSystemStopAndRemoveActor verifies specific actor stopping and -// removal. -func TestActorSystemStopAndRemoveActor(t *testing.T) { - t.Parallel() - - as := NewActorSystem() - defer func() { - err := as.Shutdown() - require.NoError(t, err) - }() - - // Make some actor IDs, then unique service keys, then use that to - // register two actors. - actor1ID := "actor-to-stop" - actor2ID := "actor-to-keep" - key1 := NewServiceKey[*testMsg, string]("service1") - key2 := NewServiceKey[*testMsg, string]("service2") - beh := newEchoBehavior(t, 0) - - ref1, err := RegisterWithSystem(as, actor1ID, key1, beh) - require.NoError(t, err) - ref2, err := RegisterWithSystem(as, actor2ID, key2, beh) - require.NoError(t, err) - - // If we remove one actor, then try to send to it, we should get an - // error. - stopped := as.StopAndRemoveActor(actor1ID) - require.True(t, stopped, "failed to stop and remove actor1") - - future1 := ref1.Ask(context.Background(), newTestMsg("ping-actor1")) - res1 := future1.Await(context.Background()) - require.True(t, res1.IsErr(), "actor1 should be stopped") - require.ErrorIs(t, res1.Err(), ErrActorTerminated) - - as.mu.RLock() - _, exists := as.actors[actor1ID] - as.mu.RUnlock() - - // The actor should no longer be found. - require.False(t, exists, "actor1 still in system's actor map") - - // Make sure that we can still send messages to the existing actor. - future2 := ref2.Ask( - context.Background(), newTestMsg("ping-actor2"), - ) - - ctxAwait2, cancelAwait2 := context.WithTimeout( - context.Background(), time.Second, - ) - res2 := future2.Await(ctxAwait2) - cancelAwait2() - - require.False( - t, res2.IsErr(), "actor2 should still be running: %v", - res2.Err(), - ) - res2.WhenOk(func(s string) { - require.Equal(t, "echo: ping-actor2", s) - }) - - stoppedNonExistent := as.StopAndRemoveActor("non-existent-actor") - require.False( - t, stoppedNonExistent, "stopping non-existent actor should "+ - "return false", - ) -} - -// TestReceptionist covers basic registration, finding, and unregistration. -func TestReceptionist(t *testing.T) { - t.Parallel() - - as := NewActorSystem() - defer func() { - err := as.Shutdown() - require.NoError(t, err) - }() - receptionist := as.Receptionist() - - key1 := NewServiceKey[*testMsg, string]("key1") - key2 := NewServiceKey[*testMsg, string]("key2") - key1Again := NewServiceKey[*testMsg, string]("key1") - - // Register 3 actor instance using the service keys we created above. - beh := newEchoBehavior(t, 0) - actor1Ref, err := RegisterWithSystem(as, "actor1-rec", key1, beh) - require.NoError(t, err) - actor2Ref, err := RegisterWithSystem(as, "actor2-rec", key1, beh) - require.NoError(t, err) - actor3Ref, err := RegisterWithSystem(as, "actor3-rec", key2, beh) - require.NoError(t, err) - - // We should be able to find the actors we registered. - foundForKey1 := FindInReceptionist(receptionist, key1) - require.Len(t, foundForKey1, 2, "should find 2 actors for key1") - require.Contains(t, foundForKey1, actor1Ref) - require.Contains(t, foundForKey1, actor2Ref) - - foundForKey1Again := FindInReceptionist(receptionist, key1Again) - require.ElementsMatch(t, foundForKey1, foundForKey1Again) - - // Same goes for the second key we added. - foundForKey2 := FindInReceptionist(receptionist, key2) - require.Len(t, foundForKey2, 1, "should find 1 actor for key2") - require.Equal(t, actor3Ref, foundForKey2[0]) - - // We shouldn't be able to find a key we didn't add. - nonExistentKey := NewServiceKey[*testMsg, string]("non-existent") - foundForNonExistent := FindInReceptionist(receptionist, nonExistentKey) - require.Empty(t, foundForNonExistent) - - // We should be able to unregister the actors we added. - unregistered := UnregisterFromReceptionist( - receptionist, key1, actor1Ref, - ) - require.True(t, unregistered, "failed to unregister actor1Ref") - - foundForKey1AfterUnreg := FindInReceptionist(receptionist, key1) - require.Len(t, foundForKey1AfterUnreg, 1) - require.Equal(t, actor2Ref, foundForKey1AfterUnreg[0]) - - // If we try to unregister the same actor again, it should fail. - unregisteredAgain := UnregisterFromReceptionist(receptionist, key1, actor1Ref) - require.False(t, unregisteredAgain) - - unregisteredLast := UnregisterFromReceptionist(receptionist, key1, actor2Ref) - require.True(t, unregisteredLast) - foundForKey1AfterAllUnreg := FindInReceptionist(receptionist, key1) - require.Empty(t, foundForKey1AfterAllUnreg) - - receptionist.mu.RLock() - _, exists := receptionist.registrations[key1.name] - receptionist.mu.RUnlock() - require.False(t, exists, "key1 should be removed from registrations map") - - // Finally, if we use the wrong key, or one that doesn't exist, that - // should also fail. - unregisteredWrongKey := UnregisterFromReceptionist(receptionist, key1, actor3Ref) - require.False(t, unregisteredWrongKey) - unregisteredNonExistentKey := UnregisterFromReceptionist(receptionist, nonExistentKey, actor1Ref) - require.False(t, unregisteredNonExistentKey) -} - -// TestServiceKeyMethods tests Spawn and Unregister methods on ServiceKey. -func TestServiceKeyMethods(t *testing.T) { - t.Parallel() - - as := NewActorSystem() - defer func() { - err := as.Shutdown() - require.NoError(t, err) - }() - - key := NewServiceKey[*testMsg, string]("sk-service") - beh := newEchoBehavior(t, 0) - - // Attempt to spawn a new actor using the service key and desired - // behavior. - actorRef, err := key.Spawn(as, "actor-sk-spawn", beh) - require.NoError(t, err) - require.NotNil(t, actorRef) - require.Equal(t, "actor-sk-spawn", actorRef.ID()) - - // We should be able to find the actor in the receptionist. - found := FindInReceptionist(as.Receptionist(), key) - require.Len(t, found, 1) - require.Equal(t, actorRef, found[0]) - - as.mu.RLock() - _, sysExists := as.actors[actorRef.ID()] - as.mu.RUnlock() - require.True(t, sysExists) - - // Next, try to unregister the actor using the service key. - success := key.Unregister(as, actorRef) - require.True(t, success, "serviceKey.Unregister failed") - - // The actor should no longer be found in the receptionist. - foundAfter := FindInReceptionist(as.Receptionist(), key) - require.Empty(t, foundAfter) - - as.mu.RLock() - _, sysExistsAfter := as.actors[actorRef.ID()] - as.mu.RUnlock() - require.False(t, sysExistsAfter) - - // If we try to send a message to the actor after unregistering it, then - // we should get an error. - future := actorRef.Ask(context.Background(), newTestMsg("ping")) - res := future.Await(context.Background()) - require.True(t, res.IsErr() && errors.Is(res.Err(), ErrActorTerminated)) - - successAgain := key.Unregister(as, actorRef) - require.False(t, successAgain) - - otherSys := NewActorSystem() // Create a different actor system - defer func() { - err := otherSys.Shutdown() - require.NoError(t, err) - }() - - // Create a dummy actor in otherSys of the correct generic type for the - // key. This actor won't be found in 'as', so Unregister should fail. - dummyBehOther := newEchoBehavior(t, 0) - dummyKeyOther := NewServiceKey[*testMsg, string]("dummy-other") - dummyActorRefOtherSys, err := RegisterWithSystem( - otherSys, "dummy-other-actor", dummyKeyOther, dummyBehOther, - ) - require.NoError(t, err) - - successNonMember := key.Unregister(as, dummyActorRefOtherSys) - require.False(t, successNonMember) -} - -// TestServiceKeyUnregisterAll tests the UnregisterAll method on ServiceKey. -// It covers scenarios including basic unregistration of multiple actors, -// attempting to unregister with no actors present, unregistering actors for -// one key while leaving others intact, and the idempotency of the operation. -func TestServiceKeyUnregisterAll(t *testing.T) { - t.Parallel() - - // Common setup for all sub-tests. - as := NewActorSystem() - defer func() { - err := as.Shutdown() - require.NoError(t, err, "ActorSystem shutdown failed.") - }() - - // Common behavior for test actors used across sub-tests. - beh := newEchoBehavior(t, 0) - - t.Run("unregister all multiple actors", func(st *testing.T) { - key1 := NewServiceKey[*testMsg, string]("sk-ua-key1") - actor1Key1, err := key1.Spawn(as, "actor1-k1-ua", beh) - require.NoError(st, err) - actor2Key1, err := key1.Spawn(as, "actor2-k1-ua", beh) - require.NoError(st, err) - - // Verify they are registered in the receptionist. - foundActorsForKey1 := FindInReceptionist( - as.Receptionist(), key1, - ) - require.Len( - st, foundActorsForKey1, 2, - "actors for key1 not in receptionist initially.", - ) - - // Verify they are in the system's actor map. - as.mu.RLock() - _, actor1Key1Exists := as.actors[actor1Key1.ID()] - _, actor2Key1Exists := as.actors[actor2Key1.ID()] - as.mu.RUnlock() - require.True( - st, actor1Key1Exists, - "actor1 for key1 not in system actors map initially.", - ) - require.True( - st, actor2Key1Exists, - "actor2 for key1 not in system actors map initially.", - ) - - // Unregister all for key1. - stoppedCountKey1 := key1.UnregisterAll(as) - require.Equal( - st, 2, stoppedCountKey1, - "UnregisterAll for key1 returned incorrect count.", - ) - - // Verify they are unregistered from the receptionist. - foundActorsForKey1After := FindInReceptionist( - as.Receptionist(), key1, - ) - require.Empty( - st, foundActorsForKey1After, - "actors for key1 still in receptionist after "+ - "UnregisterAll.", - ) - - // Verify they are removed from system actors map. - as.mu.RLock() - _, actor1Key1ExistsAfter := as.actors[actor1Key1.ID()] - _, actor2Key1ExistsAfter := as.actors[actor2Key1.ID()] - as.mu.RUnlock() - require.False( - st, actor1Key1ExistsAfter, - "Actor1 for key1 still in system actors "+ - "map after UnregisterAll.", - ) - require.False( - st, actor2Key1ExistsAfter, - "Actor2 for key1 still in system actors "+ - "map after UnregisterAll.", - ) - - // Verify actors are stopped. - resultActor1Key1 := actor1Key1.Ask( - context.Background(), newTestMsg("ping-k1-a1"), - ).Await(context.Background()) - require.True( - st, resultActor1Key1.IsErr(), - "Actor1 key1 Ask should fail after UnregisterAll.", - ) - require.ErrorIs( - st, resultActor1Key1.Err(), ErrActorTerminated, - "Actor1 key1 not terminated with correct error.", - ) - - resultActor2Key1 := actor2Key1.Ask( - context.Background(), newTestMsg("ping-k1-a2"), - ).Await(context.Background()) - require.True( - st, resultActor2Key1.IsErr(), - "Actor2 key1 Ask should fail after UnregisterAll.", - ) - require.ErrorIs( - st, resultActor2Key1.Err(), ErrActorTerminated, - "Actor2 key1 not terminated with correct error.", - ) - }) - - t.Run("unregister all with no actors for the key", func(st *testing.T) { - keyEmpty := NewServiceKey[*testMsg, string]("sk-ua-key-empty") - stoppedCountEmptyKey := keyEmpty.UnregisterAll(as) - require.Equal( - st, 0, stoppedCountEmptyKey, - "UnregisterAll for empty key returned non-zero count.", - ) - - foundActorsForKeyEmpty := FindInReceptionist( - as.Receptionist(), keyEmpty, - ) - require.Empty( - st, foundActorsForKeyEmpty, - "Receptionist not empty for keyEmpty "+ - "after UnregisterAll.", - ) - }) - - t.Run("unregister all with mixed keys", func(st *testing.T) { - keyA := NewServiceKey[*testMsg, string]("sk-ua-keyA") - keyB := NewServiceKey[*testMsg, string]("sk-ua-keyB") - - // Spawn 3 actors, two of them will share the same service key. - actorA1, err := keyA.Spawn(as, "actorA1-ua-mixed", beh) - require.NoError(st, err) - actorA2, err := keyA.Spawn(as, "actorA2-ua-mixed", beh) - require.NoError(st, err) - actorB1, err := keyB.Spawn(as, "actorB1-ua-mixed", beh) - require.NoError(st, err) - - // Make sure we're able to find them in the receptionist. - require.Len( - st, FindInReceptionist(as.Receptionist(), keyA), 2, - "KeyA initial registration count mismatch.", - ) - require.Len( - st, FindInReceptionist(as.Receptionist(), keyB), 1, - "KeyB initial registration count mismatch.", - ) - - // We'll start by unregistering all actors for keyA. - stoppedCountKeyA := keyA.UnregisterAll(as) - require.Equal( - st, 2, stoppedCountKeyA, - "UnregisterAll for keyA returned incorrect count.", - ) - - // Verify keyA actors are gone from receptionist, keyB actor - // remains. - require.Empty( - st, FindInReceptionist(as.Receptionist(), keyA), - "actors for keyA still in receptionist after "+ - "UnregisterAll.", - ) - foundActorsForKeyBAfterA := FindInReceptionist( - as.Receptionist(), keyB, - ) - require.Len( - st, foundActorsForKeyBAfterA, 1, - "Actor for keyB affected by UnregisterAll on keyA.", - ) - require.Equal( - st, actorB1, foundActorsForKeyBAfterA[0], - "Wrong actor found for keyB.", - ) - - // Verify keyA actors are removed from system map, keyB actor - // remains. - as.mu.RLock() - _, actorA1ExistsAfterMixed := as.actors[actorA1.ID()] - _, actorA2ExistsAfterMixed := as.actors[actorA2.ID()] - _, actorB1ExistsAfterMixed := as.actors[actorB1.ID()] - as.mu.RUnlock() - require.False( - st, actorA1ExistsAfterMixed, - "ActorA1 still in system actors map after "+ - "mixed UnregisterAll.", - ) - require.False( - st, actorA2ExistsAfterMixed, - "ActorA2 still in system actors map after "+ - "mixed UnregisterAll.", - ) - require.True( - st, actorB1ExistsAfterMixed, - "ActorB1 removed from system actors map incorrectly.", - ) - - // Verify keyA actors are stopped, keyB actor is running. - resultActorA1Mixed := actorA1.Ask( - context.Background(), newTestMsg("ping-kA-a1"), - ).Await(context.Background()) - require.True(st, resultActorA1Mixed.IsErr()) - require.ErrorIs( - st, resultActorA1Mixed.Err(), ErrActorTerminated, - ) - - resultActorB1Mixed := actorB1.Ask( - context.Background(), newTestMsg("ping-kB-a1"), - ).Await(context.Background()) - require.False( - st, resultActorB1Mixed.IsErr(), - "ActorB1 terminated incorrectly (mixed test): %v", - resultActorB1Mixed.Err(), - ) - resultActorB1Mixed.WhenOk(func(s string) { - require.Equal(st, "echo: ping-kB-a1", s) - }) - }) - - t.Run("idempotency of UnregisterAll", func(st *testing.T) { - keyIdempotent := NewServiceKey[*testMsg, string]( - "sk-ua-key-idem", - ) - actorIdem, err := keyIdempotent.Spawn(as, "actor-idem-ua", beh) - require.NoError(st, err) - - // First call should unregister and stop. - stoppedCountFirstCall := keyIdempotent.UnregisterAll(as) - require.Equal( - st, 1, stoppedCountFirstCall, - "UnregisterAll (first call) incorrect count.", - ) - - // Second call should do nothing and return 0. - stoppedCountSecondCall := keyIdempotent.UnregisterAll(as) - require.Equal( - st, 0, stoppedCountSecondCall, - "UnregisterAll (second call) incorrect count, not "+ - "idempotent.", - ) - - // Verify actor is gone from receptionist and system map, and is - // stopped. - require.Empty( - st, FindInReceptionist(as.Receptionist(), keyIdempotent), - "Actors for keyIdempotent still in receptionist "+ - "after calls.", - ) - - as.mu.RLock() - _, actorIdemExistsAfter := as.actors[actorIdem.ID()] - as.mu.RUnlock() - require.False( - st, actorIdemExistsAfter, - "ActorIdem still in system actors map after calls.", - ) - - resultActorIdem := actorIdem.Ask( - context.Background(), newTestMsg("ping-kidem-a1"), - ).Await(context.Background()) - require.True(st, resultActorIdem.IsErr()) - require.ErrorIs(st, resultActorIdem.Err(), ErrActorTerminated) - }) -} - -// routerTestHarness helps set up routers and their associated actors for testing. -// It uses an actorTestHarness internally for DLO observation for the router. -type routerTestHarness struct { - *actorTestHarness - as *ActorSystem - receptionist *Receptionist -} - -// newRouterTestHarness sets up a new harness for router testing. -// It creates an ActorSystem for actors that the router will route to, -// and uses the embedded actorTestHarness for the router's own DLO. -func newRouterTestHarness(t *testing.T) *routerTestHarness { - t.Helper() - system := NewActorSystem() - t.Cleanup(func() { - err := system.Shutdown() - require.NoError(t, err, "router test actor system shutdown failed") - }) - - // The DLO for the router itself will come from actorTestHarness. - // Actors managed by `system` (router targets) will use `system.DeadLetters()`. - return &routerTestHarness{ - actorTestHarness: newActorTestHarness(t), - as: system, - receptionist: system.Receptionist(), - } -} - -// newRouterTargetActor creates an actor, registers it with the harness's -// ActorSystem (h.as) and Receptionist under the given service key. This actor -// is intended to be a target for the router. -func (h *routerTestHarness) newRouterTargetActor(id string, - key ServiceKey[*testMsg, string], - beh ActorBehavior[*testMsg, string]) ActorRef[*testMsg, string] { - - h.t.Helper() - - ref, err := RegisterWithSystem(h.as, id, key, beh) - require.NoError(h.t, err) - - return ref -} - -// TestRouterNewRouter verifies that a new router can be created as expected. -func TestRouterNewRouter(t *testing.T) { - t.Parallel() - h := newRouterTestHarness(t) - - key := NewServiceKey[*testMsg, string]("router-service") - strategy := NewRoundRobinStrategy[*testMsg, string]() - - router := NewRouter(h.receptionist, key, strategy, h.dlo.Ref()) - require.NotNil(t, router, "newRouter should not return nil") - require.Equal(t, "router(router-service)", router.ID(), "router ID mismatch") -} - -// countingEchoBehavior is an echo behavior that also counts how many messages -// it has processed. -type countingEchoBehavior struct { - *echoBehavior - id string - processedMsgs int64 -} - -func newCountingEchoBehavior(t *testing.T, id string) *countingEchoBehavior { - return &countingEchoBehavior{ - echoBehavior: newEchoBehavior(t, 0), - id: id, - } -} - -func (b *countingEchoBehavior) Receive(ctx context.Context, - msg *testMsg) fn.Result[string] { - - atomic.AddInt64(&b.processedMsgs, 1) - - // Include actor ID in reply for easier verification. - res := b.echoBehavior.Receive(ctx, msg) - val, err := res.Unpack() - if err == nil { - return fn.Ok(fmt.Sprintf("%s:%s", b.id, val)) - } - return res -} - -// TestRouterTellAndAskRoundRobin verifies that the router distributes messages -// in a round robin properly. -func TestRouterTellAndAskRoundRobin(t *testing.T) { - t.Parallel() - h := newRouterTestHarness(t) - - // Make a new router for the given service key and round robin strategy. - serviceKey := NewServiceKey[*testMsg, string]("rr-service") - strategy := NewRoundRobinStrategy[*testMsg, string]() - router := NewRouter(h.receptionist, serviceKey, strategy, h.dlo.Ref()) - - // We'll now register two actors with the router, each with a different - // service key. - actor1Beh := newCountingEchoBehavior(t, "actor1") - actor2Beh := newCountingEchoBehavior(t, "actor2") - _ = h.newRouterTargetActor("actor1-rr", serviceKey, actor1Beh) - _ = h.newRouterTargetActor("actor2-rr", serviceKey, actor2Beh) - - // Nxet, we'll send a mix of Tell and Ask messages to the router. - numMessages := 6 - for i := 0; i < numMessages; i++ { - msgData := fmt.Sprintf("message-%d", i) - if i%2 == 0 { - router.Tell(context.Background(), newTestMsg(msgData)) - } else { - future := router.Ask( - context.Background(), newTestMsg(msgData), - ) - ctxAwait, cancelAwait := context.WithTimeout( - context.Background(), time.Second, - ) - - result := future.Await(ctxAwait) - cancelAwait() - require.False( - t, result.IsErr(), "ask failed: %v", result.Err(), - ) - } - } - - // Wait a bit for Tell messages to be processed. - time.Sleep(100 * time.Millisecond) - - // Each actor should have processed numMessages / 2 messages. - require.EqualValues( - t, numMessages/2, atomic.LoadInt64(&actor1Beh.processedMsgs), - "actor1 processed message count mismatch", - ) - require.EqualValues( - t, numMessages/2, atomic.LoadInt64(&actor2Beh.processedMsgs), - "actor2 processed message count mismatch", - ) - - // Router's DLO should be empty. - h.assertNoDLOMessages() -} - -// TestRouterNoActorsAvailable verifies that if no actors are available for the -// message, then an error is returned. -func TestRouterNoActorsAvailable(t *testing.T) { - t.Parallel() - h := newRouterTestHarness(t) - - serviceKey := NewServiceKey[*testMsg, string]("no-actor-service") - strategy := NewRoundRobinStrategy[*testMsg, string]() - router := NewRouter(h.receptionist, serviceKey, strategy, h.dlo.Ref()) - - // We'll send a message, then assert that it goes to the DLO. - tellMsg := newTestMsg("tell-no-actor") - router.Tell(context.Background(), tellMsg) - h.assertDLOMessage(tellMsg) - - // If we use an ask instead, then we should get an error. - askMsg := newTestMsg("ask-no-actor") - future := router.Ask(context.Background(), askMsg) - result := future.Await(context.Background()) - - require.True( - t, result.IsErr(), "ask should fail when no actors are available", - ) - require.ErrorIs(t, result.Err(), ErrNoActorsAvailable, "error mismatch") -} - -// TestRouterTellAskContextCancellation verifies that if the context is -// canceled, then sending aborts. -func TestRouterTellAskContextCancellation(t *testing.T) { - t.Parallel() - h := newRouterTestHarness(t) - - serviceKey := NewServiceKey[*testMsg, string]("ctx-cancel-service") - strategy := NewRoundRobinStrategy[*testMsg, string]() - router := NewRouter(h.receptionist, serviceKey, strategy, h.dlo.Ref()) - - // Use a regular echo actor, but we'll control context for Tell/Ask. - targetActorBeh := newEchoBehavior(t, 50*time.Millisecond) - _ = h.newRouterTargetActor("target-ctx", serviceKey, targetActorBeh) - - // Next, we'll send a Tell message with a context that will be cancelled - // before we even send. - ctxTell, cancelTell := context.WithCancel(context.Background()) - cancelTell() - router.Tell(ctxTell, newTestMsg("tell-ctx-cancelled")) - - // The Message should be dropped by actorRefImpl.Tell if ctx is - // cancelled. Router's DLO should not receive it from this path. - h.assertNoDLOMessages() - - // Next, we'll do the same for Ask. This time, we should get an error. - ctxAsk, cancelAsk := context.WithCancel(context.Background()) - cancelAsk() - futureAsk := router.Ask(ctxAsk, newTestMsg("ask-ctx-cancelled")) - resultAsk := futureAsk.Await(context.Background()) - - require.True( - t, resultAsk.IsErr(), "ask with cancelled context should fail", - ) - require.ErrorIs( - t, resultAsk.Err(), context.Canceled, - "error should be context.Canceled", - ) -} - -// TestRouterDynamicActorRegistration tests that we're able to dynamically add -// and remove actors from the router. -func TestRouterDynamicActorRegistration(t *testing.T) { - t.Parallel() - h := newRouterTestHarness(t) - - serviceKey := NewServiceKey[*testMsg, string]("dynamic-service") - strategy := NewRoundRobinStrategy[*testMsg, string]() - router := NewRouter(h.receptionist, serviceKey, strategy, h.dlo.Ref()) - - // If we try to send a mesasge to the router before any actors are - // added, we should get an error. - futureNoActor := router.Ask(context.Background(), newTestMsg("ping-no-actors")) - resNoActor := futureNoActor.Await(context.Background()) - require.ErrorIs(t, resNoActor.Err(), ErrNoActorsAvailable) - - actor1Beh := newCountingEchoBehavior(t, "actor1") - actor1Ref := h.newRouterTargetActor("actor1-dynamic", serviceKey, actor1Beh) - - // At this point, we have a new actor added, but we'll try to send a - // message to a different actor ID. This should go to the router's DLO. - futureActor1 := router.Ask(context.Background(), newTestMsg("ping-actor1")) - ctxAwaitA1, cancelAwaitA1 := context.WithTimeout(context.Background(), time.Second) - resActor1 := futureActor1.Await(ctxAwaitA1) - cancelAwaitA1() - require.False(t, resActor1.IsErr(), "ask to actor1 failed: %v", resActor1.Err()) - resActor1.WhenOk(func(s string) { - require.Equal(t, "actor1:echo: ping-actor1", s) - }) - - actor2Beh := newCountingEchoBehavior(t, "actor2") - actor2Ref := h.newRouterTargetActor( - "actor2-dynamic", serviceKey, actor2Beh, - ) - - // Now that we've added two actors above, we should round robin between - // them when sending. - ctxAwaitDA1, cancelAwaitDA1 := context.WithTimeout( - context.Background(), time.Second, - ) - router.Ask(context.Background(), newTestMsg("dynamic-ask1")).Await( - ctxAwaitDA1, - ) - cancelAwaitDA1() - - ctxAwaitDA2, cancelAwaitDA2 := context.WithTimeout(context.Background(), time.Second) - router.Ask(context.Background(), newTestMsg("dynamic-ask2")).Await(ctxAwaitDA2) - cancelAwaitDA2() - - time.Sleep(50 * time.Millisecond) - - // actor1 should have processed 2 messages (ping-actor1, dynamic-ask1), - require.EqualValues(t, 2, atomic.LoadInt64(&actor1Beh.processedMsgs)) - require.EqualValues(t, 1, atomic.LoadInt64(&actor2Beh.processedMsgs)) - - // Next, we'll unregister the first actor ref. - unregistered := UnregisterFromReceptionist( - h.receptionist, serviceKey, actor1Ref, - ) - require.True(t, unregistered) - - // All the messages should now go to the second actor. - for i := 0; i < 2; i++ { - msgData := fmt.Sprintf("to-actor2-%d", i) - future := router.Ask(context.Background(), newTestMsg(msgData)) - ctxAwaitLoop, cancelAwaitLoop := context.WithTimeout( - context.Background(), time.Second, - ) - - res := future.Await(ctxAwaitLoop) - cancelAwaitLoop() - - require.False( - t, res.IsErr(), "ask to actor2 failed: %v", res.Err(), - ) - res.WhenOk(func(s string) { - require.Equal(t, "actor2:echo: "+msgData, s) - }) - } - - // Actor 1 shouldn't have got any of the messages, they should go to - // actor 2. - require.EqualValues(t, 2, atomic.LoadInt64(&actor1Beh.processedMsgs)) - require.EqualValues(t, 1+2, atomic.LoadInt64(&actor2Beh.processedMsgs)) - - // Next, we'll unregister the second actor ref. - unregistered2 := UnregisterFromReceptionist( - h.receptionist, serviceKey, actor2Ref, - ) - require.True(t, unregistered2) - - // If we try to send another message, it should go to the DL. - tellMsg := newTestMsg("dynamic-tell-no-actors") - router.Tell(context.Background(), tellMsg) - h.assertDLOMessage(tellMsg) -} diff --git a/aliasmgr/aliasmgr_test.go b/aliasmgr/aliasmgr_test.go index cdf88423b..3237e5bbb 100644 --- a/aliasmgr/aliasmgr_test.go +++ b/aliasmgr/aliasmgr_test.go @@ -259,6 +259,7 @@ func TestGetNextScid(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { nextScid := getNextScid(test.current) require.Equal(t, test.expected, nextScid) diff --git a/amp/derivation_test.go b/amp/derivation_test.go index 3ebddca72..af8162d1d 100644 --- a/amp/derivation_test.go +++ b/amp/derivation_test.go @@ -43,6 +43,7 @@ var sharerTests = []sharerTest{ // receiver, produce identical child hashes and preimages as the sender. func TestSharer(t *testing.T) { for _, test := range sharerTests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/autopilot/agent.go b/autopilot/agent.go index 793e38d06..cbb29f38c 100644 --- a/autopilot/agent.go +++ b/autopilot/agent.go @@ -10,7 +10,7 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnwire" diff --git a/autopilot/agent_constraints.go b/autopilot/agent_constraints.go index 1b006ba47..63c7ac511 100644 --- a/autopilot/agent_constraints.go +++ b/autopilot/agent_constraints.go @@ -1,7 +1,7 @@ package autopilot import ( - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" ) // AgentConstraints is an interface the agent will query to determine what diff --git a/autopilot/agent_constraints_test.go b/autopilot/agent_constraints_test.go index 59d4932f2..64152cef8 100644 --- a/autopilot/agent_constraints_test.go +++ b/autopilot/agent_constraints_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnwire" ) diff --git a/autopilot/agent_test.go b/autopilot/agent_test.go index 0dbe4f112..82c21a2ff 100644 --- a/autopilot/agent_test.go +++ b/autopilot/agent_test.go @@ -10,8 +10,8 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/stretchr/testify/require" ) diff --git a/autopilot/betweenness_centrality_test.go b/autopilot/betweenness_centrality_test.go index 7e257a876..5571a78d2 100644 --- a/autopilot/betweenness_centrality_test.go +++ b/autopilot/betweenness_centrality_test.go @@ -40,6 +40,7 @@ func TestBetweennessCentralityEmptyGraph(t *testing.T) { ) for _, chanGraph := range chanGraphs { + chanGraph := chanGraph graph, err := chanGraph.genFunc(t) require.NoError(t, err, "unable to create graph") @@ -82,6 +83,7 @@ func TestBetweennessCentralityWithNonEmptyGraph(t *testing.T) { for _, numWorkers := range workers { for _, chanGraph := range chanGraphs { + chanGraph := chanGraph numWorkers := numWorkers graph, err := chanGraph.genFunc(t) require.NoError(t, err, "unable to create graph") @@ -108,6 +110,7 @@ func TestBetweennessCentralityWithNonEmptyGraph(t *testing.T) { require.NoError(t1, err) for _, expected := range tests { + expected := expected centrality := metric.GetMetric( expected.normalize, ) diff --git a/autopilot/centrality_testdata_test.go b/autopilot/centrality_testdata_test.go index 756feda42..38a9045af 100644 --- a/autopilot/centrality_testdata_test.go +++ b/autopilot/centrality_testdata_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/stretchr/testify/require" ) diff --git a/autopilot/combinedattach.go b/autopilot/combinedattach.go index d98fa5a97..9064a8229 100644 --- a/autopilot/combinedattach.go +++ b/autopilot/combinedattach.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" ) // WeightedHeuristic is a tuple that associates a weight to an diff --git a/autopilot/externalscoreattach.go b/autopilot/externalscoreattach.go index 1db25fb72..a979e15ee 100644 --- a/autopilot/externalscoreattach.go +++ b/autopilot/externalscoreattach.go @@ -5,7 +5,7 @@ import ( "fmt" "sync" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" ) // ExternalScoreAttachment is an implementation of the AttachmentHeuristic diff --git a/autopilot/externalscoreattach_test.go b/autopilot/externalscoreattach_test.go index 5219a56fd..7bf440aa5 100644 --- a/autopilot/externalscoreattach_test.go +++ b/autopilot/externalscoreattach_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/autopilot" ) diff --git a/autopilot/graph.go b/autopilot/graph.go index d2876ed91..be6401522 100644 --- a/autopilot/graph.go +++ b/autopilot/graph.go @@ -8,7 +8,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lnwire" @@ -101,23 +101,25 @@ func (d *databaseChannelGraph) ForEachNode(ctx context.Context, }, reset) } -// ForEachNodesChannels iterates through all connected nodes, and for each -// node, all the channels that connect to it. The passed callback will be -// called with the context, the node's pubkey, and a slice of ChannelEdge -// that connect to the node. +// ForEachNodesChannels iterates through all connected nodes, and for each node, +// all the channels that connect to it. The passed callback will be called with +// the context, the Node itself, and a slice of ChannelEdge that connect to the +// node. // // NOTE: Part of the autopilot.ChannelGraph interface. func (d *databaseChannelGraph) ForEachNodesChannels(ctx context.Context, - cb func(context.Context, NodeID, []*ChannelEdge) error, + cb func(context.Context, Node, []*ChannelEdge) error, reset func()) error { - // The channel-scoring callers only need topology data here. Address - // filtering happens through ForEachNode before connecting to peers. return d.db.ForEachNodeCached( - ctx, func(ctx context.Context, node route.Vertex, + ctx, true, func(ctx context.Context, node route.Vertex, + addrs []net.Addr, chans map[uint64]*graphdb.DirectedChannel) error { - if len(chans) == 0 { + // We'll skip over any node that doesn't have any + // advertised addresses. As we won't be able to reach + // them to actually open any channels. + if len(addrs) == 0 { return nil } @@ -132,7 +134,10 @@ func (d *databaseChannelGraph) ForEachNodesChannels(ctx context.Context, }) } - return cb(ctx, NodeID(node), edges) + return cb(ctx, &dbNode{ + pub: node, + addrs: addrs, + }, edges) }, reset, ) } @@ -191,8 +196,8 @@ func (nc dbNodeCached) Addrs() []net.Addr { func (dc *databaseChannelGraphCached) ForEachNode(ctx context.Context, cb func(context.Context, Node) error, reset func()) error { - return dc.db.ForEachNodeCached(ctx, func(ctx context.Context, - n route.Vertex, + return dc.db.ForEachNodeCached(ctx, false, func(ctx context.Context, + n route.Vertex, _ []net.Addr, channels map[uint64]*graphdb.DirectedChannel) error { if len(channels) > 0 { @@ -208,24 +213,20 @@ func (dc *databaseChannelGraphCached) ForEachNode(ctx context.Context, }, reset) } -// ForEachNodesChannels iterates through all connected nodes, and for each -// node, all the channels that connect to it. The passed callback will be -// called with the context, the node's pubkey, and a slice of ChannelEdge -// that connect to the node. +// ForEachNodesChannels iterates through all connected nodes, and for each node, +// all the channels that connect to it. The passed callback will be called with +// the context, the Node itself, and a slice of ChannelEdge that connect to the +// node. // // NOTE: Part of the autopilot.ChannelGraph interface. func (dc *databaseChannelGraphCached) ForEachNodesChannels(ctx context.Context, - cb func(context.Context, NodeID, []*ChannelEdge) error, + cb func(context.Context, Node, []*ChannelEdge) error, reset func()) error { - return dc.db.ForEachNodeCached(ctx, func(ctx context.Context, - n route.Vertex, + return dc.db.ForEachNodeCached(ctx, false, func(ctx context.Context, + n route.Vertex, _ []net.Addr, channels map[uint64]*graphdb.DirectedChannel) error { - if len(channels) == 0 { - return nil - } - edges := make([]*ChannelEdge, 0, len(channels)) for cid, channel := range channels { edges = append(edges, &ChannelEdge{ @@ -235,7 +236,18 @@ func (dc *databaseChannelGraphCached) ForEachNodesChannels(ctx context.Context, }) } - return cb(ctx, NodeID(n), edges) + if len(channels) > 0 { + node := dbNodeCached{ + node: n, + channels: channels, + } + + if err := cb(ctx, node, edges); err != nil { + return err + } + } + + return nil }, reset) } diff --git a/autopilot/graph_test.go b/autopilot/graph_test.go index b199c463d..9a52e67c8 100644 --- a/autopilot/graph_test.go +++ b/autopilot/graph_test.go @@ -3,7 +3,7 @@ package autopilot_test import ( "testing" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/autopilot" ) diff --git a/autopilot/interface.go b/autopilot/interface.go index 8c24e0340..215f92035 100644 --- a/autopilot/interface.go +++ b/autopilot/interface.go @@ -5,8 +5,8 @@ import ( "net" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lnwire" @@ -84,10 +84,10 @@ type ChannelGraph interface { // ForEachNodesChannels iterates through all connected nodes, and for // each node, all the channels that connect to it. The passed callback - // will be called with the context, the node's pubkey, and a slice of + // will be called with the context, the Node itself, and a slice of // ChannelEdge that connect to the node. ForEachNodesChannels(ctx context.Context, - cb func(context.Context, NodeID, []*ChannelEdge) error, + cb func(context.Context, Node, []*ChannelEdge) error, reset func()) error } @@ -237,8 +237,9 @@ type GraphSource interface { // channel graph cache if one is available. It is less consistent than // ForEachNode since any further calls are made across multiple // transactions. - ForEachNodeCached(ctx context.Context, + ForEachNodeCached(ctx context.Context, withAddrs bool, cb func(ctx context.Context, node route.Vertex, + addrs []net.Addr, chans map[uint64]*graphdb.DirectedChannel) error, reset func()) error } diff --git a/autopilot/manager.go b/autopilot/manager.go index aa56d15b9..600a1055b 100644 --- a/autopilot/manager.go +++ b/autopilot/manager.go @@ -6,7 +6,7 @@ import ( "sync" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" diff --git a/autopilot/prefattach.go b/autopilot/prefattach.go index b90a15e7e..267c13db3 100644 --- a/autopilot/prefattach.go +++ b/autopilot/prefattach.go @@ -6,7 +6,7 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" ) // minMedianChanSizeFraction determines the minimum size a channel must have to @@ -90,7 +90,7 @@ func (p *PrefAttachment) NodeScores(ctx context.Context, g ChannelGraph, seenChans = make(map[uint64]struct{}) ) err := g.ForEachNodesChannels( - ctx, func(_ context.Context, node NodeID, + ctx, func(_ context.Context, node Node, channels []*ChannelEdge) error { for _, e := range channels { @@ -121,7 +121,7 @@ func (p *PrefAttachment) NodeScores(ctx context.Context, g ChannelGraph, var maxChans int nodeChanNum := make(map[NodeID]int) err = g.ForEachNodesChannels( - ctx, func(ctx context.Context, node NodeID, + ctx, func(ctx context.Context, node Node, edges []*ChannelEdge) error { var nodeChans int @@ -154,16 +154,17 @@ func (p *PrefAttachment) NodeScores(ctx context.Context, g ChannelGraph, // If this node is not among our nodes to score, we can // return early. - if _, ok := nodes[node]; !ok { + nID := NodeID(node.PubKey()) + if _, ok := nodes[nID]; !ok { log.Tracef("Node %x not among nodes to score, "+ - "ignoring", node[:]) + "ignoring", nID[:]) return nil } // Otherwise we'll record the number of channels. - nodeChanNum[node] = nodeChans + nodeChanNum[nID] = nodeChans log.Tracef("Counted %v channels for node %x", nodeChans, - node[:]) + nID[:]) return nil }, func() { diff --git a/autopilot/prefattach_test.go b/autopilot/prefattach_test.go index d836a7b60..70f7e2b68 100644 --- a/autopilot/prefattach_test.go +++ b/autopilot/prefattach_test.go @@ -11,8 +11,7 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lnwire" @@ -32,14 +31,12 @@ type testGraph interface { } type testDBGraph struct { - db *graphdb.VersionedGraph + db *graphdb.ChannelGraph databaseChannelGraph } func newDiskChanGraph(t *testing.T) (testGraph, error) { - graphDB := graphdb.NewVersionedGraph( - graphdb.MakeTestGraph(t), lnwire.GossipVersion1, - ) + graphDB := graphdb.MakeTestGraph(t) require.NoError(t, graphDB.Start()) t.Cleanup(func() { require.NoError(t, graphDB.Stop()) @@ -91,6 +88,7 @@ func TestPrefAttachmentSelectEmptyGraph(t *testing.T) { } for _, chanGraph := range chanGraphs { + chanGraph := chanGraph graph, err := chanGraph.genFunc(t) require.NoError(t, err, "unable to create graph") @@ -127,6 +125,7 @@ func TestPrefAttachmentSelectTwoVertexes(t *testing.T) { ) for _, chanGraph := range chanGraphs { + chanGraph := chanGraph graph, err := chanGraph.genFunc(t) require.NoError(t, err, "unable to create graph") @@ -213,6 +212,7 @@ func TestPrefAttachmentSelectGreedyAllocation(t *testing.T) { ) for _, chanGraph := range chanGraphs { + chanGraph := chanGraph graph, err := chanGraph.genFunc(t) require.NoError(t, err, "unable to create graph") @@ -243,11 +243,11 @@ func TestPrefAttachmentSelectGreedyAllocation(t *testing.T) { twoChans := false nodes := make(map[NodeID]struct{}) err = graph.ForEachNodesChannels( - ctx, func(_ context.Context, node NodeID, + ctx, func(_ context.Context, node Node, edges []*ChannelEdge) error { numNodes++ - nodes[node] = struct{}{} + nodes[node.PubKey()] = struct{}{} numChans := 0 for range edges { @@ -325,6 +325,7 @@ func TestPrefAttachmentSelectSkipNodes(t *testing.T) { ) for _, chanGraph := range chanGraphs { + chanGraph := chanGraph graph, err := chanGraph.genFunc(t) require.NoError(t, err, "unable to create graph") @@ -416,21 +417,19 @@ func (d *testDBGraph) addRandChannel(node1, node2 *btcec.PublicKey, case errors.Is(err, graphdb.ErrGraphNodeNotFound): fallthrough case errors.Is(err, graphdb.ErrGraphNotFound): - //nolint:ll - graphNode := models.NewV1Node( - route.NewVertex(pub), - &models.NodeV1Fields{ - Addresses: []net.Addr{&net.TCPAddr{ - IP: bytes.Repeat( - []byte("a"), 16, - ), - }}, - Features: lnwire.NewFeatureVector( - nil, lnwire.Features, - ).RawFeatureVector, - AuthSigBytes: testSig.Serialize(), - }, - ) + graphNode := &models.Node{ + HaveNodeAnnouncement: true, + Addresses: []net.Addr{&net.TCPAddr{ + IP: bytes.Repeat( + []byte("a"), 16, + ), + }}, + Features: lnwire.NewFeatureVector( + nil, lnwire.Features, + ), + AuthSigBytes: testSig.Serialize(), + } + graphNode.AddPubKey(pub) err := d.db.AddNode( context.Background(), graphNode, ) @@ -448,18 +447,19 @@ func (d *testDBGraph) addRandChannel(node1, node2 *btcec.PublicKey, if err != nil { return nil, err } - - dbNode := models.NewV1Node( - route.NewVertex(nodeKey), &models.NodeV1Fields{ - Addresses: []net.Addr{&net.TCPAddr{ + dbNode := &models.Node{ + HaveNodeAnnouncement: true, + Addresses: []net.Addr{ + &net.TCPAddr{ IP: bytes.Repeat([]byte("a"), 16), - }}, - Features: lnwire.NewFeatureVector( - nil, lnwire.Features, - ).RawFeatureVector, - AuthSigBytes: testSig.Serialize(), + }, }, - ) + Features: lnwire.NewFeatureVector( + nil, lnwire.Features, + ), + AuthSigBytes: testSig.Serialize(), + } + dbNode.AddPubKey(nodeKey) if err := d.db.AddNode( context.Background(), dbNode, ); err != nil { @@ -489,26 +489,16 @@ func (d *testDBGraph) addRandChannel(node1, node2 *btcec.PublicKey, } chanID := randChanID() - nodeKey1 := route.NewVertex(lnNode1) - nodeKey2 := route.NewVertex(lnNode2) - btcKey1 := route.NewVertex(lnNode1) - btcKey2 := route.NewVertex(lnNode2) - edge, err := models.NewV1Channel( - chanID.ToUint64(), chainhash.Hash{}, nodeKey1, nodeKey2, - &models.ChannelV1Fields{ - BitcoinKey1Bytes: btcKey1, - BitcoinKey2Bytes: btcKey2, - }, models.WithCapacity(capacity), - ) - if err != nil { - return nil, nil, err + edge := &models.ChannelEdgeInfo{ + ChannelID: chanID.ToUint64(), + Capacity: capacity, + Features: lnwire.EmptyFeatureVector(), } - + edge.AddNodeKeys(lnNode1, lnNode2, lnNode1, lnNode2) if err := d.db.AddChannelEdge(ctx, edge); err != nil { return nil, nil, err } edgePolicy := &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: chanID.ToUint64(), LastUpdate: time.Now(), @@ -525,7 +515,6 @@ func (d *testDBGraph) addRandChannel(node1, node2 *btcec.PublicKey, return nil, nil, err } edgePolicy = &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: chanID.ToUint64(), LastUpdate: time.Now(), @@ -559,19 +548,19 @@ func (d *testDBGraph) addRandNode() (*btcec.PublicKey, error) { if err != nil { return nil, err } - dbNode := models.NewV1Node( - route.NewVertex(nodeKey), &models.NodeV1Fields{ - Addresses: []net.Addr{ - &net.TCPAddr{ - IP: bytes.Repeat([]byte("a"), 16), - }, + dbNode := &models.Node{ + HaveNodeAnnouncement: true, + Addresses: []net.Addr{ + &net.TCPAddr{ + IP: bytes.Repeat([]byte("a"), 16), }, - Features: lnwire.NewFeatureVector( - nil, lnwire.Features, - ).RawFeatureVector, - AuthSigBytes: testSig.Serialize(), }, - ) + Features: lnwire.NewFeatureVector( + nil, lnwire.Features, + ), + AuthSigBytes: testSig.Serialize(), + } + dbNode.AddPubKey(nodeKey) err = d.db.AddNode(context.Background(), dbNode) if err != nil { return nil, err @@ -615,15 +604,14 @@ func (m *memChannelGraph) ForEachNode(ctx context.Context, return nil } -// ForEachNodesChannels iterates through all connected nodes, and for each -// node, all the channels that connect to it. The passed callback will be -// called with the context, the node's pubkey, and a slice of ChannelEdge -// that connect to the node. +// ForEachNodesChannels iterates through all connected nodes, and for each node, +// all the channels that connect to it. The passed callback will be called with +// the context, the Node itself, and a slice of ChannelEdge that connect to the +// node. // // NOTE: Part of the autopilot.ChannelGraph interface. func (m *memChannelGraph) ForEachNodesChannels(ctx context.Context, - cb func(context.Context, NodeID, []*ChannelEdge) error, - _ func()) error { + cb func(context.Context, Node, []*ChannelEdge) error, _ func()) error { for _, node := range m.graph { edges := make([]*ChannelEdge, 0, len(node.chans)) @@ -631,7 +619,7 @@ func (m *memChannelGraph) ForEachNodesChannels(ctx context.Context, edges = append(edges, &node.chans[i]) } - if err := cb(ctx, NewNodeID(node.pub), edges); err != nil { + if err := cb(ctx, node, edges); err != nil { return err } } diff --git a/autopilot/simple_graph.go b/autopilot/simple_graph.go index d6072cd53..44f514903 100644 --- a/autopilot/simple_graph.go +++ b/autopilot/simple_graph.go @@ -2,6 +2,8 @@ package autopilot import ( "context" + + "github.com/lightningnetwork/lnd/routing/route" ) // diameterCutoff is used to discard nodes in the diameter calculation. @@ -33,11 +35,12 @@ func NewSimpleGraph(ctx context.Context, g ChannelGraph) (*SimpleGraph, error) { // The returned index is then used to create a simplified adjacency list // where each node is identified by its index instead of its pubkey, and // also to create a mapping from node index to node pubkey. - getNodeIndex := func(node NodeID) int { - nodeIndex, ok := nodes[node] + getNodeIndex := func(node route.Vertex) int { + key := NodeID(node) + nodeIndex, ok := nodes[key] if !ok { - nodes[node] = nextIndex + nodes[key] = nextIndex nodeIndex = nextIndex nextIndex++ } @@ -48,12 +51,12 @@ func NewSimpleGraph(ctx context.Context, g ChannelGraph) (*SimpleGraph, error) { // Iterate over each node and each channel and update the adj and the // node index. err := g.ForEachNodesChannels(ctx, func(_ context.Context, - node NodeID, channels []*ChannelEdge) error { + node Node, channels []*ChannelEdge) error { - u := getNodeIndex(node) + u := getNodeIndex(node.PubKey()) for _, edge := range channels { - v := getNodeIndex(NodeID(edge.Peer)) + v := getNodeIndex(edge.Peer) adj[u] = append(adj[u], v) } diff --git a/autopilot/top_centrality.go b/autopilot/top_centrality.go index 39e10a434..e96b34097 100644 --- a/autopilot/top_centrality.go +++ b/autopilot/top_centrality.go @@ -4,7 +4,7 @@ import ( "context" "runtime" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" ) // TopCentrality is a simple greedy technique to create connections to nodes diff --git a/autopilot/top_centrality_test.go b/autopilot/top_centrality_test.go index 57e8d2fa2..282e60c36 100644 --- a/autopilot/top_centrality_test.go +++ b/autopilot/top_centrality_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/stretchr/testify/require" ) @@ -83,6 +83,7 @@ func TestTopCentrality(t *testing.T) { } for _, chanGraph := range chanGraphs { + chanGraph := chanGraph success := t.Run(chanGraph.name, func(t1 *testing.T) { t1.Parallel() diff --git a/blockcache/blockcache.go b/blockcache/blockcache.go index 54749e23b..532629feb 100644 --- a/blockcache/blockcache.go +++ b/blockcache/blockcache.go @@ -1,9 +1,9 @@ package blockcache import ( - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/neutrino" "github.com/lightninglabs/neutrino/cache" "github.com/lightninglabs/neutrino/cache/lru" diff --git a/blockcache/blockcache_test.go b/blockcache/blockcache_test.go index 89cfd440b..f108ab644 100644 --- a/blockcache/blockcache_test.go +++ b/blockcache/blockcache_test.go @@ -6,9 +6,9 @@ import ( "sync" "testing" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/neutrino" "github.com/lightninglabs/neutrino/cache" "github.com/stretchr/testify/require" diff --git a/bolt12/decode.go b/bolt12/decode.go deleted file mode 100644 index 4307455b8..000000000 --- a/bolt12/decode.go +++ /dev/null @@ -1,29 +0,0 @@ -package bolt12 - -import ( - "bytes" - "fmt" - - "github.com/lightningnetwork/lnd/tlv" -) - -// decodeStream runs a single typed-stream pass over data and returns the -// canonical TypeMap. Records may be passed in any order; NewStream requires -// them sorted, so SortRecords runs first. -func decodeStream(data []byte, records ...tlv.Record) (tlv.TypeMap, error) { - tlv.SortRecords(records) - - stream, err := tlv.NewStream(records...) - if err != nil { - return nil, fmt.Errorf("create stream: %w", err) - } - - typeMap, err := stream.DecodeWithParsedTypesP2P( - bytes.NewReader(data), - ) - if err != nil { - return nil, fmt.Errorf("decode stream: %w", err) - } - - return typeMap, nil -} diff --git a/bolt12/doc.go b/bolt12/doc.go deleted file mode 100644 index c58a1ced1..000000000 --- a/bolt12/doc.go +++ /dev/null @@ -1,19 +0,0 @@ -// Package bolt12 implements encoding, decoding, and validation for BOLT 12 -// Offers, Invoice Requests, and Invoices. It provides a pure codec library -// with no LND daemon dependencies. -// -// BOLT 12 messages use TLV streams encoded with a checksumless bech32 variant -// and signed with BIP-340 Schnorr signatures over a Merkle tree of TLV fields. -// -// Human-readable prefixes: -// - lno: Offer -// - lnr: Invoice Request -// - lni: Invoice -// -// # Codec Contract -// -// Encode validates before serialising and refuses to emit bytes that would fail -// the writer requirements, invalid bytes are unrepresentable on the wire. -// Low-level decoders stay permissive so diagnostic and fuzz harnesses can -// inspect malformed input. -package bolt12 diff --git a/bolt12/helpers_test.go b/bolt12/helpers_test.go deleted file mode 100644 index 78bbdfdff..000000000 --- a/bolt12/helpers_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package bolt12 - -import ( - "bytes" - - "github.com/btcsuite/btcd/btcec/v2" -) - -// bobKey returns the deterministic spec test key for Bob, whose 32-byte scalar -// is 0x42 repeated. Used across signature and round-trip tests so the same key -// is not reconstructed in every callsite. -func bobKey() (*btcec.PrivateKey, *btcec.PublicKey) { - priv, pub := btcec.PrivKeyFromBytes(bytes.Repeat([]byte{0x42}, 32)) - - return priv, pub -} - -// aliceKey returns the deterministic spec test key for Alice, whose 32-byte -// scalar is 0x41 repeated. -func aliceKey() (*btcec.PrivateKey, *btcec.PublicKey) { - priv, pub := btcec.PrivKeyFromBytes(bytes.Repeat([]byte{0x41}, 32)) - - return priv, pub -} diff --git a/bolt12/invoice.go b/bolt12/invoice.go deleted file mode 100644 index 6a04c0120..000000000 --- a/bolt12/invoice.go +++ /dev/null @@ -1,440 +0,0 @@ -package bolt12 - -import ( - "bytes" - "fmt" - "maps" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/tlv" -) - -// Invoice represents a BOLT 12 invoice message. It mirrors all non-signature -// invoice_request fields (types 0-91) and adds invoice-specific fields (types -// 160-176) plus a Schnorr signature (type 240). -// -// An invoice in response to a request should be constructed from that request -// (e.g., using NewInvoiceFromRequest) to mirror its fields. The caller then -// populates the invoice-specific fields and signs it. -type Invoice struct { - // Fields in the 0-91 range are mirrored verbatim from the - // invoice_request (which carries the offer's fields); the byte-for-byte - // match is enforced by ValidateInvoiceAgainstRequest. - - // InvreqMetadata is the payer metadata. - InvreqMetadata tlv.OptionalRecordT[tlv.TlvType0, tlv.Blob] - - // OfferChains are the chains the offer is valid for. - OfferChains tlv.OptionalRecordT[tlv.TlvType2, ChainsRecord] - - // OfferMetadata is the offer metadata. - OfferMetadata tlv.OptionalRecordT[tlv.TlvType4, tlv.Blob] - - // OfferCurrency is the offer currency. - OfferCurrency tlv.OptionalRecordT[tlv.TlvType6, tlv.Blob] - - // OfferAmount is the offer amount. - OfferAmount tlv.OptionalRecordT[tlv.TlvType8, TUint64] - - // OfferDescription is the offer description. - OfferDescription tlv.OptionalRecordT[tlv.TlvType10, tlv.Blob] - - // OfferFeatures are the offer features. - OfferFeatures tlv.OptionalRecordT[ - tlv.TlvType12, lnwire.RawFeatureVector, - ] - - // OfferAbsoluteExpiry is the offer's absolute expiry. - OfferAbsoluteExpiry tlv.OptionalRecordT[tlv.TlvType14, TUint64] - - // OfferPaths are the offer's blinded paths. - OfferPaths tlv.OptionalRecordT[tlv.TlvType16, lnwire.BlindedPaths] - - // OfferIssuer is the offer issuer name. - OfferIssuer tlv.OptionalRecordT[tlv.TlvType18, tlv.Blob] - - // OfferQuantityMax is the offer's maximum quantity. - OfferQuantityMax tlv.OptionalRecordT[tlv.TlvType20, TUint64] - - // OfferIssuerID is the offer issuer's public key. - OfferIssuerID tlv.OptionalRecordT[tlv.TlvType22, *btcec.PublicKey] - - // InvreqChain is the requested chain. - InvreqChain tlv.OptionalRecordT[tlv.TlvType80, [32]byte] - - // InvreqAmount is the amount the payer offered. - InvreqAmount tlv.OptionalRecordT[tlv.TlvType82, TUint64] - - // InvreqFeatures are the payer's features. - InvreqFeatures tlv.OptionalRecordT[ - tlv.TlvType84, lnwire.RawFeatureVector, - ] - - // InvreqQuantity is the requested quantity. - InvreqQuantity tlv.OptionalRecordT[tlv.TlvType86, TUint64] - - // InvreqPayerID is the payer's signing public key. - InvreqPayerID tlv.OptionalRecordT[tlv.TlvType88, *btcec.PublicKey] - - // InvreqPayerNote is an optional payer note. - InvreqPayerNote tlv.OptionalRecordT[tlv.TlvType89, tlv.Blob] - - // InvreqPaths are the payer's blinded paths to send the invoice to. - InvreqPaths tlv.OptionalRecordT[tlv.TlvType90, lnwire.BlindedPaths] - - // InvreqBip353Name is the payer's BIP 353 name. - InvreqBip353Name tlv.OptionalRecordT[tlv.TlvType91, tlv.Blob] - - // Fields from type 160 on are invoice-specific. - - // InvoicePaths are the blinded paths to the recipient node. - InvoicePaths tlv.OptionalRecordT[tlv.TlvType160, lnwire.BlindedPaths] - - // InvoiceBlindedPay carries one blinded_payinfo per invoice_paths - // entry, in order. - InvoiceBlindedPay tlv.OptionalRecordT[tlv.TlvType162, BlindedPayInfos] - - // InvoiceCreatedAt is the creation time in seconds since the Unix - // epoch. - InvoiceCreatedAt tlv.OptionalRecordT[tlv.TlvType164, TUint64] - - // InvoiceRelativeExp is the expiry in seconds after creation. When - // absent the spec default of 7200 seconds applies. - InvoiceRelativeExp tlv.OptionalRecordT[tlv.TlvType166, TUint32] - - // InvoicePaymentHash is the SHA256 hash of the payment preimage. - InvoicePaymentHash tlv.OptionalRecordT[tlv.TlvType168, [32]byte] - - // InvoiceAmount is the minimum amount the payee will accept, in the - // minimal payable unit of invreq_chain. - InvoiceAmount tlv.OptionalRecordT[tlv.TlvType170, TUint64] - - // InvoiceFallbacks are optional on-chain fallback addresses. - InvoiceFallbacks tlv.OptionalRecordT[ - tlv.TlvType172, FallbackAddresses, - ] - - // InvoiceFeatures are the features of the invoice. - InvoiceFeatures tlv.OptionalRecordT[ - tlv.TlvType174, lnwire.RawFeatureVector, - ] - - // InvoiceNodeID is the public key of the recipient node, used to verify - // the signature. - InvoiceNodeID tlv.OptionalRecordT[tlv.TlvType176, *btcec.PublicKey] - - // Signature is a BIP-340 Schnorr signature covering all fields. - Signature tlv.OptionalRecordT[tlv.TlvType240, [64]byte] - - // decodedTLVs is the canonical TypeMap produced by the typed-stream - // pass that decoded this invoice. See Offer.decodedTLVs for the design - // rationale. - decodedTLVs tlv.TypeMap -} - -// AllRecords returns the canonical sorted record list for this invoice, merging -// the typed records with any extra signed-range fields that the decoder -// preserved. -// -// NOTE: this is part of the tlv.PureTLVMessage interface. -func (inv *Invoice) AllRecords() []tlv.Record { - return allRecordsFromTypeMap( - inv.allRecordProducers(), inv.decodedTLVs, - ) -} - -var _ lnwire.PureTLVMessage = (*Invoice)(nil) - -const ( - // maxWitnessVersion is the highest segwit witness version a usable - // fallback address may carry; the BOLT 12 reader ignores anything - // above it. - maxWitnessVersion = 16 - - // minWitnessProgramLen and maxWitnessProgramLen bound the witness - // program length, in bytes, of a usable fallback address. - minWitnessProgramLen = 2 - maxWitnessProgramLen = 40 -) - -// UsableFallbackAddresses returns the invoice_fallbacks entries a payer may use -// after applying the BOLT 12 reader's MUST-ignore rules for the bitcoin chain. -func (inv *Invoice) UsableFallbackAddresses() []FallbackAddress { - // Unwrap the optional up front so the filtering loop stays flat; a nil - // Addrs slice ranges as empty. - fallbacks := inv.InvoiceFallbacks.ValOpt().UnwrapOr(FallbackAddresses{}) - - var addrs []FallbackAddress - for _, a := range fallbacks.Addrs { - // MUST ignore any fallback_address for which version is greater - // than 16. - if a.Version > maxWitnessVersion { - continue - } - - // MUST ignore any fallback_address for which address is less - // than 2 or greater than 40 bytes. - if len(a.Address) < minWitnessProgramLen || - len(a.Address) > maxWitnessProgramLen { - - continue - } - - // MUST ignore any fallback_address for which address does not - // meet known requirements for the given version. NOT enforced - // here: the per-version witness-program check needs on-chain - // address rules above this codec, so a caller dispatching - // on-chain MUST apply it. - addrs = append(addrs, a) - } - - return addrs -} - -// UsablePath pairs a blinded path with its payment parameters, as returned by -// UsablePaths after the BOLT 12 reader's feature filter has been applied. -type UsablePath struct { - // Path is the blinded path to the recipient. - Path lnwire.BlindedPath - - // PayInfo is the blinded_payinfo for Path. - PayInfo BlindedPayInfo -} - -// UsablePaths returns the invoice_paths entries a payer may use, each paired -// with its blinded_payinfo, after applying the BOLT 12 reader rule that a path -// MUST NOT be used when its payinfo.features has unknown required (even) bits -// set. knownBlindedFeatures names the feature bits the reader understands. -// -// The result is empty when invoice_paths or invoice_blindedpay is absent, or -// when the two lists differ in length; ValidateInvoiceRead rejects those cases -// separately, so a caller that validates first can treat an empty result as -// "no usable paths". -func (inv *Invoice) UsablePaths( - knownBlindedFeatures map[lnwire.FeatureBit]string) []UsablePath { - - paths := inv.InvoicePaths.ValOpt().UnwrapOr(lnwire.BlindedPaths{}) - bp := inv.InvoiceBlindedPay.ValOpt().UnwrapOr(BlindedPayInfos{}) - - // Entries pair by index; a length mismatch is rejected upstream by - // ValidateInvoiceRead, so guard here to stay in bounds. - if len(paths.Paths) != len(bp.Infos) { - return nil - } - - var usable []UsablePath - for i := range bp.Infos { - // MUST NOT use the path if payinfo.features has any unknown - // even bits set. - fv := bp.Infos[i].Features - wrapped := lnwire.NewFeatureVector(&fv, knownBlindedFeatures) - if len(wrapped.UnknownRequiredFeatures()) > 0 { - continue - } - - usable = append(usable, UsablePath{ - Path: paths.Paths[i], - PayInfo: bp.Infos[i], - }) - } - - return usable -} - -// allRecordProducers returns record producers for all set fields. -func (inv *Invoice) allRecordProducers() []tlv.RecordProducer { - var p []tlv.RecordProducer - - // Invreq mirrored fields. - lnwire.AddOpt(&p, inv.InvreqMetadata) - lnwire.AddOpt(&p, inv.OfferChains) - lnwire.AddOpt(&p, inv.OfferMetadata) - lnwire.AddOpt(&p, inv.OfferCurrency) - lnwire.AddOpt(&p, inv.OfferAmount) - lnwire.AddOpt(&p, inv.OfferDescription) - lnwire.AddOpt(&p, inv.OfferFeatures) - lnwire.AddOpt(&p, inv.OfferAbsoluteExpiry) - lnwire.AddOpt(&p, inv.OfferPaths) - lnwire.AddOpt(&p, inv.OfferIssuer) - lnwire.AddOpt(&p, inv.OfferQuantityMax) - lnwire.AddOpt(&p, inv.OfferIssuerID) - lnwire.AddOpt(&p, inv.InvreqChain) - lnwire.AddOpt(&p, inv.InvreqAmount) - lnwire.AddOpt(&p, inv.InvreqFeatures) - lnwire.AddOpt(&p, inv.InvreqQuantity) - lnwire.AddOpt(&p, inv.InvreqPayerID) - lnwire.AddOpt(&p, inv.InvreqPayerNote) - lnwire.AddOpt(&p, inv.InvreqPaths) - lnwire.AddOpt(&p, inv.InvreqBip353Name) - - // Invoice-specific fields. - lnwire.AddOpt(&p, inv.InvoicePaths) - lnwire.AddOpt(&p, inv.InvoiceBlindedPay) - lnwire.AddOpt(&p, inv.InvoiceCreatedAt) - lnwire.AddOpt(&p, inv.InvoiceRelativeExp) - lnwire.AddOpt(&p, inv.InvoicePaymentHash) - lnwire.AddOpt(&p, inv.InvoiceAmount) - lnwire.AddOpt(&p, inv.InvoiceFallbacks) - lnwire.AddOpt(&p, inv.InvoiceFeatures) - lnwire.AddOpt(&p, inv.InvoiceNodeID) - lnwire.AddOpt(&p, inv.Signature) - - return p -} - -// Encode validates the invoice per writer requirements and serialises it via -// the PureTLVMessage shape. -func (inv *Invoice) Encode() ([]byte, error) { - if err := ValidateInvoiceWrite(inv); err != nil { - return nil, fmt.Errorf("validate invoice: %w", err) - } - - var buf bytes.Buffer - if err := lnwire.EncodePureTLVMessage(inv, &buf); err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -// DecodeInvoice deserializes an invoice from a TLV byte stream. Decoding is -// permissive: callers that need spec compliance must run ValidateInvoiceRead. -func DecodeInvoice(data []byte) (*Invoice, error) { - var inv Invoice - - invreqMetadata := tlv.ZeroRecordT[tlv.TlvType0, tlv.Blob]() - chains := tlv.ZeroRecordT[tlv.TlvType2, ChainsRecord]() - offerMeta := tlv.ZeroRecordT[tlv.TlvType4, tlv.Blob]() - currency := tlv.ZeroRecordT[tlv.TlvType6, tlv.Blob]() - offerAmt := tlv.ZeroRecordT[tlv.TlvType8, TUint64]() - desc := tlv.ZeroRecordT[tlv.TlvType10, tlv.Blob]() - offerFeat := tlv.ZeroRecordT[tlv.TlvType12, lnwire.RawFeatureVector]() - expiry := tlv.ZeroRecordT[tlv.TlvType14, TUint64]() - offerPaths := tlv.ZeroRecordT[tlv.TlvType16, lnwire.BlindedPaths]() - issuer := tlv.ZeroRecordT[tlv.TlvType18, tlv.Blob]() - qtyMax := tlv.ZeroRecordT[tlv.TlvType20, TUint64]() - issuerID := tlv.ZeroRecordT[tlv.TlvType22, *btcec.PublicKey]() - invreqChain := tlv.ZeroRecordT[tlv.TlvType80, [32]byte]() - invreqAmt := tlv.ZeroRecordT[tlv.TlvType82, TUint64]() - invreqFeat := tlv.ZeroRecordT[tlv.TlvType84, lnwire.RawFeatureVector]() - invreqQty := tlv.ZeroRecordT[tlv.TlvType86, TUint64]() - payerID := tlv.ZeroRecordT[tlv.TlvType88, *btcec.PublicKey]() - payerNote := tlv.ZeroRecordT[tlv.TlvType89, tlv.Blob]() - invreqPaths := tlv.ZeroRecordT[tlv.TlvType90, lnwire.BlindedPaths]() - bip353 := tlv.ZeroRecordT[tlv.TlvType91, tlv.Blob]() - invPaths := tlv.ZeroRecordT[tlv.TlvType160, lnwire.BlindedPaths]() - blindedPay := tlv.ZeroRecordT[tlv.TlvType162, BlindedPayInfos]() - createdAt := tlv.ZeroRecordT[tlv.TlvType164, TUint64]() - relExp := tlv.ZeroRecordT[tlv.TlvType166, TUint32]() - payHash := tlv.ZeroRecordT[tlv.TlvType168, [32]byte]() - invAmt := tlv.ZeroRecordT[tlv.TlvType170, TUint64]() - fallbacks := tlv.ZeroRecordT[tlv.TlvType172, FallbackAddresses]() - invFeat := tlv.ZeroRecordT[tlv.TlvType174, lnwire.RawFeatureVector]() - nodeID := tlv.ZeroRecordT[tlv.TlvType176, *btcec.PublicKey]() - sig := tlv.ZeroRecordT[tlv.TlvType240, [64]byte]() - - tm, err := decodeStream( - data, - invreqMetadata.Record(), chains.Record(), offerMeta.Record(), - currency.Record(), offerAmt.Record(), desc.Record(), - offerFeat.Record(), expiry.Record(), offerPaths.Record(), - issuer.Record(), qtyMax.Record(), issuerID.Record(), - invreqChain.Record(), invreqAmt.Record(), invreqFeat.Record(), - invreqQty.Record(), payerID.Record(), payerNote.Record(), - invreqPaths.Record(), bip353.Record(), invPaths.Record(), - blindedPay.Record(), createdAt.Record(), relExp.Record(), - payHash.Record(), invAmt.Record(), fallbacks.Record(), - invFeat.Record(), nodeID.Record(), sig.Record(), - ) - if err != nil { - return nil, fmt.Errorf("decode invoice: %w", err) - } - - lnwire.SetOptFromMap(tm, &inv.InvreqMetadata, invreqMetadata) - lnwire.SetOptFromMap(tm, &inv.OfferChains, chains) - lnwire.SetOptFromMap(tm, &inv.OfferMetadata, offerMeta) - lnwire.SetOptFromMap(tm, &inv.OfferCurrency, currency) - lnwire.SetOptFromMap(tm, &inv.OfferAmount, offerAmt) - lnwire.SetOptFromMap(tm, &inv.OfferDescription, desc) - lnwire.SetOptFromMap(tm, &inv.OfferFeatures, offerFeat) - lnwire.SetOptFromMap(tm, &inv.OfferAbsoluteExpiry, expiry) - lnwire.SetOptFromMap(tm, &inv.OfferPaths, offerPaths) - lnwire.SetOptFromMap(tm, &inv.OfferIssuer, issuer) - lnwire.SetOptFromMap(tm, &inv.OfferQuantityMax, qtyMax) - lnwire.SetOptFromMap(tm, &inv.OfferIssuerID, issuerID) - lnwire.SetOptFromMap(tm, &inv.InvreqChain, invreqChain) - lnwire.SetOptFromMap(tm, &inv.InvreqAmount, invreqAmt) - lnwire.SetOptFromMap(tm, &inv.InvreqFeatures, invreqFeat) - lnwire.SetOptFromMap(tm, &inv.InvreqQuantity, invreqQty) - lnwire.SetOptFromMap(tm, &inv.InvreqPayerID, payerID) - lnwire.SetOptFromMap(tm, &inv.InvreqPayerNote, payerNote) - lnwire.SetOptFromMap(tm, &inv.InvreqPaths, invreqPaths) - lnwire.SetOptFromMap(tm, &inv.InvreqBip353Name, bip353) - lnwire.SetOptFromMap(tm, &inv.InvoicePaths, invPaths) - lnwire.SetOptFromMap(tm, &inv.InvoiceBlindedPay, blindedPay) - lnwire.SetOptFromMap(tm, &inv.InvoiceCreatedAt, createdAt) - lnwire.SetOptFromMap(tm, &inv.InvoiceRelativeExp, relExp) - lnwire.SetOptFromMap(tm, &inv.InvoicePaymentHash, payHash) - lnwire.SetOptFromMap(tm, &inv.InvoiceAmount, invAmt) - lnwire.SetOptFromMap(tm, &inv.InvoiceFallbacks, fallbacks) - lnwire.SetOptFromMap(tm, &inv.InvoiceFeatures, invFeat) - lnwire.SetOptFromMap(tm, &inv.InvoiceNodeID, nodeID) - lnwire.SetOptFromMap(tm, &inv.Signature, sig) - - inv.decodedTLVs = tm - - return &inv, nil -} - -// NewInvoiceFromRequest constructs a new Invoice by copying (mirroring) all -// non-signature fields from the provided InvoiceRequest. When invreq_amount is -// present it is mirrored into invoice_amount per the writer requirement. The -// caller is responsible for populating the remaining invoice-specific fields -// (invoice_created_at, invoice_payment_hash, invoice_node_id, invoice_paths, -// invoice_blindedpay, ...) and signing the invoice. -func NewInvoiceFromRequest(req *InvoiceRequest) *Invoice { - inv := &Invoice{ - InvreqMetadata: req.InvreqMetadata, - OfferChains: req.OfferChains, - OfferMetadata: req.OfferMetadata, - OfferCurrency: req.OfferCurrency, - OfferAmount: req.OfferAmount, - OfferDescription: req.OfferDescription, - OfferFeatures: req.OfferFeatures, - OfferAbsoluteExpiry: req.OfferAbsoluteExpiry, - OfferPaths: req.OfferPaths, - OfferIssuer: req.OfferIssuer, - OfferQuantityMax: req.OfferQuantityMax, - OfferIssuerID: req.OfferIssuerID, - InvreqChain: req.InvreqChain, - InvreqAmount: req.InvreqAmount, - InvreqFeatures: req.InvreqFeatures, - InvreqQuantity: req.InvreqQuantity, - InvreqPayerID: req.InvreqPayerID, - InvreqPayerNote: req.InvreqPayerNote, - InvreqPaths: req.InvreqPaths, - InvreqBip353Name: req.InvreqBip353Name, - - // Carry the request's unknown signed-range TLVs. Known invreq - // types appear in the map with nil values and are skipped when - // the sidecar is merged, so this re-emits only the unknowns and - // never duplicates the typed fields copied above. Any - // signature-range entries (240-1000) cloned here are inert: - // allRecordsFromTypeMap drops them via bolt12InUnsignedRange, - // so the request's signature never leaks into the invoice. - decodedTLVs: maps.Clone(req.decodedTLVs), - } - - // Writer rule: if invreq_amount is present, invoice_amount MUST be set - // to it. When absent, the caller sets the expected amount. - req.InvreqAmount.WhenSome( - func(r tlv.RecordT[tlv.TlvType82, TUint64]) { - inv.InvoiceAmount = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType170, TUint64](r.Val), - ) - }, - ) - - return inv -} diff --git a/bolt12/invoice_error.go b/bolt12/invoice_error.go deleted file mode 100644 index 051b7d85e..000000000 --- a/bolt12/invoice_error.go +++ /dev/null @@ -1,113 +0,0 @@ -package bolt12 - -import ( - "fmt" - - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/tlv" -) - -// InvoiceError represents a BOLT 12 invoice_error message, the negative reply a -// node sends when it rejects an invoice_request or a returned invoice. -type InvoiceError struct { - // ErroneousField names the TLV type in the rejected message that caused - // the failure, letting the recipient pinpoint what to change. - ErroneousField tlv.OptionalRecordT[tlv.TlvType1, TUint64] - - // SuggestedValue provides a valid replacement for the erroneous field. - // MUST NOT be set if ErroneousField is absent. - SuggestedValue tlv.OptionalRecordT[tlv.TlvType3, tlv.Blob] - - // Error is a UTF-8 string explaining the rejection. Required by the - // spec. - Error tlv.OptionalRecordT[tlv.TlvType5, tlv.Blob] - - // decodedTLVs holds every wire TLV type, including unknown ones, so - // ValidateInvoiceErrorRead can apply the must-understand rule. - decodedTLVs tlv.TypeMap -} - -// allRecordProducers returns record producers for every set optional field, in -// declaration order. -func (ie *InvoiceError) allRecordProducers() []tlv.RecordProducer { - var p []tlv.RecordProducer - - lnwire.AddOpt(&p, ie.ErroneousField) - lnwire.AddOpt(&p, ie.SuggestedValue) - lnwire.AddOpt(&p, ie.Error) - - return p -} - -// Encode validates the invoice error per writer requirements and serialises it -// into a TLV byte stream suitable for embedding in an onion message payload at -// type 68. Note that Encode intentionally drops any unknown TLVs. Since -// invoice_error does not carry a cryptographic signature, there is no -// signature to invalidate by dropping unrecognized TLVs (unlike signed -// messages such as invoices, where unknown TLVs must be preserved to keep -// signatures valid). -func (ie *InvoiceError) Encode() ([]byte, error) { - if err := ValidateInvoiceErrorWrite(ie); err != nil { - return nil, fmt.Errorf("validate invoice error: %w", err) - } - - records := lnwire.ProduceRecordsSorted(ie.allRecordProducers()...) - - return lnwire.EncodeRecords(records) -} - -// DecodeInvoiceError deserializes an invoice error from a TLV byte stream (the -// raw value of onion message payload type 68). Decoding is permissive. Run -// ValidateInvoiceErrorRead for the BOLT 1 must-understand check. -func DecodeInvoiceError(data []byte) (*InvoiceError, error) { - var ie InvoiceError - - errField := tlv.ZeroRecordT[tlv.TlvType1, TUint64]() - sugVal := tlv.ZeroRecordT[tlv.TlvType3, tlv.Blob]() - errMsg := tlv.ZeroRecordT[tlv.TlvType5, tlv.Blob]() - - tm, err := decodeStream( - data, - errField.Record(), - sugVal.Record(), - errMsg.Record(), - ) - if err != nil { - return nil, fmt.Errorf("decode invoice error: %w", err) - } - - lnwire.SetOptFromMap(tm, &ie.ErroneousField, errField) - lnwire.SetOptFromMap(tm, &ie.SuggestedValue, sugVal) - lnwire.SetOptFromMap(tm, &ie.Error, errMsg) - ie.decodedTLVs = tm - - return &ie, nil -} - -// ErrorMessage returns the decoded error string, or empty if not set. The bytes -// originate from a remote peer over an onion message and are not sanitised -// here, so callers must scrub them before logging or display. -func (ie *InvoiceError) ErrorMessage() string { - var msg []byte - ie.Error.WhenSome(func(r tlv.RecordT[tlv.TlvType5, tlv.Blob]) { - msg = r.Val - }) - - return string(msg) -} - -// FieldNumber returns the erroneous field number, if set. -func (ie *InvoiceError) FieldNumber() (uint64, bool) { - var ( - val uint64 - ok bool - ) - ie.ErroneousField.WhenSome( - func(r tlv.RecordT[tlv.TlvType1, TUint64]) { - val = uint64(r.Val) - ok = true - }, - ) - - return val, ok -} diff --git a/bolt12/invoice_error_test.go b/bolt12/invoice_error_test.go deleted file mode 100644 index e05012690..000000000 --- a/bolt12/invoice_error_test.go +++ /dev/null @@ -1,287 +0,0 @@ -package bolt12 - -import ( - "testing" - - "github.com/lightningnetwork/lnd/tlv" - "github.com/stretchr/testify/require" -) - -// someErrField builds a set erroneous_field record for the given field -// number, keeping the test tables compact. -func someErrField(n uint64) tlv.OptionalRecordT[tlv.TlvType1, TUint64] { - return tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType1](TUint64(n)), - ) -} - -// someSuggested builds a set suggested_value record from raw bytes. -func someSuggested(b tlv.Blob) tlv.OptionalRecordT[tlv.TlvType3, tlv.Blob] { - return tlv.SomeRecordT(tlv.NewPrimitiveRecord[tlv.TlvType3](b)) -} - -// someError builds a set error record from a string. -func someError(s string) tlv.OptionalRecordT[tlv.TlvType5, tlv.Blob] { - return tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType5](tlv.Blob(s)), - ) -} - -// TestInvoiceErrorRoundTrip verifies that encoding an invoice_error and -// decoding the result recovers every field, and that re-encoding the decoded -// message reproduces the original bytes, both for a fully-populated message -// and for the minimal error-only case. Note that this round-trip property -// only guarantees exact byte reproducibility for messages containing only -// known/declared fields; any unknown fields present in decoded messages are -// intentionally dropped when re-encoded. -func TestInvoiceErrorRoundTrip(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - ie *InvoiceError - wantMsg string - wantHasField bool - wantFieldNum uint64 - wantSuggest []byte - }{ - { - name: "all fields", - ie: &InvoiceError{ - ErroneousField: someErrField(82), - SuggestedValue: someSuggested( - []byte{0x00, 0x01, 0x86, 0xa0}, - ), - Error: someError("amount too low"), - }, - wantMsg: "amount too low", - wantHasField: true, - wantFieldNum: 82, - wantSuggest: []byte{0x00, 0x01, 0x86, 0xa0}, - }, - { - name: "minimal error only", - ie: &InvoiceError{ - Error: someError("rejected"), - }, - wantMsg: "rejected", - wantHasField: false, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - encoded, err := tc.ie.Encode() - require.NoError(t, err) - require.NotEmpty(t, encoded) - - decoded, err := DecodeInvoiceError(encoded) - require.NoError(t, err) - - require.Equal(t, tc.wantMsg, decoded.ErrorMessage()) - - fieldNum, ok := decoded.FieldNumber() - require.Equal(t, tc.wantHasField, ok) - if tc.wantHasField { - require.Equal(t, tc.wantFieldNum, fieldNum) - } - - var sugVal []byte - decoded.SuggestedValue.WhenSome( - func(r tlv.RecordT[tlv.TlvType3, tlv.Blob]) { - sugVal = r.Val - }, - ) - require.Equal(t, tc.wantSuggest, sugVal) - - // Re-encoding the decoded message must reproduce the - // original bytes, pinning canonical record ordering. - reencoded, err := decoded.Encode() - require.NoError(t, err) - require.Equal(t, encoded, reencoded) - }) - } -} - -// TestInvoiceErrorRoundTripWithUnknown verifies that decoding an invoice_error -// containing unknown odd fields works, but re-encoding the decoded structure -// drops those unknown fields, yielding only the known fields in the encoded -// byte stream. -func TestInvoiceErrorRoundTripWithUnknown(t *testing.T) { - t.Parallel() - - // Create a valid invoice_error with only known fields and encode it. - ie := &InvoiceError{ - Error: someError("rejected with unknown field present"), - } - valid, err := ie.Encode() - require.NoError(t, err) - - // Append an unknown odd TLV (type 7) to the valid TLV stream. - // 0x07 (type), 0x02 (length), 0xaa, 0xbb (value). - streamWithUnknown := append( - append([]byte{}, valid...), 0x07, 0x02, 0xaa, 0xbb, - ) - - // Decode the stream. It should succeed because unknown odd fields are - // ignored/tolerated. - decoded, err := DecodeInvoiceError(streamWithUnknown) - require.NoError(t, err) - require.Equal( - t, "rejected with unknown field present", - decoded.ErrorMessage(), - ) - - // Re-encode the decoded message. - reencoded, err := decoded.Encode() - require.NoError(t, err) - - // The re-encoded stream must drop the unknown type 7 field, recovering - // exactly the 'valid' bytes, rather than 'streamWithUnknown'. - require.Equal(t, valid, reencoded) -} - -// TestInvoiceErrorEncodeValidates verifies that Encode runs the writer -// validation before serialising, so an invalid invoice_error never reaches the -// wire. -func TestInvoiceErrorEncodeValidates(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - ie *InvoiceError - wantErr error - }{ - { - name: "missing error", - ie: &InvoiceError{}, - wantErr: ErrMissingError, - }, - { - name: "empty error", - ie: &InvoiceError{Error: someError("")}, - wantErr: ErrEmptyError, - }, - { - name: "non-utf8 error", - ie: &InvoiceError{ - Error: someError(string([]byte{0xff, 0xfe})), - }, - wantErr: ErrInvalidUTF8, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - _, err := tc.ie.Encode() - require.ErrorIs(t, err, tc.wantErr) - }) - } -} - -// TestDecodeInvoiceError verifies decode-level behavior: a truncated stream -// errors, and an unknown odd TLV trailing a valid message is tolerated per the -// BOLT rule that unknown odd types may be ignored. -func TestDecodeInvoiceError(t *testing.T) { - t.Parallel() - - valid, err := (&InvoiceError{Error: someError("rejected")}).Encode() - require.NoError(t, err) - - // A valid message with an unknown odd TLV (type 7) appended after error - // (type 5), kept in ascending type order. - withOdd := append(append([]byte{}, valid...), 0x07, 0x02, 0xaa, 0xbb) - - tests := []struct { - name string - data []byte - wantErr bool - wantMsg string - }{ - { - // Type 5 (error) claims length 16 but supplies one - // byte. - name: "truncated", - data: []byte{0x05, 0x10, 0x01}, - wantErr: true, - }, - { - name: "unknown odd tolerated", - data: withOdd, - wantMsg: "rejected", - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - decoded, err := DecodeInvoiceError(tc.data) - if tc.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) - require.Equal(t, tc.wantMsg, decoded.ErrorMessage()) - }) - } -} - -// TestValidateInvoiceErrorRead verifies the BOLT 1 must-understand rule: an -// unknown even TLV is rejected (including a zero-length one, which still -// occupies a type slot), while an unknown odd TLV is tolerated. -func TestValidateInvoiceErrorRead(t *testing.T) { - t.Parallel() - - // A valid encoded invoice_error (error = "rejected", type 5). Trailers - // use types > 5 to keep the stream strictly increasing. - base, err := (&InvoiceError{Error: someError("rejected")}).Encode() - require.NoError(t, err) - - tests := []struct { - name string - trailer []byte - wantErr error - }{ - { - name: "known only", - }, - { - name: "unknown odd tolerated", - trailer: []byte{0x07, 0x02, 0xaa, 0xbb}, - }, - { - name: "unknown even rejected", - trailer: []byte{0x06, 0x02, 0xaa, 0xbb}, - wantErr: ErrUnknownEvenType, - }, - { - name: "unknown even zero-length rejected", - trailer: []byte{0x06, 0x00}, - wantErr: ErrUnknownEvenType, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - stream := append( - append([]byte{}, base...), tc.trailer..., - ) - decoded, err := DecodeInvoiceError(stream) - require.NoError(t, err) - - err = ValidateInvoiceErrorRead(decoded) - if tc.wantErr != nil { - require.ErrorIs(t, err, tc.wantErr) - } else { - require.NoError(t, err) - } - }) - } -} diff --git a/bolt12/invoice_request.go b/bolt12/invoice_request.go deleted file mode 100644 index 0093e4a67..000000000 --- a/bolt12/invoice_request.go +++ /dev/null @@ -1,302 +0,0 @@ -package bolt12 - -import ( - "bytes" - "errors" - "fmt" - "maps" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/tlv" -) - -var ( - // ErrMissingPayerID is returned when invreq_payer_id is absent. - ErrMissingPayerID = errors.New("missing invreq_payer_id") - - // ErrMissingMetadata is returned when invreq_metadata is absent. - ErrMissingMetadata = errors.New("missing invreq_metadata") -) - -// InvoiceRequest represents a BOLT 12 invoice_request message. It mirrors offer -// fields from the original offer. It also adds payer-specific fields and a -// Schnorr signature. -// -// An invoice request should be constructed from an offer (e.g., using -// NewInvoiceRequestFromOffer) unless it is a spontaneous invoice request. -type InvoiceRequest struct { - // OfferChains are the chains that the mirrored offer is valid for. - OfferChains tlv.OptionalRecordT[tlv.TlvType2, ChainsRecord] - - // OfferMetadata is the metadata from the mirrored offer. - OfferMetadata tlv.OptionalRecordT[tlv.TlvType4, tlv.Blob] - - // OfferCurrency is the currency from the mirrored offer. - OfferCurrency tlv.OptionalRecordT[tlv.TlvType6, tlv.Blob] - - // OfferAmount is the amount from the mirrored offer. - OfferAmount tlv.OptionalRecordT[tlv.TlvType8, TUint64] - - // OfferDescription is the description from the mirrored offer. - OfferDescription tlv.OptionalRecordT[tlv.TlvType10, tlv.Blob] - - // OfferFeatures are the features required by the mirrored offer. - OfferFeatures tlv.OptionalRecordT[ - tlv.TlvType12, lnwire.RawFeatureVector, - ] - - // OfferAbsoluteExpiry is the absolute expiry from the mirrored offer. - OfferAbsoluteExpiry tlv.OptionalRecordT[tlv.TlvType14, TUint64] - - // OfferPaths are the blinded paths from the mirrored offer. - OfferPaths tlv.OptionalRecordT[tlv.TlvType16, lnwire.BlindedPaths] - - // OfferIssuer is the issuer name from the mirrored offer. - OfferIssuer tlv.OptionalRecordT[tlv.TlvType18, tlv.Blob] - - // OfferQuantityMax is the maximum quantity allowed by the mirrored - // offer. - OfferQuantityMax tlv.OptionalRecordT[tlv.TlvType20, TUint64] - - // OfferIssuerID is the public key of the offer issuer. - OfferIssuerID tlv.OptionalRecordT[tlv.TlvType22, *btcec.PublicKey] - - // InvreqMetadata is a blob of unpredictable bytes provided by the - // payer. It serves multiple roles under the BOLT 12 specification: - // 1. Nonce: Ensures cryptographic signature entropy. - // 2. Idempotency Key: If the metadata is identical to a previous - // request, the receiver may reply with the previously generated - // invoice instead of minting a new one. - // 3. Message Marker: As Type 0, it canonically sits at the start of - // the invoice_request TLV stream. - InvreqMetadata tlv.OptionalRecordT[tlv.TlvType0, tlv.Blob] - - // InvreqChain is the chain that the payer is using for this request. - InvreqChain tlv.OptionalRecordT[tlv.TlvType80, [32]byte] - - // InvreqAmount is the amount the payer is offering to pay. - InvreqAmount tlv.OptionalRecordT[tlv.TlvType82, TUint64] - - // InvreqFeatures are the features provided by the payer. - InvreqFeatures tlv.OptionalRecordT[ - tlv.TlvType84, lnwire.RawFeatureVector, - ] - - // InvreqQuantity is the quantity of the offer item being requested. - InvreqQuantity tlv.OptionalRecordT[tlv.TlvType86, TUint64] - - // InvreqPayerID is the public key the payer uses to sign the request. - InvreqPayerID tlv.OptionalRecordT[tlv.TlvType88, *btcec.PublicKey] - - // InvreqPayerNote is an optional note from the payer. - InvreqPayerNote tlv.OptionalRecordT[tlv.TlvType89, tlv.Blob] - - // InvreqPaths are the blinded paths the payer wants the invoice to be - // sent to. - InvreqPaths tlv.OptionalRecordT[tlv.TlvType90, lnwire.BlindedPaths] - - // InvreqBip353Name is the BIP 353 name of the payer. - InvreqBip353Name tlv.OptionalRecordT[tlv.TlvType91, tlv.Blob] - - // Signature is a BIP-340 Schnorr signature covering all fields. - Signature tlv.OptionalRecordT[tlv.TlvType240, [64]byte] - - // decodedTLVs is the canonical TypeMap produced by the typed- stream - // pass that decoded this request. See Offer.decodedTLVs for the design - // rationale. - decodedTLVs tlv.TypeMap -} - -// AllRecords returns the canonical sorted record list for this invoice request, -// merging the typed records with any extra signed-range fields that the decoder -// preserved. -// -// NOTE: this is part of the tlv.PureTLVMessage interface. -func (ir *InvoiceRequest) AllRecords() []tlv.Record { - return allRecordsFromTypeMap( - ir.allRecordProducers(), ir.decodedTLVs, - ) -} - -var _ lnwire.PureTLVMessage = (*InvoiceRequest)(nil) - -// allRecordProducers returns the set of records that are present. -func (ir *InvoiceRequest) allRecordProducers() []tlv.RecordProducer { - var p []tlv.RecordProducer - - lnwire.AddOpt(&p, ir.InvreqMetadata) - lnwire.AddOpt(&p, ir.OfferChains) - lnwire.AddOpt(&p, ir.OfferMetadata) - lnwire.AddOpt(&p, ir.OfferCurrency) - lnwire.AddOpt(&p, ir.OfferAmount) - lnwire.AddOpt(&p, ir.OfferDescription) - lnwire.AddOpt(&p, ir.OfferFeatures) - lnwire.AddOpt(&p, ir.OfferAbsoluteExpiry) - lnwire.AddOpt(&p, ir.OfferPaths) - lnwire.AddOpt(&p, ir.OfferIssuer) - lnwire.AddOpt(&p, ir.OfferQuantityMax) - lnwire.AddOpt(&p, ir.OfferIssuerID) - lnwire.AddOpt(&p, ir.InvreqChain) - lnwire.AddOpt(&p, ir.InvreqAmount) - lnwire.AddOpt(&p, ir.InvreqFeatures) - lnwire.AddOpt(&p, ir.InvreqQuantity) - lnwire.AddOpt(&p, ir.InvreqPayerID) - lnwire.AddOpt(&p, ir.InvreqPayerNote) - lnwire.AddOpt(&p, ir.InvreqPaths) - lnwire.AddOpt(&p, ir.InvreqBip353Name) - lnwire.AddOpt(&p, ir.Signature) - - return p -} - -// Encode validates the invoice request per writer requirements and serialises -// it via the PureTLVMessage shape. -func (ir *InvoiceRequest) Encode() ([]byte, error) { - if err := ValidateInvoiceRequestWrite(ir); err != nil { - return nil, fmt.Errorf("validate invoice request: %w", err) - } - - var buf bytes.Buffer - if err := lnwire.EncodePureTLVMessage(ir, &buf); err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -// DecodeInvoiceRequest deserializes an invoice request from a TLV byte stream. -// Decoding is permissive: callers that need spec compliance must run -// ValidateInvoiceRequestRead. -func DecodeInvoiceRequest(data []byte) (*InvoiceRequest, error) { - var ir InvoiceRequest - - invreqMetadata := tlv.ZeroRecordT[tlv.TlvType0, tlv.Blob]() - chains := tlv.ZeroRecordT[tlv.TlvType2, ChainsRecord]() - metadata := tlv.ZeroRecordT[tlv.TlvType4, tlv.Blob]() - currency := tlv.ZeroRecordT[tlv.TlvType6, tlv.Blob]() - amount := tlv.ZeroRecordT[tlv.TlvType8, TUint64]() - desc := tlv.ZeroRecordT[tlv.TlvType10, tlv.Blob]() - features := tlv.ZeroRecordT[tlv.TlvType12, lnwire.RawFeatureVector]() - expiry := tlv.ZeroRecordT[tlv.TlvType14, TUint64]() - paths := tlv.ZeroRecordT[tlv.TlvType16, lnwire.BlindedPaths]() - issuer := tlv.ZeroRecordT[tlv.TlvType18, tlv.Blob]() - qtyMax := tlv.ZeroRecordT[tlv.TlvType20, TUint64]() - issuerID := tlv.ZeroRecordT[tlv.TlvType22, *btcec.PublicKey]() - invreqChain := tlv.ZeroRecordT[tlv.TlvType80, [32]byte]() - invreqAmount := tlv.ZeroRecordT[tlv.TlvType82, TUint64]() - invreqFeatures := tlv.ZeroRecordT[ - tlv.TlvType84, lnwire.RawFeatureVector, - ]() - invreqQty := tlv.ZeroRecordT[tlv.TlvType86, TUint64]() - payerID := tlv.ZeroRecordT[tlv.TlvType88, *btcec.PublicKey]() - payerNote := tlv.ZeroRecordT[tlv.TlvType89, tlv.Blob]() - invreqPaths := tlv.ZeroRecordT[tlv.TlvType90, lnwire.BlindedPaths]() - bip353 := tlv.ZeroRecordT[tlv.TlvType91, tlv.Blob]() - sig := tlv.ZeroRecordT[tlv.TlvType240, [64]byte]() - - tm, err := decodeStream( - data, invreqMetadata.Record(), chains.Record(), - metadata.Record(), currency.Record(), amount.Record(), - desc.Record(), features.Record(), expiry.Record(), - paths.Record(), issuer.Record(), qtyMax.Record(), - issuerID.Record(), invreqChain.Record(), invreqAmount.Record(), - invreqFeatures.Record(), invreqQty.Record(), payerID.Record(), - payerNote.Record(), invreqPaths.Record(), bip353.Record(), - sig.Record(), - ) - if err != nil { - return nil, fmt.Errorf("decode invoice request: %w", err) - } - - lnwire.SetOptFromMap(tm, &ir.InvreqMetadata, invreqMetadata) - lnwire.SetOptFromMap(tm, &ir.OfferChains, chains) - lnwire.SetOptFromMap(tm, &ir.OfferMetadata, metadata) - lnwire.SetOptFromMap(tm, &ir.OfferCurrency, currency) - lnwire.SetOptFromMap(tm, &ir.OfferAmount, amount) - lnwire.SetOptFromMap(tm, &ir.OfferDescription, desc) - lnwire.SetOptFromMap(tm, &ir.OfferFeatures, features) - lnwire.SetOptFromMap(tm, &ir.OfferAbsoluteExpiry, expiry) - lnwire.SetOptFromMap(tm, &ir.OfferPaths, paths) - lnwire.SetOptFromMap(tm, &ir.OfferIssuer, issuer) - lnwire.SetOptFromMap(tm, &ir.OfferQuantityMax, qtyMax) - lnwire.SetOptFromMap(tm, &ir.OfferIssuerID, issuerID) - lnwire.SetOptFromMap(tm, &ir.InvreqChain, invreqChain) - lnwire.SetOptFromMap(tm, &ir.InvreqAmount, invreqAmount) - lnwire.SetOptFromMap(tm, &ir.InvreqFeatures, invreqFeatures) - lnwire.SetOptFromMap(tm, &ir.InvreqQuantity, invreqQty) - lnwire.SetOptFromMap(tm, &ir.InvreqPayerID, payerID) - lnwire.SetOptFromMap(tm, &ir.InvreqPayerNote, payerNote) - lnwire.SetOptFromMap(tm, &ir.InvreqPaths, invreqPaths) - lnwire.SetOptFromMap(tm, &ir.InvreqBip353Name, bip353) - lnwire.SetOptFromMap(tm, &ir.Signature, sig) - - ir.decodedTLVs = tm - - return &ir, nil -} - -// NewInvoiceRequestFromOffer constructs a new InvoiceRequest by copying -// (mirroring) all fields from the provided Offer. It assigns the payer ID and -// payer metadata; the caller should subsequently sign the request. -// -// Per "MUST copy all fields from the offer (including unknown fields)", the -// offer's unknown TLVs are carried via the decodedTLVs sidecar so they are -// signed and mirrored into the invoice. Note that because unknown even TLV -// types in the offer would have already been rejected by ValidateOfferRead, any -// unknown TLVs mirrored here are guaranteed to be unknown odd TLVs ("it's ok to -// be odd") which are safe to ignore and carry forward. -// -// chain is the genesis hash the payer intends to pay on. invreq_chain is set -// only when chain is not Bitcoin mainnet (absent defaults to mainnet); writer -// validation enforces that it is one of the offer's chains. -func NewInvoiceRequestFromOffer(offer *Offer, payerID *btcec.PublicKey, - metadata []byte, chain [32]byte) (*InvoiceRequest, error) { - - if payerID == nil { - return nil, ErrMissingPayerID - } - if len(metadata) == 0 { - return nil, ErrMissingMetadata - } - - ir := &InvoiceRequest{ - OfferChains: offer.OfferChains, - OfferMetadata: offer.OfferMetadata, - OfferCurrency: offer.OfferCurrency, - OfferAmount: offer.OfferAmount, - OfferDescription: offer.OfferDescription, - OfferFeatures: offer.OfferFeatures, - OfferAbsoluteExpiry: offer.OfferAbsoluteExpiry, - OfferPaths: offer.OfferPaths, - OfferIssuer: offer.OfferIssuer, - OfferQuantityMax: offer.OfferQuantityMax, - OfferIssuerID: offer.OfferIssuerID, - - InvreqPayerID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType88](payerID), - ), - InvreqMetadata: tlv.SomeRecordT( - tlv.RecordT[tlv.TlvType0, tlv.Blob]{ - Val: metadata, - }, - ), - - // Carry the offer's unknown signed-range TLVs. Known offer - // types appear in the map with nil values and are skipped when - // the sidecar is merged, so this re-emits only the unknowns and - // never duplicates the typed fields copied above. - decodedTLVs: maps.Clone(offer.decodedTLVs), - } - - // Set invreq_chain only for non-bitcoin chains; for bitcoin mainnet the - // spec says SHOULD omit, and an absent invreq_chain defaults back to - // mainnet on the read side. - if chain != bitcoinMainnetGenesisHash { - ir.InvreqChain = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType80, [32]byte](chain), - ) - } - - return ir, nil -} diff --git a/bolt12/invoice_request_test.go b/bolt12/invoice_request_test.go deleted file mode 100644 index 71eac7146..000000000 --- a/bolt12/invoice_request_test.go +++ /dev/null @@ -1,171 +0,0 @@ -package bolt12 - -import ( - "bytes" - "testing" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/tlv" - "github.com/stretchr/testify/require" -) - -// TestInvoiceRequestRoundTrip pins encode→decode→re-encode for an -// InvoiceRequest with a representative subset of optional fields. -func TestInvoiceRequestRoundTrip(t *testing.T) { - t.Parallel() - - _, bobPub := bobKey() - - metadata := tlv.Blob("payer-metadata") - - ir := &InvoiceRequest{ - OfferDescription: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType10]( - tlv.Blob("description"), - ), - ), - InvreqPayerID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType88](bobPub), - ), - InvreqMetadata: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType0](metadata), - ), - InvreqAmount: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType82, TUint64](1000), - ), - Signature: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType240]( - [64]byte{0x01}, - ), - ), - } - - encoded, err := ir.Encode() - require.NoError(t, err) - require.NotEmpty(t, encoded) - - decoded, err := DecodeInvoiceRequest(encoded) - require.NoError(t, err) - - require.Equal( - t, bobPub.SerializeCompressed(), - decoded.InvreqPayerID.UnwrapOrFailV(t).SerializeCompressed(), - ) - require.Equal(t, metadata, decoded.InvreqMetadata.UnwrapOrFailV(t)) - require.Equal( - t, TUint64(1000), decoded.InvreqAmount.UnwrapOrFailV(t), - ) - - reencoded, err := decoded.Encode() - require.NoError(t, err) - require.Equal(t, encoded, reencoded) -} - -// TestNewInvoiceRequestFromOffer tests the constructor for mirroring all offer -// fields and properly assigning the payer ID and metadata. -func TestNewInvoiceRequestFromOffer(t *testing.T) { - t.Parallel() - - offer := validBobOffer(t) - - // Add some optional offer fields for mirroring verification. - offer.OfferDescription = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType10](tlv.Blob("description")), - ) - offer.OfferAmount = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType8, TUint64](5000), - ) - - priv, err := btcec.NewPrivateKey() - require.NoError(t, err) - - payerID := priv.PubKey() - metadata := []byte("payer-metadata") - - ir, err := NewInvoiceRequestFromOffer( - offer, payerID, metadata, bitcoinMainnetGenesisHash, - ) - require.NoError(t, err) - require.NotNil(t, ir) - - // Verify offer fields are copied exactly - require.Equal(t, offer.OfferIssuerID, ir.OfferIssuerID) - require.Equal(t, offer.OfferDescription, ir.OfferDescription) - require.Equal(t, offer.OfferAmount, ir.OfferAmount) - - // Verify payer ID and metadata are set correctly - require.Equal(t, payerID, ir.InvreqPayerID.UnwrapOrFailV(t)) - require.Equal(t, metadata, ir.InvreqMetadata.UnwrapOrFailV(t)) - - // For Bitcoin mainnet the spec says SHOULD omit invreq_chain. - require.False(t, ir.InvreqChain.IsSome()) - - // A non-bitcoin chain must be set explicitly so it does not default - // back to mainnet on the read side. - var altChain [32]byte - for i := range altChain { - altChain[i] = 0xab - } - irAlt, err := NewInvoiceRequestFromOffer( - offer, payerID, metadata, altChain, - ) - require.NoError(t, err) - require.Equal(t, altChain, irAlt.InvreqChain.UnwrapOrFailV(t)) -} - -// TestNewInvoiceRequestFromOfferMirrorsUnknownFields verifies the writer -// requirement "MUST copy all fields from the offer (including unknown fields)": -// an unknown odd TLV in the offer's signed range must survive into the -// constructed request's record set so it is signed and later mirrored into the -// invoice. -func TestNewInvoiceRequestFromOfferMirrorsUnknownFields(t *testing.T) { - t.Parallel() - - _, pub := bobKey() - - // Build a minimal valid offer, encode it, then splice in an unknown odd - // TLV (type 33, within the offer signed range) and decode it - // back so the unknown lands in the offer's decodedTLVs sidecar. - offer := &Offer{ - OfferDescription: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType10](tlv.Blob("desc")), - ), - OfferIssuerID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType22](pub), - ), - } - encoded, err := offer.Encode() - require.NoError(t, err) - - const unknownType = 33 - unknownVal := []byte("xyz") - var extra bytes.Buffer - require.NoError(t, tlv.WriteVarInt(&extra, unknownType, &[8]byte{})) - require.NoError(t, tlv.WriteVarInt( - &extra, uint64(len(unknownVal)), &[8]byte{}, - )) - extra.Write(unknownVal) - - // TLV records are canonically ordered by type; type 33 sorts after the - // offer's existing types (10, 22), so appending keeps the stream - // sorted. - spliced := append(append([]byte{}, encoded...), extra.Bytes()...) - - decodedOffer, err := decodeOffer(spliced) - require.NoError(t, err) - - ir, err := NewInvoiceRequestFromOffer( - decodedOffer, pub, []byte("metadata"), - bitcoinMainnetGenesisHash, - ) - require.NoError(t, err) - - // The unknown field must appear in the request's canonical record set. - var found bool - for _, r := range ir.AllRecords() { - if r.Type() == unknownType { - found = true - } - } - require.True(t, found, "unknown offer TLV not mirrored into request") -} diff --git a/bolt12/invoice_test.go b/bolt12/invoice_test.go deleted file mode 100644 index f15e1a753..000000000 --- a/bolt12/invoice_test.go +++ /dev/null @@ -1,359 +0,0 @@ -package bolt12 - -import ( - "bytes" - "testing" - - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/tlv" - "github.com/stretchr/testify/require" -) - -// validInvoice returns an Invoice populated with the minimum set of fields -// required to satisfy ValidateInvoiceWrite. -func validInvoice(t *testing.T) *Invoice { - t.Helper() - - _, pub := bobKey() - - var payHash [32]byte - for i := range payHash { - payHash[i] = byte(i) - } - - _, intro := aliceKey() - _, blinding := bobKey() - _, hopPub := aliceKey() - - introNode, err := lnwire.NewPubkeyIntro(intro) - require.NoError(t, err) - - return &Invoice{ - InvoiceCreatedAt: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType164, TUint64]( - TUint64(1234567890), - ), - ), - InvoiceAmount: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType170, TUint64]( - TUint64(100_000), - ), - ), - InvoicePaymentHash: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType168, [32]byte]( - payHash, - ), - ), - InvoiceNodeID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType176](pub), - ), - InvoicePaths: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType160, lnwire.BlindedPaths]( - lnwire.BlindedPaths{ - Paths: []lnwire.BlindedPath{{ - IntroductionNode: introNode, - BlindingPoint: blinding, - Hops: []lnwire.BlindedHop{{ - BlindedNodeID: hopPub, - }}, - }}, - }, - ), - ), - InvoiceBlindedPay: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType162, BlindedPayInfos]( - BlindedPayInfos{Infos: []BlindedPayInfo{{}}}, - ), - ), - } -} - -// TestUsableFallbackAddresses pins the BOLT 12 ignore semantics for -// invoice_fallbacks. -func TestUsableFallbackAddresses(t *testing.T) { - t.Parallel() - - addrs := []FallbackAddress{ - // Valid: version 0, 2 bytes. - {Version: 0, Address: []byte{0x01, 0x02}}, - // Invalid: version 17, 2 bytes. Version is not supported. - {Version: 17, Address: []byte{0x01, 0x02}}, - // Invalid: version 0, 1 byte. Address is too short. - {Version: 0, Address: []byte{0x01}}, - // Invalid: version 0, 41 bytes. Address is too long. - {Version: 0, Address: make([]byte, 41)}, - // Valid: version 16, 40 bytes. - {Version: 16, Address: make([]byte, 40)}, - } - inv := &Invoice{ - InvoiceFallbacks: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType172, FallbackAddresses]( - FallbackAddresses{Addrs: addrs}, - ), - ), - } - - got := inv.UsableFallbackAddresses() - require.Len(t, got, 2) - require.Equal(t, byte(0), got[0].Version) - require.Equal(t, byte(16), got[1].Version) - require.Len(t, got[1].Address, 40) -} - -// TestUsablePaths pins the BOLT 12 reader filter that excludes any blinded path -// whose payinfo.features carries an unknown required (even) bit, and confirms -// each surviving entry is paired with its own payinfo by index. -func TestUsablePaths(t *testing.T) { - t.Parallel() - - _, blinding := bobKey() - _, hopPub := aliceKey() - _, intro := aliceKey() - introNode, err := lnwire.NewPubkeyIntro(intro) - require.NoError(t, err) - - // hop builds a minimal single-hop blinded path; two of these populate - // invoice_paths so the by-index pairing with payinfos can be observed. - hop := lnwire.BlindedPath{ - IntroductionNode: introNode, - BlindingPoint: blinding, - Hops: []lnwire.BlindedHop{{BlindedNodeID: hopPub}}, - } - pathsRecord := func(n int) tlv.OptionalRecordT[ - tlv.TlvType160, lnwire.BlindedPaths, - ] { - - paths := make([]lnwire.BlindedPath, n) - for i := range paths { - paths[i] = hop - } - - return tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType160, lnwire.BlindedPaths]( - lnwire.BlindedPaths{Paths: paths}, - ), - ) - } - payRecord := func(infos ...BlindedPayInfo) tlv.OptionalRecordT[ - tlv.TlvType162, BlindedPayInfos, - ] { - - return tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType162, BlindedPayInfos]( - BlindedPayInfos{Infos: infos}, - ), - ) - } - - // The first payinfo carries an unknown required feature bit - // (MPPRequired); the second is featureless. - required := *lnwire.NewRawFeatureVector(lnwire.MPPRequired) - inv := &Invoice{ - InvoicePaths: pathsRecord(2), - InvoiceBlindedPay: payRecord( - BlindedPayInfo{FeeBaseMsat: 1, Features: required}, - BlindedPayInfo{FeeBaseMsat: 2}, - ), - } - - // Empty catalogue: the MPPRequired bit is unknown, so path 0 is - // filtered out and only path 1 (fee_base 2) survives. - got := inv.UsablePaths(nil) - require.Len(t, got, 1) - require.Equal(t, uint32(2), got[0].PayInfo.FeeBaseMsat) - - // Once the bit is known, both paths become usable and stay paired with - // their own payinfo in order. - known := map[lnwire.FeatureBit]string{lnwire.MPPRequired: "mpp"} - got = inv.UsablePaths(known) - require.Len(t, got, 2) - require.Equal(t, uint32(1), got[0].PayInfo.FeeBaseMsat) - require.Equal(t, uint32(2), got[1].PayInfo.FeeBaseMsat) - - // A length mismatch between paths and payinfos yields no usable paths - // (rejected upstream by ValidateInvoiceRead). - inv.InvoiceBlindedPay = payRecord(BlindedPayInfo{}) - require.Empty(t, inv.UsablePaths(known)) -} - -// TestInvoiceRoundTripPreservesAllTypes encodes a fully populated invoice then -// decodes it back, asserting every field is preserved byte-for-byte. The codec -// promises bijection on the message level, and any drift (dropped record, -// re-ordered output) breaks downstream signature verification because the -// Merkle root depends on the exact raw TLV stream. -func TestInvoiceRoundTripPreservesAllTypes(t *testing.T) { - t.Parallel() - - inv := validInvoice(t) - inv.Signature = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType240, [64]byte]([64]byte{}), - ) - - encoded, err := inv.Encode() - require.NoError(t, err) - require.NotEmpty(t, encoded) - - decoded, err := DecodeInvoice(encoded) - require.NoError(t, err) - - err = ValidateInvoiceRead(decoded, bitcoinMainnetGenesisHash, - InvoiceFeatureCatalogues{}) - require.NoError(t, err) - - // Re-encode the decoded copy and confirm canonicality. - // decode(encode(decode(encode(x)))) must equal decode(encode(x)). - encoded2, err := decoded.Encode() - require.NoError(t, err) - require.Equal(t, encoded, encoded2) -} - -// TestDecodeInvoiceRejectsTruncated locks in that DecodeInvoice surfaces an -// error when fed a truncated TLV stream rather than returning a partial -// Invoice. A silent partial-decode would let validation see fields that weren't -// actually on the wire. -func TestDecodeInvoiceRejectsTruncated(t *testing.T) { - t.Parallel() - - inv := validInvoice(t) - encoded, err := inv.Encode() - require.NoError(t, err) - - // Chop off the last byte. The truncation lands in the middle of the - // final blinded_pay record's variable-length payload. - truncated := encoded[:len(encoded)-1] - - _, err = DecodeInvoice(truncated) - require.Error(t, err) -} - -// TestNewInvoiceFromRequest verifies the constructor mirrors all non-signature -// invoice_request fields into the invoice, applies the invreq_amount -> -// invoice_amount writer rule, and does not copy the request's signature. -func TestNewInvoiceFromRequest(t *testing.T) { - t.Parallel() - - _, bobPub := bobKey() - _, alicePub := aliceKey() - - req := &InvoiceRequest{ - OfferDescription: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType10]( - tlv.Blob("description"), - ), - ), - OfferIssuerID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType22](alicePub), - ), - InvreqMetadata: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType0]( - tlv.Blob("payer-metadata"), - ), - ), - InvreqPayerID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType88](bobPub), - ), - InvreqAmount: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType82, TUint64](2500), - ), - Signature: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType240]([64]byte{0x01}), - ), - } - - inv := NewInvoiceFromRequest(req) - require.NotNil(t, inv) - - // Non-signature request fields are mirrored exactly. - require.Equal(t, req.OfferDescription, inv.OfferDescription) - require.Equal(t, req.OfferIssuerID, inv.OfferIssuerID) - require.Equal(t, req.InvreqMetadata, inv.InvreqMetadata) - require.Equal(t, req.InvreqPayerID, inv.InvreqPayerID) - require.Equal(t, req.InvreqAmount, inv.InvreqAmount) - - // invreq_amount is mirrored into invoice_amount per the writer rule. - require.Equal(t, TUint64(2500), inv.InvoiceAmount.UnwrapOrFailV(t)) - - // The request's signature is not copied. The invoice signs its own. - require.True(t, inv.Signature.IsNone()) -} - -// TestNewInvoiceFromRequestMirrorsUnknownFields verifies the writer requirement -// "MUST copy all non-signature fields from the invoice request (including -// unknown fields)": an unknown odd TLV in the request's signed range must -// survive into the constructed invoice's canonical record set so it is signed. -func TestNewInvoiceFromRequestMirrorsUnknownFields(t *testing.T) { - t.Parallel() - - _, bobPub := bobKey() - - // Build a minimal valid spontaneous request and encode it. - req := &InvoiceRequest{ - OfferDescription: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType10](tlv.Blob("desc")), - ), - InvreqMetadata: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType0](tlv.Blob("meta")), - ), - InvreqPayerID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType88](bobPub), - ), - InvreqAmount: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType82, TUint64](1000), - ), - } - encoded, err := req.Encode() - require.NoError(t, err) - - // Fill in an unknown odd TLV (type 93, within the invreq signed range - // and above the request's existing types) so the spliced stream stays - // canonically sorted and the unknown lands in the decoded request's - // decodedTLVs sidecar. - const unknownType = 93 - unknownVal := []byte("xyz") - var extra bytes.Buffer - require.NoError(t, tlv.WriteVarInt(&extra, unknownType, &[8]byte{})) - require.NoError(t, tlv.WriteVarInt( - &extra, uint64(len(unknownVal)), &[8]byte{}, - )) - extra.Write(unknownVal) - - spliced := append(append([]byte{}, encoded...), extra.Bytes()...) - - decodedReq, err := DecodeInvoiceRequest(spliced) - require.NoError(t, err) - - inv := NewInvoiceFromRequest(decodedReq) - - // The unknown field must appear in the invoice's canonical record set - // with its value preserved, not just its type. - var ( - found bool - gotVal bytes.Buffer - ) - for _, r := range inv.AllRecords() { - if r.Type() != unknownType { - continue - } - found = true - require.NoError(t, r.Encode(&gotVal)) - } - require.True(t, found, "unknown request TLV not mirrored into invoice") - require.Equal( - t, unknownVal, gotVal.Bytes(), - "unknown request TLV value not preserved", - ) -} - -// TestInvoiceEncodeValidationGate verifies that Encode runs -// ValidateInvoiceWrite and rejects invalid invoices. -func TestInvoiceEncodeValidationGate(t *testing.T) { - t.Parallel() - - inv := validInvoice(t) - inv.InvoiceCreatedAt = tlv.OptionalRecordT[ - tlv.TlvType164, TUint64, - ]{} - - _, err := inv.Encode() - require.ErrorIs(t, err, ErrMissingCreatedAt) -} diff --git a/bolt12/offer.go b/bolt12/offer.go deleted file mode 100644 index dfe8cd60f..000000000 --- a/bolt12/offer.go +++ /dev/null @@ -1,167 +0,0 @@ -package bolt12 - -import ( - "bytes" - "fmt" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/tlv" -) - -// Offer represents a BOLT 12 offer message. An offer is a long-lived, reusable -// payment template that can generate multiple invoices. -type Offer struct { - // OfferChains specifies which chains this offer is valid for. If - // absent, bitcoin is implied. - OfferChains tlv.OptionalRecordT[tlv.TlvType2, ChainsRecord] - - // OfferMetadata is opaque data set by the offer creator for its own - // use. - OfferMetadata tlv.OptionalRecordT[tlv.TlvType4, tlv.Blob] - - // OfferCurrency is the ISO 4217 currency code for the offer amount, if - // the amount is not in the chain's native unit. - OfferCurrency tlv.OptionalRecordT[tlv.TlvType6, tlv.Blob] - - // OfferAmount is the amount expected per item, encoded as a tu64. The - // unit depends on OfferCurrency (msat if absent). - OfferAmount tlv.OptionalRecordT[tlv.TlvType8, TUint64] - - // OfferDescription is a UTF-8 description of the purpose of the - // payment. - OfferDescription tlv.OptionalRecordT[tlv.TlvType10, tlv.Blob] - - // OfferFeatures is the feature bit vector for this offer. - OfferFeatures tlv.OptionalRecordT[tlv.TlvType12, - lnwire.RawFeatureVector] - - // OfferAbsoluteExpiry is the time (seconds since epoch) after which the - // offer should not be used, encoded as a tu64. - OfferAbsoluteExpiry tlv.OptionalRecordT[tlv.TlvType14, TUint64] - - // OfferPaths contains one or more blinded paths to the offer issuer. - OfferPaths tlv.OptionalRecordT[tlv.TlvType16, lnwire.BlindedPaths] - - // OfferIssuer is a UTF-8 string identifying the issuer. - OfferIssuer tlv.OptionalRecordT[tlv.TlvType18, tlv.Blob] - - // OfferQuantityMax is the maximum number of items that can be requested - // in a single invoice, encoded as a tu64. A value of 0 means unlimited. - OfferQuantityMax tlv.OptionalRecordT[tlv.TlvType20, TUint64] - - // OfferIssuerID is the public key of the offer issuer. The codec - // parses the 33-byte SEC1 compressed point on decode, so a struct - // holding a key has already passed both the length and on-curve - // checks. - OfferIssuerID tlv.OptionalRecordT[tlv.TlvType22, *btcec.PublicKey] - - // decodedTLVs is the canonical TypeMap produced by decoding this offer. - // Handled types map to nil; unhandled types map to their value bytes. - // Encoding and validation both derive their view from this single field - // so they cannot drift apart, and so signed-range extras the decoder - // did not understand are re-emitted on encode and preserve offer_id. - decodedTLVs tlv.TypeMap -} - -var _ lnwire.PureTLVMessage = (*Offer)(nil) - -// AllRecords returns the canonical sorted record list for this offer, merging -// the typed records with any extra signed-range fields that the decoder -// preserved. -func (o *Offer) AllRecords() []tlv.Record { - return allRecordsFromTypeMap( - o.allRecordProducers(), o.decodedTLVs, - ) -} - -// allRecordProducers returns record producers for every set optional field, in -// declaration order. -func (o *Offer) allRecordProducers() []tlv.RecordProducer { - var p []tlv.RecordProducer - - lnwire.AddOpt(&p, o.OfferChains) - lnwire.AddOpt(&p, o.OfferMetadata) - lnwire.AddOpt(&p, o.OfferCurrency) - lnwire.AddOpt(&p, o.OfferAmount) - lnwire.AddOpt(&p, o.OfferDescription) - lnwire.AddOpt(&p, o.OfferFeatures) - lnwire.AddOpt(&p, o.OfferAbsoluteExpiry) - lnwire.AddOpt(&p, o.OfferPaths) - lnwire.AddOpt(&p, o.OfferIssuer) - lnwire.AddOpt(&p, o.OfferQuantityMax) - lnwire.AddOpt(&p, o.OfferIssuerID) - - return p -} - -// Encode serialises the offer into a canonical TLV byte stream. -func (o *Offer) Encode() ([]byte, error) { - if err := ValidateOfferWrite(o); err != nil { - return nil, fmt.Errorf("validate offer: %w", err) - } - - var buf bytes.Buffer - if err := lnwire.EncodePureTLVMessage(o, &buf); err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -// decodeOffer parses a TLV byte stream into an Offer. Decoding is permissive — -// the spec writer requirements are not enforced here, so callers that need a -// valid offer must run ValidateOfferRead. Unknown TLVs are preserved on the -// returned offer so a later Encode can re-emit signed-range extras and keep -// offer_id stable. -func decodeOffer(data []byte) (*Offer, error) { - var o Offer - - // Prepare zero-valued records for all optional fields so the TLV - // decoder can populate them. - chains := tlv.ZeroRecordT[tlv.TlvType2, ChainsRecord]() - metadata := tlv.ZeroRecordT[tlv.TlvType4, tlv.Blob]() - currency := tlv.ZeroRecordT[tlv.TlvType6, tlv.Blob]() - amount := tlv.ZeroRecordT[tlv.TlvType8, TUint64]() - desc := tlv.ZeroRecordT[tlv.TlvType10, tlv.Blob]() - features := tlv.ZeroRecordT[tlv.TlvType12, lnwire.RawFeatureVector]() - expiry := tlv.ZeroRecordT[tlv.TlvType14, TUint64]() - paths := tlv.ZeroRecordT[tlv.TlvType16, lnwire.BlindedPaths]() - issuer := tlv.ZeroRecordT[tlv.TlvType18, tlv.Blob]() - qtyMax := tlv.ZeroRecordT[tlv.TlvType20, TUint64]() - issuerID := tlv.ZeroRecordT[tlv.TlvType22, *btcec.PublicKey]() - - tm, err := decodeStream( - data, - chains.Record(), - metadata.Record(), - currency.Record(), - amount.Record(), - desc.Record(), - features.Record(), - expiry.Record(), - paths.Record(), - issuer.Record(), - qtyMax.Record(), - issuerID.Record(), - ) - if err != nil { - return nil, fmt.Errorf("decode offer: %w", err) - } - - lnwire.SetOptFromMap(tm, &o.OfferChains, chains) - lnwire.SetOptFromMap(tm, &o.OfferMetadata, metadata) - lnwire.SetOptFromMap(tm, &o.OfferCurrency, currency) - lnwire.SetOptFromMap(tm, &o.OfferAmount, amount) - lnwire.SetOptFromMap(tm, &o.OfferDescription, desc) - lnwire.SetOptFromMap(tm, &o.OfferFeatures, features) - lnwire.SetOptFromMap(tm, &o.OfferAbsoluteExpiry, expiry) - lnwire.SetOptFromMap(tm, &o.OfferPaths, paths) - lnwire.SetOptFromMap(tm, &o.OfferIssuer, issuer) - lnwire.SetOptFromMap(tm, &o.OfferQuantityMax, qtyMax) - lnwire.SetOptFromMap(tm, &o.OfferIssuerID, issuerID) - - o.decodedTLVs = tm - - return &o, nil -} diff --git a/bolt12/offer_test.go b/bolt12/offer_test.go deleted file mode 100644 index 2a9ae325b..000000000 --- a/bolt12/offer_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package bolt12 - -import ( - "testing" - - "github.com/lightningnetwork/lnd/tlv" - "github.com/stretchr/testify/require" -) - -// TestOfferRoundTrip pins encode→decode→re-encode for an Offer with a -// representative subset of optional fields. A byte-identical re-encode is the -// invariant that keeps offer_id stable across the codec boundary. -func TestOfferRoundTrip(t *testing.T) { - t.Parallel() - - desc := tlv.Blob("coffee") - issuer := tlv.Blob("alice") - _, bobPub := bobKey() - - o := &Offer{ - OfferAmount: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType8](TUint64(1500)), - ), - OfferDescription: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType10](desc), - ), - OfferIssuer: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType18](issuer), - ), - OfferIssuerID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType22](bobPub), - ), - } - - encoded, err := o.Encode() - require.NoError(t, err) - require.NotEmpty(t, encoded) - - decoded, err := decodeOffer(encoded) - require.NoError(t, err) - - require.Equal(t, TUint64(1500), decoded.OfferAmount.UnwrapOrFailV(t)) - require.Equal(t, desc, decoded.OfferDescription.UnwrapOrFailV(t)) - require.Equal(t, issuer, decoded.OfferIssuer.UnwrapOrFailV(t)) - - reencoded, err := decoded.Encode() - require.NoError(t, err) - require.Equal(t, encoded, reencoded) -} diff --git a/bolt12/pure_tlv.go b/bolt12/pure_tlv.go deleted file mode 100644 index d20022b92..000000000 --- a/bolt12/pure_tlv.go +++ /dev/null @@ -1,52 +0,0 @@ -package bolt12 - -import ( - "slices" - - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/tlv" -) - -// bolt12InUnsignedRange reports whether a TLV type is excluded from the BOLT 12 -// Merkle tree. The spec reserves types 240-1000 for signature TLVs (the BIP-340 -// Schnorr signatures over the tree itself); every other allowed type sits in -// the signed range. -func bolt12InUnsignedRange(t tlv.Type) bool { - return t >= 240 && t <= 1000 -} - -// allRecordsFromTypeMap merges the typed-record producers with the signed-range -// subset of the supplied TypeMap (preserved unknown TLVs) and returns the -// canonical sorted record list. The signed-range subset is derived on demand -// from the same TypeMap that drives the validators, so the two views cannot -// drift apart. -func allRecordsFromTypeMap(producers []tlv.RecordProducer, - tm tlv.TypeMap) []tlv.Record { - - if len(tm) > 0 { - extra := lnwire.ExtraSignedFieldsFromTypeMapFn( - tm, bolt12InUnsignedRange, - ) - if len(extra) > 0 { - producers = append( - producers, lnwire.RecordsAsProducers( - tlv.MapToRecords(extra), - )..., - ) - } - } - - return lnwire.ProduceRecordsSorted(producers...) -} - -// sortedTypes returns the keys of tm in ascending order. Validators iterate the -// result for deterministic out-of-range and unknown-even error messages. -func sortedTypes(tm tlv.TypeMap) []tlv.Type { - out := make([]tlv.Type, 0, len(tm)) - for t := range tm { - out = append(out, t) - } - slices.Sort(out) - - return out -} diff --git a/bolt12/subtypes.go b/bolt12/subtypes.go deleted file mode 100644 index e3c49ddc0..000000000 --- a/bolt12/subtypes.go +++ /dev/null @@ -1,448 +0,0 @@ -package bolt12 - -import ( - "encoding/binary" - "errors" - "fmt" - "io" - "math" - - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/tlv" -) - -// ErrTooManyChains is returned when offer_chains declares more entries than -// maxOfferChains. -var ErrTooManyChains = errors.New("offer_chains exceeds maxOfferChains") - -// ErrNonMinimalFeatures is returned when a decoded feature vector is not -// canonically (minimally) encoded. -var ErrNonMinimalFeatures = errors.New("non-minimal feature vector encoding") - -// ErrTooManyBlindedPayInfos is returned when decoded blinded_payinfo entries -// exceed maxBlindedPayInfos. -var ErrTooManyBlindedPayInfos = errors.New( - "invoice_blindedpay exceeds maxBlindedPayInfos", -) - -// ErrInvalidHtlcRange is returned when a decoded blinded_payinfo entry carries -// an htlc_minimum_msat greater than its htlc_maximum_msat. -var ErrInvalidHtlcRange = errors.New( - "blinded_payinfo htlc_minimum_msat exceeds htlc_maximum_msat", -) - -// ErrTooManyFallbackAddrs is returned when decoded fallback_address entries -// exceed maxFallbackAddrs. -var ErrTooManyFallbackAddrs = errors.New( - "invoice_fallbacks exceeds maxFallbackAddrs", -) - -const ( - // chainHashLen is the length of a chain hash (32 bytes). - chainHashLen = 32 - - // maxOfferChains caps decoded offer_chains entries. This is a sanity - // check to prevent excessive memory allocation and is not a protocol - // limit but a local implementation choice. - maxOfferChains = 32 - - // maxBlindedPayInfos caps decoded blinded_payinfo entries to prevent - // excessive allocation and validation cost. - maxBlindedPayInfos = 32 - - // maxFallbackAddrs caps decoded fallback_address entries to prevent - // excessive allocation and validation cost. - maxFallbackAddrs = 32 - - // maxFallbackAddrLen bounds the address bytes in a single fallback - // entry. The spec encodes the length as a uint16, so 65535 is the - // format's ceiling. - maxFallbackAddrLen = math.MaxUint16 -) - -// ChainsRecord holds one or more chain hashes for the offer_chains field. -type ChainsRecord struct { - Chains [][chainHashLen]byte -} - -var _ tlv.RecordProducer = (*ChainsRecord)(nil) - -// Record returns a TLV record for ChainsRecord. -func (c *ChainsRecord) Record() tlv.Record { - return tlv.MakeDynamicRecord( - 0, c, - func() uint64 { - return uint64(len(c.Chains)) * chainHashLen - }, - encodeChainsRecord, - decodeChainsRecord, - ) -} - -// encodeChainsRecord writes the chain hashes in sequence, without a count -// prefix. -func encodeChainsRecord(w io.Writer, val any, _ *[8]byte) error { - c, ok := val.(*ChainsRecord) - if !ok { - return fmt.Errorf("expected *ChainsRecord, got %T", val) - } - - for _, chain := range c.Chains { - if _, err := w.Write(chain[:]); err != nil { - return err - } - } - - return nil -} - -// decodeChainsRecord caps the count at maxOfferChains to bound allocation. -func decodeChainsRecord(r io.Reader, val any, _ *[8]byte, l uint64) error { - c, ok := val.(*ChainsRecord) - if !ok { - return fmt.Errorf("expected *ChainsRecord, got %T", val) - } - - if l%chainHashLen != 0 { - return fmt.Errorf("chains length %d not a multiple of %d", l, - chainHashLen) - } - - numChains := l / chainHashLen - if numChains > maxOfferChains { - return fmt.Errorf("%w: %d > %d", ErrTooManyChains, numChains, - maxOfferChains) - } - - c.Chains = make([][chainHashLen]byte, numChains) - for i := range c.Chains { - if _, err := io.ReadFull(r, c.Chains[i][:]); err != nil { - return err - } - } - - return nil -} - -// BlindedPayInfo holds the payment parameters for a blinded path, corresponding -// to the blinded_payinfo subtype. -type BlindedPayInfo struct { - // FeeBaseMsat is the base fee, in millisatoshis, charged for relaying a - // payment over this blinded path. - FeeBaseMsat uint32 - - // FeeProportionalMillionths is the proportional fee, in millionths of a - // satoshi per relayed satoshi, charged over this blinded path. - FeeProportionalMillionths uint32 - - // CltvExpiryDelta is the CLTV expiry delta the path requires. - CltvExpiryDelta uint16 - - // HtlcMinimumMsat is the smallest HTLC, in millisatoshis, the path - // accepts. - HtlcMinimumMsat uint64 - - // HtlcMaximumMsat is the largest HTLC, in millisatoshis, the path - // accepts. - HtlcMaximumMsat uint64 - - // Features is the relay feature bitmap for this blinded path, typed for - // consistency with the other BOLT 12 feature fields. - // - // WARNING: RawFeatureVector re-encodes to minimal length, so setting - // non-minimal feature bytes (trailing zeros) yields different wire - // bytes than were read and invalidates the invoice signature. - Features lnwire.RawFeatureVector -} - -// BlindedPayInfos holds a list of BlindedPayInfo entries for the -// invoice_blindedpay field. -type BlindedPayInfos struct { - Infos []BlindedPayInfo -} - -// Record returns a TLV record for BlindedPayInfos. -// -// NOTE: This implements the tlv.RecordProducer interface. -func (bp *BlindedPayInfos) Record() tlv.Record { - return tlv.MakeDynamicRecord( - 0, bp, - func() uint64 { - return blindedPayInfosSize(bp) - }, - encodeBlindedPayInfos, decodeBlindedPayInfos, - ) -} - -// blindedPayInfosSize returns the encoded byte length of all blinded_payinfo -// entries, used to size the dynamic TLV record. -func blindedPayInfosSize(bp *BlindedPayInfos) uint64 { - var size uint64 - for _, info := range bp.Infos { - // fee_base(4) + fee_prop(4) + cltv(2) + htlc_min(8) + - // htlc_max(8) + flen(2) + features. - size += 4 + 4 + 2 + 8 + 8 + 2 + - uint64(info.Features.SerializeSize()) - } - - return size -} - -// encodeBlindedPayInfos writes each blinded_payinfo entry in sequence: the -// fixed fee, cltv and htlc fields followed by a u16-length-prefixed feature -// vector. Entries are concatenated without a count prefix; the count is -// recovered on decode from the surrounding invoice_paths length. -func encodeBlindedPayInfos( - w io.Writer, val interface{}, buf *[8]byte) error { - - bp, ok := val.(*BlindedPayInfos) - if !ok { - return fmt.Errorf("expected *BlindedPayInfos, got %T", val) - } - - for _, info := range bp.Infos { - binary.BigEndian.PutUint32(buf[:4], info.FeeBaseMsat) - if _, err := w.Write(buf[:4]); err != nil { - return err - } - - binary.BigEndian.PutUint32( - buf[:4], info.FeeProportionalMillionths, - ) - if _, err := w.Write(buf[:4]); err != nil { - return err - } - - binary.BigEndian.PutUint16(buf[:2], info.CltvExpiryDelta) - if _, err := w.Write(buf[:2]); err != nil { - return err - } - - binary.BigEndian.PutUint64(buf[:8], info.HtlcMinimumMsat) - if _, err := w.Write(buf[:8]); err != nil { - return err - } - - binary.BigEndian.PutUint64(buf[:8], info.HtlcMaximumMsat) - if _, err := w.Write(buf[:8]); err != nil { - return err - } - - // flen is a u16, so guard the cast before framing the minimal - // feature bytes, mirroring encodeFallbackAddrs. - flen := info.Features.SerializeSize() - if flen > math.MaxUint16 { - return fmt.Errorf("features %d exceed limit %d", - flen, math.MaxUint16) - } - - binary.BigEndian.PutUint16(buf[:2], uint16(flen)) - if _, err := w.Write(buf[:2]); err != nil { - return err - } - if err := info.Features.EncodeBase256(w); err != nil { - return err - } - } - - return nil -} - -// decodeBlindedPayInfos reads blinded_payinfo entries until the record bytes -// are exhausted. The entry count is capped at maxBlindedPayInfos to prevent -// excessive memory allocation and validation cost. -func decodeBlindedPayInfos( - r io.Reader, val interface{}, buf *[8]byte, l uint64) error { - - bp, ok := val.(*BlindedPayInfos) - if !ok { - return fmt.Errorf("expected *BlindedPayInfos, got %T", val) - } - - lr := &io.LimitedReader{R: r, N: int64(l)} - - for lr.N > 0 { - if len(bp.Infos) >= maxBlindedPayInfos { - return ErrTooManyBlindedPayInfos - } - - var info BlindedPayInfo - - if _, err := io.ReadFull(lr, buf[:4]); err != nil { - return fmt.Errorf("read fee_base: %w", err) - } - info.FeeBaseMsat = binary.BigEndian.Uint32(buf[:4]) - - if _, err := io.ReadFull(lr, buf[:4]); err != nil { - return fmt.Errorf("read fee_prop: %w", err) - } - info.FeeProportionalMillionths = binary.BigEndian.Uint32( - buf[:4], - ) - - if _, err := io.ReadFull(lr, buf[:2]); err != nil { - return fmt.Errorf("read cltv_delta: %w", err) - } - info.CltvExpiryDelta = binary.BigEndian.Uint16(buf[:2]) - - if _, err := io.ReadFull(lr, buf[:8]); err != nil { - return fmt.Errorf("read htlc_min: %w", err) - } - info.HtlcMinimumMsat = binary.BigEndian.Uint64(buf[:8]) - - if _, err := io.ReadFull(lr, buf[:8]); err != nil { - return fmt.Errorf("read htlc_max: %w", err) - } - info.HtlcMaximumMsat = binary.BigEndian.Uint64(buf[:8]) - - // Defense-in-depth decode check, mirroring the - // ErrNonMinimalFeatures guard below: reject an inverted HTLC - // range so the htlc_min <= htlc_max invariant holds for every - // downstream consumer instead of being re-derived per caller. - if info.HtlcMinimumMsat > info.HtlcMaximumMsat { - return ErrInvalidHtlcRange - } - - // flen then features, mirroring decodeFallbackAddrs: reject a - // length that overruns the remaining bytes before allocating. - // Decode into a constructed vector so its map is initialised. - if _, err := io.ReadFull(lr, buf[:2]); err != nil { - return fmt.Errorf("read flen: %w", err) - } - flen := binary.BigEndian.Uint16(buf[:2]) - if int64(flen) > lr.N { - return fmt.Errorf("flen %d exceeds remaining %d", - flen, lr.N) - } - - fv := lnwire.NewRawFeatureVector() - if err := fv.DecodeBase256(lr, int(flen)); err != nil { - return fmt.Errorf("read features: %w", err) - } - if fv.SerializeSize() != int(flen) { - return ErrNonMinimalFeatures - } - info.Features = *fv - - bp.Infos = append(bp.Infos, info) - } - - return nil -} - -// FallbackAddress represents an on-chain fallback address. -type FallbackAddress struct { - Version byte - Address []byte -} - -// FallbackAddresses holds a list of fallback addresses for the -// invoice_fallbacks field. -type FallbackAddresses struct { - Addrs []FallbackAddress -} - -// Record returns a TLV record for FallbackAddresses. -// -// NOTE: This implements the tlv.RecordProducer interface. -func (fa *FallbackAddresses) Record() tlv.Record { - return tlv.MakeDynamicRecord( - 0, fa, - func() uint64 { - return fallbackAddrsSize(fa) - }, - encodeFallbackAddrs, decodeFallbackAddrs, - ) -} - -// fallbackAddrsSize returns the encoded byte length of all fallback_address -// entries, used to size the dynamic TLV record. -func fallbackAddrsSize(fa *FallbackAddresses) uint64 { - var size uint64 - for _, a := range fa.Addrs { - // version(1) + len(2) + address - size += 1 + 2 + uint64(len(a.Address)) - } - - return size -} - -// encodeFallbackAddrs writes each fallback_address entry as a version byte, a -// u16 address length and the raw address bytes, concatenated without a count -// prefix. -func encodeFallbackAddrs( - w io.Writer, val interface{}, buf *[8]byte) error { - - fa, ok := val.(*FallbackAddresses) - if !ok { - return fmt.Errorf("expected *FallbackAddresses, got %T", val) - } - - for i, a := range fa.Addrs { - if len(a.Address) > maxFallbackAddrLen { - return fmt.Errorf("fallback %d: address %d exceeds "+ - "limit %d", i, len(a.Address), - maxFallbackAddrLen) - } - - buf[0] = a.Version - if _, err := w.Write(buf[:1]); err != nil { - return err - } - - binary.BigEndian.PutUint16(buf[:2], uint16(len(a.Address))) - if _, err := w.Write(buf[:2]); err != nil { - return err - } - if _, err := w.Write(a.Address); err != nil { - return err - } - } - - return nil -} - -// decodeFallbackAddrs reads fallback_address entries until the record bytes are -// exhausted. The entry count is capped at maxFallbackAddrs to prevent -// excessive memory allocation and validation cost. -func decodeFallbackAddrs( - r io.Reader, val interface{}, buf *[8]byte, l uint64) error { - - fa, ok := val.(*FallbackAddresses) - if !ok { - return fmt.Errorf("expected *FallbackAddresses, got %T", val) - } - - lr := &io.LimitedReader{R: r, N: int64(l)} - - for lr.N > 0 { - if len(fa.Addrs) >= maxFallbackAddrs { - return ErrTooManyFallbackAddrs - } - - var a FallbackAddress - - if _, err := io.ReadFull(lr, buf[:1]); err != nil { - return fmt.Errorf("read version: %w", err) - } - a.Version = buf[0] - - if _, err := io.ReadFull(lr, buf[:2]); err != nil { - return fmt.Errorf("read addrlen: %w", err) - } - addrLen := binary.BigEndian.Uint16(buf[:2]) - if int64(addrLen) > lr.N { - return fmt.Errorf("addrlen %d exceeds remaining %d", - addrLen, lr.N) - } - - a.Address = make([]byte, addrLen) - if _, err := io.ReadFull(lr, a.Address); err != nil { - return fmt.Errorf("read address: %w", err) - } - - fa.Addrs = append(fa.Addrs, a) - } - - return nil -} diff --git a/bolt12/subtypes_test.go b/bolt12/subtypes_test.go deleted file mode 100644 index a7c6287a3..000000000 --- a/bolt12/subtypes_test.go +++ /dev/null @@ -1,415 +0,0 @@ -package bolt12 - -import ( - "bytes" - "encoding/hex" - "math" - "testing" - - "github.com/lightningnetwork/lnd/lnwire" - "github.com/stretchr/testify/require" -) - -// TestDecodeChainsRecord pins the chain-array decoder's structural rejections. -func TestDecodeChainsRecord(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - data []byte - wantErr error - wantMsg string - }{ - { - name: "length not multiple of 32", - data: append( - bytes.Repeat( - []byte{0xaa}, chainHashLen, - ), - 187, - ), - wantMsg: "not a multiple of", - }, - { - name: "exceeds cap", - data: bytes.Repeat( - []byte{0x00}, (maxOfferChains+1)*chainHashLen, - ), - wantErr: ErrTooManyChains, - }, - } - - for _, tc := range tests { - t.Run( - tc.name, - func(t *testing.T) { - t.Parallel() - - var c ChainsRecord - err := decodeChainsRecord( - bytes.NewReader(tc.data), &c, - new([8]byte), - uint64( - len(tc.data), - ), - ) - require.Error(t, err) - - if tc.wantErr != nil { - require.ErrorIs(t, err, tc.wantErr) - } - - if tc.wantMsg != "" { - require.Contains( - t, err.Error(), tc.wantMsg, - ) - } - }, - ) - } -} - -// TestChainsRecordRoundTrip pins decode→re-encode against the BOLT 12 offer -// test vectors. -func TestChainsRecordRoundTrip(t *testing.T) { - t.Parallel() - - // bitcoinHash is the bitcoin mainnet genesis hash hex-decoded into a - // fixed array. Defined locally so the test does not depend on constants - // introduced by later commits. - bitcoinHashHex := "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365" + - "e15a089c68d6190000000000" - - var bitcoinHash [chainHashLen]byte - bitcoinHashBytes, err := hex.DecodeString(bitcoinHashHex) - require.NoError(t, err) - copy(bitcoinHash[:], bitcoinHashBytes) - - tests := []struct { - name string - // hex is the on-wire bytes of the offer_chains TLV value - // (concatenated 32-byte chain hashes), copied from - // bolt12/offers-test.json. - hex string - wantLen int - wantHash [chainHashLen]byte - }{ - { - name: "single testnet chain", - hex: "43497fd7f826957108f4a30fd9cec3ae" + - "ba79972084e90ead01ea330900000000", - wantLen: 1, - }, - { - name: "single bitcoin chain", - hex: bitcoinHashHex, - wantLen: 1, - wantHash: bitcoinHash, - }, - { - name: "two chains liquidv1 then bitcoin", - hex: "1466275836220db2944ca059a3a10ef6fd2ea684b" + - "0688d2c379296888a206003" + bitcoinHashHex, - wantLen: 2, - // Second chain in the list is bitcoin mainnet. - wantHash: bitcoinHash, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - data, err := hex.DecodeString(tc.hex) - require.NoError(t, err) - - var c ChainsRecord - err = decodeChainsRecord( - bytes.NewReader(data), &c, new([8]byte), - uint64( - len(data), - ), - ) - require.NoError(t, err) - require.Len(t, c.Chains, tc.wantLen) - - // Cross-check the canonical bitcoin chain hash where - // the row knows which slot it lives in. - var zero [chainHashLen]byte - if tc.wantHash != zero { - idx := tc.wantLen - 1 - require.Equal( - t, tc.wantHash, c.Chains[idx], - "bitcoin hash mismatch in slot %d", - idx, - ) - } - - var buf bytes.Buffer - require.NoError( - t, encodeChainsRecord(&buf, &c, new([8]byte)), - ) - - require.Equal(t, data, buf.Bytes()) - }) - } -} - -// TestFallbackAddressesRoundTrip encodes a list of fallback addresses -// covering BIP-141 v0, BIP-350 v1, a forward-compatible v2 entry, and -// a v17 entry that the spec mandates a *reader* ignore but the codec -// layer must still round-trip faithfully (the ignore policy lives at -// the invoice-consumer layer, not at the codec). The fallback list is -// on-chain payment data: a wrong version byte or mis-framed length -// translates into funds going to an unintended script, so encode/ -// decode must be a faithful bijection across the entire version -// range. -func TestFallbackAddressesRoundTrip(t *testing.T) { - t.Parallel() - - addrs := &FallbackAddresses{ - Addrs: []FallbackAddress{ - { - Version: 0, - Address: bytes.Repeat([]byte{0xab}, 20), - }, - { - Version: 1, - Address: bytes.Repeat([]byte{0xcd}, 32), - }, - { - Version: 2, - Address: bytes.Repeat([]byte{0xef}, 64), - }, - { - Version: 17, - Address: bytes.Repeat([]byte{0x99}, 20), - }, - }, - } - - var buf bytes.Buffer - require.NoError(t, encodeFallbackAddrs(&buf, addrs, new([8]byte))) - encoded := buf.Bytes() - - expectedSize := fallbackAddrsSize(addrs) - require.Equal(t, expectedSize, uint64(len(encoded))) - - var decoded FallbackAddresses - err := decodeFallbackAddrs( - bytes.NewReader(encoded), &decoded, new([8]byte), - uint64(len(encoded)), - ) - require.NoError(t, err) - require.Equal(t, addrs.Addrs, decoded.Addrs) -} - -// TestBlindedPayInfosRoundTrip encodes a list of blinded_payinfo entries and -// asserts decode reproduces them exactly. -func TestBlindedPayInfosRoundTrip(t *testing.T) { - t.Parallel() - - noFeats := *lnwire.NewRawFeatureVector() - someFeats := *lnwire.NewRawFeatureVector(8, 15) - - infos := &BlindedPayInfos{ - Infos: []BlindedPayInfo{ - { - FeeBaseMsat: 1000, - FeeProportionalMillionths: 250, - CltvExpiryDelta: 144, - HtlcMinimumMsat: 1, - HtlcMaximumMsat: 1_000_000, - Features: noFeats, - }, - { - FeeBaseMsat: 0, - FeeProportionalMillionths: 0, - CltvExpiryDelta: 40, - HtlcMinimumMsat: 0, - HtlcMaximumMsat: math.MaxUint64, - Features: someFeats, - }, - }, - } - - var buf bytes.Buffer - require.NoError(t, encodeBlindedPayInfos(&buf, infos, new([8]byte))) - encoded := buf.Bytes() - - require.Equal(t, blindedPayInfosSize(infos), uint64(len(encoded))) - - var decoded BlindedPayInfos - err := decodeBlindedPayInfos( - bytes.NewReader(encoded), &decoded, - new([8]byte), uint64(len(encoded)), - ) - require.NoError(t, err) - require.Equal(t, infos.Infos, decoded.Infos) -} - -// TestDecodeBlindedPayInfosRejectsTruncated covers truncation before the fixed -// fields and before the declared features payload. Each must fail rather than -// yield a partial BlindedPayInfos with corrupt entries. -func TestDecodeBlindedPayInfosRejectsTruncated(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - data []byte - declLen uint64 - errSubstr string - }{ - { - name: "missing fee_base", - data: nil, - declLen: 4, - errSubstr: "read fee_base", - }, - { - name: "features length exceeds remaining", - // fee_base(4) fee_prop(4) cltv(2) htlc_min(8) - // htlc_max(8) then flen=0xffff with no payload. - data: append( - make([]byte, 26), []byte{0xff, 0xff}..., - ), - declLen: 28, - errSubstr: "exceeds remaining", - }, - { - name: "exceeds cap", - data: make([]byte, (maxBlindedPayInfos+1)*28), - declLen: (maxBlindedPayInfos + 1) * 28, - errSubstr: "exceeds maxBlindedPayInfos", - }, - { - name: "non-minimal features", - // fee_base(4) + fee_prop(4) + cltv(2) + htlc_min(8) + - // htlc_max(8) followed by flen = 1, and 1 non-minimal - // feature byte (trailing zero). - data: append( - make([]byte, 26), []byte{0x00, 0x01, 0x00}..., - ), - declLen: 29, - errSubstr: "non-minimal", - }, - { - name: "inverted htlc range", - // htlc_min at bytes [10:18] = 1000, htlc_max at bytes - // [18:26] = 500, so min > max must be rejected before - // the flen/features are ever read. - data: func() []byte { - b := make([]byte, 26) - b[16], b[17] = 0x03, 0xe8 // htlc_min = 1000 - b[24], b[25] = 0x01, 0xf4 // htlc_max = 500 - - return b - }(), - declLen: 26, - errSubstr: "htlc_minimum_msat exceeds", - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - var bp BlindedPayInfos - err := decodeBlindedPayInfos( - bytes.NewReader(tc.data), &bp, new([8]byte), - tc.declLen, - ) - require.Error(t, err) - require.Contains(t, err.Error(), tc.errSubstr) - }) - } -} - -// TestEncodeFallbackAddrsRejectsOversize asserts the maxFallbackAddrLen cap is -// enforced before any bytes hit the writer. -func TestEncodeFallbackAddrsRejectsOversize(t *testing.T) { - t.Parallel() - - addrs := &FallbackAddresses{ - Addrs: []FallbackAddress{{ - Version: 0, - Address: make([]byte, maxFallbackAddrLen+1), - }}, - } - - var buf bytes.Buffer - err := encodeFallbackAddrs(&buf, addrs, new([8]byte)) - require.Error(t, err) - require.Contains(t, err.Error(), "exceeds limit") - require.Zero(t, buf.Len(), - "no bytes should be written when validation fails") -} - -// TestDecodeFallbackAddrsRejectsTruncated covers the three truncation points in -// decodeFallbackAddrs: stream ends before the version byte, before the 16-bit -// length, and before the address payload of the declared size. Each must fail -// with an error rather than yielding a partial FallbackAddresses with corrupt -// entries. -func TestDecodeFallbackAddrsRejectsTruncated(t *testing.T) { - t.Parallel() - - // Each case declares a TLV-record length that overshoots the bytes - // actually present, simulating a malformed wire payload that promises - // more data than it delivers. - tests := []struct { - name string - data []byte - declLen uint64 - errSubstr string - }{ - { - name: "missing version byte", - data: nil, - declLen: 1, - errSubstr: "read version", - }, - { - name: "missing length bytes", - data: []byte{0x00}, - declLen: 3, - errSubstr: "read addrlen", - }, - { - name: "truncated address payload", - data: []byte{ - 0x00, 0x00, 0x05, 0xab, 0xab, - }, - declLen: 8, - errSubstr: "read address", - }, - { - // addrlen > remaining trips the guard before - // allocation; without it a hostile addrlen would force - // a huge make([]byte, addrLen). - name: "addrlen exceeds remaining", - data: []byte{0x00, 0xff, 0xff, 0xab}, - declLen: 4, - errSubstr: "exceeds remaining", - }, - { - name: "exceeds cap", - data: make([]byte, (maxFallbackAddrs+1)*3), - declLen: (maxFallbackAddrs + 1) * 3, - errSubstr: "exceeds maxFallbackAddrs", - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - var fa FallbackAddresses - err := decodeFallbackAddrs( - bytes.NewReader(tc.data), &fa, new([8]byte), - tc.declLen, - ) - require.Error(t, err) - require.Contains(t, err.Error(), tc.errSubstr) - }) - } -} diff --git a/bolt12/tlv_types.go b/bolt12/tlv_types.go deleted file mode 100644 index b0918ac2a..000000000 --- a/bolt12/tlv_types.go +++ /dev/null @@ -1,39 +0,0 @@ -package bolt12 - -import ( - "github.com/lightningnetwork/lnd/tlv" -) - -// TUint64 is a uint64 that serializes using truncated encoding (tu64) -// as required by BOLT 12. Leading zero bytes are omitted. -type TUint64 uint64 - -// Record returns a TLV record using truncated uint64 encoding. -// -// NOTE: This implements the tlv.RecordProducer interface. -func (t *TUint64) Record() tlv.Record { - return tlv.MakeDynamicRecord( - 0, (*uint64)(t), - func() uint64 { - return tlv.SizeTUint64(uint64(*t)) - }, - tlv.ETUint64, tlv.DTUint64, - ) -} - -// TUint32 is a uint32 that serializes using truncated encoding (tu32) as -// required by BOLT 12. Leading zero bytes are omitted. -type TUint32 uint32 - -// Record returns a TLV record using truncated uint32 encoding. -// -// NOTE: This implements the tlv.RecordProducer interface. -func (t *TUint32) Record() tlv.Record { - return tlv.MakeDynamicRecord( - 0, (*uint32)(t), - func() uint64 { - return tlv.SizeTUint32(uint32(*t)) - }, - tlv.ETUint32, tlv.DTUint32, - ) -} diff --git a/bolt12/validate.go b/bolt12/validate.go deleted file mode 100644 index b80daaa49..000000000 --- a/bolt12/validate.go +++ /dev/null @@ -1,1845 +0,0 @@ -package bolt12 - -import ( - "bytes" - "errors" - "fmt" - "math/bits" - "slices" - "time" - "unicode/utf8" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/tlv" - "golang.org/x/text/currency" -) - -var ( - // ErrOutOfRangeType is returned when a TLV type falls outside the - // allowed offer ranges (1-79 and 1000000000-1999999999). - ErrOutOfRangeType = errors.New("TLV type outside allowed range") - - // ErrUnknownEvenType is returned when an unknown even TLV type is - // present in an allowed range. Per BOLT 1, even types are - // must-understand: if the reader does not recognise the type, it MUST - // reject the message rather than silently ignoring the field. - ErrUnknownEvenType = errors.New("unknown even TLV type") - - // ErrUnknownEvenFeature is returned when an unknown even feature - // bit is set. - ErrUnknownEvenFeature = errors.New("unknown even feature bit set") - - // ErrNilPublicKey is returned when a public-key TLV is present but - // wraps a nil pointer. - ErrNilPublicKey = errors.New("public key present but nil") - - // ErrMissingDescription is returned when offer_amount is set but - // offer_description is absent. - ErrMissingDescription = errors.New( - "offer_amount set without offer_description", - ) - - // ErrCurrencyWithoutAmount is returned when offer_currency is set - // but offer_amount is absent. - ErrCurrencyWithoutAmount = errors.New( - "offer_currency set without offer_amount", - ) - - // ErrZeroAmount is returned when offer_amount is set to zero. The spec - // requires a present offer_amount to be strictly greater than zero so - // that a zero-value cannot masquerade as "no minimum required". - ErrZeroAmount = errors.New("offer_amount must be greater than zero") - - // ErrEmptyBlindedPaths is returned when a blinded paths field is - // present on a BOLT 12 message but its list of paths is empty. The - // spec writer requirements treat "present" as implying at least one - // usable path. - ErrEmptyBlindedPaths = errors.New("blinded paths field present but " + - "empty") - - // ErrNoIssuerIdentity is returned when neither offer_issuer_id - // nor offer_paths is set. - ErrNoIssuerIdentity = errors.New( - "neither offer_issuer_id nor offer_paths set", - ) - - // ErrOfferExpired is returned when the current time is after - // offer_absolute_expiry. - ErrOfferExpired = errors.New("offer has expired") - - // ErrEmptyChains is returned when offer_chains is present but - // contains no entries. - ErrEmptyChains = errors.New( - "offer_chains present but empty", - ) - - // ErrUnsupportedChain is returned when offer_chains does not - // contain our active chain. - ErrUnsupportedChain = errors.New( - "offer does not support our chain", - ) - - // ErrInvalidUTF8 is returned when a UTF-8 field contains invalid - // sequences. - ErrInvalidUTF8 = errors.New("invalid UTF-8") - - // ErrInvalidCurrency is returned when offer_currency is not a valid ISO - // 4217 code. - ErrInvalidCurrency = errors.New("invalid offer_currency") - - // ErrMissingAmount is returned when neither offer_amount nor - // invreq_amount is present. - ErrMissingAmount = errors.New("missing amount field") - - // ErrAmountBelowExpected is returned when a present invreq_amount is - // less than the amount expected from offer_amount (times - // invreq_quantity). The spec states this from both sides: the writer - // MUST NOT set a lower amount and the reader MUST reject one. - ErrAmountBelowExpected = errors.New( - "invreq_amount below offer-expected amount", - ) - - // ErrQuantityZero is returned when invreq_quantity is present but set - // to 0 while offer_quantity_max is present. The spec distinguishes this - // from a missing field (see ErrQuantityMissing). - ErrQuantityZero = errors.New("invreq_quantity is zero") - - // ErrQuantityMissing is returned when offer_quantity_max is present but - // the invreq omits invreq_quantity entirely. The spec states this as a - // distinct rejection ("MUST reject ... if there is no invreq_quantity - // field") from a present-but-zero quantity. - ErrQuantityMissing = errors.New( - "invreq_quantity missing but offer_quantity_max present", - ) - - // ErrQuantityExceedsMax is returned when invreq_quantity is greater - // than offer_quantity_max. - ErrQuantityExceedsMax = errors.New( - "invreq_quantity exceeds offer_quantity_max", - ) - - // ErrQuantityWithoutMax is returned when invreq_quantity is set but the - // mirrored offer_quantity_max is absent. The spec only permits a - // quantity when the offer advertises a maximum. - ErrQuantityWithoutMax = errors.New( - "invreq_quantity set without offer_quantity_max", - ) - - // ErrInvalidBip353Name is returned when invreq_bip_353_name is - // structurally malformed or contains a non-alphabet byte. - ErrInvalidBip353Name = errors.New("invalid invreq_bip_353_name") - - // ErrMissingSignature is returned when a wire-form invoice or - // invoice_request is emitted without a populated signature TLV. - // Pre-sign Encode (used to compute the Merkle root) is permitted to run - // without a signature; the bech32 string-codec layer is where the - // signature becomes mandatory. - ErrMissingSignature = errors.New("missing signature") - - // ErrOfferFieldsOnSpontaneous is returned when an invoice request - // is not responding to an offer but includes offer fields (e.g. - // offer_chains, offer_amount, etc.). - ErrOfferFieldsOnSpontaneous = errors.New( - "offer fields present on non-offer response", - ) - - // ErrMissingCreatedAt is returned when invoice_created_at is absent. - ErrMissingCreatedAt = errors.New("missing invoice_created_at") - - // ErrMissingPaymentHash is returned when invoice_payment_hash is - // absent. - ErrMissingPaymentHash = errors.New("missing invoice_payment_hash") - - // ErrMissingNodeID is returned when invoice_node_id is absent. - ErrMissingNodeID = errors.New("missing invoice_node_id") - - // ErrMissingBlindedPay is returned when invoice_blindedpay is absent. - ErrMissingBlindedPay = errors.New("missing invoice_blindedpay") - - // ErrBlindedPayMismatch is returned when invoice_blindedpay does not - // correspond 1:1 with invoice_paths. - ErrBlindedPayMismatch = errors.New( - "invoice_blindedpay count does not match invoice_paths", - ) - - // ErrMissingPaths is returned when invoice_paths is absent. - ErrMissingPaths = errors.New("missing invoice_paths") - - // ErrNoUsablePaths is returned by ValidateInvoiceRead when every - // blinded path in invoice_paths carries unknown required features in - // payinfo. - ErrNoUsablePaths = errors.New( - "no blinded paths with known required features", - ) - - // ErrInvoiceExpired is returned by ValidateInvoiceExpiry when the - // caller's clock is past invoice_created_at + invoice_relative_expiry - // (default 7200 seconds when relative expiry is absent). - ErrInvoiceExpired = errors.New("invoice has expired") - - // ErrInvoiceMismatch is returned when an invoice field does not match - // the invoice request. - ErrInvoiceMismatch = errors.New( - "invoice field mismatch with request", - ) - - // ErrInvoiceNodeIDMismatch is returned when offer_issuer_id is present - // but invoice_node_id does not equal it. The spec requires the invoice - // to be signed by the offer's issuer in this case. - ErrInvoiceNodeIDMismatch = errors.New( - "invoice_node_id does not match offer_issuer_id", - ) - - // ErrZeroInvoiceAmount is returned when invoice_amount is present but - // set to zero. The spec permits a zero "minimum amount", but a - // zero-amount HTLC cannot settle past the channel-layer dust limit, so - // the codec rejects it with a typed sentinel a spec-strict caller can - // distinguish from a missing-field violation. - ErrZeroInvoiceAmount = errors.New("invoice_amount must be greater " + - "than zero") - - // ErrMissingError is returned when an invoice_error omits the error - // field. - ErrMissingError = errors.New("invoice_error missing error field") - - // ErrEmptyError is returned when an invoice_error carries a zero-length - // error field. - ErrEmptyError = errors.New( - "invoice_error error field is empty", - ) - - // ErrSuggestedWithoutField is returned when suggested_value is set - // without erroneous_field. - ErrSuggestedWithoutField = errors.New( - "suggested_value set without erroneous_field", - ) -) - -const ( - // Offer TLV types. - offerChainsType tlv.Type = 2 - offerMetadataType tlv.Type = 4 - offerCurrencyType tlv.Type = 6 - offerAmountType tlv.Type = 8 - offerDescriptionType tlv.Type = 10 - offerFeaturesType tlv.Type = 12 - offerAbsoluteExpiryType tlv.Type = 14 - offerPathsType tlv.Type = 16 - offerIssuerType tlv.Type = 18 - offerQuantityMaxType tlv.Type = 20 - offerIssuerIDType tlv.Type = 22 - - // InvoiceRequest TLV types. - invreqMetadataType tlv.Type = 0 - invreqChainType tlv.Type = 80 - invreqAmountType tlv.Type = 82 - invreqFeaturesType tlv.Type = 84 - invreqQuantityType tlv.Type = 86 - invreqPayerIDType tlv.Type = 88 - invreqPayerNoteType tlv.Type = 89 - invreqPathsType tlv.Type = 90 - invreqBip353NameType tlv.Type = 91 - signatureTLVType tlv.Type = 240 - - // Invoice TLV types. - invoicePathsType tlv.Type = 160 - invoiceBlindedPayType tlv.Type = 162 - invoiceCreatedAtType tlv.Type = 164 - invoiceRelativeExpiryType tlv.Type = 166 - invoicePaymentHashType tlv.Type = 168 - invoiceAmountType tlv.Type = 170 - invoiceFallbacksType tlv.Type = 172 - invoiceFeaturesType tlv.Type = 174 - invoiceNodeIDType tlv.Type = 176 - - // InvoiceError TLV types. - invoiceErrorErroneousFieldType tlv.Type = 1 - invoiceErrorSuggestedValueType tlv.Type = 3 - invoiceErrorErrorType tlv.Type = 5 -) - -// ValidateInvoiceErrorWrite validates an invoice_error per the BOLT 12 writer -// requirements. The checks follow the spec's writer section in order. The -// caller must check that the suggested value, if present, contains a valid -// type. -func ValidateInvoiceErrorWrite(ie *InvoiceError) error { - // - MUST set error to an explanatory string. - if !ie.Error.IsSome() { - return ErrMissingError - } - - // The spec's "explanatory string" is a type-5 UTF-8 message, so an - // empty or non-UTF-8 blob carries no explanation and fails the - // requirement even though IsSome passes. checkUTF8 mirrors the - // treatment every other BOLT 12 UTF-8 field receives. - if len(ie.Error.ValOpt().UnwrapOr(nil)) == 0 { - return ErrEmptyError - } - if err := checkUTF8(ie.Error, "error"); err != nil { - return err - } - - // - MAY set erroneous_field to a specific field number in the invoice - // or invoice_request which had a problem. - // No presence check: erroneous_field is optional for the writer. - hasErrField := ie.ErroneousField.IsSome() - - // - if it sets erroneous_field: - // - MAY set suggested_value. - // - otherwise: - // - MUST NOT set suggested_value. - if ie.SuggestedValue.IsSome() && !hasErrField { - return ErrSuggestedWithoutField - } - - // - if it sets suggested_value: - // - MUST set suggested_value to a valid field for that - // tlv_fieldnum. - // NOT CHECKED HERE: verifying the replacement is a valid encoding for - // the erroneous field needs the schema of the rejected invoice or - // invoice_request, which is caller context this validator does not - // have. - - return nil -} - -// isKnownInvoiceErrorTLVType reports whether typ is a defined invoice_error -// TLV type (1, 3, 5). -func isKnownInvoiceErrorTLVType(typ tlv.Type) bool { - switch typ { - case invoiceErrorErroneousFieldType, - invoiceErrorSuggestedValueType, - invoiceErrorErrorType: - - return true - default: - return false - } -} - -// ValidateInvoiceErrorRead applies the BOLT 1 must-understand rule to a decoded -// invoice_error: reject unknown even TLV types, tolerate unknown odd ones. It -// is the only read-side check, since BOLT 12 leaves the semantic reader -// requirements undefined (FIXME). -func ValidateInvoiceErrorRead(ie *InvoiceError) error { - for _, t := range sortedTypes(ie.decodedTLVs) { - if !isKnownInvoiceErrorTLVType(t) && t%2 == 0 { - return fmt.Errorf("%w: type %d", ErrUnknownEvenType, t) - } - } - - return nil -} - -// isKnownInvreqTLVType determines if a TLV type is defined in the -// invoice_request specification. -func isKnownInvreqTLVType(typ tlv.Type) bool { - switch typ { - case invreqMetadataType, - invreqChainType, - invreqAmountType, - invreqFeaturesType, - invreqQuantityType, - invreqPayerIDType, - invreqPayerNoteType, - invreqPathsType, - invreqBip353NameType, - signatureTLVType: - - return true - - default: - return isKnownOfferTLVType(typ) - } -} - -// ValidateInvoiceRequestWrite ensures an invoice request adheres to the BOLT 12 -// writer requirements. -// -// Note: This writer validation assumes that for requests responding to an -// offer, the caller/constructor has already mirrored the offer's fields exactly -// by using the NewInvoiceRequestFromOffer constructor, as an invoice request -// can also be created without an offer. -func ValidateInvoiceRequestWrite(ir *InvoiceRequest) error { - // A present-but-nil pubkey passes IsSome but would panic the codec on - // encode, so reject both pubkey fields. - if err := checkPubKeyNotNil( - ir.InvreqPayerID, "invreq_payer_id", - ); err != nil { - return err - } - if err := checkPubKeyNotNil( - ir.OfferIssuerID, "offer_issuer_id", - ); err != nil { - return err - } - - // - if it is responding to an offer: - isResponse := ir.OfferIssuerID.IsSome() || ir.OfferPaths.IsSome() - //nolint:nestif - if isResponse { - // - if offer_chains is set: - // - MUST set invreq_chain to one of offer_chains unless that - // chain is bitcoin, in which case it SHOULD omit - // invreq_chain. - // - otherwise (no offer_chains): - // - if it sets invreq_chain it MUST set it to bitcoin. - chains := getInvoiceRequestOfferChains(ir) - chain := getInvreqChain(ir) - if !slices.Contains(chains, chain) { - return ErrUnsupportedChain - } - - // - if offer_amount is not present: - // - MUST specify invreq_amount. - if !ir.OfferAmount.IsSome() && !ir.InvreqAmount.IsSome() { - return ErrMissingAmount - } - - // - MUST set signature.sig using the invreq_payer_id. - // NOT CHECKED HERE: signing happens after this validator runs; - // the string encoder rejects an unsigned request and the reader - // verifies signature correctness. - - // - MUST set invreq_payer_id to a transient public key. - // NOT CHECKED HERE: only presence is checked below; the caller - // MUST supply a fresh key per request and remember its secret. - if !ir.InvreqPayerID.IsSome() { - return ErrMissingPayerID - } - - // - 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. - // - otherwise: - // - MUST NOT set invreq_quantity - // - // Checked before the amount so the bounded quantity feeds the - // offer_amount*quantity product (reader uses the same order). - if err := checkInvreqQuantity(ir); err != nil { - return err - } - - // - 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 err := checkInvreqAmountMeetsOffer(ir); err != nil { - return err - } - } else { - // - otherwise (not responding to an offer): - - // - MUST set invreq_payer_id (as it would set offer_issuer_id - // for an offer). - if !ir.InvreqPayerID.IsSome() { - return ErrMissingPayerID - } - - // - MUST set invreq_paths as it would set (or not set) - // offer_paths for an offer. - if err := checkBlindedPaths(ir.InvreqPaths); err != nil { - return err - } - - // - MUST set offer_description to a complete description of the - // purpose of the payment. - if !ir.OfferDescription.IsSome() { - return ErrMissingDescription - } - - // - MUST NOT include signature, offer_metadata, offer_chains, - // offer_amount, offer_currency, offer_features, - // offer_quantity_max, offer_paths or offer_issuer_id. - // - // signature is intentionally omitted from this list: the spec's - // unsigned offerless variant conflicts with the unconditional - // reader signature check, and other implementations require a - // signature on every invoice_request. We always sign, so a - // present signature here is expected. - if ir.OfferMetadata.IsSome() || ir.OfferChains.IsSome() || - ir.OfferAmount.IsSome() || ir.OfferCurrency.IsSome() || - ir.OfferFeatures.IsSome() || - ir.OfferQuantityMax.IsSome() || - ir.OfferPaths.IsSome() || ir.OfferIssuerID.IsSome() { - - return ErrOfferFieldsOnSpontaneous - } - - // - if the chain for the invoice is not solely bitcoin: - // - MUST specify invreq_chain the offer is valid for. - // NOT CHECKED HERE: this validator has no chain context. The - // caller building the request MUST set invreq_chain for - // non-bitcoin chains; the reader enforces it via activeChain. - - // - MUST NOT set invreq_quantity. - if err := checkInvreqQuantity(ir); err != nil { - return err - } - - // - MUST set invreq_amount. - if !ir.InvreqAmount.IsSome() { - return ErrMissingAmount - } - } - - // - MUST NOT set any non-signature TLV fields outside the inclusive - // ranges: 0 to 159 and 1000000000 to 2999999999 - // - // The signature range (240-1000) is excluded: it carries signature TLV - // elements, which by design sit outside the message ranges. - for _, t := range sortedTypes(ir.decodedTLVs) { - if bolt12InUnsignedRange(t) { - continue - } - if !invreqAllowedRange(t) { - return fmt.Errorf("%w: type %d", - ErrOutOfRangeType, t) - } - } - - // - MUST set invreq_metadata to an unpredictable series of bytes. - // NOT CHECKED HERE: only presence is verified; unpredictability is the - // caller's responsibility. - if !ir.InvreqMetadata.IsSome() { - return ErrMissingMetadata - } - - // - if it sets invreq_amount: MUST set msat in multiples of the minimum - // payable unit. - // NOT CHECKED HERE: trivially satisfied for bitcoin (msat); a caller - // on a chain with a coarser unit MUST enforce it. - - // - if it supports bolt12 invoice request features: - // - MUST set invreq_features.features to the bitmap of features. - // We rely on the writer to set feature bits correctly as those are - // mostly static and the reader will also verify the features. This is - // done to not having to pass in the known feature vector for writer - // validation, similar to other write validation in this file. - - // check UTF-8 constraints and BIP 353 - err := checkUTF8(ir.InvreqPayerNote, "invreq_payer_note") - if err != nil { - return err - } - - // - if it received the offer using BIP 353 resolution: - // - MUST include invreq_bip_353_name with name/domain from the HRN. - // NOT CHECKED HERE: whether resolution was used is caller context; we - // only validate the field's alphabet/layout when present. - if err := checkBip353Name(ir.InvreqBip353Name); err != nil { - return err - } - - return nil -} - -// invreqAllowedRange determines if the TLV type falls within the allowed -// ranges for invoice request messages. -func invreqAllowedRange(typ tlv.Type) bool { - return typ <= 159 || - (typ >= 1000000000 && typ <= 2999999999) -} - -// checkInvreqAmountMeetsOffer enforces that a present invreq_amount is at least -// offer_amount times invreq_quantity. Shared by the writer and reader, which -// state the same rule from each side. -// -// It covers only the native case (offer_currency absent, amounts in msat). When -// offer_currency is present the expected amount needs a live exchange-rate -// conversion the codec cannot do, so the caller MUST compare; it returns nil. -func checkInvreqAmountMeetsOffer(ir *InvoiceRequest) error { - if !ir.OfferAmount.IsSome() || !ir.InvreqAmount.IsSome() { - return nil - } - - // NOT CHECKED HERE: the offer_currency (non-bitcoin) case. Caller MUST - // convert offer_amount to the invreq_chain currency and compare. - if ir.OfferCurrency.IsSome() { - return nil - } - - var offerAmt uint64 - ir.OfferAmount.WhenSome(func(r tlv.RecordT[tlv.TlvType8, TUint64]) { - offerAmt = uint64(r.Val) - }) - - var qty uint64 = 1 - ir.InvreqQuantity.WhenSome(func(r tlv.RecordT[tlv.TlvType86, TUint64]) { - qty = uint64(r.Val) - }) - - var invreqAmt uint64 - ir.InvreqAmount.WhenSome(func(r tlv.RecordT[tlv.TlvType82, TUint64]) { - invreqAmt = uint64(r.Val) - }) - - // Guard against overflows. - hi, expectedAmt := bits.Mul64(offerAmt, qty) - if hi != 0 { - return fmt.Errorf("%w: offer_amount %d * quantity %d "+ - "overflows uint64", ErrAmountBelowExpected, - offerAmt, qty) - } - if invreqAmt < expectedAmt { - return fmt.Errorf("%w: invreq_amount %d below expected %d", - ErrAmountBelowExpected, invreqAmt, expectedAmt) - } - - return nil -} - -// getInvreqChain returns the chain genesis hash an invoice request -// targets, defaulting to Bitcoin mainnet when invreq_chain is absent -// per the BOLT 12 reader rule. -func getInvreqChain(ir *InvoiceRequest) [32]byte { - chain := bitcoinMainnetGenesisHash - ir.InvreqChain.WhenSome( - func(r tlv.RecordT[tlv.TlvType80, [32]byte]) { - chain = r.Val - }, - ) - - return chain -} - -// ValidateInvoiceRequestRead validates an invoice request against the BOLT 12 -// reader requirements. It performs generic, stateless structural checks only. -// Stateful or contextual checks (offer matching, path verification, unit-price -// calculations) must be handled externally by the caller. -// -// Signature verification is NOT performed yet: the reader MUST also reject a -// request whose Schnorr signature does not verify against invreq_payer_id, but -// that check is deferred until the merkle/signing primitives land with the -// Invoice message (see the TODO at the end of this function). Until then, a -// caller wiring this into a handler MUST verify the signature itself. -func ValidateInvoiceRequestRead(ir *InvoiceRequest, - activeChain [32]byte, - knownFeatures map[lnwire.FeatureBit]string) error { - - // A present-but-nil pubkey passes IsSome but would panic the codec on - // encode, so reject both pubkey fields. - if err := checkPubKeyNotNil( - ir.InvreqPayerID, "invreq_payer_id", - ); err != nil { - return err - } - if err := checkPubKeyNotNil( - ir.OfferIssuerID, "offer_issuer_id", - ); err != nil { - return err - } - - // - MUST reject the invoice request if invreq_payer_id or - // invreq_metadata are not present. - if !ir.InvreqPayerID.IsSome() { - return ErrMissingPayerID - } - if !ir.InvreqMetadata.IsSome() { - return ErrMissingMetadata - } - - // - MUST reject the invoice request if any non-signature TLV fields are - // outside the inclusive ranges: 0 to 159 and 1000000000 to 2999999999 - // - // The signature range (240-1000) is excluded from the out-of-range - // check: it holds one or more signature TLV elements, so an unknown odd - // type there is a future optional signature we ignore ("it's ok to be - // odd") rather than reject. Unknown even types remain must-understand - // and are rejected everywhere, including inside the signature range. - for _, t := range sortedTypes(ir.decodedTLVs) { - if !bolt12InUnsignedRange(t) && !invreqAllowedRange(t) { - return fmt.Errorf("%w: type %d", - ErrOutOfRangeType, t) - } - if !isKnownInvreqTLVType(t) && t%2 == 0 { - return fmt.Errorf("%w: type %d", - ErrUnknownEvenType, t) - } - } - - // - if invreq_features contains unknown *even* bits that are non-zero: - // - MUST reject the invoice request. - if err := checkFeatures(ir.InvreqFeatures, knownFeatures); err != nil { - return err - } - - // - if num_hops is 0 in any blinded_path in invreq_paths: - // - MUST reject the invoice request. - if err := checkBlindedPaths(ir.InvreqPaths); err != nil { - return err - } - - // - if offer_issuer_id or offer_paths are present (response to an - // offer): - isResponse := ir.OfferIssuerID.IsSome() || ir.OfferPaths.IsSome() - if isResponse { - // NOT CHECKED HERE (need the offer store / arrival path / - // reply-path state, so the caller MUST do these): - // - MUST reject if the offer fields do not exactly match a - // valid, unexpired offer. - // - if offer_paths is present: MUST ignore the request unless - // it arrived via one of those paths; otherwise MUST ignore - // any request that arrived via a blinded path. - // - if invreq_metadata equals a previous request: MAY reply - // with the previous invoice; otherwise MUST NOT. - // - SHOULD send the invoice via the onionmsg_tlv reply_path. - - // - if offer_quantity_max is present: - // - MUST reject the invoice request if there is no - // invreq_quantity field. - // - if offer_quantity_max is non-zero: - // - MUST reject the invoice request if invreq_quantity is - // zero, OR greater than offer_quantity_max. - // - otherwise (no offer_quantity_max): - // - MUST reject the invoice request if there is an - // invreq_quantity field. - if err := checkInvreqQuantity(ir); err != nil { - return err - } - - // - if offer_amount is present: if invreq_amount is present, - // MUST reject when it is below the expected amount. The - // helper covers the native case; the currency-conversion - // case is deferred to the caller (see the helper doc). - if err := checkInvreqAmountMeetsOffer(ir); err != nil { - return err - } - - if !ir.OfferAmount.IsSome() { - // - otherwise (no offer_amount): - // - MUST reject the invoice request if it does not - // contain invreq_amount. - if !ir.InvreqAmount.IsSome() { - return ErrMissingAmount - } - } - } else { - // - otherwise (no offer_issuer_id or offer_paths, not a - // response to our offer): - - // - MUST reject the invoice request if any of the following - // are present: offer_chains, offer_features or - // offer_quantity_max. - if ir.OfferChains.IsSome() || ir.OfferFeatures.IsSome() || - ir.OfferQuantityMax.IsSome() { - - return ErrOfferFieldsOnSpontaneous - } - - // - MUST reject the invoice request if there is an - // invreq_quantity field. - if err := checkInvreqQuantity(ir); err != nil { - return err - } - - // - MUST reject the invoice request if invreq_amount is not - // present. - if !ir.InvreqAmount.IsSome() { - return ErrMissingAmount - } - - // NOT CHECKED HERE (caller's responsibility if it replies): - // - MAY use offer_amount / offer_currency for informational - // display to the user. - // - if it sends an invoice in response: MUST use invreq_paths - // if present, otherwise MUST use invreq_payer_id as - // the node id to send to. - } - - // - if invreq_chain is not present: - // - MUST reject the invoice request if bitcoin is not a supported - // chain. - // - otherwise: - // - MUST reject the invoice request if invreq_chain.chain is not a - // supported chain. - if getInvreqChain(ir) != activeChain { - return ErrUnsupportedChain - } - - // - if invreq_bip_353_name is present: - // - MUST reject the invoice request if name or domain contain any - // bytes which are not 0-9, a-z, A-Z, -, _ or . - if err := checkBip353Name(ir.InvreqBip353Name); err != nil { - return err - } - - // - MUST reject the invoice request if signature is not correct as - // detailed in Signature Calculation using the invreq_payer_id. - // TODO(bolt12): implement signature verification. - if !ir.Signature.IsSome() { - return ErrMissingSignature - } - - return nil -} - -// getInvoiceRequestOfferChains returns the chains an invoice request's mirrored -// offer is valid for. If offer_chains is absent, the spec defaults to Bitcoin -// mainnet. -func getInvoiceRequestOfferChains(ir *InvoiceRequest) [][32]byte { - chains := fn.MapOptionZ( - ir.OfferChains.ValOpt(), - func(r ChainsRecord) [][32]byte { return r.Chains }, - ) - - if len(chains) == 0 { - chains = [][32]byte{bitcoinMainnetGenesisHash} - } - - return chains -} - -// checkInvreqQuantity validates the spec coupling between offer_quantity_max -// and invreq_quantity. -func checkInvreqQuantity(ir *InvoiceRequest) error { - // Without offer_quantity_max the spec forbids invreq_quantity: the - // writer MUST NOT set it and the reader MUST reject a request that - // carries it. - if !ir.OfferQuantityMax.IsSome() { - if ir.InvreqQuantity.IsSome() { - return ErrQuantityWithoutMax - } - - return nil - } - - // offer_quantity_max is present, so invreq_quantity is mandatory. The - // spec separates "no invreq_quantity field" from "invreq_quantity is - // zero", so report them with distinct sentinels even though both - // reject. - if !ir.InvreqQuantity.IsSome() { - return ErrQuantityMissing - } - - var qty uint64 - ir.InvreqQuantity.WhenSome( - func(r tlv.RecordT[tlv.TlvType86, TUint64]) { - qty = uint64(r.Val) - }, - ) - if qty == 0 { - return ErrQuantityZero - } - - var maxQty uint64 - ir.OfferQuantityMax.WhenSome( - func(r tlv.RecordT[tlv.TlvType20, TUint64]) { - maxQty = uint64(r.Val) - }, - ) - - // If maxQty is 0 (unlimited/unknown), we only enforce that the - // requested qty is greater than zero, bypassing the upper bound check. - if maxQty > 0 && qty > maxQty { - return ErrQuantityExceedsMax - } - - return nil -} - -// checkBip353Name validates the wire layout and alphabet of -// invreq_bip_353_name. Both name and domain MUST contain only DNS-safe -// characters per the BOLT 12 reader and writer requirements. -func checkBip353Name(opt tlv.OptionalRecordT[tlv.TlvType91, tlv.Blob]) error { - var ( - data []byte - present bool - ) - opt.WhenSome(func(r tlv.RecordT[tlv.TlvType91, tlv.Blob]) { - data = r.Val - present = true - }) - - // An absent field is a no-op. A present-but-empty field is malformed - // (it cannot carry name_len) and falls through to the length check - // below rather than being mistaken for absent. - if !present { - return nil - } - - if len(data) < 1 { - return fmt.Errorf("%w: missing name_len", ErrInvalidBip353Name) - } - nameLen := int(data[0]) - if nameLen == 0 { - return fmt.Errorf("%w: empty name", ErrInvalidBip353Name) - } - - domainLenIdx := 1 + nameLen - if domainLenIdx >= len(data) { - return fmt.Errorf("%w: truncated before domain_len", - ErrInvalidBip353Name) - } - - name := data[1:domainLenIdx] - - domainStart := domainLenIdx + 1 - domainLen := int(data[domainLenIdx]) - if domainLen == 0 { - return fmt.Errorf("%w: empty domain", ErrInvalidBip353Name) - } - if domainStart+domainLen != len(data) { - return fmt.Errorf("%w: domain length mismatch", - ErrInvalidBip353Name) - } - domain := data[domainStart:] - - if err := checkBip353Alphabet(name); err != nil { - return fmt.Errorf("%w: name: %w", - ErrInvalidBip353Name, err) - } - - if err := checkBip353Alphabet(domain); err != nil { - return fmt.Errorf("%w: domain: %w", - ErrInvalidBip353Name, err) - } - - return nil -} - -// checkBip353Alphabet returns an error when any byte falls outside the BIP 353 -// alphabet. -func checkBip353Alphabet(b []byte) error { - for i, c := range b { - switch { - case c >= '0' && c <= '9': - case c >= 'a' && c <= 'z': - case c >= 'A' && c <= 'Z': - case c == '-' || c == '_' || c == '.': - default: - return fmt.Errorf("byte %d (0x%02x) outside "+ - "alphabet", i, c) - } - } - - return nil -} - -// offerAllowedRange returns true if the TLV type falls within the allowed -// ranges for offer messages: 1-79 and 1000000000-1999999999. -func offerAllowedRange(typ tlv.Type) bool { - return (typ >= 1 && typ <= 79) || - (typ >= 1000000000 && typ <= 1999999999) -} - -// isKnownOfferTLVType returns true for TLV types that are defined in the offer -// spec (even types 2-22). -func isKnownOfferTLVType(typ tlv.Type) bool { - switch typ { - case offerChainsType, - offerMetadataType, - offerCurrencyType, - offerAmountType, - offerDescriptionType, - offerFeaturesType, - offerAbsoluteExpiryType, - offerPathsType, - offerIssuerType, - offerQuantityMaxType, - offerIssuerIDType: - - return true - - default: - return false - } -} - -// ValidateOfferRead validates an offer per the BOLT 12 offer reader -// requirements. The now parameter is used for expiry checks and can be -// overridden in tests. activeChain is required: per spec, absent offer_chains -// defaults to Bitcoin mainnet, and the reader must reject offers that do not -// list a chain it operates on. Pass the genesis hash of the chain the receiver -// is willing to settle on. -func ValidateOfferRead(o *Offer, now time.Time, activeChain [32]byte, - knownFeatures map[lnwire.FeatureBit]string) error { - - // A present-but-nil offer_issuer_id passes IsSome but would panic the - // codec on encode, so reject it here. - if err := checkPubKeyNotNil( - o.OfferIssuerID, "offer_issuer_id", - ); err != nil { - return err - } - // Check TLV types are in allowed range and that unknown even types are - // rejected (even = must-understand). - for _, t := range sortedTypes(o.decodedTLVs) { - if !offerAllowedRange(t) { - return fmt.Errorf("%w: type %d", ErrOutOfRangeType, t) - } - - if !isKnownOfferTLVType(t) && t%2 == 0 { - return fmt.Errorf("%w: type %d", ErrUnknownEvenType, t) - } - } - - // Check for unknown even feature bits. - if err := checkFeatures(o.OfferFeatures, knownFeatures); err != nil { - return err - } - - // offer_chains present but empty. - var chainsEmpty bool - o.OfferChains.WhenSome( - func(r tlv.RecordT[tlv.TlvType2, ChainsRecord]) { - if len(r.Val.Chains) == 0 { - chainsEmpty = true - } - }, - ) - if chainsEmpty { - return ErrEmptyChains - } - - // Validate the offer's chain against the active chain. An absent - // offer_chains TLV means "Bitcoin mainnet" per spec, normalised by - // getOfferChains. - offerChains := getOfferChains(o) - found := slices.Contains(offerChains, activeChain) - if !found { - return ErrUnsupportedChain - } - - // offer_amount set requires offer_description. - hasAmount := o.OfferAmount.IsSome() - if hasAmount && !o.OfferDescription.IsSome() { - return ErrMissingDescription - } - - // offer_amount, if set, must be strictly greater than zero. - if err := checkAmountPositive(o.OfferAmount); err != nil { - return err - } - - // offer_currency requires offer_amount. - if o.OfferCurrency.IsSome() && !hasAmount { - return ErrCurrencyWithoutAmount - } - - // Must have either offer_issuer_id or offer_paths. - if !o.OfferIssuerID.IsSome() && !o.OfferPaths.IsSome() { - return ErrNoIssuerIdentity - } - - // Check blinded paths have at least one hop. - if err := checkBlindedPaths(o.OfferPaths); err != nil { - return err - } - - // Expiry check. A present-but-zero offer_absolute_expiry is as a valid - // timestamp in the past, it doesn't have the special meaning of "no - // expiry". - var ( - expiry uint64 - hasExpiry bool - ) - o.OfferAbsoluteExpiry.WhenSome( - func(r tlv.RecordT[tlv.TlvType14, TUint64]) { - expiry = uint64(r.Val) - hasExpiry = true - }, - ) - if hasExpiry && uint64(now.Unix()) > expiry { - return ErrOfferExpired - } - - // Validate UTF-8 fields. - if err := checkUTF8(o.OfferCurrency, "offer_currency"); err != nil { - return err - } - - if err := checkUTF8( - o.OfferDescription, "offer_description", - ); err != nil { - return err - } - - if err := checkUTF8(o.OfferIssuer, "offer_issuer"); err != nil { - return err - } - - if err := checkISO4217(o.OfferCurrency); err != nil { - return err - } - - return nil -} - -// bitcoinMainnetGenesisHash is the genesis hash for Bitcoin mainnet, used as -// the default when offer_chains is absent per the spec. -var bitcoinMainnetGenesisHash = [32]byte(*chaincfg.MainNetParams.GenesisHash) - -// getOfferChains returns the chains an offer is valid for. If offer_chains is -// absent, the spec defaults to Bitcoin mainnet. -func getOfferChains(o *Offer) [][32]byte { - chains := fn.MapOptionZ( - o.OfferChains.ValOpt(), - func(r ChainsRecord) [][32]byte { return r.Chains }, - ) - - if len(chains) == 0 { - chains = [][32]byte{bitcoinMainnetGenesisHash} - } - - return chains -} - -// ValidateOfferWrite validates an offer per the BOLT 12 offer writer -// requirements. -func ValidateOfferWrite(o *Offer) error { - // A present-but-nil offer_issuer_id passes IsSome but would panic the - // codec on encode, so reject it here. - if err := checkPubKeyNotNil( - o.OfferIssuerID, "offer_issuer_id", - ); err != nil { - return err - } - - // Writer MUST NOT set TLV fields outside allowed ranges. This check - // catches a decoded-then-mutated offer: a freshly-built struct has no - // decodedTLVs (Decode is the only writer of that field). The typed - // field set already excludes out-of-range types by construction, so a - // freshly-built offer cannot violate the range rule in the first place. - for _, t := range sortedTypes(o.decodedTLVs) { - if !offerAllowedRange(t) { - return fmt.Errorf("%w: type %d", - ErrOutOfRangeType, t) - } - } - - // offer_amount requires offer_description. - if o.OfferAmount.IsSome() && !o.OfferDescription.IsSome() { - return ErrMissingDescription - } - - // offer_amount, if set, must be strictly greater than zero. - if err := checkAmountPositive(o.OfferAmount); err != nil { - return err - } - - // offer_currency requires offer_amount. - if o.OfferCurrency.IsSome() && !o.OfferAmount.IsSome() { - return ErrCurrencyWithoutAmount - } - - // Without offer_paths, MUST set offer_issuer_id. - if !o.OfferPaths.IsSome() && !o.OfferIssuerID.IsSome() { - return ErrNoIssuerIdentity - } - - // Defense in depth: writer-side mirrors of reader rejections for - // present-but-empty offer_chains and offer_paths. - var chainsEmpty bool - o.OfferChains.WhenSome( - func(r tlv.RecordT[tlv.TlvType2, ChainsRecord]) { - if len(r.Val.Chains) == 0 { - chainsEmpty = true - } - }, - ) - if chainsEmpty { - return ErrEmptyChains - } - - if err := checkBlindedPaths(o.OfferPaths); err != nil { - return err - } - - // Defense in depth: writer-side mirrors of the reader UTF-8 checks - // for offer_currency, offer_description, and offer_issuer. - if err := checkUTF8(o.OfferCurrency, "offer_currency"); err != nil { - return err - } - - if err := checkUTF8( - o.OfferDescription, "offer_description", - ); err != nil { - return err - } - - if err := checkUTF8(o.OfferIssuer, "offer_issuer"); err != nil { - return err - } - - if err := checkISO4217(o.OfferCurrency); err != nil { - return err - } - - return nil -} - -// checkISO4217 verifies that offer_currency, if set, parses as an ISO 4217 -// code. The upstream parser is case-insensitive and rejects both malformed and -// unrecognised codes. -func checkISO4217[T tlv.TlvType](opt tlv.OptionalRecordT[T, tlv.Blob]) error { - return fn.MapOptionZ(opt.ValOpt(), func(data tlv.Blob) error { - if _, err := currency.ParseISO(string(data)); err != nil { - return fmt.Errorf("%w: %w", ErrInvalidCurrency, err) - } - - return nil - }) -} - -// checkFeatures rejects any unknown even (must-understand) feature bit. -func checkFeatures[T tlv.TlvType]( - opt tlv.OptionalRecordT[T, lnwire.RawFeatureVector], - known map[lnwire.FeatureBit]string) error { - - return fn.MapOptionZ( - opt.ValOpt(), - func(fv lnwire.RawFeatureVector) error { - wrapped := lnwire.NewFeatureVector(&fv, known) - unknown := wrapped.UnknownRequiredFeatures() - if len(unknown) == 0 { - return nil - } - - // Sort for deterministic errors. - slices.Sort(unknown) - - return fmt.Errorf("%w: bit %d", - ErrUnknownEvenFeature, unknown[0]) - }, - ) -} - -// checkBlindedPaths walks each path in a blinded paths field and rejects empty -// Paths slices and paths with zero hops. -func checkBlindedPaths[T tlv.TlvType]( - opt tlv.OptionalRecordT[T, lnwire.BlindedPaths]) error { - - return fn.MapOptionZ( - opt.ValOpt(), - func(paths lnwire.BlindedPaths) error { - if len(paths.Paths) == 0 { - return ErrEmptyBlindedPaths - } - - for i, p := range paths.Paths { - if len(p.Hops) == 0 { - return fmt.Errorf("%w: path %d", - lnwire.ErrEmptyBlindedPath, i) - } - } - - return nil - }, - ) -} - -// checkAmountPositive rejects an offer_amount that is present but zero. -func checkAmountPositive[T tlv.TlvType]( - opt tlv.OptionalRecordT[T, TUint64]) error { - - return fn.MapOptionZ(opt.ValOpt(), func(v TUint64) error { - if v == 0 { - return ErrZeroAmount - } - - return nil - }) -} - -// checkUTF8 validates that a blob field contains valid UTF-8. -func checkUTF8[T tlv.TlvType](opt tlv.OptionalRecordT[T, tlv.Blob], - name string) error { - - return fn.MapOptionZ(opt.ValOpt(), func(data tlv.Blob) error { - if !utf8.Valid(data) { - return fmt.Errorf("%w: %s", ErrInvalidUTF8, name) - } - - return nil - }) -} - -// checkPubKeyNotNil returns an error if a public key TLV is present but nil. -func checkPubKeyNotNil[T tlv.TlvType]( - opt tlv.OptionalRecordT[T, *btcec.PublicKey], name string) error { - - return fn.MapOptionZ(opt.ValOpt(), func(pk *btcec.PublicKey) error { - if pk == nil { - return fmt.Errorf("%w: %s", ErrNilPublicKey, name) - } - - return nil - }) -} - -// checkInvoiceNodeID enforces the spec rule that, when offer_issuer_id is -// present, invoice_node_id MUST equal it. Both fields live on the invoice, so -// this is verifiable without the originating offer. The offer_paths branch -// (invoice_node_id equals the final blinded_node_id on the arrival path) needs -// caller context and is not checked here. A present-but-nil offer_issuer_id or -// invoice_node_id is rejected separately as ErrNilPublicKey, so a nil here is -// treated as absent. -func checkInvoiceNodeID(inv *Invoice) error { - // A present-but-nil offer_issuer_id is rejected separately as - // ErrNilPublicKey, so a nil here means absent and there is nothing to - // check. - issuerID := inv.OfferIssuerID.ValOpt().UnwrapOr(nil) - if issuerID == nil { - return nil - } - - // invoice_node_id is likewise guarded against present-but-nil by - // checkPubKeyNotNil, so a nil here means absent; its required presence - // is enforced separately as ErrMissingNodeID. - nodeID := inv.InvoiceNodeID.ValOpt().UnwrapOr(nil) - if nodeID == nil || !nodeID.IsEqual(issuerID) { - return ErrInvoiceNodeIDMismatch - } - - return nil -} - -// ValidateInvoiceWrite validates an invoice per the BOLT 12 invoice writer -// requirements. The checks follow the spec's writer section in order. -// Requirements that depend on context this codec layer does not have -// (signing, the payment preimage, the offer or path the request arrived on) -// are noted inline as deferred to the caller or to a paired validator. -func ValidateInvoiceWrite(inv *Invoice) error { - // - MUST set invoice_created_at to the number of seconds since Midnight - // 1 January 1970, UTC when the invoice was created. - if !inv.InvoiceCreatedAt.IsSome() { - return ErrMissingCreatedAt - } - - // - 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.InvoiceAmount.IsSome() { - return ErrMissingAmount - } - - // Policy extension: reject zero invoice_amount. The spec permits it - // ("minimum amount it will accept"), but a zero-amount HTLC cannot - // settle past the channel-layer dust limit. The typed - // ErrZeroInvoiceAmount lets a spec-strict caller distinguish this from - // a missing-field violation. Symmetric with ValidateInvoiceRead. - if inv.InvoiceAmount.ValOpt().UnwrapOr(0) == 0 { - return ErrZeroInvoiceAmount - } - - // - if the invoice is in response to an invoice_request: - // - MUST copy all non-signature fields from the invoice request - // (including unknown fields). - // - if invreq_amount is present: MUST set invoice_amount to - // invreq_amount. - // - otherwise: MUST set invoice_amount to the expected amount. - // NOT CHECKED HERE: the copy is performed by NewInvoiceFromRequest and - // this validator runs on the assembled struct. The invoice_amount == - // invreq_amount equality and the byte-for-byte field mirror are - // enforced when the invoice is paired with its request in - // ValidateInvoiceAgainstRequest. The offer_currency "expected amount" - // needs a live exchange rate the codec cannot compute. - - // - MUST set invoice_payment_hash to the SHA256 hash of the - // payment_preimage that will be given in return for payment. - // NOT CHECKED HERE beyond presence: relating the hash to the preimage - // needs the preimage, which lives with the caller's logic. - if !inv.InvoicePaymentHash.IsSome() { - return ErrMissingPaymentHash - } - - // - if offer_issuer_id is present: MUST set invoice_node_id to - // offer_issuer_id. - // - otherwise, if offer_paths is present: MUST set invoice_node_id to - // the final blinded_node_id on the path the request arrived on. - // The offer_issuer_id case is enforced by checkInvoiceNodeID since both - // fields live on the invoice. The offer_paths case needs the blinded - // arrival path, which is caller context, so only presence is checked - // for it. - // - // A present-but-nil pubkey passes IsSome but would panic the codec on - // encode, so reject it before the presence check. - if err := checkPubKeyNotNil( - inv.InvoiceNodeID, "invoice_node_id", - ); err != nil { - return err - } - if !inv.InvoiceNodeID.IsSome() { - return ErrMissingNodeID - } - if err := checkInvoiceNodeID(inv); err != nil { - return err - } - - // - MUST specify exactly one signature TLV element: signature. - // - MUST set sig to the signature using invoice_node_id as described - // in Signature Calculation. - // NOT CHECKED HERE: signing happens after this validator runs. The - // string-codec layer rejects an unsigned invoice, mirroring - // ValidateInvoiceRequestWrite. - - // - 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 should not be attempted. - // NOT CHECKED HERE: the writer chooses the expiry, so there is no rule - // to enforce on the encoded value. The time comparison needs a clock - // (see ValidateInvoiceExpiry). - - // - if it accepts onchain payments: - // - MAY specify invoice_fallbacks. - // - SHOULD specify invoice_fallbacks in order of most-preferred to - // least-preferred if it has a preference. - // - for the bitcoin chain, it MUST set each fallback_address with - // version as a valid witness version and address as a valid witness - // program. - // NOT CHECKED HERE: the codec stays permissive so callers can inspect - // raw fallbacks. The spec's ignore semantics are applied on the read - // side by UsableFallbackAddresses. - - // - 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. - if !inv.InvoicePaths.IsSome() { - return ErrMissingPaths - } - - // Writer mirror of the reader rule rejecting a blinded_path with zero - // hops. - if err := checkBlindedPaths(inv.InvoicePaths); err != nil { - return err - } - - // - MUST include invoice_blindedpay with exactly one blinded_payinfo - // for each blinded_path in paths, in order. - // - MUST set features in each blinded_payinfo to match - // encrypted_data_tlv.allowed_features (or empty, if no - // allowed_features). - // NOT CHECKED HERE: matching each payinfo.features to its path's - // encrypted_data_tlv allowed_features needs the decrypted path, which - // is caller context. Only the 1:1 count is enforced below. - bp, err := inv.InvoiceBlindedPay.ValOpt().UnwrapOrErr( - ErrMissingBlindedPay, - ) - if err != nil { - return err - } - - // invoice_paths presence is enforced above, so the default is never the - // value used; UnwrapOr just avoids a second WhenSome. - paths := inv.InvoicePaths.ValOpt().UnwrapOr(lnwire.BlindedPaths{}) - if len(paths.Paths) != len(bp.Infos) { - return ErrBlindedPayMismatch - } - - // A present-but-nil pubkey passes IsSome but would panic the codec on - // encode, so reject the mirrored pubkey fields. Symmetric with - // ValidateInvoiceRequestWrite. - if err := fn.MapOptionZ(inv.InvreqPayerID.ValOpt(), - func(pk *btcec.PublicKey) error { - if pk == nil { - return fmt.Errorf("%w: invreq_payer_id", - ErrNilPublicKey) - } - - return nil - }); err != nil { - return err - } - if err := fn.MapOptionZ(inv.OfferIssuerID.ValOpt(), - func(pk *btcec.PublicKey) error { - if pk == nil { - return fmt.Errorf("%w: offer_issuer_id", - ErrNilPublicKey) - } - - return nil - }); err != nil { - return err - } - - return nil -} - -// defaultInvoiceRelativeExpiry is the spec-defined fallback when an invoice -// omits invoice_relative_expiry: two hours from creation. -const defaultInvoiceRelativeExpiry uint32 = 7200 - -// ValidateInvoiceExpiry rejects an invoice whose effective expiry is strictly -// before now. The effective expiry is invoice_created_at + -// invoice_relative_expiry, falling back to a 7200-second default per spec when -// relative expiry is absent. Per the BOLT 12 reader the invoice is rejected -// only when the current time is greater than the expiry, so the boundary second -// itself is still valid; this matches the strict comparison ValidateOfferRead -// uses for offer_absolute_expiry. Callers must invoke this separately after -// decoding. ValidateInvoiceRead covers the structural reader requirements, but -// the time check needs a clock the codec library doesn't supply. -func ValidateInvoiceExpiry(inv *Invoice, now time.Time) error { - createdAt, err := inv.InvoiceCreatedAt.ValOpt().UnwrapOrErr( - ErrMissingCreatedAt, - ) - if err != nil { - return err - } - - relExpiry := inv.InvoiceRelativeExp.ValOpt().UnwrapOr( - TUint32(defaultInvoiceRelativeExpiry), - ) - - // invoice_created_at + the relative expiry can overflow uint64 for an - // absurd timestamp. The true sum then exceeds any real clock, so the - // invoice is not expired: detect the carry rather than wrapping to a - // small value that would spuriously read as expired. - expiry, carry := bits.Add64(uint64(createdAt), uint64(relExpiry), 0) - if carry == 0 && uint64(now.Unix()) > expiry { - return ErrInvoiceExpired - } - - return nil -} - -// mirroredRecordBytes encodes the records in the invreq mirror range to their -// canonical per-record bytes, keyed by TLV type. This is the view the -// byte-for-byte invreq->invoice comparison operates on. -func mirroredRecordBytes(records []tlv.Record) (map[tlv.Type][]byte, error) { - out := make(map[tlv.Type][]byte) - for i := range records { - r := records[i] - if !invreqAllowedRange(r.Type()) { - continue - } - buf, err := lnwire.EncodeRecords([]tlv.Record{r}) - if err != nil { - return nil, fmt.Errorf( - "encode record (type %d): %w", r.Type(), err, - ) - } - out[r.Type()] = buf - } - - return out, nil -} - -// ValidateInvoiceAgainstRequest performs a byte-for-byte comparison of the -// fields in ranges 0-159 and 1000000000-2999999999 between an invoice and its -// original request, as required by the BOLT 12 invoice reader specification. -// Callers must invoke this after pairing the invoice with its originating -// request. The codec library cannot reach across that pairing on its own. -// -// The comparison runs against the canonical per-record encoding from each -// side's AllRecords output. Two structs that decode to the same typed fields -// and the same ExtraSignedFields entries produce byte-identical encodings for -// any matching type. That is the byte-mirror invariant the spec demands. -// -// The amount cross-check enforces the spec's authorized-range rule: when -// invreq_amount is present, invoice_amount MUST equal it; otherwise the payer -// relied on the offer's fixed amount, so invoice_amount MUST be at least -// offer_amount * invreq_quantity for the native (bitcoin) case. The -// offer_currency case needs a caller-supplied exchange rate and is delegated to -// the caller. -func ValidateInvoiceAgainstRequest(inv *Invoice, req *InvoiceRequest) error { - reqFields, err := mirroredRecordBytes(req.AllRecords()) - if err != nil { - return fmt.Errorf("encode request fields: %w", err) - } - - invFields, err := mirroredRecordBytes(inv.AllRecords()) - if err != nil { - return fmt.Errorf("encode invoice fields: %w", err) - } - - for typ, invBytes := range invFields { - reqBytes, ok := reqFields[typ] - if !ok { - return fmt.Errorf("%w: invoice contains unexpected "+ - "field %d", ErrInvoiceMismatch, typ) - } - if !bytes.Equal(invBytes, reqBytes) { - return fmt.Errorf("%w: field %d data mismatch", - ErrInvoiceMismatch, typ) - } - delete(reqFields, typ) - } - - if len(reqFields) > 0 { - return fmt.Errorf("%w: invoice is missing %d fields from "+ - "request", ErrInvoiceMismatch, len(reqFields)) - } - - // Spec MUST: if invreq_amount (type 82) is present, invoice_amount - // (type 170) must equal it. The byte-mirror loop cannot relate fields - // with differing type numbers, so this cross-type equality is checked - // explicitly. - if req.InvreqAmount.IsSome() { - invreqAmt := req.InvreqAmount.ValOpt().UnwrapOr(0) - invAmt := inv.InvoiceAmount.ValOpt().UnwrapOr(0) - if invAmt != invreqAmt { - return fmt.Errorf("%w: invoice_amount %d != "+ - "invreq_amount %d", ErrInvoiceMismatch, invAmt, - invreqAmt) - } - - return nil - } - - // Spec SHOULD: with invreq_amount absent the payer relied on the - // offer's fixed amount, so confirm invoice_amount is within the - // authorized range. For the native (non-offer_currency) case that range - // is bounded below by offer_amount * invreq_quantity, computable here - // from the mirrored offer fields. The offer_currency case is delegated - // to the caller (see checkInvoiceAmountMeetsOffer). - return checkInvoiceAmountMeetsOffer(inv) -} - -// checkInvoiceAmountMeetsOffer confirms invoice_amount is at least the offer's -// authorized amount for the native (bitcoin) case, where the expected amount is -// offer_amount * invreq_quantity. It is a no-op when offer_amount is absent -// (there is nothing to bound against) or when offer_currency is present (the -// conversion into the invreq_chain currency needs a caller-supplied exchange -// rate, so the bound is delegated). This mirrors the request-side -// checkInvreqAmountMeetsOffer and is only meaningful when invreq_amount is -// absent, since a present invreq_amount pins invoice_amount by exact equality. -func checkInvoiceAmountMeetsOffer(inv *Invoice) error { - if !inv.OfferAmount.IsSome() { - return nil - } - - // NOT CHECKED HERE: the offer_currency (non-bitcoin) case. Caller MUST - // convert offer_amount to the invreq_chain currency and compare. - if inv.OfferCurrency.IsSome() { - return nil - } - - offerAmt := uint64(inv.OfferAmount.ValOpt().UnwrapOr(0)) - qty := uint64(inv.InvreqQuantity.ValOpt().UnwrapOr(1)) - invAmt := uint64(inv.InvoiceAmount.ValOpt().UnwrapOr(0)) - - // Guard against overflow of offer_amount * quantity. - hi, expectedAmt := bits.Mul64(offerAmt, qty) - if hi != 0 { - return fmt.Errorf("%w: offer_amount %d * quantity %d "+ - "overflows uint64", ErrAmountBelowExpected, offerAmt, - qty) - } - if invAmt < expectedAmt { - return fmt.Errorf("%w: invoice_amount %d below expected %d", - ErrAmountBelowExpected, invAmt, expectedAmt) - } - - return nil -} - -// isKnownInvoiceTLVType returns true for TLV types that are defined in the -// invoice spec. -func isKnownInvoiceTLVType(typ tlv.Type) bool { - if isKnownInvreqTLVType(typ) { - return true - } - - switch typ { - case invoicePathsType, invoiceBlindedPayType, invoiceCreatedAtType, - invoiceRelativeExpiryType, invoicePaymentHashType, - invoiceAmountType, invoiceFallbacksType, invoiceFeaturesType, - invoiceNodeIDType: - - return true - - default: - return false - } -} - -// InvoiceFeatureCatalogues names the two feature-bit catalogues the invoice -// reader validates against. They are grouped in a struct rather than passed as -// two positional map[lnwire.FeatureBit]string arguments because the identical -// types would otherwise let a caller transpose them silently: validating -// invoice_features against the blinded-path catalogue and vice versa compiles -// cleanly but misvalidates. Named fields make the swap impossible. -type InvoiceFeatureCatalogues struct { - // Invoice names the feature bits the reader understands for the - // top-level invoice_features field. - Invoice map[lnwire.FeatureBit]string - - // Blinded names the feature bits the reader understands for each - // blinded_payinfo.features field carried in invoice_blindedpay. - Blinded map[lnwire.FeatureBit]string -} - -// ValidateInvoiceRead validates an invoice against the BOLT 12 reader -// requirements, running the stateless structural checks against activeChain -// (the chain the reader supports). -// -// Note: This only performs stateless structural checks. Cryptographic Schnorr -// signature verification and identity-path binding are deferred to the caller -// (see the TODO at the end of this function). Additionally, while it verifies -// that at least one usable path is present, downstream callers must re-apply -// the same features.Blinded filter at path selection time (via -// Invoice.UsablePaths) to avoid selecting paths with unknown required features. -func ValidateInvoiceRead(inv *Invoice, activeChain [32]byte, - features InvoiceFeatureCatalogues) error { - // - MUST reject the invoice if invoice_amount is not present. - if !inv.InvoiceAmount.IsSome() { - return ErrMissingAmount - } - - // Policy extension. See ValidateInvoiceWrite. - if inv.InvoiceAmount.ValOpt().UnwrapOr(0) == 0 { - return ErrZeroInvoiceAmount - } - - // - MUST reject the invoice if invoice_created_at is not present. - if !inv.InvoiceCreatedAt.IsSome() { - return ErrMissingCreatedAt - } - - // - MUST reject the invoice if invoice_payment_hash is not present. - if !inv.InvoicePaymentHash.IsSome() { - return ErrMissingPaymentHash - } - - // - MUST reject the invoice if invoice_node_id is not present. A - // present-but-nil pubkey passes IsSome but would panic the codec, so - // reject it before the presence check. - if err := checkPubKeyNotNil( - inv.InvoiceNodeID, "invoice_node_id", - ); err != nil { - return err - } - if !inv.InvoiceNodeID.IsSome() { - return ErrMissingNodeID - } - - // - if invreq_chain is not present: - // - MUST reject the invoice if bitcoin is not a supported chain. - // - otherwise: - // - MUST reject the invoice if invreq_chain.chain is not a supported - // chain. - // invreq_chain defaults to bitcoin mainnet when absent. activeChain is - // the chain the reader supports. - chain := inv.InvreqChain.ValOpt().UnwrapOr(bitcoinMainnetGenesisHash) - if chain != activeChain { - return ErrUnsupportedChain - } - - // - 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. - // checkFeatures enforces those invoice_features bit rules below. - // - // Separately, BOLT 1 makes unknown even TLV types must-understand, so - // reject those here over the decoded type set. Unlike the - // invoice_request reader, the invoice reader defines no out-of-range - // type rejection, so unknown odd types are simply ignored ("it's ok to - // be odd"). The signature range (240-1000) is exempt for the same - // reason, matching the invoice_request reader and the Merkle path. - for _, t := range sortedTypes(inv.decodedTLVs) { - if bolt12InUnsignedRange(t) { - continue - } - if !isKnownInvoiceTLVType(t) && t%2 == 0 { - return fmt.Errorf("%w: type %d", ErrUnknownEvenType, t) - } - } - err := checkFeatures(inv.InvoiceFeatures, features.Invoice) - if err != nil { - return err - } - - // - 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. - // NOT CHECKED HERE: the comparison needs a clock the codec doesn't - // supply. Callers run ValidateInvoiceExpiry separately. - - // - MUST reject the invoice if invoice_paths is not present or is - // empty. - if !inv.InvoicePaths.IsSome() { - return ErrMissingPaths - } - - // - MUST reject the invoice if num_hops is 0 in any blinded_path in - // invoice_paths (checkBlindedPaths also rejects an empty path list). - if err := checkBlindedPaths(inv.InvoicePaths); err != nil { - return err - } - - // - MUST reject the invoice if invoice_blindedpay is not present. - bp, err := inv.InvoiceBlindedPay.ValOpt().UnwrapOrErr( - ErrMissingBlindedPay, - ) - if err != nil { - return err - } - - // - MUST reject the invoice if invoice_blindedpay does not contain - // exactly one blinded_payinfo per invoice_paths.blinded_path. - paths := inv.InvoicePaths.ValOpt().UnwrapOr(lnwire.BlindedPaths{}) - if len(paths.Paths) != len(bp.Infos) { - return ErrBlindedPayMismatch - } - - // - 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. - // UsablePaths applies that filter; a caller selecting a path downstream - // should use it rather than the unfiltered invoice_paths. - if len(inv.UsablePaths(features.Blinded)) == 0 { - return ErrNoUsablePaths - } - - // - if the invoice is a response to an invoice_request: - // - MUST reject the invoice if all fields in ranges 0 to 159 and - // 1000000000 to 2999999999 (inclusive) do not exactly match the - // invoice request. - // - if offer_issuer_id is present: MUST reject the invoice if - // invoice_node_id is not equal to offer_issuer_id. - // - otherwise, if offer_paths is present: MUST reject the invoice if - // invoice_node_id is not equal to the final blinded_node_id it sent - // the invoice request to. - // The offer_issuer_id case is checked here by checkInvoiceNodeID (both - // fields live on the invoice). NOT CHECKED HERE: the byte-for-byte - // field mirror and the invreq_amount == invoice_amount rule are - // enforced by ValidateInvoiceAgainstRequest once the invoice is paired - // with its request; the offer_paths blinded_node_id case needs the - // arrival path and stays with the caller. - if err := checkInvoiceNodeID(inv); err != nil { - return err - } - - // - MUST reject the invoice if signature is not a valid signature using - // invoice_node_id as described in Signature Calculation. - // TODO(bolt12): implement signature verification. For now only - // presence is enforced, mirroring ValidateInvoiceRequestRead. - if !inv.Signature.IsSome() { - return ErrMissingSignature - } - - // - SHOULD prefer to use earlier invoice_paths over later ones if it - // has no other reason for preference. - // - if invoice_features contains the MPP/compulsory bit: MUST pay - // via multiple separate blinded paths; the MPP/optional bit MAY, - // otherwise MUST NOT use multiple parts. - // - if invreq_amount is present: MUST reject the invoice if - // invoice_amount is not equal to invreq_amount (otherwise SHOULD - // confirm invoice_amount.msat is within the authorized range). - // - for the bitcoin chain, if the invoice specifies invoice_fallbacks: - // - MUST ignore any fallback_address with version greater than 16, - // address shorter than 2 or longer than 40 bytes, or an address that - // does not meet known requirements for the given version. - // - the invreq_paths / blinded-path / reply_path arrival rules. - // NOT CHECKED HERE: these are payment-time or transport concerns - // handled outside this codec. invreq_amount equality is enforced by - // ValidateInvoiceAgainstRequest; the fallback ignore rules by - // UsableFallbackAddresses. - - return nil -} diff --git a/bolt12/validate_test.go b/bolt12/validate_test.go deleted file mode 100644 index f0650e6d6..000000000 --- a/bolt12/validate_test.go +++ /dev/null @@ -1,2776 +0,0 @@ -package bolt12 - -import ( - "math" - "testing" - "time" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/tlv" - "github.com/stretchr/testify/require" -) - -// validBobOffer is the spec-minimal happy-path offer that each table row -// mutates to isolate the rule under test. -func validBobOffer(t *testing.T) *Offer { - t.Helper() - - _, pub := bobKey() - - return &Offer{ - OfferIssuerID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType22](pub), - ), - } -} - -// TestValidateOfferWrite pins the BOLT 12 writer-side MUSTs that the codec can -// enforce. -func TestValidateOfferWrite(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - mutate func(*Offer) - wantErr error - }{ - { - name: "happy path with issuer_id only", - mutate: func(*Offer) {}, - wantErr: nil, - }, - { - name: "amount without description", - mutate: func(o *Offer) { - o.OfferAmount = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType8]( - TUint64(1000), - ), - ) - }, - wantErr: ErrMissingDescription, - }, - { - name: "currency without amount", - mutate: func(o *Offer) { - o.OfferCurrency = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType6]( - tlv.Blob("USD"), - ), - ) - }, - wantErr: ErrCurrencyWithoutAmount, - }, - { - name: "zero amount with description", - mutate: func(o *Offer) { - o.OfferAmount = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType8]( - TUint64(0), - ), - ) - o.OfferDescription = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType10]( - tlv.Blob("a tip"), - ), - ) - }, - wantErr: ErrZeroAmount, - }, - { - name: "no issuer or paths", - mutate: func(o *Offer) { - o.OfferIssuerID = tlv.OptionalRecordT[ - tlv.TlvType22, *btcec.PublicKey]{} - }, - wantErr: ErrNoIssuerIdentity, - }, - { - name: "present-but-nil issuer_id", - mutate: func(o *Offer) { - o.OfferIssuerID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType22]( - (*btcec.PublicKey)(nil), - ), - ) - }, - wantErr: ErrNilPublicKey, - }, - { - name: "empty offer_chains", - mutate: func(o *Offer) { - o.OfferChains = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType2]( - ChainsRecord{ - Chains: nil, - }, - ), - ) - }, - wantErr: ErrEmptyChains, - }, - { - name: "currency wrong length", - mutate: func(o *Offer) { - addAmountAndDescription(o) - o.OfferCurrency = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType6]( - tlv.Blob("US"), - ), - ) - }, - wantErr: ErrInvalidCurrency, - }, - { - name: "currency unknown ISO 4217 code", - mutate: func(o *Offer) { - addAmountAndDescription(o) - o.OfferCurrency = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType6]( - tlv.Blob("ZZZ"), - ), - ) - }, - wantErr: ErrInvalidCurrency, - }, - { - // Pins the docstring claim that ValidateOfferWrite's - // offerAllowedRange loop exists to catch a - // decoded-then-mutated offer with an out-of-range TLV - // resurfacing via decodedTLVs. - name: "out-of-range TLV in decoded extras", - mutate: func(o *Offer) { - o.decodedTLVs = tlv.TypeMap{ - 200: nil, - } - }, - wantErr: ErrOutOfRangeType, - }, - { - name: "empty blinded paths list", - mutate: func(o *Offer) { - o.OfferPaths = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType16]( - lnwire.BlindedPaths{ - Paths: nil, - }, - ), - ) - }, - wantErr: ErrEmptyBlindedPaths, - }, - { - name: "blinded path with zero hops", - mutate: func(o *Offer) { - _, intro := aliceKey() - _, blinding := bobKey() - pk := lnwire.PubkeyIntro{ - Pubkey: intro, - } - o.OfferPaths = tlv.SomeRecordT( - //nolint:ll - tlv.NewRecordT[tlv.TlvType16]( - lnwire.BlindedPaths{ - Paths: []lnwire.BlindedPath{{ - IntroductionNode: pk, - BlindingPoint: blinding, - Hops: nil, - }}, - }, - ), - ) - }, - wantErr: lnwire.ErrEmptyBlindedPath, - }, - { - name: "invalid UTF-8 in description", - mutate: func(o *Offer) { - addAmountAndDescription(o) - o.OfferDescription = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType10]( - tlv.Blob("\xff\xff"), - ), - ) - }, - wantErr: ErrInvalidUTF8, - }, - { - name: "invalid UTF-8 in issuer", - mutate: func(o *Offer) { - o.OfferIssuer = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType18]( - tlv.Blob("\xff\x00"), - ), - ) - }, - wantErr: ErrInvalidUTF8, - }, - } - - for _, tc := range tests { - t.Run( - tc.name, - func(t *testing.T) { - t.Parallel() - - o := validBobOffer(t) - tc.mutate(o) - - err := ValidateOfferWrite(o) - if tc.wantErr == nil { - require.NoError(t, err) - - return - } - require.ErrorIs(t, err, tc.wantErr) - }, - ) - } -} - -// TestValidateOfferRead pins the BOLT 12 reader-side MUSTs so a malformed or -// unsafe offer is rejected before any invoice request reaches the wire. -func TestValidateOfferRead(t *testing.T) { - t.Parallel() - - now := time.Unix(1_700_000_000, 0) - - var nonBitcoin [32]byte - nonBitcoin[0] = 0x01 - - tests := []struct { - name string - mutate func(*Offer) - activeChain [32]byte - known map[lnwire.FeatureBit]string - wantErr error - }{ - { - name: "happy path on bitcoin mainnet", - mutate: func(*Offer) {}, - activeChain: bitcoinMainnetGenesisHash, - }, - { - name: "present-but-nil offer_issuer_id", - mutate: func(o *Offer) { - o.OfferIssuerID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType22]( - (*btcec.PublicKey)(nil), - ), - ) - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: ErrNilPublicKey, - }, - { - name: "out-of-range TLV in decoded extras", - mutate: func(o *Offer) { - o.decodedTLVs = tlv.TypeMap{ - 200: nil, - } - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: ErrOutOfRangeType, - }, - { - name: "unknown even TLV type in range rejected", - mutate: func(o *Offer) { - o.decodedTLVs = tlv.TypeMap{ - 24: nil, - } - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: ErrUnknownEvenType, - }, - { - name: "unknown even feature bit rejected", - mutate: func(o *Offer) { - o.OfferFeatures = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType12]( - *lnwire.NewRawFeatureVector(0), - ), - ) - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: ErrUnknownEvenFeature, - }, - { - name: "unknown odd feature bit ignored", - mutate: func(o *Offer) { - o.OfferFeatures = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType12]( - *lnwire.NewRawFeatureVector(1), - ), - ) - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: nil, - }, - { - name: "non-bitcoin chain rejected when " + - "offer_chains absent", - mutate: func(*Offer) {}, - activeChain: nonBitcoin, - wantErr: ErrUnsupportedChain, - }, - { - name: "explicit chain list missing active chain", - mutate: func(o *Offer) { - var c [32]byte - c[0] = 0xaa - o.OfferChains = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType2]( - ChainsRecord{ - Chains: [][32]byte{c}, - }, - ), - ) - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: ErrUnsupportedChain, - }, - { - name: "empty offer_chains list", - mutate: func(o *Offer) { - o.OfferChains = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType2]( - ChainsRecord{ - Chains: nil, - }, - ), - ) - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: ErrEmptyChains, - }, - { - name: "amount without description", - mutate: func(o *Offer) { - o.OfferAmount = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType8]( - TUint64(1000), - ), - ) - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: ErrMissingDescription, - }, - { - name: "currency without amount", - mutate: func(o *Offer) { - o.OfferCurrency = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType6]( - tlv.Blob("USD"), - ), - ) - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: ErrCurrencyWithoutAmount, - }, - { - name: "zero amount with description", - mutate: func(o *Offer) { - o.OfferAmount = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType8]( - TUint64(0), - ), - ) - o.OfferDescription = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType10]( - tlv.Blob("a tip"), - ), - ) - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: ErrZeroAmount, - }, - { - name: "missing issuer and paths", - mutate: func(o *Offer) { - o.OfferIssuerID = tlv.OptionalRecordT[ - tlv.TlvType22, *btcec.PublicKey]{} - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: ErrNoIssuerIdentity, - }, - { - name: "blinded path with zero hops", - mutate: func(o *Offer) { - _, intro := aliceKey() - _, blinding := bobKey() - pk := lnwire.PubkeyIntro{ - Pubkey: intro, - } - o.OfferPaths = tlv.SomeRecordT( - //nolint:ll - tlv.NewRecordT[tlv.TlvType16]( - lnwire.BlindedPaths{ - Paths: []lnwire.BlindedPath{{ - IntroductionNode: pk, - BlindingPoint: blinding, - Hops: nil, - }}, - }, - ), - ) - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: lnwire.ErrEmptyBlindedPath, - }, - { - name: "expired offer", - mutate: func(o *Offer) { - expiry := uint64(now.Unix()) - 1 - o.OfferAbsoluteExpiry = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType14]( - TUint64(expiry), - ), - ) - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: ErrOfferExpired, - }, - { - name: "currency wrong length", - mutate: func(o *Offer) { - addAmountAndDescription(o) - o.OfferCurrency = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType6]( - tlv.Blob("US"), - ), - ) - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: ErrInvalidCurrency, - }, - { - name: "TLV type boundary 0 - out of range", - mutate: func(o *Offer) { - o.decodedTLVs = tlv.TypeMap{ - 0: nil, - } - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: ErrOutOfRangeType, - }, - { - name: "TLV type boundary 1 - valid and ignored (odd)", - mutate: func(o *Offer) { - o.decodedTLVs = tlv.TypeMap{ - 1: nil, - } - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: nil, - }, - { - name: "TLV type boundary 79 - valid and ignored (odd)", - mutate: func(o *Offer) { - o.decodedTLVs = tlv.TypeMap{ - 79: nil, - } - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: nil, - }, - { - name: "TLV type boundary 80 - out of range", - mutate: func(o *Offer) { - o.decodedTLVs = tlv.TypeMap{ - 80: nil, - } - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: ErrOutOfRangeType, - }, - { - name: "TLV type boundary 999999999 - out of range", - mutate: func(o *Offer) { - o.decodedTLVs = tlv.TypeMap{ - 999999999: nil, - } - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: ErrOutOfRangeType, - }, - { - name: "TLV type boundary 1000000000 - even and " + - "rejected", - mutate: func(o *Offer) { - o.decodedTLVs = tlv.TypeMap{ - 1000000000: nil, - } - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: ErrUnknownEvenType, - }, - { - name: "TLV type boundary 1000000001 - valid and " + - "ignored (odd)", - mutate: func(o *Offer) { - o.decodedTLVs = tlv.TypeMap{ - 1000000001: nil, - } - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: nil, - }, - { - name: "TLV type boundary 1999999999 - valid and " + - "ignored (odd)", - mutate: func(o *Offer) { - o.decodedTLVs = tlv.TypeMap{ - 1999999999: nil, - } - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: nil, - }, - { - name: "TLV type boundary 2000000000 - out of range", - mutate: func(o *Offer) { - o.decodedTLVs = tlv.TypeMap{ - 2000000000: nil, - } - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: ErrOutOfRangeType, - }, - { - name: "invalid UTF-8 in description", - mutate: func(o *Offer) { - addAmountAndDescription(o) - o.OfferDescription = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType10]( - tlv.Blob("\xff\xff"), - ), - ) - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: ErrInvalidUTF8, - }, - { - name: "invalid UTF-8 in issuer", - mutate: func(o *Offer) { - o.OfferIssuer = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType18]( - tlv.Blob("\xff\x00"), - ), - ) - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: ErrInvalidUTF8, - }, - { - name: "quantity max = 0 (unlimited)", - mutate: func(o *Offer) { - o.OfferQuantityMax = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType20]( - TUint64(0), - ), - ) - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: nil, - }, - { - name: "quantity max > 0 (e.g. 5)", - mutate: func(o *Offer) { - o.OfferQuantityMax = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType20]( - TUint64(5), - ), - ) - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: nil, - }, - { - name: "now == expiry boundary (valid)", - mutate: func(o *Offer) { - expiry := uint64(now.Unix()) - o.OfferAbsoluteExpiry = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType14]( - TUint64(expiry), - ), - ) - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: nil, - }, - { - name: "symmetric explicit bitcoin chain list " + - "(inverted-default invariant)", - mutate: func(o *Offer) { - o.OfferChains = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType2]( - ChainsRecord{ - //nolint:ll - Chains: [][32]byte{ - bitcoinMainnetGenesisHash, - }, - }, - ), - ) - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: nil, - }, - { - name: "multi-error determinism (sortedTypes contract " + - "returning first sorted error)", - mutate: func(o *Offer) { - // 24 is unknown even type (in range) -> returns - // ErrUnknownEvenType 200 is out of range type - // -> returns ErrOutOfRangeType Since 24 is - // sorted before 200, we must return - // ErrUnknownEvenType. - o.decodedTLVs = tlv.TypeMap{ - 200: nil, - 24: nil, - } - }, - activeChain: bitcoinMainnetGenesisHash, - wantErr: ErrUnknownEvenType, - }, - { - name: "known even feature bit accepted", - mutate: func(o *Offer) { - o.OfferFeatures = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType12]( - *lnwire.NewRawFeatureVector(0), - ), - ) - }, - activeChain: bitcoinMainnetGenesisHash, - known: map[lnwire.FeatureBit]string{ - 0: "test_feature", - }, - wantErr: nil, - }, - } - - for _, tc := range tests { - t.Run( - tc.name, - func(t *testing.T) { - t.Parallel() - - o := validBobOffer(t) - tc.mutate(o) - - err := ValidateOfferRead( - o, now, tc.activeChain, tc.known, - ) - if tc.wantErr == nil { - require.NoError(t, err) - - return - } - require.ErrorIs(t, err, tc.wantErr) - }, - ) - } -} - -// addAmountAndDescription satisfies the dependency rules so currency-shape rows -// are not short-circuited before the ISO 4217 check runs. -func addAmountAndDescription(o *Offer) { - o.OfferAmount = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType8]( - TUint64(1000), - ), - ) - o.OfferDescription = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType10]( - tlv.Blob("a tip"), - ), - ) -} - -// validInvoiceRequest is the spec-minimal happy-path invoice request that -// each table row mutates to isolate the rule under test. -func validInvoiceRequest(t *testing.T) *InvoiceRequest { - t.Helper() - - ir := &InvoiceRequest{} - - privKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - ir.InvreqPayerID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType88](privKey.PubKey()), - ) - - ir.InvreqMetadata = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType0]( - []byte("metadata"), - ), - ) - - ir.InvreqAmount = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType82, TUint64](1000), - ) - - ir.Signature = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType240]([64]byte{0x01}), - ) - - return ir -} - -// TestValidateInvoiceRequestWrite pins the BOLT 12 writer-side MUSTs so a -// malformed or incomplete invoice request is rejected. -func TestValidateInvoiceRequestWrite(t *testing.T) { - t.Parallel() - - privKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - payerID := privKey.PubKey() - - tests := []struct { - name string - mutate func(*InvoiceRequest) - wantErr error - }{ - { - name: "missing payer_id", - mutate: func(ir *InvoiceRequest) { - ir.InvreqPayerID = tlv.OptionalRecordT[ - tlv.TlvType88, *btcec.PublicKey]{} - }, - wantErr: ErrMissingPayerID, - }, - { - name: "present-but-nil payer_id", - mutate: func(ir *InvoiceRequest) { - ir.InvreqPayerID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType88]( - (*btcec.PublicKey)(nil), - ), - ) - }, - wantErr: ErrNilPublicKey, - }, - { - name: "present-but-nil offer_issuer_id", - mutate: func(ir *InvoiceRequest) { - ir.OfferIssuerID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType22]( - (*btcec.PublicKey)(nil), - ), - ) - }, - wantErr: ErrNilPublicKey, - }, - { - name: "missing description", - mutate: func(ir *InvoiceRequest) { - ir.OfferDescription = tlv.OptionalRecordT[ - tlv.TlvType10, tlv.Blob]{} - }, - wantErr: ErrMissingDescription, - }, - { - name: "missing metadata", - mutate: func(ir *InvoiceRequest) { - ir.InvreqMetadata = tlv.OptionalRecordT[ - tlv.TlvType0, tlv.Blob]{} - }, - wantErr: ErrMissingMetadata, - }, - { - name: "missing amount", - mutate: func(ir *InvoiceRequest) { - ir.InvreqAmount = tlv.OptionalRecordT[ - tlv.TlvType82, TUint64]{} - }, - wantErr: ErrMissingAmount, - }, - { - name: "invalid UTF-8 in payer_note", - mutate: func(ir *InvoiceRequest) { - ir.InvreqPayerNote = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType89]( - []byte{0xff}, - ), - ) - }, - wantErr: ErrInvalidUTF8, - }, - { - name: "empty blinded paths", - mutate: func(ir *InvoiceRequest) { - ir.InvreqPaths = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType90]( - lnwire.BlindedPaths{Paths: nil}, - ), - ) - }, - wantErr: ErrEmptyBlindedPaths, - }, - { - name: "happy path", - mutate: func(*InvoiceRequest) {}, - }, - { - name: "spontaneous request carrying quantity", - mutate: func(ir *InvoiceRequest) { - ir.InvreqQuantity = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType86]( - TUint64(1), - ), - ) - }, - wantErr: ErrQuantityWithoutMax, - }, - { - name: "spontaneous request offer quantity max", - mutate: func(ir *InvoiceRequest) { - ir.OfferQuantityMax = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType20]( - TUint64(10), - ), - ) - }, - wantErr: ErrOfferFieldsOnSpontaneous, - }, - { - name: "spontaneous request offer quantity max dup", - mutate: func(ir *InvoiceRequest) { - ir.OfferQuantityMax = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType20]( - TUint64(10), - ), - ) - }, - wantErr: ErrOfferFieldsOnSpontaneous, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - ir := &InvoiceRequest{ - OfferDescription: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType10]( - tlv.Blob("description"), - ), - ), - InvreqPayerID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType88]( - payerID, - ), - ), - InvreqMetadata: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType0]( - []byte("metadata"), - ), - ), - InvreqAmount: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType82, TUint64]( - 1000, - ), - ), - } - - tc.mutate(ir) - - err := ValidateInvoiceRequestWrite(ir) - if tc.wantErr == nil { - require.NoError(t, err) - return - } - require.ErrorIs(t, err, tc.wantErr) - }) - } -} - -// TestValidateInvoiceRequestWriteAmountConstraints tests the writer constraints -// on invreq_amount. -func TestValidateInvoiceRequestWriteAmountConstraints(t *testing.T) { - t.Parallel() - - privKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - baseRequest := func() *InvoiceRequest { - return &InvoiceRequest{ - OfferDescription: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType10]( - tlv.Blob("description"), - ), - ), - InvreqPayerID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType88]( - privKey.PubKey(), - ), - ), - InvreqMetadata: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType0]( - []byte("metadata"), - ), - ), - } - } - - // 1. Spontaneous request (not responding to an offer): - // - MUST set invreq_amount. - t.Run("spontaneous_amount_required", func(t *testing.T) { - ir := baseRequest() - - // Absent invreq_amount -> invalid. - err := ValidateInvoiceRequestWrite(ir) - require.ErrorIs(t, err, ErrMissingAmount) - - // Present invreq_amount -> valid. - ir.InvreqAmount = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType82, TUint64](1000), - ) - require.NoError(t, ValidateInvoiceRequestWrite(ir)) - }) - - // 2. Responding to an offer. - t.Run("response_to_offer", func(t *testing.T) { - baseResponseRequest := func() *InvoiceRequest { - ir := baseRequest() - ir.OfferIssuerID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType22]( - privKey.PubKey(), - ), - ) - - return ir - } - - // Case A: OfferAmount is absent. - // - MUST specify invreq_amount. - t.Run("offer_amount_absent", func(t *testing.T) { - ir := baseResponseRequest() - - // InvreqAmount absent -> invalid. - err := ValidateInvoiceRequestWrite(ir) - require.ErrorIs(t, err, ErrMissingAmount) - - // InvreqAmount present -> valid. - ir.InvreqAmount = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType82, TUint64](1000), - ) - require.NoError(t, ValidateInvoiceRequestWrite(ir)) - }) - - // Case B: OfferAmount present, OfferCurrency absent (Bitcoin). - t.Run("offer_amount_present_bitcoin", func(t *testing.T) { - ir := baseResponseRequest() - ir.OfferAmount = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType8, TUint64](1000), - ) - ir.OfferQuantityMax = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType20](TUint64(10)), - ) - ir.InvreqQuantity = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType86, TUint64](2), - ) - - // InvreqAmount is optional (MAY omit it). - require.NoError(t, ValidateInvoiceRequestWrite(ir)) - - // If set, it MUST be >= OfferAmount * Quantity - // (1000 * 2 = 2000). InvreqAmount < expected -> - // invalid. - ir.InvreqAmount = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType82, TUint64](1999), - ) - err := ValidateInvoiceRequestWrite(ir) - require.ErrorIs(t, err, ErrAmountBelowExpected) - - // InvreqAmount >= expected -> valid. - ir.InvreqAmount = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType82, TUint64](2000), - ) - require.NoError(t, ValidateInvoiceRequestWrite(ir)) - }) - - // Case C: OfferAmount present, OfferCurrency present - // (non-Bitcoin). - t.Run("offer_amount_present_non_bitcoin", func(t *testing.T) { - ir := baseResponseRequest() - ir.OfferAmount = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType8, TUint64](1000), - ) - ir.OfferCurrency = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType6]( - tlv.Blob("USD"), - ), - ) - ir.OfferQuantityMax = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType20](TUint64(10)), - ) - ir.InvreqQuantity = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType86, TUint64](2), - ) - - // InvreqAmount < OfferAmount * Quantity is allowed - // because currency conversion is checked dynamically - // at runtime, not statically inside - // ValidateInvoiceRequestWrite. - ir.InvreqAmount = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType82, TUint64](100), - ) - require.NoError(t, ValidateInvoiceRequestWrite(ir)) - }) - }) -} - -// TestValidateInvoiceRequestWriteChainConstraints tests the writer constraints -// on invreq_chain. -func TestValidateInvoiceRequestWriteChainConstraints(t *testing.T) { - t.Parallel() - - privKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - testnetHash := [32]byte{1} - regtestHash := [32]byte{2} - - // 1. Not responding to an offer: any invreq_chain is accepted. - t.Run("spontaneous_any_chain_accepted", func(t *testing.T) { - ir := &InvoiceRequest{ - OfferDescription: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType10]( - tlv.Blob("description"), - ), - ), - InvreqPayerID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType88]( - privKey.PubKey(), - ), - ), - InvreqMetadata: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType0]( - []byte("metadata"), - ), - ), - InvreqAmount: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType82, TUint64](1000), - ), - } - - // Absent chain is OK. - require.NoError(t, ValidateInvoiceRequestWrite(ir)) - - // Bitcoin chain is OK. - ir.InvreqChain = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType80]( - bitcoinMainnetGenesisHash, - ), - ) - require.NoError(t, ValidateInvoiceRequestWrite(ir)) - - // Non-bitcoin chain is OK. - ir.InvreqChain = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType80](testnetHash), - ) - require.NoError(t, ValidateInvoiceRequestWrite(ir)) - }) - - // 2. Responding to an offer. - t.Run("response_to_offer", func(t *testing.T) { - baseRequest := func() *InvoiceRequest { - return &InvoiceRequest{ - OfferIssuerID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType22]( - privKey.PubKey(), - ), - ), - InvreqPayerID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType88]( - privKey.PubKey(), - ), - ), - InvreqMetadata: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType0]( - []byte("metadata"), - ), - ), - InvreqAmount: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType82, TUint64]( - 1000, - ), - ), - } - } - - // Case A: OfferChains is absent (defaults to Bitcoin mainnet). - t.Run("offer_chains_absent", func(t *testing.T) { - ir := baseRequest() - - // InvreqChain absent (valid, defaults to bitcoin). - require.NoError(t, ValidateInvoiceRequestWrite(ir)) - - // InvreqChain == bitcoin (valid). - ir.InvreqChain = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType80]( - bitcoinMainnetGenesisHash, - ), - ) - require.NoError(t, ValidateInvoiceRequestWrite(ir)) - - // InvreqChain != bitcoin (invalid). - ir.InvreqChain = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType80]( - testnetHash, - ), - ) - require.ErrorIs( - t, ValidateInvoiceRequestWrite(ir), - ErrUnsupportedChain, - ) - }) - - // Case B: OfferChains is present. - t.Run("offer_chains_present", func(t *testing.T) { - // Sub-case B1: OfferChains contains only Bitcoin. - ir := baseRequest() - ir.OfferChains = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType2](ChainsRecord{ - Chains: [][32]byte{ - bitcoinMainnetGenesisHash, - }, - }), - ) - - // InvreqChain absent (valid, defaults to bitcoin). - require.NoError(t, ValidateInvoiceRequestWrite(ir)) - - // InvreqChain == bitcoin (valid). - ir.InvreqChain = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType80]( - bitcoinMainnetGenesisHash, - ), - ) - require.NoError(t, ValidateInvoiceRequestWrite(ir)) - - // InvreqChain == testnet (invalid, not in offer - // chains). - ir.InvreqChain = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType80]( - testnetHash, - ), - ) - require.ErrorIs( - t, ValidateInvoiceRequestWrite(ir), - ErrUnsupportedChain, - ) - - // Sub-case B2: OfferChains contains only Testnet - // (not Bitcoin). - ir = baseRequest() - ir.OfferChains = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType2](ChainsRecord{ - Chains: [][32]byte{testnetHash}, - }), - ) - - // InvreqChain absent (invalid, defaults to bitcoin - // which is not in offer chains). - require.ErrorIs( - t, ValidateInvoiceRequestWrite(ir), - ErrUnsupportedChain, - ) - - // InvreqChain == testnet (valid, is in offer chains). - ir.InvreqChain = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType80]( - testnetHash, - ), - ) - require.NoError(t, ValidateInvoiceRequestWrite(ir)) - - // InvreqChain == regtest (invalid, not in offer - // chains). - ir.InvreqChain = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType80]( - regtestHash, - ), - ) - require.ErrorIs( - t, ValidateInvoiceRequestWrite(ir), - ErrUnsupportedChain, - ) - }) - }) -} - -// TestValidateInvoiceRequestReadSentinels table-drives selected reader-side -// validation branches in ValidateInvoiceRequestRead. Each row starts from a -// minimal structurally valid invoice request and mutates one condition to -// assert the corresponding sentinel error, or nil for accepted optional -// unknown fields. Additional reader-side checks such as amount, chain and -// BIP-353 validation are covered by focused tests below. -func TestValidateInvoiceRequestReadSentinels(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - mutate func(*InvoiceRequest) - known map[lnwire.FeatureBit]string - wantErr error - }{ - { - name: "missing payer id", - mutate: func(ir *InvoiceRequest) { - ir.InvreqPayerID = tlv.OptionalRecordT[ - tlv.TlvType88, *btcec.PublicKey, - ]{} - }, - wantErr: ErrMissingPayerID, - }, - { - name: "present-but-nil payer_id", - mutate: func(ir *InvoiceRequest) { - ir.InvreqPayerID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType88]( - (*btcec.PublicKey)(nil), - ), - ) - }, - wantErr: ErrNilPublicKey, - }, - { - name: "present-but-nil offer_issuer_id", - mutate: func(ir *InvoiceRequest) { - ir.OfferIssuerID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType22]( - (*btcec.PublicKey)(nil), - ), - ) - }, - wantErr: ErrNilPublicKey, - }, - { - name: "missing metadata", - mutate: func(ir *InvoiceRequest) { - ir.InvreqMetadata = tlv.OptionalRecordT[ - tlv.TlvType0, tlv.Blob, - ]{} - }, - wantErr: ErrMissingMetadata, - }, - { - name: "missing signature", - mutate: func(ir *InvoiceRequest) { - ir.Signature = tlv.OptionalRecordT[ - tlv.TlvType240, [64]byte, - ]{} - }, - wantErr: ErrMissingSignature, - }, - { - name: "missing amount", - mutate: func(ir *InvoiceRequest) { - ir.InvreqAmount = tlv.OptionalRecordT[ - tlv.TlvType82, TUint64, - ]{} - ir.OfferAmount = tlv.OptionalRecordT[ - tlv.TlvType8, TUint64, - ]{} - }, - wantErr: ErrMissingAmount, - }, - { - name: "quantity missing with quantity_max", - mutate: func(ir *InvoiceRequest) { - _, pub := bobKey() - ir.OfferIssuerID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType22]( - pub, - ), - ) - ir.OfferQuantityMax = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType20]( - TUint64(10), - ), - ) - }, - wantErr: ErrQuantityMissing, - }, - { - name: "quantity present but zero with quantity_max", - mutate: func(ir *InvoiceRequest) { - _, pub := bobKey() - ir.OfferIssuerID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType22]( - pub, - ), - ) - ir.OfferQuantityMax = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType20]( - TUint64(10), - ), - ) - ir.InvreqQuantity = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType86]( - TUint64(0), - ), - ) - }, - wantErr: ErrQuantityZero, - }, - { - name: "quantity exceeds max", - mutate: func(ir *InvoiceRequest) { - _, pub := bobKey() - ir.OfferIssuerID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType22]( - pub, - ), - ) - ir.OfferQuantityMax = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType20]( - TUint64(5), - ), - ) - ir.InvreqQuantity = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType86]( - TUint64(99), - ), - ) - }, - wantErr: ErrQuantityExceedsMax, - }, - { - name: "invreq_quantity without offer_quantity_max", - mutate: func(ir *InvoiceRequest) { - _, pub := bobKey() - ir.OfferIssuerID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType22]( - pub, - ), - ) - ir.InvreqQuantity = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType86]( - TUint64(1), - ), - ) - }, - wantErr: ErrQuantityWithoutMax, - }, - { - name: "spontaneous request carrying offer field " + - "(quantity max)", - mutate: func(ir *InvoiceRequest) { - ir.OfferQuantityMax = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType20]( - TUint64(10), - ), - ) - }, - wantErr: ErrOfferFieldsOnSpontaneous, - }, - { - name: "spontaneous request carrying quantity", - mutate: func(ir *InvoiceRequest) { - ir.InvreqQuantity = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType86]( - TUint64(1), - ), - ) - }, - wantErr: ErrQuantityWithoutMax, - }, - { - name: "out-of-range TLV in decoded extras", - mutate: func(ir *InvoiceRequest) { - ir.decodedTLVs = tlv.TypeMap{200: nil} - }, - wantErr: ErrOutOfRangeType, - }, - { - name: "unknown even TLV type in range", - mutate: func(ir *InvoiceRequest) { - ir.decodedTLVs = tlv.TypeMap{158: nil} - }, - wantErr: ErrUnknownEvenType, - }, - { - name: "unknown even type 34 rejected", - mutate: func(ir *InvoiceRequest) { - ir.decodedTLVs = tlv.TypeMap{34: nil} - }, - wantErr: ErrUnknownEvenType, - }, - { - // An unknown odd type in the signature range - // (240-1000) is a future optional signature element - // and is ignored, not rejected. - name: "unknown odd TLV in signature range ignored", - mutate: func(ir *InvoiceRequest) { - ir.decodedTLVs = tlv.TypeMap{501: nil} - }, - wantErr: nil, - }, - { - // An unknown even type stays must-understand even - // inside the signature range. - name: "unknown even TLV in signature range rejected", - mutate: func(ir *InvoiceRequest) { - ir.decodedTLVs = tlv.TypeMap{500: nil} - }, - wantErr: ErrUnknownEvenType, - }, - { - name: "unknown even feature bit", - mutate: func(ir *InvoiceRequest) { - ir.InvreqFeatures = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType84]( - *lnwire.NewRawFeatureVector(0), - ), - ) - }, - wantErr: ErrUnknownEvenFeature, - }, - { - name: "known even feature bit accepted", - mutate: func(ir *InvoiceRequest) { - ir.InvreqFeatures = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType84]( - *lnwire.NewRawFeatureVector(0), - ), - ) - }, - known: map[lnwire.FeatureBit]string{ - 0: "test_feature", - }, - wantErr: nil, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - ir := validInvoiceRequest(t) - tc.mutate(ir) - - if tc.name == "known even feature bit accepted" { - ir.Signature = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType240]( - [64]byte{0x01}, - ), - ) - } - - err := ValidateInvoiceRequestRead( - ir, bitcoinMainnetGenesisHash, tc.known, - ) - require.ErrorIs(t, err, tc.wantErr) - }) - } -} - -// TestValidateInvoiceRequestReadAmountBelowExpected pins the reader-side -// mirror of the writer's expected-amount rule: when offer_amount is present -// (native bitcoin, no offer_currency) a present invreq_amount below -// offer_amount*invreq_quantity MUST be rejected. The amount check runs before -// the signature verification, so an unsigned struct suffices to exercise it. -func TestValidateInvoiceRequestReadAmountBelowExpected(t *testing.T) { - t.Parallel() - - _, pub := bobKey() - ir := &InvoiceRequest{ - OfferIssuerID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType22](pub), - ), - OfferAmount: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType8](TUint64(1000)), - ), - OfferQuantityMax: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType20](TUint64(10)), - ), - InvreqQuantity: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType86](TUint64(2)), - ), - InvreqPayerID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType88](pub), - ), - InvreqMetadata: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType0](tlv.Blob("m")), - ), - } - - // expected = 1000 * 2 = 2000; 1999 is below. - ir.InvreqAmount = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType82](TUint64(1999)), - ) - err := ValidateInvoiceRequestRead(ir, bitcoinMainnetGenesisHash, nil) - require.ErrorIs(t, err, ErrAmountBelowExpected) -} - -// TestValidateInvoiceRequestAmountOverflow pins the guard against an -// offer_amount * invreq_quantity product that overflows uint64. -func TestValidateInvoiceRequestAmountOverflow(t *testing.T) { - t.Parallel() - - _, pub := bobKey() - - newRequest := func() *InvoiceRequest { - ir := &InvoiceRequest{} - ir.OfferIssuerID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType22](pub), - ) - ir.OfferAmount = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType8](TUint64(2)), - ) - - // quantity_max zero means unlimited, so the bound check does - // not cap the quantity below. - ir.OfferQuantityMax = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType20](TUint64(0)), - ) - // offer_amount(2) * quantity(2^63) == 2^64, which truncates to - // zero on an unchecked uint64 multiply; an unguarded validator - // would then accept invreq_amount(1) as "at least zero". - ir.InvreqQuantity = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType86]( - TUint64(1 << 63), - ), - ) - ir.InvreqAmount = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType82](TUint64(1)), - ) - ir.InvreqPayerID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType88](pub), - ) - ir.InvreqMetadata = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType0](tlv.Blob("m")), - ) - - return ir - } - - // The reader MUST reject the overflowing request. - readErr := ValidateInvoiceRequestRead( - newRequest(), bitcoinMainnetGenesisHash, nil, - ) - require.ErrorIs(t, readErr, ErrAmountBelowExpected) - - // The writer MUST reject it too (same rule, both sides). - writeErr := ValidateInvoiceRequestWrite(newRequest()) - require.ErrorIs(t, writeErr, ErrAmountBelowExpected) -} - -// TestValidateInvoiceRequestReadChain pins the spec invreq_chain rule: -// an absent invreq_chain defaults to Bitcoin mainnet and must be -// rejected on a non-mainnet node, while a present invreq_chain that -// disagrees with activeChain must also be rejected. The happy path -// (matching chain) is already covered by TestValidateInvoiceRequestRead. -func TestValidateInvoiceRequestReadChain(t *testing.T) { - t.Parallel() - - var altChain [32]byte - for i := range altChain { - altChain[i] = 0xaa - } - - t.Run("absent chain rejected on non-mainnet", func(t *testing.T) { - t.Parallel() - - ir := validInvoiceRequest(t) - err := ValidateInvoiceRequestRead(ir, altChain, nil) - require.ErrorIs(t, err, ErrUnsupportedChain) - }) - - t.Run("present chain mismatch rejected", func(t *testing.T) { - t.Parallel() - - ir := validInvoiceRequest(t) - ir.InvreqChain = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType80](altChain), - ) - // The chain check runs before signature verification, so a - // mismatched chain is rejected regardless of the signature. - err := ValidateInvoiceRequestRead( - ir, bitcoinMainnetGenesisHash, nil, - ) - require.ErrorIs(t, err, ErrUnsupportedChain) - }) -} - -// bip353Blob assembles a name+domain pair into the wire layout expected -// by invreq_bip_353_name (TLV 91). -func bip353Blob(name, domain []byte) []byte { - out := make([]byte, 0, 2+len(name)+len(domain)) - out = append(out, byte(len(name))) - out = append(out, name...) - out = append(out, byte(len(domain))) - out = append(out, domain...) - - return out -} - -// TestCheckBip353Name exercises the BIP 353 alphabet and structural -// requirements directly so each rejection path is pinned independently -// of the surrounding invoice-request validators. -func TestCheckBip353Name(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - blob []byte - wantErr bool - }{ - { - name: "happy path with allowed alphabet", - blob: bip353Blob( - []byte("alice.example-1_2"), - []byte("example.com"), - ), - wantErr: false, - }, - { - name: "absent field is no-op", - blob: nil, - wantErr: false, - }, - { - name: "present but empty rejected", - blob: []byte{}, - wantErr: true, - }, - { - name: "empty name rejected", - blob: []byte{ - 0x00, 0x05, 'e', 'x', '.', 'c', 'o', 'm', - }, - wantErr: true, - }, - { - name: "empty domain rejected", - blob: []byte{0x05, 'a', 'l', 'i', 'c', 'e', 0x00}, - wantErr: true, - }, - { - name: "both empty name and domain rejected", - blob: []byte{0x00, 0x00}, - wantErr: true, - }, - { - name: "name byte outside alphabet", - blob: bip353Blob( - []byte("alice@bob"), []byte("ex.com"), - ), - wantErr: true, - }, - { - name: "domain byte outside alphabet", - blob: bip353Blob([]byte("alice"), []byte("ex com")), - wantErr: true, - }, - { - name: "name truncated before domain_len", - blob: []byte{0x05, 'a', 'l', 'i'}, - wantErr: true, - }, - { - name: "domain length mismatch", - blob: []byte{0x01, 'a', 0x05, 'b'}, - wantErr: true, - }, - { - name: "control byte rejected in name", - blob: bip353Blob( - []byte{'a', 0x00, 'b'}, []byte("ex"), - ), - wantErr: true, - }, - { - name: "domain length shorter than remaining " + - "bytes rejected", - blob: []byte{0x01, 'a', 0x01, 'b', 'c'}, - wantErr: true, - }, - { - name: "minimal valid name and domain", - blob: []byte{0x01, 'a', 0x01, 'b'}, - wantErr: false, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - var opt tlv.OptionalRecordT[tlv.TlvType91, tlv.Blob] - if tc.blob != nil { - opt = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType91]( - tc.blob, - ), - ) - } - - err := checkBip353Name(opt) - if tc.wantErr { - require.ErrorIs(t, err, ErrInvalidBip353Name) - } else { - require.NoError(t, err) - } - }) - } -} - -// encodeIRBypassValidate serialises an InvoiceRequest skipping the -// validate-on-encode gate. -func encodeIRBypassValidate(ir *InvoiceRequest) ([]byte, error) { - records := lnwire.ProduceRecordsSorted(ir.allRecordProducers()...) - return lnwire.EncodeRecords(records) -} - -// encodeInvBypassValidate is the Invoice analogue for encodeIRBypassValidate. -func encodeInvBypassValidate(inv *Invoice) ([]byte, error) { - records := lnwire.ProduceRecordsSorted(inv.allRecordProducers()...) - return lnwire.EncodeRecords(records) -} - -// TestValidateInvoiceRead table-drives every reader-side rejection in -// ValidateInvoiceRead. -func TestValidateInvoiceRead(t *testing.T) { - t.Parallel() - - _, intro := aliceKey() - introNode, err := lnwire.NewPubkeyIntro(intro) - require.NoError(t, err) - - baseline := func() *Invoice { - return validInvoice(t) - } - - tests := []struct { - name string - mutate func(*Invoice) - wantErr error - }{ - { - name: "missing amount", - mutate: func(inv *Invoice) { - inv.InvoiceAmount = tlv.OptionalRecordT[ - tlv.TlvType170, TUint64, - ]{} - }, - wantErr: ErrMissingAmount, - }, - { - name: "zero amount", - mutate: func(inv *Invoice) { - inv.InvoiceAmount = tlv.SomeRecordT( - tlv.NewRecordT[ - tlv.TlvType170, TUint64, - ](TUint64(0)), - ) - }, - wantErr: ErrZeroInvoiceAmount, - }, - { - name: "missing created_at", - mutate: func(inv *Invoice) { - inv.InvoiceCreatedAt = tlv.OptionalRecordT[ - tlv.TlvType164, TUint64, - ]{} - }, - wantErr: ErrMissingCreatedAt, - }, - { - name: "missing payment_hash", - mutate: func(inv *Invoice) { - inv.InvoicePaymentHash = tlv.OptionalRecordT[ - tlv.TlvType168, [32]byte, - ]{} - }, - wantErr: ErrMissingPaymentHash, - }, - { - name: "missing node_id", - mutate: func(inv *Invoice) { - inv.InvoiceNodeID = tlv.OptionalRecordT[ - tlv.TlvType176, *btcec.PublicKey, - ]{} - }, - wantErr: ErrMissingNodeID, - }, - { - name: "missing paths", - mutate: func(inv *Invoice) { - inv.InvoicePaths = tlv.OptionalRecordT[ - tlv.TlvType160, lnwire.BlindedPaths, - ]{} - }, - wantErr: ErrMissingPaths, - }, - { - name: "missing blinded_pay", - mutate: func(inv *Invoice) { - inv.InvoiceBlindedPay = tlv.OptionalRecordT[ - tlv.TlvType162, BlindedPayInfos, - ]{} - }, - wantErr: ErrMissingBlindedPay, - }, - { - name: "paths count exceeds blinded_pay count", - mutate: func(inv *Invoice) { - path := lnwire.BlindedPath{ - IntroductionNode: introNode, - Hops: []lnwire.BlindedHop{ - {}, - }, - } - paths := lnwire.BlindedPaths{ - Paths: []lnwire.BlindedPath{path, path}, - } - inv.InvoicePaths = tlv.SomeRecordT( - tlv.NewRecordT[ - tlv.TlvType160, - lnwire.BlindedPaths, - ](paths), - ) - }, - wantErr: ErrBlindedPayMismatch, - }, - { - // The invoice reader defines no out-of-range type - // rejection, so an unknown odd type outside every known - // range is ignored rather than rejected: validation - // proceeds to the final signature check. - name: "unknown odd out-of-range type ignored", - mutate: func(inv *Invoice) { - inv.decodedTLVs = tlv.TypeMap{2001: nil} - }, - wantErr: ErrMissingSignature, - }, - { - name: "unknown even type", - mutate: func(inv *Invoice) { - inv.decodedTLVs = tlv.TypeMap{200: nil} - }, - wantErr: ErrUnknownEvenType, - }, - { - // Baseline invoice_node_id is bob; an offer_issuer_id - // of alice must be rejected as a mismatch. - name: "node_id does not match offer_issuer_id", - mutate: func(inv *Invoice) { - _, alice := aliceKey() - inv.OfferIssuerID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType22]( - alice, - ), - ) - }, - wantErr: ErrInvoiceNodeIDMismatch, - }, - { - // A present-but-nil invoice_node_id passes IsSome but - // would panic the codec on encode, so it must be - // rejected as ErrNilPublicKey rather than treated as a - // missing or mismatched field. - name: "present-but-nil node_id", - mutate: func(inv *Invoice) { - inv.InvoiceNodeID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType176]( - (*btcec.PublicKey)(nil), - ), - ) - }, - wantErr: ErrNilPublicKey, - }, - { - name: "unsupported chain", - mutate: func(inv *Invoice) { - inv.InvreqChain = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType80]( - [32]byte{0x01}, - ), - ) - }, - wantErr: ErrUnsupportedChain, - }, - { - name: "unknown even feature", - mutate: func(inv *Invoice) { - fv := *lnwire.NewRawFeatureVector(0) - inv.InvoiceFeatures = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType174](fv), - ) - }, - wantErr: ErrUnknownEvenFeature, - }, - { - name: "empty invoice_paths", - mutate: func(inv *Invoice) { - paths := lnwire.BlindedPaths{Paths: nil} - inv.InvoicePaths = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType160](paths), - ) - }, - wantErr: ErrEmptyBlindedPaths, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - inv := baseline() - tc.mutate(inv) - - err := ValidateInvoiceRead( - inv, bitcoinMainnetGenesisHash, - InvoiceFeatureCatalogues{}, - ) - require.ErrorIs(t, err, tc.wantErr) - }) - } -} - -// TestValidateInvoiceReadAcceptsSignatureRange pins the rule that an unknown -// odd TLV anywhere in the signature range (240-1000) is ignored rather than -// rejected. -func TestValidateInvoiceReadAcceptsSignatureRange(t *testing.T) { - t.Parallel() - - _, pub := bobKey() - - _, intro := aliceKey() - _, blinding := bobKey() - _, hopPub := aliceKey() - introNode, err := lnwire.NewPubkeyIntro(intro) - require.NoError(t, err) - - inv := &Invoice{ - InvoiceCreatedAt: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType164, TUint64](TUint64(123)), - ), - InvoiceAmount: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType170, TUint64](TUint64(1000)), - ), - InvoicePaymentHash: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType168, [32]byte]( - [32]byte{}, - ), - ), - InvoiceNodeID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType176](pub), - ), - InvoicePaths: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType160, lnwire.BlindedPaths]( - lnwire.BlindedPaths{ - Paths: []lnwire.BlindedPath{{ - IntroductionNode: introNode, - BlindingPoint: blinding, - Hops: []lnwire.BlindedHop{{ - BlindedNodeID: hopPub, - }}, - }}, - }, - ), - ), - InvoiceBlindedPay: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType162, BlindedPayInfos]( - BlindedPayInfos{Infos: []BlindedPayInfo{{}}}, - ), - ), - Signature: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType240, [64]byte]( - [64]byte{}, - ), - ), - } - - // An unknown odd type at 241 sits inside the signature range and must - // be ignored, not rejected as out-of-range or unknown-even. - inv.decodedTLVs = tlv.TypeMap{241: nil} - - err = ValidateInvoiceRead( - inv, bitcoinMainnetGenesisHash, - InvoiceFeatureCatalogues{}, - ) - require.NoError(t, err) -} - -// TestValidateInvoiceExpiry covers the relative-expiry default, an explicit -// relative expiry, the expired/not-expired boundary, and the overflow guard -// that keeps an absurd created_at from wrapping into a spurious expiry. -func TestValidateInvoiceExpiry(t *testing.T) { - t.Parallel() - - invoice := func(createdAt uint64, relExp *uint32) *Invoice { - inv := &Invoice{ - InvoiceCreatedAt: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType164, TUint64]( - TUint64(createdAt), - ), - ), - } - if relExp != nil { - inv.InvoiceRelativeExp = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType166, TUint32]( - TUint32(*relExp), - ), - ) - } - - return inv - } - - relExp := func(v uint32) *uint32 { return &v } - - tests := []struct { - name string - inv *Invoice - now int64 - wantErr error - }{ - { - name: "missing created_at", - inv: &Invoice{}, - now: 1000, - wantErr: ErrMissingCreatedAt, - }, - { - name: "within default expiry", - inv: invoice(1000, nil), - now: 1000 + 7199, - }, - { - // The boundary second itself is still valid: the spec - // rejects only when now is strictly greater than - // created_at + expiry. - name: "at default expiry boundary", - inv: invoice(1000, nil), - now: 1000 + 7200, - }, - { - name: "past default expiry", - inv: invoice(1000, nil), - now: 1000 + 7201, - wantErr: ErrInvoiceExpired, - }, - { - name: "within explicit expiry", - inv: invoice(1000, relExp(100)), - now: 1099, - }, - { - // The exact expiry second is still valid (strict ">"). - name: "at explicit expiry boundary", - inv: invoice(1000, relExp(100)), - now: 1100, - }, - { - name: "past explicit expiry", - inv: invoice(1000, relExp(100)), - now: 1101, - wantErr: ErrInvoiceExpired, - }, - { - name: "overflow is not expired", - inv: invoice(math.MaxUint64, relExp(100)), - now: 9223372036854775807, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - err := ValidateInvoiceExpiry( - tc.inv, time.Unix(tc.now, 0), - ) - if tc.wantErr != nil { - require.ErrorIs(t, err, tc.wantErr) - - return - } - require.NoError(t, err) - }) - } -} - -// TestValidateInvoiceAgainstRequest table-drives the mirror-field comparison -// between an invoice and the request it is responding to. -func TestValidateInvoiceAgainstRequest(t *testing.T) { - t.Parallel() - - // The request whose mirrored fields every invoice below is compared - // against: payer metadata plus offer_amount. - ir := &InvoiceRequest{ - InvreqMetadata: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType0]( - []byte("metadata"), - ), - ), - OfferAmount: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType8, TUint64](1000), - ), - } - - irEncoded, err := encodeIRBypassValidate(ir) - require.NoError(t, err) - irDecoded, err := DecodeInvoiceRequest(irEncoded) - require.NoError(t, err) - - // baseline mirrors the request's fields exactly. Invoice-specific - // fields >= 160 (here invoice_amount) are excluded from the mirror - // comparison, so the baseline validates cleanly. - baseline := func() *Invoice { - return &Invoice{ - InvreqMetadata: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType0]( - []byte("metadata"), - ), - ), - OfferAmount: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType8, TUint64](1000), - ), - InvoiceAmount: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType170, TUint64](1000), - ), - } - } - - tests := []struct { - name string - mutate func(*Invoice) - wantErr error - errContains string - }{ - { - name: "matching mirrored fields", - mutate: func(inv *Invoice) {}, - }, - { - name: "missing mirrored field", - mutate: func(inv *Invoice) { - inv.OfferAmount = tlv.OptionalRecordT[ - tlv.TlvType8, TUint64, - ]{} - }, - wantErr: ErrInvoiceMismatch, - errContains: "missing 1 fields", - }, - { - name: "extra mirrored field", - mutate: func(inv *Invoice) { - inv.OfferDescription = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType10]( - []byte("extra"), - ), - ) - }, - wantErr: ErrInvoiceMismatch, - errContains: "unexpected field 10", - }, - { - name: "mismatched field data", - mutate: func(inv *Invoice) { - inv.InvreqMetadata = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType0]( - []byte("different"), - ), - ) - }, - wantErr: ErrInvoiceMismatch, - errContains: "data mismatch", - }, - { - name: "equal length byte difference", - mutate: func(inv *Invoice) { - inv.InvreqMetadata = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType0]( - []byte("metadatA"), - ), - ) - }, - wantErr: ErrInvoiceMismatch, - errContains: "data mismatch", - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - inv := baseline() - tc.mutate(inv) - - invEncoded, err := encodeInvBypassValidate(inv) - require.NoError(t, err) - invDecoded, err := DecodeInvoice(invEncoded) - require.NoError(t, err) - - err = ValidateInvoiceAgainstRequest( - invDecoded, irDecoded, - ) - if tc.wantErr == nil { - require.NoError(t, err) - - return - } - require.ErrorIs(t, err, tc.wantErr) - require.Contains(t, err.Error(), tc.errContains) - }) - } -} - -// TestValidateInvoiceAgainstRequestAmountMirror covers the cross-field -// invreq_amount (82) vs invoice_amount (170) equality rule. -func TestValidateInvoiceAgainstRequestAmountMirror(t *testing.T) { - t.Parallel() - - ir := &InvoiceRequest{ - InvreqMetadata: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType0]( - []byte("metadata"), - ), - ), - InvreqAmount: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType82, TUint64](2500), - ), - } - irEncoded, err := encodeIRBypassValidate(ir) - require.NoError(t, err) - irDecoded, err := DecodeInvoiceRequest(irEncoded) - require.NoError(t, err) - - build := func(invAmt uint64) *Invoice { - return &Invoice{ - InvreqMetadata: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType0]( - []byte("metadata"), - ), - ), - InvreqAmount: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType82, TUint64](2500), - ), - InvoiceAmount: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType170, TUint64]( - TUint64(invAmt), - ), - ), - } - } - - // Equal amounts pass. - matchEnc, _ := encodeInvBypassValidate(build(2500)) - matchDec, _ := DecodeInvoice(matchEnc) - require.NoError(t, ValidateInvoiceAgainstRequest(matchDec, irDecoded)) - - // Mismatched amounts fail. - missEnc, _ := encodeInvBypassValidate(build(2501)) - missDec, _ := DecodeInvoice(missEnc) - err = ValidateInvoiceAgainstRequest(missDec, irDecoded) - require.ErrorIs(t, err, ErrInvoiceMismatch) - require.Contains(t, err.Error(), "invoice_amount") -} - -// TestValidateInvoiceAgainstRequestOfferAmount pins the offer-amount lower -// bound applied when invreq_amount is absent: the payee MUST NOT charge less -// than offer_amount * invreq_quantity for the native (non-offer_currency) case, -// while the offer_currency case is delegated to the caller. -func TestValidateInvoiceAgainstRequestOfferAmount(t *testing.T) { - t.Parallel() - - // build constructs a mirrored (invoice, request) pair carrying a fixed - // offer_amount and optional quantity/currency, with no invreq_amount so - // the offer-amount bound is what gets exercised. - build := func(offerAmt uint64, qty *uint64, currency []byte, - invAmt uint64) (*Invoice, *InvoiceRequest) { - - ir := &InvoiceRequest{ - InvreqMetadata: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType0]( - []byte("metadata"), - ), - ), - OfferAmount: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType8, TUint64]( - TUint64(offerAmt), - ), - ), - } - inv := &Invoice{ - InvreqMetadata: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType0]( - []byte("metadata"), - ), - ), - OfferAmount: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType8, TUint64]( - TUint64(offerAmt), - ), - ), - InvoiceAmount: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType170, TUint64]( - TUint64(invAmt), - ), - ), - } - if qty != nil { - ir.InvreqQuantity = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType86, TUint64]( - TUint64(*qty), - ), - ) - inv.InvreqQuantity = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType86, TUint64]( - TUint64(*qty), - ), - ) - } - if currency != nil { - ir.OfferCurrency = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType6](currency), - ) - inv.OfferCurrency = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType6](currency), - ) - } - - return inv, ir - } - - // roundtrip encodes and decodes both sides so the comparison runs over - // canonical wire bytes, mirroring the production flow. - roundtrip := func(inv *Invoice, ir *InvoiceRequest) error { - irEnc, err := encodeIRBypassValidate(ir) - require.NoError(t, err) - irDec, err := DecodeInvoiceRequest(irEnc) - require.NoError(t, err) - - invEnc, err := encodeInvBypassValidate(inv) - require.NoError(t, err) - invDec, err := DecodeInvoice(invEnc) - require.NoError(t, err) - - return ValidateInvoiceAgainstRequest(invDec, irDec) - } - - qty := func(v uint64) *uint64 { return &v } - - t.Run("at offer amount passes", func(t *testing.T) { - t.Parallel() - - inv, ir := build(1000, nil, nil, 1000) - require.NoError(t, roundtrip(inv, ir)) - }) - - t.Run("above offer amount passes", func(t *testing.T) { - t.Parallel() - - inv, ir := build(1000, nil, nil, 2000) - require.NoError(t, roundtrip(inv, ir)) - }) - - t.Run("below offer amount rejected", func(t *testing.T) { - t.Parallel() - - inv, ir := build(1000, nil, nil, 999) - require.ErrorIs(t, roundtrip(inv, ir), ErrAmountBelowExpected) - }) - - t.Run("quantity scales the bound", func(t *testing.T) { - t.Parallel() - - // 1000 * 3 = 3000 expected; 2999 is below, 3000 at the bound. - inv, ir := build(1000, qty(3), nil, 2999) - require.ErrorIs(t, roundtrip(inv, ir), ErrAmountBelowExpected) - - inv, ir = build(1000, qty(3), nil, 3000) - require.NoError(t, roundtrip(inv, ir)) - }) - - t.Run("offer_currency bound delegated", func(t *testing.T) { - t.Parallel() - - // With offer_currency present the bitcoin-unit bound does not - // apply, so an invoice_amount below offer_amount still passes - // this validator; the caller applies the exchange-rate check. - inv, ir := build(1000, nil, []byte("USD"), 1) - require.NoError(t, roundtrip(inv, ir)) - }) -} - -// TestValidateInvoiceWrite table-drives the writer-side checks of -// ValidateInvoiceWrite by clearing required fields on a valid baseline invoice. -func TestValidateInvoiceWrite(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - mutate func(*Invoice) - wantErr error - }{ - { - name: "valid baseline invoice", - mutate: func(inv *Invoice) {}, - wantErr: nil, - }, - { - name: "missing created_at", - mutate: func(inv *Invoice) { - inv.InvoiceCreatedAt = tlv.OptionalRecordT[ - tlv.TlvType164, TUint64, - ]{} - }, - wantErr: ErrMissingCreatedAt, - }, - { - name: "missing amount", - mutate: func(inv *Invoice) { - inv.InvoiceAmount = tlv.OptionalRecordT[ - tlv.TlvType170, TUint64, - ]{} - }, - wantErr: ErrMissingAmount, - }, - { - name: "missing payment_hash", - mutate: func(inv *Invoice) { - inv.InvoicePaymentHash = tlv.OptionalRecordT[ - tlv.TlvType168, [32]byte, - ]{} - }, - wantErr: ErrMissingPaymentHash, - }, - { - name: "missing node_id", - mutate: func(inv *Invoice) { - inv.InvoiceNodeID = tlv.OptionalRecordT[ - tlv.TlvType176, *btcec.PublicKey, - ]{} - }, - wantErr: ErrMissingNodeID, - }, - { - name: "missing paths", - mutate: func(inv *Invoice) { - inv.InvoicePaths = tlv.OptionalRecordT[ - tlv.TlvType160, lnwire.BlindedPaths, - ]{} - }, - wantErr: ErrMissingPaths, - }, - { - name: "missing blinded_pay", - mutate: func(inv *Invoice) { - inv.InvoiceBlindedPay = tlv.OptionalRecordT[ - tlv.TlvType162, BlindedPayInfos, - ]{} - }, - wantErr: ErrMissingBlindedPay, - }, - { - name: "present non-nil payer_id and matching " + - "non-nil offer_issuer_id", - mutate: func(inv *Invoice) { - _, payerID := bobKey() - _, issuerID := aliceKey() - inv.InvreqPayerID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType88]( - payerID, - ), - ) - inv.OfferIssuerID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType22]( - issuerID, - ), - ) - inv.InvoiceNodeID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType176]( - issuerID, - ), - ) - }, - wantErr: nil, - }, - { - name: "mismatched node_id and offer_issuer_id", - mutate: func(inv *Invoice) { - _, issuerID := aliceKey() - inv.OfferIssuerID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType22]( - issuerID, - ), - ) - }, - wantErr: ErrInvoiceNodeIDMismatch, - }, - { - name: "zero invoice_amount", - mutate: func(inv *Invoice) { - inv.InvoiceAmount = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType170]( - TUint64(0), - ), - ) - }, - wantErr: ErrZeroInvoiceAmount, - }, - { - name: "blinded pay info mismatch", - mutate: func(inv *Invoice) { - infos := BlindedPayInfos{ - Infos: []BlindedPayInfo{{}, {}}, - } - inv.InvoiceBlindedPay = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType162](infos), - ) - }, - wantErr: ErrBlindedPayMismatch, - }, - { - name: "empty invoice_paths", - mutate: func(inv *Invoice) { - paths := lnwire.BlindedPaths{Paths: nil} - inv.InvoicePaths = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType160](paths), - ) - }, - wantErr: ErrEmptyBlindedPaths, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - inv := validInvoice(t) - tc.mutate(inv) - - err := ValidateInvoiceWrite(inv) - if tc.wantErr == nil { - require.NoError(t, err) - } else { - require.ErrorIs(t, err, tc.wantErr) - } - }) - } -} - -// TestValidateFeaturesWithCatalogue verifies that both Role 1 endpoint features -// and Role 2 routing path features are correctly validated using injected -// catalogues. -func TestValidateFeaturesWithCatalogue(t *testing.T) { - t.Parallel() - - // Role 1 validation verifies endpoint features on ValidateInvoiceRead. - t.Run("endpoint features (Role 1)", func(t *testing.T) { - t.Parallel() - - inv := validInvoice(t) - inv.Signature = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType240]( - [64]byte{}, - ), - ) - - // Set MPP required (bit 16, even/required) - fv := *lnwire.NewRawFeatureVector(lnwire.MPPRequired) - inv.InvoiceFeatures = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType174](fv), - ) - - // An unknown required bit must be rejected. - err := ValidateInvoiceRead( - inv, bitcoinMainnetGenesisHash, - InvoiceFeatureCatalogues{}, - ) - require.ErrorIs(t, err, ErrUnknownEvenFeature) - - // A known required bit must pass. - known := map[lnwire.FeatureBit]string{ - lnwire.MPPRequired: "mpp", - } - err = ValidateInvoiceRead( - inv, bitcoinMainnetGenesisHash, - InvoiceFeatureCatalogues{Invoice: known}, - ) - require.NoError(t, err) - }) - - // Role 2 validation verifies routing path features on - // ValidateInvoiceRead. - t.Run("routing path features (Role 2)", func(t *testing.T) { - t.Parallel() - - inv := validInvoice(t) - inv.Signature = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType240]( - [64]byte{}, - ), - ) - - // Set an even required feature bit on the path's features (e.g. - // bit 16). - fv := *lnwire.NewRawFeatureVector(lnwire.MPPRequired) - inv.InvoiceBlindedPay = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType162](BlindedPayInfos{ - Infos: []BlindedPayInfo{{ - Features: fv, - }}, - }), - ) - - // If there are no known features in the catalogue, there are - // zero usable paths and we expect ErrNoUsablePaths. - err := ValidateInvoiceRead( - inv, bitcoinMainnetGenesisHash, - InvoiceFeatureCatalogues{}, - ) - require.ErrorIs(t, err, ErrNoUsablePaths) - - // A known features catalogue for blinded pay results in at - // least one usable path, which must pass. - knownBlinded := map[lnwire.FeatureBit]string{ - lnwire.MPPRequired: "mpp", - } - err = ValidateInvoiceRead( - inv, bitcoinMainnetGenesisHash, - InvoiceFeatureCatalogues{Blinded: knownBlinded}, - ) - require.NoError(t, err) - }) - - // Writer side ignores features, as we set the features. - t.Run("writer side ignores features", func(t *testing.T) { - t.Parallel() - - inv := validInvoice(t) - fv := *lnwire.NewRawFeatureVector(lnwire.MPPRequired) - inv.InvoiceFeatures = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType174](fv), - ) - - require.NoError(t, ValidateInvoiceWrite(inv)) - }) -} - -// TestValidateInvoiceWriteRejectsNilPubkeys verifies the writer rejects a -// present-but-nil mirrored pubkey field, which would otherwise panic the codec -// on encode. Symmetric with ValidateInvoiceRequestWrite. -func TestValidateInvoiceWriteRejectsNilPubkeys(t *testing.T) { - t.Parallel() - - t.Run("present-but-nil payer_id", func(t *testing.T) { - t.Parallel() - - inv := validInvoice(t) - inv.InvreqPayerID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType88]( - (*btcec.PublicKey)(nil), - ), - ) - require.ErrorIs(t, ValidateInvoiceWrite(inv), ErrNilPublicKey) - }) - - t.Run("present-but-nil offer_issuer_id", func(t *testing.T) { - t.Parallel() - - inv := validInvoice(t) - inv.OfferIssuerID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType22]( - (*btcec.PublicKey)(nil), - ), - ) - require.ErrorIs(t, ValidateInvoiceWrite(inv), ErrNilPublicKey) - }) - - t.Run("present-but-nil node_id", func(t *testing.T) { - t.Parallel() - - inv := validInvoice(t) - inv.InvoiceNodeID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType176]( - (*btcec.PublicKey)(nil), - ), - ) - require.ErrorIs(t, ValidateInvoiceWrite(inv), ErrNilPublicKey) - }) -} - -// TestValidateInvoiceErrorWrite verifies the BOLT 12 invoice_error writer -// requirements: error is mandatory and must be a non-empty UTF-8 string, and -// suggested_value may only accompany a set erroneous_field. -func TestValidateInvoiceErrorWrite(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - ie *InvoiceError - wantErr error - }{ - { - name: "missing error", - ie: &InvoiceError{}, - wantErr: ErrMissingError, - }, - { - name: "empty error", - ie: &InvoiceError{Error: someError("")}, - wantErr: ErrEmptyError, - }, - { - name: "non-utf8 error", - ie: &InvoiceError{ - Error: someError(string([]byte{0xff, 0xfe})), - }, - wantErr: ErrInvalidUTF8, - }, - { - name: "suggested without erroneous field", - ie: &InvoiceError{ - SuggestedValue: someSuggested([]byte{0x01}), - Error: someError("bad"), - }, - wantErr: ErrSuggestedWithoutField, - }, - { - // The spec permits erroneous_field on its own; - // suggested_value is only "MAY set" once - // erroneous_field is present. - name: "erroneous field without suggested value", - ie: &InvoiceError{ - ErroneousField: someErrField(82), - Error: someError("bad amount"), - }, - wantErr: nil, - }, - { - name: "valid with all fields", - ie: &InvoiceError{ - ErroneousField: someErrField(82), - SuggestedValue: someSuggested([]byte{0x01}), - Error: someError("bad amount"), - }, - wantErr: nil, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - err := ValidateInvoiceErrorWrite(tc.ie) - if tc.wantErr == nil { - require.NoError(t, err) - - return - } - require.ErrorIs(t, err, tc.wantErr) - }) - } -} diff --git a/build/log_test.go b/build/log_test.go index d58b3ddda..dbce428e4 100644 --- a/build/log_test.go +++ b/build/log_test.go @@ -92,6 +92,7 @@ func TestParseAndSetDebugLevels(t *testing.T) { } for _, test := range testCases { + test := test t.Run(test.name, func(t *testing.T) { m := &mockSubLogger{ subLogLevels: make(map[string]string), diff --git a/build/version.go b/build/version.go index 2fff198ac..0d7b69633 100644 --- a/build/version.go +++ b/build/version.go @@ -44,10 +44,10 @@ const ( AppMajor uint = 0 // AppMinor defines the minor version of this binary. - AppMinor uint = 21 + AppMinor uint = 20 // AppPatch defines the application patch for this binary. - AppPatch uint = 99 + AppPatch uint = 02 // AppPreRelease MUST only contain characters from semanticAlphabet per // the semantic versioning spec. diff --git a/cert/go.mod b/cert/go.mod index a3004677f..4dc5f2a78 100644 --- a/cert/go.mod +++ b/cert/go.mod @@ -1,6 +1,6 @@ module github.com/lightningnetwork/lnd/cert -go 1.25.11 +go 1.24.11 require github.com/stretchr/testify v1.8.2 diff --git a/chainntnfs/best_block_view.go b/chainntnfs/best_block_view.go index 4ad934ee9..c043e68e7 100644 --- a/chainntnfs/best_block_view.go +++ b/chainntnfs/best_block_view.go @@ -6,7 +6,7 @@ import ( "sync" "sync/atomic" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" ) // BestBlockView is an interface that allows the querying of the most diff --git a/chainntnfs/best_block_view_test.go b/chainntnfs/best_block_view_test.go index 9b4960071..2a55bc8b0 100644 --- a/chainntnfs/best_block_view_test.go +++ b/chainntnfs/best_block_view_test.go @@ -7,8 +7,8 @@ import ( "testing/quick" "time" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/lntest/mock" "github.com/lightningnetwork/lnd/lntest/wait" diff --git a/chainntnfs/bitcoindnotify/bitcoind.go b/chainntnfs/bitcoindnotify/bitcoind.go index 5028c78df..3cf53978c 100644 --- a/chainntnfs/bitcoindnotify/bitcoind.go +++ b/chainntnfs/bitcoindnotify/bitcoind.go @@ -1,18 +1,17 @@ package bitcoindnotify import ( - "context" "errors" "fmt" "sync" "sync/atomic" "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/chain" "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/chainntnfs" @@ -176,7 +175,7 @@ func (b *BitcoindNotifier) startNotifier() error { // Connect to bitcoind, and register for notifications on connected, // and disconnected blocks. - if err := b.chainConn.Start(context.Background()); err != nil { + if err := b.chainConn.Start(); err != nil { return err } if err := b.chainConn.NotifyBlocks(); err != nil { diff --git a/chainntnfs/bitcoindnotify/bitcoind_dev.go b/chainntnfs/bitcoindnotify/bitcoind_dev.go index f16b1ba36..d71caf4df 100644 --- a/chainntnfs/bitcoindnotify/bitcoind_dev.go +++ b/chainntnfs/bitcoindnotify/bitcoind_dev.go @@ -4,11 +4,10 @@ package bitcoindnotify import ( - "context" "fmt" "time" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcwallet/chain" "github.com/lightningnetwork/lnd/chainntnfs" ) @@ -24,7 +23,7 @@ func (b *BitcoindNotifier) UnsafeStart(bestHeight int32, bestHash *chainhash.Has // Connect to bitcoind, and register for notifications on connected, // and disconnected blocks. - if err := b.chainConn.Start(context.Background()); err != nil { + if err := b.chainConn.Start(); err != nil { return err } if err := b.chainConn.NotifyBlocks(); err != nil { diff --git a/chainntnfs/bitcoindnotify/bitcoind_test.go b/chainntnfs/bitcoindnotify/bitcoind_test.go index 141e183c2..0a2d14fbc 100644 --- a/chainntnfs/bitcoindnotify/bitcoind_test.go +++ b/chainntnfs/bitcoindnotify/bitcoind_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcwallet/chain" diff --git a/chainntnfs/bitcoindnotify/driver.go b/chainntnfs/bitcoindnotify/driver.go index b24fdf78a..1968f74c6 100644 --- a/chainntnfs/bitcoindnotify/driver.go +++ b/chainntnfs/bitcoindnotify/driver.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcwallet/chain" "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/chainntnfs" diff --git a/chainntnfs/btcdnotify/btcd.go b/chainntnfs/btcdnotify/btcd.go index ebe2c62b8..91178044c 100644 --- a/chainntnfs/btcdnotify/btcd.go +++ b/chainntnfs/btcdnotify/btcd.go @@ -8,12 +8,12 @@ import ( "time" "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/chain" "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/chainntnfs" diff --git a/chainntnfs/btcdnotify/btcd_dev.go b/chainntnfs/btcdnotify/btcd_dev.go index bd2a15726..11b20ff7c 100644 --- a/chainntnfs/btcdnotify/btcd_dev.go +++ b/chainntnfs/btcdnotify/btcd_dev.go @@ -7,7 +7,7 @@ import ( "fmt" "time" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/chainntnfs" ) diff --git a/chainntnfs/btcdnotify/btcd_test.go b/chainntnfs/btcdnotify/btcd_test.go index df7a6ce6a..6a1b97854 100644 --- a/chainntnfs/btcdnotify/btcd_test.go +++ b/chainntnfs/btcdnotify/btcd_test.go @@ -7,7 +7,7 @@ import ( "bytes" "testing" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/integration/rpctest" "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/chainntnfs" diff --git a/chainntnfs/btcdnotify/driver.go b/chainntnfs/btcdnotify/driver.go index 08afbd694..067b48cf8 100644 --- a/chainntnfs/btcdnotify/driver.go +++ b/chainntnfs/btcdnotify/driver.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/rpcclient" "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/chainntnfs" diff --git a/chainntnfs/interface.go b/chainntnfs/interface.go index 9dc9fd7b9..342d268fd 100644 --- a/chainntnfs/interface.go +++ b/chainntnfs/interface.go @@ -10,9 +10,9 @@ import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" ) diff --git a/chainntnfs/interface_dev.go b/chainntnfs/interface_dev.go index 472d863fd..eb3072247 100644 --- a/chainntnfs/interface_dev.go +++ b/chainntnfs/interface_dev.go @@ -3,7 +3,7 @@ package chainntnfs -import "github.com/btcsuite/btcd/chainhash/v2" +import "github.com/btcsuite/btcd/chaincfg/chainhash" // TestChainNotifier enables the use of methods that are only present during // testing for ChainNotifiers. diff --git a/chainntnfs/mempool.go b/chainntnfs/mempool.go index 82ca06935..2e31751fc 100644 --- a/chainntnfs/mempool.go +++ b/chainntnfs/mempool.go @@ -4,8 +4,8 @@ import ( "sync" "sync/atomic" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnutils" ) @@ -298,6 +298,8 @@ func (m *MempoolNotifier) notifySpent(spentInputs inputsWithTx) { // Iterate the spent inputs to notify the subscribers concurrently. for op, tx := range spentInputs { + op, tx := op, tx + m.wg.Add(1) go notifyAll(tx, op) } diff --git a/chainntnfs/mempool_test.go b/chainntnfs/mempool_test.go index 905b0631d..c0da43fa1 100644 --- a/chainntnfs/mempool_test.go +++ b/chainntnfs/mempool_test.go @@ -4,8 +4,8 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/stretchr/testify/require" ) diff --git a/chainntnfs/mocks.go b/chainntnfs/mocks.go index 3579bbbd4..4a888b162 100644 --- a/chainntnfs/mocks.go +++ b/chainntnfs/mocks.go @@ -1,8 +1,8 @@ package chainntnfs import ( - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/stretchr/testify/mock" ) diff --git a/chainntnfs/neutrinonotify/neutrino.go b/chainntnfs/neutrinonotify/neutrino.go index 06752f059..51e82a118 100644 --- a/chainntnfs/neutrinonotify/neutrino.go +++ b/chainntnfs/neutrinonotify/neutrino.go @@ -9,12 +9,12 @@ import ( "time" "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/btcutil/v2/gcs/builder" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/gcs/builder" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/neutrino" "github.com/lightninglabs/neutrino/headerfs" "github.com/lightningnetwork/lnd/blockcache" diff --git a/chainntnfs/neutrinonotify/neutrino_dev.go b/chainntnfs/neutrinonotify/neutrino_dev.go index 44aa9c2e0..e70cdf4d0 100644 --- a/chainntnfs/neutrinonotify/neutrino_dev.go +++ b/chainntnfs/neutrinonotify/neutrino_dev.go @@ -7,7 +7,7 @@ import ( "fmt" "time" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" "github.com/lightninglabs/neutrino" "github.com/lightningnetwork/lnd/chainntnfs" diff --git a/chainntnfs/test/test_interface.go b/chainntnfs/test/test_interface.go index 7c899897f..7536d24c5 100644 --- a/chainntnfs/test/test_interface.go +++ b/chainntnfs/test/test_interface.go @@ -11,11 +11,11 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/chain" _ "github.com/btcsuite/btcwallet/walletdb/bdb" // Required to auto-register the boltdb walletdb implementation. "github.com/lightninglabs/neutrino" diff --git a/chainntnfs/test_utils.go b/chainntnfs/test_utils.go index adca0a4d0..17e379e12 100644 --- a/chainntnfs/test_utils.go +++ b/chainntnfs/test_utils.go @@ -9,13 +9,13 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/integration/rpctest" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntest/unittest" "github.com/stretchr/testify/require" @@ -36,8 +36,8 @@ func randPubKeyHashScript() ([]byte, *btcec.PrivateKey, error) { return nil, nil, err } - pubKeyHash := address.Hash160(privKey.PubKey().SerializeCompressed()) - addrScript, err := address.NewAddressWitnessPubKeyHash( + pubKeyHash := btcutil.Hash160(privKey.PubKey().SerializeCompressed()) + addrScript, err := btcutil.NewAddressWitnessPubKeyHash( pubKeyHash, unittest.NetParams, ) if err != nil { diff --git a/chainntnfs/txnotifier.go b/chainntnfs/txnotifier.go index 3530b8a44..af85c2980 100644 --- a/chainntnfs/txnotifier.go +++ b/chainntnfs/txnotifier.go @@ -7,10 +7,10 @@ import ( "sync" "sync/atomic" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" ) const ( diff --git a/chainntnfs/txnotifier_test.go b/chainntnfs/txnotifier_test.go index e5c9b2210..1bd9f4f1c 100644 --- a/chainntnfs/txnotifier_test.go +++ b/chainntnfs/txnotifier_test.go @@ -6,9 +6,9 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/stretchr/testify/require" ) @@ -172,6 +172,7 @@ func TestTxNotifierRegistrationValidation(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase success := t.Run(testCase.name, func(t *testing.T) { hintCache := newMockHintCache() n := chainntnfs.NewTxNotifier( @@ -1942,18 +1943,14 @@ func TestTxNotifierConfirmHintCache(t *testing.T) { // the height hints should remain unchanged. This simulates blocks // confirming while the historical dispatch is processing the // registration. - _, err = hintCache.QueryConfirmHint( - ntfn1.HistoricalDispatch.ConfRequest, - ) + hint, err := hintCache.QueryConfirmHint(ntfn1.HistoricalDispatch.ConfRequest) if err != chainntnfs.ErrConfirmHintNotFound { t.Fatalf("unexpected error when querying for height hint "+ "want: %v, got %v", chainntnfs.ErrConfirmHintNotFound, err) } - _, err = hintCache.QueryConfirmHint( - ntfn2.HistoricalDispatch.ConfRequest, - ) + hint, err = hintCache.QueryConfirmHint(ntfn2.HistoricalDispatch.ConfRequest) if err != chainntnfs.ErrConfirmHintNotFound { t.Fatalf("unexpected error when querying for height hint "+ "want: %v, got %v", @@ -1982,9 +1979,7 @@ func TestTxNotifierConfirmHintCache(t *testing.T) { // Now that both notifications are waiting at tip for confirmations, // they should have their height hints updated to the latest block // height. - hint, err := hintCache.QueryConfirmHint( - ntfn1.HistoricalDispatch.ConfRequest, - ) + hint, err = hintCache.QueryConfirmHint(ntfn1.HistoricalDispatch.ConfRequest) require.NoError(t, err, "unable to query for hint") if hint != tx1Height { t.Fatalf("expected hint %d, got %d", diff --git a/chainparams/store.go b/chainparams/store.go deleted file mode 100644 index a71f52da0..000000000 --- a/chainparams/store.go +++ /dev/null @@ -1,114 +0,0 @@ -package chainparams - -import ( - "context" - "database/sql" - "errors" - "fmt" - - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/lightningnetwork/lnd/lncfg" - "github.com/lightningnetwork/lnd/sqldb" - "github.com/lightningnetwork/lnd/sqldb/sqlc" -) - -// ErrNetworkMismatch is returned by ValidateNetwork when the network stored in -// the database does not match the network lnd is configured to use. -var ErrNetworkMismatch = errors.New("database network mismatch") - -// SQLChainParamQueries defines the SQL queries required by Store. -type SQLChainParamQueries interface { - InsertChainNetwork(ctx context.Context, network string) error - GetChainNetwork(ctx context.Context) (string, error) -} - -// BatchedChainParamQueries is a version of SQLChainParamQueries that is -// capable of batched database operations. -type BatchedChainParamQueries interface { - SQLChainParamQueries - - sqldb.BatchedTx[SQLChainParamQueries] -} - -// Store is a database-backed store that persists and retrieves chain-level -// parameters such as the network the database was initialised for. -type Store struct { - db BatchedChainParamQueries -} - -// NewStore creates a new chain params Store backed by the given BaseDB. -func NewStore(db *sqldb.BaseDB) *Store { - executor := sqldb.NewTransactionExecutor( - db, func(tx *sql.Tx) SQLChainParamQueries { - return db.WithTx(tx) - }, - ) - - return &Store{db: executor} -} - -// ValidateNetwork checks that the network stored in the chain_params table -// matches the provided network. On the first call the network is persisted so -// that subsequent restarts can detect an accidental network switch. -func (s *Store) ValidateNetwork(ctx context.Context, - net *chaincfg.Params) error { - - network, err := normalizeNetworkName(net) - if err != nil { - return err - } - - return s.db.ExecTx( - ctx, sqldb.WriteTxOpt(), - func(tx SQLChainParamQueries) error { - // Insert the network only if the chain_params table is - // still empty. This is a no-op on every startup after - // the first. - err := tx.InsertChainNetwork(ctx, network) - if err != nil { - return fmt.Errorf("unable to set network in "+ - "chain_params: %w", err) - } - - // Read back whatever is stored. This is either the - // value we just inserted (first startup) or a value - // from a previous run. - storedNetwork, err := tx.GetChainNetwork(ctx) - if err != nil { - return fmt.Errorf("unable to read network "+ - "from chain_params: %w", err) - } - - if storedNetwork != network { - return fmt.Errorf("%w: the database was "+ - "previously used with network '%s', "+ - "but lnd is now configured for "+ - "network '%s'. To fix this, either "+ - "point lnd at a different database "+ - "or reconfigure lnd to use "+ - "network '%s'", ErrNetworkMismatch, - storedNetwork, network, storedNetwork) - } - - return nil - }, sqldb.NoOpReset, - ) -} - -// normalizeNetworkName returns the stable network identifier persisted in the -// chain_params table. -func normalizeNetworkName(net *chaincfg.Params) (string, error) { - if net == nil { - return "", fmt.Errorf("chain parameters must not be nil") - } - - network := lncfg.NormalizeNetwork(net.Name) - if network == "" { - return "", fmt.Errorf("chain parameters must define a network") - } - - return network, nil -} - -// Compile-time check that *sqlc.Queries implements SQLChainParamQueries. -var _ SQLChainParamQueries = (*sqlc.Queries)(nil) diff --git a/chainparams/store_test.go b/chainparams/store_test.go deleted file mode 100644 index eb6d577b9..000000000 --- a/chainparams/store_test.go +++ /dev/null @@ -1,87 +0,0 @@ -//go:build test_db_postgres || test_db_sqlite - -package chainparams - -import ( - "testing" - - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/stretchr/testify/require" -) - -// TestValidateNetworkMismatch verifies that ValidateNetwork persists the first -// network and fails when a different network is used later. -func TestValidateNetworkMismatch(t *testing.T) { - t.Parallel() - - store := NewStore(newTestDB(t)) - - // First call: persists regtest into the store. - err := store.ValidateNetwork(t.Context(), &chaincfg.RegressionNetParams) - require.NoError(t, err) - - // Second call: a different network — must fail with ErrNetworkMismatch. - err = store.ValidateNetwork(t.Context(), &chaincfg.SimNetParams) - require.ErrorIs(t, err, ErrNetworkMismatch) -} - -// TestValidateNetworkSameNetwork verifies that ValidateNetwork succeeds when -// called repeatedly with the same network (idempotent-match path). -func TestValidateNetworkSameNetwork(t *testing.T) { - t.Parallel() - - store := NewStore(newTestDB(t)) - - // First call: persists the network. - err := store.ValidateNetwork(t.Context(), &chaincfg.RegressionNetParams) - require.NoError(t, err) - - // Second call: same network again — reads the stored value and must - // succeed (idempotent match). - err = store.ValidateNetwork(t.Context(), &chaincfg.RegressionNetParams) - require.NoError(t, err) -} - -// TestValidateNetworkNormalizesTestnet verifies that network aliases collapse -// to the same persisted value. -func TestValidateNetworkNormalizesTestnet(t *testing.T) { - t.Parallel() - - store := NewStore(newTestDB(t)) - - // First call: persists canonical testnet3 parameters. - err := store.ValidateNetwork(t.Context(), &chaincfg.TestNet3Params) - require.NoError(t, err) - - // Second call: same logical network, different Name field — still - // matches after normalization (not ErrNetworkMismatch). - testnetAlias := chaincfg.TestNet3Params - testnetAlias.Name = "testnet" - - err = store.ValidateNetwork(t.Context(), &testnetAlias) - require.NoError(t, err) -} - -// TestValidateNetworkRejectsEmptyName verifies that malformed network params -// are rejected before touching the database. -func TestValidateNetworkRejectsEmptyName(t *testing.T) { - t.Parallel() - - store := NewStore(newTestDB(t)) - - // Empty Params.Name — rejected before any database read or write. - err := store.ValidateNetwork(t.Context(), &chaincfg.Params{}) - require.ErrorContains(t, err, "must define a network") -} - -// TestValidateNetworkRejectsNilParams verifies that callers provide network -// parameters. -func TestValidateNetworkRejectsNilParams(t *testing.T) { - t.Parallel() - - store := NewStore(newTestDB(t)) - - // Nil params — rejected before any database read or write. - err := store.ValidateNetwork(t.Context(), nil) - require.ErrorContains(t, err, "must not be nil") -} diff --git a/chainparams/test_postgres.go b/chainparams/test_postgres.go deleted file mode 100644 index 698f1500c..000000000 --- a/chainparams/test_postgres.go +++ /dev/null @@ -1,21 +0,0 @@ -//go:build test_db_postgres && !test_db_sqlite - -package chainparams - -import ( - "testing" - - "github.com/lightningnetwork/lnd/sqldb" -) - -// newTestDB creates a Postgres-backed BaseDB for use in unit tests. -func newTestDB(t testing.TB) *sqldb.BaseDB { - pgFixture := sqldb.NewTestPgFixture( - t, sqldb.DefaultPostgresFixtureLifetime, - ) - t.Cleanup(func() { - pgFixture.TearDown(t) - }) - - return sqldb.NewTestPostgresDB(t, pgFixture).GetBaseDB() -} diff --git a/chainparams/test_sqlite.go b/chainparams/test_sqlite.go deleted file mode 100644 index 998b57d0c..000000000 --- a/chainparams/test_sqlite.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build !test_db_postgres && test_db_sqlite - -package chainparams - -import ( - "testing" - - "github.com/lightningnetwork/lnd/sqldb" -) - -// newTestDB creates a SQLite-backed BaseDB for use in unit tests. -func newTestDB(t testing.TB) *sqldb.BaseDB { - return sqldb.NewTestSqliteDB(t).GetBaseDB() -} diff --git a/chainreg/chainparams.go b/chainreg/chainparams.go index ecaf6dbee..166a1fec5 100644 --- a/chainreg/chainparams.go +++ b/chainreg/chainparams.go @@ -1,8 +1,8 @@ package chainreg import ( - bitcoinCfg "github.com/btcsuite/btcd/chaincfg/v2" - bitcoinWire "github.com/btcsuite/btcd/wire/v2" + bitcoinCfg "github.com/btcsuite/btcd/chaincfg" + bitcoinWire "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/keychain" ) diff --git a/chainreg/chainregistry.go b/chainreg/chainregistry.go index 6740848a5..71e9c04a6 100644 --- a/chainreg/chainregistry.go +++ b/chainreg/chainregistry.go @@ -2,6 +2,7 @@ package chainreg import ( "encoding/hex" + "encoding/json" "errors" "fmt" "io" @@ -12,7 +13,7 @@ import ( "strings" "time" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcwallet/chain" "github.com/lightninglabs/neutrino" @@ -451,43 +452,52 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) { return nil, nil, err } - // Fetch all active ZMQ notifications from bitcoind. - zmq, err := chainConn.GetZmqNotifications() + // Fetch all active zmq notifications from the bitcoind client. + resp, err := chainConn.RawRequest("getzmqnotifications", nil) if err != nil { return nil, nil, err } + zmq := []struct { + Type string `json:"type"` + Address string `json:"address"` + }{} + + if err = json.Unmarshal([]byte(resp), &zmq); err != nil { + return nil, nil, err + } + pubRawBlockActive := false pubRawTxActive := false for i := range zmq { if zmq[i].Type == "pubrawblock" { - if zmq[i].Address.Port() != - zmqPubRawBlockURL.Port() { - - log.Warnf("zmq block port "+ - "mismatch: lnd is "+ - "using %s but "+ - "bitcoind reports %s "+ - "- ensure the port "+ - "is correct", + url, err := url.Parse(zmq[i].Address) + if err != nil { + return nil, nil, err + } + if url.Port() != zmqPubRawBlockURL.Port() { + log.Warnf( + "unable to subscribe to zmq block events on "+ + "%s (bitcoind is running on %s)", zmqPubRawBlockURL.Host, - zmq[i].Address.Host) + url.Host, + ) } pubRawBlockActive = true } if zmq[i].Type == "pubrawtx" { - if zmq[i].Address.Port() != - zmqPubRawTxURL.Port() { - - log.Warnf("zmq tx port "+ - "mismatch: lnd is "+ - "using %s but "+ - "bitcoind reports %s "+ - "- ensure the port "+ - "is correct", + url, err := url.Parse(zmq[i].Address) + if err != nil { + return nil, nil, err + } + if url.Port() != zmqPubRawTxURL.Port() { + log.Warnf( + "unable to subscribe to zmq tx events on "+ + "%s (bitcoind is running on %s)", zmqPubRawTxURL.Host, - zmq[i].Address.Host) + url.Host, + ) } pubRawTxActive = true } @@ -525,7 +535,7 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) { // Make sure the bitcoind chain backend maintains a // healthy connection to the network by checking the // number of outbound peers. - return checkOutboundPeersBitcoind(chainConn) + return checkOutboundPeers(chainConn) } case "btcd": @@ -792,11 +802,19 @@ func NewChainControl(walletConfig lnwallet.Config, // getblockchaininfo. func getBitcoindHealthCheckCmd(client *rpcclient.Client) (string, int64, error) { // Query bitcoind to get our current version. - info, err := client.GetNetworkInfo() + resp, err := client.RawRequest("getnetworkinfo", nil) if err != nil { return "", 0, err } + // Parse the response to retrieve bitcoind's version. + info := struct { + Version int64 `json:"version"` + }{} + if err := json.Unmarshal(resp, &info); err != nil { + return "", 0, err + } + // Bitcoind returns a single value representing the semantic version: // 1000000 * CLIENT_VERSION_MAJOR + 10000 * CLIENT_VERSION_MINOR // + 100 * CLIENT_VERSION_REVISION + 1 * CLIENT_VERSION_BUILD @@ -804,10 +822,10 @@ func getBitcoindHealthCheckCmd(client *rpcclient.Client) (string, int64, error) // The uptime call was added in version 0.15.0, so we return it for // any version value >= 150000, as per the above calculation. if info.Version >= 150000 { - return "uptime", int64(info.Version), nil + return "uptime", info.Version, nil } - return "getblockchaininfo", int64(info.Version), nil + return "getblockchaininfo", info.Version, nil } var ( @@ -891,24 +909,6 @@ var ( } ) -// checkOutboundPeersBitcoind checks the number of outbound peers connected to -// a bitcoind backend. If the number of outbound peers is below 6, a warning is -// logged. This function is intended to ensure that the chain backend maintains -// a healthy connection to the network. -// -// This helper is bitcoind-specific because btcd does not currently implement -// getnetworkinfo. -func checkOutboundPeersBitcoind(client *rpcclient.Client) error { - info, err := client.GetNetworkInfo() - if err != nil { - return err - } - - logOutboundPeerCount(int(info.ConnectionsOut)) - - return nil -} - // checkOutboundPeers checks the number of outbound peers connected to the // provided RPC client. If the number of outbound peers is below 6, a warning // is logged. This function is intended to ensure that the chain backend @@ -926,14 +926,6 @@ func checkOutboundPeers(client *rpcclient.Client) error { } } - logOutboundPeerCount(outboundPeers) - - return nil -} - -// logOutboundPeerCount logs a warning when the number of outbound peers is -// below the minimum threshold. -func logOutboundPeerCount(outboundPeers int) { if outboundPeers < DefaultMinOutboundPeers { log.Warnf("The chain backend has an insufficient number "+ "of connected outbound peers (%d connected, expected "+ @@ -941,4 +933,6 @@ func logOutboundPeerCount(outboundPeers int) { "Connect to more trusted nodes manually if necessary.", outboundPeers, DefaultMinOutboundPeers) } + + return nil } diff --git a/chainreg/no_chain_backend.go b/chainreg/no_chain_backend.go index 5e5555374..f68202ea9 100644 --- a/chainreg/no_chain_backend.go +++ b/chainreg/no_chain_backend.go @@ -1,14 +1,13 @@ package chainreg import ( - "context" "errors" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/lightningnetwork/lnd/chainntnfs" @@ -128,7 +127,7 @@ type NoChainSource struct { BestBlockTime time.Time } -func (n *NoChainSource) Start(_ context.Context) error { +func (n *NoChainSource) Start() error { n.notifChan = make(chan interface{}) go func() { @@ -193,13 +192,13 @@ func (n *NoChainSource) SendRawTransaction(*wire.MsgTx, bool) (*chainhash.Hash, return nil, errNotImplemented } -func (n *NoChainSource) Rescan(*chainhash.Hash, []address.Address, - map[wire.OutPoint]address.Address) error { +func (n *NoChainSource) Rescan(*chainhash.Hash, []btcutil.Address, + map[wire.OutPoint]btcutil.Address) error { return nil } -func (n *NoChainSource) NotifyReceived([]address.Address) error { +func (n *NoChainSource) NotifyReceived([]btcutil.Address) error { return nil } @@ -221,15 +220,6 @@ func (n *NoChainSource) TestMempoolAccept([]*wire.MsgTx, return nil, nil } -// SubmitPackage is a stub implementation of the chain.Interface method for -// NoChainSource; there is no chain backend, so it always returns -// errNotImplemented. -func (n *NoChainSource) SubmitPackage([]*wire.MsgTx, - *float64) (*btcjson.SubmitPackageResult, error) { - - return nil, errNotImplemented -} - func (n *NoChainSource) MapRPCErr(err error) error { return err } diff --git a/chainreg/taproot_check.go b/chainreg/taproot_check.go index 45c5c5b19..dedc717eb 100644 --- a/chainreg/taproot_check.go +++ b/chainreg/taproot_check.go @@ -2,7 +2,6 @@ package chainreg import ( "encoding/json" - "slices" "github.com/btcsuite/btcd/rpcclient" ) @@ -49,7 +48,6 @@ func backendSupportsTaproot(rpc *rpcclient.Client) bool { } info := struct { - ScriptFlags []string `json:"script_flags"` Deployments map[string]struct { Type string `json:"type"` Active bool `json:"active"` @@ -61,14 +59,6 @@ func backendSupportsTaproot(rpc *rpcclient.Client) bool { return false } - // Before Bitcoin Core v31, taproot was still included as a BIP9 - // deployment. _, ok := info.Deployments["taproot"] - - // Since v31, taproot is activated at genesis and no longer appears - // as a deployment. Also in v31, Bitcoin Core added a "script_flags" - // field to getdeploymentinfo which lists all the verification flags. - hasFlag := slices.Contains(info.ScriptFlags, "TAPROOT") - - return ok || hasFlag + return ok } diff --git a/chanacceptor/acceptor_test.go b/chanacceptor/acceptor_test.go index b2b0bf3cb..5a6aaa012 100644 --- a/chanacceptor/acceptor_test.go +++ b/chanacceptor/acceptor_test.go @@ -6,8 +6,8 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwallet/chancloser" "github.com/lightningnetwork/lnd/lnwire" @@ -134,6 +134,8 @@ func (c *channelAcceptorCtx) queryAndAssert(queries map[*lnwire.OpenChannel]*Cha ) for request, expected := range queries { + request := request + expected := expected go func() { resp := c.acceptor.Accept(&ChannelAcceptRequest{ diff --git a/chanacceptor/interface.go b/chanacceptor/interface.go index 0939d2330..3149cb05b 100644 --- a/chanacceptor/interface.go +++ b/chanacceptor/interface.go @@ -4,7 +4,7 @@ import ( "errors" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnwire" ) diff --git a/chanacceptor/merge.go b/chanacceptor/merge.go index 176405903..34f191743 100644 --- a/chanacceptor/merge.go +++ b/chanacceptor/merge.go @@ -4,7 +4,7 @@ import ( "bytes" "fmt" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnwire" ) diff --git a/chanacceptor/merge_test.go b/chanacceptor/merge_test.go index 0ffaadeb8..c6b6579a7 100644 --- a/chanacceptor/merge_test.go +++ b/chanacceptor/merge_test.go @@ -182,6 +182,7 @@ func TestMergeResponse(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { resp, err := mergeResponse(test.current, test.new) diff --git a/chanacceptor/rpcacceptor.go b/chanacceptor/rpcacceptor.go index fec798e3e..aff8c3dc7 100644 --- a/chanacceptor/rpcacceptor.go +++ b/chanacceptor/rpcacceptor.go @@ -7,8 +7,8 @@ import ( "sync" "time" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwallet/chancloser" @@ -356,30 +356,6 @@ func (r *RPCAcceptor) sendAcceptRequests(errChan chan error, ): commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT - case channelFeatures.OnlyContains( - lnwire.SimpleTaprootChannelsRequiredFinal, - lnwire.ZeroConfRequired, - lnwire.ScidAliasRequired, - ): - commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - case channelFeatures.OnlyContains( - lnwire.SimpleTaprootChannelsRequiredFinal, - lnwire.ZeroConfRequired, - ): - commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - case channelFeatures.OnlyContains( - lnwire.SimpleTaprootChannelsRequiredFinal, - lnwire.ScidAliasRequired, - ): - commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - case channelFeatures.OnlyContains( - lnwire.SimpleTaprootChannelsRequiredFinal, - ): - commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - case channelFeatures.OnlyContains( lnwire.SimpleTaprootOverlayChansRequired, lnwire.ZeroConfRequired, diff --git a/chanacceptor/rpcacceptor_test.go b/chanacceptor/rpcacceptor_test.go index d1f3b2f02..de1f380c1 100644 --- a/chanacceptor/rpcacceptor_test.go +++ b/chanacceptor/rpcacceptor_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwallet/chancloser" @@ -118,6 +118,7 @@ func TestValidateAcceptorResponse(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { // Create an acceptor, everything can be nil because diff --git a/chanbackup/backup.go b/chanbackup/backup.go index 6af6f381c..cf7217ae3 100644 --- a/chanbackup/backup.go +++ b/chanbackup/backup.go @@ -4,9 +4,8 @@ import ( "context" "fmt" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" ) @@ -15,11 +14,11 @@ import ( // commitment transaction broadcast. type LiveChannelSource interface { // FetchAllChannels returns all known live channels. - FetchAllChannels() ([]*chanstate.OpenChannel, error) + FetchAllChannels() ([]*channeldb.OpenChannel, error) // FetchChannel attempts to locate a live channel identified by the // passed chanPoint. Optionally an existing db tx can be supplied. - FetchChannel(chanPoint wire.OutPoint) (*chanstate.OpenChannel, error) + FetchChannel(chanPoint wire.OutPoint) (*channeldb.OpenChannel, error) } // assembleChanBackup attempts to assemble a static channel backup for the @@ -27,7 +26,7 @@ type LiveChannelSource interface { // the channel, as well as addressing information so we can find the peer and // reconnect to them to initiate the protocol. func assembleChanBackup(ctx context.Context, addrSource channeldb.AddrSource, - openChan *chanstate.OpenChannel) (*Single, error) { + openChan *channeldb.OpenChannel) (*Single, error) { log.Debugf("Crafting backup for ChannelPoint(%v)", openChan.FundingOutpoint) @@ -56,7 +55,7 @@ func assembleChanBackup(ctx context.Context, addrSource channeldb.AddrSource, // in loss of funds! This may happen if an outdated channel backup is attempted // to be used to force close the channel. func buildCloseTxInputs( - targetChan *chanstate.OpenChannel) fn.Option[CloseTxInputs] { + targetChan *channeldb.OpenChannel) fn.Option[CloseTxInputs] { log.Debugf("Crafting CloseTxInputs for ChannelPoint(%v)", targetChan.FundingOutpoint) diff --git a/chanbackup/backup_test.go b/chanbackup/backup_test.go index 264649b07..05a24090c 100644 --- a/chanbackup/backup_test.go +++ b/chanbackup/backup_test.go @@ -7,13 +7,13 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/channeldb" "github.com/stretchr/testify/require" ) type mockChannelSource struct { - chans map[wire.OutPoint]*chanstate.OpenChannel + chans map[wire.OutPoint]*channeldb.OpenChannel failQuery bool @@ -22,19 +22,17 @@ type mockChannelSource struct { func newMockChannelSource() *mockChannelSource { return &mockChannelSource{ - chans: make(map[wire.OutPoint]*chanstate.OpenChannel), + chans: make(map[wire.OutPoint]*channeldb.OpenChannel), addrs: make(map[[33]byte][]net.Addr), } } -func (m *mockChannelSource) FetchAllChannels() ( - []*chanstate.OpenChannel, error) { - +func (m *mockChannelSource) FetchAllChannels() ([]*channeldb.OpenChannel, error) { if m.failQuery { return nil, fmt.Errorf("fail") } - chans := make([]*chanstate.OpenChannel, 0, len(m.chans)) + chans := make([]*channeldb.OpenChannel, 0, len(m.chans)) for _, channel := range m.chans { chans = append(chans, channel) } @@ -43,7 +41,7 @@ func (m *mockChannelSource) FetchAllChannels() ( } func (m *mockChannelSource) FetchChannel(chanPoint wire.OutPoint) ( - *chanstate.OpenChannel, error) { + *channeldb.OpenChannel, error) { if m.failQuery { return nil, fmt.Errorf("fail") diff --git a/chanbackup/backupfile_test.go b/chanbackup/backupfile_test.go index 11d66e115..e97d4ceba 100644 --- a/chanbackup/backupfile_test.go +++ b/chanbackup/backupfile_test.go @@ -431,6 +431,7 @@ func TestCreateArchiveFile(t *testing.T) { } for _, tc := range tests { + tc := tc t.Run(tc.name, func(t *testing.T) { defer os.RemoveAll(archiveDir) if tc.setup != nil { diff --git a/chanbackup/pubsub.go b/chanbackup/pubsub.go index 4b5493f9d..6304f0cb3 100644 --- a/chanbackup/pubsub.go +++ b/chanbackup/pubsub.go @@ -9,8 +9,8 @@ import ( "sync" "sync/atomic" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnutils" ) @@ -31,7 +31,7 @@ type Swapper interface { // ChannelWithAddrs bundles an open channel along with all the addresses for // the channel peer. type ChannelWithAddrs struct { - *chanstate.OpenChannel + *channeldb.OpenChannel // Addrs is the set of addresses that we can use to reach the target // peer. diff --git a/chanbackup/pubsub_test.go b/chanbackup/pubsub_test.go index 9d5ea4e91..615f170fa 100644 --- a/chanbackup/pubsub_test.go +++ b/chanbackup/pubsub_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnencrypt" "github.com/stretchr/testify/require" diff --git a/chanbackup/single.go b/chanbackup/single.go index 5cf654789..01d14f6c0 100644 --- a/chanbackup/single.go +++ b/chanbackup/single.go @@ -8,10 +8,10 @@ import ( "net" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnencrypt" @@ -59,11 +59,6 @@ const ( // channel with a top level tapscript commitment. TapscriptRootVersion = 6 - // SimpleTaprootFinalVersion is a version that denotes this channel is - // using the production musig2 based taproot commitment format with - // final scripts (OP_CHECKSIGVERIFY instead of OP_CHECKSIG + OP_DROP). - SimpleTaprootFinalVersion = 7 - // closeTxVersionMask is the byte mask used that is ORed to version byte // on wire indicating that the backup has CloseTxInputs. closeTxVersionMask = 1 << 7 @@ -99,8 +94,7 @@ func DecodeVersion(encoded byte) (SingleBackupVersion, bool) { // IsTaproot returns if this is a backup of a taproot channel. This will also be // true for simple taproot overlay channels when a version is added. func (v SingleBackupVersion) IsTaproot() bool { - return v == SimpleTaprootVersion || v == TapscriptRootVersion || - v == SimpleTaprootFinalVersion + return v == SimpleTaprootVersion || v == TapscriptRootVersion } // HasTapscriptRoot returns true if the channel is using a top level tapscript @@ -169,7 +163,7 @@ type Single struct { // // NOTE: Of the items in the ChannelConstraints, we only write the CSV // delay. - LocalChanCfg chanstate.ChannelConfig + LocalChanCfg channeldb.ChannelConfig // RemoteChanCfg is the remote channel confirmation. We store this as // well since we'll need some of their keys to re-derive things like @@ -178,7 +172,7 @@ type Single struct { // // NOTE: Of the items in the ChannelConstraints, we only write the CSV // delay. - RemoteChanCfg chanstate.ChannelConfig + RemoteChanCfg channeldb.ChannelConfig // ShaChainRootDesc describes how to derive the private key that was // used as the shachain root for this channel. @@ -234,7 +228,7 @@ type CloseTxInputs struct { // connect to the channel peer. If possible, we include the data needed to // produce a force close transaction from the most recent state using externally // provided private key. -func NewSingle(channel *chanstate.OpenChannel, +func NewSingle(channel *channeldb.OpenChannel, nodeAddrs []net.Addr) Single { var shaChainRootDesc keychain.KeyDescriptor @@ -308,9 +302,6 @@ func NewSingle(channel *chanstate.OpenChannel, } switch { - case channel.ChanType.IsTaprootFinal(): - single.Version = SimpleTaprootFinalVersion - case channel.ChanType.IsTaproot(): if channel.ChanType.HasTapscriptRoot() { single.Version = TapscriptRootVersion @@ -360,7 +351,6 @@ func (s *Single) Serialize(w io.Writer) error { case ScriptEnforcedLeaseVersion: case SimpleTaprootVersion: case TapscriptRootVersion: - case SimpleTaprootFinalVersion: default: return fmt.Errorf("unable to serialize w/ unknown "+ "version: %v", s.Version) @@ -595,7 +585,6 @@ func (s *Single) Deserialize(r io.Reader) error { case ScriptEnforcedLeaseVersion: case SimpleTaprootVersion: case TapscriptRootVersion: - case SimpleTaprootFinalVersion: default: return fmt.Errorf("unable to de-serialize w/ unknown "+ "version: %v", s.Version) diff --git a/chanbackup/single_test.go b/chanbackup/single_test.go index 3468fb086..f1f805c14 100644 --- a/chanbackup/single_test.go +++ b/chanbackup/single_test.go @@ -9,10 +9,10 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnencrypt" @@ -135,7 +135,7 @@ func assertSingleEqual(t *testing.T, a, b Single) { } } -func genRandomOpenChannelShell() (*chanstate.OpenChannel, error) { +func genRandomOpenChannelShell() (*channeldb.OpenChannel, error) { var testPriv [32]byte if _, err := rand.Read(testPriv[:]); err != nil { return nil, err @@ -162,11 +162,11 @@ func genRandomOpenChannelShell() (*chanstate.OpenChannel, error) { isInitiator = true } - chanType := chanstate.ChannelType(rand.Intn(1 << 12)) + chanType := channeldb.ChannelType(rand.Intn(1 << 12)) - localCfg := chanstate.ChannelConfig{ - ChannelStateBounds: chanstate.ChannelStateBounds{}, - CommitmentParams: chanstate.CommitmentParams{ + localCfg := channeldb.ChannelConfig{ + ChannelStateBounds: channeldb.ChannelStateBounds{}, + CommitmentParams: channeldb.CommitmentParams{ CsvDelay: uint16(rand.Int63()), }, MultiSigKey: keychain.KeyDescriptor{ @@ -201,8 +201,8 @@ func genRandomOpenChannelShell() (*chanstate.OpenChannel, error) { }, } - remoteCfg := chanstate.ChannelConfig{ - CommitmentParams: chanstate.CommitmentParams{ + remoteCfg := channeldb.ChannelConfig{ + CommitmentParams: channeldb.CommitmentParams{ CsvDelay: uint16(rand.Int63()), }, MultiSigKey: keychain.KeyDescriptor{ @@ -222,14 +222,14 @@ func genRandomOpenChannelShell() (*chanstate.OpenChannel, error) { }, } - var localCommit chanstate.ChannelCommitment + var localCommit channeldb.ChannelCommitment if chanType.IsTaproot() { var commitSig [64]byte if _, err := rand.Read(commitSig[:]); err != nil { return nil, err } - localCommit = chanstate.ChannelCommitment{ + localCommit = channeldb.ChannelCommitment{ CommitTx: sampleCommitTx, CommitSig: commitSig[:], CommitHeight: rand.Uint64(), @@ -245,7 +245,7 @@ func genRandomOpenChannelShell() (*chanstate.OpenChannel, error) { tapscriptRootOption = fn.Some(tapscriptRoot) } - return &chanstate.OpenChannel{ + return &channeldb.OpenChannel{ ChainHash: chainHash, ChanType: chanType, IsInitiator: isInitiator, diff --git a/chanfitness/chanevent.go b/chanfitness/chanevent.go index 1edfec5be..b829a6c30 100644 --- a/chanfitness/chanevent.go +++ b/chanfitness/chanevent.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/clock" ) diff --git a/chanfitness/chanevent_test.go b/chanfitness/chanevent_test.go index 40a0e539d..43046db25 100644 --- a/chanfitness/chanevent_test.go +++ b/chanfitness/chanevent_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/clock" "github.com/stretchr/testify/require" ) @@ -388,6 +388,7 @@ func TestGetOnlinePeriod(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -544,6 +545,7 @@ func TestUptime(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { score := &peerLog{ diff --git a/chanfitness/chaneventstore.go b/chanfitness/chaneventstore.go index 016f67d6a..881e9a35e 100644 --- a/chanfitness/chaneventstore.go +++ b/chanfitness/chaneventstore.go @@ -17,10 +17,9 @@ import ( "sync/atomic" "time" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channelnotifier" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/peernotifier" "github.com/lightningnetwork/lnd/routing/route" @@ -85,14 +84,7 @@ type Config struct { // GetOpenChannels provides a list of existing open channels which is // used to populate the ChannelEventStore with a set of channels on // startup. - GetOpenChannels func() ([]*chanstate.OpenChannel, error) - - // IsPeerOnline returns whether the peer with the given pubkey is - // currently connected. It is used to seed the initial online state of a - // peer when we first start tracking it, so that uptime is calculated - // from the peer's actual connectivity rather than assuming it is - // online. - IsPeerOnline func(route.Vertex) bool + GetOpenChannels func() ([]*channeldb.OpenChannel, error) // Clock is the time source that the subsystem uses, provided here // for ease of testing. @@ -299,8 +291,8 @@ func (c *ChannelEventStore) getOrCreatePeerMonitor( peerMonitor = newPeerLog(c.cfg.Clock, flapCount, lastFlap) c.peers[peer] = peerMonitor - // Send an liveness event given it's the first time we see this peer. - peerMonitor.onlineEvent(c.cfg.IsPeerOnline(peer)) + // Send an online event given it's the first time we see this peer. + peerMonitor.onlineEvent(true) return peerMonitor, nil } diff --git a/chanfitness/chaneventstore_test.go b/chanfitness/chaneventstore_test.go index eed1d12cc..ecec3ea47 100644 --- a/chanfitness/chaneventstore_test.go +++ b/chanfitness/chaneventstore_test.go @@ -6,9 +6,8 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/subscribe" @@ -36,7 +35,7 @@ func TestStartStoreError(t *testing.T) { name string ChannelEvents func() (subscribe.Subscription, error) PeerEvents func() (subscribe.Subscription, error) - GetChannels func() ([]*chanstate.OpenChannel, error) + GetChannels func() ([]*channeldb.OpenChannel, error) }{ { name: "Channel events fail", @@ -51,13 +50,14 @@ func TestStartStoreError(t *testing.T) { name: "Get open channels fails", ChannelEvents: okSubscribeFunc, PeerEvents: okSubscribeFunc, - GetChannels: func() ([]*chanstate.OpenChannel, error) { + GetChannels: func() ([]*channeldb.OpenChannel, error) { return nil, errors.New("intentional test err") }, }, } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { clock := clock.NewTestClock(testNow) @@ -288,59 +288,6 @@ func TestGetChanInfo(t *testing.T) { ctx.stop() } -// TestGetChanInfoOfflinePeer tests that a channel whose peer is offline when we -// start tracking it reports zero uptime, rather than assuming the peer is -// online (which would incorrectly report 100% uptime). -func TestGetChanInfoOfflinePeer(t *testing.T) { - ctx := newChanEventStoreTestCtx(t) - - // Report the peer as offline so that the channel open seeds an offline - // event instead of assuming the peer is connected. - ctx.peerOnline = func(route.Vertex) bool { return false } - - ctx.start() - - now := ctx.clock.Now() - - peer, pk, channel := ctx.newChannel() - ctx.sendChannelOpenedUpdate(pk, channel) - - // Wait for our channel to be recognized by our store. - require.Eventually(t, func() bool { - _, err := ctx.store.GetChanInfo(channel, peer) - return err == nil - }, timeout, time.Millisecond*20) - - // Advance our clock by an hour. Since the peer has been offline the - // whole time, we expect the channel to have a full hour of lifetime but - // zero uptime. - now = now.Add(time.Hour) - ctx.clock.SetTime(now) - - info, err := ctx.store.GetChanInfo(channel, peer) - require.NoError(t, err) - require.Equal(t, time.Hour, info.Lifetime) - require.Equal(t, time.Duration(0), info.Uptime) - - // Once the peer comes online, uptime should start accruing from that - // point. We issue a blocking GetChanInfo afterwards to ensure the - // online event has been fully processed (and timestamped at the current - // time) by the store's main loop before we advance the clock. - ctx.peerEvent(peer, true) - _, err = ctx.store.GetChanInfo(channel, peer) - require.NoError(t, err) - - now = now.Add(time.Hour) - ctx.clock.SetTime(now) - - info, err = ctx.store.GetChanInfo(channel, peer) - require.NoError(t, err) - require.Equal(t, time.Hour*2, info.Lifetime) - require.Equal(t, time.Hour, info.Uptime) - - ctx.stop() -} - // TestFlapCount tests querying the store for peer flap counts, covering the // case where the peer is tracked in memory, and the case where we need to // lookup the peer on disk. diff --git a/chanfitness/chaneventstore_testctx_test.go b/chanfitness/chaneventstore_testctx_test.go index 72a2530c3..aff4c5fca 100644 --- a/chanfitness/chaneventstore_testctx_test.go +++ b/chanfitness/chaneventstore_testctx_test.go @@ -5,11 +5,10 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channelnotifier" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/peernotifier" "github.com/lightningnetwork/lnd/routing/route" @@ -50,13 +49,6 @@ type chanEventStoreTestCtx struct { // used to prevent calling of functions which can only be called after // shutdown. stopped chan struct{} - - // peerOnline determines what the store's IsPeerOnline config returns - // for a peer. It defaults to reporting peers as online so that the - // channel open seeds an online event, matching the historical test - // assumption. Tests that exercise offline peers may override it before - // starting the store. - peerOnline func(route.Vertex) bool } // newChanEventStoreTestCtx creates a test context which can be used to test @@ -70,21 +62,17 @@ func newChanEventStoreTestCtx(t *testing.T) *chanEventStoreTestCtx { flapUpdates: make(peerFlapCountMap), flapCountUpdates: make(chan peerFlapCountMap), stopped: make(chan struct{}), - peerOnline: func(route.Vertex) bool { return true }, } cfg := &Config{ Clock: testCtx.clock, - IsPeerOnline: func(peer route.Vertex) bool { - return testCtx.peerOnline(peer) - }, SubscribeChannelEvents: func() (subscribe.Subscription, error) { return testCtx.channelSubscription, nil }, SubscribePeerEvents: func() (subscribe.Subscription, error) { return testCtx.peerSubscription, nil }, - GetOpenChannels: func() ([]*chanstate.OpenChannel, error) { + GetOpenChannels: func() ([]*channeldb.OpenChannel, error) { return nil, nil }, WriteFlapCount: func(updates map[route.Vertex]*channeldb.FlapCount) error { @@ -193,7 +181,7 @@ func (c *chanEventStoreTestCtx) closeChannel(channel wire.OutPoint, peer *btcec.PublicKey) { update := channelnotifier.ClosedChannelEvent{ - CloseSummary: &chanstate.ChannelCloseSummary{ + CloseSummary: &channeldb.ChannelCloseSummary{ ChanPoint: channel, RemotePub: peer, }, @@ -233,7 +221,7 @@ func (c *chanEventStoreTestCtx) sendChannelOpenedUpdate(pubkey *btcec.PublicKey, channel wire.OutPoint) { update := channelnotifier.OpenChannelEvent{ - Channel: &chanstate.OpenChannel{ + Channel: &channeldb.OpenChannel{ FundingOutpoint: channel, IdentityPub: pubkey, }, diff --git a/chanfitness/interface.go b/chanfitness/interface.go index 23726ae30..22678d650 100644 --- a/chanfitness/interface.go +++ b/chanfitness/interface.go @@ -3,7 +3,7 @@ package chanfitness import ( "time" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" ) // peerMonitor is an interface implemented by entities that monitor our peers diff --git a/chanfitness/rate_limit_test.go b/chanfitness/rate_limit_test.go index 6e5ebf97d..b9bca8086 100644 --- a/chanfitness/rate_limit_test.go +++ b/chanfitness/rate_limit_test.go @@ -39,6 +39,7 @@ func TestGetRateLimit(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -90,6 +91,7 @@ func TestCooldownFlapCount(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/channel_notifier.go b/channel_notifier.go index c700e7ba6..8affd48f0 100644 --- a/channel_notifier.go +++ b/channel_notifier.go @@ -4,11 +4,10 @@ import ( "context" "fmt" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chanbackup" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channelnotifier" - "github.com/lightningnetwork/lnd/chanstate" ) // channelNotifier is an implementation of the chanbackup.ChannelNotifier @@ -47,7 +46,7 @@ func (c *channelNotifier) SubscribeChans(ctx context.Context, // sendChanOpenUpdate is a closure that sends a ChannelEvent to the // chanUpdates channel to inform subscribers about new pending or // confirmed channels. - sendChanOpenUpdate := func(newOrPendingChan *chanstate.OpenChannel) { + sendChanOpenUpdate := func(newOrPendingChan *channeldb.OpenChannel) { _, nodeAddrs, err := c.addrs.AddrsForNode( ctx, newOrPendingChan.IdentityPub, ) diff --git a/channeldb/channel.go b/channeldb/channel.go index 135565c7c..d57647594 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -2,20 +2,28 @@ package channeldb import ( "bytes" + "crypto/hmac" + "crypto/sha256" "encoding/binary" "errors" "fmt" "io" "net" + "strconv" + "strings" + "sync" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/walletdb" - cstate "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" + "github.com/lightningnetwork/lnd/htlcswitch/hop" + "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lntypes" @@ -24,17 +32,15 @@ import ( "github.com/lightningnetwork/lnd/tlv" ) -const ( - // HTLCBlindingPointTLV is the tlv type used for storing blinding - // points with HTLCs. - HTLCBlindingPointTLV tlv.Type = 0 -) - const ( // AbsoluteThawHeightThreshold is the threshold at which a thaw height // begins to be interpreted as an absolute block height, rather than a // relative one. - AbsoluteThawHeightThreshold = cstate.AbsoluteThawHeightThreshold + AbsoluteThawHeightThreshold uint32 = 500000 + + // HTLCBlindingPointTLV is the tlv type used for storing blinding + // points with HTLCs. + HTLCBlindingPointTLV tlv.Type = 0 ) var ( @@ -171,49 +177,50 @@ var ( var ( // ErrNoCommitmentsFound is returned when a channel has not set // commitment states. - ErrNoCommitmentsFound = cstate.ErrNoCommitmentsFound + ErrNoCommitmentsFound = fmt.Errorf("no commitments found") // ErrNoChanInfoFound is returned when a particular channel does not // have any channels state. - ErrNoChanInfoFound = cstate.ErrNoChanInfoFound + ErrNoChanInfoFound = fmt.Errorf("no chan info found") // ErrNoRevocationsFound is returned when revocation state for a // particular channel cannot be found. - ErrNoRevocationsFound = cstate.ErrNoRevocationsFound + ErrNoRevocationsFound = fmt.Errorf("no revocations found") // ErrNoPendingCommit is returned when there is not a pending // commitment for a remote party. A new commitment is written to disk // each time we write a new state in order to be properly fault // tolerant. - ErrNoPendingCommit = cstate.ErrNoPendingCommit + ErrNoPendingCommit = fmt.Errorf("no pending commits found") // ErrNoCommitPoint is returned when no data loss commit point is found // in the database. - ErrNoCommitPoint = cstate.ErrNoCommitPoint + ErrNoCommitPoint = fmt.Errorf("no commit point found") // ErrNoCloseTx is returned when no closing tx is found for a channel // in the state CommitBroadcasted. - ErrNoCloseTx = cstate.ErrNoCloseTx + ErrNoCloseTx = fmt.Errorf("no closing tx found") // ErrNoShutdownInfo is returned when no shutdown info has been // persisted for a channel. - ErrNoShutdownInfo = cstate.ErrNoShutdownInfo + ErrNoShutdownInfo = errors.New("no shutdown info") // ErrNoRestoredChannelMutation is returned when a caller attempts to // mutate a channel that's been recovered. - ErrNoRestoredChannelMutation = cstate.ErrNoRestoredChannelMutation + ErrNoRestoredChannelMutation = fmt.Errorf("cannot mutate restored " + + "channel state") // ErrChanBorked is returned when a caller attempts to mutate a borked // channel. - ErrChanBorked = cstate.ErrChanBorked + ErrChanBorked = fmt.Errorf("cannot mutate borked channel") // ErrMissingIndexEntry is returned when a caller attempts to close a // channel and the outpoint is missing from the index. - ErrMissingIndexEntry = cstate.ErrMissingIndexEntry + ErrMissingIndexEntry = fmt.Errorf("missing outpoint from index") // ErrOnionBlobLength is returned is an onion blob with incorrect // length is read from disk. - ErrOnionBlobLength = cstate.ErrOnionBlobLength + ErrOnionBlobLength = errors.New("onion blob < 1366 bytes") ) const ( @@ -222,27 +229,6 @@ const ( indexStatusType tlv.Type = 0 ) -type ( - // OpenChannel encapsulates the persistent and dynamic state of an open - // channel with a remote node. - OpenChannel = cstate.OpenChannel - - // ChannelCommitment is a snapshot of the commitment state at a - // particular point in the commitment chain. - ChannelCommitment = cstate.ChannelCommitment - - // HTLC is the on-disk representation of a hash time-locked contract. - HTLC = cstate.HTLC - - // LogUpdate represents a pending update to the remote commitment - // chain. - LogUpdate = cstate.LogUpdate - - // CommitDiff represents the delta needed to apply the state - // transition between two subsequent commitment states. - CommitDiff = cstate.CommitDiff -) - // openChannelTlvData houses the new data fields that are stored for each // channel in a TLV stream within the root bucket. This is stored as a TLV // stream appended to the existing hard-coded fields in the channel's root @@ -279,13 +265,6 @@ type openChannelTlvData struct { // confirmationHeight records the block height at which the funding // transaction was first confirmed. confirmationHeight tlv.RecordT[tlv.TlvType8, uint32] - - // closeConfirmationHeight records the block height at which the closing - // transaction was first confirmed. This is used to calculate the - // remaining confirmations until the channel is considered fully closed. - // Note: if not set, it means either the channel has not been - // closed yet, or it was closed before this field was introduced. - closeConfirmationHeight tlv.OptionalRecordT[tlv.TlvType9, uint32] } // encode serializes the openChannelTlvData to the given io.Writer. @@ -308,11 +287,6 @@ func (c *openChannelTlvData) encode(w io.Writer) error { c.customBlob.WhenSome(func(blob tlv.RecordT[tlv.TlvType7, tlv.Blob]) { tlvRecords = append(tlvRecords, blob.Record()) }) - c.closeConfirmationHeight.WhenSome( - func(h tlv.RecordT[tlv.TlvType9, uint32]) { - tlvRecords = append(tlvRecords, h.Record()) - }, - ) tlv.SortRecords(tlvRecords) @@ -330,7 +304,6 @@ func (c *openChannelTlvData) decode(r io.Reader) error { memo := c.memo.Zero() tapscriptRoot := c.tapscriptRoot.Zero() blob := c.customBlob.Zero() - closeConfHeight := c.closeConfirmationHeight.Zero() // Create the tlv stream. tlvStream, err := tlv.NewStream( @@ -342,7 +315,6 @@ func (c *openChannelTlvData) decode(r io.Reader) error { tapscriptRoot.Record(), blob.Record(), c.confirmationHeight.Record(), - closeConfHeight.Record(), ) if err != nil { return err @@ -362,9 +334,6 @@ func (c *openChannelTlvData) decode(r io.Reader) error { if _, ok := tlvs[c.customBlob.TlvType()]; ok { c.customBlob = tlv.SomeRecordT(blob) } - if _, ok := tlvs[closeConfHeight.TlvType()]; ok { - c.closeConfirmationHeight = tlv.SomeRecordT(closeConfHeight) - } return nil } @@ -382,110 +351,251 @@ const ( outpointClosed indexStatus = 1 ) -// isOutpointClosed reports whether the supplied chanKey has been flipped to -// outpointClosed in the supplied outpointBucket. The flip is performed in the -// same transaction as the rest of CloseChannel (sync and tombstone paths -// alike), so a true result is the authoritative "this channel went through -// CloseChannel" signal. On tombstone-enabled backends the chanBucket may still -// exist on disk; readers consult this helper to skip those entries. Callers -// fetch outpointBucket once and pass it in, which lets loop-style readers -// hoist the bucket lookup out of the inner loop. -func isOutpointClosed(opBucket kvdb.RBucket, chanKey []byte) (bool, error) { - if opBucket == nil { - return false, nil - } - raw := opBucket.Get(chanKey) - if raw == nil { - return false, nil - } - - var status uint8 - statusRecord := tlv.MakePrimitiveRecord(indexStatusType, &status) - stream, err := tlv.NewStream(statusRecord) - if err != nil { - return false, err - } - if err := stream.Decode(bytes.NewReader(raw)); err != nil { - return false, fmt.Errorf("decode outpoint status for "+ - "chan_key=%x: %w", chanKey, err) - } - - return indexStatus(status) == outpointClosed, nil -} - // ChannelType is an enum-like type that describes one of several possible -// channel types. -type ChannelType = cstate.ChannelType +// channel types. Each open channel is associated with a particular type as the +// channel type may determine how higher level operations are conducted such as +// fee negotiation, channel closing, the format of HTLCs, etc. Structure-wise, +// a ChannelType is a bit field, with each bit denoting a modification from the +// base channel type of single funder. +type ChannelType uint64 const ( + // NOTE: iota isn't used here for this enum needs to be stable + // long-term as it will be persisted to the database. + // SingleFunderBit represents a channel wherein one party solely funds // the entire capacity of the channel. - SingleFunderBit = cstate.SingleFunderBit + SingleFunderBit ChannelType = 0 // DualFunderBit represents a channel wherein both parties contribute - // funds towards the total capacity of the channel. - DualFunderBit = cstate.DualFunderBit + // funds towards the total capacity of the channel. The channel may be + // funded symmetrically or asymmetrically. + DualFunderBit ChannelType = 1 << 0 // SingleFunderTweaklessBit is similar to the basic SingleFunder channel - // type, but it omits the tweak for one's key. - SingleFunderTweaklessBit = cstate.SingleFunderTweaklessBit + // type, but it omits the tweak for one's key in the commitment + // transaction of the remote party. + SingleFunderTweaklessBit ChannelType = 1 << 1 // NoFundingTxBit denotes if we have the funding transaction locally on - // disk. - NoFundingTxBit = cstate.NoFundingTxBit + // disk. This bit may be on if the funding transaction was crafted by a + // wallet external to the primary daemon. + NoFundingTxBit ChannelType = 1 << 2 // AnchorOutputsBit indicates that the channel makes use of anchor - // outputs to bump the commitment transaction's effective feerate. - AnchorOutputsBit = cstate.AnchorOutputsBit + // outputs to bump the commitment transaction's effective feerate. This + // channel type also uses a delayed to_remote output script. + AnchorOutputsBit ChannelType = 1 << 3 // FrozenBit indicates that the channel is a frozen channel, meaning // that only the responder can decide to cooperatively close the // channel. - FrozenBit = cstate.FrozenBit + FrozenBit ChannelType = 1 << 4 // ZeroHtlcTxFeeBit indicates that the channel should use zero-fee // second-level HTLC transactions. - ZeroHtlcTxFeeBit = cstate.ZeroHtlcTxFeeBit + ZeroHtlcTxFeeBit ChannelType = 1 << 5 // LeaseExpirationBit indicates that the channel has been leased for a - // period of time. - LeaseExpirationBit = cstate.LeaseExpirationBit + // period of time, constraining every output that pays to the channel + // initiator with an additional CLTV of the lease maturity. + LeaseExpirationBit ChannelType = 1 << 6 // ZeroConfBit indicates that the channel is a zero-conf channel. - ZeroConfBit = cstate.ZeroConfBit + ZeroConfBit ChannelType = 1 << 7 // ScidAliasChanBit indicates that the channel has negotiated the // scid-alias channel type. - ScidAliasChanBit = cstate.ScidAliasChanBit + ScidAliasChanBit ChannelType = 1 << 8 // ScidAliasFeatureBit indicates that the scid-alias feature bit was // negotiated during the lifetime of this channel. - ScidAliasFeatureBit = cstate.ScidAliasFeatureBit + ScidAliasFeatureBit ChannelType = 1 << 9 // SimpleTaprootFeatureBit indicates that the simple-taproot-chans // feature bit was negotiated during the lifetime of the channel. - SimpleTaprootFeatureBit = cstate.SimpleTaprootFeatureBit + SimpleTaprootFeatureBit ChannelType = 1 << 10 // TapscriptRootBit indicates that this is a MuSig2 channel with a top - // level tapscript commitment. - TapscriptRootBit = cstate.TapscriptRootBit - - // TaprootFinalBit indicates that this is a MuSig2 channel using the - // final/production taproot scripts and feature bits 80/81. - TaprootFinalBit = cstate.TaprootFinalBit + // level tapscript commitment. This MUST be set along with the + // SimpleTaprootFeatureBit. + TapscriptRootBit ChannelType = 1 << 11 ) +// IsSingleFunder returns true if the channel type if one of the known single +// funder variants. +func (c ChannelType) IsSingleFunder() bool { + return c&DualFunderBit == 0 +} + +// IsDualFunder returns true if the ChannelType has the DualFunderBit set. +func (c ChannelType) IsDualFunder() bool { + return c&DualFunderBit == DualFunderBit +} + +// IsTweakless returns true if the target channel uses a commitment that +// doesn't tweak the key for the remote party. +func (c ChannelType) IsTweakless() bool { + return c&SingleFunderTweaklessBit == SingleFunderTweaklessBit +} + +// HasFundingTx returns true if this channel type is one that has a funding +// transaction stored locally. +func (c ChannelType) HasFundingTx() bool { + return c&NoFundingTxBit == 0 +} + +// HasAnchors returns true if this channel type has anchor outputs on its +// commitment. +func (c ChannelType) HasAnchors() bool { + return c&AnchorOutputsBit == AnchorOutputsBit +} + +// ZeroHtlcTxFee returns true if this channel type uses second-level HTLC +// transactions signed with zero-fee. +func (c ChannelType) ZeroHtlcTxFee() bool { + return c&ZeroHtlcTxFeeBit == ZeroHtlcTxFeeBit +} + +// IsFrozen returns true if the channel is considered to be "frozen". A frozen +// channel means that only the responder can initiate a cooperative channel +// closure. +func (c ChannelType) IsFrozen() bool { + return c&FrozenBit == FrozenBit +} + +// HasLeaseExpiration returns true if the channel originated from a lease. +func (c ChannelType) HasLeaseExpiration() bool { + return c&LeaseExpirationBit == LeaseExpirationBit +} + +// HasZeroConf returns true if the channel is a zero-conf channel. +func (c ChannelType) HasZeroConf() bool { + return c&ZeroConfBit == ZeroConfBit +} + +// HasScidAliasChan returns true if the scid-alias channel type was negotiated. +func (c ChannelType) HasScidAliasChan() bool { + return c&ScidAliasChanBit == ScidAliasChanBit +} + +// HasScidAliasFeature returns true if the scid-alias feature bit was +// negotiated during the lifetime of this channel. +func (c ChannelType) HasScidAliasFeature() bool { + return c&ScidAliasFeatureBit == ScidAliasFeatureBit +} + +// IsTaproot returns true if the channel is using taproot features. +func (c ChannelType) IsTaproot() bool { + return c&SimpleTaprootFeatureBit == SimpleTaprootFeatureBit +} + +// HasTapscriptRoot returns true if the channel is using a top level tapscript +// root commitment. +func (c ChannelType) HasTapscriptRoot() bool { + return c&TapscriptRootBit == TapscriptRootBit +} + // ChannelStateBounds are the parameters from OpenChannel and AcceptChannel -// that bound the abstract channel state. -type ChannelStateBounds = cstate.ChannelStateBounds +// that are responsible for providing bounds on the state space of the abstract +// channel state. These values must be remembered for normal channel operation +// but they do not impact how we compute the commitment transactions themselves. +type ChannelStateBounds struct { + // ChanReserve is an absolute reservation on the channel for the + // owner of this set of constraints. This means that the current + // settled balance for this node CANNOT dip below the reservation + // amount. This acts as a defense against costless attacks when + // either side no longer has any skin in the game. + ChanReserve btcutil.Amount -// CommitmentParams are the parameters from OpenChannel and AcceptChannel that -// are required to render an abstract channel state to a concrete commitment -// transaction. -type CommitmentParams = cstate.CommitmentParams + // MaxPendingAmount is the maximum pending HTLC value that the + // owner of these constraints can offer the remote node at a + // particular time. + MaxPendingAmount lnwire.MilliSatoshi -// ChannelConfig houses the channel configuration for one side of a channel. -type ChannelConfig = cstate.ChannelConfig + // MinHTLC is the minimum HTLC value that the owner of these + // constraints can offer the remote node. If any HTLCs below this + // amount are offered, then the HTLC will be rejected. This, in + // tandem with the dust limit allows a node to regulate the + // smallest HTLC that it deems economically relevant. + MinHTLC lnwire.MilliSatoshi + + // MaxAcceptedHtlcs is the maximum number of HTLCs that the owner of + // this set of constraints can offer the remote node. This allows each + // node to limit their over all exposure to HTLCs that may need to be + // acted upon in the case of a unilateral channel closure or a contract + // breach. + MaxAcceptedHtlcs uint16 +} + +// CommitmentParams are the parameters from OpenChannel and +// AcceptChannel that are required to render an abstract channel state to a +// concrete commitment transaction. These values are necessary to (re)compute +// the commitment transaction. We treat these differently than the state space +// bounds because their history needs to be stored in order to properly handle +// chain resolution. +type CommitmentParams struct { + // DustLimit is the threshold (in satoshis) below which any outputs + // should be trimmed. When an output is trimmed, it isn't materialized + // as an actual output, but is instead burned to miner's fees. + DustLimit btcutil.Amount + + // CsvDelay is the relative time lock delay expressed in blocks. Any + // settled outputs that pay to the owner of this channel configuration + // MUST ensure that the delay branch uses this value as the relative + // time lock. Similarly, any HTLC's offered by this node should use + // this value as well. + CsvDelay uint16 +} + +// ChannelConfig is a struct that houses the various configuration opens for +// channels. Each side maintains an instance of this configuration file as it +// governs: how the funding and commitment transaction to be created, the +// nature of HTLC's allotted, the keys to be used for delivery, and relative +// time lock parameters. +type ChannelConfig struct { + // ChannelStateBounds is the set of constraints that must be + // upheld for the duration of the channel for the owner of this channel + // configuration. Constraints govern a number of flow control related + // parameters, also including the smallest HTLC that will be accepted + // by a participant. + ChannelStateBounds + + // CommitmentParams is an embedding of the parameters + // required to render an abstract channel state into a concrete + // commitment transaction. + CommitmentParams + + // MultiSigKey is the key to be used within the 2-of-2 output script + // for the owner of this channel config. + MultiSigKey keychain.KeyDescriptor + + // RevocationBasePoint is the base public key to be used when deriving + // revocation keys for the remote node's commitment transaction. This + // will be combined along with a per commitment secret to derive a + // unique revocation key for each state. + RevocationBasePoint keychain.KeyDescriptor + + // PaymentBasePoint is the base public key to be used when deriving + // the key used within the non-delayed pay-to-self output on the + // commitment transaction for a node. This will be combined with a + // tweak derived from the per-commitment point to ensure unique keys + // for each commitment transaction. + PaymentBasePoint keychain.KeyDescriptor + + // DelayBasePoint is the base public key to be used when deriving the + // key used within the delayed pay-to-self output on the commitment + // transaction for a node. This will be combined with a tweak derived + // from the per-commitment point to ensure unique keys for each + // commitment transaction. + DelayBasePoint keychain.KeyDescriptor + + // HtlcBasePoint is the base public key to be used when deriving the + // local HTLC key. The derived key (combined with the tweak derived + // from the per-commitment point) is used within the "to self" clause + // within any HTLC output scripts. + HtlcBasePoint keychain.KeyDescriptor +} // commitTlvData stores all the optional data that may be stored as a TLV stream // at the _end_ of the normal serialized commit on disk. @@ -534,15 +644,97 @@ func (c *commitTlvData) decode(r io.Reader) error { return nil } -// amendCommitTlvData updates the commitment with the given auxiliary TLV data. -func amendCommitTlvData(c *ChannelCommitment, auxData commitTlvData) { +// ChannelCommitment is a snapshot of the commitment state at a particular +// point in the commitment chain. With each state transition, a snapshot of the +// current state along with all non-settled HTLCs are recorded. These snapshots +// detail the state of the _remote_ party's commitment at a particular state +// number. For ourselves (the local node) we ONLY store our most recent +// (unrevoked) state for safety purposes. +type ChannelCommitment struct { + // CommitHeight is the update number that this ChannelDelta represents + // the total number of commitment updates to this point. This can be + // viewed as sort of a "commitment height" as this number is + // monotonically increasing. + CommitHeight uint64 + + // LocalLogIndex is the cumulative log index index of the local node at + // this point in the commitment chain. This value will be incremented + // for each _update_ added to the local update log. + LocalLogIndex uint64 + + // LocalHtlcIndex is the current local running HTLC index. This value + // will be incremented for each outgoing HTLC the local node offers. + LocalHtlcIndex uint64 + + // RemoteLogIndex is the cumulative log index index of the remote node + // at this point in the commitment chain. This value will be + // incremented for each _update_ added to the remote update log. + RemoteLogIndex uint64 + + // RemoteHtlcIndex is the current remote running HTLC index. This value + // will be incremented for each outgoing HTLC the remote node offers. + RemoteHtlcIndex uint64 + + // LocalBalance is the current available settled balance within the + // channel directly spendable by us. + // + // NOTE: This is the balance *after* subtracting any commitment fee, + // AND anchor output values. + LocalBalance lnwire.MilliSatoshi + + // RemoteBalance is the current available settled balance within the + // channel directly spendable by the remote node. + // + // NOTE: This is the balance *after* subtracting any commitment fee, + // AND anchor output values. + RemoteBalance lnwire.MilliSatoshi + + // CommitFee is the amount calculated to be paid in fees for the + // current set of commitment transactions. The fee amount is persisted + // with the channel in order to allow the fee amount to be removed and + // recalculated with each channel state update, including updates that + // happen after a system restart. + CommitFee btcutil.Amount + + // FeePerKw is the min satoshis/kilo-weight that should be paid within + // the commitment transaction for the entire duration of the channel's + // lifetime. This field may be updated during normal operation of the + // channel as on-chain conditions change. + // + // TODO(halseth): make this SatPerKWeight. Cannot be done atm because + // this will cause the import cycle lnwallet<->channeldb. Fee + // estimation stuff should be in its own package. + FeePerKw btcutil.Amount + + // CommitTx is the latest version of the commitment state, broadcast + // able by us. + CommitTx *wire.MsgTx + + // CustomBlob is an optional blob that can be used to store information + // specific to a custom channel type. This may track some custom + // specific state for this given commitment. + CustomBlob fn.Option[tlv.Blob] + + // CommitSig is one half of the signature required to fully complete + // the script for the commitment transaction above. This is the + // signature signed by the remote party for our version of the + // commitment transactions. + CommitSig []byte + + // Htlcs is the set of HTLC's that are pending at this particular + // commitment height. + Htlcs []HTLC +} + +// amendTlvData updates the channel with the given auxiliary TLV data. +func (c *ChannelCommitment) amendTlvData(auxData commitTlvData) { auxData.customBlob.WhenSomeV(func(blob tlv.Blob) { c.CustomBlob = fn.Some(blob) }) } -// extractCommitTlvData creates a new commitTlvData from the given commitment. -func extractCommitTlvData(c *ChannelCommitment) commitTlvData { +// extractTlvData creates a new commitTlvData from the given commitment. +func (c *ChannelCommitment) extractTlvData() commitTlvData { var auxData commitTlvData c.CustomBlob.WhenSome(func(blob tlv.Blob) { @@ -556,41 +748,108 @@ func extractCommitTlvData(c *ChannelCommitment) commitTlvData { // ChannelStatus is a bit vector used to indicate whether an OpenChannel is in // the default usable state, or a state where it shouldn't be used. -type ChannelStatus = cstate.ChannelStatus +type ChannelStatus uint64 var ( // ChanStatusDefault is the normal state of an open channel. - ChanStatusDefault = cstate.ChanStatusDefault + ChanStatusDefault ChannelStatus // ChanStatusBorked indicates that the channel has entered an - // irreconcilable state. - ChanStatusBorked = cstate.ChanStatusBorked + // irreconcilable state, triggered by a state desynchronization or + // channel breach. Channels in this state should never be added to the + // htlc switch. + ChanStatusBorked ChannelStatus = 1 // ChanStatusCommitBroadcasted indicates that a commitment for this // channel has been broadcasted. - ChanStatusCommitBroadcasted = cstate.ChanStatusCommitBroadcasted + ChanStatusCommitBroadcasted ChannelStatus = 1 << 1 // ChanStatusLocalDataLoss indicates that we have lost channel state - // for this channel. - ChanStatusLocalDataLoss = cstate.ChanStatusLocalDataLoss - - // ChanStatusRestored signals that the channel has been restored and - // doesn't have all fields a typical channel will have. - ChanStatusRestored = cstate.ChanStatusRestored - - // ChanStatusCoopBroadcasted indicates that a cooperative close for this - // channel has been broadcasted. - ChanStatusCoopBroadcasted = cstate.ChanStatusCoopBroadcasted - - // ChanStatusLocalCloseInitiator indicates that we initiated closing the + // for this channel, and broadcasting our latest commitment might be + // considered a breach. + // + // TODO(halseh): actually enforce that we are not force closing such a // channel. - ChanStatusLocalCloseInitiator = cstate.ChanStatusLocalCloseInitiator + ChanStatusLocalDataLoss ChannelStatus = 1 << 2 + + // ChanStatusRestored is a status flag that signals that the channel + // has been restored, and doesn't have all the fields a typical channel + // will have. + ChanStatusRestored ChannelStatus = 1 << 3 + + // ChanStatusCoopBroadcasted indicates that a cooperative close for + // this channel has been broadcasted. Older cooperatively closed + // channels will only have this status set. Newer ones will also have + // close initiator information stored using the local/remote initiator + // status. This status is set in conjunction with the initiator status + // so that we do not need to check multiple channel statues for + // cooperative closes. + ChanStatusCoopBroadcasted ChannelStatus = 1 << 4 + + // ChanStatusLocalCloseInitiator indicates that we initiated closing + // the channel. + ChanStatusLocalCloseInitiator ChannelStatus = 1 << 5 // ChanStatusRemoteCloseInitiator indicates that the remote node // initiated closing the channel. - ChanStatusRemoteCloseInitiator = cstate.ChanStatusRemoteCloseInitiator + ChanStatusRemoteCloseInitiator ChannelStatus = 1 << 6 ) +// chanStatusStrings maps a ChannelStatus to a human friendly string that +// describes that status. +var chanStatusStrings = map[ChannelStatus]string{ + ChanStatusDefault: "ChanStatusDefault", + ChanStatusBorked: "ChanStatusBorked", + ChanStatusCommitBroadcasted: "ChanStatusCommitBroadcasted", + ChanStatusLocalDataLoss: "ChanStatusLocalDataLoss", + ChanStatusRestored: "ChanStatusRestored", + ChanStatusCoopBroadcasted: "ChanStatusCoopBroadcasted", + ChanStatusLocalCloseInitiator: "ChanStatusLocalCloseInitiator", + ChanStatusRemoteCloseInitiator: "ChanStatusRemoteCloseInitiator", +} + +// orderedChanStatusFlags is an in-order list of all that channel status flags. +var orderedChanStatusFlags = []ChannelStatus{ + ChanStatusBorked, + ChanStatusCommitBroadcasted, + ChanStatusLocalDataLoss, + ChanStatusRestored, + ChanStatusCoopBroadcasted, + ChanStatusLocalCloseInitiator, + ChanStatusRemoteCloseInitiator, +} + +// String returns a human-readable representation of the ChannelStatus. +func (c ChannelStatus) String() string { + // If no flags are set, then this is the default case. + if c == ChanStatusDefault { + return chanStatusStrings[ChanStatusDefault] + } + + // Add individual bit flags. + statusStr := "" + for _, flag := range orderedChanStatusFlags { + if c&flag == flag { + statusStr += chanStatusStrings[flag] + "|" + c -= flag + } + } + + // Remove anything to the right of the final bar, including it as well. + statusStr = strings.TrimRight(statusStr, "|") + + // Add any remaining flags which aren't accounted for as hex. + if c != 0 { + statusStr += "|0x" + strconv.FormatUint(uint64(c), 16) + } + + // If this was purely an unknown flag, then remove the extra bar at the + // start of the string. + statusStr = strings.TrimLeft(statusStr, "|") + + return statusStr +} + // FinalHtlcByte defines a byte type that encodes information about the final // htlc resolution. type FinalHtlcByte byte @@ -605,85 +864,422 @@ const ( FinalHtlcOffchainBit FinalHtlcByte = 1 << 1 ) -// amendOpenChannelTlvData updates the channel with the given auxiliary TLV -// data. -func amendOpenChannelTlvData(channel *OpenChannel, auxData openChannelTlvData) { - channel.RevocationKeyLocator = auxData.revokeKeyLoc.Val.KeyLocator - channel.InitialLocalBalance = lnwire.MilliSatoshi( +// OpenChannel encapsulates the persistent and dynamic state of an open channel +// with a remote node. An open channel supports several options for on-disk +// serialization depending on the exact context. Full (upon channel creation) +// state commitments, and partial (due to a commitment update) writes are +// supported. Each partial write due to a state update appends the new update +// to an on-disk log, which can then subsequently be queried in order to +// "time-travel" to a prior state. +type OpenChannel struct { + // ChanType denotes which type of channel this is. + ChanType ChannelType + + // ChainHash is a hash which represents the blockchain that this + // channel will be opened within. This value is typically the genesis + // hash. In the case that the original chain went through a contentious + // hard-fork, then this value will be tweaked using the unique fork + // point on each branch. + ChainHash chainhash.Hash + + // FundingOutpoint is the outpoint of the final funding transaction. + // This value uniquely and globally identifies the channel within the + // target blockchain as specified by the chain hash parameter. + FundingOutpoint wire.OutPoint + + // ShortChannelID encodes the exact location in the chain in which the + // channel was initially confirmed. This includes: the block height, + // transaction index, and the output within the target transaction. + // + // If IsZeroConf(), then this will the "base" (very first) ALIAS scid + // and the confirmed SCID will be stored in ConfirmedScid. + ShortChannelID lnwire.ShortChannelID + + // IsPending indicates whether a channel's funding transaction has been + // confirmed. + IsPending bool + + // IsInitiator is a bool which indicates if we were the original + // initiator for the channel. This value may affect how higher levels + // negotiate fees, or close the channel. + IsInitiator bool + + // chanStatus is the current status of this channel. If it is not in + // the state Default, it should not be used for forwarding payments. + chanStatus ChannelStatus + + // FundingBroadcastHeight is the height in which the funding + // transaction was broadcast. This value can be used by higher level + // sub-systems to determine if a channel is stale and/or should have + // been confirmed before a certain height. + FundingBroadcastHeight uint32 + + // ConfirmationHeight records the block height at which the funding + // transaction was first confirmed. + ConfirmationHeight uint32 + + // NumConfsRequired is the number of confirmations a channel's funding + // transaction must have received in order to be considered available + // for normal transactional use. + NumConfsRequired uint16 + + // ChannelFlags holds the flags that were sent as part of the + // open_channel message. + ChannelFlags lnwire.FundingFlag + + // IdentityPub is the identity public key of the remote node this + // channel has been established with. + IdentityPub *btcec.PublicKey + + // Capacity is the total capacity of this channel. + Capacity btcutil.Amount + + // TotalMSatSent is the total number of milli-satoshis we've sent + // within this channel. + TotalMSatSent lnwire.MilliSatoshi + + // TotalMSatReceived is the total number of milli-satoshis we've + // received within this channel. + TotalMSatReceived lnwire.MilliSatoshi + + // InitialLocalBalance is the balance we have during the channel + // opening. When we are not the initiator, this value represents the + // push amount. + InitialLocalBalance lnwire.MilliSatoshi + + // InitialRemoteBalance is the balance they have during the channel + // opening. + InitialRemoteBalance lnwire.MilliSatoshi + + // LocalChanCfg is the channel configuration for the local node. + LocalChanCfg ChannelConfig + + // RemoteChanCfg is the channel configuration for the remote node. + RemoteChanCfg ChannelConfig + + // LocalCommitment is the current local commitment state for the local + // party. This is stored distinct from the state of the remote party + // as there are certain asymmetric parameters which affect the + // structure of each commitment. + LocalCommitment ChannelCommitment + + // RemoteCommitment is the current remote commitment state for the + // remote party. This is stored distinct from the state of the local + // party as there are certain asymmetric parameters which affect the + // structure of each commitment. + RemoteCommitment ChannelCommitment + + // RemoteCurrentRevocation is the current revocation for their + // commitment transaction. However, since this the derived public key, + // we don't yet have the private key so we aren't yet able to verify + // that it's actually in the hash chain. + RemoteCurrentRevocation *btcec.PublicKey + + // RemoteNextRevocation is the revocation key to be used for the *next* + // commitment transaction we create for the local node. Within the + // specification, this value is referred to as the + // per-commitment-point. + RemoteNextRevocation *btcec.PublicKey + + // RevocationProducer is used to generate the revocation in such a way + // that remote side might store it efficiently and have the ability to + // restore the revocation by index if needed. Current implementation of + // secret producer is shachain producer. + RevocationProducer shachain.Producer + + // RevocationStore is used to efficiently store the revocations for + // previous channels states sent to us by remote side. Current + // implementation of secret store is shachain store. + RevocationStore shachain.Store + + // Packager is used to create and update forwarding packages for this + // channel, which encodes all necessary information to recover from + // failures and reforward HTLCs that were not fully processed. + Packager FwdPackager + + // FundingTxn is the transaction containing this channel's funding + // outpoint. Upon restarts, this txn will be rebroadcast if the channel + // is found to be pending. + // + // NOTE: This value will only be populated for single-funder channels + // for which we are the initiator, and that we also have the funding + // transaction for. One can check this by using the HasFundingTx() + // method on the ChanType field. + FundingTxn *wire.MsgTx + + // LocalShutdownScript is set to a pre-set script if the channel was opened + // by the local node with option_upfront_shutdown_script set. If the option + // was not set, the field is empty. + LocalShutdownScript lnwire.DeliveryAddress + + // RemoteShutdownScript is set to a pre-set script if the channel was opened + // by the remote node with option_upfront_shutdown_script set. If the option + // was not set, the field is empty. + RemoteShutdownScript lnwire.DeliveryAddress + + // ThawHeight is the height when a frozen channel once again becomes a + // normal channel. If this is zero, then there're no restrictions on + // this channel. If the value is lower than 500,000, then it's + // interpreted as a relative height, or an absolute height otherwise. + ThawHeight uint32 + + // LastWasRevoke is a boolean that determines if the last update we sent + // was a revocation (true) or a commitment signature (false). + LastWasRevoke bool + + // RevocationKeyLocator stores the KeyLocator information that we will + // need to derive the shachain root for this channel. This allows us to + // have private key isolation from lnd. + RevocationKeyLocator keychain.KeyLocator + + // confirmedScid is the confirmed ShortChannelID for a zero-conf + // channel. If the channel is unconfirmed, then this will be the + // default ShortChannelID. This is only set for zero-conf channels. + confirmedScid lnwire.ShortChannelID + + // Memo is any arbitrary information we wish to store locally about the + // channel that will be useful to our future selves. + Memo []byte + + // TapscriptRoot is an optional tapscript root used to derive the MuSig2 + // funding output. + TapscriptRoot fn.Option[chainhash.Hash] + + // CustomBlob is an optional blob that can be used to store information + // specific to a custom channel type. This information is only created + // at channel funding time, and after wards is to be considered + // immutable. + CustomBlob fn.Option[tlv.Blob] + + // TODO(roasbeef): eww + Db *ChannelStateDB + + // TODO(roasbeef): just need to store local and remote HTLC's? + + sync.RWMutex +} + +// String returns a string representation of the channel. +func (c *OpenChannel) String() string { + indexStr := "height=%v, local_htlc_index=%v, local_log_index=%v, " + + "remote_htlc_index=%v, remote_log_index=%v" + + commit := c.LocalCommitment + local := fmt.Sprintf(indexStr, commit.CommitHeight, + commit.LocalHtlcIndex, commit.LocalLogIndex, + commit.RemoteHtlcIndex, commit.RemoteLogIndex, + ) + + commit = c.RemoteCommitment + remote := fmt.Sprintf(indexStr, commit.CommitHeight, + commit.LocalHtlcIndex, commit.LocalLogIndex, + commit.RemoteHtlcIndex, commit.RemoteLogIndex, + ) + + return fmt.Sprintf("SCID=%v, status=%v, initiator=%v, pending=%v, "+ + "local commitment has %s, remote commitment has %s", + c.ShortChannelID, c.chanStatus, c.IsInitiator, c.IsPending, + local, remote, + ) +} + +// Initiator returns the ChannelParty that originally opened this channel. +func (c *OpenChannel) Initiator() lntypes.ChannelParty { + c.RLock() + defer c.RUnlock() + + if c.IsInitiator { + return lntypes.Local + } + + return lntypes.Remote +} + +// ShortChanID returns the current ShortChannelID of this channel. +func (c *OpenChannel) ShortChanID() lnwire.ShortChannelID { + c.RLock() + defer c.RUnlock() + + return c.ShortChannelID +} + +// ZeroConfRealScid returns the zero-conf channel's confirmed scid. This should +// only be called if IsZeroConf returns true. +func (c *OpenChannel) ZeroConfRealScid() lnwire.ShortChannelID { + c.RLock() + defer c.RUnlock() + + return c.confirmedScid +} + +// ZeroConfConfirmed returns whether the zero-conf channel has confirmed. This +// should only be called if IsZeroConf returns true. +func (c *OpenChannel) ZeroConfConfirmed() bool { + c.RLock() + defer c.RUnlock() + + return c.confirmedScid != hop.Source +} + +// IsZeroConf returns whether the option_zeroconf channel type was negotiated. +func (c *OpenChannel) IsZeroConf() bool { + c.RLock() + defer c.RUnlock() + + return c.ChanType.HasZeroConf() +} + +// IsOptionScidAlias returns whether the option_scid_alias channel type was +// negotiated. +func (c *OpenChannel) IsOptionScidAlias() bool { + c.RLock() + defer c.RUnlock() + + return c.ChanType.HasScidAliasChan() +} + +// NegotiatedAliasFeature returns whether the option-scid-alias feature bit was +// negotiated. +func (c *OpenChannel) NegotiatedAliasFeature() bool { + c.RLock() + defer c.RUnlock() + + return c.ChanType.HasScidAliasFeature() +} + +// ChanStatus returns the current ChannelStatus of this channel. +func (c *OpenChannel) ChanStatus() ChannelStatus { + c.RLock() + defer c.RUnlock() + + return c.chanStatus +} + +// ApplyChanStatus allows the caller to modify the internal channel state in a +// thead-safe manner. +func (c *OpenChannel) ApplyChanStatus(status ChannelStatus) error { + c.Lock() + defer c.Unlock() + + return c.putChanStatus(status) +} + +// ClearChanStatus allows the caller to clear a particular channel status from +// the primary channel status bit field. After this method returns, a call to +// HasChanStatus(status) should return false. +func (c *OpenChannel) ClearChanStatus(status ChannelStatus) error { + c.Lock() + defer c.Unlock() + + return c.clearChanStatus(status) +} + +// HasChanStatus returns true if the internal bitfield channel status of the +// target channel has the specified status bit set. +func (c *OpenChannel) HasChanStatus(status ChannelStatus) bool { + c.RLock() + defer c.RUnlock() + + return c.hasChanStatus(status) +} + +func (c *OpenChannel) hasChanStatus(status ChannelStatus) bool { + // Special case ChanStatusDefualt since it isn't actually flag, but a + // particular combination (or lack-there-of) of flags. + if status == ChanStatusDefault { + return c.chanStatus == ChanStatusDefault + } + + return c.chanStatus&status == status +} + +// BroadcastHeight returns the height at which the funding tx was broadcast. +func (c *OpenChannel) BroadcastHeight() uint32 { + c.RLock() + defer c.RUnlock() + + return c.FundingBroadcastHeight +} + +// SetBroadcastHeight sets the FundingBroadcastHeight. +func (c *OpenChannel) SetBroadcastHeight(height uint32) { + c.Lock() + defer c.Unlock() + + c.FundingBroadcastHeight = height +} + +// amendTlvData updates the channel with the given auxiliary TLV data. +func (c *OpenChannel) amendTlvData(auxData openChannelTlvData) { + c.RevocationKeyLocator = auxData.revokeKeyLoc.Val.KeyLocator + c.InitialLocalBalance = lnwire.MilliSatoshi( auxData.initialLocalBalance.Val, ) - channel.InitialRemoteBalance = lnwire.MilliSatoshi( + c.InitialRemoteBalance = lnwire.MilliSatoshi( auxData.initialRemoteBalance.Val, ) - channel.SetConfirmedScidForStore(auxData.realScid.Val) - channel.ConfirmationHeight = auxData.confirmationHeight.Val + c.confirmedScid = auxData.realScid.Val + c.ConfirmationHeight = auxData.confirmationHeight.Val auxData.memo.WhenSomeV(func(memo []byte) { - channel.Memo = memo + c.Memo = memo }) auxData.tapscriptRoot.WhenSomeV(func(h [32]byte) { - channel.TapscriptRoot = fn.Some[chainhash.Hash](h) + c.TapscriptRoot = fn.Some[chainhash.Hash](h) }) auxData.customBlob.WhenSomeV(func(blob tlv.Blob) { - channel.CustomBlob = fn.Some(blob) - }) - auxData.closeConfirmationHeight.WhenSomeV(func(h uint32) { - channel.CloseConfirmationHeight = fn.Some(h) + c.CustomBlob = fn.Some(blob) }) } -// extractOpenChannelTlvData creates a new openChannelTlvData from the given -// channel. -func extractOpenChannelTlvData(channel *OpenChannel) openChannelTlvData { +// extractTlvData creates a new openChannelTlvData from the given channel. +func (c *OpenChannel) extractTlvData() openChannelTlvData { auxData := openChannelTlvData{ revokeKeyLoc: tlv.NewRecordT[tlv.TlvType1]( - keyLocRecord{channel.RevocationKeyLocator}, + keyLocRecord{c.RevocationKeyLocator}, ), initialLocalBalance: tlv.NewPrimitiveRecord[tlv.TlvType2]( - uint64(channel.InitialLocalBalance), + uint64(c.InitialLocalBalance), ), initialRemoteBalance: tlv.NewPrimitiveRecord[tlv.TlvType3]( - uint64(channel.InitialRemoteBalance), + uint64(c.InitialRemoteBalance), ), realScid: tlv.NewRecordT[tlv.TlvType4]( - channel.ConfirmedScidForStore(), + c.confirmedScid, ), confirmationHeight: tlv.NewPrimitiveRecord[tlv.TlvType8]( - channel.ConfirmationHeight, + c.ConfirmationHeight, ), } - if len(channel.Memo) != 0 { + if len(c.Memo) != 0 { auxData.memo = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType5](channel.Memo), + tlv.NewPrimitiveRecord[tlv.TlvType5](c.Memo), ) } - channel.TapscriptRoot.WhenSome(func(h chainhash.Hash) { + c.TapscriptRoot.WhenSome(func(h chainhash.Hash) { auxData.tapscriptRoot = tlv.SomeRecordT( tlv.NewPrimitiveRecord[tlv.TlvType6, [32]byte](h), ) }) - channel.CustomBlob.WhenSome(func(blob tlv.Blob) { + c.CustomBlob.WhenSome(func(blob tlv.Blob) { auxData.customBlob = tlv.SomeRecordT( tlv.NewPrimitiveRecord[tlv.TlvType7](blob), ) }) - channel.CloseConfirmationHeight.WhenSome(func(h uint32) { - auxData.closeConfirmationHeight = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType9](h), - ) - }) return auxData } -// RefreshChannel updates the in-memory channel state using the latest state -// observed on disk. -func (c *ChannelStateDB) RefreshChannel(channel *OpenChannel) error { - return kvdb.View(c.backend, func(tx kvdb.RTx) error { +// Refresh updates the in-memory channel state using the latest state observed +// on disk. +func (c *OpenChannel) Refresh() error { + c.Lock() + defer c.Unlock() + + err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error { chanBucket, err := fetchChanBucket( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) if err != nil { return err @@ -691,27 +1287,30 @@ func (c *ChannelStateDB) RefreshChannel(channel *OpenChannel) error { // We'll re-populating the in-memory channel with the info // fetched from disk. - if err := fetchChanInfo(chanBucket, channel); err != nil { + if err := fetchChanInfo(chanBucket, c); err != nil { return fmt.Errorf("unable to fetch chan info: %w", err) } // Also populate the channel's commitment states for both sides // of the channel. - err = fetchChanCommitments(chanBucket, channel) - if err != nil { + if err := fetchChanCommitments(chanBucket, c); err != nil { return fmt.Errorf("unable to fetch chan commitments: "+ "%v", err) } // Also retrieve the current revocation state. - err = fetchChanRevocationState(chanBucket, channel) - if err != nil { + if err := fetchChanRevocationState(chanBucket, c); err != nil { return fmt.Errorf("unable to fetch chan revocations: "+ "%v", err) } return nil }, func() {}) + if err != nil { + return err + } + + return nil } // fetchChanBucket is a helper function that returns the bucket where a @@ -751,20 +1350,7 @@ func fetchChanBucket(tx kvdb.RTx, nodeKey *btcec.PublicKey, if err := graphdb.WriteOutpoint(&chanPointBuf, outPoint); err != nil { return nil, err } - chanKey := chanPointBuf.Bytes() - - // Treat already-closed channels as gone. The chanBucket may still - // exist on tombstone-enabled backends; the outpoint flip is the - // source of truth. - closed, err := isOutpointClosed(tx.ReadBucket(outpointBucket), chanKey) - if err != nil { - return nil, err - } - if closed { - return nil, ErrChannelNotFound - } - - chanBucket := chainBucket.NestedReadBucket(chanKey) + chanBucket := chainBucket.NestedReadBucket(chanPointBuf.Bytes()) if chanBucket == nil { return nil, ErrChannelNotFound } @@ -811,20 +1397,7 @@ func fetchChanBucketRw(tx kvdb.RwTx, nodeKey *btcec.PublicKey, if err := graphdb.WriteOutpoint(&chanPointBuf, outPoint); err != nil { return nil, err } - chanKey := chanPointBuf.Bytes() - - // Treat already-closed channels as gone. The chanBucket may still - // exist on tombstone-enabled backends; the outpoint flip is the - // source of truth. - closed, err := isOutpointClosed(tx.ReadBucket(outpointBucket), chanKey) - if err != nil { - return nil, err - } - if closed { - return nil, ErrChannelNotFound - } - - chanBucket := chainBucket.NestedReadWriteBucket(chanKey) + chanBucket := chainBucket.NestedReadWriteBucket(chanPointBuf.Bytes()) if chanBucket == nil { return nil, ErrChannelNotFound } @@ -852,9 +1425,9 @@ func fetchFinalHtlcsBucketRw(tx kvdb.RwTx, return chanBucket, nil } -// fullSyncOpenChannel syncs the contents of an OpenChannel while re-using an -// existing database transaction. -func fullSyncOpenChannel(tx kvdb.RwTx, c *OpenChannel) error { +// fullSync syncs the contents of an OpenChannel while re-using an existing +// database transaction. +func (c *OpenChannel) fullSync(tx kvdb.RwTx) error { // Fetch the outpoint bucket and check if the outpoint already exists. opBucket := tx.ReadWriteBucket(outpointBucket) if opBucket == nil { @@ -944,145 +1517,142 @@ func fullSyncOpenChannel(tx kvdb.RwTx, c *OpenChannel) error { return putOpenChannel(chanBucket, c) } -// MarkChannelConfirmationHeight updates the channel's confirmation height once -// the channel opening transaction receives one confirmation. -func (c *ChannelStateDB) MarkChannelConfirmationHeight(channel *OpenChannel, - height uint32) error { +// MarkConfirmationHeight updates the channel's confirmation height once the +// channel opening transaction receives one confirmation. +func (c *OpenChannel) MarkConfirmationHeight(height uint32) error { + c.Lock() + defer c.Unlock() - return kvdb.Update(c.backend, func(tx kvdb.RwTx) error { + if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { chanBucket, err := fetchChanBucketRw( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) if err != nil { return err } - diskChannel, err := fetchOpenChannel( - chanBucket, &channel.FundingOutpoint, - ) + channel, err := fetchOpenChannel(chanBucket, &c.FundingOutpoint) if err != nil { return err } - diskChannel.ConfirmationHeight = height + channel.ConfirmationHeight = height - return putOpenChannel(chanBucket, diskChannel) - }, func() {}) + return putOpenChannel(chanBucket, channel) + }, func() {}); err != nil { + return err + } + + c.ConfirmationHeight = height + + return nil } -// MarkChannelCloseConfirmationHeight updates the channel's close confirmation -// height when the closing transaction is first detected in a block. -func (c *ChannelStateDB) MarkChannelCloseConfirmationHeight( - channel *OpenChannel, height fn.Option[uint32]) error { - - return kvdb.Update(c.backend, func(tx kvdb.RwTx) error { - chanBucket, err := fetchChanBucketRw( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, - ) - if err != nil { - return err - } - - diskChannel, err := fetchOpenChannel( - chanBucket, &channel.FundingOutpoint, - ) - if err != nil { - return err - } - - diskChannel.CloseConfirmationHeight = height - - return putOpenChannel(chanBucket, diskChannel) - }, func() {}) -} - -// MarkChannelOpen marks a channel as fully open given a locator that uniquely +// MarkAsOpen marks a channel as fully open given a locator that uniquely // describes its location within the chain. -func (c *ChannelStateDB) MarkChannelOpen(channel *OpenChannel, - openLoc lnwire.ShortChannelID) error { +func (c *OpenChannel) MarkAsOpen(openLoc lnwire.ShortChannelID) error { + c.Lock() + defer c.Unlock() - return kvdb.Update(c.backend, func(tx kvdb.RwTx) error { + if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { chanBucket, err := fetchChanBucketRw( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) if err != nil { return err } - diskChannel, err := fetchOpenChannel( - chanBucket, &channel.FundingOutpoint, - ) + channel, err := fetchOpenChannel(chanBucket, &c.FundingOutpoint) if err != nil { return err } - diskChannel.IsPending = false - diskChannel.ShortChannelID = openLoc + channel.IsPending = false + channel.ShortChannelID = openLoc - return putOpenChannel(chanBucket, diskChannel) - }, func() {}) + return putOpenChannel(chanBucket, channel) + }, func() {}); err != nil { + return err + } + + c.IsPending = false + c.ShortChannelID = openLoc + c.Packager = NewChannelPackager(openLoc) + + return nil } -// MarkChannelRealScid marks the zero-conf channel's confirmed ShortChannelID. -func (c *ChannelStateDB) MarkChannelRealScid(channel *OpenChannel, - realScid lnwire.ShortChannelID) error { +// MarkRealScid marks the zero-conf channel's confirmed ShortChannelID. This +// should only be done if IsZeroConf returns true. +func (c *OpenChannel) MarkRealScid(realScid lnwire.ShortChannelID) error { + c.Lock() + defer c.Unlock() - return kvdb.Update(c.backend, func(tx kvdb.RwTx) error { + if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { chanBucket, err := fetchChanBucketRw( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) if err != nil { return err } - diskChannel, err := fetchOpenChannel( - chanBucket, &channel.FundingOutpoint, + channel, err := fetchOpenChannel( + chanBucket, &c.FundingOutpoint, ) if err != nil { return err } - diskChannel.SetConfirmedScidForStore(realScid) + channel.confirmedScid = realScid - return putOpenChannel(chanBucket, diskChannel) - }, func() {}) + return putOpenChannel(chanBucket, channel) + }, func() {}); err != nil { + return err + } + + c.confirmedScid = realScid + + return nil } -// MarkChannelScidAliasNegotiated adds ScidAliasFeatureBit to ChanType in the -// database. -func (c *ChannelStateDB) MarkChannelScidAliasNegotiated( - channel *OpenChannel) error { +// MarkScidAliasNegotiated adds ScidAliasFeatureBit to ChanType in-memory and +// in the database. +func (c *OpenChannel) MarkScidAliasNegotiated() error { + c.Lock() + defer c.Unlock() - return kvdb.Update(c.backend, func(tx kvdb.RwTx) error { + if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { chanBucket, err := fetchChanBucketRw( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) if err != nil { return err } - diskChannel, err := fetchOpenChannel( - chanBucket, &channel.FundingOutpoint, + channel, err := fetchOpenChannel( + chanBucket, &c.FundingOutpoint, ) if err != nil { return err } - diskChannel.ChanType |= ScidAliasFeatureBit + channel.ChanType |= ScidAliasFeatureBit + return putOpenChannel(chanBucket, channel) + }, func() {}); err != nil { + return err + } - return putOpenChannel(chanBucket, diskChannel) - }, func() {}) + c.ChanType |= ScidAliasFeatureBit + + return nil } -// MarkChannelDataLoss marks the channel as local-data-loss and stores the -// commit point needed if the remote force closes. -func (c *ChannelStateDB) MarkChannelDataLoss(channel *OpenChannel, - commitPoint *btcec.PublicKey) error { +// MarkDataLoss marks sets the channel status to LocalDataLoss and stores the +// passed commitPoint for use to retrieve funds in case the remote force closes +// the channel. +func (c *OpenChannel) MarkDataLoss(commitPoint *btcec.PublicKey) error { + c.Lock() + defer c.Unlock() var b bytes.Buffer if err := WriteElement(&b, commitPoint); err != nil { @@ -1093,20 +1663,17 @@ func (c *ChannelStateDB) MarkChannelDataLoss(channel *OpenChannel, return chanBucket.Put(dataLossCommitPointKey, b.Bytes()) } - return c.putChanStatus(channel, ChanStatusLocalDataLoss, putCommitPoint) + return c.putChanStatus(ChanStatusLocalDataLoss, putCommitPoint) } -// FetchChannelDataLossCommitPoint retrieves the commit point stored when the -// channel was marked as local-data-loss. -func (c *ChannelStateDB) FetchChannelDataLossCommitPoint( - channel *OpenChannel) (*btcec.PublicKey, error) { - +// DataLossCommitPoint retrieves the stored commit point set during +// MarkDataLoss. If not found ErrNoCommitPoint is returned. +func (c *OpenChannel) DataLossCommitPoint() (*btcec.PublicKey, error) { var commitPoint *btcec.PublicKey - err := kvdb.View(c.backend, func(tx kvdb.RTx) error { + err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error { chanBucket, err := fetchChanBucket( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) switch err { case nil: @@ -1136,35 +1703,223 @@ func (c *ChannelStateDB) FetchChannelDataLossCommitPoint( return commitPoint, nil } -// MarkChannelBorked marks the channel as irreconcilable. -func (c *ChannelStateDB) MarkChannelBorked(channel *OpenChannel) error { - return c.ApplyChannelStatus(channel, ChanStatusBorked) +// MarkBorked marks the event when the channel as reached an irreconcilable +// state, such as a channel breach or state desynchronization. Borked channels +// should never be added to the switch. +func (c *OpenChannel) MarkBorked() error { + c.Lock() + defer c.Unlock() + + return c.putChanStatus(ChanStatusBorked) +} + +// SecondCommitmentPoint returns the second per-commitment-point for use in the +// channel_ready message. +func (c *OpenChannel) SecondCommitmentPoint() (*btcec.PublicKey, error) { + c.RLock() + defer c.RUnlock() + + // Since we start at commitment height = 0, the second per commitment + // point is actually at the 1st index. + revocation, err := c.RevocationProducer.AtIndex(1) + if err != nil { + return nil, err + } + + return input.ComputeCommitmentPoint(revocation[:]), nil } var ( - // DeriveMusig2Shachain derives a shachain producer for the taproot - // channel from normal shachain revocation root. - DeriveMusig2Shachain = cstate.DeriveMusig2Shachain - - // NewMusigVerificationNonce generates the local or verification nonce - // for another musig2 session. - NewMusigVerificationNonce = cstate.NewMusigVerificationNonce + // taprootRevRootKey is the key used to derive the revocation root for + // the taproot nonces. This is done via HMAC of the existing revocation + // root. + taprootRevRootKey = []byte("taproot-rev-root") ) -// StoreChannelShutdownInfo persists the ShutdownInfo for the target channel. -func (c *ChannelStateDB) StoreChannelShutdownInfo(channel *OpenChannel, - info *ShutdownInfo) error { +// DeriveMusig2Shachain derives a shachain producer for the taproot channel +// from normal shachain revocation root. +func DeriveMusig2Shachain(revRoot shachain.Producer) (shachain.Producer, error) { //nolint:ll + // In order to obtain the revocation root hash to create the taproot + // revocation, we'll encode the producer into a buffer, then use that + // to derive the shachain root needed. + var rootHashBuf bytes.Buffer + if err := revRoot.Encode(&rootHashBuf); err != nil { + return nil, fmt.Errorf("unable to encode producer: %w", err) + } + revRootHash := chainhash.HashH(rootHashBuf.Bytes()) + + // For taproot channel types, we'll also generate a distinct shachain + // root using the same seed information. We'll use this to generate + // verification nonces for the channel. We'll bind with this a simple + // hmac. + taprootRevHmac := hmac.New(sha256.New, taprootRevRootKey) + if _, err := taprootRevHmac.Write(revRootHash[:]); err != nil { + return nil, err + } + + taprootRevRoot := taprootRevHmac.Sum(nil) + + // Once we have the root, we can then generate our shachain producer + // and from that generate the per-commitment point. + return shachain.NewRevocationProducerFromBytes( + taprootRevRoot, + ) +} + +// NewMusigVerificationNonce generates the local or verification nonce for +// another musig2 session. In order to permit our implementation to not have to +// write any secret nonce state to disk, we'll use the _next_ shachain +// pre-image as our primary randomness source. When used to generate the nonce +// again to broadcast our commitment hte current height will be used. +func NewMusigVerificationNonce(pubKey *btcec.PublicKey, targetHeight uint64, + shaGen shachain.Producer) (*musig2.Nonces, error) { + + // Now that we know what height we need, we'll grab the shachain + // pre-image at the target destination. + nextPreimage, err := shaGen.AtIndex(targetHeight) + if err != nil { + return nil, err + } + + shaChainRand := musig2.WithCustomRand(bytes.NewBuffer(nextPreimage[:])) + pubKeyOpt := musig2.WithPublicKey(pubKey) + + return musig2.GenNonces(pubKeyOpt, shaChainRand) +} + +// ChanSyncMsg returns the ChannelReestablish message that should be sent upon +// reconnection with the remote peer that we're maintaining this channel with. +// The information contained within this message is necessary to re-sync our +// commitment chains in the case of a last or only partially processed message. +// When the remote party receives this message one of three things may happen: +// +// 1. We're fully synced and no messages need to be sent. +// 2. We didn't get the last CommitSig message they sent, so they'll re-send +// it. +// 3. We didn't get the last RevokeAndAck message they sent, so they'll +// re-send it. +// +// If this is a restored channel, having status ChanStatusRestored, then we'll +// modify our typical chan sync message to ensure they force close even if +// we're on the very first state. +func (c *OpenChannel) ChanSyncMsg() (*lnwire.ChannelReestablish, error) { + c.Lock() + defer c.Unlock() + + // The remote commitment height that we'll send in the + // ChannelReestablish message is our current commitment height plus + // one. If the receiver thinks that our commitment height is actually + // *equal* to this value, then they'll re-send the last commitment that + // they sent but we never fully processed. + localHeight := c.LocalCommitment.CommitHeight + nextLocalCommitHeight := localHeight + 1 + + // The second value we'll send is the height of the remote commitment + // from our PoV. If the receiver thinks that their height is actually + // *one plus* this value, then they'll re-send their last revocation. + remoteChainTipHeight := c.RemoteCommitment.CommitHeight + + // If this channel has undergone a commitment update, then in order to + // prove to the remote party our knowledge of their prior commitment + // state, we'll also send over the last commitment secret that the + // remote party sent. + var lastCommitSecret [32]byte + if remoteChainTipHeight != 0 { + remoteSecret, err := c.RevocationStore.LookUp( + remoteChainTipHeight - 1, + ) + if err != nil { + return nil, err + } + lastCommitSecret = [32]byte(*remoteSecret) + } + + // Additionally, we'll send over the current unrevoked commitment on + // our local commitment transaction. + currentCommitSecret, err := c.RevocationProducer.AtIndex( + localHeight, + ) + if err != nil { + return nil, err + } + + // If we've restored this channel, then we'll purposefully give them an + // invalid LocalUnrevokedCommitPoint so they'll force close the channel + // allowing us to sweep our funds. + if c.hasChanStatus(ChanStatusRestored) { + currentCommitSecret[0] ^= 1 + + // If this is a tweakless channel, then we'll purposefully send + // a next local height taht's invalid to trigger a force close + // on their end. We do this as tweakless channels don't require + // that the commitment point is valid, only that it's present. + if c.ChanType.IsTweakless() { + nextLocalCommitHeight = 0 + } + } + + // If this is a taproot channel, then we'll need to generate our next + // verification nonce to send to the remote party. They'll use this to + // sign the next update to our commitment transaction. + var nextTaprootNonce lnwire.OptMusig2NonceTLV + if c.ChanType.IsTaproot() { + taprootRevProducer, err := DeriveMusig2Shachain( + c.RevocationProducer, + ) + if err != nil { + return nil, err + } + + nextNonce, err := NewMusigVerificationNonce( + c.LocalChanCfg.MultiSigKey.PubKey, + nextLocalCommitHeight, taprootRevProducer, + ) + if err != nil { + return nil, fmt.Errorf("unable to gen next "+ + "nonce: %w", err) + } + + nextTaprootNonce = lnwire.SomeMusig2Nonce(nextNonce.PubNonce) + } + + return &lnwire.ChannelReestablish{ + ChanID: lnwire.NewChanIDFromOutPoint( + c.FundingOutpoint, + ), + NextLocalCommitHeight: nextLocalCommitHeight, + RemoteCommitTailHeight: remoteChainTipHeight, + LastRemoteCommitSecret: lastCommitSecret, + LocalUnrevokedCommitPoint: input.ComputeCommitmentPoint( + currentCommitSecret[:], + ), + LocalNonce: nextTaprootNonce, + }, nil +} + +// MarkShutdownSent serialises and persist the given ShutdownInfo for this +// channel. Persisting this info represents the fact that we have sent the +// Shutdown message to the remote side and hence that we should re-transmit the +// same Shutdown message on re-establish. +func (c *OpenChannel) MarkShutdownSent(info *ShutdownInfo) error { + c.Lock() + defer c.Unlock() + + return c.storeShutdownInfo(info) +} + +// storeShutdownInfo serialises the ShutdownInfo and persists it under the +// shutdownInfoKey. +func (c *OpenChannel) storeShutdownInfo(info *ShutdownInfo) error { var b bytes.Buffer - err := encodeShutdownInfo(info, &b) + err := info.encode(&b) if err != nil { return err } - return kvdb.Update(c.backend, func(tx kvdb.RwTx) error { + return kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { chanBucket, err := fetchChanBucketRw( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) if err != nil { return err @@ -1174,16 +1929,17 @@ func (c *ChannelStateDB) StoreChannelShutdownInfo(channel *OpenChannel, }, func() {}) } -// FetchChannelShutdownInfo fetches the persisted ShutdownInfo for the target -// channel. -func (c *ChannelStateDB) FetchChannelShutdownInfo( - channel *OpenChannel) (fn.Option[ShutdownInfo], error) { +// ShutdownInfo decodes the shutdown info stored for this channel and returns +// the result. If no shutdown info has been persisted for this channel then the +// ErrNoShutdownInfo error is returned. +func (c *OpenChannel) ShutdownInfo() (fn.Option[ShutdownInfo], error) { + c.RLock() + defer c.RUnlock() var shutdownInfo *ShutdownInfo - err := kvdb.View(c.backend, func(tx kvdb.RTx) error { + err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error { chanBucket, err := fetchChanBucket( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) switch { case err == nil: @@ -1214,68 +1970,73 @@ func (c *ChannelStateDB) FetchChannelShutdownInfo( return fn.Some[ShutdownInfo](*shutdownInfo), nil } -// isChannelBorked returns true if the channel has been marked as borked in the +// isBorked returns true if the channel has been marked as borked in the // database. This requires an existing database transaction to already be // active. // // NOTE: The primary mutex should already be held before this method is called. -func isChannelBorked(channel *OpenChannel, chanBucket kvdb.RBucket) ( - bool, error) { - - diskChannel, err := fetchOpenChannel( - chanBucket, &channel.FundingOutpoint, - ) +func (c *OpenChannel) isBorked(chanBucket kvdb.RBucket) (bool, error) { + channel, err := fetchOpenChannel(chanBucket, &c.FundingOutpoint) if err != nil { return false, err } - return diskChannel.ChannelStatusForStore() != ChanStatusDefault, nil + return channel.chanStatus != ChanStatusDefault, nil } -// MarkChannelCommitmentBroadcasted marks the channel as having a commitment -// transaction broadcast. -func (c *ChannelStateDB) MarkChannelCommitmentBroadcasted( - channel *OpenChannel, closeTx *wire.MsgTx, +// MarkCommitmentBroadcasted marks the channel as a commitment transaction has +// been broadcast, either our own or the remote, and we should watch the chain +// for it to confirm before taking any further action. It takes as argument the +// closing tx _we believe_ will appear in the chain. This is only used to +// republish this tx at startup to ensure propagation, and we should still +// handle the case where a different tx actually hits the chain. +func (c *OpenChannel) MarkCommitmentBroadcasted(closeTx *wire.MsgTx, closer lntypes.ChannelParty) error { return c.markBroadcasted( - channel, ChanStatusCommitBroadcasted, forceCloseTxKey, - closeTx, closer, + ChanStatusCommitBroadcasted, forceCloseTxKey, closeTx, + closer, ) } -// MarkChannelCoopBroadcasted marks the channel as having a cooperative close -// transaction broadcast. -func (c *ChannelStateDB) MarkChannelCoopBroadcasted(channel *OpenChannel, +// MarkCoopBroadcasted marks the channel to indicate that a cooperative close +// transaction has been broadcast, either our own or the remote, and that we +// should watch the chain for it to confirm before taking further action. It +// takes as argument a cooperative close tx that could appear on chain, and +// should be rebroadcast upon startup. This is only used to republish and +// ensure propagation, and we should still handle the case where a different tx +// actually hits the chain. +func (c *OpenChannel) MarkCoopBroadcasted(closeTx *wire.MsgTx, + closer lntypes.ChannelParty) error { + + return c.markBroadcasted( + ChanStatusCoopBroadcasted, coopCloseTxKey, closeTx, + closer, + ) +} + +// markBroadcasted is a helper function which modifies the channel status of the +// receiving channel and inserts a close transaction under the requested key, +// which should specify either a coop or force close. It adds a status which +// indicates the party that initiated the channel close. +func (c *OpenChannel) markBroadcasted(status ChannelStatus, key []byte, closeTx *wire.MsgTx, closer lntypes.ChannelParty) error { - return c.markBroadcasted( - channel, ChanStatusCoopBroadcasted, coopCloseTxKey, - closeTx, closer, - ) -} + c.Lock() + defer c.Unlock() -// markBroadcasted modifies the channel status and inserts a close transaction -// under the requested key, which should specify either a coop or force close. -// It adds a status which indicates the party that initiated the channel close. -func (c *ChannelStateDB) markBroadcasted(channel *OpenChannel, - status ChannelStatus, key []byte, closeTx *wire.MsgTx, - closer lntypes.ChannelParty) error { + // If a closing tx is provided, we'll generate a closure to write the + // transaction in the appropriate bucket under the given key. + var putClosingTx func(kvdb.RwBucket) error + if closeTx != nil { + var b bytes.Buffer + if err := WriteElement(&b, closeTx); err != nil { + return err + } - if closeTx == nil { - return fmt.Errorf("closeTx must be non-nil") - } - - channel.Lock() - defer channel.Unlock() - - var b bytes.Buffer - if err := WriteElement(&b, closeTx); err != nil { - return err - } - - putClosingTx := func(chanBucket kvdb.RwBucket) error { - return chanBucket.Put(key, b.Bytes()) + putClosingTx = func(chanBucket kvdb.RwBucket) error { + return chanBucket.Put(key, b.Bytes()) + } } // Add the initiator status to the status provided. These statuses are @@ -1287,36 +2048,29 @@ func (c *ChannelStateDB) markBroadcasted(channel *OpenChannel, status |= ChanStatusRemoteCloseInitiator } - return c.putChanStatus(channel, status, putClosingTx) + return c.putChanStatus(status, putClosingTx) } -// FetchChannelBroadcastedCommitment fetches the stored unilateral closing -// transaction. -func (c *ChannelStateDB) FetchChannelBroadcastedCommitment( - channel *OpenChannel) (*wire.MsgTx, error) { - - return c.getClosingTx(channel, forceCloseTxKey) +// BroadcastedCommitment retrieves the stored unilateral closing tx set during +// MarkCommitmentBroadcasted. If not found ErrNoCloseTx is returned. +func (c *OpenChannel) BroadcastedCommitment() (*wire.MsgTx, error) { + return c.getClosingTx(forceCloseTxKey) } -// FetchChannelBroadcastedCooperative fetches the stored cooperative closing -// transaction. -func (c *ChannelStateDB) FetchChannelBroadcastedCooperative( - channel *OpenChannel) (*wire.MsgTx, error) { - - return c.getClosingTx(channel, coopCloseTxKey) +// BroadcastedCooperative retrieves the stored cooperative closing tx set during +// MarkCoopBroadcasted. If not found ErrNoCloseTx is returned. +func (c *OpenChannel) BroadcastedCooperative() (*wire.MsgTx, error) { + return c.getClosingTx(coopCloseTxKey) } -// getClosingTx returns the stored closing transaction for key. The caller -// should use either the force or coop closing keys. -func (c *ChannelStateDB) getClosingTx(channel *OpenChannel, - key []byte) (*wire.MsgTx, error) { - +// getClosingTx is a helper method which returns the stored closing transaction +// for key. The caller should use either the force or coop closing keys. +func (c *OpenChannel) getClosingTx(key []byte) (*wire.MsgTx, error) { var closeTx *wire.MsgTx - err := kvdb.View(c.backend, func(tx kvdb.RTx) error { + err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error { chanBucket, err := fetchChanBucket( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) switch err { case nil: @@ -1342,41 +2096,30 @@ func (c *ChannelStateDB) getClosingTx(channel *OpenChannel, return closeTx, nil } -// ApplyChannelStatus adds the target status to the channel's persisted status -// bit field. -func (c *ChannelStateDB) ApplyChannelStatus(channel *OpenChannel, - status ChannelStatus) error { +// putChanStatus appends the given status to the channel. fs is an optional +// list of closures that are given the chanBucket in order to atomically add +// extra information together with the new status. +func (c *OpenChannel) putChanStatus(status ChannelStatus, + fs ...func(kvdb.RwBucket) error) error { - return c.putChanStatus(channel, status) -} - -// putChanStatus appends the given status to the channel. fs is an optional list -// of closures that are given the chanBucket in order to atomically add extra -// information together with the new status. -func (c *ChannelStateDB) putChanStatus(channel *OpenChannel, - status ChannelStatus, fs ...func(kvdb.RwBucket) error) error { - - if err := kvdb.Update(c.backend, func(tx kvdb.RwTx) error { + if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { chanBucket, err := fetchChanBucketRw( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) if err != nil { return err } - diskChannel, err := fetchOpenChannel( - chanBucket, &channel.FundingOutpoint, - ) + channel, err := fetchOpenChannel(chanBucket, &c.FundingOutpoint) if err != nil { return err } // Add this status to the existing bitvector found in the DB. - status = diskChannel.ChannelStatusForStore() | status - diskChannel.SetChannelStatusForStore(status) + status = channel.chanStatus | status + channel.chanStatus = status - if err := putOpenChannel(chanBucket, diskChannel); err != nil { + if err := putOpenChannel(chanBucket, channel); err != nil { return err } @@ -1397,43 +2140,36 @@ func (c *ChannelStateDB) putChanStatus(channel *OpenChannel, } // Update the in-memory representation to keep it in sync with the DB. - channel.SetChannelStatusForStore(status) + c.chanStatus = status return nil } -// ClearChannelStatus clears the target status from the channel's persisted -// status bit field. -func (c *ChannelStateDB) ClearChannelStatus(channel *OpenChannel, - status ChannelStatus) error { - - if err := kvdb.Update(c.backend, func(tx kvdb.RwTx) error { +func (c *OpenChannel) clearChanStatus(status ChannelStatus) error { + if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { chanBucket, err := fetchChanBucketRw( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) if err != nil { return err } - diskChannel, err := fetchOpenChannel( - chanBucket, &channel.FundingOutpoint, - ) + channel, err := fetchOpenChannel(chanBucket, &c.FundingOutpoint) if err != nil { return err } // Unset this bit in the bitvector on disk. - status = diskChannel.ChannelStatusForStore() & ^status - diskChannel.SetChannelStatusForStore(status) + status = channel.chanStatus & ^status + channel.chanStatus = status - return putOpenChannel(chanBucket, diskChannel) + return putOpenChannel(chanBucket, channel) }, func() {}); err != nil { return err } // Update the in-memory representation to keep it in sync with the DB. - channel.SetChannelStatusForStore(status) + c.chanStatus = status return nil } @@ -1515,28 +2251,38 @@ func fetchOpenChannel(chanBucket kvdb.RBucket, err) } + channel.Packager = NewChannelPackager(channel.ShortChannelID) + return channel, nil } -// SyncPendingChannel writes a pending channel to the store and records the -// funding broadcast height. -func (c *ChannelStateDB) SyncPendingChannel(channel *OpenChannel, - addr net.Addr, pendingHeight uint32) error { +// SyncPending writes the contents of the channel to the database while it's in +// the pending (waiting for funding confirmation) state. The IsPending flag +// will be set to true. When the channel's funding transaction is confirmed, +// the channel should be marked as "open" and the IsPending flag set to false. +// Note that this function also creates a LinkNode relationship between this +// newly created channel and a new LinkNode instance. This allows listing all +// channels in the database globally, or according to the LinkNode they were +// created with. +// +// TODO(roasbeef): addr param should eventually be an lnwire.NetAddress type +// that includes service bits. +func (c *OpenChannel) SyncPending(addr net.Addr, pendingHeight uint32) error { + c.Lock() + defer c.Unlock() - channel.FundingBroadcastHeight = pendingHeight + c.FundingBroadcastHeight = pendingHeight - return kvdb.Update(c.backend, func(tx kvdb.RwTx) error { - return syncNewChannel(tx, channel, []net.Addr{addr}, c.backend) + return kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { + return syncNewChannel(tx, c, []net.Addr{addr}) }, func() {}) } // syncNewChannel will write the passed channel to disk, and also create a // LinkNode (if needed) for the channel peer. -func syncNewChannel(tx kvdb.RwTx, c *OpenChannel, addrs []net.Addr, - backend kvdb.Backend) error { - +func syncNewChannel(tx kvdb.RwTx, c *OpenChannel, addrs []net.Addr) error { // First, sync all the persistent channel state to disk. - if err := fullSyncOpenChannel(tx, c); err != nil { + if err := c.fullSync(tx); err != nil { return err } @@ -1556,8 +2302,8 @@ func syncNewChannel(tx kvdb.RwTx, c *OpenChannel, addrs []net.Addr, // for this channel. The LinkNode metadata contains reachability, // up-time, and service bits related information. linkNode := NewLinkNode( - &LinkNodeDB{backend: backend}, wire.MainNet, c.IdentityPub, - addrs..., + &LinkNodeDB{backend: c.Db.backend}, + wire.MainNet, c.IdentityPub, addrs..., ) // TODO(roasbeef): do away with link node all together? @@ -1565,17 +2311,36 @@ func syncNewChannel(tx kvdb.RwTx, c *OpenChannel, addrs []net.Addr, return putLinkNode(nodeInfoBucket, linkNode) } -// UpdateChannelCommitment updates the local commitment state. -func (c *ChannelStateDB) UpdateChannelCommitment(channel *OpenChannel, - newCommitment *ChannelCommitment, +// UpdateCommitment updates the local commitment state. It locks in the pending +// local updates that were received by us from the remote party. The commitment +// state completely describes the balance state at this point in the commitment +// chain. In addition to that, it persists all the remote log updates that we +// have acked, but not signed a remote commitment for yet. These need to be +// persisted to be able to produce a valid commit signature if a restart would +// occur. This method its to be called when we revoke our prior commitment +// state. +// +// A map is returned of all the htlc resolutions that were locked in this +// commitment. Keys correspond to htlc indices and values indicate whether the +// htlc was settled or failed. +func (c *OpenChannel) UpdateCommitment(newCommitment *ChannelCommitment, unsignedAckedUpdates []LogUpdate) (map[uint64]bool, error) { + c.Lock() + defer c.Unlock() + + // If this is a restored channel, then we want to avoid mutating the + // state as all, as it's impossible to do so in a protocol compliant + // manner. + if c.hasChanStatus(ChanStatusRestored) { + return nil, ErrNoRestoredChannelMutation + } + var finalHtlcs = make(map[uint64]bool) - err := kvdb.Update(c.backend, func(tx kvdb.RwTx) error { + err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { chanBucket, err := fetchChanBucketRw( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) if err != nil { return err @@ -1583,7 +2348,7 @@ func (c *ChannelStateDB) UpdateChannelCommitment(channel *OpenChannel, // If the channel is marked as borked, then for safety reasons, // we shouldn't attempt any further updates. - isBorked, err := isChannelBorked(channel, chanBucket) + isBorked, err := c.isBorked(chanBucket) if err != nil { return err } @@ -1591,7 +2356,7 @@ func (c *ChannelStateDB) UpdateChannelCommitment(channel *OpenChannel, return ErrChanBorked } - if err = putChanInfo(chanBucket, channel); err != nil { + if err = putChanInfo(chanBucket, c); err != nil { return fmt.Errorf("unable to store chan info: %w", err) } @@ -1646,9 +2411,9 @@ func (c *ChannelStateDB) UpdateChannelCommitment(channel *OpenChannel, // Get the bucket where settled htlcs are recorded if the user // opted in to storing this information. var finalHtlcsBucket kvdb.RwBucket - if c.parent.storeFinalHtlcResolutions { + if c.Db.parent.storeFinalHtlcResolutions { bucket, err := fetchFinalHtlcsBucketRw( - tx, channel.ShortChannelID, + tx, c.ShortChannelID, ) if err != nil { return err @@ -1697,6 +2462,8 @@ func (c *ChannelStateDB) UpdateChannelCommitment(channel *OpenChannel, return nil, err } + c.LocalCommitment = *newCommitment + return finalHtlcs, nil } @@ -1746,7 +2513,131 @@ func processFinalHtlc(finalHtlcsBucket walletdb.ReadWriteBucket, upd LogUpdate, return nil } -// serializeHtlcExtraData encodes a TLV stream of extra data to be stored with a +// ActiveHtlcs returns a slice of HTLC's which are currently active on *both* +// commitment transactions. +func (c *OpenChannel) ActiveHtlcs() []HTLC { + c.RLock() + defer c.RUnlock() + + // We'll only return HTLC's that are locked into *both* commitment + // transactions. So we'll iterate through their set of HTLC's to note + // which ones are present on their commitment. + remoteHtlcs := make(map[[32]byte]struct{}) + for _, htlc := range c.RemoteCommitment.Htlcs { + log.Tracef("RemoteCommitment has htlc: id=%v, update=%v "+ + "incoming=%v", htlc.HtlcIndex, htlc.LogIndex, + htlc.Incoming) + + onionHash := sha256.Sum256(htlc.OnionBlob[:]) + remoteHtlcs[onionHash] = struct{}{} + } + + // Now that we know which HTLC's they have, we'll only mark the HTLC's + // as active if *we* know them as well. + activeHtlcs := make([]HTLC, 0, len(remoteHtlcs)) + for _, htlc := range c.LocalCommitment.Htlcs { + log.Tracef("LocalCommitment has htlc: id=%v, update=%v "+ + "incoming=%v", htlc.HtlcIndex, htlc.LogIndex, + htlc.Incoming) + + onionHash := sha256.Sum256(htlc.OnionBlob[:]) + if _, ok := remoteHtlcs[onionHash]; !ok { + log.Tracef("Skipped htlc due to onion mismatched: "+ + "id=%v, update=%v incoming=%v", + htlc.HtlcIndex, htlc.LogIndex, htlc.Incoming) + + continue + } + + activeHtlcs = append(activeHtlcs, htlc) + } + + return activeHtlcs +} + +// HTLC is the on-disk representation of a hash time-locked contract. HTLCs are +// contained within ChannelDeltas which encode the current state of the +// commitment between state updates. +// +// TODO(roasbeef): save space by using smaller ints at tail end? +type HTLC struct { + // TODO(yy): can embed an HTLCEntry here. + + // Signature is the signature for the second level covenant transaction + // for this HTLC. The second level transaction is a timeout tx in the + // case that this is an outgoing HTLC, and a success tx in the case + // that this is an incoming HTLC. + // + // TODO(roasbeef): make [64]byte instead? + Signature []byte + + // RHash is the payment hash of the HTLC. + RHash [32]byte + + // Amt is the amount of milli-satoshis this HTLC escrows. + Amt lnwire.MilliSatoshi + + // RefundTimeout is the absolute timeout on the HTLC that the sender + // must wait before reclaiming the funds in limbo. + RefundTimeout uint32 + + // OutputIndex is the output index for this particular HTLC output + // within the commitment transaction. + OutputIndex int32 + + // Incoming denotes whether we're the receiver or the sender of this + // HTLC. + Incoming bool + + // OnionBlob is an opaque blob which is used to complete multi-hop + // routing. + OnionBlob [lnwire.OnionPacketSize]byte + + // HtlcIndex is the HTLC counter index of this active, outstanding + // HTLC. This differs from the LogIndex, as the HtlcIndex is only + // incremented for each offered HTLC, while they LogIndex is + // incremented for each update (includes settle+fail). + HtlcIndex uint64 + + // LogIndex is the cumulative log index of this HTLC. This differs + // from the HtlcIndex as this will be incremented for each new log + // update added. + LogIndex uint64 + + // ExtraData contains any additional information that was transmitted + // with the HTLC via TLVs. This data *must* already be encoded as a + // TLV stream, and may be empty. The length of this data is naturally + // limited by the space available to TLVs in update_add_htlc: + // = 65535 bytes (bolt 8 maximum message size): + // - 2 bytes (bolt 1 message_type) + // - 32 bytes (channel_id) + // - 8 bytes (id) + // - 8 bytes (amount_msat) + // - 32 bytes (payment_hash) + // - 4 bytes (cltv_expiry) + // - 1366 bytes (onion_routing_packet) + // = 64083 bytes maximum possible TLV stream + // + // Note that this extra data is stored inline with the OnionBlob for + // legacy reasons, see serialization/deserialization functions for + // detail. + ExtraData lnwire.ExtraOpaqueData + + // BlindingPoint is an optional blinding point included with the HTLC. + // + // Note: this field is not a part of on-disk representation of the + // HTLC. It is stored in the ExtraData field, which is used to store + // a TLV stream of additional information associated with the HTLC. + BlindingPoint lnwire.BlindingPointRecord + + // CustomRecords is a set of custom TLV records that are associated with + // this HTLC. These records are used to store additional information + // about the HTLC that is not part of the standard HTLC fields. This + // field is encoded within the ExtraData field. + CustomRecords lnwire.CustomRecords +} + +// serializeExtraData encodes a TLV stream of extra data to be stored with a // HTLC. It uses the update_add_htlc TLV types, because this is where extra // data is passed with a HTLC. At present blinding points are the only extra // data that we will store, and the function is a no-op if a nil blinding @@ -1754,7 +2645,7 @@ func processFinalHtlc(finalHtlcsBucket walletdb.ReadWriteBucket, upd LogUpdate, // // This function MUST be called to persist all HTLC values when they are // serialized. -func serializeHtlcExtraData(h *HTLC) error { +func (h *HTLC) serializeExtraData() error { var records []tlv.RecordProducer h.BlindingPoint.WhenSome(func(b tlv.RecordT[lnwire.BlindingPointTlvType, *btcec.PublicKey]) { @@ -1770,12 +2661,12 @@ func serializeHtlcExtraData(h *HTLC) error { return h.ExtraData.PackRecords(records...) } -// deserializeHtlcExtraData extracts TLVs from the extra data persisted for the -// HTLC and populates values in the struct accordingly. +// deserializeExtraData extracts TLVs from the extra data persisted for the +// htlc and populates values in the struct accordingly. // // This function MUST be called to populate the struct properly when HTLCs // are deserialized. -func deserializeHtlcExtraData(h *HTLC) error { +func (h *HTLC) deserializeExtraData() error { if len(h.ExtraData) == 0 { return nil } @@ -1827,7 +2718,7 @@ func SerializeHtlcs(b io.Writer, htlcs ...HTLC) error { for _, htlc := range htlcs { // Populate TLV stream for any additional fields contained // in the TLV. - if err := serializeHtlcExtraData(&htlc); err != nil { + if err := htlc.serializeExtraData(); err != nil { return err } @@ -1919,7 +2810,7 @@ func DeserializeHtlcs(r io.Reader) ([]HTLC, error) { // Finally, deserialize any TLVs contained in that extra data // if they are present. - if err := deserializeHtlcExtraData(&htlcs[i]); err != nil { + if err := htlcs[i].deserializeExtraData(); err != nil { return nil, err } } @@ -1927,6 +2818,37 @@ func DeserializeHtlcs(r io.Reader) ([]HTLC, error) { return htlcs, nil } +// Copy returns a full copy of the target HTLC. +func (h *HTLC) Copy() HTLC { + clone := HTLC{ + Incoming: h.Incoming, + Amt: h.Amt, + RefundTimeout: h.RefundTimeout, + OutputIndex: h.OutputIndex, + } + copy(clone.Signature[:], h.Signature) + copy(clone.RHash[:], h.RHash[:]) + copy(clone.ExtraData, h.ExtraData) + clone.BlindingPoint = h.BlindingPoint + clone.CustomRecords = h.CustomRecords.Copy() + + return clone +} + +// LogUpdate represents a pending update to the remote commitment chain. The +// log update may be an add, fail, or settle entry. We maintain this data in +// order to be able to properly retransmit our proposed state if necessary. +type LogUpdate struct { + // LogIndex is the log index of this proposed commitment update entry. + LogIndex uint64 + + // UpdateMsg is the update message that was included within our + // local update log. The LogIndex value denotes the log index of this + // update which will be used when restoring our local update log if + // we're left with a dangling update on restart. + UpdateMsg lnwire.Message +} + // serializeLogUpdate writes a log update to the provided io.Writer. func serializeLogUpdate(w io.Writer, l *LogUpdate) error { return WriteElements(w, l.LogIndex, l.UpdateMsg) @@ -1942,6 +2864,61 @@ func deserializeLogUpdate(r io.Reader) (*LogUpdate, error) { return l, nil } +// CommitDiff represents the delta needed to apply the state transition between +// two subsequent commitment states. Given state N and state N+1, one is able +// to apply the set of messages contained within the CommitDiff to N to arrive +// at state N+1. Each time a new commitment is extended, we'll write a new +// commitment (along with the full commitment state) to disk so we can +// re-transmit the state in the case of a connection loss or message drop. +type CommitDiff struct { + // ChannelCommitment is the full commitment state that one would arrive + // at by applying the set of messages contained in the UpdateDiff to + // the prior accepted commitment. + Commitment ChannelCommitment + + // LogUpdates is the set of messages sent prior to the commitment state + // transition in question. Upon reconnection, if we detect that they + // don't have the commitment, then we re-send this along with the + // proper signature. + LogUpdates []LogUpdate + + // CommitSig is the exact CommitSig message that should be sent after + // the set of LogUpdates above has been retransmitted. The signatures + // within this message should properly cover the new commitment state + // and also the HTLC's within the new commitment state. + CommitSig *lnwire.CommitSig + + // OpenedCircuitKeys is a set of unique identifiers for any downstream + // Add packets included in this commitment txn. After a restart, this + // set of htlcs is acked from the link's incoming mailbox to ensure + // there isn't an attempt to re-add them to this commitment txn. + OpenedCircuitKeys []models.CircuitKey + + // ClosedCircuitKeys records the unique identifiers for any settle/fail + // packets that were resolved by this commitment txn. After a restart, + // this is used to ensure those circuits are removed from the circuit + // map, and the downstream packets in the link's mailbox are removed. + ClosedCircuitKeys []models.CircuitKey + + // AddAcks specifies the locations (commit height, pkg index) of any + // Adds that were failed/settled in this commit diff. This will ack + // entries in *this* channel's forwarding packages. + // + // NOTE: This value is not serialized, it is used to atomically mark the + // resolution of adds, such that they will not be reprocessed after a + // restart. + AddAcks []AddRef + + // SettleFailAcks specifies the locations (chan id, commit height, pkg + // index) of any Settles or Fails that were locked into this commit + // diff, and originate from *another* channel, i.e. the outgoing link. + // + // NOTE: This value is not serialized, it is used to atomically acks + // settles and fails from the forwarding packages of other channels, + // such that they will not be reforwarded internally after a restart. + SettleFailAcks []SettleFailRef +} + // serializeLogUpdates serializes provided list of updates to a stream. func serializeLogUpdates(w io.Writer, logUpdates []LogUpdate) error { numUpdates := uint16(len(logUpdates)) @@ -2018,7 +2995,7 @@ func serializeCommitDiff(w io.Writer, diff *CommitDiff) error { // nolint: dupl // We'll also encode the commit aux data stream here. We do this here // rather than above (at the call to serializeChanCommit), to ensure // backwards compat for reads to existing non-custom channels. - auxData := extractCommitTlvData(&diff.Commitment) + auxData := diff.Commitment.extractTlvData() if err := auxData.encode(w); err != nil { return fmt.Errorf("unable to write aux data: %w", err) } @@ -2092,22 +3069,33 @@ func deserializeCommitDiff(r io.Reader) (*CommitDiff, error) { return nil, fmt.Errorf("unable to decode aux data: %w", err) } - amendCommitTlvData(&d.Commitment, auxData) + d.Commitment.amendTlvData(auxData) return &d, nil } -// AppendRemoteCommitChain appends a new CommitDiff to the remote party's -// commitment chain. -func (c *ChannelStateDB) AppendRemoteCommitChain(channel *OpenChannel, - diff *CommitDiff) error { +// AppendRemoteCommitChain appends a new CommitDiff to the end of the +// commitment chain for the remote party. This method is to be used once we +// have prepared a new commitment state for the remote party, but before we +// transmit it to the remote party. The contents of the argument should be +// sufficient to retransmit the updates and signature needed to reconstruct the +// state in full, in the case that we need to retransmit. +func (c *OpenChannel) AppendRemoteCommitChain(diff *CommitDiff) error { + c.Lock() + defer c.Unlock() - return kvdb.Update(c.backend, func(tx kvdb.RwTx) error { + // If this is a restored channel, then we want to avoid mutating the + // state at all, as it's impossible to do so in a protocol compliant + // manner. + if c.hasChanStatus(ChanStatusRestored) { + return ErrNoRestoredChannelMutation + } + + return kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { // First, we'll grab the writable bucket where this channel's // data resides. chanBucket, err := fetchChanBucketRw( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) if err != nil { return err @@ -2115,7 +3103,7 @@ func (c *ChannelStateDB) AppendRemoteCommitChain(channel *OpenChannel, // If the channel is marked as borked, then for safety reasons, // we shouldn't attempt any further updates. - isBorked, err := isChannelBorked(channel, chanBucket) + isBorked, err := c.isBorked(chanBucket) if err != nil { return err } @@ -2128,9 +3116,7 @@ func (c *ChannelStateDB) AppendRemoteCommitChain(channel *OpenChannel, // Mark all of these as being fully processed in our forwarding // package, which prevents us from reprocessing them after // startup. - packager := NewChannelPackager(channel.ShortChannelID) - - err = packager.AckAddHtlcs(tx, diff.AddAcks...) + err = c.Packager.AckAddHtlcs(tx, diff.AddAcks...) if err != nil { return err } @@ -2140,9 +3126,7 @@ func (c *ChannelStateDB) AppendRemoteCommitChain(channel *OpenChannel, // prevents the same fails and settles from being retransmitted // after restarts. The actual fail or settle we need to // propagate to the remote party is now in the commit diff. - err = packager.AckSettleFails( - tx, diff.SettleFailAcks..., - ) + err = c.Packager.AckSettleFails(tx, diff.SettleFailAcks...) if err != nil { return err } @@ -2170,15 +3154,16 @@ func (c *ChannelStateDB) AppendRemoteCommitChain(channel *OpenChannel, } // RemoteCommitChainTip returns the "tip" of the current remote commitment -// chain. -func (c *ChannelStateDB) RemoteCommitChainTip(channel *OpenChannel) ( - *CommitDiff, error) { - +// chain. This value will be non-nil iff, we've created a new commitment for +// the remote party that they haven't yet ACK'd. In this case, their commitment +// chain will have a length of two: their current unrevoked commitment, and +// this new pending commitment. Once they revoked their prior state, we'll swap +// these pointers, causing the tip and the tail to point to the same entry. +func (c *OpenChannel) RemoteCommitChainTip() (*CommitDiff, error) { var cd *CommitDiff - err := kvdb.View(c.backend, func(tx kvdb.RTx) error { + err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error { chanBucket, err := fetchChanBucket( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) switch err { case nil: @@ -2213,14 +3198,11 @@ func (c *ChannelStateDB) RemoteCommitChainTip(channel *OpenChannel) ( // UnsignedAckedUpdates retrieves the persisted unsigned acked remote log // updates that still need to be signed for. -func (c *ChannelStateDB) UnsignedAckedUpdates(channel *OpenChannel) ( - []LogUpdate, error) { - +func (c *OpenChannel) UnsignedAckedUpdates() ([]LogUpdate, error) { var updates []LogUpdate - err := kvdb.View(c.backend, func(tx kvdb.RTx) error { + err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error { chanBucket, err := fetchChanBucket( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) switch err { case nil: @@ -2250,14 +3232,11 @@ func (c *ChannelStateDB) UnsignedAckedUpdates(channel *OpenChannel) ( // RemoteUnsignedLocalUpdates retrieves the persisted, unsigned local log // updates that the remote still needs to sign for. -func (c *ChannelStateDB) RemoteUnsignedLocalUpdates(channel *OpenChannel) ( - []LogUpdate, error) { - +func (c *OpenChannel) RemoteUnsignedLocalUpdates() ([]LogUpdate, error) { var updates []LogUpdate - err := kvdb.View(c.backend, func(tx kvdb.RTx) error { + err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error { chanBucket, err := fetchChanBucket( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) switch err { case nil: @@ -2286,23 +3265,28 @@ func (c *ChannelStateDB) RemoteUnsignedLocalUpdates(channel *OpenChannel) ( return updates, nil } -// InsertNextRevocation inserts the next commitment point into the persisted -// channel state. -func (c *ChannelStateDB) InsertNextRevocation(channel *OpenChannel, - revKey *btcec.PublicKey) error { +// InsertNextRevocation inserts the _next_ commitment point (revocation) into +// the database, and also modifies the internal RemoteNextRevocation attribute +// to point to the passed key. This method is to be using during final channel +// set up, _after_ the channel has been fully confirmed. +// +// NOTE: If this method isn't called, then the target channel won't be able to +// propose new states for the commitment state of the remote party. +func (c *OpenChannel) InsertNextRevocation(revKey *btcec.PublicKey) error { + c.Lock() + defer c.Unlock() - channel.RemoteNextRevocation = revKey + c.RemoteNextRevocation = revKey - err := kvdb.Update(c.backend, func(tx kvdb.RwTx) error { + err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { chanBucket, err := fetchChanBucketRw( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) if err != nil { return err } - return putChanRevocationState(chanBucket, channel) + return putChanRevocationState(chanBucket, c) }, func() {}) if err != nil { return err @@ -2311,19 +3295,33 @@ func (c *ChannelStateDB) InsertNextRevocation(channel *OpenChannel, return nil } -// AdvanceCommitChainTail records the new state transition within the -// revocation log and promotes the pending remote commitment to the current -// remote commitment. -func (c *ChannelStateDB) AdvanceCommitChainTail(channel *OpenChannel, - fwdPkg *FwdPkg, updates []LogUpdate, ourOutputIndex, - theirOutputIndex uint32) error { +// AdvanceCommitChainTail records the new state transition within an on-disk +// append-only log which records all state transitions by the remote peer. In +// the case of an uncooperative broadcast of a prior state by the remote peer, +// this log can be consulted in order to reconstruct the state needed to +// rectify the situation. This method will add the current commitment for the +// remote party to the revocation log, and promote the current pending +// commitment to the current remote commitment. The updates parameter is the +// set of local updates that the peer still needs to send us a signature for. +// We store this set of updates in case we go down. +func (c *OpenChannel) AdvanceCommitChainTail(fwdPkg *FwdPkg, + updates []LogUpdate, ourOutputIndex, theirOutputIndex uint32) error { + + c.Lock() + defer c.Unlock() + + // If this is a restored channel, then we want to avoid mutating the + // state at all, as it's impossible to do so in a protocol compliant + // manner. + if c.hasChanStatus(ChanStatusRestored) { + return ErrNoRestoredChannelMutation + } var newRemoteCommit *ChannelCommitment - err := kvdb.Update(c.backend, func(tx kvdb.RwTx) error { + err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { chanBucket, err := fetchChanBucketRw( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) if err != nil { return err @@ -2331,7 +3329,7 @@ func (c *ChannelStateDB) AdvanceCommitChainTail(channel *OpenChannel, // If the channel is marked as borked, then for safety reasons, // we shouldn't attempt any further updates. - isBorked, err := isChannelBorked(channel, chanBucket) + isBorked, err := c.isBorked(chanBucket) if err != nil { return err } @@ -2342,8 +3340,7 @@ func (c *ChannelStateDB) AdvanceCommitChainTail(channel *OpenChannel, // Persist the latest preimage state to disk as the remote peer // has just added to our local preimage store, and given us a // new pending revocation key. - err = putChanRevocationState(chanBucket, channel) - if err != nil { + if err := putChanRevocationState(chanBucket, c); err != nil { return err } @@ -2382,8 +3379,8 @@ func (c *ChannelStateDB) AdvanceCommitChainTail(channel *OpenChannel, // With the commitment pointer swapped, we can now add the // revoked (prior) state to the revocation log. err = putRevocationLog( - logBucket, &channel.RemoteCommitment, ourOutputIndex, - theirOutputIndex, c.parent.noRevLogAmtData, + logBucket, &c.RemoteCommitment, ourOutputIndex, + theirOutputIndex, c.Db.parent.noRevLogAmtData, ) if err != nil { return err @@ -2392,9 +3389,7 @@ func (c *ChannelStateDB) AdvanceCommitChainTail(channel *OpenChannel, // Lastly, we write the forwarding package to disk so that we // can properly recover from failures and reforward HTLCs that // have not received a corresponding settle/fail. - packager := NewChannelPackager(channel.ShortChannelID) - err = packager.AddFwdPkg(tx, fwdPkg) - if err != nil { + if err := c.Packager.AddFwdPkg(tx, fwdPkg); err != nil { return err } @@ -2466,13 +3461,21 @@ func (c *ChannelStateDB) AdvanceCommitChainTail(channel *OpenChannel, // With the db transaction complete, we'll swap over the in-memory // pointer of the new remote commitment, which was previously the tip // of the commit chain. - channel.RemoteCommitment = *newRemoteCommit + c.RemoteCommitment = *newRemoteCommit return nil } // FinalHtlcInfo contains information about the final outcome of an htlc. -type FinalHtlcInfo = cstate.FinalHtlcInfo +type FinalHtlcInfo struct { + // Settled is true is the htlc was settled. If false, the htlc was + // failed. + Settled bool + + // Offchain indicates whether the htlc was resolved off-chain or + // on-chain. + Offchain bool +} // putFinalHtlc writes the final htlc outcome to the database. Additionally it // records whether the htlc was resolved off-chain or on-chain. @@ -2493,17 +3496,40 @@ func putFinalHtlc(finalHtlcsBucket kvdb.RwBucket, id uint64, return finalHtlcsBucket.Put(key[:], []byte{byte(finalHtlcByte)}) } +// NextLocalHtlcIndex returns the next unallocated local htlc index. To ensure +// this always returns the next index that has been not been allocated, this +// will first try to examine any pending commitments, before falling back to the +// last locked-in remote commitment. +func (c *OpenChannel) NextLocalHtlcIndex() (uint64, error) { + // First, load the most recent commit diff that we initiated for the + // remote party. If no pending commit is found, this is not treated as + // a critical error, since we can always fall back. + pendingRemoteCommit, err := c.RemoteCommitChainTip() + if err != nil && err != ErrNoPendingCommit { + return 0, err + } + + // If a pending commit was found, its local htlc index will be at least + // as large as the one on our local commitment. + if pendingRemoteCommit != nil { + return pendingRemoteCommit.Commitment.LocalHtlcIndex, nil + } + + // Otherwise, fallback to using the local htlc index of their commitment. + return c.RemoteCommitment.LocalHtlcIndex, nil +} + // LoadFwdPkgs scans the forwarding log for any packages that haven't been // processed, and returns their deserialized log updates in map indexed by the // remote commitment height at which the updates were locked in. -func (c *ChannelStateDB) LoadFwdPkgs(channel *OpenChannel) ([]*FwdPkg, - error) { +func (c *OpenChannel) LoadFwdPkgs() ([]*FwdPkg, error) { + c.RLock() + defer c.RUnlock() var fwdPkgs []*FwdPkg - if err := kvdb.View(c.backend, func(tx kvdb.RTx) error { + if err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error { var err error - packager := NewChannelPackager(channel.ShortChannelID) - fwdPkgs, err = packager.LoadFwdPkgs(tx) + fwdPkgs, err = c.Packager.LoadFwdPkgs(tx) return err }, func() { fwdPkgs = nil @@ -2517,12 +3543,12 @@ func (c *ChannelStateDB) LoadFwdPkgs(channel *OpenChannel) ([]*FwdPkg, // AckAddHtlcs updates the AckAddFilter containing any of the provided AddRefs // indicating that a response to this Add has been committed to the remote party. // Doing so will prevent these Add HTLCs from being reforwarded internally. -func (c *ChannelStateDB) AckAddHtlcs(channel *OpenChannel, - addRefs ...AddRef) error { +func (c *OpenChannel) AckAddHtlcs(addRefs ...AddRef) error { + c.Lock() + defer c.Unlock() - return kvdb.Update(c.backend, func(tx kvdb.RwTx) error { - packager := NewChannelPackager(channel.ShortChannelID) - return packager.AckAddHtlcs(tx, addRefs...) + return kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { + return c.Packager.AckAddHtlcs(tx, addRefs...) }, func() {}) } @@ -2530,23 +3556,23 @@ func (c *ChannelStateDB) AckAddHtlcs(channel *OpenChannel, // SettleFailRefs, indicating that the response has been delivered to the // incoming link, corresponding to a particular AddRef. Doing so will prevent // the responses from being retransmitted internally. -func (c *ChannelStateDB) AckSettleFails(channel *OpenChannel, - settleFailRefs ...SettleFailRef) error { +func (c *OpenChannel) AckSettleFails(settleFailRefs ...SettleFailRef) error { + c.Lock() + defer c.Unlock() - return kvdb.Update(c.backend, func(tx kvdb.RwTx) error { - packager := NewChannelPackager(channel.ShortChannelID) - return packager.AckSettleFails(tx, settleFailRefs...) + return kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { + return c.Packager.AckSettleFails(tx, settleFailRefs...) }, func() {}) } // SetFwdFilter atomically sets the forwarding filter for the forwarding package // identified by `height`. -func (c *ChannelStateDB) SetFwdFilter(channel *OpenChannel, height uint64, - fwdFilter *PkgFilter) error { +func (c *OpenChannel) SetFwdFilter(height uint64, fwdFilter *PkgFilter) error { + c.Lock() + defer c.Unlock() - return kvdb.Update(c.backend, func(tx kvdb.RwTx) error { - packager := NewChannelPackager(channel.ShortChannelID) - return packager.SetFwdFilter(tx, height, fwdFilter) + return kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { + return c.Packager.SetFwdFilter(tx, height, fwdFilter) }, func() {}) } @@ -2555,14 +3581,13 @@ func (c *ChannelStateDB) SetFwdFilter(channel *OpenChannel, height uint64, // later packages won't be removed. // // NOTE: This method should only be called on packages marked FwdStateCompleted. -func (c *ChannelStateDB) RemoveFwdPkgs(channel *OpenChannel, - heights ...uint64) error { - - return kvdb.Update(c.backend, func(tx kvdb.RwTx) error { - packager := NewChannelPackager(channel.ShortChannelID) +func (c *OpenChannel) RemoveFwdPkgs(heights ...uint64) error { + c.Lock() + defer c.Unlock() + return kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { for _, height := range heights { - err := packager.RemovePkg(tx, height) + err := c.Packager.RemovePkg(tx, height) if err != nil { return err } @@ -2573,22 +3598,26 @@ func (c *ChannelStateDB) RemoveFwdPkgs(channel *OpenChannel, } // revocationLogTailCommitHeight returns the commit height at the end of the -// revocation log. -func (c *ChannelStateDB) revocationLogTailCommitHeight( - channel *OpenChannel) (uint64, error) { +// revocation log. This entry represents the last previous state for the remote +// node's commitment chain. The ChannelDelta returned by this method will +// always lag one state behind the most current (unrevoked) state of the remote +// node's commitment chain. +// NOTE: used in unit test only. +func (c *OpenChannel) revocationLogTailCommitHeight() (uint64, error) { + c.RLock() + defer c.RUnlock() var height uint64 // If we haven't created any state updates yet, then we'll exit early as // there's nothing to be found on disk in the revocation bucket. - if channel.RemoteCommitment.CommitHeight == 0 { + if c.RemoteCommitment.CommitHeight == 0 { return height, nil } - if err := kvdb.View(c.backend, func(tx kvdb.RTx) error { + if err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error { chanBucket, err := fetchChanBucket( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) if err != nil { return err @@ -2620,16 +3649,16 @@ func (c *ChannelStateDB) revocationLogTailCommitHeight( // This value is always monotonically increasing. This method is provided in // order to allow multiple instances of a particular open channel to obtain a // consistent view of the number of channel updates to date. -func (c *ChannelStateDB) CommitmentHeight(channel *OpenChannel) ( - uint64, error) { +func (c *OpenChannel) CommitmentHeight() (uint64, error) { + c.RLock() + defer c.RUnlock() var height uint64 - err := kvdb.View(c.backend, func(tx kvdb.RTx) error { + err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error { // Get the bucket dedicated to storing the metadata for open // channels. chanBucket, err := fetchChanBucket( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) if err != nil { return err @@ -2657,16 +3686,18 @@ func (c *ChannelStateDB) CommitmentHeight(channel *OpenChannel) ( // intended to be used for obtaining the relevant data needed to claim all // funds rightfully spendable in the case of an on-chain broadcast of the // commitment transaction. -func (c *ChannelStateDB) FindPreviousState(channel *OpenChannel, +func (c *OpenChannel) FindPreviousState( updateNum uint64) (*RevocationLog, *ChannelCommitment, error) { + c.RLock() + defer c.RUnlock() + commit := &ChannelCommitment{} rl := &RevocationLog{} - err := kvdb.View(c.backend, func(tx kvdb.RTx) error { + err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error { chanBucket, err := fetchChanBucket( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) if err != nil { return err @@ -2692,313 +3723,460 @@ func (c *ChannelStateDB) FindPreviousState(channel *OpenChannel, return rl, commit, nil } -// ClosureType is an enum like structure that details exactly how a channel was -// closed. -type ClosureType = cstate.ClosureType +// ClosureType is an enum like structure that details exactly _how_ a channel +// was closed. Three closure types are currently possible: none, cooperative, +// local force close, remote force close, and (remote) breach. +type ClosureType uint8 const ( // CooperativeClose indicates that a channel has been closed - // cooperatively. - CooperativeClose = cstate.CooperativeClose + // cooperatively. This means that both channel peers were online and + // signed a new transaction paying out the settled balance of the + // contract. + CooperativeClose ClosureType = 0 // LocalForceClose indicates that we have unilaterally broadcast our // current commitment state on-chain. - LocalForceClose = cstate.LocalForceClose + LocalForceClose ClosureType = 1 // RemoteForceClose indicates that the remote peer has unilaterally // broadcast their current commitment state on-chain. - RemoteForceClose = cstate.RemoteForceClose + RemoteForceClose ClosureType = 4 // BreachClose indicates that the remote peer attempted to broadcast a - // prior revoked channel state. - BreachClose = cstate.BreachClose + // prior _revoked_ channel state. + BreachClose ClosureType = 2 // FundingCanceled indicates that the channel never was fully opened - // before it was marked as closed in the database. - FundingCanceled = cstate.FundingCanceled + // before it was marked as closed in the database. This can happen if + // we or the remote fail at some point during the opening workflow, or + // we timeout waiting for the funding transaction to be confirmed. + FundingCanceled ClosureType = 3 - // Abandoned indicates that the channel state was removed without any - // further actions. - Abandoned = cstate.Abandoned + // Abandoned indicates that the channel state was removed without + // any further actions. This is intended to clean up unusable + // channels during development. + Abandoned ClosureType = 5 ) // ChannelCloseSummary contains the final state of a channel at the point it -// was closed. -type ChannelCloseSummary = cstate.ChannelCloseSummary +// was closed. Once a channel is closed, all the information pertaining to that +// channel within the openChannelBucket is deleted, and a compact summary is +// put in place instead. +type ChannelCloseSummary struct { + // ChanPoint is the outpoint for this channel's funding transaction, + // and is used as a unique identifier for the channel. + ChanPoint wire.OutPoint -// CloseChannel closes the supplied channel via the strategy selected at DB -// construction. On synchronous backends the channel's nested state — the -// revocation log, the per-channel forwarding-package bucket, and the -// chanBucket itself — is deleted inline. On tombstone-enabled backends none -// of the bulk state is touched; the outpointBucket flip to outpointClosed -// signals that the channel is logically closed. -func (c *ChannelStateDB) CloseChannel(channel *OpenChannel, - summary *ChannelCloseSummary, statuses ...ChannelStatus) error { + // ShortChanID encodes the exact location in the chain in which the + // channel was initially confirmed. This includes: the block height, + // transaction index, and the output within the target transaction. + ShortChanID lnwire.ShortChannelID - if c.tombstoneClosedChannels { - return c.closeChannelTombstone(channel, summary, statuses...) - } + // ChainHash is the hash of the genesis block that this channel resides + // within. + ChainHash chainhash.Hash - return c.closeChannelSync(channel, summary, statuses...) + // ClosingTXID is the txid of the transaction which ultimately closed + // this channel. + ClosingTXID chainhash.Hash + + // RemotePub is the public key of the remote peer that we formerly had + // a channel with. + RemotePub *btcec.PublicKey + + // Capacity was the total capacity of the channel. + Capacity btcutil.Amount + + // CloseHeight is the height at which the funding transaction was + // spent. + CloseHeight uint32 + + // SettledBalance is our total balance settled balance at the time of + // channel closure. This _does not_ include the sum of any outputs that + // have been time-locked as a result of the unilateral channel closure. + SettledBalance btcutil.Amount + + // TimeLockedBalance is the sum of all the time-locked outputs at the + // time of channel closure. If we triggered the force closure of this + // channel, then this value will be non-zero if our settled output is + // above the dust limit. If we were on the receiving side of a channel + // force closure, then this value will be non-zero if we had any + // outstanding outgoing HTLC's at the time of channel closure. + TimeLockedBalance btcutil.Amount + + // CloseType details exactly _how_ the channel was closed. Five closure + // types are possible: cooperative, local force, remote force, breach + // and funding canceled. + CloseType ClosureType + + // IsPending indicates whether this channel is in the 'pending close' + // state, which means the channel closing transaction has been + // confirmed, but not yet been fully resolved. In the case of a channel + // that has been cooperatively closed, it will go straight into the + // fully resolved state as soon as the closing transaction has been + // confirmed. However, for channels that have been force closed, they'll + // stay marked as "pending" until _all_ the pending funds have been + // swept. + IsPending bool + + // RemoteCurrentRevocation is the current revocation for their + // commitment transaction. However, since this is the derived public key, + // we don't yet have the private key so we aren't yet able to verify + // that it's actually in the hash chain. + RemoteCurrentRevocation *btcec.PublicKey + + // RemoteNextRevocation is the revocation key to be used for the *next* + // commitment transaction we create for the local node. Within the + // specification, this value is referred to as the + // per-commitment-point. + RemoteNextRevocation *btcec.PublicKey + + // LocalChanConfig is the channel configuration for the local node. + LocalChanConfig ChannelConfig + + // LastChanSyncMsg is the ChannelReestablish message for this channel + // for the state at the point where it was closed. + LastChanSyncMsg *lnwire.ChannelReestablish } -// locateOpenChannel performs the open-channel-bucket descent for a -// CloseChannel transaction: it returns the chain bucket, the channel bucket, -// and the serialized chanKey for the supplied OpenChannel. A chanKey already -// flipped to outpointClosed surfaces ErrChannelNotFound so a redundant -// CloseChannel does not re-archive or re-flip the index. -func locateOpenChannel(tx kvdb.RwTx, channel *OpenChannel) (kvdb.RwBucket, - kvdb.RwBucket, []byte, error) { - - openChanBucket := tx.ReadWriteBucket(openChannelBucket) - if openChanBucket == nil { - return nil, nil, nil, ErrNoChanDBExists - } - - nodePub := channel.IdentityPub.SerializeCompressed() - nodeChanBucket := openChanBucket.NestedReadWriteBucket(nodePub) - if nodeChanBucket == nil { - return nil, nil, nil, ErrNoActiveChannels - } - - chainBucket := nodeChanBucket.NestedReadWriteBucket( - channel.ChainHash[:], - ) - if chainBucket == nil { - return nil, nil, nil, ErrNoActiveChannels - } - - var chanPointBuf bytes.Buffer - if err := graphdb.WriteOutpoint( - &chanPointBuf, &channel.FundingOutpoint, - ); err != nil { - return nil, nil, nil, err - } - chanKey := chanPointBuf.Bytes() - - chanBucket := chainBucket.NestedReadWriteBucket(chanKey) - if chanBucket == nil { - return nil, nil, nil, ErrNoActiveChannels - } - - // A channel whose outpoint is already flipped to outpointClosed must - // not be re-closed: on tombstone backends the chanBucket survives a - // previous close, but the index flip is the authoritative record that - // the channel is gone from the open-channel view. - closed, err := isOutpointClosed(tx.ReadBucket(outpointBucket), chanKey) - if err != nil { - return nil, nil, nil, err - } - if closed { - return nil, nil, nil, ErrChannelNotFound - } - - return chainBucket, chanBucket, chanKey, nil -} - -// updateClosedOutpointIndex flips the outpoint index entry for chanKey from -// open to closed. The index entry must already exist; it was placed there -// when the channel was opened. -func updateClosedOutpointIndex(tx kvdb.RwTx, chanKey []byte) error { - opBucket := tx.ReadWriteBucket(outpointBucket) - if opBucket == nil { - return ErrNoChanDBExists - } - if opBucket.Get(chanKey) == nil { - return ErrMissingIndexEntry - } - - status := uint8(outpointClosed) - statusRecord := tlv.MakePrimitiveRecord(indexStatusType, &status) - opStream, err := tlv.NewStream(statusRecord) - if err != nil { - return err - } - - var b bytes.Buffer - if err := opStream.Encode(&b); err != nil { - return err - } - - return opBucket.Put(chanKey, b.Bytes()) -} - -// archiveClosedChannel writes the immutable close-time records of the -// channel: a copy of the open-channel state under historicalChannelBucket -// (with the supplied close statuses OR'd into chanStatus) and the close -// summary under closeSummaryBucket. -func archiveClosedChannel(tx kvdb.RwTx, chanKey []byte, - chanState *OpenChannel, summary *ChannelCloseSummary, +// CloseChannel closes a previously active Lightning channel. Closing a channel +// entails deleting all saved state within the database concerning this +// channel. This method also takes a struct that summarizes the state of the +// channel at closing, this compact representation will be the only component +// of a channel left over after a full closing. It takes an optional set of +// channel statuses which will be written to the historical channel bucket. +// These statuses are used to record close initiators. +func (c *OpenChannel) CloseChannel(summary *ChannelCloseSummary, statuses ...ChannelStatus) error { - historicalBucket, err := tx.CreateTopLevelBucket( - historicalChannelBucket, - ) - if err != nil { - return err - } - historicalChanBucket, err := historicalBucket.CreateBucketIfNotExists( - chanKey, - ) - if err != nil { - return err - } + c.Lock() + defer c.Unlock() - for _, s := range statuses { - chanState.SetChannelStatusForStore( - chanState.ChannelStatusForStore() | s, - ) - } + return kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { + openChanBucket := tx.ReadWriteBucket(openChannelBucket) + if openChanBucket == nil { + return ErrNoChanDBExists + } - if err := putOpenChannel(historicalChanBucket, chanState); err != nil { - return err - } + nodePub := c.IdentityPub.SerializeCompressed() + nodeChanBucket := openChanBucket.NestedReadWriteBucket(nodePub) + if nodeChanBucket == nil { + return ErrNoActiveChannels + } - return putChannelCloseSummary(tx, chanKey, summary, chanState) -} + chainBucket := nodeChanBucket.NestedReadWriteBucket(c.ChainHash[:]) + if chainBucket == nil { + return ErrNoActiveChannels + } -// closeChannelSync performs the historical synchronous close path: in a -// single write transaction it wipes the forwarding-package state, deletes -// the channel bucket and its nested revocation log entries, updates the -// outpoint index, and archives the close summary. It is used by backends -// where nested-bucket deletion is cheap (bbolt, etcd). -func (c *ChannelStateDB) closeChannelSync(channel *OpenChannel, - summary *ChannelCloseSummary, statuses ...ChannelStatus) error { - - return kvdb.Update(c.backend, func(tx kvdb.RwTx) error { - chainBucket, chanBucket, chanKey, err := locateOpenChannel( - tx, channel, - ) + var chanPointBuf bytes.Buffer + err := graphdb.WriteOutpoint(&chanPointBuf, &c.FundingOutpoint) if err != nil { return err } + chanKey := chanPointBuf.Bytes() + chanBucket := chainBucket.NestedReadWriteBucket( + chanKey, + ) + if chanBucket == nil { + return ErrNoActiveChannels + } + // Before we delete the channel state, we'll read out the full + // details, as we'll also store portions of this information + // for record keeping. chanState, err := fetchOpenChannel( - chanBucket, &channel.FundingOutpoint, + chanBucket, &c.FundingOutpoint, ) if err != nil { return err } - packager := NewChannelPackager(chanState.ShortChannelID) - if err = packager.Wipe(tx); err != nil { + // Delete all the forwarding packages stored for this particular + // channel. + if err = chanState.Packager.Wipe(tx); err != nil { return err } - if err := deleteOpenChannel(chanBucket); err != nil { + // Now that the index to this channel has been deleted, purge + // the remaining channel metadata from the database. + err = deleteOpenChannel(chanBucket) + if err != nil { return err } - if channel.ChanType.IsFrozen() || - channel.ChanType.HasLeaseExpiration() { - - if err := deleteThawHeight(chanBucket); err != nil { + // We'll also remove the channel from the frozen channel bucket + // if we need to. + if c.ChanType.IsFrozen() || c.ChanType.HasLeaseExpiration() { + err := deleteThawHeight(chanBucket) + if err != nil { return err } } + // With the base channel data deleted, attempt to delete the + // information stored within the revocation log. if err := deleteLogBucket(chanBucket); err != nil { return err } - if err := chainBucket.DeleteNestedBucket(chanKey); err != nil { - return err - } - - if err := updateClosedOutpointIndex(tx, chanKey); err != nil { - return err - } - - return archiveClosedChannel( - tx, chanKey, chanState, summary, statuses..., - ) - }, func() {}) -} - -// closeChannelTombstone performs the tombstone close path used by -// KV-over-SQL backends. The channel's per-channel state is left intact — -// touching it would trigger the cascading nested-bucket delete this path -// exists to avoid — and the outpointBucket flip from outpointOpen to -// outpointClosed serves as the authoritative closed-channel marker. The -// disk space is reclaimed wholesale by the upcoming native-SQL -// channel-state migration. -func (c *ChannelStateDB) closeChannelTombstone(channel *OpenChannel, - summary *ChannelCloseSummary, statuses ...ChannelStatus) error { - - return kvdb.Update(c.backend, func(tx kvdb.RwTx) error { - _, chanBucket, chanKey, err := locateOpenChannel(tx, channel) + err = chainBucket.DeleteNestedBucket(chanPointBuf.Bytes()) if err != nil { return err } - chanState, err := fetchOpenChannel( - chanBucket, &channel.FundingOutpoint, + // Fetch the outpoint bucket to see if the outpoint exists or + // not. + opBucket := tx.ReadWriteBucket(outpointBucket) + if opBucket == nil { + return ErrNoChanDBExists + } + + // Add the closed outpoint to our outpoint index. This should + // replace an open outpoint in the index. + if opBucket.Get(chanPointBuf.Bytes()) == nil { + return ErrMissingIndexEntry + } + + status := uint8(outpointClosed) + + // Write the IndexStatus of this outpoint as the first entry in a tlv + // stream. + statusRecord := tlv.MakePrimitiveRecord(indexStatusType, &status) + opStream, err := tlv.NewStream(statusRecord) + if err != nil { + return err + } + + var b bytes.Buffer + if err := opStream.Encode(&b); err != nil { + return err + } + + // Finally add the closed outpoint and tlv stream to the index. + if err := opBucket.Put(chanPointBuf.Bytes(), b.Bytes()); err != nil { + return err + } + + // Add channel state to the historical channel bucket. + historicalBucket, err := tx.CreateTopLevelBucket( + historicalChannelBucket, ) if err != nil { return err } - if err := updateClosedOutpointIndex(tx, chanKey); err != nil { + historicalChanBucket, err := + historicalBucket.CreateBucketIfNotExists(chanKey) + if err != nil { return err } - return archiveClosedChannel( - tx, chanKey, chanState, summary, statuses..., + // Apply any additional statuses to the channel state. + for _, status := range statuses { + chanState.chanStatus |= status + } + + err = putOpenChannel(historicalChanBucket, chanState) + if err != nil { + return err + } + + // Finally, create a summary of this channel in the closed + // channel bucket for this node. + return putChannelCloseSummary( + tx, chanPointBuf.Bytes(), summary, chanState, ) }, func() {}) } -// ChannelSnapshot is a frozen snapshot of the current channel state. -type ChannelSnapshot = cstate.ChannelSnapshot +// ChannelSnapshot is a frozen snapshot of the current channel state. A +// snapshot is detached from the original channel that generated it, providing +// read-only access to the current or prior state of an active channel. +// +// TODO(roasbeef): remove all together? pretty much just commitment +type ChannelSnapshot struct { + // RemoteIdentity is the identity public key of the remote node that we + // are maintaining the open channel with. + RemoteIdentity btcec.PublicKey + + // ChanPoint is the outpoint that created the channel. This output is + // found within the funding transaction and uniquely identified the + // channel on the resident chain. + ChannelPoint wire.OutPoint + + // ChainHash is the genesis hash of the chain that the channel resides + // within. + ChainHash chainhash.Hash + + // Capacity is the total capacity of the channel. + Capacity btcutil.Amount + + // TotalMSatSent is the total number of milli-satoshis we've sent + // within this channel. + TotalMSatSent lnwire.MilliSatoshi + + // TotalMSatReceived is the total number of milli-satoshis we've + // received within this channel. + TotalMSatReceived lnwire.MilliSatoshi + + // ChannelCommitment is the current up-to-date commitment for the + // target channel. + ChannelCommitment +} + +// Snapshot returns a read-only snapshot of the current channel state. This +// snapshot includes information concerning the current settled balance within +// the channel, metadata detailing total flows, and any outstanding HTLCs. +func (c *OpenChannel) Snapshot() *ChannelSnapshot { + c.RLock() + defer c.RUnlock() + + localCommit := c.LocalCommitment + snapshot := &ChannelSnapshot{ + RemoteIdentity: *c.IdentityPub, + ChannelPoint: c.FundingOutpoint, + Capacity: c.Capacity, + TotalMSatSent: c.TotalMSatSent, + TotalMSatReceived: c.TotalMSatReceived, + ChainHash: c.ChainHash, + ChannelCommitment: ChannelCommitment{ + LocalBalance: localCommit.LocalBalance, + RemoteBalance: localCommit.RemoteBalance, + CommitHeight: localCommit.CommitHeight, + CommitFee: localCommit.CommitFee, + }, + } + + localCommit.CustomBlob.WhenSome(func(blob tlv.Blob) { + blobCopy := make([]byte, len(blob)) + copy(blobCopy, blob) + + snapshot.ChannelCommitment.CustomBlob = fn.Some(blobCopy) + }) + + // Copy over the current set of HTLCs to ensure the caller can't mutate + // our internal state. + snapshot.Htlcs = make([]HTLC, len(localCommit.Htlcs)) + for i, h := range localCommit.Htlcs { + snapshot.Htlcs[i] = h.Copy() + } + + return snapshot +} // LatestCommitments returns the two latest commitments for both the local and // remote party. These commitments are read from disk to ensure that only the // latest fully committed state is returned. The first commitment returned is // the local commitment, and the second returned is the remote commitment. -func (c *ChannelStateDB) LatestCommitments(channel *OpenChannel) ( - *ChannelCommitment, *ChannelCommitment, error) { - - err := kvdb.View(c.backend, func(tx kvdb.RTx) error { +func (c *OpenChannel) LatestCommitments() (*ChannelCommitment, *ChannelCommitment, error) { + err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error { chanBucket, err := fetchChanBucket( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) if err != nil { return err } - return fetchChanCommitments(chanBucket, channel) + return fetchChanCommitments(chanBucket, c) }, func() {}) if err != nil { return nil, nil, err } - return &channel.LocalCommitment, &channel.RemoteCommitment, nil + return &c.LocalCommitment, &c.RemoteCommitment, nil } // RemoteRevocationStore returns the most up to date commitment version of the // revocation storage tree for the remote party. This method can be used when // acting on a possible contract breach to ensure, that the caller has the most // up to date information required to deliver justice. -func (c *ChannelStateDB) RemoteRevocationStore(channel *OpenChannel) ( - shachain.Store, error) { - - err := kvdb.View(c.backend, func(tx kvdb.RTx) error { +func (c *OpenChannel) RemoteRevocationStore() (shachain.Store, error) { + err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error { chanBucket, err := fetchChanBucket( - tx, channel.IdentityPub, &channel.FundingOutpoint, - channel.ChainHash, + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, ) if err != nil { return err } - return fetchChanRevocationState(chanBucket, channel) + return fetchChanRevocationState(chanBucket, c) }, func() {}) if err != nil { return nil, err } - return channel.RevocationStore, nil + return c.RevocationStore, nil +} + +// AbsoluteThawHeight determines a frozen channel's absolute thaw height. If the +// channel is not frozen, then 0 is returned. +func (c *OpenChannel) AbsoluteThawHeight() (uint32, error) { + // Only frozen channels have a thaw height. + if !c.ChanType.IsFrozen() && !c.ChanType.HasLeaseExpiration() { + return 0, nil + } + + // If the channel has the frozen bit set and it's thaw height is below + // the absolute threshold, then it's interpreted as a relative height to + // the chain's current height. + if c.ChanType.IsFrozen() && c.ThawHeight < AbsoluteThawHeightThreshold { + // We'll only known of the channel's short ID once it's + // confirmed. + if c.IsPending { + return 0, errors.New("cannot use relative thaw " + + "height for unconfirmed channel") + } + + // For non-zero-conf channels, this is the base height to use. + blockHeightBase := c.ShortChannelID.BlockHeight + + // If this is a zero-conf channel, the ShortChannelID will be + // an alias. + if c.IsZeroConf() { + if !c.ZeroConfConfirmed() { + return 0, errors.New("cannot use relative " + + "height for unconfirmed zero-conf " + + "channel") + } + + // Use the confirmed SCID's BlockHeight. + blockHeightBase = c.confirmedScid.BlockHeight + } + + return blockHeightBase + c.ThawHeight, nil + } + + return c.ThawHeight, nil +} + +// DeriveHeightHint derives the block height for the channel opening. +func (c *OpenChannel) DeriveHeightHint() uint32 { + // As a height hint, we'll try to use the opening height, but if the + // channel isn't yet open, then we'll use the height it was broadcast + // at. This may be an unconfirmed zero-conf channel. + heightHint := c.ShortChanID().BlockHeight + if heightHint == 0 { + heightHint = c.BroadcastHeight() + } + + // Since no zero-conf state is stored in a channel backup, the below + // logic will not be triggered for restored, zero-conf channels. Set + // the height hint for zero-conf channels. + if c.IsZeroConf() { + if c.ZeroConfConfirmed() { + // If the zero-conf channel is confirmed, we'll use the + // confirmed SCID's block height. + heightHint = c.ZeroConfRealScid().BlockHeight + } else { + // The zero-conf channel is unconfirmed. We'll need to + // use the FundingBroadcastHeight. + heightHint = c.BroadcastHeight() + } + } + + return heightHint } func putChannelCloseSummary(tx kvdb.RwTx, chanID []byte, @@ -3177,7 +4355,7 @@ func fundingTxPresent(channel *OpenChannel) bool { return chanType.IsSingleFunder() && chanType.HasFundingTx() && channel.IsInitiator && - !channel.HasChanStatusForStore(ChanStatusRestored) + !channel.hasChanStatus(ChanStatusRestored) } func putChanInfo(chanBucket kvdb.RwBucket, channel *OpenChannel) error { @@ -3185,7 +4363,7 @@ func putChanInfo(chanBucket kvdb.RwBucket, channel *OpenChannel) error { if err := WriteElements(&w, channel.ChanType, channel.ChainHash, channel.FundingOutpoint, channel.ShortChannelID, channel.IsPending, channel.IsInitiator, - channel.ChannelStatusForStore(), channel.FundingBroadcastHeight, + channel.chanStatus, channel.FundingBroadcastHeight, channel.NumConfsRequired, channel.ChannelFlags, channel.IdentityPub, channel.Capacity, channel.TotalMSatSent, channel.TotalMSatReceived, @@ -3208,7 +4386,7 @@ func putChanInfo(chanBucket kvdb.RwBucket, channel *OpenChannel) error { return err } - auxData := extractOpenChannelTlvData(channel) + auxData := channel.extractTlvData() if err := auxData.encode(&w); err != nil { return fmt.Errorf("unable to encode aux data: %w", err) } @@ -3298,7 +4476,7 @@ func putChanCommitment(chanBucket kvdb.RwBucket, c *ChannelCommitment, } // Before we write to disk, we'll also write our aux data as well. - auxData := extractCommitTlvData(c) + auxData := c.extractTlvData() if err := auxData.encode(&b); err != nil { return fmt.Errorf("unable to write aux data: %w", err) } @@ -3309,7 +4487,7 @@ func putChanCommitment(chanBucket kvdb.RwBucket, c *ChannelCommitment, func putChanCommitments(chanBucket kvdb.RwBucket, channel *OpenChannel) error { // If this is a restored channel, then we don't have any commitments to // write. - if channel.HasChanStatusForStore(ChanStatusRestored) { + if channel.hasChanStatus(ChanStatusRestored) { return nil } @@ -3364,18 +4542,16 @@ func fetchChanInfo(chanBucket kvdb.RBucket, channel *OpenChannel) error { } r := bytes.NewReader(infoBytes) - var chanStatus ChannelStatus if err := ReadElements(r, &channel.ChanType, &channel.ChainHash, &channel.FundingOutpoint, &channel.ShortChannelID, &channel.IsPending, &channel.IsInitiator, - &chanStatus, &channel.FundingBroadcastHeight, + &channel.chanStatus, &channel.FundingBroadcastHeight, &channel.NumConfsRequired, &channel.ChannelFlags, &channel.IdentityPub, &channel.Capacity, &channel.TotalMSatSent, &channel.TotalMSatReceived, ); err != nil { return err } - channel.SetChannelStatusForStore(chanStatus) // For single funder channels that we initiated and have the funding // transaction to, read the funding txn. @@ -3414,7 +4590,9 @@ func fetchChanInfo(chanBucket kvdb.RBucket, channel *OpenChannel) error { // Assign all the relevant fields from the aux data into the actual // open channel. - amendOpenChannelTlvData(channel, auxData) + channel.amendTlvData(auxData) + + channel.Packager = NewChannelPackager(channel.ShortChannelID) // Finally, read the optional shutdown scripts. if err := getOptionalUpfrontShutdownScript( @@ -3478,7 +4656,7 @@ func fetchChanCommitment(chanBucket kvdb.RBucket, "chan aux data: %w", err) } - amendCommitTlvData(&chanCommit, auxData) + chanCommit.amendTlvData(auxData) return chanCommit, nil } @@ -3488,7 +4666,7 @@ func fetchChanCommitments(chanBucket kvdb.RBucket, channel *OpenChannel) error { // If this is a restored channel, then we don't have any commitments to // read. - if channel.HasChanStatusForStore(ChanStatusRestored) { + if channel.hasChanStatus(ChanStatusRestored) { return nil } @@ -3637,13 +4815,40 @@ func DKeyLocator(r io.Reader, val interface{}, buf *[8]byte, l uint64) error { // ShutdownInfo contains various info about the shutdown initiation of a // channel. -type ShutdownInfo = cstate.ShutdownInfo +type ShutdownInfo struct { + // DeliveryScript is the address that we have included in any previous + // Shutdown message for a particular channel and so should include in + // any future re-sends of the Shutdown message. + DeliveryScript tlv.RecordT[tlv.TlvType0, lnwire.DeliveryAddress] + + // LocalInitiator is true if we sent a Shutdown message before ever + // receiving a Shutdown message from the remote peer. + LocalInitiator tlv.RecordT[tlv.TlvType1, bool] +} // NewShutdownInfo constructs a new ShutdownInfo object. -var NewShutdownInfo = cstate.NewShutdownInfo +func NewShutdownInfo(deliveryScript lnwire.DeliveryAddress, + locallyInitiated bool) *ShutdownInfo { -// encodeShutdownInfo serialises the ShutdownInfo to the given io.Writer. -func encodeShutdownInfo(s *ShutdownInfo, w io.Writer) error { + return &ShutdownInfo{ + DeliveryScript: tlv.NewRecordT[tlv.TlvType0](deliveryScript), + LocalInitiator: tlv.NewPrimitiveRecord[tlv.TlvType1]( + locallyInitiated, + ), + } +} + +// Closer identifies the ChannelParty that initiated the coop-closure process. +func (s ShutdownInfo) Closer() lntypes.ChannelParty { + if s.LocalInitiator.Val { + return lntypes.Local + } + + return lntypes.Remote +} + +// encode serialises the ShutdownInfo to the given io.Writer. +func (s *ShutdownInfo) encode(w io.Writer) error { records := []tlv.Record{ s.DeliveryScript.Record(), s.LocalInitiator.Record(), diff --git a/channeldb/channel_test.go b/channeldb/channel_test.go index c955ea96b..475040677 100644 --- a/channeldb/channel_test.go +++ b/channeldb/channel_test.go @@ -12,9 +12,9 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" _ "github.com/btcsuite/btcwallet/walletdb/bdb" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/clock" @@ -414,6 +414,7 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel { RevocationProducer: producer, RevocationStore: store, Db: cdb, + Packager: NewChannelPackager(chanID), FundingTxn: channels.TestFundingTx, ThawHeight: uint32(defaultPendingHeight), InitialLocalBalance: lnwire.MilliSatoshi(9000), @@ -565,6 +566,7 @@ func TestOptionalShutdown(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { fullDB, err := MakeTestDB(t) @@ -877,7 +879,7 @@ func TestChannelStateTransition(t *testing.T) { // The state number recovered from the tail of the revocation log // should be identical to this current state. - logTailHeight, err := cdb.revocationLogTailCommitHeight(channel) + logTailHeight, err := channel.revocationLogTailCommitHeight() require.NoError(t, err, "unable to retrieve log") if logTailHeight != oldRemoteCommit.CommitHeight { t.Fatal("update number doesn't match") @@ -920,7 +922,7 @@ func TestChannelStateTransition(t *testing.T) { // Once again, state number recovered from the tail of the revocation // log should be identical to this current state. - logTailHeight, err = cdb.revocationLogTailCommitHeight(channel) + logTailHeight, err = channel.revocationLogTailCommitHeight() require.NoError(t, err, "unable to retrieve log") if logTailHeight != oldRemoteCommit.CommitHeight { t.Fatal("update number doesn't match") @@ -937,9 +939,7 @@ func TestChannelStateTransition(t *testing.T) { } // At this point, we should have 2 forwarding packages added. - fwdPkgs := loadFwdPkgs( - t, cdb.backend, NewChannelPackager(channel.ShortChanID()), - ) + fwdPkgs := loadFwdPkgs(t, cdb.backend, channel.Packager) require.Len(t, fwdPkgs, 2, "wrong number of forwarding packages") // Now attempt to delete the channel from the database. @@ -974,9 +974,7 @@ func TestChannelStateTransition(t *testing.T) { } // All forwarding packages of this channel has been deleted too. - fwdPkgs = loadFwdPkgs( - t, cdb.backend, NewChannelPackager(channel.ShortChanID()), - ) + fwdPkgs = loadFwdPkgs(t, cdb.backend, channel.Packager) require.Empty(t, fwdPkgs, "no forwarding packages should exist") } @@ -1241,16 +1239,21 @@ func TestFetchWaitingCloseChannels(t *testing.T) { t.Fatalf("unable to mark commitment broadcast: %v", err) } - // A nil close tx must be rejected. - err = channel.MarkCoopBroadcasted( + // Now try to marking a coop close with a nil tx. This should + // succeed, but it shouldn't exit when queried. + if err = channel.MarkCoopBroadcasted( nil, lntypes.Local, - ) - require.Error(t, err, "nil tx should be rejected") + ); err != nil { + t.Fatalf("unable to mark nil coop broadcast: %v", err) + } + _, err := channel.BroadcastedCooperative() + if err != ErrNoCloseTx { + t.Fatalf("expected no closing tx error, got: %v", err) + } - // Modify the close tx deterministically and also mark + // Finally, modify the close tx deterministically and also mark // it as coop closed. Later we will test that distinct - // transactions are returned for both coop and force - // closes. + // transactions are returned for both coop and force closes. closeTx.TxIn[0].PreviousOutPoint.Index ^= 1 if err := channel.MarkCoopBroadcasted( closeTx, lntypes.Local, @@ -1327,6 +1330,7 @@ func TestShutdownInfo(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -1420,6 +1424,16 @@ func TestRefresh(t *testing.T) { "updated before refreshing short_chan_id") } + // Now that the receiver's short channel id has been updated, check to + // ensure that the channel packager's source has been updated as well. + // This ensures that the packager will read and write to buckets + // corresponding to the new short chan id, instead of the prior. + if state.Packager.(*ChannelPackager).source != chanOpenLoc { + t.Fatalf("channel packager source was not updated: want %v, "+ + "got %v", chanOpenLoc, + state.Packager.(*ChannelPackager).source) + } + // Now, refresh the state of the pending channel. err = pendingChannel.Refresh() require.NoError(t, err, "unable to refresh short_chan_id") @@ -1432,15 +1446,21 @@ func TestRefresh(t *testing.T) { pendingChannel.ShortChanID()) } + // Check to ensure that the _other_ OpenChannel channel packager's + // source has also been updated after the refresh. This ensures that the + // other packagers will read and write to buckets corresponding to the + // updated short chan id. + if pendingChannel.Packager.(*ChannelPackager).source != chanOpenLoc { + t.Fatalf("channel packager source was not updated: want %v, "+ + "got %v", chanOpenLoc, + pendingChannel.Packager.(*ChannelPackager).source) + } + // Check to ensure that this channel is no longer pending and this field // is up to date. if pendingChannel.IsPending { t.Fatalf("channel pending state wasn't updated: want false got true") } - - require.Equal( - t, chanOpenLoc, NewChannelPackager(state.ShortChanID()).source, - ) } // TestCloseInitiator tests the setting of close initiator statuses for @@ -1498,6 +1518,7 @@ func TestCloseInitiator(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -1538,7 +1559,7 @@ func TestCloseInitiator(t *testing.T) { if !dbChans[0].HasChanStatus(status) { t.Fatalf("expected channel to have "+ "status: %v, has status: %v", - status, dbChans[0].ChanStatus()) + status, dbChans[0].chanStatus) } } }) @@ -1618,10 +1639,12 @@ func TestHasChanStatus(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { - c := &OpenChannel{} - c.SetChannelStatusForStore(test.status) + c := &OpenChannel{ + chanStatus: test.status, + } for status, expHas := range test.expHas { has := c.HasChanStatus(status) @@ -1799,6 +1822,7 @@ func TestHTLCsExtraData(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.name, func(t *testing.T) { t.Parallel() diff --git a/channeldb/chanstate_assertions.go b/channeldb/chanstate_assertions.go deleted file mode 100644 index 3f29d7632..000000000 --- a/channeldb/chanstate_assertions.go +++ /dev/null @@ -1,7 +0,0 @@ -package channeldb - -import "github.com/lightningnetwork/lnd/chanstate" - -// Compile-time assertions that ChannelStateDB satisfies the channel-state -// store contracts while the KV implementation still lives in channeldb. -var _ chanstate.Store = (*ChannelStateDB)(nil) diff --git a/channeldb/close_channel_test.go b/channeldb/close_channel_test.go deleted file mode 100644 index e2dcc634e..000000000 --- a/channeldb/close_channel_test.go +++ /dev/null @@ -1,412 +0,0 @@ -package channeldb - -import ( - "bytes" - "testing" - - "github.com/btcsuite/btcd/wire/v2" - graphdb "github.com/lightningnetwork/lnd/graph/db" - "github.com/lightningnetwork/lnd/kvdb" - "github.com/lightningnetwork/lnd/tlv" - "github.com/stretchr/testify/require" -) - -// writeTestRevlogEntries writes n entries directly into the -// revocationLogBucket of the given channel. The helper navigates the raw KV -// tree so the test does not depend on the higher-level commit-chain -// machinery. -func writeTestRevlogEntries(t *testing.T, cdb *ChannelStateDB, - ch *OpenChannel, n int) { - - t.Helper() - - err := kvdb.Update(cdb.backend, func(tx kvdb.RwTx) error { - openChanBkt := tx.ReadWriteBucket(openChannelBucket) - require.NotNil(t, openChanBkt, "openChannelBucket missing") - - nodePub := ch.IdentityPub.SerializeCompressed() - nodeBkt := openChanBkt.NestedReadWriteBucket(nodePub) - require.NotNil(t, nodeBkt, "node bucket missing") - - chainBkt := nodeBkt.NestedReadWriteBucket(ch.ChainHash[:]) - require.NotNil(t, chainBkt, "chain bucket missing") - - var chanKeyBuf bytes.Buffer - err := graphdb.WriteOutpoint(&chanKeyBuf, &ch.FundingOutpoint) - require.NoError(t, err) - - chanBkt := chainBkt.NestedReadWriteBucket(chanKeyBuf.Bytes()) - require.NotNil(t, chanBkt, "channel bucket missing") - - logBkt, err := chanBkt.CreateBucketIfNotExists( - revocationLogBucket, - ) - require.NoError(t, err) - - for i := range n { - commit := testChannelCommit - commit.CommitHeight = uint64(i) - - err := putRevocationLog(logBkt, &commit, 0, 1, false) - require.NoError(t, err) - } - - return nil - }, func() {}) - require.NoError(t, err) -} - -// writeTestForwardingPackages writes n empty forwarding packages for the -// given channel using distinct remote commitment heights. -func writeTestForwardingPackages(t *testing.T, cdb *ChannelStateDB, - ch *OpenChannel, n int) { - - t.Helper() - - packager := NewChannelPackager(ch.ShortChanID()) - err := kvdb.Update(cdb.backend, func(tx kvdb.RwTx) error { - for i := range n { - pkg := NewFwdPkg( - ch.ShortChanID(), uint64(i), nil, nil, - ) - if err := packager.AddFwdPkg(tx, pkg); err != nil { - return err - } - } - - return nil - }, func() {}) - require.NoError(t, err) -} - -// countRevlogEntries returns the number of entries in the revocationLogBucket -// for the given channel, or -1 if the channel bucket no longer exists in -// openChannelBucket. -func countRevlogEntries(t *testing.T, cdb *ChannelStateDB, - ch *OpenChannel) int { - - t.Helper() - - count := -1 - err := kvdb.View(cdb.backend, func(tx kvdb.RTx) error { - openChanBkt := tx.ReadBucket(openChannelBucket) - if openChanBkt == nil { - return nil - } - - nodePub := ch.IdentityPub.SerializeCompressed() - nodeBkt := openChanBkt.NestedReadBucket(nodePub) - if nodeBkt == nil { - return nil - } - - chainBkt := nodeBkt.NestedReadBucket(ch.ChainHash[:]) - if chainBkt == nil { - return nil - } - - var chanKeyBuf bytes.Buffer - if err := graphdb.WriteOutpoint( - &chanKeyBuf, &ch.FundingOutpoint, - ); err != nil { - return err - } - - chanBkt := chainBkt.NestedReadBucket(chanKeyBuf.Bytes()) - if chanBkt == nil { - return nil - } - - logBkt := chanBkt.NestedReadBucket(revocationLogBucket) - if logBkt == nil { - count = 0 - return nil - } - - c := 0 - if err := logBkt.ForEach(func(_, _ []byte) error { - c++ - return nil - }); err != nil { - return err - } - - count = c - - return nil - }, func() {}) - require.NoError(t, err) - - return count -} - -// readOutpointStatus decodes the indexStatus TLV byte stored under -// outpointBucket for the given outpoint. Used to verify the index flip -// performed by the close path. -func readOutpointStatus(t *testing.T, cdb *ChannelStateDB, - op wire.OutPoint) indexStatus { - - t.Helper() - - var chanKeyBuf bytes.Buffer - require.NoError(t, graphdb.WriteOutpoint(&chanKeyBuf, &op)) - - var status uint8 - err := kvdb.View(cdb.backend, func(tx kvdb.RTx) error { - bkt := tx.ReadBucket(outpointBucket) - require.NotNil(t, bkt, "outpointBucket missing") - - raw := bkt.Get(chanKeyBuf.Bytes()) - require.NotNil(t, raw, "outpoint entry missing") - - statusRecord := tlv.MakePrimitiveRecord( - indexStatusType, &status, - ) - stream, err := tlv.NewStream(statusRecord) - if err != nil { - return err - } - - return stream.Decode(bytes.NewReader(raw)) - }, func() {}) - require.NoError(t, err) - - return indexStatus(status) -} - -// closeChannelForTest invokes CloseChannel on a freshly created OpenChannel -// using a minimal close summary derived from the channel state itself. -func closeChannelForTest(t *testing.T, cdb *ChannelStateDB, ch *OpenChannel) { - t.Helper() - - summary := &ChannelCloseSummary{ - ChanPoint: ch.FundingOutpoint, - RemotePub: ch.IdentityPub, - ChainHash: ch.ChainHash, - ShortChanID: ch.ShortChannelID, - CloseType: CooperativeClose, - } - require.NoError(t, cdb.CloseChannel(ch, summary)) -} - -// TestCloseChannelTombstoneWritePath verifies the on-disk artefacts the -// tombstone close path produces in a single write transaction: the outpoint -// index flips from open to closed, the historical-channel record and close -// summary are written, and the bulk per-channel state (revocation log, -// forwarding packages) is left intact — that retention is the entire reason -// for the tombstone path on these backends. -func TestCloseChannelTombstoneWritePath(t *testing.T) { - t.Parallel() - - fullDB, err := MakeTestDB(t, OptionTombstoneClosedChannels(true)) - require.NoError(t, err) - - cdb := fullDB.ChannelStateDB() - require.True(t, cdb.tombstoneClosedChannels) - - ch := createTestChannel(t, cdb, openChannelOption()) - - const numRevlogEntries = 5 - const numFwdPkgs = 3 - writeTestRevlogEntries(t, cdb, ch, numRevlogEntries) - writeTestForwardingPackages(t, cdb, ch, numFwdPkgs) - - closeChannelForTest(t, cdb, ch) - - // Outpoint index flipped from open to closed — the authoritative - // closed-channel marker on tombstone backends. - require.Equal(t, outpointClosed, readOutpointStatus( - t, cdb, ch.FundingOutpoint, - )) - - // Historical-channel record exists for this chanKey. - histChan, err := cdb.FetchHistoricalChannel(&ch.FundingOutpoint) - require.NoError(t, err) - require.Equal(t, ch.FundingOutpoint, histChan.FundingOutpoint) - - // Close summary readable via FetchClosedChannel. - closeSummary, err := cdb.FetchClosedChannel(&ch.FundingOutpoint) - require.NoError(t, err) - require.Equal(t, ch.FundingOutpoint, closeSummary.ChanPoint) - - // Bulk state preserved on disk — tombstoning's whole point. - require.Equal(t, numRevlogEntries, countRevlogEntries(t, cdb, ch)) - - packager := NewChannelPackager(ch.ShortChanID()) - var fwdPkgs []*FwdPkg - require.NoError(t, kvdb.View(cdb.backend, func(tx kvdb.RTx) error { - fwdPkgs, err = packager.LoadFwdPkgs(tx) - return err - }, func() {})) - require.Len(t, fwdPkgs, numFwdPkgs) -} - -// TestCloseChannelTombstoneRedundantClose verifies that a second CloseChannel -// call against an already-closed channel is rejected with ErrChannelNotFound -// rather than silently re-archiving or re-flipping the outpoint. The guard -// lives in locateOpenChannel. -func TestCloseChannelTombstoneRedundantClose(t *testing.T) { - t.Parallel() - - fullDB, err := MakeTestDB(t, OptionTombstoneClosedChannels(true)) - require.NoError(t, err) - - cdb := fullDB.ChannelStateDB() - ch := createTestChannel(t, cdb, openChannelOption()) - - closeChannelForTest(t, cdb, ch) - - summary := &ChannelCloseSummary{ - ChanPoint: ch.FundingOutpoint, - RemotePub: ch.IdentityPub, - ChainHash: ch.ChainHash, - ShortChanID: ch.ShortChannelID, - CloseType: CooperativeClose, - } - require.ErrorIs(t, cdb.CloseChannel(ch, summary), ErrChannelNotFound) -} - -// TestCloseChannelTombstoneRemovesFromOpenScans verifies that after a -// tombstone close the channel disappears from every open-channel scan -// (FetchAllChannels, FetchOpenChannels, FetchPermAndTempPeers) while the -// outpoint index reflects the close. The bulk historical state remains on -// disk — that is the entire point of the tombstone path. -func TestCloseChannelTombstoneRemovesFromOpenScans(t *testing.T) { - t.Parallel() - - fullDB, err := MakeTestDB(t, OptionTombstoneClosedChannels(true)) - require.NoError(t, err) - - cdb := fullDB.ChannelStateDB() - require.True(t, cdb.tombstoneClosedChannels) - - // Two channels share an identity pubkey via createTestChannel, so we - // can verify the closed one disappears while the other is still - // surfaced by per-peer lookups. - ch1 := createTestChannel(t, cdb, openChannelOption()) - ch2 := createTestChannel(t, cdb, openChannelOption()) - - const numRevlogEntries = 5 - writeTestRevlogEntries(t, cdb, ch1, numRevlogEntries) - - openChans, err := cdb.FetchAllChannels() - require.NoError(t, err) - require.Len(t, openChans, 2) - - closeChannelForTest(t, cdb, ch1) - - openChans, err = cdb.FetchAllChannels() - require.NoError(t, err) - require.Len(t, openChans, 1) - require.Equal( - t, ch2.FundingOutpoint, openChans[0].FundingOutpoint, - ) - - openChans, err = cdb.FetchOpenChannels(ch1.IdentityPub) - require.NoError(t, err) - require.Len(t, openChans, 1) - require.Equal( - t, ch2.FundingOutpoint, openChans[0].FundingOutpoint, - ) - - // FetchPermAndTempPeers should still mark the peer as having a closed - // channel (via the historical-channel second pass), even though the - // open-channel-bucket pass now skips the closed chanKey. - peers, err := cdb.FetchPermAndTempPeers(ch1.ChainHash[:]) - require.NoError(t, err) - peerKey := string(ch1.IdentityPub.SerializeCompressed()) - require.True(t, peers[peerKey].HasOpenOrClosedChan) - - // The bulk historical state stays put — that is the whole point of - // the tombstone path on these backends. - require.Equal(t, numRevlogEntries, countRevlogEntries(t, cdb, ch1)) - - // The outpoint index for ch1 must flip to closed; ch2's stays open. - require.Equal(t, outpointClosed, readOutpointStatus( - t, cdb, ch1.FundingOutpoint, - )) - require.Equal(t, outpointOpen, readOutpointStatus( - t, cdb, ch2.FundingOutpoint, - )) -} - -// TestClosedChannelHiddenFromFetchChannel verifies that a targeted -// FetchChannel lookup returns ErrChannelNotFound for a closed channel. -// FetchChannel goes through channelScanner, exercising the closed-state -// check inside that iteration site rather than the direct fetchChanBucket -// lookup path. -func TestClosedChannelHiddenFromFetchChannel(t *testing.T) { - t.Parallel() - - fullDB, err := MakeTestDB(t, OptionTombstoneClosedChannels(true)) - require.NoError(t, err) - - cdb := fullDB.ChannelStateDB() - ch := createTestChannel(t, cdb, openChannelOption()) - - closeChannelForTest(t, cdb, ch) - - _, err = cdb.FetchChannel(ch.FundingOutpoint) - require.ErrorIs(t, err, ErrChannelNotFound) -} - -// TestClosedChannelHiddenFromDirectMethods verifies that direct OpenChannel -// methods which descend through fetchChanBucket / fetchChanBucketRw observe -// the closed-state flip and return ErrChannelNotFound rather than reading -// stale per-channel state. -func TestClosedChannelHiddenFromDirectMethods(t *testing.T) { - t.Parallel() - - fullDB, err := MakeTestDB(t, OptionTombstoneClosedChannels(true)) - require.NoError(t, err) - - cdb := fullDB.ChannelStateDB() - ch := createTestChannel(t, cdb, openChannelOption()) - - closeChannelForTest(t, cdb, ch) - - require.ErrorIs(t, ch.Refresh(), ErrChannelNotFound) - require.ErrorIs(t, ch.MarkBorked(), ErrChannelNotFound) -} - -// TestCloseChannelSync exercises the synchronous one-shot close path used by -// backends that do not opt in to tombstones (bbolt, etcd). It locks in the -// invariant that after CloseChannel returns the channel bucket and its -// revocation-log entries are already gone, and that the close summary, -// historical record, and outpoint flip are all in place. -func TestCloseChannelSync(t *testing.T) { - t.Parallel() - - fullDB, err := MakeTestDB(t) - require.NoError(t, err) - - cdb := fullDB.ChannelStateDB() - require.False(t, cdb.tombstoneClosedChannels) - - ch := createTestChannel(t, cdb, openChannelOption()) - - const numRevlogEntries = 4 - writeTestRevlogEntries(t, cdb, ch, numRevlogEntries) - writeTestForwardingPackages(t, cdb, ch, 3) - - closeChannelForTest(t, cdb, ch) - - // The synchronous path wipes the chanBucket inline, so - // countRevlogEntries must report -1 (bucket is gone, not just empty). - require.Equal(t, -1, countRevlogEntries(t, cdb, ch), - "channel bucket must be deleted after sync close") - - // Forwarding packages are wiped inline. - var fwdPkgs []*FwdPkg - packager := NewChannelPackager(ch.ShortChanID()) - require.NoError(t, kvdb.View(cdb.backend, func(tx kvdb.RTx) error { - fwdPkgs, err = packager.LoadFwdPkgs(tx) - return err - }, func() {})) - require.Empty(t, fwdPkgs) - - // The outpoint index reflects the close. - require.Equal(t, outpointClosed, readOutpointStatus( - t, cdb, ch.FundingOutpoint, - )) -} diff --git a/channeldb/codec.go b/channeldb/codec.go index a23bdd556..e5bab3d5f 100644 --- a/channeldb/codec.go +++ b/channeldb/codec.go @@ -9,9 +9,9 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" diff --git a/channeldb/db.go b/channeldb/db.go index 3ed623eb1..91f188628 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -11,7 +11,7 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/walletdb" mig "github.com/lightningnetwork/lnd/channeldb/migration" "github.com/lightningnetwork/lnd/channeldb/migration12" @@ -30,9 +30,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb/migration32" "github.com/lightningnetwork/lnd/channeldb/migration33" "github.com/lightningnetwork/lnd/channeldb/migration34" - "github.com/lightningnetwork/lnd/channeldb/migration35" "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/clock" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/invoices" @@ -43,14 +41,6 @@ import ( const ( dbName = "channel.db" - - // missingDBVersionRecoveryVersion is the latest mandatory DB - // version before the init ordering regression that could create a - // DB without writing the DB version key. Affected DBs are therefore - // already at least this version, so recovery starts here to run the - // v0.21 waiting proof migration without replaying older migrations - // against a modern DB. - missingDBVersionRecoveryVersion = 33 ) var ( @@ -313,13 +303,6 @@ var ( number: 33, migration: migration33.MigrateMCStoreNameSpacedResults, }, - { - // We skip mandatory version 34 because that - // number is already associated with an - // optional migration package. - number: 35, - migration: migration35.MigrateWaitingProofStore, - }, } // optionalVersions stores all optional migrations that are applied @@ -427,8 +410,7 @@ func CreateWithBackend(backend kvdb.Backend, modifiers ...OptionModifier) (*DB, linkNodeDB: &LinkNodeDB{ backend: backend, }, - backend: backend, - tombstoneClosedChannels: opts.tombstoneClosedChannels, + backend: backend, }, clock: opts.clock, dryRun: opts.dryRun, @@ -510,36 +492,21 @@ func initChannelDB(db kvdb.Backend) error { return err } - meta := &Meta{} - metaErr := FetchMeta(meta, tx) - for _, tlb := range dbTopLevelBuckets { if _, err := tx.CreateTopLevelBucket(tlb); err != nil { return err } } - switch { - // Metadata with a DB version already exists. Required - // top-level buckets were created above, so init is complete. - case metaErr == nil: - return nil - - // There is no metadata bucket at all, so this is a fresh DB. - // Initialize the DB version after creating the required - // top-level buckets. - case errors.Is(metaErr, ErrMetaNotFound): - meta.DbVersionNumber = getLatestDBVersion(dbVersions) - return putMeta(meta, tx) - - // The DB already has a metadata bucket but no version key. - // Leave recovery to the migration path, which can infer a - // safe starting version before writing the version key. - case errors.Is(metaErr, ErrDBVersionNotFound): + meta := &Meta{} + // Check if DB is already initialized. + err := FetchMeta(meta, tx) + if err == nil { return nil } - return metaErr + meta.DbVersionNumber = getLatestDBVersion(dbVersions) + return putMeta(meta, tx) }, func() {}) if err != nil { return fmt.Errorf("unable to create new channeldb: %w", err) @@ -573,12 +540,6 @@ type ChannelStateDB struct { // backend points to the actual backend holding the channel state // database. This may be a real backend or a cache middleware. backend kvdb.Backend - - // tombstoneClosedChannels is set by OptionTombstoneClosedChannels. - // When true, CloseChannel skips deleting nested per-channel state and - // relies on the outpointBucket flip to outpointClosed as the - // authoritative closed-channel signal. - tombstoneClosedChannels bool } // GetParentDB returns the "main" channeldb.DB object that is the owner of this @@ -652,7 +613,7 @@ func (c *ChannelStateDB) fetchOpenChannels(tx kvdb.RTx, // Finally, we both of the necessary buckets retrieved, fetch // all the active channels related to this node. - nodeChannels, err := c.fetchNodeChannels(tx, chainBucket) + nodeChannels, err := c.fetchNodeChannels(chainBucket) if err != nil { return fmt.Errorf("unable to read channel for "+ "chain_hash=%x, node_key=%x: %v", @@ -668,19 +629,12 @@ func (c *ChannelStateDB) fetchOpenChannels(tx kvdb.RTx, // fetchNodeChannels retrieves all active channels from the target chainBucket // which is under a node's dedicated channel bucket. This function is typically -// used to fetch all the active channels related to a particular node. Channels -// already flipped to outpointClosed in the outpoint index are skipped silently -// — readers see only channels that are still considered open. -func (c *ChannelStateDB) fetchNodeChannels(tx kvdb.RTx, - chainBucket kvdb.RBucket) ([]*OpenChannel, error) { +// used to fetch all the active channels related to a particular node. +func (c *ChannelStateDB) fetchNodeChannels(chainBucket kvdb.RBucket) ( + []*OpenChannel, error) { var channels []*OpenChannel - // Hoist the outpoint-bucket lookup so the closed-channel check inside - // the loop is a per-iteration map probe rather than a tx-level bucket - // resolve. - opBucket := tx.ReadBucket(outpointBucket) - // A node may have channels on several chains, so for each known chain, // we'll extract all the channels. err := chainBucket.ForEach(func(chanPoint, v []byte) error { @@ -689,24 +643,12 @@ func (c *ChannelStateDB) fetchNodeChannels(tx kvdb.RTx, return nil } - // Skip already-closed channels. The chanBucket still exists - // on disk on tombstone-enabled backends; the outpoint flip is - // the sole signal that the channel should be treated as - // closed. - isClosed, err := isOutpointClosed(opBucket, chanPoint) - if err != nil { - return err - } - if isClosed { - return nil - } - // Once we've found a valid channel bucket, we'll extract it // from the node's chain bucket. chanBucket := chainBucket.NestedReadBucket(chanPoint) var outPoint wire.OutPoint - err = graphdb.ReadOutpoint( + err := graphdb.ReadOutpoint( bytes.NewReader(chanPoint), &outPoint, ) if err != nil { @@ -753,8 +695,9 @@ func (c *ChannelStateDB) FetchChannel(chanPoint wire.OutPoint) (*OpenChannel, // FetchChannelByID attempts to locate a channel specified by the passed channel // ID. If the channel cannot be found, then an error will be returned. -func (c *ChannelStateDB) FetchChannelByID(id lnwire.ChannelID) (*OpenChannel, - error) { +// Optionally an existing db tx can be supplied. +func (c *ChannelStateDB) FetchChannelByID(tx kvdb.RTx, id lnwire.ChannelID) ( + *OpenChannel, error) { selector := func(chainBkt walletdb.ReadBucket) ([]byte, *wire.OutPoint, error) { @@ -797,11 +740,14 @@ func (c *ChannelStateDB) FetchChannelByID(id lnwire.ChannelID) (*OpenChannel, return targetChanPointBytes, targetChanPoint, nil } - return c.channelScanner(nil, selector) + return c.channelScanner(tx, selector) } // ChanCount is used by the server in determining access control. -type ChanCount = chanstate.ChanCount +type ChanCount struct { + HasOpenOrClosedChan bool + PendingOpenCount uint64 +} // FetchPermAndTempPeers returns a map where the key is the remote node's // public key and the value is a struct that has a tally of the pending-open @@ -817,11 +763,6 @@ func (c *ChannelStateDB) FetchPermAndTempPeers( return ErrNoChanDBExists } - // Hoist the outpoint-bucket lookup so the closed-channel check - // inside the nested chainBucket.ForEach below is a per-channel - // map probe rather than a tx-level bucket resolve. - opBucket := tx.ReadBucket(outpointBucket) - openChanErr := openChanBucket.ForEach(func(nodePub, v []byte) error { @@ -855,22 +796,6 @@ func (c *ChannelStateDB) FetchPermAndTempPeers( return nil } - // Skip already-closed channels: they are - // logically closed even though their - // per-channel state still resides under - // chainBucket. The closed peer's protected - // status is established below via the - // historical-channel scan. - isClosed, err := isOutpointClosed( - opBucket, chanPoint, - ) - if err != nil { - return err - } - if isClosed { - return nil - } - chanBucket := chainBucket.NestedReadBucket( chanPoint, ) @@ -1043,11 +968,6 @@ func (c *ChannelStateDB) channelScanner(tx kvdb.RTx, return ErrNoActiveChannels } - // Hoist the outpoint-bucket lookup so the closed-channel - // check inside the per-chain ForEach below pays one tx-level - // bucket resolve total instead of one per visited chanKey. - opBucket := tx.ReadBucket(outpointBucket) - // Within the node channel bucket, are the set of node pubkeys // we have channels with, we don't know the entire set, so we'll // check them all. @@ -1096,19 +1016,6 @@ func (c *ChannelStateDB) channelScanner(tx kvdb.RTx, return err } - // An already-closed channel is logically gone - // and must not be surfaced by lookup-style - // scans. - isClosed, err := isOutpointClosed( - opBucket, targetChanBytes, - ) - if err != nil { - return err - } - if isClosed { - return nil - } - chanBucket := chainBucket.NestedReadBucket( targetChanBytes, ) @@ -1272,9 +1179,7 @@ func fetchChannels(c *ChannelStateDB, filters ...fetchChannelsFilter) ( "bucket for chain=%x", chainHash[:]) } - nodeChans, err := c.fetchNodeChannels( - tx, chainBucket, - ) + nodeChans, err := c.fetchNodeChannels(chainBucket) if err != nil { return fmt.Errorf("unable to read "+ "channel for chain_hash=%x, "+ @@ -1699,8 +1604,17 @@ func (c *ChannelStateDB) RepairLinkNodes(network wire.BitcoinNet) error { } // ChannelShell is a shell of a channel that is meant to be used for channel -// recovery purposes. -type ChannelShell = chanstate.ChannelShell +// recovery purposes. It contains a minimal OpenChannel instance along with +// addresses for that target node. +type ChannelShell struct { + // NodeAddrs the set of addresses that this node has known to be + // reachable at in the past. + NodeAddrs []net.Addr + + // Chan is a shell of an OpenChannel, it contains only the items + // required to restore the channel on disk. + Chan *OpenChannel +} // RestoreChannelShells is a method that allows the caller to reconstruct the // state of an OpenChannel from the ChannelShell. We'll attempt to write the @@ -1717,10 +1631,7 @@ func (c *ChannelStateDB) RestoreChannelShells(channelShells ...*ChannelShell) er // been restored, this will signal to other sub-systems // to not attempt to use the channel as if it was a // regular one. - channel.SetChannelStatusForStore( - channel.ChannelStatusForStore() | - ChanStatusRestored, - ) + channel.chanStatus |= ChanStatusRestored // First, we'll attempt to create a new open channel // and link node for this channel. If the channel @@ -1728,7 +1639,7 @@ func (c *ChannelStateDB) RestoreChannelShells(channelShells ...*ChannelShell) er // is idempotent, we'll continue to the next step. channel.Db = c err := syncNewChannel( - tx, channel, channelShell.NodeAddrs, c.backend, + tx, channel, channelShell.NodeAddrs, ) if err != nil { return err @@ -1877,43 +1788,16 @@ func (c *ChannelStateDB) DeleteChannelOpeningState(outPoint []byte) error { // applies migration functions to the current database and recovers the // previous state of db if at least one error/panic appeared during migration. func (d *DB) syncVersions(versions []mandatoryVersion) error { - latestVersion := getLatestDBVersion(versions) - meta, err := d.FetchMeta() if err != nil { - switch { - case errors.Is(err, ErrMetaNotFound): + if err == ErrMetaNotFound { meta = &Meta{} - - case errors.Is(err, ErrDBVersionNotFound): - recoveryVersion := uint32( - missingDBVersionRecoveryVersion, - ) - - // Missing DB version recovery is only valid for DBs - // created after the init ordering regression. Older DBs - // wrote the DB version before init returned, so a - // missing version key on a sub-33 DB is not a valid - // state to infer from. - if latestVersion < recoveryVersion { - return fmt.Errorf("unable to recover missing "+ - "DB version key: latest_version=%v "+ - "recovery_version=%v", latestVersion, - recoveryVersion) - } - - log.Warnf("DB version key missing, recovering from "+ - "db_version=%v", recoveryVersion) - - meta = &Meta{ - DbVersionNumber: recoveryVersion, - } - - default: + } else { return err } } + latestVersion := getLatestDBVersion(versions) log.Infof("Checking for schema update: latest_version=%v, "+ "db_version=%v", latestVersion, meta.DbVersionNumber) diff --git a/channeldb/db_test.go b/channeldb/db_test.go index 1f87e3b66..ec2394a1c 100644 --- a/channeldb/db_test.go +++ b/channeldb/db_test.go @@ -11,15 +11,14 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/shachain" "github.com/stretchr/testify/require" ) @@ -253,7 +252,7 @@ func TestFetchChannel(t *testing.T) { // Next, attempt to fetch the channel by its channel ID. chanID := lnwire.NewChanIDFromOutPoint(channelState.FundingOutpoint) - dbChannel, err = cdb.FetchChannelByID(chanID) + dbChannel, err = cdb.FetchChannelByID(nil, chanID) require.NoError(t, err, "unable to fetch channel") // The decoded channel state should be identical to what we stored @@ -272,7 +271,7 @@ func TestFetchChannel(t *testing.T) { require.ErrorIs(t, err, ErrChannelNotFound) chanID2 := lnwire.NewChanIDFromOutPoint(channelState2.FundingOutpoint) - _, err = cdb.FetchChannelByID(chanID2) + _, err = cdb.FetchChannelByID(nil, chanID2) require.ErrorIs(t, err, ErrChannelNotFound) } @@ -307,37 +306,33 @@ func genRandomChannelShell() (*ChannelShell, error) { CsvDelay: uint16(rand.Int63()), } - channel := &OpenChannel{ - ChainHash: rev, - FundingOutpoint: chanPoint, - ShortChannelID: lnwire.NewShortChanIDFromInt( - uint64(rand.Int63()), - ), - IdentityPub: pub, - LocalChanCfg: ChannelConfig{ - CommitmentParams: commitParams, - PaymentBasePoint: keychain.KeyDescriptor{ - KeyLocator: keychain.KeyLocator{ - Family: keychain.KeyFamily( - rand.Int63(), - ), - Index: uint32(rand.Int63()), - }, - }, - }, - RemoteCurrentRevocation: pub, - IsPending: false, - RevocationStore: shachain.NewRevocationStore(), - RevocationProducer: shaChainProducer, - } - channel.SetChannelStatusForStore(chanStatus) - return &ChannelShell{ NodeAddrs: []net.Addr{&net.TCPAddr{ IP: net.ParseIP("127.0.0.1"), Port: 18555, }}, - Chan: channel, + Chan: &OpenChannel{ + chanStatus: chanStatus, + ChainHash: rev, + FundingOutpoint: chanPoint, + ShortChannelID: lnwire.NewShortChanIDFromInt( + uint64(rand.Int63()), + ), + IdentityPub: pub, + LocalChanCfg: ChannelConfig{ + CommitmentParams: commitParams, + PaymentBasePoint: keychain.KeyDescriptor{ + KeyLocator: keychain.KeyLocator{ + Family: keychain.KeyFamily(rand.Int63()), + Index: uint32(rand.Int63()), + }, + }, + }, + RemoteCurrentRevocation: pub, + IsPending: false, + RevocationStore: shachain.NewRevocationStore(), + RevocationProducer: shaChainProducer, + }, }, nil } @@ -407,7 +402,7 @@ func TestRestoreChannelShells(t *testing.T) { } if !nodeChans[0].HasChanStatus(ChanStatusRestored) { t.Fatalf("node has wrong status flags: %v", - nodeChans[0].ChanStatus()) + nodeChans[0].chanStatus) } // We should also be able to find the channel if we query for it @@ -598,6 +593,7 @@ func TestFetchChannels(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -624,7 +620,7 @@ func TestFetchChannels(t *testing.T) { ) err = pendingClosing.MarkCoopBroadcasted( - wire.NewMsgTx(2), lntypes.Local, + nil, lntypes.Local, ) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -646,7 +642,7 @@ func TestFetchChannels(t *testing.T) { openChannelOption(), ) err = openClosing.MarkCoopBroadcasted( - wire.NewMsgTx(2), lntypes.Local, + nil, lntypes.Local, ) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -815,17 +811,16 @@ func createNode(priv *btcec.PrivateKey) *models.Node { updateTime := rand.Int63() pub := priv.PubKey().SerializeCompressed() - n := models.NewV1Node( - route.NewVertex(priv.PubKey()), - &models.NodeV1Fields{ - AuthSigBytes: testSig.Serialize(), - LastUpdate: time.Unix(updateTime, 0), - Color: color.RGBA{1, 2, 3, 0}, - Alias: "kek" + string(pub), - Features: testFeatures.RawFeatureVector, - Addresses: testAddrs, - }, - ) + n := &models.Node{ + HaveNodeAnnouncement: true, + AuthSigBytes: testSig.Serialize(), + LastUpdate: time.Unix(updateTime, 0), + Color: color.RGBA{1, 2, 3, 0}, + Alias: "kek" + string(pub), + Features: testFeatures, + Addresses: testAddrs, + } + copy(n.PubKeyBytes[:], priv.PubKey().SerializeCompressed()) return n } diff --git a/channeldb/error.go b/channeldb/error.go index adadd2ae6..c2b2dde0d 100644 --- a/channeldb/error.go +++ b/channeldb/error.go @@ -42,10 +42,6 @@ var ( // created. ErrMetaNotFound = fmt.Errorf("unable to locate meta information") - // ErrDBVersionNotFound is returned when the meta bucket exists, but - // the DB version key hasn't been written. - ErrDBVersionNotFound = fmt.Errorf("unable to locate db version") - // ErrNoClosedChannels is returned when a node is queries for all the // channels it has closed, but it hasn't yet closed any channels. ErrNoClosedChannels = fmt.Errorf("no channel have been closed yet") diff --git a/channeldb/forwarding_log.go b/channeldb/forwarding_log.go index cdd8f8b75..a80985a01 100644 --- a/channeldb/forwarding_log.go +++ b/channeldb/forwarding_log.go @@ -2,7 +2,6 @@ package channeldb import ( "bytes" - "context" "errors" "io" "sort" @@ -41,10 +40,6 @@ const ( // full forwarding event (including the timestamp) is 40 bytes, we can // safely return 50k entries in a single response. MaxResponseEvents = 50000 - - // defaultDeleteBatchSize is the default number of forwarding events - // deleted per database transaction when no batch size is specified. - defaultDeleteBatchSize = 10_000 ) // ForwardingLog returns an instance of the ForwardingLog object backed by the @@ -421,161 +416,6 @@ func (f *ForwardingLog) Query(q ForwardingEventQuery) (ForwardingLogTimeSlice, return resp, nil } -// DeleteStats contains statistics about a forwarding history deletion -// operation. -type DeleteStats struct { - // NumEventsDeleted is the total number of forwarding events that were - // deleted from the database. - NumEventsDeleted uint64 - - // TotalFeeMsat is the sum of all fees (AmtIn - AmtOut) from the - // deleted events, expressed in millisatoshis. - TotalFeeMsat int64 -} - -// DeleteForwardingEvents deletes all forwarding events with a timestamp at or -// before the specified endTime from the database. The deletion is performed in -// batches to avoid holding large database transactions. This method returns -// statistics about the deletion including the number of events deleted and the -// total fees earned from those events. -// -// The batchSize parameter controls how many events are deleted per database -// transaction. If batchSize is 0, a default of 10000 is used. The maximum -// allowed batch size is MaxResponseEvents (50000) to prevent resource -// exhaustion. -// -// If the context is cancelled between batches, the method returns the partial -// statistics accumulated so far along with the context error. Callers can -// safely re-run the operation with the same parameters to resume deletion since -// committed batches are not rolled back. -func (f *ForwardingLog) DeleteForwardingEvents(ctx context.Context, - endTime time.Time, batchSize int) (DeleteStats, error) { - - // Set default batch size if not specified, and enforce maximum. - if batchSize <= 0 { - batchSize = defaultDeleteBatchSize - } - if batchSize > MaxResponseEvents { - batchSize = MaxResponseEvents - } - - // Encode the end time once outside the loop since it does not change - // between batches. - var endTimeBytes [8]byte - byteOrder.PutUint64(endTimeBytes[:], uint64(endTime.UnixNano())) - - var stats DeleteStats - - // We'll continue deleting batches until there are no more events to - // delete or the context is cancelled. - for { - // Check for cancellation between batches so callers can abort - // cleanly. Partial stats are returned so the caller knows how - // much was deleted before the abort. - if err := ctx.Err(); err != nil { - return stats, err - } - - var ( - batchDeleted int - batchFees int64 - ) - - err := kvdb.Update(f.db, func(tx kvdb.RwTx) error { - // Fetch the forwarding log bucket. If it doesn't exist, - // there's nothing to delete. - logBucket := tx.ReadWriteBucket(forwardingLogBucket) - if logBucket == nil { - return ErrNoForwardingEvents - } - - // We'll use a cursor to iterate through events in time - // order. - cursor := logBucket.ReadWriteCursor() - - // Collect keys to delete in this batch. We can't delete - // while iterating as it may corrupt the cursor. - keysToDelete := make([][]byte, 0, batchSize) - - // Seek to the beginning and iterate through events - // until we reach the end time or batch limit. - // - //nolint:ll - for timestamp, eventBytes := cursor.First(); timestamp != nil; timestamp, eventBytes = cursor.Next() { - // Stop if we've passed the end time. - // - //nolint:ll - if bytes.Compare(timestamp, endTimeBytes[:]) > 0 { - break - } - - // Stop if we've reached the batch size limit. - if len(keysToDelete) >= batchSize { - break - } - - // Decode the event to obtain the fee. - readBuf := bytes.NewReader(eventBytes) - if readBuf.Len() > 0 { - var event ForwardingEvent - err := decodeForwardingEvent( - readBuf, &event, - ) - if err != nil { - return err - } - - // Calculate the fee for this event. Cast - // before subtracting to avoid uint64 - // underflow if AmtOut > AmtIn. - fee := int64(event.AmtIn) - - int64(event.AmtOut) - batchFees += fee - } - - // Make a copy of the key to delete later. - keyCopy := make([]byte, len(timestamp)) - copy(keyCopy, timestamp) - keysToDelete = append(keysToDelete, keyCopy) - } - - // Now delete all the collected keys. - for _, key := range keysToDelete { - if err := logBucket.Delete(key); err != nil { - return err - } - } - - batchDeleted = len(keysToDelete) - - return nil - }, func() { - batchDeleted = 0 - batchFees = 0 - }) - - if err != nil { - // If the bucket doesn't exist, we're done. - if errors.Is(err, ErrNoForwardingEvents) { - break - } - - return stats, err - } - - // Update our running statistics. - stats.NumEventsDeleted += uint64(batchDeleted) - stats.TotalFeeMsat += batchFees - - // If we deleted fewer events than the batch size, we're done. - if batchDeleted < batchSize { - break - } - } - - return stats, nil -} - // makeUniqueTimestamps takes a slice of forwarding events, sorts it by the // event timestamps and then makes sure there are no duplicates in the // timestamps. If duplicates are found, some of the timestamps are increased on diff --git a/channeldb/forwarding_log_test.go b/channeldb/forwarding_log_test.go index 950b4cba8..0f589f88a 100644 --- a/channeldb/forwarding_log_test.go +++ b/channeldb/forwarding_log_test.go @@ -13,7 +13,6 @@ import ( "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "pgregory.net/rapid" ) // TestForwardingLogBasicStorageAndQuery tests that we're able to store and @@ -38,7 +37,7 @@ func TestForwardingLogBasicStorageAndQuery(t *testing.T) { // minutes after the prior event. numEvents := 100 events := make([]ForwardingEvent, numEvents) - for i := range numEvents { + for i := 0; i < numEvents; i++ { events[i] = ForwardingEvent{ Timestamp: timestamp, IncomingChanID: lnwire.NewShortChanIDFromInt(uint64(rand.Int63())), @@ -108,7 +107,7 @@ func TestForwardingLogQueryOptions(t *testing.T) { // minutes after the prior event. numEvents := 20 events := make([]ForwardingEvent, numEvents) - for i := range numEvents { + for i := 0; i < numEvents; i++ { events[i] = ForwardingEvent{ Timestamp: endTime, IncomingChanID: lnwire.NewShortChanIDFromInt(uint64(rand.Int63())), @@ -209,7 +208,7 @@ func TestForwardingLogQueryLimit(t *testing.T) { // minutes after the prior event. numEvents := 200 events := make([]ForwardingEvent, numEvents) - for i := range numEvents { + for i := 0; i < numEvents; i++ { events[i] = ForwardingEvent{ Timestamp: endTime, IncomingChanID: lnwire.NewShortChanIDFromInt(uint64(rand.Int63())), @@ -320,7 +319,7 @@ func TestForwardingLogStoreEvent(t *testing.T) { numEvents := 20 events := make([]ForwardingEvent, numEvents) ts := time.Now().UnixNano() - for i := range numEvents { + for i := 0; i < numEvents; i++ { events[i] = ForwardingEvent{ Timestamp: time.Unix(0, ts+int64(i)), IncomingChanID: lnwire.NewShortChanIDFromInt(uint64(rand.Int63())), @@ -504,7 +503,7 @@ func TestForwardingLogQueryChanIDs(t *testing.T) { } events := make([]ForwardingEvent, numEvents) - for i := range numEvents { + for i := 0; i < numEvents; i++ { events[i] = ForwardingEvent{ Timestamp: endTime, IncomingChanID: incomingChanIDs[i%len(incomingChanIDs)], @@ -607,526 +606,3 @@ func TestForwardingLogQueryChanIDs(t *testing.T) { }) } } - -// TestForwardingLogDeletion tests the basic deletion functionality of the -// forwarding log. -func TestForwardingLogDeletion(t *testing.T) { - t.Parallel() - - db, err := MakeTestDB(t) - require.NoError(t, err, "unable to make test db") - - log := ForwardingLog{ - db: db, - } - - // Create 50 events spanning 500 minutes (10 min intervals). - initialTime := time.Unix(1000, 0) - timestamp := initialTime - numEvents := 50 - events := make([]ForwardingEvent, numEvents) - - var expectedTotalFees int64 - for i := range numEvents { - amtIn := lnwire.MilliSatoshi(10000 + rand.Intn(5000)) - amtOut := lnwire.MilliSatoshi(9000 + rand.Intn(4000)) - events[i] = ForwardingEvent{ - Timestamp: timestamp, - IncomingChanID: lnwire.NewShortChanIDFromInt(uint64(i)), - OutgoingChanID: lnwire.NewShortChanIDFromInt( - uint64(i + 100), - ), - AmtIn: amtIn, - AmtOut: amtOut, - IncomingHtlcID: fn.Some(uint64(i)), - OutgoingHtlcID: fn.Some(uint64(i)), - } - expectedTotalFees += int64(amtIn - amtOut) - timestamp = timestamp.Add(time.Minute * 10) - } - - // Add all events to the database. - err = log.AddForwardingEvents(events) - require.NoError(t, err, "unable to add events") - - // Delete all events (use timestamp after the last event). - deleteTime := timestamp.Add(time.Minute) - stats, err := log.DeleteForwardingEvents( - t.Context(), deleteTime, 0, - ) - require.NoError(t, err, "unable to delete events") - - // Verify statistics. - require.Equal(t, uint64(numEvents), stats.NumEventsDeleted, - "wrong number of events deleted") - require.Equal(t, expectedTotalFees, stats.TotalFeeMsat, - "wrong total fees") - - // Verify all events were deleted by querying. - query := ForwardingEventQuery{ - StartTime: initialTime, - EndTime: timestamp, - NumMaxEvents: 1000, - } - result, err := log.Query(query) - require.NoError(t, err, "query failed") - require.Empty(t, result.ForwardingEvents, "events should be deleted") -} - -// TestForwardingLogPartialDeletion tests that we can delete a subset of events -// based on time. -func TestForwardingLogPartialDeletion(t *testing.T) { - t.Parallel() - - db, err := MakeTestDB(t) - require.NoError(t, err, "unable to make test db") - - log := ForwardingLog{ - db: db, - } - - initialTime := time.Unix(2000, 0) - timestamp := initialTime - numEvents := 100 - events := make([]ForwardingEvent, numEvents) - - for i := range numEvents { - events[i] = ForwardingEvent{ - Timestamp: timestamp, - IncomingChanID: lnwire.NewShortChanIDFromInt(uint64(i)), - OutgoingChanID: lnwire.NewShortChanIDFromInt( - uint64(i + 100), - ), - AmtIn: lnwire.MilliSatoshi(10000), - AmtOut: lnwire.MilliSatoshi(9500), - IncomingHtlcID: fn.Some(uint64(i)), - OutgoingHtlcID: fn.Some(uint64(i)), - } - timestamp = timestamp.Add(time.Minute * 10) - } - - err = log.AddForwardingEvents(events) - require.NoError(t, err, "unable to add events") - - // Delete only the first 50 events (events 0-49). The 50th event is at - // initialTime + 50*10 minutes. - deleteTime := events[49].Timestamp.Add(time.Nanosecond) - stats, err := log.DeleteForwardingEvents( - t.Context(), deleteTime, 0, - ) - require.NoError(t, err, "unable to delete events") - - // Should have deleted exactly 50 events. - require.Equal( - t, uint64(50), stats.NumEventsDeleted, - "wrong number of events deleted", - ) - - // Fee per event is 500 msat, so total should be 50 * 500 = 25000. - require.Equal(t, int64(25000), stats.TotalFeeMsat, "wrong total fees") - - // Query to verify remaining events (should be 50 events left). - query := ForwardingEventQuery{ - StartTime: initialTime, - EndTime: timestamp, - NumMaxEvents: 1000, - } - result, err := log.Query(query) - require.NoError(t, err, "query failed") - require.Len( - t, result.ForwardingEvents, 50, - "wrong number of remaining events", - ) - - // The remaining events should be events[50:]. - require.Equal( - t, events[50:], result.ForwardingEvents, - "wrong events remaining", - ) -} - -// TestForwardingLogBatchDeletion tests that deletion works correctly with -// different batch sizes. -func TestForwardingLogBatchDeletion(t *testing.T) { - t.Parallel() - - db, err := MakeTestDB(t) - require.NoError(t, err, "unable to make test db") - - log := ForwardingLog{ - db: db, - } - - initialTime := time.Unix(3000, 0) - timestamp := initialTime - numEvents := 250 - events := make([]ForwardingEvent, numEvents) - - for i := range numEvents { - events[i] = ForwardingEvent{ - Timestamp: timestamp, - IncomingChanID: lnwire.NewShortChanIDFromInt(uint64(i)), - OutgoingChanID: lnwire.NewShortChanIDFromInt( - uint64(i + 100), - ), - AmtIn: lnwire.MilliSatoshi(10000), - AmtOut: lnwire.MilliSatoshi(9000), - IncomingHtlcID: fn.Some(uint64(i)), - OutgoingHtlcID: fn.Some(uint64(i)), - } - timestamp = timestamp.Add(time.Minute) - } - - err = log.AddForwardingEvents(events) - require.NoError(t, err, "unable to add events") - - // Delete with a small batch size to test multiple batches. - deleteTime := timestamp.Add(time.Minute) - stats, err := log.DeleteForwardingEvents(t.Context(), deleteTime, 75) - require.NoError(t, err, "unable to delete events") - - // Should have deleted all events across multiple batches. - require.Equal(t, uint64(numEvents), stats.NumEventsDeleted, - "wrong number of events deleted") - - // Fee per event is 1000 msat. - expectedFees := int64(numEvents * 1000) - require.Equal(t, expectedFees, stats.TotalFeeMsat, "wrong total fees") - - // Verify all deleted. - query := ForwardingEventQuery{ - StartTime: initialTime, - EndTime: timestamp, - NumMaxEvents: 1000, - } - result, err := log.Query(query) - require.NoError(t, err, "query failed") - require.Empty(t, result.ForwardingEvents, "events should be deleted") -} - -// TestForwardingLogDeleteEmpty tests deletion on an empty database. -func TestForwardingLogDeleteEmpty(t *testing.T) { - t.Parallel() - - db, err := MakeTestDB(t) - require.NoError(t, err, "unable to make test db") - - log := ForwardingLog{ - db: db, - } - - // Try to delete from empty database. - deleteTime := time.Now() - stats, err := log.DeleteForwardingEvents( - t.Context(), deleteTime, 0, - ) - require.NoError(t, err, "delete should not error on empty db") - - // Should have deleted 0 events with 0 fees. - require.Equal( - t, uint64(0), stats.NumEventsDeleted, - "should delete 0 events", - ) - require.Equal( - t, int64(0), stats.TotalFeeMsat, "should have 0 fees", - ) -} - -// TestForwardingLogDeleteTimeBoundary tests deletion at exact time boundaries. -func TestForwardingLogDeleteTimeBoundary(t *testing.T) { - t.Parallel() - - db, err := MakeTestDB(t) - require.NoError(t, err, "unable to make test db") - - log := ForwardingLog{ - db: db, - } - - baseTime := time.Unix(5000, 0) - events := []ForwardingEvent{ - { - Timestamp: baseTime, - IncomingChanID: lnwire.NewShortChanIDFromInt(1), - OutgoingChanID: lnwire.NewShortChanIDFromInt(101), - AmtIn: 10000, - AmtOut: 9000, - IncomingHtlcID: fn.Some(uint64(0)), - OutgoingHtlcID: fn.Some(uint64(0)), - }, - { - Timestamp: baseTime.Add(time.Hour), - IncomingChanID: lnwire.NewShortChanIDFromInt(2), - OutgoingChanID: lnwire.NewShortChanIDFromInt(102), - AmtIn: 10000, - AmtOut: 9000, - IncomingHtlcID: fn.Some(uint64(1)), - OutgoingHtlcID: fn.Some(uint64(1)), - }, - { - Timestamp: baseTime.Add(2 * time.Hour), - IncomingChanID: lnwire.NewShortChanIDFromInt(3), - OutgoingChanID: lnwire.NewShortChanIDFromInt(103), - AmtIn: 10000, - AmtOut: 9000, - IncomingHtlcID: fn.Some(uint64(2)), - OutgoingHtlcID: fn.Some(uint64(2)), - }, - } - - err = log.AddForwardingEvents(events) - require.NoError(t, err, "unable to add events") - - // Delete events at exactly the second event's timestamp. This should - // delete events at baseTime and baseTime+1h. - deleteTime := baseTime.Add(time.Hour) - stats, err := log.DeleteForwardingEvents( - t.Context(), deleteTime, 0, - ) - require.NoError(t, err, "unable to delete events") - - // Should delete exactly 2 events (those at or before deleteTime). - require.Equal( - t, uint64(2), stats.NumEventsDeleted, - "wrong number of events deleted", - ) - - query := ForwardingEventQuery{ - StartTime: baseTime, - EndTime: baseTime.Add(3 * time.Hour), - NumMaxEvents: 10, - } - result, err := log.Query(query) - require.NoError(t, err, "query failed") - - // We should have 1 event remaining. - require.Len( - t, result.ForwardingEvents, 1, "wrong number remaining", - ) - require.Equal( - t, events[2], result.ForwardingEvents[0], - "wrong event remaining", - ) -} - -// TestForwardingLogDeleteMaxBatchSize tests that the max batch size is -// enforced. -func TestForwardingLogDeleteMaxBatchSize(t *testing.T) { - t.Parallel() - - db, err := MakeTestDB(t) - require.NoError(t, err, "unable to make test db") - - log := ForwardingLog{ - db: db, - } - - // Create some events. - initialTime := time.Unix(6000, 0) - events := []ForwardingEvent{ - { - Timestamp: initialTime, - IncomingChanID: lnwire.NewShortChanIDFromInt(1), - OutgoingChanID: lnwire.NewShortChanIDFromInt(101), - AmtIn: 10000, - AmtOut: 9000, - IncomingHtlcID: fn.Some(uint64(0)), - OutgoingHtlcID: fn.Some(uint64(0)), - }, - } - - err = log.AddForwardingEvents(events) - require.NoError(t, err, "unable to add events") - - // Try to delete with a batch size larger than MaxResponseEvents. - deleteTime := initialTime.Add(time.Hour) - stats, err := log.DeleteForwardingEvents( - t.Context(), deleteTime, MaxResponseEvents+1000, - ) - require.NoError(t, err, "delete should succeed") - - // Should have deleted the event (batch size should be capped). - require.Equal( - t, uint64(1), stats.NumEventsDeleted, "event should be deleted", - ) -} - -// TestForwardingLogDeleteIdempotent tests that deletion is idempotent. -func TestForwardingLogDeleteIdempotent(t *testing.T) { - t.Parallel() - - db, err := MakeTestDB(t) - require.NoError(t, err, "unable to make test db") - - log := ForwardingLog{ - db: db, - } - - // Create events. - initialTime := time.Unix(7000, 0) - timestamp := initialTime - events := make([]ForwardingEvent, 10) - for i := range 10 { - events[i] = ForwardingEvent{ - Timestamp: timestamp, - IncomingChanID: lnwire.NewShortChanIDFromInt(uint64(i)), - OutgoingChanID: lnwire.NewShortChanIDFromInt( - uint64(i + 100), - ), - AmtIn: lnwire.MilliSatoshi(10000), - AmtOut: lnwire.MilliSatoshi(9000), - IncomingHtlcID: fn.Some(uint64(i)), - OutgoingHtlcID: fn.Some(uint64(i)), - } - timestamp = timestamp.Add(time.Minute) - } - - err = log.AddForwardingEvents(events) - require.NoError(t, err, "unable to add events") - - deleteTime := timestamp - stats1, err := log.DeleteForwardingEvents(t.Context(), deleteTime, 0) - require.NoError(t, err, "first delete failed") - require.Equal(t, uint64(10), stats1.NumEventsDeleted) - - // Delete again with same time - should delete 0 events. - stats2, err := log.DeleteForwardingEvents(t.Context(), deleteTime, 0) - require.NoError(t, err, "second delete failed") - require.Equal( - t, uint64(0), stats2.NumEventsDeleted, "should be idempotent", - ) - require.Equal(t, int64(0), stats2.TotalFeeMsat, "should have no fees") -} - -// TestForwardingLogDeleteInvariants uses property-based testing to verify key -// invariants of the deletion logic. -func TestForwardingLogDeleteInvariants(t *testing.T) { - rapid.Check(t, func(rt *rapid.T) { - db, err := MakeTestDB(t) - require.NoError(rt, err, "unable to make test db") - - log := ForwardingLog{ - db: db, - } - - // Generate a random set of events. - baseTime := time.Unix( - rapid.Int64Range(10000, 100000).Draw(rt, "base_time"), - 0, - ) - numEvents := rapid.IntRange(1, 100).Draw(rt, "num_events") - - events := make([]ForwardingEvent, numEvents) - timestamp := baseTime - for i := range numEvents { - amtIn := rapid.Uint64Range(1000, 100000). - Draw(rt, "amt_in") - - amtOut := rapid.Uint64Range(500, amtIn). - Draw(rt, "amt_out") - - events[i] = ForwardingEvent{ - Timestamp: timestamp, - IncomingChanID: lnwire.NewShortChanIDFromInt( - rapid.Uint64().Draw(rt, "in_chan"), - ), - OutgoingChanID: lnwire.NewShortChanIDFromInt( - rapid.Uint64().Draw(rt, "out_chan"), - ), - AmtIn: lnwire.MilliSatoshi(amtIn), - AmtOut: lnwire.MilliSatoshi(amtOut), - IncomingHtlcID: fn.Some(uint64(i)), - OutgoingHtlcID: fn.Some(uint64(i)), - } - // Add random interval between events (1 second to 1 - // hour). - interval := rapid.Int64Range(1, 3600).Draw( - rt, "interval", - ) - timestamp = timestamp.Add( - time.Duration(interval) * time.Second, - ) - } - - // Add events to database. - err = log.AddForwardingEvents(events) - require.NoError(rt, err, "unable to add events") - - // Pick a random delete time somewhere in the middle or after. - // This gives us a mix of partial and full deletions. - deleteIndex := rapid.IntRange(0, numEvents).Draw( - rt, "delete_index", - ) - - var deleteTime time.Time - if deleteIndex < numEvents { - deleteTime = events[deleteIndex].Timestamp - } else { - deleteTime = timestamp.Add(time.Hour) - } - - // Pick a random batch size, then delete with that batch size. - batchSize := rapid.IntRange(1, 100).Draw(rt, "batch_size") - stats, err := log.DeleteForwardingEvents( - t.Context(), deleteTime, batchSize, - ) - require.NoError(rt, err, "delete failed") - - // Invariant 1: Number of deleted events should match count - // before delete time. - expectedDeleted := 0 - var expectedFees int64 - for _, event := range events { - if event.Timestamp.Before(deleteTime) || - event.Timestamp.Equal(deleteTime) { - - expectedDeleted++ - - expectedFees += int64( - event.AmtIn - event.AmtOut, - ) - } - } - require.Equal( - rt, uint64(expectedDeleted), stats.NumEventsDeleted, - "deleted count doesn't match", - ) - - // Invariant 2: Total fees should equal sum of deleted event - // fees. - require.Equal(rt, expectedFees, stats.TotalFeeMsat, - "total fees don't match") - - // Invariant 3: Query should only return events after delete - // time. - query := ForwardingEventQuery{ - StartTime: baseTime, - EndTime: timestamp.Add(time.Hour), - NumMaxEvents: uint32(numEvents * 2), - } - result, err := log.Query(query) - require.NoError(rt, err, "query failed") - - expectedRemaining := numEvents - expectedDeleted - require.Len(rt, result.ForwardingEvents, expectedRemaining, - "wrong number of remaining events") - - // Invariant 4: All remaining events should be after delete - // time. - for _, event := range result.ForwardingEvents { - require.True(rt, event.Timestamp.After(deleteTime), - "remaining event is not after delete time: "+ - "%v <= %v", event.Timestamp, deleteTime) - } - - // Invariant 5: Second deletion should be idempotent. - stats2, err := log.DeleteForwardingEvents( - t.Context(), deleteTime, batchSize, - ) - require.NoError(rt, err, "second delete failed") - require.Equal(rt, uint64(0), stats2.NumEventsDeleted, - "second delete should delete nothing") - require.Equal(rt, int64(0), stats2.TotalFeeMsat, - "second delete should have no fees") - }) -} diff --git a/channeldb/forwarding_package.go b/channeldb/forwarding_package.go index 6b9dfd3f5..c393a53b3 100644 --- a/channeldb/forwarding_package.go +++ b/channeldb/forwarding_package.go @@ -2,58 +2,40 @@ package channeldb import ( "bytes" + "encoding/binary" "errors" + "fmt" + "io" - cstate "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" ) -type ( - // AddRef is used to identify a particular Add in a FwdPkg. - AddRef = cstate.AddRef +// ErrCorruptedFwdPkg signals that the on-disk structure of the forwarding +// package has potentially been mangled. +var ErrCorruptedFwdPkg = errors.New("fwding package db has been corrupted") - // SettleFailRef is used to locate a Settle/Fail in another channel's - // FwdPkg. - SettleFailRef = cstate.SettleFailRef - - // FwdState is an enum used to describe the lifecycle of a FwdPkg. - FwdState = cstate.FwdState - - // PkgFilter is used to compactly represent a particular subset of the - // Adds in a forwarding package. - PkgFilter = cstate.PkgFilter - - // FwdPkg records all adds, settles, and fails that were locked in as a - // result of the remote peer sending us a revocation. - FwdPkg = cstate.FwdPkg -) +// FwdState is an enum used to describe the lifecycle of a FwdPkg. +type FwdState byte const ( // FwdStateLockedIn is the starting state for all forwarding packages. - FwdStateLockedIn = cstate.FwdStateLockedIn + // Packages in this state have not yet committed to the exact set of + // Adds to forward to the switch. + FwdStateLockedIn FwdState = iota // FwdStateProcessed marks the state in which all Adds have been - // locally processed. - FwdStateProcessed = cstate.FwdStateProcessed + // locally processed and the forwarding decision to the switch has been + // persisted. + FwdStateProcessed - // FwdStateCompleted signals that all Adds have been acked, and that - // all settles and fails have been delivered to their sources. - FwdStateCompleted = cstate.FwdStateCompleted + // FwdStateCompleted signals that all Adds have been acked, and that all + // settles and fails have been delivered to their sources. Packages in + // this state can be removed permanently. + FwdStateCompleted ) var ( - // NewPkgFilter initializes an empty PkgFilter supporting `count` - // elements. - NewPkgFilter = cstate.NewPkgFilter - - // NewFwdPkg initializes a new forwarding package in FwdStateLockedIn. - NewFwdPkg = cstate.NewFwdPkg - - // ErrCorruptedFwdPkg signals that the on-disk structure of the - // forwarding package has potentially been mangled. - ErrCorruptedFwdPkg = errors.New("fwding package db has been corrupted") - // fwdPackagesKey is the root-level bucket that all forwarding packages // are written. This bucket is further subdivided based on the short // channel ID of each channel. @@ -117,6 +99,283 @@ var ( settleFailFilterKey = []byte("settle-fail-filter-key") ) +// PkgFilter is used to compactly represent a particular subset of the Adds in a +// forwarding package. Each filter is represented as a simple, statically-sized +// bitvector, where the elements are intended to be the indices of the Adds as +// they are written in the FwdPkg. +type PkgFilter struct { + count uint16 + filter []byte +} + +// NewPkgFilter initializes an empty PkgFilter supporting `count` elements. +func NewPkgFilter(count uint16) *PkgFilter { + // We add 7 to ensure that the integer division yields properly rounded + // values. + filterLen := (count + 7) / 8 + + return &PkgFilter{ + count: count, + filter: make([]byte, filterLen), + } +} + +// Count returns the number of elements represented by this PkgFilter. +func (f *PkgFilter) Count() uint16 { + return f.count +} + +// Set marks the `i`-th element as included by this filter. +// NOTE: It is assumed that i is always less than count. +func (f *PkgFilter) Set(i uint16) { + byt := i / 8 + bit := i % 8 + + // Set the i-th bit in the filter. + // TODO(conner): ignore if > count to prevent panic? + f.filter[byt] |= byte(1 << (7 - bit)) +} + +// Contains queries the filter for membership of index `i`. +// NOTE: It is assumed that i is always less than count. +func (f *PkgFilter) Contains(i uint16) bool { + byt := i / 8 + bit := i % 8 + + // Read the i-th bit in the filter. + // TODO(conner): ignore if > count to prevent panic? + return f.filter[byt]&(1<<(7-bit)) != 0 +} + +// Equal checks two PkgFilters for equality. +func (f *PkgFilter) Equal(f2 *PkgFilter) bool { + if f == f2 { + return true + } + if f.count != f2.count { + return false + } + + return bytes.Equal(f.filter, f2.filter) +} + +// IsFull returns true if every element in the filter has been Set, and false +// otherwise. +func (f *PkgFilter) IsFull() bool { + // Batch validate bytes that are fully used. + for i := uint16(0); i < f.count/8; i++ { + if f.filter[i] != 0xFF { + return false + } + } + + // If the count is not a multiple of 8, check that the filter contains + // all remaining bits. + rem := f.count % 8 + for idx := f.count - rem; idx < f.count; idx++ { + if !f.Contains(idx) { + return false + } + } + + return true +} + +// Size returns number of bytes produced when the PkgFilter is serialized. +func (f *PkgFilter) Size() uint16 { + // 2 bytes for uint16 `count`, then round up number of bytes required to + // represent `count` bits. + return 2 + (f.count+7)/8 +} + +// Encode writes the filter to the provided io.Writer. +func (f *PkgFilter) Encode(w io.Writer) error { + if err := binary.Write(w, binary.BigEndian, f.count); err != nil { + return err + } + + _, err := w.Write(f.filter) + + return err +} + +// Decode reads the filter from the provided io.Reader. +func (f *PkgFilter) Decode(r io.Reader) error { + if err := binary.Read(r, binary.BigEndian, &f.count); err != nil { + return err + } + + f.filter = make([]byte, f.Size()-2) + _, err := io.ReadFull(r, f.filter) + + return err +} + +// String returns a human-readable string. +func (f *PkgFilter) String() string { + return fmt.Sprintf("count=%v, filter=%v", f.count, f.filter) +} + +// FwdPkg records all adds, settles, and fails that were locked in as a result +// of the remote peer sending us a revocation. Each package is identified by +// the short chanid and remote commitment height corresponding to the revocation +// that locked in the HTLCs. For everything except a locally initiated payment, +// settles and fails in a forwarding package must have a corresponding Add in +// another package, and can be removed individually once the source link has +// received the fail/settle. +// +// Adds cannot be removed, as we need to present the same batch of Adds to +// properly handle replay protection. Instead, we use a PkgFilter to mark that +// we have finished processing a particular Add. A FwdPkg should only be deleted +// after the AckFilter is full and all settles and fails have been persistently +// removed. +type FwdPkg struct { + // Source identifies the channel that wrote this forwarding package. + Source lnwire.ShortChannelID + + // Height is the height of the remote commitment chain that locked in + // this forwarding package. + Height uint64 + + // State signals the persistent condition of the package and directs how + // to reprocess the package in the event of failures. + State FwdState + + // Adds contains all add messages which need to be processed and + // forwarded to the switch. Adds does not change over the life of a + // forwarding package. + Adds []LogUpdate + + // FwdFilter is a filter containing the indices of all Adds that were + // forwarded to the switch. + // + // NOTE: This value signals when persisted to disk that the fwd package + // has been processed and garbage collection can happen. So it also + // has to be set for packages with no adds (empty packages or only + // settle/fail packages) so that they can be garbage collected as well. + FwdFilter *PkgFilter + + // AckFilter is a filter containing the indices of all Adds for which + // the source has received a settle or fail and is reflected in the next + // commitment txn. A package should not be removed until IsFull() + // returns true. + AckFilter *PkgFilter + + // SettleFails contains all settle and fail messages that should be + // forwarded to the switch. + SettleFails []LogUpdate + + // SettleFailFilter is a filter containing the indices of all Settle or + // Fails originating in this package that have been received and locked + // into the incoming link's commitment state. + SettleFailFilter *PkgFilter +} + +// NewFwdPkg initializes a new forwarding package in FwdStateLockedIn. This +// should be used to create a package at the time we receive a revocation. +func NewFwdPkg(source lnwire.ShortChannelID, height uint64, + addUpdates, settleFailUpdates []LogUpdate) *FwdPkg { + + nAddUpdates := uint16(len(addUpdates)) + nSettleFailUpdates := uint16(len(settleFailUpdates)) + + return &FwdPkg{ + Source: source, + Height: height, + State: FwdStateLockedIn, + Adds: addUpdates, + FwdFilter: NewPkgFilter(nAddUpdates), + AckFilter: NewPkgFilter(nAddUpdates), + SettleFails: settleFailUpdates, + SettleFailFilter: NewPkgFilter(nSettleFailUpdates), + } +} + +// SourceRef is a convenience method that returns an AddRef to this forwarding +// package for the index in the argument. It is the caller's responsibility +// to ensure that the index is in bounds. +func (f *FwdPkg) SourceRef(i uint16) AddRef { + return AddRef{ + Height: f.Height, + Index: i, + } +} + +// DestRef is a convenience method that returns a SettleFailRef to this +// forwarding package for the index in the argument. It is the caller's +// responsibility to ensure that the index is in bounds. +func (f *FwdPkg) DestRef(i uint16) SettleFailRef { + return SettleFailRef{ + Source: f.Source, + Height: f.Height, + Index: i, + } +} + +// ID returns an unique identifier for this package, used to ensure that sphinx +// replay processing of this batch is idempotent. +func (f *FwdPkg) ID() []byte { + var id = make([]byte, 16) + byteOrder.PutUint64(id[:8], f.Source.ToUint64()) + byteOrder.PutUint64(id[8:], f.Height) + return id +} + +// String returns a human-readable description of the forwarding package. +func (f *FwdPkg) String() string { + return fmt.Sprintf("%T(src=%v, height=%v, nadds=%v, nfailsettles=%v)", + f, f.Source, f.Height, len(f.Adds), len(f.SettleFails)) +} + +// AddRef is used to identify a particular Add in a FwdPkg. The short channel ID +// is assumed to be that of the packager. +type AddRef struct { + // Height is the remote commitment height that locked in the Add. + Height uint64 + + // Index is the index of the Add within the fwd pkg's Adds. + // + // NOTE: This index is static over the lifetime of a forwarding package. + Index uint16 +} + +// Encode serializes the AddRef to the given io.Writer. +func (a *AddRef) Encode(w io.Writer) error { + if err := binary.Write(w, binary.BigEndian, a.Height); err != nil { + return err + } + + return binary.Write(w, binary.BigEndian, a.Index) +} + +// Decode deserializes the AddRef from the given io.Reader. +func (a *AddRef) Decode(r io.Reader) error { + if err := binary.Read(r, binary.BigEndian, &a.Height); err != nil { + return err + } + + return binary.Read(r, binary.BigEndian, &a.Index) +} + +// SettleFailRef is used to locate a Settle/Fail in another channel's FwdPkg. A +// channel does not remove its own Settle/Fail htlcs, so the source is provided +// to locate a db bucket belonging to another channel. +type SettleFailRef struct { + // Source identifies the outgoing link that locked in the settle or + // fail. This is then used by the *incoming* link to find the settle + // fail in another link's forwarding packages. + Source lnwire.ShortChannelID + + // Height is the remote commitment height that locked in this + // Settle/Fail. + Height uint64 + + // Index is the index of the Add with the fwd pkg's SettleFails. + // + // NOTE: This index is static over the lifetime of a forwarding package. + Index uint16 +} + // SettleFailAcker is a generic interface providing the ability to acknowledge // settle/fail HTLCs stored in forwarding packages. type SettleFailAcker interface { @@ -177,10 +436,6 @@ func (*SwitchPackager) LoadChannelFwdPkgs(tx kvdb.RTx, // FwdPackager supports all operations required to modify fwd packages, such as // creation, updates, reading, and removal. The interfaces are broken down in // this way to support future delegation of the subinterfaces. -// -// TODO(ziggie): This kvdb transaction-level interface can likely be removed -// now that chanstate.OpenChannelFwdPkgStore provides the backend-independent -// forwarding package abstraction. type FwdPackager interface { // AddFwdPkg serializes and writes a FwdPkg for this channel at the // remote commitment height included in the forwarding package. diff --git a/channeldb/forwarding_package_test.go b/channeldb/forwarding_package_test.go index 5f47336f6..b11764bee 100644 --- a/channeldb/forwarding_package_test.go +++ b/channeldb/forwarding_package_test.go @@ -6,7 +6,7 @@ import ( "runtime" "testing" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" diff --git a/channeldb/height_hint.go b/channeldb/height_hint.go index 5c44d330c..9744c17c0 100644 --- a/channeldb/height_hint.go +++ b/channeldb/height_hint.go @@ -91,6 +91,7 @@ func (c *HeightHintCache) CommitSpendHint(height uint32, } for _, spendRequest := range spendRequests { + spendRequest := spendRequest spendHintKey, err := spendHintKey(&spendRequest) if err != nil { return err @@ -160,6 +161,7 @@ func (c *HeightHintCache) PurgeSpendHint( } for _, spendRequest := range spendRequests { + spendRequest := spendRequest spendHintKey, err := spendHintKey(&spendRequest) if err != nil { return err @@ -196,6 +198,7 @@ func (c *HeightHintCache) CommitConfirmHint(height uint32, } for _, confRequest := range confRequests { + confRequest := confRequest confHintKey, err := confHintKey(&confRequest) if err != nil { return err @@ -266,6 +269,7 @@ func (c *HeightHintCache) PurgeConfirmHint( } for _, confRequest := range confRequests { + confRequest := confRequest confHintKey, err := confHintKey(&confRequest) if err != nil { return err diff --git a/channeldb/height_hint_test.go b/channeldb/height_hint_test.go index bf2dd8949..1549ee5f4 100644 --- a/channeldb/height_hint_test.go +++ b/channeldb/height_hint_test.go @@ -4,8 +4,8 @@ import ( "bytes" "testing" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/stretchr/testify/require" ) diff --git a/channeldb/invoices.go b/channeldb/invoices.go index cee6164f1..ab8d1426f 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -938,6 +938,7 @@ func (k *kvInvoiceUpdater) storeAddHtlcsUpdate() error { // As we don't update the settle index above for AMP invoices, we'll do // it here for each sub-AMP invoice that was settled. for settledSetID := range k.settledSetIDs { + settledSetID := settledSetID err := k.setSettleMetaFields(&settledSetID) if err != nil { return err @@ -1861,6 +1862,7 @@ func ampStateEncoder(w io.Writer, val interface{}, buf *[8]byte) error { // inner length prefix. for setID, ampState := range *v { setID := [32]byte(setID) + ampState := ampState htlcState := uint8(ampState.State) settleDate := ampState.SettleDate diff --git a/channeldb/log.go b/channeldb/log.go index eab01704d..fb2a85d01 100644 --- a/channeldb/log.go +++ b/channeldb/log.go @@ -13,7 +13,6 @@ import ( "github.com/lightningnetwork/lnd/channeldb/migration32" "github.com/lightningnetwork/lnd/channeldb/migration33" "github.com/lightningnetwork/lnd/channeldb/migration34" - "github.com/lightningnetwork/lnd/channeldb/migration35" "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11" "github.com/lightningnetwork/lnd/kvdb" ) @@ -49,6 +48,5 @@ func UseLogger(logger btclog.Logger) { migration32.UseLogger(logger) migration33.UseLogger(logger) migration34.UseLogger(logger) - migration35.UseLogger(logger) kvdb.UseLogger(logger) } diff --git a/channeldb/meta.go b/channeldb/meta.go index b23c88014..127acf51f 100644 --- a/channeldb/meta.go +++ b/channeldb/meta.go @@ -46,7 +46,7 @@ type Meta struct { // FetchMeta fetches the metadata from boltdb and returns filled meta structure. func (d *DB) FetchMeta() (*Meta, error) { - meta := &Meta{} + var meta *Meta err := kvdb.View(d, func(tx kvdb.RTx) error { return FetchMeta(meta, tx) @@ -70,11 +70,11 @@ func FetchMeta(meta *Meta, tx kvdb.RTx) error { data := metaBucket.Get(dbVersionKey) if data == nil { - return ErrDBVersionNotFound + meta.DbVersionNumber = getLatestDBVersion(dbVersions) + } else { + meta.DbVersionNumber = byteOrder.Uint32(data) } - meta.DbVersionNumber = byteOrder.Uint32(data) - return nil } diff --git a/channeldb/meta_test.go b/channeldb/meta_test.go index a7a0b4fd9..066678c1a 100644 --- a/channeldb/meta_test.go +++ b/channeldb/meta_test.go @@ -2,14 +2,12 @@ package channeldb import ( "bytes" - "encoding/binary" "errors" "fmt" "testing" "github.com/btcsuite/btcwallet/walletdb" "github.com/lightningnetwork/lnd/kvdb" - "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" ) @@ -549,7 +547,7 @@ func TestApplyOptionalVersions(t *testing.T) { require.Equal(t, 0, migrateCount, "expected no migration") // Check the optional meta is not updated. - _, err = db.fetchOptionalMeta() + om, err := db.fetchOptionalMeta() require.NoError(t, err, "error getting optional meta") // Enable all optional migrations. @@ -565,7 +563,7 @@ func TestApplyOptionalVersions(t *testing.T) { ) // Fetch the updated optional meta. - om, err := db.fetchOptionalMeta() + om, err = db.fetchOptionalMeta() require.NoError(t, err, "error getting optional meta") // Verify that the optional meta is updated as expected. @@ -605,217 +603,6 @@ func TestFetchMeta(t *testing.T) { require.NoError(t, err) require.Equal(t, LatestDBVersion(), meta.DbVersionNumber) - - err = db.View(func(tx walletdb.ReadTx) error { - metaBucket := tx.ReadBucket(metaBucket) - require.NotNil(t, metaBucket) - - versionBytes := metaBucket.Get(dbVersionKey) - require.Len(t, versionBytes, 4) - require.Equal( - t, LatestDBVersion(), byteOrder.Uint32(versionBytes), - ) - - return nil - }, func() {}) - require.NoError(t, err) -} - -// TestFetchMetaMissingDBVersion asserts that metadata with no DB version key is -// reported as incomplete metadata. -func TestFetchMetaMissingDBVersion(t *testing.T) { - t.Parallel() - - backend, cleanup, err := kvdb.GetTestBackend(t.TempDir(), "cdb") - require.NoError(t, err) - t.Cleanup(cleanup) - - err = kvdb.Update(backend, func(tx kvdb.RwTx) error { - _, err := tx.CreateTopLevelBucket(metaBucket) - - return err - }, func() {}) - require.NoError(t, err) - - db := &DB{ - Backend: backend, - } - - _, err = db.FetchMeta() - require.ErrorIs(t, err, ErrDBVersionNotFound) - - err = kvdb.View(backend, func(tx kvdb.RTx) error { - meta := &Meta{} - err := FetchMeta(meta, tx) - require.ErrorIs(t, err, ErrDBVersionNotFound) - - return nil - }, func() {}) - require.NoError(t, err) -} - -// TestInitChannelDBCreatesMissingTopLevelBuckets asserts that initialized DBs -// with missing top-level buckets are repaired during initialization. -func TestInitChannelDBCreatesMissingTopLevelBuckets(t *testing.T) { - t.Parallel() - - backend, cleanup, err := kvdb.GetTestBackend(t.TempDir(), "cdb") - require.NoError(t, err) - t.Cleanup(cleanup) - - err = kvdb.Update(backend, func(tx kvdb.RwTx) error { - meta := &Meta{ - DbVersionNumber: LatestDBVersion(), - } - - return putMeta(meta, tx) - }, func() {}) - require.NoError(t, err) - - err = kvdb.View(backend, func(tx kvdb.RTx) error { - require.Nil(t, tx.ReadBucket(historicalChannelBucket)) - - return nil - }, func() {}) - require.NoError(t, err) - - require.NoError(t, initChannelDB(backend)) - - err = kvdb.View(backend, func(tx kvdb.RTx) error { - require.NotNil(t, tx.ReadBucket(historicalChannelBucket)) - - return nil - }, func() {}) - require.NoError(t, err) -} - -// TestMissingDBVersionRunsWaitingProofMigration asserts that a DB initialized -// without a version key is recovered from the last v0.20 mandatory version so -// migration 35 can migrate legacy waiting proof records. -func TestMissingDBVersionRunsWaitingProofMigration(t *testing.T) { - t.Parallel() - - backend, cleanup, err := kvdb.GetTestBackend(t.TempDir(), "cdb") - require.NoError(t, err) - t.Cleanup(cleanup) - - const scid = 101 - ann := &lnwire.AnnounceSignatures1{ - ChannelID: lnwire.ChannelID{1, 2, 3}, - ShortChannelID: lnwire.NewShortChanIDFromInt(scid), - NodeSignature: wireSig, - BitcoinSignature: wireSig, - ExtraOpaqueData: []byte{4, 5, 6}, - } - - legacyKey, legacyValue := encodeLegacyWaitingProof(t, true, ann) - - err = kvdb.Update(backend, func(tx kvdb.RwTx) error { - _, err := tx.CreateTopLevelBucket(metaBucket) - if err != nil { - return err - } - - bucket, err := tx.CreateTopLevelBucket(waitingProofsBucketKey) - if err != nil { - return err - } - - return bucket.Put(legacyKey[:], legacyValue) - }, func() {}) - require.NoError(t, err) - - db, err := CreateWithBackend(backend) - require.NoError(t, err) - t.Cleanup(func() { - require.NoError(t, db.Close()) - }) - - err = db.View(func(tx kvdb.RTx) error { - metaBucket := tx.ReadBucket(metaBucket) - require.NotNil(t, metaBucket) - - versionBytes := metaBucket.Get(dbVersionKey) - require.Len(t, versionBytes, 4) - require.Equal( - t, LatestDBVersion(), byteOrder.Uint32(versionBytes), - ) - - bucket := tx.ReadBucket(waitingProofsBucketKey) - require.NotNil(t, bucket) - require.Nil(t, bucket.Get(legacyKey[:])) - - proof := NewWaitingProof(true, ann) - typedKey := proof.Key() - require.NotNil(t, bucket.Get(typedKey[:])) - - return nil - }, func() {}) - require.NoError(t, err) - - store, err := NewWaitingProofStore(db) - require.NoError(t, err) - - proof := NewWaitingProof(true, ann) - migratedProof, err := store.Get(proof.Key()) - require.NoError(t, err) - require.Equal(t, proof.Key(), migratedProof.Key()) -} - -// TestMissingDBVersionRecoveryRequiresBaseline asserts that a missing version -// key cannot be recovered if the target version list does not include the -// recovery baseline. -func TestMissingDBVersionRecoveryRequiresBaseline(t *testing.T) { - t.Parallel() - - backend, cleanup, err := kvdb.GetTestBackend(t.TempDir(), "cdb") - require.NoError(t, err) - t.Cleanup(cleanup) - - err = kvdb.Update(backend, func(tx kvdb.RwTx) error { - _, err := tx.CreateTopLevelBucket(metaBucket) - - return err - }, func() {}) - require.NoError(t, err) - - db := &DB{ - Backend: backend, - } - - versions := []mandatoryVersion{ - { - number: 0, - migration: nil, - }, - { - number: 1, - migration: nil, - }, - } - - err = db.syncVersions(versions) - require.ErrorContains(t, err, "unable to recover missing DB version") -} - -// encodeLegacyWaitingProof encodes a waiting proof using the pre-migration -// format. -func encodeLegacyWaitingProof(t *testing.T, isRemote bool, - ann *lnwire.AnnounceSignatures1) ([9]byte, []byte) { - - t.Helper() - - var key [9]byte - binary.BigEndian.PutUint64(key[:8], ann.ShortChannelID.ToUint64()) - if isRemote { - key[8] = 1 - } - - var value bytes.Buffer - require.NoError(t, binary.Write(&value, byteOrder, isRemote)) - require.NoError(t, ann.Encode(&value, 0)) - - return key, value.Bytes() } // TestMarkerAndTombstone tests that markers like a tombstone can be added to a diff --git a/channeldb/migration/create_tlb_test.go b/channeldb/migration/create_tlb_test.go index 31171651b..164e8f27f 100644 --- a/channeldb/migration/create_tlb_test.go +++ b/channeldb/migration/create_tlb_test.go @@ -37,6 +37,7 @@ func TestCreateTLB(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { migtest.ApplyMigration( t, diff --git a/channeldb/migration/lnwire21/accept_channel.go b/channeldb/migration/lnwire21/accept_channel.go index 2a50e2dfc..6069d3d00 100644 --- a/channeldb/migration/lnwire21/accept_channel.go +++ b/channeldb/migration/lnwire21/accept_channel.go @@ -4,7 +4,7 @@ import ( "io" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" ) // AcceptChannel is the message Bob sends to Alice after she initiates the diff --git a/channeldb/migration/lnwire21/channel_announcement.go b/channeldb/migration/lnwire21/channel_announcement.go index 3c1f55dea..897844fb1 100644 --- a/channeldb/migration/lnwire21/channel_announcement.go +++ b/channeldb/migration/lnwire21/channel_announcement.go @@ -4,7 +4,7 @@ import ( "bytes" "io" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" ) // ChannelAnnouncement message is used to announce the existence of a channel diff --git a/channeldb/migration/lnwire21/channel_id.go b/channeldb/migration/lnwire21/channel_id.go index 7814cfcce..0a9e08226 100644 --- a/channeldb/migration/lnwire21/channel_id.go +++ b/channeldb/migration/lnwire21/channel_id.go @@ -5,8 +5,8 @@ import ( "encoding/hex" "math" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" ) const ( diff --git a/channeldb/migration/lnwire21/channel_update.go b/channeldb/migration/lnwire21/channel_update.go index 79d6edf64..77e65e76b 100644 --- a/channeldb/migration/lnwire21/channel_update.go +++ b/channeldb/migration/lnwire21/channel_update.go @@ -5,7 +5,7 @@ import ( "fmt" "io" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" ) // ChanUpdateMsgFlags is a bitfield that signals whether optional fields are diff --git a/channeldb/migration/lnwire21/closing_signed.go b/channeldb/migration/lnwire21/closing_signed.go index af8dc2ad3..b4838854e 100644 --- a/channeldb/migration/lnwire21/closing_signed.go +++ b/channeldb/migration/lnwire21/closing_signed.go @@ -3,7 +3,7 @@ package lnwire import ( "io" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" ) // ClosingSigned is sent by both parties to a channel once the channel is clear diff --git a/channeldb/migration/lnwire21/funding_created.go b/channeldb/migration/lnwire21/funding_created.go index 16edbfa74..c14321ec8 100644 --- a/channeldb/migration/lnwire21/funding_created.go +++ b/channeldb/migration/lnwire21/funding_created.go @@ -3,7 +3,7 @@ package lnwire import ( "io" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" ) // FundingCreated is sent from Alice (the initiator) to Bob (the responder), diff --git a/channeldb/migration/lnwire21/gossip_timestamp_range.go b/channeldb/migration/lnwire21/gossip_timestamp_range.go index b4716bede..039257c26 100644 --- a/channeldb/migration/lnwire21/gossip_timestamp_range.go +++ b/channeldb/migration/lnwire21/gossip_timestamp_range.go @@ -3,7 +3,7 @@ package lnwire import ( "io" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" ) // GossipTimestampRange is a message that allows the sender to restrict the set diff --git a/channeldb/migration/lnwire21/lnwire.go b/channeldb/migration/lnwire21/lnwire.go index 95e1924f4..e3f96e2cd 100644 --- a/channeldb/migration/lnwire21/lnwire.go +++ b/channeldb/migration/lnwire21/lnwire.go @@ -11,9 +11,9 @@ import ( "net" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/tor" ) diff --git a/channeldb/migration/lnwire21/msat.go b/channeldb/migration/lnwire21/msat.go index 6a80e726c..3c3d85c33 100644 --- a/channeldb/migration/lnwire21/msat.go +++ b/channeldb/migration/lnwire21/msat.go @@ -4,7 +4,7 @@ import ( "fmt" "io" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/tlv" ) diff --git a/channeldb/migration/lnwire21/netaddress.go b/channeldb/migration/lnwire21/netaddress.go index 0b9311b3c..dd5a7c57b 100644 --- a/channeldb/migration/lnwire21/netaddress.go +++ b/channeldb/migration/lnwire21/netaddress.go @@ -5,7 +5,7 @@ import ( "net" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" ) // NetAddress represents information pertaining to the identity and network diff --git a/channeldb/migration/lnwire21/open_channel.go b/channeldb/migration/lnwire21/open_channel.go index 7955109d1..36747894e 100644 --- a/channeldb/migration/lnwire21/open_channel.go +++ b/channeldb/migration/lnwire21/open_channel.go @@ -4,8 +4,8 @@ import ( "io" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" ) // FundingFlag represents the possible bit mask values for the ChannelFlags diff --git a/channeldb/migration/lnwire21/query_channel_range.go b/channeldb/migration/lnwire21/query_channel_range.go index 8677a3bb8..9546fcd32 100644 --- a/channeldb/migration/lnwire21/query_channel_range.go +++ b/channeldb/migration/lnwire21/query_channel_range.go @@ -4,7 +4,7 @@ import ( "io" "math" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" ) // QueryChannelRange is a message sent by a node in order to query the diff --git a/channeldb/migration/lnwire21/query_short_chan_ids.go b/channeldb/migration/lnwire21/query_short_chan_ids.go index 96ccdd9d8..7e8d1e68f 100644 --- a/channeldb/migration/lnwire21/query_short_chan_ids.go +++ b/channeldb/migration/lnwire21/query_short_chan_ids.go @@ -8,7 +8,7 @@ import ( "sort" "sync" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" ) // ShortChanIDEncoding is an enum-like type that represents exactly how a set diff --git a/channeldb/migration/lnwire21/reply_short_chan_ids_end.go b/channeldb/migration/lnwire21/reply_short_chan_ids_end.go index 335692a6c..d77aa0b5c 100644 --- a/channeldb/migration/lnwire21/reply_short_chan_ids_end.go +++ b/channeldb/migration/lnwire21/reply_short_chan_ids_end.go @@ -3,7 +3,7 @@ package lnwire import ( "io" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" ) // ReplyShortChanIDsEnd is a message that marks the end of a streaming message diff --git a/channeldb/migration12/invoices.go b/channeldb/migration12/invoices.go index 69d79e5c7..6b83518f3 100644 --- a/channeldb/migration12/invoices.go +++ b/channeldb/migration12/invoices.go @@ -6,7 +6,7 @@ import ( "io" "time" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/tlv" diff --git a/channeldb/migration12/migration_test.go b/channeldb/migration12/migration_test.go index 7baae6435..75d74fdd6 100644 --- a/channeldb/migration12/migration_test.go +++ b/channeldb/migration12/migration_test.go @@ -192,6 +192,7 @@ func genAfterMigration(afterBytes []byte) func(kvdb.RwTx) error { // final struct, but verifies that the field is properly removed. func TestTLVInvoiceMigration(t *testing.T) { for _, test := range migrationTests { + test := test t.Run(test.name, func(t *testing.T) { migtest.ApplyMigration( t, diff --git a/channeldb/migration16/migration.go b/channeldb/migration16/migration.go index cab2d2025..64a5e493d 100644 --- a/channeldb/migration16/migration.go +++ b/channeldb/migration16/migration.go @@ -6,7 +6,7 @@ import ( "errors" "fmt" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/kvdb" ) diff --git a/channeldb/migration16/migration_test.go b/channeldb/migration16/migration_test.go index 7fb55466e..dc2d017fb 100644 --- a/channeldb/migration16/migration_test.go +++ b/channeldb/migration16/migration_test.go @@ -105,6 +105,7 @@ func TestMigrateSequenceIndex(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { // Before the migration we have a payments bucket. diff --git a/channeldb/migration20/codec.go b/channeldb/migration20/codec.go index 829476b3d..37481c997 100644 --- a/channeldb/migration20/codec.go +++ b/channeldb/migration20/codec.go @@ -4,7 +4,7 @@ import ( "encoding/binary" "io" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" ) var ( diff --git a/channeldb/migration20/migration.go b/channeldb/migration20/migration.go index 2829d747e..c341d6a55 100644 --- a/channeldb/migration20/migration.go +++ b/channeldb/migration20/migration.go @@ -4,7 +4,7 @@ import ( "bytes" "fmt" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/tlv" ) diff --git a/channeldb/migration21/common/enclosed_types.go b/channeldb/migration21/common/enclosed_types.go index 44b81c4df..d129a6d52 100644 --- a/channeldb/migration21/common/enclosed_types.go +++ b/channeldb/migration21/common/enclosed_types.go @@ -6,9 +6,9 @@ import ( "io" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/keychain" ) diff --git a/channeldb/migration21/current/current_codec.go b/channeldb/migration21/current/current_codec.go index 90d0f4923..d4a28e69b 100644 --- a/channeldb/migration21/current/current_codec.go +++ b/channeldb/migration21/current/current_codec.go @@ -7,9 +7,9 @@ import ( "io" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/channeldb/migration21/common" "github.com/lightningnetwork/lnd/keychain" diff --git a/channeldb/migration21/legacy/legacy_codec.go b/channeldb/migration21/legacy/legacy_codec.go index a251352b7..e14a42a5c 100644 --- a/channeldb/migration21/legacy/legacy_codec.go +++ b/channeldb/migration21/legacy/legacy_codec.go @@ -6,9 +6,9 @@ import ( "io" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/channeldb/migration21/common" "github.com/lightningnetwork/lnd/keychain" diff --git a/channeldb/migration21/migration_test.go b/channeldb/migration21/migration_test.go index fd3d1f12b..735e45dc0 100644 --- a/channeldb/migration21/migration_test.go +++ b/channeldb/migration21/migration_test.go @@ -9,8 +9,8 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/channeldb/migration21/common" diff --git a/channeldb/migration23/migration_test.go b/channeldb/migration23/migration_test.go index c2dbdfd7b..350c4cc91 100644 --- a/channeldb/migration23/migration_test.go +++ b/channeldb/migration23/migration_test.go @@ -155,6 +155,7 @@ func TestMigrateHtlcAttempts(t *testing.T) { } for _, test := range tests { + test := test migtest.ApplyMigration( t, diff --git a/channeldb/migration24/migration_test.go b/channeldb/migration24/migration_test.go index 77d54ac9d..417c3d0ed 100644 --- a/channeldb/migration24/migration_test.go +++ b/channeldb/migration24/migration_test.go @@ -10,9 +10,9 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11" "github.com/lightningnetwork/lnd/channeldb/migtest" diff --git a/channeldb/migration25/migration_test.go b/channeldb/migration25/migration_test.go index 9da539cfd..6098e440e 100644 --- a/channeldb/migration25/migration_test.go +++ b/channeldb/migration25/migration_test.go @@ -6,9 +6,9 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" mig24 "github.com/lightningnetwork/lnd/channeldb/migration24" mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11" @@ -229,6 +229,7 @@ func TestMigrateInitialBalances(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { migtest.ApplyMigration( t, diff --git a/channeldb/migration26/migration_test.go b/channeldb/migration26/migration_test.go index 32430ed24..a775386f7 100644 --- a/channeldb/migration26/migration_test.go +++ b/channeldb/migration26/migration_test.go @@ -5,8 +5,8 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" mig25 "github.com/lightningnetwork/lnd/channeldb/migration25" mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11" @@ -85,6 +85,7 @@ func TestMigrateBalancesToTlvRecords(t *testing.T) { } for _, tc := range testCases { + tc := tc // Before running the test, set the balance fields based on the // test params. diff --git a/channeldb/migration27/migration_test.go b/channeldb/migration27/migration_test.go index a53ba54a0..f2005ed65 100644 --- a/channeldb/migration27/migration_test.go +++ b/channeldb/migration27/migration_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" mig25 "github.com/lightningnetwork/lnd/channeldb/migration25" mig26 "github.com/lightningnetwork/lnd/channeldb/migration26" mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11" @@ -97,6 +97,7 @@ func TestMigrateHistoricalBalances(t *testing.T) { } for _, tc := range testCases { + tc := tc // testChannel is used to test the balance fields are correctly // set. diff --git a/channeldb/migration29/codec.go b/channeldb/migration29/codec.go index f94a7fe4e..fe11348a6 100644 --- a/channeldb/migration29/codec.go +++ b/channeldb/migration29/codec.go @@ -5,7 +5,7 @@ import ( "encoding/hex" "io" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" ) var ( diff --git a/channeldb/migration29/migration.go b/channeldb/migration29/migration.go index 9cd93030e..7ca3a8f89 100644 --- a/channeldb/migration29/migration.go +++ b/channeldb/migration29/migration.go @@ -3,7 +3,7 @@ package migration29 import ( "bytes" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/kvdb" ) diff --git a/channeldb/migration30/iterator_test.go b/channeldb/migration30/iterator_test.go index 6184a57ed..8d2206eb5 100644 --- a/channeldb/migration30/iterator_test.go +++ b/channeldb/migration30/iterator_test.go @@ -4,9 +4,9 @@ import ( "bytes" "testing" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" mig25 "github.com/lightningnetwork/lnd/channeldb/migration25" mig26 "github.com/lightningnetwork/lnd/channeldb/migration26" @@ -123,6 +123,7 @@ func TestLocateChanBucket(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { err := testLocator(tc.locator) require.Equal(t, tc.expectedErr, err) @@ -282,6 +283,7 @@ func TestFindNextMigrateHeight(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { // Create a test channel. c := createTestChannel(nil) @@ -656,6 +658,7 @@ func TestLocalNextUpdateNum(t *testing.T) { cdb, err := migtest.MakeDB(t) require.NoError(t, err) + tc := tc t.Run(tc.name, func(t *testing.T) { // Setup the test case. c, height := tc.setup(cdb) diff --git a/channeldb/migration30/lnwallet.go b/channeldb/migration30/lnwallet.go index db5bc42fc..02e84a3b9 100644 --- a/channeldb/migration30/lnwallet.go +++ b/channeldb/migration30/lnwallet.go @@ -4,7 +4,7 @@ import ( "bytes" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" mig25 "github.com/lightningnetwork/lnd/channeldb/migration25" mig26 "github.com/lightningnetwork/lnd/channeldb/migration26" mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11" diff --git a/channeldb/migration30/migration_test.go b/channeldb/migration30/migration_test.go index 9202d9168..01773292c 100644 --- a/channeldb/migration30/migration_test.go +++ b/channeldb/migration30/migration_test.go @@ -78,6 +78,7 @@ func TestMigrateRevocationLog(t *testing.T) { fmt.Printf("withAmtData is set to: %v\n", withAmtData) for i, tc := range testCases { + tc := tc // Construct a test case name that can be easily traced. name := fmt.Sprintf("case_%d", i) @@ -168,6 +169,7 @@ func TestValidateMigration(t *testing.T) { } for _, tc := range testCases { + tc := tc // Create a test db. cdb, err := migtest.MakeDB(t) diff --git a/channeldb/migration30/revocation_log.go b/channeldb/migration30/revocation_log.go index 6d8360467..c4f3f1626 100644 --- a/channeldb/migration30/revocation_log.go +++ b/channeldb/migration30/revocation_log.go @@ -6,7 +6,7 @@ import ( "io" "math" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" mig24 "github.com/lightningnetwork/lnd/channeldb/migration24" mig25 "github.com/lightningnetwork/lnd/channeldb/migration25" diff --git a/channeldb/migration30/test_mock.go b/channeldb/migration30/test_mock.go index 01cf6593b..0246ca90d 100644 --- a/channeldb/migration30/test_mock.go +++ b/channeldb/migration30/test_mock.go @@ -4,7 +4,7 @@ import ( "encoding/binary" "io" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/shachain" "github.com/stretchr/testify/mock" ) diff --git a/channeldb/migration30/test_utils.go b/channeldb/migration30/test_utils.go index adfa0c5a8..e272cf41c 100644 --- a/channeldb/migration30/test_utils.go +++ b/channeldb/migration30/test_utils.go @@ -7,9 +7,9 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" mig25 "github.com/lightningnetwork/lnd/channeldb/migration25" mig26 "github.com/lightningnetwork/lnd/channeldb/migration26" diff --git a/channeldb/migration32/codec.go b/channeldb/migration32/codec.go index f03e637dc..9f626c574 100644 --- a/channeldb/migration32/codec.go +++ b/channeldb/migration32/codec.go @@ -5,7 +5,7 @@ import ( "fmt" "io" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" ) diff --git a/channeldb/migration32/mission_control_store.go b/channeldb/migration32/mission_control_store.go index c7dcaeb00..701c4b31e 100644 --- a/channeldb/migration32/mission_control_store.go +++ b/channeldb/migration32/mission_control_store.go @@ -6,7 +6,7 @@ import ( "math" "time" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/tlv" diff --git a/channeldb/migration32/route.go b/channeldb/migration32/route.go index 31a3bc525..a35338e50 100644 --- a/channeldb/migration32/route.go +++ b/channeldb/migration32/route.go @@ -7,7 +7,7 @@ import ( "io" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/tlv" ) diff --git a/channeldb/migration35/log.go b/channeldb/migration35/log.go deleted file mode 100644 index c39335d4f..000000000 --- a/channeldb/migration35/log.go +++ /dev/null @@ -1,14 +0,0 @@ -package migration35 - -import ( - "github.com/btcsuite/btclog/v2" -) - -// log is a logger that is initialized as disabled. This means the package will -// not perform any logging by default until a logger is set. -var log = btclog.Disabled - -// UseLogger uses a specified logger to output package logging info. -func UseLogger(logger btclog.Logger) { - log = logger -} diff --git a/channeldb/migration35/migration.go b/channeldb/migration35/migration.go deleted file mode 100644 index 1160f5735..000000000 --- a/channeldb/migration35/migration.go +++ /dev/null @@ -1,211 +0,0 @@ -package migration35 - -import ( - "bytes" - "encoding/binary" - "fmt" - - lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" - "github.com/lightningnetwork/lnd/kvdb" -) - -var ( - // waitingProofsBucketKey is the top-level bucket that stores waiting - // proofs. - waitingProofsBucketKey = []byte("waitingproofs") - - // byteOrder is the preferred DB byte order. - byteOrder = binary.BigEndian -) - -// waitingProofType represents the type of a waiting proof record. -type waitingProofType uint8 - -const ( - // waitingProofTypeV1 represents AnnounceSignatures1 proofs (gossip v1). - waitingProofTypeV1 waitingProofType = 0 -) - -// legacyWaitingProofKey is the key format used by legacy waiting proof -// records: [scid(8) || isRemote(1)]. -type legacyWaitingProofKey [9]byte - -// waitingProofKey is the updated key format used by waiting proof records: -// [proofType(1) || scid(8) || isRemote(1)]. -type waitingProofKey [10]byte - -// waitingProof is a migration-only representation of a waiting proof record. -type waitingProof struct { - announceSignatures *lnwire.AnnounceSignatures - isRemote bool -} - -// LegacyKey computes the legacy waiting proof store key. -func (p *waitingProof) LegacyKey() legacyWaitingProofKey { - var key legacyWaitingProofKey - binary.BigEndian.PutUint64( - key[:8], p.announceSignatures.ShortChannelID.ToUint64(), - ) - - if p.isRemote { - key[8] = 1 - } - - return key -} - -// Key computes the updated waiting proof store key. -func (p *waitingProof) Key() waitingProofKey { - var key waitingProofKey - key[0] = byte(waitingProofTypeV1) - - binary.BigEndian.PutUint64( - key[1:9], p.announceSignatures.ShortChannelID.ToUint64(), - ) - - if p.isRemote { - key[9] = 1 - } - - return key -} - -// decodeLegacyWaitingProof decodes a pre-migration waiting proof in the -// legacy format: isRemote + raw AnnounceSignatures payload. -func decodeLegacyWaitingProof(v []byte) (*waitingProof, error) { - r := bytes.NewReader(v) - - // Decode the legacy side bit first. - var isRemote bool - if err := binary.Read(r, byteOrder, &isRemote); err != nil { - return nil, err - } - - // Decode the legacy AnnounceSignatures payload. - ann := &lnwire.AnnounceSignatures{} - if err := ann.Decode(r, 0); err != nil { - return nil, err - } - - // Reconstruct the migration-local waiting proof representation. - return &waitingProof{ - announceSignatures: ann, - isRemote: isRemote, - }, nil -} - -// encodeUpdatedWaitingProof encodes a waiting proof in the new format: -// type byte + isRemote + raw AnnounceSignatures payload. -func encodeUpdatedWaitingProof(p *waitingProof) ([]byte, error) { - var b bytes.Buffer - - // Prefix the payload with the explicit waiting proof type. - if err := binary.Write(&b, byteOrder, waitingProofTypeV1); err != nil { - return nil, err - } - - // Preserve the side bit after the type prefix. - if err := binary.Write(&b, byteOrder, p.isRemote); err != nil { - return nil, err - } - - // Encode the existing AnnounceSignatures payload unchanged. - if err := p.announceSignatures.Encode(&b, 0); err != nil { - return nil, err - } - - return b.Bytes(), nil -} - -// MigrateWaitingProofStore migrates waiting proofs to include a leading proof -// type byte and rewrites record keys to include proof type as well. -func MigrateWaitingProofStore(tx kvdb.RwTx) error { - log.Info("Migrating waiting proof store") - - bucket := tx.ReadWriteBucket(waitingProofsBucketKey) - - // If the bucket doesn't exist there is no data to migrate. - if bucket == nil { - return nil - } - - type migratedProof struct { - oldKey []byte - newKey waitingProofKey - value []byte - } - - var migratedProofs []migratedProof - - err := bucket.ForEach(func(k, v []byte) error { - // Skip nested bucket references. - if v == nil { - return nil - } - - switch len(k) { - case len(legacyWaitingProofKey{}): - // Legacy records continue below and are migrated. - - case len(waitingProofKey{}): - // The record already uses the typed key format. This - // makes the migration safe to re-run during missing - // version key recovery. - return nil - - default: - return fmt.Errorf("unexpected waiting proof key %x", k) - } - - proof, err := decodeLegacyWaitingProof(v) - if err != nil { - return fmt.Errorf("decode waiting proof for key %x: %w", - k, err) - } - - // Sanity check: the key should match the proof content. - legacyKey := proof.LegacyKey() - if !bytes.Equal(k, legacyKey[:]) { - return fmt.Errorf("proof key (%x) does not "+ - "match bucket key (%x)", legacyKey, k) - } - - updatedProofValue, err := encodeUpdatedWaitingProof(proof) - if err != nil { - return fmt.Errorf("encode updated waiting "+ - "proof for key %x: %w", k, err) - } - - oldKey := make([]byte, len(k)) - copy(oldKey, k) - - migratedProofs = append(migratedProofs, migratedProof{ - oldKey: oldKey, - newKey: proof.Key(), - value: updatedProofValue, - }) - - return nil - }) - if err != nil { - return err - } - - for _, proof := range migratedProofs { - if err := bucket.Delete(proof.oldKey); err != nil { - return fmt.Errorf( - "delete legacy waiting proof key %x: %w", - proof.oldKey, err, - ) - } - - if err := bucket.Put(proof.newKey[:], proof.value); err != nil { - return fmt.Errorf( - "put updated waiting proof key %x: %w", - proof.newKey, err, - ) - } - } - - return nil -} diff --git a/channeldb/migration35/migration_test.go b/channeldb/migration35/migration_test.go deleted file mode 100644 index bbb517828..000000000 --- a/channeldb/migration35/migration_test.go +++ /dev/null @@ -1,278 +0,0 @@ -package migration35 - -import ( - "bytes" - "encoding/binary" - "encoding/hex" - "fmt" - "testing" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/ecdsa" - lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" - "github.com/lightningnetwork/lnd/channeldb/migtest" - "github.com/lightningnetwork/lnd/kvdb" -) - -var ( - testRBytes, _ = hex.DecodeString("8ce2bc69281ce27da07e6683571" + - "319d18e949ddfa2965fb6caa1bf0314f882d7") - testSBytes, _ = hex.DecodeString("299105481d63e0f4bc2a" + - "88121167221b6700d72a0ead154c03be696a292d24ae") - testRScalar = new(btcec.ModNScalar) - testSScalar = new(btcec.ModNScalar) - _ = testRScalar.SetByteSlice(testRBytes) - _ = testSScalar.SetByteSlice(testSBytes) - testECDSA = ecdsa.NewSignature(testRScalar, testSScalar) - testSig, _ = lnwire.NewSigFromSignature(testECDSA) -) - -// encodeLegacyProof encodes a waiting proof in the pre-migration format. -func encodeLegacyProof(p *waitingProof) []byte { - var b bytes.Buffer - - err := binary.Write(&b, byteOrder, p.isRemote) - if err != nil { - panic(err) - } - - err = p.announceSignatures.Encode(&b, 0) - if err != nil { - panic(err) - } - - return b.Bytes() -} - -// newAnnSig creates a deterministic announce signatures test message. -func newAnnSig(scid uint64, chanIDByte byte) *lnwire.AnnounceSignatures { - return &lnwire.AnnounceSignatures{ - ChannelID: lnwire.ChannelID{chanIDByte}, - ShortChannelID: lnwire.NewShortChanIDFromInt(scid), - NodeSignature: testSig, - BitcoinSignature: testSig, - ExtraOpaqueData: []byte{chanIDByte, 1, 2, 3}, - } -} - -// makeHappyPathSetup creates pre- and post-migration callbacks for the -// successful migration test case. -func makeHappyPathSetup() (func(tx kvdb.RwTx) error, - func(tx kvdb.RwTx) error) { - - proof1 := &waitingProof{ - announceSignatures: newAnnSig(10, 1), - isRemote: false, - } - proof2 := &waitingProof{ - announceSignatures: newAnnSig(11, 2), - isRemote: true, - } - - legacyKey1 := proof1.LegacyKey() - legacyKey2 := proof2.LegacyKey() - - before := func(tx kvdb.RwTx) error { - bucket, err := tx.CreateTopLevelBucket(waitingProofsBucketKey) - if err != nil { - return err - } - - err = bucket.Put(legacyKey1[:], encodeLegacyProof(proof1)) - if err != nil { - return err - } - - return bucket.Put(legacyKey2[:], encodeLegacyProof(proof2)) - } - - expected := map[waitingProofKey]*waitingProof{ - proof1.Key(): proof1, - proof2.Key(): proof2, - } - - after := func(tx kvdb.RwTx) error { - bucket := tx.ReadWriteBucket(waitingProofsBucketKey) - if bucket == nil { - return fmt.Errorf("waiting proofs bucket not found") - } - - for key, proof := range expected { - migrated := bucket.Get(key[:]) - if migrated == nil { - return fmt.Errorf("migrated key %x "+ - "not found", key) - } - - expectedBytes, err := encodeUpdatedWaitingProof(proof) - if err != nil { - return err - } - - if !bytes.Equal(migrated, expectedBytes) { - return fmt.Errorf("unexpected "+ - "migrated bytes for "+ - "key %x", key) - } - } - - if bucket.Get(legacyKey1[:]) != nil { - return fmt.Errorf( - "legacy key %x still exists", legacyKey1, - ) - } - - if bucket.Get(legacyKey2[:]) != nil { - return fmt.Errorf( - "legacy key %x still exists", legacyKey2, - ) - } - - return nil - } - - return before, after -} - -// makeKeyMismatchSetup creates pre- and post-migration callbacks for the key -// mismatch failure case. -func makeKeyMismatchSetup() (func(tx kvdb.RwTx) error, - func(tx kvdb.RwTx) error) { - - proof := &waitingProof{ - announceSignatures: newAnnSig(15, 4), - isRemote: false, - } - wrongKey := legacyWaitingProofKey{} - binary.BigEndian.PutUint64(wrongKey[:8], 99) - - before := func(tx kvdb.RwTx) error { - bucket, err := tx.CreateTopLevelBucket(waitingProofsBucketKey) - if err != nil { - return err - } - - return bucket.Put(wrongKey[:], encodeLegacyProof(proof)) - } - - after := func(tx kvdb.RwTx) error { - return nil - } - - return before, after -} - -// makeMixedLegacyAndTypedSetup creates pre- and post-migration callbacks for a -// bucket that already contains a typed waiting proof. -func makeMixedLegacyAndTypedSetup() (func(tx kvdb.RwTx) error, - func(tx kvdb.RwTx) error) { - - legacyProof := &waitingProof{ - announceSignatures: newAnnSig(20, 5), - isRemote: false, - } - typedProof := &waitingProof{ - announceSignatures: newAnnSig(21, 6), - isRemote: true, - } - - legacyKey := legacyProof.LegacyKey() - migratedKey := legacyProof.Key() - typedKey := typedProof.Key() - typedValue, err := encodeUpdatedWaitingProof(typedProof) - if err != nil { - panic(err) - } - - before := func(tx kvdb.RwTx) error { - bucket, err := tx.CreateTopLevelBucket(waitingProofsBucketKey) - if err != nil { - return err - } - - err = bucket.Put(legacyKey[:], encodeLegacyProof(legacyProof)) - if err != nil { - return err - } - - return bucket.Put(typedKey[:], typedValue) - } - - after := func(tx kvdb.RwTx) error { - bucket := tx.ReadWriteBucket(waitingProofsBucketKey) - if bucket == nil { - return fmt.Errorf("waiting proofs bucket not found") - } - - if bucket.Get(legacyKey[:]) != nil { - return fmt.Errorf("legacy key %x still exists", - legacyKey) - } - - migratedValue := bucket.Get(migratedKey[:]) - if migratedValue == nil { - return fmt.Errorf("migrated key %x not found", - migratedKey) - } - - expectedMigratedValue, err := encodeUpdatedWaitingProof( - legacyProof, - ) - if err != nil { - return err - } - - if !bytes.Equal(migratedValue, expectedMigratedValue) { - return fmt.Errorf("unexpected migrated value") - } - - if !bytes.Equal(bucket.Get(typedKey[:]), typedValue) { - return fmt.Errorf("typed proof was modified") - } - - return nil - } - - return before, after -} - -// TestMigrateWaitingProofStore verifies the waiting proof migration behavior. -func TestMigrateWaitingProofStore(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - setup func() ( - func(tx kvdb.RwTx) error, func(tx kvdb.RwTx) error, - ) - shouldFail bool - }{ - { - name: "happy path", - setup: makeHappyPathSetup, - shouldFail: false, - }, - { - name: "key mismatch fails", - setup: makeKeyMismatchSetup, - shouldFail: true, - }, - { - name: "mixed legacy and typed proofs", - setup: makeMixedLegacyAndTypedSetup, - shouldFail: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - before, after := tc.setup() - migtest.ApplyMigration( - t, before, after, MigrateWaitingProofStore, - tc.shouldFail, - ) - }) - } -} diff --git a/channeldb/migration_01_to_11/channel.go b/channeldb/migration_01_to_11/channel.go index 083bbb431..ec025263b 100644 --- a/channeldb/migration_01_to_11/channel.go +++ b/channeldb/migration_01_to_11/channel.go @@ -9,9 +9,9 @@ import ( "sync" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/shachain" diff --git a/channeldb/migration_01_to_11/channel_test.go b/channeldb/migration_01_to_11/channel_test.go index 4e5cd7a6b..76a16c7d0 100644 --- a/channeldb/migration_01_to_11/channel_test.go +++ b/channeldb/migration_01_to_11/channel_test.go @@ -6,9 +6,9 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" _ "github.com/btcsuite/btcwallet/walletdb/bdb" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/keychain" diff --git a/channeldb/migration_01_to_11/codec.go b/channeldb/migration_01_to_11/codec.go index 28c295c9c..ca2f5cff6 100644 --- a/channeldb/migration_01_to_11/codec.go +++ b/channeldb/migration_01_to_11/codec.go @@ -7,9 +7,9 @@ import ( "net" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/shachain" diff --git a/channeldb/migration_01_to_11/graph.go b/channeldb/migration_01_to_11/graph.go index c7d77d2e3..9035e3e6f 100644 --- a/channeldb/migration_01_to_11/graph.go +++ b/channeldb/migration_01_to_11/graph.go @@ -10,9 +10,9 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/kvdb" ) diff --git a/channeldb/migration_01_to_11/invoices.go b/channeldb/migration_01_to_11/invoices.go index 15a1a9b8b..3adbbf5f2 100644 --- a/channeldb/migration_01_to_11/invoices.go +++ b/channeldb/migration_01_to_11/invoices.go @@ -7,7 +7,7 @@ import ( "io" "time" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lntypes" diff --git a/channeldb/migration_01_to_11/migration_11_invoices.go b/channeldb/migration_01_to_11/migration_11_invoices.go index b87fdf974..91d0e00f9 100644 --- a/channeldb/migration_01_to_11/migration_11_invoices.go +++ b/channeldb/migration_01_to_11/migration_11_invoices.go @@ -6,8 +6,8 @@ import ( "fmt" "io" - bitcoinCfg "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/wire/v2" + bitcoinCfg "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11/zpay32" "github.com/lightningnetwork/lnd/kvdb" diff --git a/channeldb/migration_01_to_11/migration_11_invoices_test.go b/channeldb/migration_01_to_11/migration_11_invoices_test.go index 00ff0fd10..55c188bd9 100644 --- a/channeldb/migration_01_to_11/migration_11_invoices_test.go +++ b/channeldb/migration_01_to_11/migration_11_invoices_test.go @@ -7,7 +7,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - bitcoinCfg "github.com/btcsuite/btcd/chaincfg/v2" + bitcoinCfg "github.com/btcsuite/btcd/chaincfg" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/zpay32" litecoinCfg "github.com/ltcsuite/ltcd/chaincfg" @@ -41,7 +41,7 @@ func beforeMigrationFuncV11(t *testing.T, d *DB, invoices []Invoice) { invoiceNum++ var buf bytes.Buffer - err := serializeInvoiceLegacy(&buf, &invoice) + err := serializeInvoiceLegacy(&buf, &invoice) // nolint:scopelint if err != nil { return err } diff --git a/channeldb/migration_01_to_11/migrations_test.go b/channeldb/migration_01_to_11/migrations_test.go index dcec0a87a..7c28d2039 100644 --- a/channeldb/migration_01_to_11/migrations_test.go +++ b/channeldb/migration_01_to_11/migrations_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/davecgh/go-spew/spew" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/kvdb" diff --git a/channeldb/migration_01_to_11/payments.go b/channeldb/migration_01_to_11/payments.go index 1ead7575f..7e9eebd64 100644 --- a/channeldb/migration_01_to_11/payments.go +++ b/channeldb/migration_01_to_11/payments.go @@ -10,7 +10,7 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lntypes" diff --git a/channeldb/migration_01_to_11/zpay32/decode.go b/channeldb/migration_01_to_11/zpay32/decode.go index 4b3265186..9f27543c0 100644 --- a/channeldb/migration_01_to_11/zpay32/decode.go +++ b/channeldb/migration_01_to_11/zpay32/decode.go @@ -8,12 +8,12 @@ import ( "strings" "time" - "github.com/btcsuite/btcd/address/v2" - "github.com/btcsuite/btcd/address/v2/bech32" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/bech32" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" ) @@ -378,13 +378,13 @@ func parseMinFinalCLTVExpiry(data []byte) (*uint64, error) { // parseFallbackAddr converts the data (encoded in base32) into a fallback // on-chain address. -func parseFallbackAddr(data []byte, net *chaincfg.Params) (address.Address, error) { +func parseFallbackAddr(data []byte, net *chaincfg.Params) (btcutil.Address, error) { // Checks if the data is empty or contains a version without an address. if len(data) < 2 { return nil, fmt.Errorf("empty fallback address field") } - var addr address.Address + var addr btcutil.Address version := data[0] switch version { @@ -396,9 +396,9 @@ func parseFallbackAddr(data []byte, net *chaincfg.Params) (address.Address, erro switch len(witness) { case 20: - addr, err = address.NewAddressWitnessPubKeyHash(witness, net) + addr, err = btcutil.NewAddressWitnessPubKeyHash(witness, net) case 32: - addr, err = address.NewAddressWitnessScriptHash(witness, net) + addr, err = btcutil.NewAddressWitnessScriptHash(witness, net) default: return nil, fmt.Errorf("unknown witness program length %d", len(witness)) @@ -413,7 +413,7 @@ func parseFallbackAddr(data []byte, net *chaincfg.Params) (address.Address, erro return nil, err } - addr, err = address.NewAddressPubKeyHash(pubKeyHash, net) + addr, err = btcutil.NewAddressPubKeyHash(pubKeyHash, net) if err != nil { return nil, err } @@ -423,7 +423,7 @@ func parseFallbackAddr(data []byte, net *chaincfg.Params) (address.Address, erro return nil, err } - addr, err = address.NewAddressScriptHashFromHash(scriptHash, net) + addr, err = btcutil.NewAddressScriptHashFromHash(scriptHash, net) if err != nil { return nil, err } diff --git a/channeldb/migration_01_to_11/zpay32/invoice.go b/channeldb/migration_01_to_11/zpay32/invoice.go index 9a857e95e..cf24e4e10 100644 --- a/channeldb/migration_01_to_11/zpay32/invoice.go +++ b/channeldb/migration_01_to_11/zpay32/invoice.go @@ -5,9 +5,9 @@ import ( "fmt" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" ) @@ -171,7 +171,7 @@ type Invoice struct { // FallbackAddr is an on-chain address that can be used for payment in // case the Lightning payment fails. // Optional. - FallbackAddr address.Address + FallbackAddr btcutil.Address // RouteHints represents one or more different route hints. Each route // hint can be individually used to reach the destination. These usually @@ -242,7 +242,7 @@ func Expiry(expiry time.Duration) func(*Invoice) { // FallbackAddr is a functional option that allows callers of NewInvoice to set // the Invoice's fallback on-chain address that can be used for payment in case // the Lightning payment fails -func FallbackAddr(fallbackAddr address.Address) func(*Invoice) { +func FallbackAddr(fallbackAddr btcutil.Address) func(*Invoice) { return func(i *Invoice) { i.FallbackAddr = fallbackAddr } diff --git a/channeldb/nodes.go b/channeldb/nodes.go index 688735ce1..70f6fad8b 100644 --- a/channeldb/nodes.go +++ b/channeldb/nodes.go @@ -9,7 +9,7 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/kvdb" ) diff --git a/channeldb/nodes_test.go b/channeldb/nodes_test.go index 413e0bc39..a88e45228 100644 --- a/channeldb/nodes_test.go +++ b/channeldb/nodes_test.go @@ -7,7 +7,7 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/kvdb" "github.com/stretchr/testify/require" ) diff --git a/channeldb/options.go b/channeldb/options.go index eec3b5446..a8ec8cfd6 100644 --- a/channeldb/options.go +++ b/channeldb/options.go @@ -71,16 +71,6 @@ type Options struct { // storeFinalHtlcResolutions determines whether to persistently store // the final resolution of incoming htlcs. storeFinalHtlcResolutions bool - - // tombstoneClosedChannels, when true, instructs CloseChannel to skip - // the cascading deletion of nested per-channel state and rely on the - // outpoint-index flip to mark the channel as closed. KV-over-SQL - // backends (sqlite, postgres) opt in because nested-bucket deletes - // inside a write transaction translate into a long-running - // ON DELETE CASCADE that holds the database write-lock for many - // seconds on long-lived channels. bbolt and etcd leave this off; the - // synchronous delete is already cheap there. - tombstoneClosedChannels bool } // DefaultOptions returns an Options populated with default values. @@ -161,14 +151,3 @@ func OptionGcDecayedLog(noGc bool) OptionModifier { o.OptionalMiragtionConfig.MigrationFlags[1] = !noGc } } - -// OptionTombstoneClosedChannels controls whether CloseChannel skips the -// cascading deletion of nested per-channel state and relies on the -// outpoint-index flip to mark the channel as closed. Set this to true on -// KV-over-SQL backends (sqlite, postgres); leave it false for bbolt and -// etcd. -func OptionTombstoneClosedChannels(enabled bool) OptionModifier { - return func(o *Options) { - o.tombstoneClosedChannels = enabled - } -} diff --git a/channeldb/reports.go b/channeldb/reports.go index 98e54beb7..4f46bd9e1 100644 --- a/channeldb/reports.go +++ b/channeldb/reports.go @@ -5,9 +5,9 @@ import ( "errors" "io" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/tlv" diff --git a/channeldb/reports_test.go b/channeldb/reports_test.go index ce03ef7cc..1148fdf03 100644 --- a/channeldb/reports_test.go +++ b/channeldb/reports_test.go @@ -4,8 +4,8 @@ import ( "bytes" "testing" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/kvdb" "github.com/stretchr/testify/require" @@ -46,6 +46,7 @@ func TestPersistReport(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { db, err := MakeTestDB(t) @@ -192,6 +193,7 @@ func TestFetchChannelWriteBucket(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { db, err := MakeTestDB(t) diff --git a/channeldb/revocation_log.go b/channeldb/revocation_log.go index 8340c8b19..5a7f7a76b 100644 --- a/channeldb/revocation_log.go +++ b/channeldb/revocation_log.go @@ -2,54 +2,34 @@ package channeldb import ( "bytes" + "encoding/binary" "errors" "io" "math" - cstate "github.com/lightningnetwork/lnd/chanstate" + "github.com/btcsuite/btcd/btcutil" + "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/kvdb" + "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/tlv" ) const ( // OutputIndexEmpty is used when the output index doesn't exist. - OutputIndexEmpty = cstate.OutputIndexEmpty + OutputIndexEmpty = math.MaxUint16 ) type ( // BigSizeAmount is a type alias for a TLV record of a btcutil.Amount. - BigSizeAmount = cstate.BigSizeAmount + BigSizeAmount = tlv.BigSizeT[btcutil.Amount] // BigSizeMilliSatoshi is a type alias for a TLV record of a // lnwire.MilliSatoshi. - BigSizeMilliSatoshi = cstate.BigSizeMilliSatoshi - - // SparsePayHash is a type alias for a 32 byte array, which when - // serialized is able to save some space by not including an empty - // payment hash on disk. - SparsePayHash = cstate.SparsePayHash - - // HTLCEntry specifies the minimal info needed to be stored on disk for - // ALL the historical HTLCs, which is useful for constructing - // RevocationLog when a breach is detected. - HTLCEntry = cstate.HTLCEntry - - // RevocationLog stores the info needed to construct a breach - // retribution. - RevocationLog = cstate.RevocationLog + BigSizeMilliSatoshi = tlv.BigSizeT[lnwire.MilliSatoshi] ) var ( - // NewSparsePayHash creates a new SparsePayHash from a 32 byte array. - NewSparsePayHash = cstate.NewSparsePayHash - - // NewHTLCEntryFromHTLC creates a new HTLCEntry from an HTLC. - NewHTLCEntryFromHTLC = cstate.NewHTLCEntryFromHTLC - - // NewRevocationLog creates a new RevocationLog from the given - // parameters. - NewRevocationLog = cstate.NewRevocationLog - // revocationLogBucketDeprecated is dedicated for storing the necessary // delta state between channel updates required to re-construct a past // state in order to punish a counterparty attempting a non-cooperative @@ -75,6 +55,266 @@ var ( ErrOutputIndexTooBig = errors.New("output index is over uint16") ) +// SparsePayHash is a type alias for a 32 byte array, which when serialized is +// able to save some space by not including an empty payment hash on disk. +type SparsePayHash [32]byte + +// NewSparsePayHash creates a new SparsePayHash from a 32 byte array. +func NewSparsePayHash(rHash [32]byte) SparsePayHash { + return SparsePayHash(rHash) +} + +// Record returns a tlv record for the SparsePayHash. +func (s *SparsePayHash) Record() tlv.Record { + // We use a zero for the type here, as this'll be used along with the + // RecordT type. + return tlv.MakeDynamicRecord( + 0, s, s.hashLen, + sparseHashEncoder, sparseHashDecoder, + ) +} + +// hashLen is used by MakeDynamicRecord to return the size of the RHash. +// +// NOTE: for zero hash, we return a length 0. +func (s *SparsePayHash) hashLen() uint64 { + if bytes.Equal(s[:], lntypes.ZeroHash[:]) { + return 0 + } + + return 32 +} + +// sparseHashEncoder is the customized encoder which skips encoding the empty +// hash. +func sparseHashEncoder(w io.Writer, val interface{}, buf *[8]byte) error { + v, ok := val.(*SparsePayHash) + if !ok { + return tlv.NewTypeForEncodingErr(val, "SparsePayHash") + } + + // If the value is an empty hash, we will skip encoding it. + if bytes.Equal(v[:], lntypes.ZeroHash[:]) { + return nil + } + + vArray := (*[32]byte)(v) + + return tlv.EBytes32(w, vArray, buf) +} + +// sparseHashDecoder is the customized decoder which skips decoding the empty +// hash. +func sparseHashDecoder(r io.Reader, val interface{}, buf *[8]byte, + l uint64) error { + + v, ok := val.(*SparsePayHash) + if !ok { + return tlv.NewTypeForEncodingErr(val, "SparsePayHash") + } + + // If the length is zero, we will skip encoding the empty hash. + if l == 0 { + return nil + } + + vArray := (*[32]byte)(v) + + return tlv.DBytes32(r, vArray, buf, 32) +} + +// HTLCEntry specifies the minimal info needed to be stored on disk for ALL the +// historical HTLCs, which is useful for constructing RevocationLog when a +// breach is detected. +// The actual size of each HTLCEntry varies based on its RHash and Amt(sat), +// summarized as follows, +// +// | RHash empty | Amt<=252 | Amt<=65,535 | Amt<=4,294,967,295 | otherwise | +// |:-----------:|:--------:|:-----------:|:------------------:|:---------:| +// | true | 19 | 21 | 23 | 26 | +// | false | 51 | 53 | 55 | 58 | +// +// So the size varies from 19 bytes to 58 bytes, where most likely to be 23 or +// 55 bytes. +// +// NOTE: all the fields saved to disk use the primitive go types so they can be +// made into tlv records without further conversion. +type HTLCEntry struct { + // RHash is the payment hash of the HTLC. + RHash tlv.RecordT[tlv.TlvType0, SparsePayHash] + + // RefundTimeout is the absolute timeout on the HTLC that the sender + // must wait before reclaiming the funds in limbo. + RefundTimeout tlv.RecordT[tlv.TlvType1, uint32] + + // OutputIndex is the output index for this particular HTLC output + // within the commitment transaction. + // + // NOTE: we use uint16 instead of int32 here to save us 2 bytes, which + // gives us a max number of HTLCs of 65K. + OutputIndex tlv.RecordT[tlv.TlvType2, uint16] + + // Incoming denotes whether we're the receiver or the sender of this + // HTLC. + Incoming tlv.RecordT[tlv.TlvType3, bool] + + // Amt is the amount of satoshis this HTLC escrows. + Amt tlv.RecordT[tlv.TlvType4, tlv.BigSizeT[btcutil.Amount]] + + // CustomBlob is an optional blob that can be used to store information + // specific to revocation handling for a custom channel type. + CustomBlob tlv.OptionalRecordT[tlv.TlvType5, tlv.Blob] + + // HtlcIndex is the index of the HTLC in the channel. + HtlcIndex tlv.OptionalRecordT[tlv.TlvType6, tlv.BigSizeT[uint64]] +} + +// toTlvStream converts an HTLCEntry record into a tlv representation. +func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) { + records := []tlv.Record{ + h.RHash.Record(), + h.RefundTimeout.Record(), + h.OutputIndex.Record(), + h.Incoming.Record(), + h.Amt.Record(), + } + + h.CustomBlob.WhenSome(func(r tlv.RecordT[tlv.TlvType5, tlv.Blob]) { + records = append(records, r.Record()) + }) + + h.HtlcIndex.WhenSome(func(r tlv.RecordT[tlv.TlvType6, + tlv.BigSizeT[uint64]]) { + + records = append(records, r.Record()) + }) + + tlv.SortRecords(records) + + return tlv.NewStream(records...) +} + +// NewHTLCEntryFromHTLC creates a new HTLCEntry from an HTLC. +func NewHTLCEntryFromHTLC(htlc HTLC) (*HTLCEntry, error) { + h := &HTLCEntry{ + RHash: tlv.NewRecordT[tlv.TlvType0]( + NewSparsePayHash(htlc.RHash), + ), + RefundTimeout: tlv.NewPrimitiveRecord[tlv.TlvType1]( + htlc.RefundTimeout, + ), + OutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType2]( + uint16(htlc.OutputIndex), + ), + Incoming: tlv.NewPrimitiveRecord[tlv.TlvType3](htlc.Incoming), + Amt: tlv.NewRecordT[tlv.TlvType4]( + tlv.NewBigSizeT(htlc.Amt.ToSatoshis()), + ), + HtlcIndex: tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType6]( + tlv.NewBigSizeT(htlc.HtlcIndex), + )), + } + + if len(htlc.CustomRecords) != 0 { + blob, err := htlc.CustomRecords.Serialize() + if err != nil { + return nil, err + } + + h.CustomBlob = tlv.SomeRecordT( + tlv.NewPrimitiveRecord[tlv.TlvType5, tlv.Blob](blob), + ) + } + + return h, nil +} + +// RevocationLog stores the info needed to construct a breach retribution. Its +// fields can be viewed as a subset of a ChannelCommitment's. In the database, +// all historical versions of the RevocationLog are saved using the +// CommitHeight as the key. +type RevocationLog struct { + // OurOutputIndex specifies our output index in this commitment. In a + // remote commitment transaction, this is the to remote output index. + OurOutputIndex tlv.RecordT[tlv.TlvType0, uint16] + + // TheirOutputIndex specifies their output index in this commitment. In + // a remote commitment transaction, this is the to local output index. + TheirOutputIndex tlv.RecordT[tlv.TlvType1, uint16] + + // CommitTxHash is the hash of the latest version of the commitment + // state, broadcast able by us. + CommitTxHash tlv.RecordT[tlv.TlvType2, [32]byte] + + // HTLCEntries is the set of HTLCEntry's that are pending at this + // particular commitment height. + HTLCEntries []*HTLCEntry + + // OurBalance is the current available balance within the channel + // directly spendable by us. In other words, it is the value of the + // to_remote output on the remote parties' commitment transaction. + // + // NOTE: this is an option so that it is clear if the value is zero or + // nil. Since migration 30 of the channeldb initially did not include + // this field, it could be the case that the field is not present for + // all revocation logs. + OurBalance tlv.OptionalRecordT[tlv.TlvType3, BigSizeMilliSatoshi] + + // TheirBalance is the current available balance within the channel + // directly spendable by the remote node. In other words, it is the + // value of the to_local output on the remote parties' commitment. + // + // NOTE: this is an option so that it is clear if the value is zero or + // nil. Since migration 30 of the channeldb initially did not include + // this field, it could be the case that the field is not present for + // all revocation logs. + TheirBalance tlv.OptionalRecordT[tlv.TlvType4, BigSizeMilliSatoshi] + + // CustomBlob is an optional blob that can be used to store information + // specific to a custom channel type. This information is only created + // at channel funding time, and after wards is to be considered + // immutable. + CustomBlob tlv.OptionalRecordT[tlv.TlvType5, tlv.Blob] +} + +// NewRevocationLog creates a new RevocationLog from the given parameters. +func NewRevocationLog(ourOutputIndex uint16, theirOutputIndex uint16, + commitHash [32]byte, ourBalance, + theirBalance fn.Option[lnwire.MilliSatoshi], htlcs []*HTLCEntry, + customBlob fn.Option[tlv.Blob]) RevocationLog { + + rl := RevocationLog{ + OurOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType0]( + ourOutputIndex, + ), + TheirOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType1]( + theirOutputIndex, + ), + CommitTxHash: tlv.NewPrimitiveRecord[tlv.TlvType2](commitHash), + HTLCEntries: htlcs, + } + + ourBalance.WhenSome(func(balance lnwire.MilliSatoshi) { + rl.OurBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType3]( + tlv.NewBigSizeT(balance), + )) + }) + + theirBalance.WhenSome(func(balance lnwire.MilliSatoshi) { + rl.TheirBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType4]( + tlv.NewBigSizeT(balance), + )) + }) + + customBlob.WhenSome(func(blob tlv.Blob) { + rl.CustomBlob = tlv.SomeRecordT( + tlv.NewPrimitiveRecord[tlv.TlvType5, tlv.Blob](blob), + ) + }) + + return rl +} + // putRevocationLog uses the fields `CommitTx` and `Htlcs` from a // ChannelCommitment to construct a revocation log entry and saves them to // disk. It also saves our output index and their output index, which are @@ -167,36 +407,269 @@ func fetchRevocationLog(log kvdb.RBucket, // serializeRevocationLog serializes a RevocationLog record based on tlv // format. func serializeRevocationLog(w io.Writer, rl *RevocationLog) error { - return cstate.SerializeRevocationLog(w, rl) + // Add the tlv records for all non-optional fields. + records := []tlv.Record{ + rl.OurOutputIndex.Record(), + rl.TheirOutputIndex.Record(), + rl.CommitTxHash.Record(), + } + + // Now we add any optional fields that are non-nil. + rl.OurBalance.WhenSome( + func(r tlv.RecordT[tlv.TlvType3, BigSizeMilliSatoshi]) { + records = append(records, r.Record()) + }, + ) + + rl.TheirBalance.WhenSome( + func(r tlv.RecordT[tlv.TlvType4, BigSizeMilliSatoshi]) { + records = append(records, r.Record()) + }, + ) + + rl.CustomBlob.WhenSome(func(r tlv.RecordT[tlv.TlvType5, tlv.Blob]) { + records = append(records, r.Record()) + }) + + // Create the tlv stream. + tlvStream, err := tlv.NewStream(records...) + if err != nil { + return err + } + + // Write the tlv stream. + if err := writeTlvStream(w, tlvStream); err != nil { + return err + } + + // Write the HTLCs. + return serializeHTLCEntries(w, rl.HTLCEntries) } // serializeHTLCEntries serializes a list of HTLCEntry records based on tlv // format. func serializeHTLCEntries(w io.Writer, htlcs []*HTLCEntry) error { - return cstate.SerializeHTLCEntries(w, htlcs) + for _, htlc := range htlcs { + // Create the tlv stream. + tlvStream, err := htlc.toTlvStream() + if err != nil { + return err + } + + // Write the tlv stream. + if err := writeTlvStream(w, tlvStream); err != nil { + return err + } + } + + return nil } // deserializeRevocationLog deserializes a RevocationLog based on tlv format. func deserializeRevocationLog(r io.Reader) (RevocationLog, error) { - return cstate.DeserializeRevocationLog(r) + var rl RevocationLog + + ourBalance := rl.OurBalance.Zero() + theirBalance := rl.TheirBalance.Zero() + customBlob := rl.CustomBlob.Zero() + + // Create the tlv stream. + tlvStream, err := tlv.NewStream( + rl.OurOutputIndex.Record(), + rl.TheirOutputIndex.Record(), + rl.CommitTxHash.Record(), + ourBalance.Record(), + theirBalance.Record(), + customBlob.Record(), + ) + if err != nil { + return rl, err + } + + // Read the tlv stream. + parsedTypes, err := readTlvStream(r, tlvStream) + if err != nil { + return rl, err + } + + if t, ok := parsedTypes[ourBalance.TlvType()]; ok && t == nil { + rl.OurBalance = tlv.SomeRecordT(ourBalance) + } + + if t, ok := parsedTypes[theirBalance.TlvType()]; ok && t == nil { + rl.TheirBalance = tlv.SomeRecordT(theirBalance) + } + + if t, ok := parsedTypes[customBlob.TlvType()]; ok && t == nil { + rl.CustomBlob = tlv.SomeRecordT(customBlob) + } + + // Read the HTLC entries. + rl.HTLCEntries, err = deserializeHTLCEntries(r) + + return rl, err } // deserializeHTLCEntries deserializes a list of HTLC entries based on tlv // format. func deserializeHTLCEntries(r io.Reader) ([]*HTLCEntry, error) { - return cstate.DeserializeHTLCEntries(r) + var ( + htlcs []*HTLCEntry + + // htlcIndexBlob defines the tlv record type to be used when + // decoding from the disk. We use it instead of the one defined + // in `HTLCEntry.HtlcIndex` as previously this field was encoded + // using `uint16`, thus we will read it as raw bytes and + // deserialize it further below. + htlcIndexBlob tlv.OptionalRecordT[tlv.TlvType6, tlv.Blob] + ) + + for { + var htlc HTLCEntry + + customBlob := htlc.CustomBlob.Zero() + htlcIndex := htlcIndexBlob.Zero() + + // Create the tlv stream. + records := []tlv.Record{ + htlc.RHash.Record(), + htlc.RefundTimeout.Record(), + htlc.OutputIndex.Record(), + htlc.Incoming.Record(), + htlc.Amt.Record(), + customBlob.Record(), + htlcIndex.Record(), + } + + tlvStream, err := tlv.NewStream(records...) + if err != nil { + return nil, err + } + + // Read the HTLC entry. + parsedTypes, err := readTlvStream(r, tlvStream) + if err != nil { + // We've reached the end when hitting an EOF. + if err == io.ErrUnexpectedEOF { + break + } + return nil, err + } + + if t, ok := parsedTypes[customBlob.TlvType()]; ok && t == nil { + htlc.CustomBlob = tlv.SomeRecordT(customBlob) + } + + if t, ok := parsedTypes[htlcIndex.TlvType()]; ok && t == nil { + record, err := deserializeHtlcIndexCompatible( + htlcIndex.Val, + ) + if err != nil { + return nil, err + } + + htlc.HtlcIndex = record + } + + // Append the entry. + htlcs = append(htlcs, &htlc) + } + + return htlcs, nil +} + +// deserializeHtlcIndexCompatible takes raw bytes and decodes it into an +// optional record that's assigned to the entry's HtlcIndex. +// +// NOTE: previously this `HtlcIndex` was a tlv record that used `uint16` to +// encode its value. Given now its value is encoded using BigSizeT, and for any +// BigSizeT, its possible length values are 1, 3, 5, and 8. This means if the +// tlv record has a length of 2, we know for sure it must be an old record +// whose value was encoded using uint16. +func deserializeHtlcIndexCompatible(rawBytes []byte) ( + tlv.OptionalRecordT[tlv.TlvType6, tlv.BigSizeT[uint64]], error) { + + var ( + // record defines the record that's used by the HtlcIndex in the + // entry. + record tlv.OptionalRecordT[ + tlv.TlvType6, tlv.BigSizeT[uint64], + ] + + // htlcIndexVal is the decoded uint64 value. + htlcIndexVal uint64 + ) + + // If the length of the tlv record is 2, it must be encoded using uint16 + // as the BigSizeT encoding cannot have this length. + if len(rawBytes) == 2 { + // Decode the raw bytes into uint16 and convert it into uint64. + htlcIndexVal = uint64(binary.BigEndian.Uint16(rawBytes)) + } else { + // This value is encoded using BigSizeT, we now use the decoder + // to deserialize the raw bytes. + r := bytes.NewBuffer(rawBytes) + + // Create a buffer to be used in the decoding process. + buf := [8]byte{} + + // Use the BigSizeT's decoder. + err := tlv.DBigSize(r, &htlcIndexVal, &buf, 8) + if err != nil { + return record, err + } + } + + record = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType6]( + tlv.NewBigSizeT(htlcIndexVal), + )) + + return record, nil } // writeTlvStream is a helper function that encodes the tlv stream into the // writer. func writeTlvStream(w io.Writer, s *tlv.Stream) error { - return cstate.WriteTlvStream(w, s) + var b bytes.Buffer + if err := s.Encode(&b); err != nil { + return err + } + + // Write the stream's length as a varint. + err := tlv.WriteVarInt(w, uint64(b.Len()), &[8]byte{}) + if err != nil { + return err + } + + if _, err = w.Write(b.Bytes()); err != nil { + return err + } + + return nil } // readTlvStream is a helper function that decodes the tlv stream from the // reader. func readTlvStream(r io.Reader, s *tlv.Stream) (tlv.TypeMap, error) { - return cstate.ReadTlvStream(r, s) + var bodyLen uint64 + + // Read the stream's length. + bodyLen, err := tlv.ReadVarInt(r, &[8]byte{}) + switch { + // We'll convert any EOFs to ErrUnexpectedEOF, since this results in an + // invalid record. + case err == io.EOF: + return nil, io.ErrUnexpectedEOF + + // Other unexpected errors. + case err != nil: + return nil, err + } + + // TODO(yy): add overflow check. + lr := io.LimitReader(r, int64(bodyLen)) + + return s.DecodeWithParsedTypes(lr) } // fetchOldRevocationLog finds the revocation log from the deprecated diff --git a/channeldb/revocation_log_test.go b/channeldb/revocation_log_test.go index d59de8103..6e7afb9a3 100644 --- a/channeldb/revocation_log_test.go +++ b/channeldb/revocation_log_test.go @@ -8,7 +8,7 @@ import ( "math/rand" "testing" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lntest/channels" @@ -341,6 +341,7 @@ func TestSerializeAndDeserializeRevLog(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -585,6 +586,7 @@ func TestPutRevocationLog(t *testing.T) { } for _, tc := range testCases { + tc := tc fullDB, err := MakeTestDB(t) require.NoError(t, err) @@ -684,6 +686,7 @@ func TestFetchRevocationLogCompatible(t *testing.T) { } for _, tc := range testCases { + tc := tc fullDB, err := MakeTestDB(t) require.NoError(t, err) diff --git a/channeldb/waitingproof.go b/channeldb/waitingproof.go index 95d10cb2b..0c3913f9b 100644 --- a/channeldb/waitingproof.go +++ b/channeldb/waitingproof.go @@ -8,7 +8,6 @@ import ( "io" "sync" - "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" ) @@ -182,228 +181,54 @@ func (s *WaitingProofStore) Get(key WaitingProofKey) (*WaitingProof, error) { return proof, err } -// WaitingProofKey is the proof key which uniquely identifies the waiting proof -// object. The key includes proof type, short channel ID, and side -// (local/remote) to avoid cross-version collisions. -type WaitingProofKey [10]byte - -// WaitingProofType represents the type of proof encoded in a waiting proof -// record. -type WaitingProofType uint8 - -const ( - // WaitingProofTypeV1 represents a waiting proof containing an - // AnnounceSignatures1 message (gossip v1, P2WSH channels). - WaitingProofTypeV1 WaitingProofType = 0 - - // WaitingProofTypeV2 represents a waiting proof containing an - // AnnounceSignatures2 message (gossip v2, taproot channels). - WaitingProofTypeV2 WaitingProofType = 1 -) - -// typeToWaitingProof returns an empty instance of the WaitingProofInner -// implementation corresponding to the given proof type. -func typeToWaitingProof(pt WaitingProofType) (WaitingProofInner, bool) { - switch pt { - case WaitingProofTypeV1: - return &V1WaitingProof{}, true - case WaitingProofTypeV2: - return &V2WaitingProof{}, true - default: - return nil, false - } -} - -// WaitingProofInner is an interface that must be implemented by any waiting -// proof payload to be stored in the waiting proof store. -type WaitingProofInner interface { - // SCID returns the short channel ID of the channel that the waiting - // proof is for. - SCID() lnwire.ShortChannelID - - // Encode encodes the waiting proof to the given buffer. - Encode(w *bytes.Buffer, pver uint32) error - - // Decode parses the bytes from the given reader to reconstruct the - // waiting proof. - Decode(r io.Reader, pver uint32) error - - // Type returns the waiting proof type. - Type() WaitingProofType -} - -// V1WaitingProof wraps an AnnounceSignatures1 message for storage as a -// waiting proof. -type V1WaitingProof struct { - lnwire.AnnounceSignatures1 -} - -// SCID returns the short channel ID of the channel. -// -// NOTE: this is part of the WaitingProofInner interface. -func (p *V1WaitingProof) SCID() lnwire.ShortChannelID { - return p.ShortChannelID -} - -// Type returns the waiting proof type. -// -// NOTE: this is part of the WaitingProofInner interface. -func (p *V1WaitingProof) Type() WaitingProofType { - return WaitingProofTypeV1 -} - -// A compile time check to ensure V1WaitingProof implements the -// WaitingProofInner interface. -var _ WaitingProofInner = (*V1WaitingProof)(nil) - -// V2WaitingProof wraps an AnnounceSignatures2 message for storage as a -// waiting proof. It also stores the combined MuSig2 signing nonce needed to -// reconstruct the final signature. -type V2WaitingProof struct { - lnwire.AnnounceSignatures2 - - // CombinedNonce is the final combined signing nonce (R = R_1 + b*R_2) - // derived from the aggregate of all signers' public nonces. It is used - // as the R value in the final Schnorr signature. - CombinedNonce *btcec.PublicKey -} - -// SCID returns the short channel ID of the channel. -// -// NOTE: this is part of the WaitingProofInner interface. -func (p *V2WaitingProof) SCID() lnwire.ShortChannelID { - return p.ShortChannelID.Val -} - -// Decode parses the bytes from the given reader to reconstruct the waiting -// proof. -// -// NOTE: this is part of the WaitingProofInner interface. -func (p *V2WaitingProof) Decode(r io.Reader, pver uint32) error { - // Read the nonce-presence marker first. - var noncePresent bool - if err := binary.Read(r, byteOrder, &noncePresent); err != nil { - return err - } - - // If present, parse and store the combined signing nonce. - if noncePresent { - var nonceBytes [btcec.PubKeyBytesLenCompressed]byte - if err := binary.Read(r, byteOrder, &nonceBytes); err != nil { - return err - } - - nonce, err := btcec.ParsePubKey(nonceBytes[:]) - if err != nil { - return err - } - - p.CombinedNonce = nonce - } - - // Decode the underlying AnnounceSignatures2 payload. - return p.AnnounceSignatures2.Decode(r, pver) -} - -// Encode encodes the waiting proof to the given buffer. -// -// NOTE: this is part of the WaitingProofInner interface. -func (p *V2WaitingProof) Encode(w *bytes.Buffer, pver uint32) error { - // Write whether a combined nonce follows. - noncePresent := p.CombinedNonce != nil - if err := binary.Write(w, byteOrder, noncePresent); err != nil { - return err - } - - // If present, serialize and write the combined signing nonce. - if noncePresent { - err := binary.Write( - w, byteOrder, p.CombinedNonce.SerializeCompressed(), - ) - if err != nil { - return err - } - } - - // Encode the underlying AnnounceSignatures2 payload. - return p.AnnounceSignatures2.Encode(w, pver) -} - -// Type returns the waiting proof type. -// -// NOTE: this is part of the WaitingProofInner interface. -func (p *V2WaitingProof) Type() WaitingProofType { - return WaitingProofTypeV2 -} - -// A compile time check to ensure V2WaitingProof implements the -// WaitingProofInner interface. -var _ WaitingProofInner = (*V2WaitingProof)(nil) +// WaitingProofKey is the proof key which uniquely identifies the waiting +// proof object. The goal of this key is distinguish the local and remote +// proof for the same channel id. +type WaitingProofKey [9]byte // WaitingProof is the storable object, which encapsulate the half proof and // the information about from which side this proof came. This structure is // needed to make channel proof exchange persistent, so that after client // restart we may receive remote/local half proof and process it. type WaitingProof struct { - WaitingProofInner + *lnwire.AnnounceSignatures1 isRemote bool } -// NewWaitingProof constructs a new waiting proof instance for an -// AnnounceSignatures1 message. +// NewWaitingProof constructs a new waiting prof instance. func NewWaitingProof(isRemote bool, proof *lnwire.AnnounceSignatures1) *WaitingProof { return &WaitingProof{ - WaitingProofInner: &V1WaitingProof{*proof}, - isRemote: isRemote, - } -} - -// NewV2WaitingProof constructs a new waiting proof instance for an -// AnnounceSignatures2 message. -func NewV2WaitingProof(isRemote bool, proof *lnwire.AnnounceSignatures2, - combinedNonce *btcec.PublicKey) *WaitingProof { - - return &WaitingProof{ - WaitingProofInner: &V2WaitingProof{ - AnnounceSignatures2: *proof, - CombinedNonce: combinedNonce, - }, - isRemote: isRemote, + AnnounceSignatures1: proof, + isRemote: isRemote, } } // OppositeKey returns the key which uniquely identifies opposite waiting proof. func (p *WaitingProof) OppositeKey() WaitingProofKey { - var key WaitingProofKey - key[0] = byte(p.Type()) - binary.BigEndian.PutUint64(key[1:9], p.SCID().ToUint64()) + var key [9]byte + binary.BigEndian.PutUint64(key[:8], p.ShortChannelID.ToUint64()) if !p.isRemote { - key[9] = 1 + key[8] = 1 } return key } // Key returns the key which uniquely identifies waiting proof. func (p *WaitingProof) Key() WaitingProofKey { - var key WaitingProofKey - key[0] = byte(p.Type()) - binary.BigEndian.PutUint64(key[1:9], p.SCID().ToUint64()) + var key [9]byte + binary.BigEndian.PutUint64(key[:8], p.ShortChannelID.ToUint64()) if p.isRemote { - key[9] = 1 + key[8] = 1 } return key } // Encode writes the internal representation of waiting proof in byte stream. func (p *WaitingProof) Encode(w io.Writer) error { - if err := binary.Write(w, byteOrder, p.Type()); err != nil { - return err - } - if err := binary.Write(w, byteOrder, p.isRemote); err != nil { return err } @@ -415,31 +240,26 @@ func (p *WaitingProof) Encode(w io.Writer) error { return fmt.Errorf("expect io.Writer to be *bytes.Buffer") } - return p.WaitingProofInner.Encode(buf, 0) + if err := p.AnnounceSignatures1.Encode(buf, 0); err != nil { + return err + } + + return nil } // Decode reads the data from the byte stream and initializes the // waiting proof object with it. func (p *WaitingProof) Decode(r io.Reader) error { - var proofType WaitingProofType - if err := binary.Read(r, byteOrder, &proofType); err != nil { - return err - } - if err := binary.Read(r, byteOrder, &p.isRemote); err != nil { return err } - proof, ok := typeToWaitingProof(proofType) - if !ok { - return fmt.Errorf("unknown waiting proof type: %v", proofType) - } - - if err := proof.Decode(r, 0); err != nil { + msg := &lnwire.AnnounceSignatures1{} + if err := msg.Decode(r, 0); err != nil { return err } - p.WaitingProofInner = proof + p.AnnounceSignatures1 = msg return nil } diff --git a/channeldb/waitingproof_test.go b/channeldb/waitingproof_test.go index ad0436936..7155a6c99 100644 --- a/channeldb/waitingproof_test.go +++ b/channeldb/waitingproof_test.go @@ -1,13 +1,10 @@ package channeldb import ( - "bytes" - "encoding/binary" "errors" "reflect" "testing" - "github.com/btcsuite/btcd/btcec/v2" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" @@ -58,216 +55,3 @@ func TestWaitingProofStore(t *testing.T) { t.Fatal(err) } } - -// TestWaitingProofEncodePrefix asserts that waiting proofs are encoded with the -// V1 waiting proof type prefix. -func TestWaitingProofEncodePrefix(t *testing.T) { - t.Parallel() - - proof := NewWaitingProof(true, &lnwire.AnnounceSignatures1{ - NodeSignature: wireSig, - BitcoinSignature: wireSig, - ExtraOpaqueData: []byte{1, 2, 3}, - }) - - var encoded bytes.Buffer - require.NoError(t, proof.Encode(&encoded)) - - var proofType WaitingProofType - require.NoError(t, binary.Read(&encoded, byteOrder, &proofType)) - require.Equal(t, WaitingProofTypeV1, proofType) -} - -// TestWaitingProofDecodeUnknownType asserts that decoding fails for unknown -// waiting proof type prefixes. -func TestWaitingProofDecodeUnknownType(t *testing.T) { - t.Parallel() - - var encoded bytes.Buffer - require.NoError(t, binary.Write(&encoded, byteOrder, uint8(99))) - require.NoError(t, binary.Write(&encoded, byteOrder, true)) - - msg := &lnwire.AnnounceSignatures1{ - NodeSignature: wireSig, - BitcoinSignature: wireSig, - } - require.NoError(t, msg.Encode(&encoded, 0)) - - var proof WaitingProof - err := proof.Decode(&encoded) - require.ErrorContains(t, err, "unknown waiting proof type") -} - -// TestWaitingProofV2RoundTrip asserts that a V2 waiting proof can be encoded -// and decoded correctly, both with and without an aggregate nonce. -func TestWaitingProofV2RoundTrip(t *testing.T) { - t.Parallel() - - partialSig := lnwire.NewPartialSig(*testRScalar) - - annSig2 := lnwire.NewAnnSigs2( - lnwire.ChannelID{1, 2, 3}, - lnwire.NewShortChanIDFromInt(42), - partialSig, - ) - - // Generate a deterministic public key for the combined nonce. - combinedNonce := pubKey - - testCases := []struct { - name string - combinedNonce *btcec.PublicKey - }{ - { - name: "with combined nonce", - combinedNonce: combinedNonce, - }, - { - name: "without combined nonce", - combinedNonce: nil, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - proof := NewV2WaitingProof( - true, annSig2, tc.combinedNonce, - ) - - var buf bytes.Buffer - require.NoError(t, proof.Encode(&buf)) - - // Verify the type prefix is V2. - var proofType WaitingProofType - r := bytes.NewReader(buf.Bytes()) - require.NoError(t, binary.Read( - r, byteOrder, &proofType, - )) - require.Equal(t, WaitingProofTypeV2, proofType) - - // Decode and compare. - var decoded WaitingProof - require.NoError(t, decoded.Decode( - bytes.NewReader(buf.Bytes()), - )) - - require.Equal(t, proof.isRemote, decoded.isRemote) - require.Equal(t, proof.Key(), decoded.Key()) - - inner := decoded.WaitingProofInner - decodedV2, ok := inner.(*V2WaitingProof) - require.True(t, ok) - - origInner := proof.WaitingProofInner - origV2, ok := origInner.(*V2WaitingProof) - require.True(t, ok) - - require.Equal( - t, - origV2.ShortChannelID.Val, - decodedV2.ShortChannelID.Val, - ) - require.Equal( - t, - origV2.ChannelID.Val, - decodedV2.ChannelID.Val, - ) - - if tc.combinedNonce != nil { - require.NotNil(t, decodedV2.CombinedNonce) - require.True( - t, - tc.combinedNonce.IsEqual( - decodedV2.CombinedNonce, - ), - ) - } else { - require.Nil(t, decodedV2.CombinedNonce) - } - }) - } -} - -// TestWaitingProofV2Store tests add/get/remove of V2 waiting proofs through -// the store. -func TestWaitingProofV2Store(t *testing.T) { - t.Parallel() - - db, err := MakeTestDB(t) - require.NoError(t, err) - - store, err := NewWaitingProofStore(db) - require.NoError(t, err) - - partialSig := lnwire.NewPartialSig(*testRScalar) - annSig2 := lnwire.NewAnnSigs2( - lnwire.ChannelID{5, 6, 7}, - lnwire.NewShortChanIDFromInt(100), - partialSig, - ) - - proof := NewV2WaitingProof(true, annSig2, pubKey) - - require.NoError(t, store.Add(proof)) - - got, err := store.Get(proof.Key()) - require.NoError(t, err) - require.Equal(t, proof.Key(), got.Key()) - require.Equal(t, proof.isRemote, got.isRemote) - - gotV2, ok := got.WaitingProofInner.(*V2WaitingProof) - require.True(t, ok) - require.True(t, pubKey.IsEqual(gotV2.CombinedNonce)) - - require.NoError(t, store.Remove(proof.Key())) - - _, err = store.Get(proof.Key()) - require.ErrorIs(t, err, ErrWaitingProofNotFound) -} - -// TestWaitingProofCrossVersionKeyIsolation asserts that V1 and V2 waiting -// proofs for the same channel side are keyed independently. -func TestWaitingProofCrossVersionKeyIsolation(t *testing.T) { - t.Parallel() - - db, err := MakeTestDB(t) - require.NoError(t, err) - - store, err := NewWaitingProofStore(db) - require.NoError(t, err) - - scid := lnwire.NewShortChanIDFromInt(777) - - v1Proof := NewWaitingProof(true, &lnwire.AnnounceSignatures1{ - ShortChannelID: scid, - NodeSignature: wireSig, - BitcoinSignature: wireSig, - ExtraOpaqueData: []byte{1}, - }) - - partialSig := lnwire.NewPartialSig(*testRScalar) - v2AnnSig := lnwire.NewAnnSigs2( - lnwire.ChannelID{9, 9, 9}, - scid, - partialSig, - ) - v2Proof := NewV2WaitingProof(true, v2AnnSig, pubKey) - - require.NotEqual(t, v1Proof.Key(), v2Proof.Key()) - - require.NoError(t, store.Add(v1Proof)) - require.NoError(t, store.Add(v2Proof)) - - gotV1, err := store.Get(v1Proof.Key()) - require.NoError(t, err) - _, ok := gotV1.WaitingProofInner.(*V1WaitingProof) - require.True(t, ok) - - gotV2, err := store.Get(v2Proof.Key()) - require.NoError(t, err) - gotV2Inner, ok := gotV2.WaitingProofInner.(*V2WaitingProof) - require.True(t, ok) - require.True(t, pubKey.IsEqual(gotV2Inner.CombinedNonce)) -} diff --git a/channelnotifier/channelnotifier.go b/channelnotifier/channelnotifier.go index c9430dd72..f13ca6d75 100644 --- a/channelnotifier/channelnotifier.go +++ b/channelnotifier/channelnotifier.go @@ -3,8 +3,8 @@ package channelnotifier import ( "sync" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/subscribe" ) @@ -17,7 +17,7 @@ type ChannelNotifier struct { ntfnServer *subscribe.Server - chanDB chanstate.Store + chanDB *channeldb.ChannelStateDB } // PendingOpenChannelEvent represents a new event where a new channel has @@ -30,14 +30,14 @@ type PendingOpenChannelEvent struct { // channel. This might not have been persisted to the channel DB yet // because we are still waiting for the final message from the remote // peer. - PendingChannel *chanstate.OpenChannel + PendingChannel *channeldb.OpenChannel } // OpenChannelEvent represents a new event where a channel goes from pending // open to open. type OpenChannelEvent struct { // Channel is the channel that has become open. - Channel *chanstate.OpenChannel + Channel *channeldb.OpenChannel } // ActiveLinkEvent represents a new event where the link becomes active in the @@ -69,13 +69,7 @@ type InactiveChannelEvent struct { // ClosedChannelEvent represents a new event where a channel becomes closed. type ClosedChannelEvent struct { // CloseSummary is the summary of the channel close that has occurred. - CloseSummary *chanstate.ChannelCloseSummary -} - -// ChannelUpdateEvent represents a new event where a channel's state is updated. -type ChannelUpdateEvent struct { - // Channel is the channel that has been updated. - Channel *chanstate.OpenChannel + CloseSummary *channeldb.ChannelCloseSummary } // FullyResolvedChannelEvent represents a new event where a channel becomes @@ -97,7 +91,7 @@ type FundingTimeoutEvent struct { // New creates a new channel notifier. The ChannelNotifier gets channel // events from peers and from the chain arbitrator, and dispatches them to // its clients. -func New(chanDB chanstate.Store) *ChannelNotifier { +func New(chanDB *channeldb.ChannelStateDB) *ChannelNotifier { return &ChannelNotifier{ ntfnServer: subscribe.NewServer(), chanDB: chanDB, @@ -130,11 +124,6 @@ func (c *ChannelNotifier) Stop() error { // any time the Server is made aware of a new event. The subscription provides // channel events from the point of subscription onwards. // -// NOTE: This subscription includes both channel lifecycle events and higher -// frequency channel state updates, such as ChannelUpdateEvent. Callers that -// only need lifecycle updates should explicitly filter for the event types they -// consume. -// // TODO(carlaKC): update to allow subscriptions to specify a block height from // which we would like to subscribe to events. func (c *ChannelNotifier) SubscribeChannelEvents() (*subscribe.Client, error) { @@ -147,7 +136,7 @@ func (c *ChannelNotifier) SubscribeChannelEvents() (*subscribe.Client, error) { // persisted to the DB because we still wait for the final message from the // remote peer. func (c *ChannelNotifier) NotifyPendingOpenChannelEvent(chanPoint wire.OutPoint, - pendingChan *chanstate.OpenChannel) { + pendingChan *channeldb.OpenChannel) { event := PendingOpenChannelEvent{ ChannelPoint: &chanPoint, @@ -191,23 +180,6 @@ func (c *ChannelNotifier) NotifyClosedChannelEvent(chanPoint wire.OutPoint) { } } -// NotifyEarlyClosedChannelEvent dispatches a ClosedChannelEvent built from the -// supplied close summary, without consulting the channel database. This is -// used by the chain watcher to insta-dispatch CLOSED_CHANNEL events to RPC -// subscribers as soon as a coop close is first detected on chain, before the -// async N-conf path has persisted the close in the database. The summary's -// IsPending field will typically be true at this point; callers should set it -// accordingly. -func (c *ChannelNotifier) NotifyEarlyClosedChannelEvent( - summary *chanstate.ChannelCloseSummary) { - - event := ClosedChannelEvent{CloseSummary: summary} - if err := c.ntfnServer.SendUpdate(event); err != nil { - log.Warnf("Unable to send early closed channel update: %v", - err) - } -} - // NotifyFullyResolvedChannelEvent notifies the channelEventNotifier goroutine // that a channel was fully resolved on chain. func (c *ChannelNotifier) NotifyFullyResolvedChannelEvent( @@ -266,14 +238,3 @@ func (c *ChannelNotifier) NotifyInactiveChannelEvent(chanPoint wire.OutPoint) { log.Warnf("Unable to send inactive channel update: %v", err) } } - -// NotifyChannelUpdateEvent notifies subscribers that a channel's state has been -// updated. -func (c *ChannelNotifier) NotifyChannelUpdateEvent( - channel *chanstate.OpenChannel) { - - event := ChannelUpdateEvent{Channel: channel} - if err := c.ntfnServer.SendUpdate(event); err != nil { - log.Warnf("Unable to send channel update: %v", err) - } -} diff --git a/channelnotifier/channelnotifier_test.go b/channelnotifier/channelnotifier_test.go deleted file mode 100644 index 165d0e34d..000000000 --- a/channelnotifier/channelnotifier_test.go +++ /dev/null @@ -1,134 +0,0 @@ -package channelnotifier - -import ( - "testing" - "time" - - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/chanstate" - "github.com/stretchr/testify/require" -) - -// TestChannelUpdateEvent tests that channel update events are properly -// notified to subscribers. -func TestChannelUpdateEvent(t *testing.T) { - // Initialize the notification server. - ntfnServer := New(nil) - require.NoError(t, ntfnServer.Start()) - - defer func() { - require.NoError(t, ntfnServer.Stop()) - }() - - // Subscribe to channel events. - sub, err := ntfnServer.SubscribeChannelEvents() - require.NoError(t, err) - defer sub.Cancel() - - // Create a mock channel state. - channel := &chanstate.OpenChannel{} - - // Notify the server of a channel update event. - ntfnServer.NotifyChannelUpdateEvent(channel) - - // Consume the event. - select { - case event := <-sub.Updates(): - updateEvent, ok := event.(ChannelUpdateEvent) - require.True(t, ok) - require.Equal(t, channel, updateEvent.Channel) - - case <-time.After(time.Second): - t.Fatalf("expected to receive channel update event") - } -} - -// TestNotifyEarlyClosedChannelEvent verifies that the early-dispatch path -// delivers exactly the supplied close summary to subscribers without -// consulting the channel database. This is the path used by the chain watcher -// at first conf to insta-dispatch CLOSED_CHANNEL events for cooperative -// closes, before the close summary is persisted. -func TestNotifyEarlyClosedChannelEvent(t *testing.T) { - t.Parallel() - - // Pass nil for chanDB; the early-dispatch path must not touch it. - ntfnServer := New(nil) - require.NoError(t, ntfnServer.Start()) - t.Cleanup(func() { - require.NoError(t, ntfnServer.Stop()) - }) - - sub, err := ntfnServer.SubscribeChannelEvents() - require.NoError(t, err) - t.Cleanup(sub.Cancel) - - // Build a close summary with IsPending=true to mirror what the chain - // watcher will hand in at first-conf detection. - chanPoint := wire.OutPoint{ - Hash: chainhash.Hash{0x01, 0x02, 0x03}, - Index: 4, - } - summary := &chanstate.ChannelCloseSummary{ - ChanPoint: chanPoint, - CloseType: chanstate.CooperativeClose, - IsPending: true, - } - - ntfnServer.NotifyEarlyClosedChannelEvent(summary) - - select { - case event := <-sub.Updates(): - closedEvent, ok := event.(ClosedChannelEvent) - require.True( - t, ok, "expected ClosedChannelEvent, got %T", event, - ) - require.NotNil(t, closedEvent.CloseSummary) - require.True(t, closedEvent.CloseSummary.IsPending, - "early dispatched summary must carry IsPending=true") - require.Equal(t, summary, closedEvent.CloseSummary, - "early dispatched summary must reach subscriber "+ - "verbatim") - - case <-time.After(time.Second): - t.Fatal("expected to receive early closed channel event") - } -} - -// TestNotifyEarlyClosedChannelEventSingleEvent guards against accidental -// re-dispatch: a single early-notify call must produce exactly one event, -// not two (e.g. a fan-out bug between the early and the legacy paths). -func TestNotifyEarlyClosedChannelEventSingleEvent(t *testing.T) { - t.Parallel() - - ntfnServer := New(nil) - require.NoError(t, ntfnServer.Start()) - t.Cleanup(func() { - require.NoError(t, ntfnServer.Stop()) - }) - - sub, err := ntfnServer.SubscribeChannelEvents() - require.NoError(t, err) - t.Cleanup(sub.Cancel) - - summary := &chanstate.ChannelCloseSummary{ - ChanPoint: wire.OutPoint{Index: 7}, - CloseType: chanstate.CooperativeClose, - IsPending: true, - } - ntfnServer.NotifyEarlyClosedChannelEvent(summary) - - // Drain the single expected event. - select { - case <-sub.Updates(): - case <-time.After(time.Second): - t.Fatal("expected to receive early closed channel event") - } - - // Any further read should not produce another event. - select { - case extra := <-sub.Updates(): - t.Fatalf("unexpected second event: %T", extra) - case <-time.After(50 * time.Millisecond): - } -} diff --git a/chanrestore.go b/chanrestore.go index dfa1ea5f5..a041f571a 100644 --- a/chanrestore.go +++ b/chanrestore.go @@ -6,12 +6,11 @@ import ( "net" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chanbackup" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" @@ -36,7 +35,7 @@ const ( // need the secret key chain in order obtain the prior shachain root so we can // verify the DLP protocol as initiated by the remote node. type chanDBRestorer struct { - db chanstate.OpenChannelStore + db *channeldb.ChannelStateDB secretKeys keychain.SecretKeyRing @@ -171,13 +170,6 @@ func (c *chanDBRestorer) openChannelShell(backup chanbackup.Single) ( chanType |= channeldb.SimpleTaprootFeatureBit chanType |= channeldb.TapscriptRootBit - case chanbackup.SimpleTaprootFinalVersion: - chanType = channeldb.ZeroHtlcTxFeeBit - chanType |= channeldb.AnchorOutputsBit - chanType |= channeldb.SingleFunderTweaklessBit - chanType |= channeldb.SimpleTaprootFeatureBit - chanType |= channeldb.TaprootFinalBit - default: return nil, fmt.Errorf("unknown Single version: %w", err) } @@ -187,7 +179,7 @@ func (c *chanDBRestorer) openChannelShell(backup chanbackup.Single) ( chanShell := channeldb.ChannelShell{ NodeAddrs: backup.Addresses, - Chan: &chanstate.OpenChannel{ + Chan: &channeldb.OpenChannel{ ChanType: chanType, ChainHash: backup.ChainHash, IsInitiator: backup.IsInitiator, @@ -337,11 +329,6 @@ func (s *server) ConnectPeer(nodePub *btcec.PublicKey, addrs []net.Addr) error { "with chan restore", nodePub.SerializeCompressed()) } - // Strip persisted Tor v2 .onion entries that may have been carried - // over in an old static channel backup: Tor stopped serving v2 in 2021 - // and the dial would never succeed. Covered by TestWithoutV2Onion. - addrs = withoutV2Onion(addrs) - // For each of the known addresses, we'll attempt to launch a // persistent connection to the (pub, addr) pair. In the event that any // of them connect, all the other stale requests will be canceled. diff --git a/chanstate/channel.go b/chanstate/channel.go deleted file mode 100644 index 0950f4c72..000000000 --- a/chanstate/channel.go +++ /dev/null @@ -1,18 +0,0 @@ -package chanstate - -// ChanCount is used by the server in determining access control. -type ChanCount struct { - HasOpenOrClosedChan bool - PendingOpenCount uint64 -} - -// FinalHtlcInfo contains information about the final outcome of an htlc. -type FinalHtlcInfo struct { - // Settled is true is the htlc was settled. If false, the htlc was - // failed. - Settled bool - - // Offchain indicates whether the htlc was resolved off-chain or - // on-chain. - Offchain bool -} diff --git a/chanstate/channel_status.go b/chanstate/channel_status.go deleted file mode 100644 index b19fe3659..000000000 --- a/chanstate/channel_status.go +++ /dev/null @@ -1,110 +0,0 @@ -package chanstate - -import ( - "strconv" - "strings" -) - -// ChannelStatus is a bit vector used to indicate whether an OpenChannel is in -// the default usable state, or a state where it shouldn't be used. -type ChannelStatus uint64 - -var ( - // ChanStatusDefault is the normal state of an open channel. - ChanStatusDefault ChannelStatus - - // ChanStatusBorked indicates that the channel has entered an - // irreconcilable state, triggered by a state desynchronization or - // channel breach. Channels in this state should never be added to the - // htlc switch. - ChanStatusBorked ChannelStatus = 1 - - // ChanStatusCommitBroadcasted indicates that a commitment for this - // channel has been broadcasted. - ChanStatusCommitBroadcasted ChannelStatus = 1 << 1 - - // ChanStatusLocalDataLoss indicates that we have lost channel state - // for this channel, and broadcasting our latest commitment might be - // considered a breach. - // - // TODO(halseh): actually enforce that we are not force closing such a - // channel. - ChanStatusLocalDataLoss ChannelStatus = 1 << 2 - - // ChanStatusRestored is a status flag that signals that the channel - // has been restored, and doesn't have all the fields a typical channel - // will have. - ChanStatusRestored ChannelStatus = 1 << 3 - - // ChanStatusCoopBroadcasted indicates that a cooperative close for - // this channel has been broadcasted. Older cooperatively closed - // channels will only have this status set. Newer ones will also have - // close initiator information stored using the local/remote initiator - // status. This status is set in conjunction with the initiator status - // so that we do not need to check multiple channel statues for - // cooperative closes. - ChanStatusCoopBroadcasted ChannelStatus = 1 << 4 - - // ChanStatusLocalCloseInitiator indicates that we initiated closing - // the channel. - ChanStatusLocalCloseInitiator ChannelStatus = 1 << 5 - - // ChanStatusRemoteCloseInitiator indicates that the remote node - // initiated closing the channel. - ChanStatusRemoteCloseInitiator ChannelStatus = 1 << 6 -) - -// chanStatusStrings maps a ChannelStatus to a human friendly string that -// describes that status. -var chanStatusStrings = map[ChannelStatus]string{ - ChanStatusDefault: "ChanStatusDefault", - ChanStatusBorked: "ChanStatusBorked", - ChanStatusCommitBroadcasted: "ChanStatusCommitBroadcasted", - ChanStatusLocalDataLoss: "ChanStatusLocalDataLoss", - ChanStatusRestored: "ChanStatusRestored", - ChanStatusCoopBroadcasted: "ChanStatusCoopBroadcasted", - ChanStatusLocalCloseInitiator: "ChanStatusLocalCloseInitiator", - ChanStatusRemoteCloseInitiator: "ChanStatusRemoteCloseInitiator", -} - -// orderedChanStatusFlags is an in-order list of all that channel status flags. -var orderedChanStatusFlags = []ChannelStatus{ - ChanStatusBorked, - ChanStatusCommitBroadcasted, - ChanStatusLocalDataLoss, - ChanStatusRestored, - ChanStatusCoopBroadcasted, - ChanStatusLocalCloseInitiator, - ChanStatusRemoteCloseInitiator, -} - -// String returns a human-readable representation of the ChannelStatus. -func (c ChannelStatus) String() string { - // If no flags are set, then this is the default case. - if c == ChanStatusDefault { - return chanStatusStrings[ChanStatusDefault] - } - - // Add individual bit flags. - statusStr := "" - for _, flag := range orderedChanStatusFlags { - if c&flag == flag { - statusStr += chanStatusStrings[flag] + "|" - c -= flag - } - } - - // Remove anything to the right of the final bar, including it as well. - statusStr = strings.TrimRight(statusStr, "|") - - // Add any remaining flags which aren't accounted for as hex. - if c != 0 { - statusStr += "|0x" + strconv.FormatUint(uint64(c), 16) - } - - // If this was purely an unknown flag, then remove the extra bar at the - // start of the string. - statusStr = strings.TrimLeft(statusStr, "|") - - return statusStr -} diff --git a/chanstate/channel_type.go b/chanstate/channel_type.go deleted file mode 100644 index 9666307be..000000000 --- a/chanstate/channel_type.go +++ /dev/null @@ -1,157 +0,0 @@ -package chanstate - -// ChannelType is an enum-like type that describes one of several possible -// channel types. Each open channel is associated with a particular type as the -// channel type may determine how higher level operations are conducted such as -// fee negotiation, channel closing, the format of HTLCs, etc. Structure-wise, -// a ChannelType is a bit field, with each bit denoting a modification from the -// base channel type of single funder. -type ChannelType uint64 - -const ( - // NOTE: iota isn't used here for this enum needs to be stable - // long-term as it will be persisted to the database. - - // SingleFunderBit represents a channel wherein one party solely funds - // the entire capacity of the channel. - SingleFunderBit ChannelType = 0 - - // DualFunderBit represents a channel wherein both parties contribute - // funds towards the total capacity of the channel. The channel may be - // funded symmetrically or asymmetrically. - DualFunderBit ChannelType = 1 << 0 - - // SingleFunderTweaklessBit is similar to the basic SingleFunder channel - // type, but it omits the tweak for one's key in the commitment - // transaction of the remote party. - SingleFunderTweaklessBit ChannelType = 1 << 1 - - // NoFundingTxBit denotes if we have the funding transaction locally on - // disk. This bit may be on if the funding transaction was crafted by a - // wallet external to the primary daemon. - NoFundingTxBit ChannelType = 1 << 2 - - // AnchorOutputsBit indicates that the channel makes use of anchor - // outputs to bump the commitment transaction's effective feerate. This - // channel type also uses a delayed to_remote output script. - AnchorOutputsBit ChannelType = 1 << 3 - - // FrozenBit indicates that the channel is a frozen channel, meaning - // that only the responder can decide to cooperatively close the - // channel. - FrozenBit ChannelType = 1 << 4 - - // ZeroHtlcTxFeeBit indicates that the channel should use zero-fee - // second-level HTLC transactions. - ZeroHtlcTxFeeBit ChannelType = 1 << 5 - - // LeaseExpirationBit indicates that the channel has been leased for a - // period of time, constraining every output that pays to the channel - // initiator with an additional CLTV of the lease maturity. - LeaseExpirationBit ChannelType = 1 << 6 - - // ZeroConfBit indicates that the channel is a zero-conf channel. - ZeroConfBit ChannelType = 1 << 7 - - // ScidAliasChanBit indicates that the channel has negotiated the - // scid-alias channel type. - ScidAliasChanBit ChannelType = 1 << 8 - - // ScidAliasFeatureBit indicates that the scid-alias feature bit was - // negotiated during the lifetime of this channel. - ScidAliasFeatureBit ChannelType = 1 << 9 - - // SimpleTaprootFeatureBit indicates that the simple-taproot-chans - // feature bit was negotiated during the lifetime of the channel. - SimpleTaprootFeatureBit ChannelType = 1 << 10 - - // TapscriptRootBit indicates that this is a MuSig2 channel with a top - // level tapscript commitment. This MUST be set along with the - // SimpleTaprootFeatureBit. - TapscriptRootBit ChannelType = 1 << 11 - - // TaprootFinalBit indicates that this is a MuSig2 channel using the - // final/production taproot scripts and feature bits 80/81. This MUST - // be set along with the SimpleTaprootFeatureBit. - TaprootFinalBit ChannelType = 1 << 12 -) - -// IsSingleFunder returns true if the channel type if one of the known single -// funder variants. -func (c ChannelType) IsSingleFunder() bool { - return c&DualFunderBit == 0 -} - -// IsDualFunder returns true if the ChannelType has the DualFunderBit set. -func (c ChannelType) IsDualFunder() bool { - return c&DualFunderBit == DualFunderBit -} - -// IsTweakless returns true if the target channel uses a commitment that -// doesn't tweak the key for the remote party. -func (c ChannelType) IsTweakless() bool { - return c&SingleFunderTweaklessBit == SingleFunderTweaklessBit -} - -// HasFundingTx returns true if this channel type is one that has a funding -// transaction stored locally. -func (c ChannelType) HasFundingTx() bool { - return c&NoFundingTxBit == 0 -} - -// HasAnchors returns true if this channel type has anchor outputs on its -// commitment. -func (c ChannelType) HasAnchors() bool { - return c&AnchorOutputsBit == AnchorOutputsBit -} - -// ZeroHtlcTxFee returns true if this channel type uses second-level HTLC -// transactions signed with zero-fee. -func (c ChannelType) ZeroHtlcTxFee() bool { - return c&ZeroHtlcTxFeeBit == ZeroHtlcTxFeeBit -} - -// IsFrozen returns true if the channel is considered to be "frozen". A frozen -// channel means that only the responder can initiate a cooperative channel -// closure. -func (c ChannelType) IsFrozen() bool { - return c&FrozenBit == FrozenBit -} - -// HasLeaseExpiration returns true if the channel originated from a lease. -func (c ChannelType) HasLeaseExpiration() bool { - return c&LeaseExpirationBit == LeaseExpirationBit -} - -// HasZeroConf returns true if the channel is a zero-conf channel. -func (c ChannelType) HasZeroConf() bool { - return c&ZeroConfBit == ZeroConfBit -} - -// HasScidAliasChan returns true if the scid-alias channel type was negotiated. -func (c ChannelType) HasScidAliasChan() bool { - return c&ScidAliasChanBit == ScidAliasChanBit -} - -// HasScidAliasFeature returns true if the scid-alias feature bit was -// negotiated during the lifetime of this channel. -func (c ChannelType) HasScidAliasFeature() bool { - return c&ScidAliasFeatureBit == ScidAliasFeatureBit -} - -// IsTaproot returns true if the channel is using taproot features. -func (c ChannelType) IsTaproot() bool { - return c&SimpleTaprootFeatureBit == SimpleTaprootFeatureBit -} - -// HasTapscriptRoot returns true if the channel is using a top level tapscript -// root commitment. -func (c ChannelType) HasTapscriptRoot() bool { - return c&TapscriptRootBit == TapscriptRootBit -} - -// IsTaprootFinal returns true if the channel is using final/production taproot -// scripts and feature bits. -func (c ChannelType) IsTaprootFinal() bool { - return c&TaprootFinalBit == TaprootFinalBit -} diff --git a/chanstate/close_summary.go b/chanstate/close_summary.go deleted file mode 100644 index e58254513..000000000 --- a/chanstate/close_summary.go +++ /dev/null @@ -1,126 +0,0 @@ -package chanstate - -import ( - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/lnwire" -) - -// ClosureType is an enum like structure that details exactly _how_ a channel -// was closed. Three closure types are currently possible: none, cooperative, -// local force close, remote force close, and (remote) breach. -type ClosureType uint8 - -const ( - // CooperativeClose indicates that a channel has been closed - // cooperatively. This means that both channel peers were online and - // signed a new transaction paying out the settled balance of the - // contract. - CooperativeClose ClosureType = 0 - - // LocalForceClose indicates that we have unilaterally broadcast our - // current commitment state on-chain. - LocalForceClose ClosureType = 1 - - // RemoteForceClose indicates that the remote peer has unilaterally - // broadcast their current commitment state on-chain. - RemoteForceClose ClosureType = 4 - - // BreachClose indicates that the remote peer attempted to broadcast a - // prior _revoked_ channel state. - BreachClose ClosureType = 2 - - // FundingCanceled indicates that the channel never was fully opened - // before it was marked as closed in the database. This can happen if - // we or the remote fail at some point during the opening workflow, or - // we timeout waiting for the funding transaction to be confirmed. - FundingCanceled ClosureType = 3 - - // Abandoned indicates that the channel state was removed without - // any further actions. This is intended to clean up unusable - // channels during development. - Abandoned ClosureType = 5 -) - -// ChannelCloseSummary contains the final state of a channel at the point it -// was closed. Once a channel is closed, all the information pertaining to that -// channel within the openChannelBucket is deleted, and a compact summary is -// put in place instead. -type ChannelCloseSummary struct { - // ChanPoint is the outpoint for this channel's funding transaction, - // and is used as a unique identifier for the channel. - ChanPoint wire.OutPoint - - // ShortChanID encodes the exact location in the chain in which the - // channel was initially confirmed. This includes: the block height, - // transaction index, and the output within the target transaction. - ShortChanID lnwire.ShortChannelID - - // ChainHash is the hash of the genesis block that this channel resides - // within. - ChainHash chainhash.Hash - - // ClosingTXID is the txid of the transaction which ultimately closed - // this channel. - ClosingTXID chainhash.Hash - - // RemotePub is the public key of the remote peer that we formerly had - // a channel with. - RemotePub *btcec.PublicKey - - // Capacity was the total capacity of the channel. - Capacity btcutil.Amount - - // CloseHeight is the height at which the funding transaction was - // spent. - CloseHeight uint32 - - // SettledBalance is our total balance settled balance at the time of - // channel closure. This _does not_ include the sum of any outputs that - // have been time-locked as a result of the unilateral channel closure. - SettledBalance btcutil.Amount - - // TimeLockedBalance is the sum of all the time-locked outputs at the - // time of channel closure. If we triggered the force closure of this - // channel, then this value will be non-zero if our settled output is - // above the dust limit. If we were on the receiving side of a channel - // force closure, then this value will be non-zero if we had any - // outstanding outgoing HTLC's at the time of channel closure. - TimeLockedBalance btcutil.Amount - - // CloseType details exactly _how_ the channel was closed. Five closure - // types are possible: cooperative, local force, remote force, breach - // and funding canceled. - CloseType ClosureType - - // IsPending indicates whether this channel is in the 'pending close' - // state, which means the channel closing transaction has been - // confirmed, but not yet been fully resolved. In the case of a channel - // that has been cooperatively closed, it will go straight into the - // fully resolved state as soon as the closing transaction has been - // confirmed. However, for channels that have been force closed, they'll - // stay marked as "pending" until _all_ the pending funds have been - // swept. - IsPending bool - - // RemoteCurrentRevocation is the current revocation for their - // commitment transaction. However, since this is the derived public - // key, we don't yet have the private key so we aren't yet able to - // verify that it's actually in the hash chain. - RemoteCurrentRevocation *btcec.PublicKey - - // RemoteNextRevocation is the revocation key to be used for the *next* - // commitment transaction we create for the local node. Within the - // specification, this value is referred to as the - // per-commitment-point. - RemoteNextRevocation *btcec.PublicKey - - // LocalChanConfig is the channel configuration for the local node. - LocalChanConfig ChannelConfig - - // LastChanSyncMsg is the ChannelReestablish message for this channel - // for the state at the point where it was closed. - LastChanSyncMsg *lnwire.ChannelReestablish -} diff --git a/chanstate/commitment.go b/chanstate/commitment.go deleted file mode 100644 index 42ef1f2dc..000000000 --- a/chanstate/commitment.go +++ /dev/null @@ -1,304 +0,0 @@ -package chanstate - -import ( - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/graph/db/models" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/tlv" -) - -// ChannelCommitment is a snapshot of the commitment state at a particular -// point in the commitment chain. With each state transition, a snapshot of the -// current state along with all non-settled HTLCs are recorded. These snapshots -// detail the state of the _remote_ party's commitment at a particular state -// number. For ourselves (the local node) we ONLY store our most recent -// (unrevoked) state for safety purposes. -type ChannelCommitment struct { - // CommitHeight is the update number that this ChannelDelta represents - // the total number of commitment updates to this point. This can be - // viewed as sort of a "commitment height" as this number is - // monotonically increasing. - CommitHeight uint64 - - // LocalLogIndex is the cumulative log index of the local node at this - // point in the commitment chain. This value will be incremented for - // each _update_ added to the local update log. - LocalLogIndex uint64 - - // LocalHtlcIndex is the current local running HTLC index. This value - // will be incremented for each outgoing HTLC the local node offers. - LocalHtlcIndex uint64 - - // RemoteLogIndex is the cumulative log index of the remote node at - // this point in the commitment chain. This value will be incremented - // for each _update_ added to the remote update log. - RemoteLogIndex uint64 - - // RemoteHtlcIndex is the current remote running HTLC index. This value - // will be incremented for each outgoing HTLC the remote node offers. - RemoteHtlcIndex uint64 - - // LocalBalance is the current available settled balance within the - // channel directly spendable by us. - // - // NOTE: This is the balance *after* subtracting any commitment fee, - // AND anchor output values. - LocalBalance lnwire.MilliSatoshi - - // RemoteBalance is the current available settled balance within the - // channel directly spendable by the remote node. - // - // NOTE: This is the balance *after* subtracting any commitment fee, - // AND anchor output values. - RemoteBalance lnwire.MilliSatoshi - - // CommitFee is the amount calculated to be paid in fees for the - // current set of commitment transactions. The fee amount is persisted - // with the channel in order to allow the fee amount to be removed and - // recalculated with each channel state update, including updates that - // happen after a system restart. - CommitFee btcutil.Amount - - // FeePerKw is the min satoshis/kilo-weight that should be paid within - // the commitment transaction for the entire duration of the channel's - // lifetime. This field may be updated during normal operation of the - // channel as on-chain conditions change. - // - // TODO(halseth): make this SatPerKWeight. Cannot be done atm because - // this will cause the import cycle lnwallet<->channeldb. Fee - // estimation stuff should be in its own package. - FeePerKw btcutil.Amount - - // CommitTx is the latest version of the commitment state, broadcast - // able by us. - CommitTx *wire.MsgTx - - // CustomBlob is an optional blob that can be used to store information - // specific to a custom channel type. This may track some custom - // specific state for this given commitment. - CustomBlob fn.Option[tlv.Blob] - - // CommitSig is one half of the signature required to fully complete - // the script for the commitment transaction above. This is the - // signature signed by the remote party for our version of the - // commitment transactions. - CommitSig []byte - - // Htlcs is the set of HTLC's that are pending at this particular - // commitment height. - Htlcs []HTLC -} - -// Copy returns a deep copy of the channel commitment. -func (c *ChannelCommitment) Copy() ChannelCommitment { - c2 := *c - if c.CommitTx != nil { - c2.CommitTx = c.CommitTx.Copy() - } - if len(c.CommitSig) > 0 { - c2.CommitSig = make([]byte, len(c.CommitSig)) - copy(c2.CommitSig, c.CommitSig) - } - - c.CustomBlob.WhenSome(func(blob tlv.Blob) { - blobCopy := make([]byte, len(blob)) - copy(blobCopy, blob) - c2.CustomBlob = fn.Some(blobCopy) - }) - - if len(c.Htlcs) > 0 { - c2.Htlcs = make([]HTLC, len(c.Htlcs)) - for i, h := range c.Htlcs { - c2.Htlcs[i] = h.Copy() - } - } - - return c2 -} - -// HTLC is the on-disk representation of a hash time-locked contract. HTLCs are -// contained within ChannelDeltas which encode the current state of the -// commitment between state updates. -// -// TODO(roasbeef): save space by using smaller ints at tail end? -type HTLC struct { - // TODO(yy): can embed an HTLCEntry here. - - // Signature is the signature for the second level covenant transaction - // for this HTLC. The second level transaction is a timeout tx in the - // case that this is an outgoing HTLC, and a success tx in the case - // that this is an incoming HTLC. - // - // TODO(roasbeef): make [64]byte instead? - Signature []byte - - // RHash is the payment hash of the HTLC. - RHash [32]byte - - // Amt is the amount of milli-satoshis this HTLC escrows. - Amt lnwire.MilliSatoshi - - // RefundTimeout is the absolute timeout on the HTLC that the sender - // must wait before reclaiming the funds in limbo. - RefundTimeout uint32 - - // OutputIndex is the output index for this particular HTLC output - // within the commitment transaction. - OutputIndex int32 - - // Incoming denotes whether we're the receiver or the sender of this - // HTLC. - Incoming bool - - // OnionBlob is an opaque blob which is used to complete multi-hop - // routing. - OnionBlob [lnwire.OnionPacketSize]byte - - // HtlcIndex is the HTLC counter index of this active, outstanding - // HTLC. This differs from the LogIndex, as the HtlcIndex is only - // incremented for each offered HTLC, while they LogIndex is - // incremented for each update (includes settle+fail). - HtlcIndex uint64 - - // LogIndex is the cumulative log index of this HTLC. This differs - // from the HtlcIndex as this will be incremented for each new log - // update added. - LogIndex uint64 - - // ExtraData contains any additional information that was transmitted - // with the HTLC via TLVs. This data *must* already be encoded as a - // TLV stream, and may be empty. The length of this data is naturally - // limited by the space available to TLVs in update_add_htlc: - // = 65535 bytes (bolt 8 maximum message size): - // - 2 bytes (bolt 1 message_type) - // - 32 bytes (channel_id) - // - 8 bytes (id) - // - 8 bytes (amount_msat) - // - 32 bytes (payment_hash) - // - 4 bytes (cltv_expiry) - // - 1366 bytes (onion_routing_packet) - // = 64083 bytes maximum possible TLV stream - // - // Note that this extra data is stored inline with the OnionBlob for - // legacy reasons, see serialization/deserialization functions for - // detail. - ExtraData lnwire.ExtraOpaqueData - - // BlindingPoint is an optional blinding point included with the HTLC. - // - // Note: this field is not a part of on-disk representation of the - // HTLC. It is stored in the ExtraData field, which is used to store - // a TLV stream of additional information associated with the HTLC. - BlindingPoint lnwire.BlindingPointRecord - - // CustomRecords is a set of custom TLV records that are associated with - // this HTLC. These records are used to store additional information - // about the HTLC that is not part of the standard HTLC fields. This - // field is encoded within the ExtraData field. - CustomRecords lnwire.CustomRecords -} - -// Copy returns a full copy of the target HTLC. -func (h *HTLC) Copy() HTLC { - clone := HTLC{ - Incoming: h.Incoming, - Amt: h.Amt, - RefundTimeout: h.RefundTimeout, - OutputIndex: h.OutputIndex, - RHash: h.RHash, - OnionBlob: h.OnionBlob, - HtlcIndex: h.HtlcIndex, - LogIndex: h.LogIndex, - } - if len(h.Signature) > 0 { - clone.Signature = make([]byte, len(h.Signature)) - copy(clone.Signature, h.Signature) - } - if len(h.ExtraData) > 0 { - clone.ExtraData = make(lnwire.ExtraOpaqueData, len(h.ExtraData)) - copy(clone.ExtraData, h.ExtraData) - } - clone.BlindingPoint = h.BlindingPoint - if h.CustomRecords != nil { - clone.CustomRecords = make( - lnwire.CustomRecords, len(h.CustomRecords), - ) - for k, v := range h.CustomRecords { - clone.CustomRecords[k] = make([]byte, len(v)) - copy(clone.CustomRecords[k], v) - } - } - - return clone -} - -// LogUpdate represents a pending update to the remote commitment chain. The -// log update may be an add, fail, or settle entry. We maintain this data in -// order to be able to properly retransmit our proposed state if necessary. -type LogUpdate struct { - // LogIndex is the log index of this proposed commitment update entry. - LogIndex uint64 - - // UpdateMsg is the update message that was included within our - // local update log. The LogIndex value denotes the log index of this - // update which will be used when restoring our local update log if - // we're left with a dangling update on restart. - UpdateMsg lnwire.Message -} - -// CommitDiff represents the delta needed to apply the state transition between -// two subsequent commitment states. Given state N and state N+1, one is able -// to apply the set of messages contained within the CommitDiff to N to arrive -// at state N+1. Each time a new commitment is extended, we'll write a new -// commitment (along with the full commitment state) to disk so we can -// re-transmit the state in the case of a connection loss or message drop. -type CommitDiff struct { - // ChannelCommitment is the full commitment state that one would arrive - // at by applying the set of messages contained in the UpdateDiff to - // the prior accepted commitment. - Commitment ChannelCommitment - - // LogUpdates is the set of messages sent prior to the commitment state - // transition in question. Upon reconnection, if we detect that they - // don't have the commitment, then we re-send this along with the - // proper signature. - LogUpdates []LogUpdate - - // CommitSig is the exact CommitSig message that should be sent after - // the set of LogUpdates above has been retransmitted. The signatures - // within this message should properly cover the new commitment state - // and also the HTLC's within the new commitment state. - CommitSig *lnwire.CommitSig - - // OpenedCircuitKeys is a set of unique identifiers for any downstream - // Add packets included in this commitment txn. After a restart, this - // set of htlcs is acked from the link's incoming mailbox to ensure - // there isn't an attempt to re-add them to this commitment txn. - OpenedCircuitKeys []models.CircuitKey - - // ClosedCircuitKeys records the unique identifiers for any settle/fail - // packets that were resolved by this commitment txn. After a restart, - // this is used to ensure those circuits are removed from the circuit - // map, and the downstream packets in the link's mailbox are removed. - ClosedCircuitKeys []models.CircuitKey - - // AddAcks specifies the locations (commit height, pkg index) of any - // Adds that were failed/settled in this commit diff. This will ack - // entries in *this* channel's forwarding packages. - // - // NOTE: This value is not serialized, it is used to atomically mark the - // resolution of adds, such that they will not be reprocessed after a - // restart. - AddAcks []AddRef - - // SettleFailAcks specifies the locations (chan id, commit height, pkg - // index) of any Settles or Fails that were locked into this commit - // diff, and originate from *another* channel, i.e. the outgoing link. - // - // NOTE: This value is not serialized, it is used to atomically acks - // settles and fails from the forwarding packages of other channels, - // such that they will not be reforwarded internally after a restart. - SettleFailAcks []SettleFailRef -} diff --git a/chanstate/commitment_test.go b/chanstate/commitment_test.go deleted file mode 100644 index de2b9effd..000000000 --- a/chanstate/commitment_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package chanstate - -import ( - "bytes" - "testing" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/tlv" - "github.com/stretchr/testify/require" -) - -// TestHTLCCopy asserts that copying an HTLC produces an independent deep copy. -func TestHTLCCopy(t *testing.T) { - t.Parallel() - - _, blindingPoint := btcec.PrivKeyFromBytes(bytes.Repeat([]byte{1}, 32)) - - var rHash [32]byte - copy(rHash[:], bytes.Repeat([]byte{2}, len(rHash))) - - var onionBlob [lnwire.OnionPacketSize]byte - copy(onionBlob[:], bytes.Repeat([]byte{3}, len(onionBlob))) - - htlc := HTLC{ - Signature: []byte{4, 5, 6}, - RHash: rHash, - Amt: 1000, - RefundTimeout: 144, - OutputIndex: 3, - Incoming: true, - OnionBlob: onionBlob, - HtlcIndex: 42, - LogIndex: 43, - ExtraData: lnwire.ExtraOpaqueData{7, 8, 9}, - BlindingPoint: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType]( - blindingPoint, - ), - ), - CustomRecords: lnwire.CustomRecords{ - lnwire.MinCustomRecordsTlvType: []byte{10, 11, 12}, - }, - } - - clone := htlc.Copy() - require.Equal(t, htlc, clone) - - clone.Signature[0] = 0 - require.Equal(t, byte(4), htlc.Signature[0]) - - clone.ExtraData[0] = 0 - require.Equal(t, byte(7), htlc.ExtraData[0]) - - clone.CustomRecords[lnwire.MinCustomRecordsTlvType] = []byte{0} - require.Equal( - t, []byte{10, 11, 12}, - htlc.CustomRecords[lnwire.MinCustomRecordsTlvType], - ) - - clone = htlc.Copy() - clone.CustomRecords[lnwire.MinCustomRecordsTlvType][0] = 0 - require.Equal( - t, []byte{10, 11, 12}, - htlc.CustomRecords[lnwire.MinCustomRecordsTlvType], - ) -} diff --git a/chanstate/config.go b/chanstate/config.go deleted file mode 100644 index e9adecf25..000000000 --- a/chanstate/config.go +++ /dev/null @@ -1,108 +0,0 @@ -package chanstate - -import ( - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/lightningnetwork/lnd/keychain" - "github.com/lightningnetwork/lnd/lnwire" -) - -// ChannelStateBounds are the parameters from OpenChannel and AcceptChannel -// that are responsible for providing bounds on the state space of the abstract -// channel state. These values must be remembered for normal channel operation -// but they do not impact how we compute the commitment transactions themselves. -type ChannelStateBounds struct { - // ChanReserve is an absolute reservation on the channel for the - // owner of this set of constraints. This means that the current - // settled balance for this node CANNOT dip below the reservation - // amount. This acts as a defense against costless attacks when - // either side no longer has any skin in the game. - ChanReserve btcutil.Amount - - // MaxPendingAmount is the maximum pending HTLC value that the - // owner of these constraints can offer the remote node at a - // particular time. - MaxPendingAmount lnwire.MilliSatoshi - - // MinHTLC is the minimum HTLC value that the owner of these - // constraints can offer the remote node. If any HTLCs below this - // amount are offered, then the HTLC will be rejected. This, in - // tandem with the dust limit allows a node to regulate the - // smallest HTLC that it deems economically relevant. - MinHTLC lnwire.MilliSatoshi - - // MaxAcceptedHtlcs is the maximum number of HTLCs that the owner of - // this set of constraints can offer the remote node. This allows each - // node to limit their over all exposure to HTLCs that may need to be - // acted upon in the case of a unilateral channel closure or a contract - // breach. - MaxAcceptedHtlcs uint16 -} - -// CommitmentParams are the parameters from OpenChannel and -// AcceptChannel that are required to render an abstract channel state to a -// concrete commitment transaction. These values are necessary to (re)compute -// the commitment transaction. We treat these differently than the state space -// bounds because their history needs to be stored in order to properly handle -// chain resolution. -type CommitmentParams struct { - // DustLimit is the threshold (in satoshis) below which any outputs - // should be trimmed. When an output is trimmed, it isn't materialized - // as an actual output, but is instead burned to miner's fees. - DustLimit btcutil.Amount - - // CsvDelay is the relative time lock delay expressed in blocks. Any - // settled outputs that pay to the owner of this channel configuration - // MUST ensure that the delay branch uses this value as the relative - // time lock. Similarly, any HTLC's offered by this node should use - // this value as well. - CsvDelay uint16 -} - -// ChannelConfig is a struct that houses the various configuration opens for -// channels. Each side maintains an instance of this configuration file as it -// governs: how the funding and commitment transaction to be created, the -// nature of HTLC's allotted, the keys to be used for delivery, and relative -// time lock parameters. -type ChannelConfig struct { - // ChannelStateBounds is the set of constraints that must be - // upheld for the duration of the channel for the owner of this channel - // configuration. Constraints govern a number of flow control related - // parameters, also including the smallest HTLC that will be accepted - // by a participant. - ChannelStateBounds - - // CommitmentParams is an embedding of the parameters - // required to render an abstract channel state into a concrete - // commitment transaction. - CommitmentParams - - // MultiSigKey is the key to be used within the 2-of-2 output script - // for the owner of this channel config. - MultiSigKey keychain.KeyDescriptor - - // RevocationBasePoint is the base public key to be used when deriving - // revocation keys for the remote node's commitment transaction. This - // will be combined along with a per commitment secret to derive a - // unique revocation key for each state. - RevocationBasePoint keychain.KeyDescriptor - - // PaymentBasePoint is the base public key to be used when deriving - // the key used within the non-delayed pay-to-self output on the - // commitment transaction for a node. This will be combined with a - // tweak derived from the per-commitment point to ensure unique keys - // for each commitment transaction. - PaymentBasePoint keychain.KeyDescriptor - - // DelayBasePoint is the base public key to be used when deriving the - // key used within the delayed pay-to-self output on the commitment - // transaction for a node. This will be combined with a tweak derived - // from the per-commitment point to ensure unique keys for each - // commitment transaction. - DelayBasePoint keychain.KeyDescriptor - - // HtlcBasePoint is the base public key to be used when deriving the - // local HTLC key. The derived key (combined with the tweak derived - // from the per-commitment point) is used within the "to self" clause - // within any HTLC output scripts. - HtlcBasePoint keychain.KeyDescriptor -} diff --git a/chanstate/errors.go b/chanstate/errors.go deleted file mode 100644 index 4e8415cf9..000000000 --- a/chanstate/errors.go +++ /dev/null @@ -1,55 +0,0 @@ -package chanstate - -import ( - "errors" - "fmt" -) - -var ( - // ErrNoCommitmentsFound is returned when a channel has not set - // commitment states. - ErrNoCommitmentsFound = fmt.Errorf("no commitments found") - - // ErrNoChanInfoFound is returned when a particular channel does not - // have any channels state. - ErrNoChanInfoFound = fmt.Errorf("no chan info found") - - // ErrNoRevocationsFound is returned when revocation state for a - // particular channel cannot be found. - ErrNoRevocationsFound = fmt.Errorf("no revocations found") - - // ErrNoPendingCommit is returned when there is not a pending - // commitment for a remote party. A new commitment is written to disk - // each time we write a new state in order to be properly fault - // tolerant. - ErrNoPendingCommit = fmt.Errorf("no pending commits found") - - // ErrNoCommitPoint is returned when no data loss commit point is found - // in the database. - ErrNoCommitPoint = fmt.Errorf("no commit point found") - - // ErrNoCloseTx is returned when no closing tx is found for a channel - // in the state CommitBroadcasted. - ErrNoCloseTx = fmt.Errorf("no closing tx found") - - // ErrNoShutdownInfo is returned when no shutdown info has been - // persisted for a channel. - ErrNoShutdownInfo = errors.New("no shutdown info") - - // ErrNoRestoredChannelMutation is returned when a caller attempts to - // mutate a channel that's been recovered. - ErrNoRestoredChannelMutation = fmt.Errorf("cannot mutate restored " + - "channel state") - - // ErrChanBorked is returned when a caller attempts to mutate a borked - // channel. - ErrChanBorked = fmt.Errorf("cannot mutate borked channel") - - // ErrMissingIndexEntry is returned when a caller attempts to close a - // channel and the outpoint is missing from the index. - ErrMissingIndexEntry = fmt.Errorf("missing outpoint from index") - - // ErrOnionBlobLength is returned is an onion blob with incorrect - // length is read from disk. - ErrOnionBlobLength = errors.New("onion blob < 1366 bytes") -) diff --git a/chanstate/forwarding.go b/chanstate/forwarding.go deleted file mode 100644 index 49728fed2..000000000 --- a/chanstate/forwarding.go +++ /dev/null @@ -1,308 +0,0 @@ -package chanstate - -import ( - "bytes" - "encoding/binary" - "fmt" - "io" - - "github.com/lightningnetwork/lnd/lnwire" -) - -// AddRef is used to identify a particular Add in a FwdPkg. The short channel ID -// is assumed to be that of the packager. -type AddRef struct { - // Height is the remote commitment height that locked in the Add. - Height uint64 - - // Index is the index of the Add within the fwd pkg's Adds. - // - // NOTE: This index is static over the lifetime of a forwarding package. - Index uint16 -} - -// Encode serializes the AddRef to the given io.Writer. -func (a *AddRef) Encode(w io.Writer) error { - if err := binary.Write(w, binary.BigEndian, a.Height); err != nil { - return err - } - - return binary.Write(w, binary.BigEndian, a.Index) -} - -// Decode deserializes the AddRef from the given io.Reader. -func (a *AddRef) Decode(r io.Reader) error { - if err := binary.Read(r, binary.BigEndian, &a.Height); err != nil { - return err - } - - return binary.Read(r, binary.BigEndian, &a.Index) -} - -// SettleFailRef is used to locate a Settle/Fail in another channel's FwdPkg. A -// channel does not remove its own Settle/Fail htlcs, so the source is provided -// to locate a db bucket belonging to another channel. -type SettleFailRef struct { - // Source identifies the outgoing link that locked in the settle or - // fail. This is then used by the *incoming* link to find the settle - // fail in another link's forwarding packages. - Source lnwire.ShortChannelID - - // Height is the remote commitment height that locked in this - // Settle/Fail. - Height uint64 - - // Index is the index of the Add with the fwd pkg's SettleFails. - // - // NOTE: This index is static over the lifetime of a forwarding package. - Index uint16 -} - -// FwdState is an enum used to describe the lifecycle of a FwdPkg. -type FwdState byte - -const ( - // FwdStateLockedIn is the starting state for all forwarding packages. - // Packages in this state have not yet committed to the exact set of - // Adds to forward to the switch. - FwdStateLockedIn FwdState = iota - - // FwdStateProcessed marks the state in which all Adds have been - // locally processed and the forwarding decision to the switch has been - // persisted. - FwdStateProcessed - - // FwdStateCompleted signals that all Adds have been acked, and that all - // settles and fails have been delivered to their sources. Packages in - // this state can be removed permanently. - FwdStateCompleted -) - -// PkgFilter is used to compactly represent a particular subset of the Adds in a -// forwarding package. Each filter is represented as a simple, statically-sized -// bitvector, where the elements are intended to be the indices of the Adds as -// they are written in the FwdPkg. -type PkgFilter struct { - count uint16 - filter []byte -} - -// NewPkgFilter initializes an empty PkgFilter supporting `count` elements. -func NewPkgFilter(count uint16) *PkgFilter { - // We add 7 to ensure that the integer division yields properly rounded - // values. - filterLen := (count + 7) / 8 - - return &PkgFilter{ - count: count, - filter: make([]byte, filterLen), - } -} - -// Count returns the number of elements represented by this PkgFilter. -func (f *PkgFilter) Count() uint16 { - return f.count -} - -// Set marks the `i`-th element as included by this filter. -// NOTE: It is assumed that i is always less than count. -func (f *PkgFilter) Set(i uint16) { - byt := i / 8 - bit := i % 8 - - // Set the i-th bit in the filter. - // TODO(conner): ignore if > count to prevent panic? - f.filter[byt] |= byte(1 << (7 - bit)) -} - -// Contains queries the filter for membership of index `i`. -// NOTE: It is assumed that i is always less than count. -func (f *PkgFilter) Contains(i uint16) bool { - byt := i / 8 - bit := i % 8 - - // Read the i-th bit in the filter. - // TODO(conner): ignore if > count to prevent panic? - return f.filter[byt]&(1<<(7-bit)) != 0 -} - -// Equal checks two PkgFilters for equality. -func (f *PkgFilter) Equal(f2 *PkgFilter) bool { - if f == f2 { - return true - } - if f.count != f2.count { - return false - } - - return bytes.Equal(f.filter, f2.filter) -} - -// IsFull returns true if every element in the filter has been Set, and false -// otherwise. -func (f *PkgFilter) IsFull() bool { - // Batch validate bytes that are fully used. - for i := uint16(0); i < f.count/8; i++ { - if f.filter[i] != 0xFF { - return false - } - } - - // If the count is not a multiple of 8, check that the filter contains - // all remaining bits. - rem := f.count % 8 - for idx := f.count - rem; idx < f.count; idx++ { - if !f.Contains(idx) { - return false - } - } - - return true -} - -// Size returns number of bytes produced when the PkgFilter is serialized. -func (f *PkgFilter) Size() uint16 { - // 2 bytes for uint16 `count`, then round up number of bytes required to - // represent `count` bits. - return 2 + (f.count+7)/8 -} - -// Encode writes the filter to the provided io.Writer. -func (f *PkgFilter) Encode(w io.Writer) error { - if err := binary.Write(w, binary.BigEndian, f.count); err != nil { - return err - } - - _, err := w.Write(f.filter) - - return err -} - -// Decode reads the filter from the provided io.Reader. -func (f *PkgFilter) Decode(r io.Reader) error { - if err := binary.Read(r, binary.BigEndian, &f.count); err != nil { - return err - } - - f.filter = make([]byte, f.Size()-2) - _, err := io.ReadFull(r, f.filter) - - return err -} - -// String returns a human-readable string. -func (f *PkgFilter) String() string { - return fmt.Sprintf("count=%v, filter=%v", f.count, f.filter) -} - -// FwdPkg records all adds, settles, and fails that were locked in as a result -// of the remote peer sending us a revocation. Each package is identified by -// the short chanid and remote commitment height corresponding to the revocation -// that locked in the HTLCs. For everything except a locally initiated payment, -// settles and fails in a forwarding package must have a corresponding Add in -// another package, and can be removed individually once the source link has -// received the fail/settle. -// -// Adds cannot be removed, as we need to present the same batch of Adds to -// properly handle replay protection. Instead, we use a PkgFilter to mark that -// we have finished processing a particular Add. A FwdPkg should only be deleted -// after the AckFilter is full and all settles and fails have been persistently -// removed. -type FwdPkg struct { - // Source identifies the channel that wrote this forwarding package. - Source lnwire.ShortChannelID - - // Height is the height of the remote commitment chain that locked in - // this forwarding package. - Height uint64 - - // State signals the persistent condition of the package and directs how - // to reprocess the package in the event of failures. - State FwdState - - // Adds contains all add messages which need to be processed and - // forwarded to the switch. Adds does not change over the life of a - // forwarding package. - Adds []LogUpdate - - // FwdFilter is a filter containing the indices of all Adds that were - // forwarded to the switch. - // - // NOTE: This value signals when persisted to disk that the fwd package - // has been processed and garbage collection can happen. So it also - // has to be set for packages with no adds (empty packages or only - // settle/fail packages) so that they can be garbage collected as well. - FwdFilter *PkgFilter - - // AckFilter is a filter containing the indices of all Adds for which - // the source has received a settle or fail and is reflected in the next - // commitment txn. A package should not be removed until IsFull() - // returns true. - AckFilter *PkgFilter - - // SettleFails contains all settle and fail messages that should be - // forwarded to the switch. - SettleFails []LogUpdate - - // SettleFailFilter is a filter containing the indices of all Settle or - // Fails originating in this package that have been received and locked - // into the incoming link's commitment state. - SettleFailFilter *PkgFilter -} - -// NewFwdPkg initializes a new forwarding package in FwdStateLockedIn. This -// should be used to create a package at the time we receive a revocation. -func NewFwdPkg(source lnwire.ShortChannelID, height uint64, - addUpdates, settleFailUpdates []LogUpdate) *FwdPkg { - - nAddUpdates := uint16(len(addUpdates)) - nSettleFailUpdates := uint16(len(settleFailUpdates)) - - return &FwdPkg{ - Source: source, - Height: height, - State: FwdStateLockedIn, - Adds: addUpdates, - FwdFilter: NewPkgFilter(nAddUpdates), - AckFilter: NewPkgFilter(nAddUpdates), - SettleFails: settleFailUpdates, - SettleFailFilter: NewPkgFilter(nSettleFailUpdates), - } -} - -// SourceRef is a convenience method that returns an AddRef to this forwarding -// package for the index in the argument. It is the caller's responsibility -// to ensure that the index is in bounds. -func (f *FwdPkg) SourceRef(i uint16) AddRef { - return AddRef{ - Height: f.Height, - Index: i, - } -} - -// DestRef is a convenience method that returns a SettleFailRef to this -// forwarding package for the index in the argument. It is the caller's -// responsibility to ensure that the index is in bounds. -func (f *FwdPkg) DestRef(i uint16) SettleFailRef { - return SettleFailRef{ - Source: f.Source, - Height: f.Height, - Index: i, - } -} - -// ID returns an unique identifier for this package, used to ensure that sphinx -// replay processing of this batch is idempotent. -func (f *FwdPkg) ID() []byte { - var id = make([]byte, 16) - binary.BigEndian.PutUint64(id[:8], f.Source.ToUint64()) - binary.BigEndian.PutUint64(id[8:], f.Height) - - return id -} - -// String returns a human-readable description of the forwarding package. -func (f *FwdPkg) String() string { - return fmt.Sprintf("%T(src=%v, height=%v, nadds=%v, nfailsettles=%v)", - f, f.Source, f.Height, len(f.Adds), len(f.SettleFails)) -} diff --git a/chanstate/interface.go b/chanstate/interface.go deleted file mode 100644 index cce8158df..000000000 --- a/chanstate/interface.go +++ /dev/null @@ -1,426 +0,0 @@ -package chanstate - -import ( - "net" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/graph/db/models" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/shachain" -) - -// Store is the full persistence contract for the channel-state subsystem. -// Consumers depend on this interface rather than the concrete -// channeldb.ChannelStateDB so the underlying storage can be swapped without -// touching call sites. -// -// NOTE: This is named Store instead of DB to avoid confusion with the existing -// concrete channeldb.ChannelStateDB type during the migration. Once the channel -// state implementation moves into this package and the old concrete type is no -// longer part of consumer-facing code, this name can be revisited. -type Store interface { //nolint:interfacebloat - // OpenChannelStore owns open-channel records. - OpenChannelStore - - // HistoricalChannelStore owns the post-close historical channel view. - HistoricalChannelStore - - // OpenChannelLifecycleStore owns persisted lifecycle state for open - // channel records. - OpenChannelLifecycleStore - - // OpenChannelStatusStore owns persisted status flags for open channel - // records. - OpenChannelStatusStore - - // OpenChannelShutdownStore owns persisted shutdown state. - OpenChannelShutdownStore - - // OpenChannelCloseTxStore owns persisted closing transaction state. - OpenChannelCloseTxStore - - // OpenChannelCommitmentStore owns persisted commitment state for open - // channel records. - OpenChannelCommitmentStore - - // OpenChannelFwdPkgStore owns forwarding packages tied to open - // channel records. - OpenChannelFwdPkgStore - - // ClosedChannelStore owns closed-channel summaries and lifecycle - // mutations. - ClosedChannelStore - - // FinalHTLCStore owns final HTLC outcome data. - FinalHTLCStore - - // ChannelSetupStore owns temporary state used while setting up a - // channel. - ChannelSetupStore - - // LinkNodeMaintainer owns link-node maintenance derived from channel - // state. - LinkNodeMaintainer -} - -// OpenChannelStore owns open-channel records. -type OpenChannelStore interface { - // FetchOpenChannels starts a new database transaction and returns - // all stored currently active/open channels associated with the - // target nodeID. In the case that no active channels are known to - // have been created with this node, then a zero-length slice is - // returned. - FetchOpenChannels(nodeID *btcec.PublicKey) ([]*OpenChannel, error) - - // FetchChannel attempts to locate a channel specified by the passed - // channel point. If the channel cannot be found, then an error will - // be returned. - FetchChannel(chanPoint wire.OutPoint) (*OpenChannel, error) - - // FetchChannelByID attempts to locate a channel specified by the - // passed channel ID. If the channel cannot be found, then an error - // will be returned. - FetchChannelByID(id lnwire.ChannelID) (*OpenChannel, error) - - // FetchAllChannels attempts to retrieve all open channels currently - // stored within the database, including pending open, fully open and - // channels waiting for a closing transaction to confirm. - FetchAllChannels() ([]*OpenChannel, error) - - // FetchAllOpenChannels will return all channels that have the - // funding transaction confirmed, and is not waiting for a closing - // transaction to be confirmed. - FetchAllOpenChannels() ([]*OpenChannel, error) - - // FetchPendingChannels will return channels that have completed the - // process of generating and broadcasting funding transactions, but - // whose funding transactions have yet to be confirmed on the - // blockchain. - FetchPendingChannels() ([]*OpenChannel, error) - - // FetchWaitingCloseChannels will return all channels that have been - // opened, but are now waiting for a closing transaction to be - // confirmed. - // - // NOTE: This includes channels that are also pending to be opened. - FetchWaitingCloseChannels() ([]*OpenChannel, error) - - // FetchPermAndTempPeers returns a map where the key is the remote - // node's public key and the value is a struct that has a tally of - // the pending-open channels and whether the peer has an open or - // closed channel with us. - FetchPermAndTempPeers(chainHash []byte) (map[string]ChanCount, error) - - // RestoreChannelShells reconstructs the state of an OpenChannel from - // the ChannelShell. We'll attempt to write the new channel to disk, - // create a LinkNode instance with the passed node addresses, and - // finally create an edge within the graph for the channel as well. - // This method is idempotent, so repeated calls with the same set of - // channel shells won't modify the database after the initial call. - RestoreChannelShells(channelShells ...*ChannelShell) error -} - -// HistoricalChannelStore owns the post-close historical channel view. -type HistoricalChannelStore interface { - // FetchHistoricalChannel fetches open channel data from the - // historical channel bucket. - FetchHistoricalChannel(outPoint *wire.OutPoint) (*OpenChannel, error) -} - -// OpenChannelLifecycleStore owns persisted lifecycle state for open channel -// records. -type OpenChannelLifecycleStore interface { - // SyncPendingChannel writes a pending channel to the store and records - // the funding broadcast height. - SyncPendingChannel(channel *OpenChannel, addr net.Addr, - pendingHeight uint32) error - - // RefreshChannel updates the in-memory channel state using the latest - // state observed on disk. - RefreshChannel(channel *OpenChannel) error - - // MarkChannelConfirmationHeight updates the channel's confirmation - // height once the channel opening transaction receives one - // confirmation. - MarkChannelConfirmationHeight(channel *OpenChannel, height uint32) error - - // MarkChannelCloseConfirmationHeight updates the channel's close - // confirmation height when the closing transaction is first detected - // in a block. - MarkChannelCloseConfirmationHeight(channel *OpenChannel, - height fn.Option[uint32]) error - - // MarkChannelOpen marks a channel as fully open given a locator that - // uniquely describes its location within the chain. - MarkChannelOpen(channel *OpenChannel, - openLoc lnwire.ShortChannelID) error - - // MarkChannelRealScid marks the zero-conf channel's confirmed - // ShortChannelID. - MarkChannelRealScid(channel *OpenChannel, - realScid lnwire.ShortChannelID) error - - // MarkChannelScidAliasNegotiated marks that the scid-alias feature - // bit was negotiated during the lifetime of the channel. - MarkChannelScidAliasNegotiated(channel *OpenChannel) error -} - -// OpenChannelStatusStore owns persisted status flags for open channel records. -type OpenChannelStatusStore interface { - // ApplyChannelStatus adds the target status to the channel's - // persisted status bit field. - ApplyChannelStatus(channel *OpenChannel, status ChannelStatus) error - - // ClearChannelStatus clears the target status from the channel's - // persisted status bit field. - ClearChannelStatus(channel *OpenChannel, status ChannelStatus) error - - // MarkChannelDataLoss marks the channel as local-data-loss and stores - // the commit point needed if the remote force closes. - MarkChannelDataLoss(channel *OpenChannel, - commitPoint *btcec.PublicKey) error - - // FetchChannelDataLossCommitPoint retrieves the commit point stored - // when the channel was marked as local-data-loss. - FetchChannelDataLossCommitPoint(channel *OpenChannel) ( - *btcec.PublicKey, error) - - // MarkChannelBorked marks the channel as irreconcilable. - MarkChannelBorked(channel *OpenChannel) error -} - -// OpenChannelShutdownStore owns persisted shutdown state. -type OpenChannelShutdownStore interface { - // StoreChannelShutdownInfo persists the ShutdownInfo for the target - // channel. - StoreChannelShutdownInfo(channel *OpenChannel, info *ShutdownInfo) error - - // FetchChannelShutdownInfo fetches the persisted ShutdownInfo for the - // target channel. - FetchChannelShutdownInfo(channel *OpenChannel) ( - fn.Option[ShutdownInfo], error) -} - -// OpenChannelCloseTxStore owns persisted closing transaction state. -type OpenChannelCloseTxStore interface { - // MarkChannelCommitmentBroadcasted marks the channel as having a - // commitment transaction broadcast. - MarkChannelCommitmentBroadcasted(channel *OpenChannel, - closeTx *wire.MsgTx, closer lntypes.ChannelParty) error - - // MarkChannelCoopBroadcasted marks the channel as having a - // cooperative close transaction broadcast. - MarkChannelCoopBroadcasted(channel *OpenChannel, closeTx *wire.MsgTx, - closer lntypes.ChannelParty) error - - // FetchChannelBroadcastedCommitment fetches the stored unilateral - // closing transaction. - FetchChannelBroadcastedCommitment(channel *OpenChannel) (*wire.MsgTx, - error) - - // FetchChannelBroadcastedCooperative fetches the stored cooperative - // closing transaction. - FetchChannelBroadcastedCooperative(channel *OpenChannel) (*wire.MsgTx, - error) -} - -// OpenChannelCommitmentStore owns persisted commitment state for open channel -// records. -type OpenChannelCommitmentStore interface { - OpenChannelCommitmentMutationStore - OpenChannelCommitmentQueryStore -} - -// OpenChannelCommitmentMutationStore owns persisted commitment mutations for -// open channel records. -type OpenChannelCommitmentMutationStore interface { - // UpdateChannelCommitment updates the local commitment state. It - // locks in pending local updates received from the remote party and - // persists remote log updates that have been acked, but not signed - // for yet. The returned map contains all HTLC resolutions locked into - // this commitment, keyed by HTLC index. - UpdateChannelCommitment(channel *OpenChannel, - newCommitment *ChannelCommitment, - unsignedAckedUpdates []LogUpdate) (map[uint64]bool, error) - - // AppendRemoteCommitChain appends a new CommitDiff to the remote - // party's commitment chain. This is used after preparing a new remote - // commitment state, before transmitting it to the remote party. - AppendRemoteCommitChain(channel *OpenChannel, diff *CommitDiff) error - - // RemoteCommitChainTip returns the "tip" of the current remote - // commitment chain. - RemoteCommitChainTip(channel *OpenChannel) (*CommitDiff, error) - - // UnsignedAckedUpdates retrieves the persisted unsigned acked remote - // log updates that still need to be signed for. - UnsignedAckedUpdates(channel *OpenChannel) ([]LogUpdate, error) - - // RemoteUnsignedLocalUpdates retrieves the persisted, unsigned local - // log updates that the remote still needs to sign for. - RemoteUnsignedLocalUpdates(channel *OpenChannel) ([]LogUpdate, error) - - // InsertNextRevocation inserts the next commitment point into the - // persisted channel state. - InsertNextRevocation(channel *OpenChannel, - revKey *btcec.PublicKey) error - - // AdvanceCommitChainTail records the new state transition within the - // revocation log and promotes the pending remote commitment to the - // current remote commitment. - AdvanceCommitChainTail(channel *OpenChannel, fwdPkg *FwdPkg, - updates []LogUpdate, ourOutputIndex, - theirOutputIndex uint32) error -} - -// OpenChannelCommitmentQueryStore owns persisted commitment queries for open -// channel records. -type OpenChannelCommitmentQueryStore interface { - // CommitmentHeight returns the current persisted commitment height. - CommitmentHeight(channel *OpenChannel) (uint64, error) - - // LatestCommitments returns the two latest commitments for both the - // local and remote party. - LatestCommitments(channel *OpenChannel) (*ChannelCommitment, - *ChannelCommitment, error) - - // RemoteRevocationStore returns the most up to date commitment version - // of the revocation storage tree for the remote party. - RemoteRevocationStore(channel *OpenChannel) (shachain.Store, error) - - // FindPreviousState scans through the append-only log in an attempt to - // recover the previous channel state indicated by the update number. - FindPreviousState(channel *OpenChannel, updateNum uint64) ( - *RevocationLog, *ChannelCommitment, error) -} - -// OpenChannelFwdPkgStore owns forwarding packages tied to open channel -// records. -type OpenChannelFwdPkgStore interface { - // LoadFwdPkgs loads forwarding packages that have not been processed. - LoadFwdPkgs(channel *OpenChannel) ([]*FwdPkg, error) - - // AckAddHtlcs marks add HTLCs in forwarding packages as resolved. - AckAddHtlcs(channel *OpenChannel, addRefs ...AddRef) error - - // AckSettleFails marks settles or fails as delivered to the incoming - // link. - AckSettleFails(channel *OpenChannel, - settleFailRefs ...SettleFailRef) error - - // SetFwdFilter writes the forwarding filter for the forwarding package - // identified by height. - SetFwdFilter(channel *OpenChannel, height uint64, - fwdFilter *PkgFilter) error - - // RemoveFwdPkgs removes forwarding packages by remote commitment - // height. - RemoveFwdPkgs(channel *OpenChannel, heights ...uint64) error -} - -// ClosedChannelStore owns closed-channel summaries and lifecycle mutations. -type ClosedChannelStore interface { - // FetchClosedChannels attempts to fetch all closed channels from the - // database. The pendingOnly bool toggles if channels that aren't yet - // fully closed should be returned in the response or not. When a - // channel was cooperatively closed, it becomes fully closed after a - // single confirmation. When a channel was forcibly closed, it will - // become fully closed after _all_ the pending funds (if any) have - // been swept. - FetchClosedChannels(pendingOnly bool) ( - []*ChannelCloseSummary, error) - - // FetchClosedChannel queries for a channel close summary using the - // channel point of the channel in question. - FetchClosedChannel(chanID *wire.OutPoint) ( - *ChannelCloseSummary, error) - - // FetchClosedChannelForID queries for a channel close summary using - // the channel ID of the channel in question. - FetchClosedChannelForID(cid lnwire.ChannelID) ( - *ChannelCloseSummary, error) - - // MarkChanFullyClosed marks a channel as fully closed within the - // database. A channel should be marked as fully closed if the - // channel was initially cooperatively closed and it's reached a - // single confirmation, or after all the pending funds in a channel - // that has been forcibly closed have been swept. - MarkChanFullyClosed(chanPoint *wire.OutPoint) error - - // CloseChannel marks the given channel as closed: the open-channel - // record is removed and the supplied ChannelCloseSummary is - // archived so the channel becomes retrievable via - // FetchClosedChannel and FetchClosedChannelForID. Any ChannelStatus - // values are merged into the archived summary. Returns - // ErrChannelCloseSummaryNil if summary is nil. - CloseChannel(channel *OpenChannel, summary *ChannelCloseSummary, - statuses ...ChannelStatus) error - - // AbandonChannel attempts to remove the target channel from the open - // channel database. If the channel was already removed (has a closed - // channel entry), then we'll return a nil error. Otherwise, we'll - // insert a new close summary into the database. - AbandonChannel(chanPoint *wire.OutPoint, bestHeight uint32) error -} - -// FinalHTLCStore owns final HTLC outcome data. -type FinalHTLCStore interface { - // LookupFinalHtlc retrieves a final htlc resolution from the - // database. If the htlc has no final resolution yet, ErrHtlcUnknown - // is returned. - LookupFinalHtlc(chanID lnwire.ShortChannelID, - htlcIndex uint64) (*FinalHtlcInfo, error) - - // PutOnchainFinalHtlcOutcome stores the final on-chain outcome of an - // htlc in the database. - PutOnchainFinalHtlcOutcome(chanID lnwire.ShortChannelID, - htlcID uint64, settled bool) error -} - -// ChannelSetupStore owns temporary state used while setting up a channel. This -// state should be deleted once the link comes up. -type ChannelSetupStore interface { - // SaveChannelOpeningState saves the serialized channel state for the - // provided chanPoint to the channelOpeningStateBucket. - SaveChannelOpeningState(outPoint, serializedState []byte) error - - // GetChannelOpeningState fetches the serialized channel state for - // the provided outPoint from the database, or returns - // ErrChannelNotFound if the channel is not found. - GetChannelOpeningState(outPoint []byte) ([]byte, error) - - // DeleteChannelOpeningState removes any state for outPoint from the - // database. - DeleteChannelOpeningState(outPoint []byte) error - - // SaveInitialForwardingPolicy saves the serialized forwarding policy - // for the provided permanent channel id. - SaveInitialForwardingPolicy(chanID lnwire.ChannelID, - forwardingPolicy *models.ForwardingPolicy) error - - // GetInitialForwardingPolicy fetches the serialized forwarding policy - // for the provided channel id from the database, or returns - // ErrChannelNotFound if a forwarding policy for this channel id is not - // found. - GetInitialForwardingPolicy(chanID lnwire.ChannelID) ( - *models.ForwardingPolicy, error) - - // DeleteInitialForwardingPolicy removes the forwarding policy for a - // given channel from the database. - DeleteInitialForwardingPolicy(chanID lnwire.ChannelID) error -} - -// LinkNodeMaintainer owns link-node maintenance derived from channel state. -type LinkNodeMaintainer interface { - // PruneLinkNodes attempts to prune all link nodes found within the - // database with whom we no longer have any open channels with. - PruneLinkNodes() error - - // RepairLinkNodes scans all channels in the database and ensures - // that a link node exists for each remote peer. This should be - // called on startup to ensure that our database is consistent. - RepairLinkNodes(network wire.BitcoinNet) error -} diff --git a/chanstate/kv_revocation_log.go b/chanstate/kv_revocation_log.go deleted file mode 100644 index 37bae338b..000000000 --- a/chanstate/kv_revocation_log.go +++ /dev/null @@ -1,307 +0,0 @@ -package chanstate - -import ( - "bytes" - "encoding/binary" - "errors" - "io" - - "github.com/lightningnetwork/lnd/tlv" -) - -// This file contains the KV/TLV serialization helpers for revocation logs. -// The domain types remain in revocation_log.go. - -// htlcEntryToTlvStream converts an HTLCEntry record into a tlv representation. -func htlcEntryToTlvStream(h *HTLCEntry) (*tlv.Stream, error) { - records := []tlv.Record{ - h.RHash.Record(), - h.RefundTimeout.Record(), - h.OutputIndex.Record(), - h.Incoming.Record(), - h.Amt.Record(), - } - - h.CustomBlob.WhenSome(func(r tlv.RecordT[tlv.TlvType5, tlv.Blob]) { - records = append(records, r.Record()) - }) - - h.HtlcIndex.WhenSome(func(r tlv.RecordT[tlv.TlvType6, - tlv.BigSizeT[uint64]]) { - - records = append(records, r.Record()) - }) - - tlv.SortRecords(records) - - return tlv.NewStream(records...) -} - -// SerializeRevocationLog serializes a RevocationLog record based on tlv -// format. -func SerializeRevocationLog(w io.Writer, rl *RevocationLog) error { - // Add the tlv records for all non-optional fields. - records := []tlv.Record{ - rl.OurOutputIndex.Record(), - rl.TheirOutputIndex.Record(), - rl.CommitTxHash.Record(), - } - - // Now we add any optional fields that are non-nil. - rl.OurBalance.WhenSome( - func(r tlv.RecordT[tlv.TlvType3, BigSizeMilliSatoshi]) { - records = append(records, r.Record()) - }, - ) - - rl.TheirBalance.WhenSome( - func(r tlv.RecordT[tlv.TlvType4, BigSizeMilliSatoshi]) { - records = append(records, r.Record()) - }, - ) - - rl.CustomBlob.WhenSome(func(r tlv.RecordT[tlv.TlvType5, tlv.Blob]) { - records = append(records, r.Record()) - }) - - // Create the tlv stream. - tlvStream, err := tlv.NewStream(records...) - if err != nil { - return err - } - - // Write the tlv stream. - if err := WriteTlvStream(w, tlvStream); err != nil { - return err - } - - // Write the HTLCs. - return SerializeHTLCEntries(w, rl.HTLCEntries) -} - -// SerializeHTLCEntries serializes a list of HTLCEntry records based on tlv -// format. -func SerializeHTLCEntries(w io.Writer, htlcs []*HTLCEntry) error { - for _, htlc := range htlcs { - // Create the tlv stream. - tlvStream, err := htlcEntryToTlvStream(htlc) - if err != nil { - return err - } - - // Write the tlv stream. - if err := WriteTlvStream(w, tlvStream); err != nil { - return err - } - } - - return nil -} - -// DeserializeRevocationLog deserializes a RevocationLog based on tlv format. -func DeserializeRevocationLog(r io.Reader) (RevocationLog, error) { - var rl RevocationLog - - ourBalance := rl.OurBalance.Zero() - theirBalance := rl.TheirBalance.Zero() - customBlob := rl.CustomBlob.Zero() - - // Create the tlv stream. - tlvStream, err := tlv.NewStream( - rl.OurOutputIndex.Record(), - rl.TheirOutputIndex.Record(), - rl.CommitTxHash.Record(), - ourBalance.Record(), - theirBalance.Record(), - customBlob.Record(), - ) - if err != nil { - return rl, err - } - - // Read the tlv stream. - parsedTypes, err := ReadTlvStream(r, tlvStream) - if err != nil { - return rl, err - } - - if t, ok := parsedTypes[ourBalance.TlvType()]; ok && t == nil { - rl.OurBalance = tlv.SomeRecordT(ourBalance) - } - - if t, ok := parsedTypes[theirBalance.TlvType()]; ok && t == nil { - rl.TheirBalance = tlv.SomeRecordT(theirBalance) - } - - if t, ok := parsedTypes[customBlob.TlvType()]; ok && t == nil { - rl.CustomBlob = tlv.SomeRecordT(customBlob) - } - - // Read the HTLC entries. - rl.HTLCEntries, err = DeserializeHTLCEntries(r) - - return rl, err -} - -// DeserializeHTLCEntries deserializes a list of HTLC entries based on tlv -// format. -func DeserializeHTLCEntries(r io.Reader) ([]*HTLCEntry, error) { - var ( - htlcs []*HTLCEntry - - // htlcIndexBlob defines the tlv record type to be used when - // decoding from the disk. We use it instead of the one defined - // in `HTLCEntry.HtlcIndex` as previously this field was encoded - // using `uint16`, thus we will read it as raw bytes and - // deserialize it further below. - htlcIndexBlob tlv.OptionalRecordT[tlv.TlvType6, tlv.Blob] - ) - - for { - var htlc HTLCEntry - - customBlob := htlc.CustomBlob.Zero() - htlcIndex := htlcIndexBlob.Zero() - - // Create the tlv stream. - records := []tlv.Record{ - htlc.RHash.Record(), - htlc.RefundTimeout.Record(), - htlc.OutputIndex.Record(), - htlc.Incoming.Record(), - htlc.Amt.Record(), - customBlob.Record(), - htlcIndex.Record(), - } - - tlvStream, err := tlv.NewStream(records...) - if err != nil { - return nil, err - } - - // Read the HTLC entry. - parsedTypes, err := ReadTlvStream(r, tlvStream) - if err != nil { - // We've reached the end when hitting an EOF. - if errors.Is(err, io.ErrUnexpectedEOF) { - break - } - - return nil, err - } - - if t, ok := parsedTypes[customBlob.TlvType()]; ok && t == nil { - htlc.CustomBlob = tlv.SomeRecordT(customBlob) - } - - if t, ok := parsedTypes[htlcIndex.TlvType()]; ok && t == nil { - record, err := deserializeHtlcIndexCompatible( - htlcIndex.Val, - ) - if err != nil { - return nil, err - } - - htlc.HtlcIndex = record - } - - // Append the entry. - htlcs = append(htlcs, &htlc) - } - - return htlcs, nil -} - -// deserializeHtlcIndexCompatible takes raw bytes and decodes it into an -// optional record that's assigned to the entry's HtlcIndex. -// -// NOTE: previously this `HtlcIndex` was a tlv record that used `uint16` to -// encode its value. Given now its value is encoded using BigSizeT, and for any -// BigSizeT, its possible length values are 1, 3, 5, and 8. This means if the -// tlv record has a length of 2, we know for sure it must be an old record -// whose value was encoded using uint16. -func deserializeHtlcIndexCompatible(rawBytes []byte) ( - tlv.OptionalRecordT[tlv.TlvType6, tlv.BigSizeT[uint64]], error) { - - var ( - // record defines the record that's used by the HtlcIndex in the - // entry. - record tlv.OptionalRecordT[ - tlv.TlvType6, tlv.BigSizeT[uint64], - ] - - // htlcIndexVal is the decoded uint64 value. - htlcIndexVal uint64 - ) - - // If the length of the tlv record is 2, it must be encoded using uint16 - // as the BigSizeT encoding cannot have this length. - if len(rawBytes) == 2 { - // Decode the raw bytes into uint16 and convert it into uint64. - htlcIndexVal = uint64(binary.BigEndian.Uint16(rawBytes)) - } else { - // This value is encoded using BigSizeT, we now use the decoder - // to deserialize the raw bytes. - r := bytes.NewBuffer(rawBytes) - - // Create a buffer to be used in the decoding process. - buf := [8]byte{} - - // Use the BigSizeT's decoder. - err := tlv.DBigSize(r, &htlcIndexVal, &buf, 8) - if err != nil { - return record, err - } - } - - record = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType6]( - tlv.NewBigSizeT(htlcIndexVal), - )) - - return record, nil -} - -// WriteTlvStream is a helper function that encodes the tlv stream into the -// writer. -func WriteTlvStream(w io.Writer, s *tlv.Stream) error { - var b bytes.Buffer - if err := s.Encode(&b); err != nil { - return err - } - - // Write the stream's length as a varint. - err := tlv.WriteVarInt(w, uint64(b.Len()), &[8]byte{}) - if err != nil { - return err - } - - if _, err = w.Write(b.Bytes()); err != nil { - return err - } - - return nil -} - -// ReadTlvStream is a helper function that decodes the tlv stream from the -// reader. -func ReadTlvStream(r io.Reader, s *tlv.Stream) (tlv.TypeMap, error) { - var bodyLen uint64 - - // Read the stream's length. - bodyLen, err := tlv.ReadVarInt(r, &[8]byte{}) - switch { - // We'll convert any EOFs to ErrUnexpectedEOF, since this results in an - // invalid record. - case errors.Is(err, io.EOF): - return nil, io.ErrUnexpectedEOF - - // Other unexpected errors. - case err != nil: - return nil, err - } - - // TODO(yy): add overflow check. - lr := io.LimitReader(r, int64(bodyLen)) - - return s.DecodeWithParsedTypes(lr) -} diff --git a/chanstate/log.go b/chanstate/log.go deleted file mode 100644 index 029e91db7..000000000 --- a/chanstate/log.go +++ /dev/null @@ -1,29 +0,0 @@ -package chanstate - -import ( - "github.com/btcsuite/btclog/v2" - "github.com/lightningnetwork/lnd/build" -) - -// log is a logger that is initialized with no output filters. This means the -// package will not perform any logging by default until the caller requests -// it. -var log btclog.Logger - -// init initializes the package-global logger instance. -func init() { - UseLogger(build.NewSubLogger("CHST", nil)) -} - -// DisableLog disables all library log output. Logging output is disabled by -// default until UseLogger is called. -func DisableLog() { - UseLogger(btclog.Disabled) -} - -// UseLogger uses a specified Logger to output package logging info. This -// should be used in preference to SetLogWriter if the caller is also using -// btclog. -func UseLogger(logger btclog.Logger) { - log = logger -} diff --git a/chanstate/open_channel.go b/chanstate/open_channel.go deleted file mode 100644 index 2011ebff4..000000000 --- a/chanstate/open_channel.go +++ /dev/null @@ -1,1282 +0,0 @@ -package chanstate - -import ( - "errors" - "fmt" - "net" - "sync" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/htlcswitch/hop" - "github.com/lightningnetwork/lnd/input" - "github.com/lightningnetwork/lnd/keychain" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/shachain" - "github.com/lightningnetwork/lnd/tlv" -) - -// ChannelShell contains the minimal channel state and peer addresses needed to -// restore a channel during recovery. -type ChannelShell struct { - // NodeAddrs is the set of addresses that this node has known to be - // reachable at in the past. - NodeAddrs []net.Addr - - // Chan is the minimal OpenChannel state required to restore the - // channel on disk. - Chan *OpenChannel -} - -// OpenChannel encapsulates the persistent and dynamic state of an open channel -// with a remote node. An open channel supports several options for on-disk -// serialization depending on the exact context. Full (upon channel creation) -// state commitments, and partial (due to a commitment update) writes are -// supported. Each partial write due to a state update appends the new update -// to an on-disk log, which can then subsequently be queried in order to -// "time-travel" to a prior state. -type OpenChannel struct { - // ChanType denotes which type of channel this is. - ChanType ChannelType - - // ChainHash is a hash which represents the blockchain that this - // channel will be opened within. This value is typically the genesis - // hash. In the case that the original chain went through a contentious - // hard-fork, then this value will be tweaked using the unique fork - // point on each branch. - ChainHash chainhash.Hash - - // FundingOutpoint is the outpoint of the final funding transaction. - // This value uniquely and globally identifies the channel within the - // target blockchain as specified by the chain hash parameter. - FundingOutpoint wire.OutPoint - - // ShortChannelID encodes the exact location in the chain in which the - // channel was initially confirmed. This includes: the block height, - // transaction index, and the output within the target transaction. - // - // If IsZeroConf(), then this will the "base" (very first) ALIAS scid - // and the confirmed SCID will be stored in ConfirmedScid. - ShortChannelID lnwire.ShortChannelID - - // IsPending indicates whether a channel's funding transaction has been - // confirmed. - IsPending bool - - // IsInitiator is a bool which indicates if we were the original - // initiator for the channel. This value may affect how higher levels - // negotiate fees, or close the channel. - IsInitiator bool - - // chanStatus is the current status of this channel. If it is not in - // the state Default, it should not be used for forwarding payments. - chanStatus ChannelStatus - - // FundingBroadcastHeight is the height in which the funding - // transaction was broadcast. This value can be used by higher level - // sub-systems to determine if a channel is stale and/or should have - // been confirmed before a certain height. - FundingBroadcastHeight uint32 - - // ConfirmationHeight records the block height at which the funding - // transaction was first confirmed. - ConfirmationHeight uint32 - - // CloseConfirmationHeight records the block height at which the closing - // transaction was first confirmed. This is used to track remaining - // confirmations until the channel is considered fully closed. It is - // None if the closing transaction has not yet been confirmed, or if - // this data was not available (e.g. channels closed before this - // field was introduced). - CloseConfirmationHeight fn.Option[uint32] - - // NumConfsRequired is the number of confirmations a channel's funding - // transaction must have received in order to be considered available - // for normal transactional use. - NumConfsRequired uint16 - - // ChannelFlags holds the flags that were sent as part of the - // open_channel message. - ChannelFlags lnwire.FundingFlag - - // IdentityPub is the identity public key of the remote node this - // channel has been established with. - IdentityPub *btcec.PublicKey - - // Capacity is the total capacity of this channel. - Capacity btcutil.Amount - - // TotalMSatSent is the total number of milli-satoshis we've sent - // within this channel. - TotalMSatSent lnwire.MilliSatoshi - - // TotalMSatReceived is the total number of milli-satoshis we've - // received within this channel. - TotalMSatReceived lnwire.MilliSatoshi - - // InitialLocalBalance is the balance we have during the channel - // opening. When we are not the initiator, this value represents the - // push amount. - InitialLocalBalance lnwire.MilliSatoshi - - // InitialRemoteBalance is the balance they have during the channel - // opening. - InitialRemoteBalance lnwire.MilliSatoshi - - // LocalChanCfg is the channel configuration for the local node. - LocalChanCfg ChannelConfig - - // RemoteChanCfg is the channel configuration for the remote node. - RemoteChanCfg ChannelConfig - - // LocalCommitment is the current local commitment state for the local - // party. This is stored distinct from the state of the remote party - // as there are certain asymmetric parameters which affect the - // structure of each commitment. - LocalCommitment ChannelCommitment - - // RemoteCommitment is the current remote commitment state for the - // remote party. This is stored distinct from the state of the local - // party as there are certain asymmetric parameters which affect the - // structure of each commitment. - RemoteCommitment ChannelCommitment - - // RemoteCurrentRevocation is the current revocation for their - // commitment transaction. However, since this the derived public key, - // we don't yet have the private key so we aren't yet able to verify - // that it's actually in the hash chain. - RemoteCurrentRevocation *btcec.PublicKey - - // RemoteNextRevocation is the revocation key to be used for the *next* - // commitment transaction we create for the local node. Within the - // specification, this value is referred to as the - // per-commitment-point. - RemoteNextRevocation *btcec.PublicKey - - // RevocationProducer is used to generate the revocation in such a way - // that remote side might store it efficiently and have the ability to - // restore the revocation by index if needed. Current implementation of - // secret producer is shachain producer. - RevocationProducer shachain.Producer - - // RevocationStore is used to efficiently store the revocations for - // previous channels states sent to us by remote side. Current - // implementation of secret store is shachain store. - RevocationStore shachain.Store - - // FundingTxn is the transaction containing this channel's funding - // outpoint. Upon restarts, this txn will be rebroadcast if the channel - // is found to be pending. - // - // NOTE: This value will only be populated for single-funder channels - // for which we are the initiator, and that we also have the funding - // transaction for. One can check this by using the HasFundingTx() - // method on the ChanType field. - FundingTxn *wire.MsgTx - - // LocalShutdownScript is set to a pre-set script if the channel was - // opened by the local node with option_upfront_shutdown_script set. If - // the option was not set, the field is empty. - LocalShutdownScript lnwire.DeliveryAddress - - // RemoteShutdownScript is set to a pre-set script if the channel was - // opened by the remote node with option_upfront_shutdown_script set. If - // the option was not set, the field is empty. - RemoteShutdownScript lnwire.DeliveryAddress - - // ThawHeight is the height when a frozen channel once again becomes a - // normal channel. If this is zero, then there're no restrictions on - // this channel. If the value is lower than 500,000, then it's - // interpreted as a relative height, or an absolute height otherwise. - ThawHeight uint32 - - // LastWasRevoke is a boolean that determines if the last update we sent - // was a revocation (true) or a commitment signature (false). - LastWasRevoke bool - - // RevocationKeyLocator stores the KeyLocator information that we will - // need to derive the shachain root for this channel. This allows us to - // have private key isolation from lnd. - RevocationKeyLocator keychain.KeyLocator - - // confirmedScid is the confirmed ShortChannelID for a zero-conf - // channel. If the channel is unconfirmed, then this will be the - // default ShortChannelID. This is only set for zero-conf channels. - confirmedScid lnwire.ShortChannelID - - // Memo is any arbitrary information we wish to store locally about the - // channel that will be useful to our future selves. - Memo []byte - - // TapscriptRoot is an optional tapscript root used to derive the MuSig2 - // funding output. - TapscriptRoot fn.Option[chainhash.Hash] - - // CustomBlob is an optional blob that can be used to store information - // specific to a custom channel type. This information is only created - // at channel funding time, and after wards is to be considered - // immutable. - CustomBlob fn.Option[tlv.Blob] - - // Db persists channel state through the Store contract. This field - // intentionally keeps the existing name while callers still construct - // channels through the channeldb compatibility alias. The store - // interface keeps receiver methods backend independent while the KV - // implementation remains in channeldb. - Db Store - - // TODO(roasbeef): just need to store local and remote HTLC's? - - sync.RWMutex -} - -// String returns a string representation of the channel. -func (c *OpenChannel) String() string { - indexStr := "height=%v, local_htlc_index=%v, local_log_index=%v, " + - "remote_htlc_index=%v, remote_log_index=%v" - - commit := c.LocalCommitment - local := fmt.Sprintf(indexStr, commit.CommitHeight, - commit.LocalHtlcIndex, commit.LocalLogIndex, - commit.RemoteHtlcIndex, commit.RemoteLogIndex, - ) - - commit = c.RemoteCommitment - remote := fmt.Sprintf(indexStr, commit.CommitHeight, - commit.LocalHtlcIndex, commit.LocalLogIndex, - commit.RemoteHtlcIndex, commit.RemoteLogIndex, - ) - - return fmt.Sprintf("SCID=%v, status=%v, initiator=%v, pending=%v, "+ - "local commitment has %s, remote commitment has %s", - c.ShortChannelID, c.chanStatus, c.IsInitiator, c.IsPending, - local, remote, - ) -} - -// Initiator returns the ChannelParty that originally opened this channel. -func (c *OpenChannel) Initiator() lntypes.ChannelParty { - c.RLock() - defer c.RUnlock() - - if c.IsInitiator { - return lntypes.Local - } - - return lntypes.Remote -} - -// ShortChanID returns the current ShortChannelID of this channel. -func (c *OpenChannel) ShortChanID() lnwire.ShortChannelID { - c.RLock() - defer c.RUnlock() - - return c.ShortChannelID -} - -// ZeroConfRealScid returns the zero-conf channel's confirmed scid. This should -// only be called if IsZeroConf returns true. -func (c *OpenChannel) ZeroConfRealScid() lnwire.ShortChannelID { - c.RLock() - defer c.RUnlock() - - return c.confirmedScid -} - -// ZeroConfConfirmed returns whether the zero-conf channel has confirmed. This -// should only be called if IsZeroConf returns true. -func (c *OpenChannel) ZeroConfConfirmed() bool { - c.RLock() - defer c.RUnlock() - - return c.confirmedScid != hop.Source -} - -// IsZeroConf returns whether the option_zeroconf channel type was negotiated. -func (c *OpenChannel) IsZeroConf() bool { - c.RLock() - defer c.RUnlock() - - return c.ChanType.HasZeroConf() -} - -// IsOptionScidAlias returns whether the option_scid_alias channel type was -// negotiated. -func (c *OpenChannel) IsOptionScidAlias() bool { - c.RLock() - defer c.RUnlock() - - return c.ChanType.HasScidAliasChan() -} - -// NegotiatedAliasFeature returns whether the option-scid-alias feature bit was -// negotiated. -func (c *OpenChannel) NegotiatedAliasFeature() bool { - c.RLock() - defer c.RUnlock() - - return c.ChanType.HasScidAliasFeature() -} - -// ChanStatus returns the current ChannelStatus of this channel. -func (c *OpenChannel) ChanStatus() ChannelStatus { - c.RLock() - defer c.RUnlock() - - return c.chanStatus -} - -// ChannelStatusForStore returns the in-memory channel status without taking -// the channel mutex. -// -// TODO(chanstate): remove the ForStore accessors once the KV-backed store code -// has moved out of channeldb or no longer needs direct access to OpenChannel's -// private persistence fields. -// -// NOTE: This is a preliminary migration hook for KV-backed store code that -// still lives in channeldb during this refactor. Callers are responsible for -// synchronization. Normal callers should use ChanStatus. -func (c *OpenChannel) ChannelStatusForStore() ChannelStatus { - return c.chanStatus -} - -// SetChannelStatusForStore updates the in-memory channel status without taking -// the channel mutex. -// -// NOTE: This is a preliminary migration hook for KV-backed store code that -// still lives in channeldb during this refactor. Callers are responsible for -// synchronization. Normal callers should use ApplyChanStatus or -// ClearChanStatus when the status change must be persisted. -func (c *OpenChannel) SetChannelStatusForStore(status ChannelStatus) { - c.chanStatus = status -} - -// ApplyChanStatus allows the caller to modify the internal channel state in a -// thead-safe manner. -func (c *OpenChannel) ApplyChanStatus(status ChannelStatus) error { - c.Lock() - defer c.Unlock() - - return c.Db.ApplyChannelStatus(c, status) -} - -// ClearChanStatus allows the caller to clear a particular channel status from -// the primary channel status bit field. After this method returns, a call to -// HasChanStatus(status) should return false. -func (c *OpenChannel) ClearChanStatus(status ChannelStatus) error { - c.Lock() - defer c.Unlock() - - return c.Db.ClearChannelStatus(c, status) -} - -// HasChanStatus returns true if the internal bitfield channel status of the -// target channel has the specified status bit set. -func (c *OpenChannel) HasChanStatus(status ChannelStatus) bool { - c.RLock() - defer c.RUnlock() - - return c.hasChanStatus(status) -} - -func (c *OpenChannel) hasChanStatus(status ChannelStatus) bool { - // Special case ChanStatusDefualt since it isn't actually flag, but a - // particular combination (or lack-there-of) of flags. - if status == ChanStatusDefault { - return c.chanStatus == ChanStatusDefault - } - - return c.chanStatus&status == status -} - -// HasChanStatusForStore returns true if the internal bitfield channel status -// has the specified status bit set, without taking the channel mutex. -// -// NOTE: This is a preliminary migration hook for KV-backed store code that -// still lives in channeldb during this refactor. Callers are responsible for -// synchronization. Normal callers should use HasChanStatus. -func (c *OpenChannel) HasChanStatusForStore(status ChannelStatus) bool { - return c.hasChanStatus(status) -} - -// ConfirmedScidForStore returns the in-memory confirmed SCID without taking -// the channel mutex. -// -// NOTE: This is a preliminary migration hook for KV-backed store code that -// still lives in channeldb during this refactor. Callers are responsible for -// synchronization. Normal callers should use ZeroConfRealScid. -func (c *OpenChannel) ConfirmedScidForStore() lnwire.ShortChannelID { - return c.confirmedScid -} - -// SetConfirmedScidForStore updates the in-memory confirmed SCID without taking -// the channel mutex. -// -// NOTE: This is a preliminary migration hook for KV-backed store code that -// still lives in channeldb during this refactor. Callers are responsible for -// synchronization. -func (c *OpenChannel) SetConfirmedScidForStore(scid lnwire.ShortChannelID) { - c.confirmedScid = scid -} - -// BroadcastHeight returns the height at which the funding tx was broadcast. -func (c *OpenChannel) BroadcastHeight() uint32 { - c.RLock() - defer c.RUnlock() - - return c.FundingBroadcastHeight -} - -// SetBroadcastHeight sets the FundingBroadcastHeight. -func (c *OpenChannel) SetBroadcastHeight(height uint32) { - c.Lock() - defer c.Unlock() - - c.FundingBroadcastHeight = height -} - -// Refresh updates the in-memory channel state using the latest state observed -// on disk. -func (c *OpenChannel) Refresh() error { - c.Lock() - defer c.Unlock() - - return c.Db.RefreshChannel(c) -} - -// MarkConfirmationHeight updates the channel's confirmation height once the -// channel opening transaction receives one confirmation. -func (c *OpenChannel) MarkConfirmationHeight(height uint32) error { - c.Lock() - defer c.Unlock() - - if err := c.Db.MarkChannelConfirmationHeight(c, height); err != nil { - return err - } - - c.ConfirmationHeight = height - - return nil -} - -// ResetCloseConfirmationHeight clears the channel's close confirmation height -// when the spending transaction is reorged out. -func (c *OpenChannel) ResetCloseConfirmationHeight() error { - return c.MarkCloseConfirmationHeight(fn.None[uint32]()) -} - -// MarkCloseConfirmationHeight updates the channel's close confirmation height -// when the closing transaction is first detected in a block (spend height). -func (c *OpenChannel) MarkCloseConfirmationHeight( - height fn.Option[uint32]) error { - - c.Lock() - defer c.Unlock() - - err := c.Db.MarkChannelCloseConfirmationHeight(c, height) - if err != nil { - return err - } - - c.CloseConfirmationHeight = height - - return nil -} - -// MarkAsOpen marks a channel as fully open given a locator that uniquely -// describes its location within the chain. -func (c *OpenChannel) MarkAsOpen(openLoc lnwire.ShortChannelID) error { - c.Lock() - defer c.Unlock() - - if err := c.Db.MarkChannelOpen(c, openLoc); err != nil { - return err - } - - c.IsPending = false - c.ShortChannelID = openLoc - - return nil -} - -// MarkRealScid marks the zero-conf channel's confirmed ShortChannelID. This -// should only be done if IsZeroConf returns true. -func (c *OpenChannel) MarkRealScid(realScid lnwire.ShortChannelID) error { - c.Lock() - defer c.Unlock() - - if err := c.Db.MarkChannelRealScid(c, realScid); err != nil { - return err - } - - c.confirmedScid = realScid - - return nil -} - -// MarkScidAliasNegotiated adds ScidAliasFeatureBit to ChanType in-memory and -// in the database. -func (c *OpenChannel) MarkScidAliasNegotiated() error { - c.Lock() - defer c.Unlock() - - if err := c.Db.MarkChannelScidAliasNegotiated(c); err != nil { - return err - } - - c.ChanType |= ScidAliasFeatureBit - - return nil -} - -// MarkDataLoss marks sets the channel status to LocalDataLoss and stores the -// passed commitPoint for use to retrieve funds in case the remote force closes -// the channel. -func (c *OpenChannel) MarkDataLoss(commitPoint *btcec.PublicKey) error { - c.Lock() - defer c.Unlock() - - return c.Db.MarkChannelDataLoss(c, commitPoint) -} - -// DataLossCommitPoint retrieves the stored commit point set during -// MarkDataLoss. If not found ErrNoCommitPoint is returned. -func (c *OpenChannel) DataLossCommitPoint() (*btcec.PublicKey, error) { - return c.Db.FetchChannelDataLossCommitPoint(c) -} - -// MarkBorked marks the event when the channel as reached an irreconcilable -// state, such as a channel breach or state desynchronization. Borked channels -// should never be added to the switch. -func (c *OpenChannel) MarkBorked() error { - c.Lock() - defer c.Unlock() - - return c.Db.MarkChannelBorked(c) -} - -// SecondCommitmentPoint returns the second per-commitment-point for use in the -// channel_ready message. -func (c *OpenChannel) SecondCommitmentPoint() (*btcec.PublicKey, error) { - c.RLock() - defer c.RUnlock() - - // Since we start at commitment height = 0, the second per commitment - // point is actually at the 1st index. - revocation, err := c.RevocationProducer.AtIndex(1) - if err != nil { - return nil, err - } - - return input.ComputeCommitmentPoint(revocation[:]), nil -} - -// ChanSyncMsg returns the ChannelReestablish message that should be sent upon -// reconnection with the remote peer that we're maintaining this channel with. -// The information contained within this message is necessary to re-sync our -// commitment chains in the case of a last or only partially processed message. -// When the remote party receives this message one of three things may happen: -// -// 1. We're fully synced and no messages need to be sent. -// 2. We didn't get the last CommitSig message they sent, so they'll re-send -// it. -// 3. We didn't get the last RevokeAndAck message they sent, so they'll -// re-send it. -// -// If this is a restored channel, having status ChanStatusRestored, then we'll -// modify our typical chan sync message to ensure they force close even if -// we're on the very first state. -func (c *OpenChannel) ChanSyncMsg() (*lnwire.ChannelReestablish, error) { - c.Lock() - defer c.Unlock() - - // The remote commitment height that we'll send in the - // ChannelReestablish message is our current commitment height plus - // one. If the receiver thinks that our commitment height is actually - // *equal* to this value, then they'll re-send the last commitment that - // they sent but we never fully processed. - localHeight := c.LocalCommitment.CommitHeight - nextLocalCommitHeight := localHeight + 1 - - // The second value we'll send is the height of the remote commitment - // from our PoV. If the receiver thinks that their height is actually - // *one plus* this value, then they'll re-send their last revocation. - remoteChainTipHeight := c.RemoteCommitment.CommitHeight - - // If this channel has undergone a commitment update, then in order to - // prove to the remote party our knowledge of their prior commitment - // state, we'll also send over the last commitment secret that the - // remote party sent. - var lastCommitSecret [32]byte - if remoteChainTipHeight != 0 { - remoteSecret, err := c.RevocationStore.LookUp( - remoteChainTipHeight - 1, - ) - if err != nil { - return nil, err - } - lastCommitSecret = [32]byte(*remoteSecret) - } - - // Additionally, we'll send over the current unrevoked commitment on - // our local commitment transaction. - currentCommitSecret, err := c.RevocationProducer.AtIndex( - localHeight, - ) - if err != nil { - return nil, err - } - - // If we've restored this channel, then we'll purposefully give them an - // invalid LocalUnrevokedCommitPoint so they'll force close the channel - // allowing us to sweep our funds. - if c.hasChanStatus(ChanStatusRestored) { - currentCommitSecret[0] ^= 1 - - // If this is a tweakless channel, then we'll purposefully send - // a next local height taht's invalid to trigger a force close - // on their end. We do this as tweakless channels don't require - // that the commitment point is valid, only that it's present. - if c.ChanType.IsTweakless() { - nextLocalCommitHeight = 0 - } - } - - // If this is a taproot channel, then we'll need to generate our next - // verification nonce to send to the remote party. They'll use this to - // sign the next update to our commitment transaction. - var ( - nextTaprootNonce lnwire.OptMusig2NonceTLV - nextLocalNonces lnwire.OptLocalNonces - ) - if c.ChanType.IsTaproot() { - taprootRevProducer, err := DeriveMusig2Shachain( - c.RevocationProducer, - ) - if err != nil { - return nil, err - } - - nextNonce, err := NewMusigVerificationNonce( - c.LocalChanCfg.MultiSigKey.PubKey, - nextLocalCommitHeight, taprootRevProducer, - ) - if err != nil { - return nil, fmt.Errorf("unable to gen next "+ - "nonce: %w", err) - } - - fundingTxid := c.FundingOutpoint.Hash - nonce := nextNonce.PubNonce - - // Final taproot channels use the map-based LocalNonces - // field keyed by funding TXID. Staging channels use the - // legacy single LocalNonce field. - if c.ChanType.IsTaprootFinal() { - noncesMap := make(map[chainhash.Hash]lnwire.Musig2Nonce) - noncesMap[fundingTxid] = nonce - nextLocalNonces = lnwire.SomeLocalNonces( - lnwire.LocalNoncesData{NoncesMap: noncesMap}, - ) - } else { - nextTaprootNonce = lnwire.SomeMusig2Nonce(nonce) - } - } - - return &lnwire.ChannelReestablish{ - ChanID: lnwire.NewChanIDFromOutPoint( - c.FundingOutpoint, - ), - NextLocalCommitHeight: nextLocalCommitHeight, - RemoteCommitTailHeight: remoteChainTipHeight, - LastRemoteCommitSecret: lastCommitSecret, - LocalUnrevokedCommitPoint: input.ComputeCommitmentPoint( - currentCommitSecret[:], - ), - LocalNonce: nextTaprootNonce, - LocalNonces: nextLocalNonces, - }, nil -} - -// MarkShutdownSent serialises and persist the given ShutdownInfo for this -// channel. Persisting this info represents the fact that we have sent the -// Shutdown message to the remote side and hence that we should re-transmit the -// same Shutdown message on re-establish. -func (c *OpenChannel) MarkShutdownSent(info *ShutdownInfo) error { - c.Lock() - defer c.Unlock() - - return c.Db.StoreChannelShutdownInfo(c, info) -} - -// ShutdownInfo decodes the shutdown info stored for this channel and returns -// the result. If no shutdown info has been persisted for this channel then the -// ErrNoShutdownInfo error is returned. -func (c *OpenChannel) ShutdownInfo() (fn.Option[ShutdownInfo], error) { - c.RLock() - defer c.RUnlock() - - return c.Db.FetchChannelShutdownInfo(c) -} - -// MarkCommitmentBroadcasted marks the channel as a commitment transaction has -// been broadcast, either our own or the remote, and we should watch the chain -// for it to confirm before taking any further action. It takes as argument the -// closing tx _we believe_ will appear in the chain. This is only used to -// republish this tx at startup to ensure propagation, and we should still -// handle the case where a different tx actually hits the chain. -func (c *OpenChannel) MarkCommitmentBroadcasted(closeTx *wire.MsgTx, - closer lntypes.ChannelParty) error { - - return c.Db.MarkChannelCommitmentBroadcasted(c, closeTx, closer) -} - -// MarkCoopBroadcasted marks the channel to indicate that a cooperative close -// transaction has been broadcast, either our own or the remote, and that we -// should watch the chain for it to confirm before taking further action. It -// takes as argument a cooperative close tx that could appear on chain, and -// should be rebroadcast upon startup. This is only used to republish and -// ensure propagation, and we should still handle the case where a different tx -// actually hits the chain. -func (c *OpenChannel) MarkCoopBroadcasted(closeTx *wire.MsgTx, - closer lntypes.ChannelParty) error { - - return c.Db.MarkChannelCoopBroadcasted(c, closeTx, closer) -} - -// BroadcastedCommitment retrieves the stored unilateral closing tx set during -// MarkCommitmentBroadcasted. If not found ErrNoCloseTx is returned. -func (c *OpenChannel) BroadcastedCommitment() (*wire.MsgTx, error) { - return c.Db.FetchChannelBroadcastedCommitment(c) -} - -// BroadcastedCooperative retrieves the stored cooperative closing tx set during -// MarkCoopBroadcasted. If not found ErrNoCloseTx is returned. -func (c *OpenChannel) BroadcastedCooperative() (*wire.MsgTx, error) { - return c.Db.FetchChannelBroadcastedCooperative(c) -} - -// SyncPending writes the contents of the channel to the database while it's in -// the pending (waiting for funding confirmation) state. The IsPending flag -// will be set to true. When the channel's funding transaction is confirmed, -// the channel should be marked as "open" and the IsPending flag set to false. -// Note that this function also creates a LinkNode relationship between this -// newly created channel and a new LinkNode instance. This allows listing all -// channels in the database globally, or according to the LinkNode they were -// created with. -// -// TODO(roasbeef): addr param should eventually be an lnwire.NetAddress type -// that includes service bits. -func (c *OpenChannel) SyncPending(addr net.Addr, pendingHeight uint32) error { - c.Lock() - defer c.Unlock() - - return c.Db.SyncPendingChannel(c, addr, pendingHeight) -} - -// UpdateCommitment updates the local commitment state. It locks in the pending -// local updates that were received by us from the remote party. The commitment -// state completely describes the balance state at this point in the commitment -// chain. In addition to that, it persists all the remote log updates that we -// have acked, but not signed a remote commitment for yet. These need to be -// persisted to be able to produce a valid commit signature if a restart would -// occur. This method its to be called when we revoke our prior commitment -// state. -// -// A map is returned of all the htlc resolutions that were locked in this -// commitment. Keys correspond to htlc indices and values indicate whether the -// htlc was settled or failed. -func (c *OpenChannel) UpdateCommitment(newCommitment *ChannelCommitment, - unsignedAckedUpdates []LogUpdate) (map[uint64]bool, error) { - - c.Lock() - defer c.Unlock() - - // If this is a restored channel, then we want to avoid mutating the - // state as all, as it's impossible to do so in a protocol compliant - // manner. - if c.hasChanStatus(ChanStatusRestored) { - return nil, ErrNoRestoredChannelMutation - } - - finalHtlcs, err := c.Db.UpdateChannelCommitment( - c, newCommitment, unsignedAckedUpdates, - ) - if err != nil { - return nil, err - } - - c.LocalCommitment = *newCommitment - - return finalHtlcs, nil -} - -// ActiveHtlcs returns a slice of HTLC's which are currently active on *both* -// commitment transactions. -func (c *OpenChannel) ActiveHtlcs() []HTLC { - c.RLock() - defer c.RUnlock() - - // htlcKey uniquely identifies an HTLC within the channel state by its - // channel-level HTLC index and the direction of the offer. This is used - // to match the same HTLC across the local and remote commitment - // snapshots. - type htlcKey struct { - index uint64 - incoming bool - } - - // We'll only return HTLC's that are locked into *both* commitment - // transactions. So we'll iterate through their set of HTLC's to note - // which ones are present on their commitment. - // - // HTLC identity is defined by the channel-level HTLC index plus the - // direction of the offer. The onion blob is routing payload data and - // can be duplicated by buggy or malicious senders, so it is not a - // robust key for matching the same HTLC across commitment snapshots. - remoteHtlcs := make(map[htlcKey]struct{}) - for _, htlc := range c.RemoteCommitment.Htlcs { - log.Tracef("RemoteCommitment has htlc: id=%v, update=%v "+ - "incoming=%v", htlc.HtlcIndex, htlc.LogIndex, - htlc.Incoming) - - remoteHtlcs[htlcKey{ - index: htlc.HtlcIndex, - incoming: htlc.Incoming, - }] = struct{}{} - } - - // Now that we know which HTLC's they have, we'll only mark the HTLC's - // as active if *we* know them as well. - activeHtlcs := make([]HTLC, 0, len(remoteHtlcs)) - for _, htlc := range c.LocalCommitment.Htlcs { - log.Tracef("LocalCommitment has htlc: id=%v, update=%v "+ - "incoming=%v", htlc.HtlcIndex, htlc.LogIndex, - htlc.Incoming) - - _, ok := remoteHtlcs[htlcKey{ - index: htlc.HtlcIndex, - incoming: htlc.Incoming, - }] - if !ok { - log.Tracef("Skipped htlc due to identity mismatch: "+ - "id=%v, update=%v incoming=%v", - htlc.HtlcIndex, htlc.LogIndex, htlc.Incoming) - - continue - } - - activeHtlcs = append(activeHtlcs, htlc) - } - - return activeHtlcs -} - -// AppendRemoteCommitChain appends a new CommitDiff to the end of the -// commitment chain for the remote party. This method is to be used once we -// have prepared a new commitment state for the remote party, but before we -// transmit it to the remote party. The contents of the argument should be -// sufficient to retransmit the updates and signature needed to reconstruct the -// state in full, in the case that we need to retransmit. -func (c *OpenChannel) AppendRemoteCommitChain(diff *CommitDiff) error { - c.Lock() - defer c.Unlock() - - // If this is a restored channel, then we want to avoid mutating the - // state at all, as it's impossible to do so in a protocol compliant - // manner. - if c.hasChanStatus(ChanStatusRestored) { - return ErrNoRestoredChannelMutation - } - - return c.Db.AppendRemoteCommitChain(c, diff) -} - -// RemoteCommitChainTip returns the "tip" of the current remote commitment -// chain. This value will be non-nil iff, we've created a new commitment for -// the remote party that they haven't yet ACK'd. In this case, their commitment -// chain will have a length of two: their current unrevoked commitment, and -// this new pending commitment. Once they revoked their prior state, we'll swap -// these pointers, causing the tip and the tail to point to the same entry. -func (c *OpenChannel) RemoteCommitChainTip() (*CommitDiff, error) { - return c.Db.RemoteCommitChainTip(c) -} - -// UnsignedAckedUpdates retrieves the persisted unsigned acked remote log -// updates that still need to be signed for. -func (c *OpenChannel) UnsignedAckedUpdates() ([]LogUpdate, error) { - return c.Db.UnsignedAckedUpdates(c) -} - -// RemoteUnsignedLocalUpdates retrieves the persisted, unsigned local log -// updates that the remote still needs to sign for. -func (c *OpenChannel) RemoteUnsignedLocalUpdates() ([]LogUpdate, error) { - return c.Db.RemoteUnsignedLocalUpdates(c) -} - -// InsertNextRevocation inserts the _next_ commitment point (revocation) into -// the database, and also modifies the internal RemoteNextRevocation attribute -// to point to the passed key. This method is to be using during final channel -// set up, _after_ the channel has been fully confirmed. -// -// NOTE: If this method isn't called, then the target channel won't be able to -// propose new states for the commitment state of the remote party. -func (c *OpenChannel) InsertNextRevocation(revKey *btcec.PublicKey) error { - c.Lock() - defer c.Unlock() - - return c.Db.InsertNextRevocation(c, revKey) -} - -// AdvanceCommitChainTail records the new state transition within an on-disk -// append-only log which records all state transitions by the remote peer. In -// the case of an uncooperative broadcast of a prior state by the remote peer, -// this log can be consulted in order to reconstruct the state needed to -// rectify the situation. This method will add the current commitment for the -// remote party to the revocation log, and promote the current pending -// commitment to the current remote commitment. The updates parameter is the -// set of local updates that the peer still needs to send us a signature for. -// We store this set of updates in case we go down. -func (c *OpenChannel) AdvanceCommitChainTail(fwdPkg *FwdPkg, - updates []LogUpdate, ourOutputIndex, theirOutputIndex uint32) error { - - c.Lock() - defer c.Unlock() - - // If this is a restored channel, then we want to avoid mutating the - // state at all, as it's impossible to do so in a protocol compliant - // manner. - if c.hasChanStatus(ChanStatusRestored) { - return ErrNoRestoredChannelMutation - } - - return c.Db.AdvanceCommitChainTail( - c, fwdPkg, updates, ourOutputIndex, theirOutputIndex, - ) -} - -// NextLocalHtlcIndex returns the next unallocated local htlc index. To ensure -// this always returns the next index that has been not been allocated, this -// will first try to examine any pending commitments, before falling back to the -// last locked-in remote commitment. -func (c *OpenChannel) NextLocalHtlcIndex() (uint64, error) { - // First, load the most recent commit diff that we initiated for the - // remote party. If no pending commit is found, this is not treated as - // a critical error, since we can always fall back. - pendingRemoteCommit, err := c.RemoteCommitChainTip() - if err != nil && !errors.Is(err, ErrNoPendingCommit) { - return 0, err - } - - // If a pending commit was found, its local htlc index will be at least - // as large as the one on our local commitment. - if pendingRemoteCommit != nil { - return pendingRemoteCommit.Commitment.LocalHtlcIndex, nil - } - - // Otherwise, fallback to using the local htlc index of their - // commitment. - return c.RemoteCommitment.LocalHtlcIndex, nil -} - -// LoadFwdPkgs scans the forwarding log for any packages that haven't been -// processed, and returns their deserialized log updates in map indexed by the -// remote commitment height at which the updates were locked in. -func (c *OpenChannel) LoadFwdPkgs() ([]*FwdPkg, error) { - c.RLock() - defer c.RUnlock() - - return c.Db.LoadFwdPkgs(c) -} - -// AckAddHtlcs updates the AckAddFilter containing any of the provided AddRefs -// indicating that a response to this Add has been committed to the remote -// party. Doing so will prevent these Add HTLCs from being reforwarded -// internally. -func (c *OpenChannel) AckAddHtlcs(addRefs ...AddRef) error { - c.Lock() - defer c.Unlock() - - return c.Db.AckAddHtlcs(c, addRefs...) -} - -// AckSettleFails updates the SettleFailFilter containing any of the provided -// SettleFailRefs, indicating that the response has been delivered to the -// incoming link, corresponding to a particular AddRef. Doing so will prevent -// the responses from being retransmitted internally. -func (c *OpenChannel) AckSettleFails(settleFailRefs ...SettleFailRef) error { - c.Lock() - defer c.Unlock() - - return c.Db.AckSettleFails(c, settleFailRefs...) -} - -// SetFwdFilter atomically sets the forwarding filter for the forwarding package -// identified by `height`. -func (c *OpenChannel) SetFwdFilter(height uint64, fwdFilter *PkgFilter) error { - c.Lock() - defer c.Unlock() - - return c.Db.SetFwdFilter(c, height, fwdFilter) -} - -// RemoveFwdPkgs atomically removes forwarding packages specified by the -// remote commitment heights. If one of the intermediate RemovePkg calls fails, -// then the later packages won't be removed. -// -// NOTE: This method should only be called on packages marked FwdStateCompleted. -func (c *OpenChannel) RemoveFwdPkgs(heights ...uint64) error { - c.Lock() - defer c.Unlock() - - return c.Db.RemoveFwdPkgs(c, heights...) -} - -// CommitmentHeight returns the current commitment height. The commitment -// height represents the number of updates to the commitment state to date. -// This value is always monotonically increasing. This method is provided in -// order to allow multiple instances of a particular open channel to obtain a -// consistent view of the number of channel updates to date. -func (c *OpenChannel) CommitmentHeight() (uint64, error) { - c.RLock() - defer c.RUnlock() - - return c.Db.CommitmentHeight(c) -} - -// FindPreviousState scans through the append-only log in an attempt to recover -// the previous channel state indicated by the update number. This method is -// intended to be used for obtaining the relevant data needed to claim all -// funds rightfully spendable in the case of an on-chain broadcast of the -// commitment transaction. -func (c *OpenChannel) FindPreviousState( - updateNum uint64) (*RevocationLog, *ChannelCommitment, error) { - - c.RLock() - defer c.RUnlock() - - return c.Db.FindPreviousState(c, updateNum) -} - -// CloseChannel closes a previously active Lightning channel. Closing a -// channel entails persisting a record of the close while either purging the -// nested per-channel state inline (synchronous backends like bbolt and etcd) -// or skipping the cascading delete on tombstone-enabled backends, where the -// outpoint-index flip to outpointClosed is the authoritative marker. The -// compact summary written to closedChannelBucket and the historical record -// under historicalChannelBucket are populated identically across both paths, -// so historical reads remain uniform regardless of backend. The optional set -// of channel statuses is OR'd into the chanStatus written to the historical -// bucket and is used to record close initiators. -func (c *OpenChannel) CloseChannel(summary *ChannelCloseSummary, - statuses ...ChannelStatus) error { - - c.Lock() - defer c.Unlock() - - return c.Db.CloseChannel(c, summary, statuses...) -} - -// Snapshot returns a read-only snapshot of the current channel state. This -// snapshot includes information concerning the current settled balance within -// the channel, metadata detailing total flows, and any outstanding HTLCs. -func (c *OpenChannel) Snapshot() *ChannelSnapshot { - c.RLock() - defer c.RUnlock() - - localCommit := c.LocalCommitment - snapshot := &ChannelSnapshot{ - RemoteIdentity: *c.IdentityPub, - ChannelPoint: c.FundingOutpoint, - Capacity: c.Capacity, - TotalMSatSent: c.TotalMSatSent, - TotalMSatReceived: c.TotalMSatReceived, - ChainHash: c.ChainHash, - ChannelCommitment: ChannelCommitment{ - LocalBalance: localCommit.LocalBalance, - RemoteBalance: localCommit.RemoteBalance, - CommitHeight: localCommit.CommitHeight, - CommitFee: localCommit.CommitFee, - }, - } - - localCommit.CustomBlob.WhenSome(func(blob tlv.Blob) { - blobCopy := make([]byte, len(blob)) - copy(blobCopy, blob) - - snapshot.ChannelCommitment.CustomBlob = fn.Some(blobCopy) - }) - - // Copy over the current set of HTLCs to ensure the caller can't mutate - // our internal state. - snapshot.Htlcs = make([]HTLC, len(localCommit.Htlcs)) - for i, h := range localCommit.Htlcs { - snapshot.Htlcs[i] = h.Copy() - } - - return snapshot -} - -// Copy returns a deep copy of the channel state. -func (c *OpenChannel) Copy() *OpenChannel { - c.RLock() - defer c.RUnlock() - - clone := &OpenChannel{ - ChanType: c.ChanType, - ChainHash: c.ChainHash, - FundingOutpoint: c.FundingOutpoint, - ShortChannelID: c.ShortChannelID, - IsPending: c.IsPending, - IsInitiator: c.IsInitiator, - chanStatus: c.chanStatus, - FundingBroadcastHeight: c.FundingBroadcastHeight, - ConfirmationHeight: c.ConfirmationHeight, - CloseConfirmationHeight: c.CloseConfirmationHeight, - NumConfsRequired: c.NumConfsRequired, - ChannelFlags: c.ChannelFlags, - IdentityPub: c.IdentityPub, - Capacity: c.Capacity, - TotalMSatSent: c.TotalMSatSent, - TotalMSatReceived: c.TotalMSatReceived, - InitialLocalBalance: c.InitialLocalBalance, - InitialRemoteBalance: c.InitialRemoteBalance, - LocalChanCfg: c.LocalChanCfg, - RemoteChanCfg: c.RemoteChanCfg, - LocalCommitment: c.LocalCommitment.Copy(), - RemoteCommitment: c.RemoteCommitment.Copy(), - RemoteCurrentRevocation: c.RemoteCurrentRevocation, - RemoteNextRevocation: c.RemoteNextRevocation, - RevocationProducer: c.RevocationProducer, - RevocationStore: c.RevocationStore, - ThawHeight: c.ThawHeight, - LastWasRevoke: c.LastWasRevoke, - RevocationKeyLocator: c.RevocationKeyLocator, - confirmedScid: c.confirmedScid, - TapscriptRoot: c.TapscriptRoot, - Db: c.Db, - } - - if c.FundingTxn != nil { - clone.FundingTxn = c.FundingTxn.Copy() - } - - if len(c.LocalShutdownScript) > 0 { - clone.LocalShutdownScript = make( - lnwire.DeliveryAddress, - len(c.LocalShutdownScript), - ) - copy(clone.LocalShutdownScript, c.LocalShutdownScript) - } - if len(c.RemoteShutdownScript) > 0 { - clone.RemoteShutdownScript = make( - lnwire.DeliveryAddress, - len(c.RemoteShutdownScript), - ) - copy(clone.RemoteShutdownScript, c.RemoteShutdownScript) - } - - if len(c.Memo) > 0 { - clone.Memo = make([]byte, len(c.Memo)) - copy(clone.Memo, c.Memo) - } - - c.CustomBlob.WhenSome(func(blob tlv.Blob) { - blobCopy := make([]byte, len(blob)) - copy(blobCopy, blob) - clone.CustomBlob = fn.Some(blobCopy) - }) - - return clone -} - -// LatestCommitments returns the two latest commitments for both the local and -// remote party. These commitments are read from disk to ensure that only the -// latest fully committed state is returned. The first commitment returned is -// the local commitment, and the second returned is the remote commitment. -func (c *OpenChannel) LatestCommitments() (*ChannelCommitment, - *ChannelCommitment, error) { - - return c.Db.LatestCommitments(c) -} - -// RemoteRevocationStore returns the most up to date commitment version of the -// revocation storage tree for the remote party. This method can be used when -// acting on a possible contract breach to ensure, that the caller has the most -// up to date information required to deliver justice. -func (c *OpenChannel) RemoteRevocationStore() (shachain.Store, error) { - return c.Db.RemoteRevocationStore(c) -} - -// AbsoluteThawHeight determines a frozen channel's absolute thaw height. If the -// channel is not frozen, then 0 is returned. -func (c *OpenChannel) AbsoluteThawHeight() (uint32, error) { - // Only frozen channels have a thaw height. - if !c.ChanType.IsFrozen() && !c.ChanType.HasLeaseExpiration() { - return 0, nil - } - - // If the channel has the frozen bit set and it's thaw height is below - // the absolute threshold, then it's interpreted as a relative height to - // the chain's current height. - if c.ChanType.IsFrozen() && c.ThawHeight < AbsoluteThawHeightThreshold { - // We'll only known of the channel's short ID once it's - // confirmed. - if c.IsPending { - return 0, errors.New("cannot use relative thaw " + - "height for unconfirmed channel") - } - - // For non-zero-conf channels, this is the base height to use. - blockHeightBase := c.ShortChannelID.BlockHeight - - // If this is a zero-conf channel, the ShortChannelID will be - // an alias. - if c.IsZeroConf() { - if !c.ZeroConfConfirmed() { - return 0, errors.New("cannot use relative " + - "height for unconfirmed zero-conf " + - "channel") - } - - // Use the confirmed SCID's BlockHeight. - blockHeightBase = c.confirmedScid.BlockHeight - } - - return blockHeightBase + c.ThawHeight, nil - } - - return c.ThawHeight, nil -} - -// DeriveHeightHint derives the block height for the channel opening. -func (c *OpenChannel) DeriveHeightHint() uint32 { - // As a height hint, we'll try to use the opening height, but if the - // channel isn't yet open, then we'll use the height it was broadcast - // at. This may be an unconfirmed zero-conf channel. - heightHint := c.ShortChanID().BlockHeight - if heightHint == 0 { - heightHint = c.BroadcastHeight() - } - - // Since no zero-conf state is stored in a channel backup, the below - // logic will not be triggered for restored, zero-conf channels. Set - // the height hint for zero-conf channels. - if c.IsZeroConf() { - if c.ZeroConfConfirmed() { - // If the zero-conf channel is confirmed, we'll use the - // confirmed SCID's block height. - heightHint = c.ZeroConfRealScid().BlockHeight - } else { - // The zero-conf channel is unconfirmed. We'll need to - // use the FundingBroadcastHeight. - heightHint = c.BroadcastHeight() - } - } - - return heightHint -} diff --git a/chanstate/open_channel_test.go b/chanstate/open_channel_test.go deleted file mode 100644 index ace3e2c04..000000000 --- a/chanstate/open_channel_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package chanstate - -import ( - "testing" - - "github.com/lightningnetwork/lnd/lnwire" - "github.com/stretchr/testify/require" -) - -// TestActiveHtlcsMatchesByHTLCIdentity asserts that ActiveHtlcs matches HTLCs -// by their channel identity, not by their onion blob. Onion blobs are routing -// payload data and can be duplicated, while the HTLC index plus direction -// identifies an offered HTLC within the channel state. -func TestActiveHtlcsMatchesByHTLCIdentity(t *testing.T) { - t.Parallel() - - var onionBlob [lnwire.OnionPacketSize]byte - onionBlob[0] = 1 - - matchingHTLC := HTLC{ - HtlcIndex: 7, - LogIndex: 10, - Incoming: false, - OnionBlob: onionBlob, - } - duplicateOnionHTLC := HTLC{ - HtlcIndex: 8, - LogIndex: 11, - Incoming: false, - OnionBlob: onionBlob, - } - oppositeDirectionHTLC := HTLC{ - HtlcIndex: 7, - LogIndex: 12, - Incoming: true, - OnionBlob: onionBlob, - } - - channel := &OpenChannel{ - LocalCommitment: ChannelCommitment{ - Htlcs: []HTLC{ - matchingHTLC, - duplicateOnionHTLC, - oppositeDirectionHTLC, - }, - }, - RemoteCommitment: ChannelCommitment{ - Htlcs: []HTLC{ - matchingHTLC, - }, - }, - } - - activeHtlcs := channel.ActiveHtlcs() - require.Len(t, activeHtlcs, 1) - require.Equal(t, matchingHTLC.HtlcIndex, activeHtlcs[0].HtlcIndex) - require.Equal(t, matchingHTLC.Incoming, activeHtlcs[0].Incoming) -} diff --git a/chanstate/revocation_log.go b/chanstate/revocation_log.go deleted file mode 100644 index 0f1635e34..000000000 --- a/chanstate/revocation_log.go +++ /dev/null @@ -1,262 +0,0 @@ -package chanstate - -import ( - "bytes" - "io" - "math" - - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/tlv" -) - -const ( - // OutputIndexEmpty is used when the output index doesn't exist. - OutputIndexEmpty = math.MaxUint16 -) - -type ( - // BigSizeAmount is a type alias for a TLV record of a btcutil.Amount. - BigSizeAmount = tlv.BigSizeT[btcutil.Amount] - - // BigSizeMilliSatoshi is a type alias for a TLV record of a - // lnwire.MilliSatoshi. - BigSizeMilliSatoshi = tlv.BigSizeT[lnwire.MilliSatoshi] -) - -// SparsePayHash is a type alias for a 32 byte array, which when serialized is -// able to save some space by not including an empty payment hash on disk. -type SparsePayHash [32]byte - -// NewSparsePayHash creates a new SparsePayHash from a 32 byte array. -func NewSparsePayHash(rHash [32]byte) SparsePayHash { - return SparsePayHash(rHash) -} - -// Record returns a tlv record for the SparsePayHash. -func (s *SparsePayHash) Record() tlv.Record { - // We use a zero for the type here, as this'll be used along with the - // RecordT type. - return tlv.MakeDynamicRecord( - 0, s, s.hashLen, - sparseHashEncoder, sparseHashDecoder, - ) -} - -// hashLen is used by MakeDynamicRecord to return the size of the RHash. -// -// NOTE: for zero hash, we return a length 0. -func (s *SparsePayHash) hashLen() uint64 { - if bytes.Equal(s[:], lntypes.ZeroHash[:]) { - return 0 - } - - return 32 -} - -// sparseHashEncoder is the customized encoder which skips encoding the empty -// hash. -func sparseHashEncoder(w io.Writer, val interface{}, buf *[8]byte) error { - v, ok := val.(*SparsePayHash) - if !ok { - return tlv.NewTypeForEncodingErr(val, "SparsePayHash") - } - - // If the value is an empty hash, we will skip encoding it. - if bytes.Equal(v[:], lntypes.ZeroHash[:]) { - return nil - } - - vArray := (*[32]byte)(v) - - return tlv.EBytes32(w, vArray, buf) -} - -// sparseHashDecoder is the customized decoder which skips decoding the empty -// hash. -func sparseHashDecoder(r io.Reader, val interface{}, buf *[8]byte, - l uint64) error { - - v, ok := val.(*SparsePayHash) - if !ok { - return tlv.NewTypeForEncodingErr(val, "SparsePayHash") - } - - // If the length is zero, we will skip encoding the empty hash. - if l == 0 { - return nil - } - - vArray := (*[32]byte)(v) - - return tlv.DBytes32(r, vArray, buf, 32) -} - -// HTLCEntry specifies the minimal info needed to be stored on disk for ALL the -// historical HTLCs, which is useful for constructing RevocationLog when a -// breach is detected. -// The actual size of each HTLCEntry varies based on its RHash and Amt(sat), -// summarized as follows, -// -// | RHash | Amt<=252 | Amt<=65,535 | Amt<=4,294,967,295 | otherwise | -// |:-----:|:--------:|:-----------:|:------------------:|:---------:| -// | true | 19 | 21 | 23 | 26 | -// | false | 51 | 53 | 55 | 58 | -// -// So the size varies from 19 bytes to 58 bytes, where most likely to be 23 or -// 55 bytes. -// -// NOTE: all the fields saved to disk use the primitive go types so they can be -// made into tlv records without further conversion. -type HTLCEntry struct { - // RHash is the payment hash of the HTLC. - RHash tlv.RecordT[tlv.TlvType0, SparsePayHash] - - // RefundTimeout is the absolute timeout on the HTLC that the sender - // must wait before reclaiming the funds in limbo. - RefundTimeout tlv.RecordT[tlv.TlvType1, uint32] - - // OutputIndex is the output index for this particular HTLC output - // within the commitment transaction. - // - // NOTE: we use uint16 instead of int32 here to save us 2 bytes, which - // gives us a max number of HTLCs of 65K. - OutputIndex tlv.RecordT[tlv.TlvType2, uint16] - - // Incoming denotes whether we're the receiver or the sender of this - // HTLC. - Incoming tlv.RecordT[tlv.TlvType3, bool] - - // Amt is the amount of satoshis this HTLC escrows. - Amt tlv.RecordT[tlv.TlvType4, tlv.BigSizeT[btcutil.Amount]] - - // CustomBlob is an optional blob that can be used to store information - // specific to revocation handling for a custom channel type. - CustomBlob tlv.OptionalRecordT[tlv.TlvType5, tlv.Blob] - - // HtlcIndex is the index of the HTLC in the channel. - HtlcIndex tlv.OptionalRecordT[tlv.TlvType6, tlv.BigSizeT[uint64]] -} - -// NewHTLCEntryFromHTLC creates a new HTLCEntry from an HTLC. -func NewHTLCEntryFromHTLC(htlc HTLC) (*HTLCEntry, error) { - h := &HTLCEntry{ - RHash: tlv.NewRecordT[tlv.TlvType0]( - NewSparsePayHash(htlc.RHash), - ), - RefundTimeout: tlv.NewPrimitiveRecord[tlv.TlvType1]( - htlc.RefundTimeout, - ), - OutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType2]( - uint16(htlc.OutputIndex), - ), - Incoming: tlv.NewPrimitiveRecord[tlv.TlvType3](htlc.Incoming), - Amt: tlv.NewRecordT[tlv.TlvType4]( - tlv.NewBigSizeT(htlc.Amt.ToSatoshis()), - ), - HtlcIndex: tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType6]( - tlv.NewBigSizeT(htlc.HtlcIndex), - )), - } - - if len(htlc.CustomRecords) != 0 { - blob, err := htlc.CustomRecords.Serialize() - if err != nil { - return nil, err - } - - h.CustomBlob = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType5, tlv.Blob](blob), - ) - } - - return h, nil -} - -// RevocationLog stores the info needed to construct a breach retribution. Its -// fields can be viewed as a subset of a ChannelCommitment's. In the database, -// all historical versions of the RevocationLog are saved using the -// CommitHeight as the key. -type RevocationLog struct { - // OurOutputIndex specifies our output index in this commitment. In a - // remote commitment transaction, this is the to remote output index. - OurOutputIndex tlv.RecordT[tlv.TlvType0, uint16] - - // TheirOutputIndex specifies their output index in this commitment. In - // a remote commitment transaction, this is the to local output index. - TheirOutputIndex tlv.RecordT[tlv.TlvType1, uint16] - - // CommitTxHash is the hash of the latest version of the commitment - // state, broadcast able by us. - CommitTxHash tlv.RecordT[tlv.TlvType2, [32]byte] - - // HTLCEntries is the set of HTLCEntry's that are pending at this - // particular commitment height. - HTLCEntries []*HTLCEntry - - // OurBalance is the current available balance within the channel - // directly spendable by us. In other words, it is the value of the - // to_remote output on the remote parties' commitment transaction. - // - // NOTE: this is an option so that it is clear if the value is zero or - // nil. Since migration 30 of the channeldb initially did not include - // this field, it could be the case that the field is not present for - // all revocation logs. - OurBalance tlv.OptionalRecordT[tlv.TlvType3, BigSizeMilliSatoshi] - - // TheirBalance is the current available balance within the channel - // directly spendable by the remote node. In other words, it is the - // value of the to_local output on the remote parties' commitment. - // - // NOTE: this is an option so that it is clear if the value is zero or - // nil. Since migration 30 of the channeldb initially did not include - // this field, it could be the case that the field is not present for - // all revocation logs. - TheirBalance tlv.OptionalRecordT[tlv.TlvType4, BigSizeMilliSatoshi] - - // CustomBlob is an optional blob that can be used to store information - // specific to a custom channel type. This information is only created - // at channel funding time, and after wards is to be considered - // immutable. - CustomBlob tlv.OptionalRecordT[tlv.TlvType5, tlv.Blob] -} - -// NewRevocationLog creates a new RevocationLog from the given parameters. -func NewRevocationLog(ourOutputIndex uint16, theirOutputIndex uint16, - commitHash [32]byte, ourBalance, - theirBalance fn.Option[lnwire.MilliSatoshi], htlcs []*HTLCEntry, - customBlob fn.Option[tlv.Blob]) RevocationLog { - - rl := RevocationLog{ - OurOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType0]( - ourOutputIndex, - ), - TheirOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType1]( - theirOutputIndex, - ), - CommitTxHash: tlv.NewPrimitiveRecord[tlv.TlvType2](commitHash), - HTLCEntries: htlcs, - } - - ourBalance.WhenSome(func(balance lnwire.MilliSatoshi) { - rl.OurBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType3]( - tlv.NewBigSizeT(balance), - )) - }) - - theirBalance.WhenSome(func(balance lnwire.MilliSatoshi) { - rl.TheirBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType4]( - tlv.NewBigSizeT(balance), - )) - }) - - customBlob.WhenSome(func(blob tlv.Blob) { - rl.CustomBlob = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType5, tlv.Blob](blob), - ) - }) - - return rl -} diff --git a/chanstate/shutdown.go b/chanstate/shutdown.go deleted file mode 100644 index 4c1dca31a..000000000 --- a/chanstate/shutdown.go +++ /dev/null @@ -1,41 +0,0 @@ -package chanstate - -import ( - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/tlv" -) - -// ShutdownInfo contains various info about the shutdown initiation of a -// channel. -type ShutdownInfo struct { - // DeliveryScript is the address that we have included in any previous - // Shutdown message for a particular channel and so should include in - // any future re-sends of the Shutdown message. - DeliveryScript tlv.RecordT[tlv.TlvType0, lnwire.DeliveryAddress] - - // LocalInitiator is true if we sent a Shutdown message before ever - // receiving a Shutdown message from the remote peer. - LocalInitiator tlv.RecordT[tlv.TlvType1, bool] -} - -// NewShutdownInfo constructs a new ShutdownInfo object. -func NewShutdownInfo(deliveryScript lnwire.DeliveryAddress, - locallyInitiated bool) *ShutdownInfo { - - return &ShutdownInfo{ - DeliveryScript: tlv.NewRecordT[tlv.TlvType0](deliveryScript), - LocalInitiator: tlv.NewPrimitiveRecord[tlv.TlvType1]( - locallyInitiated, - ), - } -} - -// Closer identifies the ChannelParty that initiated the coop-closure process. -func (s ShutdownInfo) Closer() lntypes.ChannelParty { - if s.LocalInitiator.Val { - return lntypes.Local - } - - return lntypes.Remote -} diff --git a/chanstate/snapshot.go b/chanstate/snapshot.go deleted file mode 100644 index 2bae2edf0..000000000 --- a/chanstate/snapshot.go +++ /dev/null @@ -1,44 +0,0 @@ -package chanstate - -import ( - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/lnwire" -) - -// ChannelSnapshot is a frozen snapshot of the current channel state. A -// snapshot is detached from the original channel that generated it, providing -// read-only access to the current or prior state of an active channel. -// -// TODO(roasbeef): remove all together? pretty much just commitment. -type ChannelSnapshot struct { - // RemoteIdentity is the identity public key of the remote node that we - // are maintaining the open channel with. - RemoteIdentity btcec.PublicKey - - // ChanPoint is the outpoint that created the channel. This output is - // found within the funding transaction and uniquely identified the - // channel on the resident chain. - ChannelPoint wire.OutPoint - - // ChainHash is the genesis hash of the chain that the channel resides - // within. - ChainHash chainhash.Hash - - // Capacity is the total capacity of the channel. - Capacity btcutil.Amount - - // TotalMSatSent is the total number of milli-satoshis we've sent - // within this channel. - TotalMSatSent lnwire.MilliSatoshi - - // TotalMSatReceived is the total number of milli-satoshis we've - // received within this channel. - TotalMSatReceived lnwire.MilliSatoshi - - // ChannelCommitment is the current up-to-date commitment for the - // target channel. - ChannelCommitment -} diff --git a/chanstate/taproot.go b/chanstate/taproot.go deleted file mode 100644 index cfa33108c..000000000 --- a/chanstate/taproot.go +++ /dev/null @@ -1,79 +0,0 @@ -package chanstate - -import ( - "bytes" - "crypto/hmac" - "crypto/sha256" - "fmt" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/lightningnetwork/lnd/shachain" -) - -const ( - // AbsoluteThawHeightThreshold is the threshold at which a thaw height - // begins to be interpreted as an absolute block height, rather than a - // relative one. - AbsoluteThawHeightThreshold uint32 = 500000 -) - -var ( - // taprootRevRootKey is the key used to derive the revocation root for - // the taproot nonces. This is done via HMAC of the existing revocation - // root. - taprootRevRootKey = []byte("taproot-rev-root") -) - -// DeriveMusig2Shachain derives a shachain producer for the taproot channel -// from normal shachain revocation root. -func DeriveMusig2Shachain(revRoot shachain.Producer) (shachain.Producer, error) { //nolint:ll - // In order to obtain the revocation root hash to create the taproot - // revocation, we'll encode the producer into a buffer, then use that - // to derive the shachain root needed. - var rootHashBuf bytes.Buffer - if err := revRoot.Encode(&rootHashBuf); err != nil { - return nil, fmt.Errorf("unable to encode producer: %w", err) - } - - revRootHash := chainhash.HashH(rootHashBuf.Bytes()) - - // For taproot channel types, we'll also generate a distinct shachain - // root using the same seed information. We'll use this to generate - // verification nonces for the channel. We'll bind with this a simple - // hmac. - taprootRevHmac := hmac.New(sha256.New, taprootRevRootKey) - if _, err := taprootRevHmac.Write(revRootHash[:]); err != nil { - return nil, err - } - - taprootRevRoot := taprootRevHmac.Sum(nil) - - // Once we have the root, we can then generate our shachain producer - // and from that generate the per-commitment point. - return shachain.NewRevocationProducerFromBytes( - taprootRevRoot, - ) -} - -// NewMusigVerificationNonce generates the local or verification nonce for -// another musig2 session. In order to permit our implementation to not have to -// write any secret nonce state to disk, we'll use the _next_ shachain -// pre-image as our primary randomness source. When used to generate the nonce -// again to broadcast our commitment hte current height will be used. -func NewMusigVerificationNonce(pubKey *btcec.PublicKey, targetHeight uint64, - shaGen shachain.Producer) (*musig2.Nonces, error) { - - // Now that we know what height we need, we'll grab the shachain - // pre-image at the target destination. - nextPreimage, err := shaGen.AtIndex(targetHeight) - if err != nil { - return nil, err - } - - shaChainRand := musig2.WithCustomRand(bytes.NewBuffer(nextPreimage[:])) - pubKeyOpt := musig2.WithPublicKey(pubKey) - - return musig2.GenNonces(pubKeyOpt, shaChainRand) -} diff --git a/clock/go.mod b/clock/go.mod index 6c3afbc00..1c176ad4a 100644 --- a/clock/go.mod +++ b/clock/go.mod @@ -1,6 +1,6 @@ module github.com/lightningnetwork/lnd/clock -go 1.25.11 +go 1.24.11 require github.com/stretchr/testify v1.8.2 diff --git a/cmd/commands/chainrpc_active.go b/cmd/commands/chainrpc_active.go index 3975be8cd..0f1f8b612 100644 --- a/cmd/commands/chainrpc_active.go +++ b/cmd/commands/chainrpc_active.go @@ -8,8 +8,8 @@ import ( "fmt" "strconv" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnrpc/chainrpc" "github.com/urfave/cli" ) diff --git a/cmd/commands/cmd_debug.go b/cmd/commands/cmd_debug.go index eca1a3063..37024f5ec 100644 --- a/cmd/commands/cmd_debug.go +++ b/cmd/commands/cmd_debug.go @@ -23,26 +23,14 @@ var getDebugInfoCommand = cli.Command{ Category: "Debug", Usage: "Returns debug information related to the active daemon.", Action: actionDecorator(getDebugInfo), - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "include_log", - Usage: "if set, the log file content is " + - "included in the response in " + - "addition to the config", - }, - }, } -// getDebugInfo retrieves debug information from the daemon, optionally -// including the log file content. func getDebugInfo(ctx *cli.Context) error { ctxc := getContext() client, cleanUp := getClient(ctx) defer cleanUp() - req := &lnrpc.GetDebugInfoRequest{ - IncludeLog: ctx.Bool("include_log"), - } + req := &lnrpc.GetDebugInfoRequest{} resp, err := client.GetDebugInfo(ctxc, req) if err != nil { return err @@ -73,14 +61,12 @@ var encryptDebugPackageCommand = cli.Command{ The file by default contains the output of the following commands: - lncli getinfo - - lncli getdebuginfo (config only) + - lncli getdebuginfo - lncli getnetworkinfo By specifying the following flags, additional information can be added to the file (usually this will be requested by the developer depending on the issue at hand): - --include_log: - - includes the log file content in the debug info --peers: - lncli listpeers --onchain: @@ -127,11 +113,6 @@ var encryptDebugPackageCommand = cli.Command{ "(lncli listchannels, lncli pendingchannels, " + "lncli closedchannels)", }, - cli.BoolFlag{ - Name: "include_log", - Usage: "include the log file content in the " + - "debug package", - }, }, Action: actionDecorator(encryptDebugPackage), } @@ -245,9 +226,7 @@ func collectDebugPackageInfo(ctx *cli.Context) ([]byte, error) { } debugInfo, err := client.GetDebugInfo( - ctxc, &lnrpc.GetDebugInfoRequest{ - IncludeLog: ctx.Bool("include_log"), - }, + ctxc, &lnrpc.GetDebugInfoRequest{}, ) if err != nil { return nil, fmt.Errorf("error getting debug info: %w", err) diff --git a/cmd/commands/cmd_mission_control.go b/cmd/commands/cmd_mission_control.go index 33f98881c..fe4acb25c 100644 --- a/cmd/commands/cmd_mission_control.go +++ b/cmd/commands/cmd_mission_control.go @@ -4,7 +4,7 @@ import ( "fmt" "strconv" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing" diff --git a/cmd/commands/cmd_open_channel.go b/cmd/commands/cmd_open_channel.go index 48fba50e8..b4fe83f20 100644 --- a/cmd/commands/cmd_open_channel.go +++ b/cmd/commands/cmd_open_channel.go @@ -13,10 +13,9 @@ import ( "strconv" "strings" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwallet/chanfunding" "github.com/urfave/cli" @@ -59,22 +58,9 @@ Signed base64 encoded PSBT or hex encoded raw wire TX (or path to file): ` // of memory issues or other weird errors. psbtMaxFileSize = 1024 * 1024 - channelTypeTweakless = "tweakless" - channelTypeAnchors = "anchors" - - // channelTypeSimpleTaproot selects the production taproot channel - // type (feature bits 80/81). This is the recommended taproot variant. + channelTypeTweakless = "tweakless" + channelTypeAnchors = "anchors" channelTypeSimpleTaproot = "taproot" - - // channelTypeSimpleTaprootStaging selects the legacy staging taproot - // channel type using development feature bits. Kept for compatibility - // with peers that have not upgraded to the final variant. - channelTypeSimpleTaprootStaging = "taproot-staging" - - // channelTypeSimpleTaprootFinalAlias is a deprecated alias for - // "taproot" that resolves to the same production taproot channel type. - // Retained so existing scripts continue to work. - channelTypeSimpleTaprootFinalAlias = "taproot-final" ) // TODO(roasbeef): change default number of confirmations. @@ -267,12 +253,8 @@ var openChannelCommand = cli.Command{ cli.StringFlag{ Name: "channel_type", Usage: fmt.Sprintf("(optional) the type of channel to "+ - "propose to the remote peer (%q, %q, %q, %q). "+ - "%q is accepted as a deprecated alias for %q", + "propose to the remote peer (%q, %q, %q)", channelTypeTweakless, channelTypeAnchors, - channelTypeSimpleTaproot, - channelTypeSimpleTaprootStaging, - channelTypeSimpleTaprootFinalAlias, channelTypeSimpleTaproot), }, cli.BoolFlag{ @@ -424,7 +406,7 @@ func openChannel(ctx *cli.Context) error { if ctx.IsSet("utxo") { utxos := ctx.StringSlice("utxo") - outpoints, err := lnd.UtxosToOutpoints(utxos) + outpoints, err := UtxosToOutpoints(utxos) if err != nil { return fmt.Errorf("unable to decode utxos: %w", err) } @@ -453,10 +435,7 @@ func openChannel(ctx *cli.Context) error { req.Private = ctx.Bool("private") - // Parse the channel type and map it to its RPC representation. The - // bare "taproot" string now selects the production (final) variant; - // "taproot-staging" preserves access to the legacy development bits. - // "taproot-final" is accepted as a deprecated alias for "taproot". + // Parse the channel type and map it to its RPC representation. channelType := ctx.String("channel_type") switch channelType { case "": @@ -465,9 +444,7 @@ func openChannel(ctx *cli.Context) error { req.CommitmentType = lnrpc.CommitmentType_STATIC_REMOTE_KEY case channelTypeAnchors: req.CommitmentType = lnrpc.CommitmentType_ANCHORS - case channelTypeSimpleTaproot, channelTypeSimpleTaprootFinalAlias: - req.CommitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - case channelTypeSimpleTaprootStaging: + case channelTypeSimpleTaproot: req.CommitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT default: return fmt.Errorf("unsupported channel type %v", channelType) diff --git a/cmd/commands/cmd_payments.go b/cmd/commands/cmd_payments.go index 57290bd54..d13b52da2 100644 --- a/cmd/commands/cmd_payments.go +++ b/cmd/commands/cmd_payments.go @@ -15,7 +15,7 @@ import ( "strings" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/jedib0t/go-pretty/v6/table" "github.com/jedib0t/go-pretty/v6/text" "github.com/lightningnetwork/lnd/lnrpc" @@ -1497,11 +1497,6 @@ var listPaymentsCommand = cli.Command{ "payments with creation date less than or " + "equal to it", }, - cli.BoolFlag{ - Name: "omit_hops", - Usage: "if set, omit hop-level route data to " + - "reduce query cost and response size", - }, }, Action: actionDecorator(listPayments), } @@ -1519,7 +1514,6 @@ func listPayments(ctx *cli.Context) error { CountTotalPayments: ctx.Bool("count_total_payments"), CreationDateStart: ctx.Uint64("creation_date_start"), CreationDateEnd: ctx.Uint64("creation_date_end"), - OmitHops: ctx.Bool("omit_hops"), } payments, err := client.ListPayments(ctxc, req) @@ -1942,126 +1936,6 @@ func deletePayments(ctx *cli.Context) error { return nil } -var deleteFwdHistoryCommand = cli.Command{ - Name: "deletefwdhistory", - Category: "Payments", - Usage: "Delete old forwarding history for privacy.", - ArgsUsage: "age | before", - Description: ` - Deletes all forwarding history events with a timestamp at or before a - specified time. This is useful for implementing data retention policies - for privacy purposes. The command permanently removes old forwarding - events from the database and returns statistics about the deletion - including total fees earned. - - Time can be specified in two ways: - 1. Relative age (standard Go or custom units): e.g., "-1w", "-24h", - "-1M" - 2. Absolute Unix timestamp: e.g., "1640995200" - - Supported relative time units: - - Standard Go: ns, us/µs, ms, s, m, h (e.g., "-24h", "-1.5h") - - Custom units: d (days), w (weeks), M (months=30.44d), - y (years=365.25d) - - Examples: - # Delete events from ~1 month ago and earlier: - lncli deletefwdhistory --age="-1M" - - # Delete events from ~1 month ago and earlier - lncli deletefwdhistory --age="-720h" - - # Delete events at or before Jan 1, 2022: - lncli deletefwdhistory --before=1640995200 - - NOTE: As with deletepayments, removing events from the database frees up - disk space within bbolt, but that space is only reclaimed after - compacting the database. Consider enabling auto-compaction - (db.bolt.auto-compact=true). - - WARNING: This operation is irreversible. Deleted forwarding history - cannot be recovered. A minimum age validation is enforced to prevent - accidental deletion of very recent data. - `, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "age", - Usage: "delete events at or before this age in the " + - "past " + - `(e.g., "-1w", "-1M", "-24h", "-720h")`, - }, - cli.Uint64Flag{ - Name: "before", - Usage: "delete events at or before this Unix " + - "timestamp (seconds)", - }, - cli.BoolFlag{ - Name: "force, f", - Usage: "skip the confirmation prompt, useful for " + - "scripts", - }, - }, - Action: actionDecorator(deleteFwdHistory), -} - -func deleteFwdHistory(ctx *cli.Context) error { - ctxc := getContext() - conn := getClientConn(ctx, false) - defer conn.Close() - - client := routerrpc.NewRouterClient(conn) - - // Show command help if no arguments or flags are provided. - if ctx.NArg() > 0 || (!ctx.IsSet("age") && !ctx.IsSet("before")) { - _ = cli.ShowCommandHelp(ctx, "deletefwdhistory") - return nil - } - - // User must specify exactly one of age or until. - if ctx.IsSet("age") && ctx.IsSet("before") { - return fmt.Errorf("cannot use both --age and --before; " + - "specify one time parameter") - } - - req := &routerrpc.DeleteForwardingHistoryRequest{} - - //nolint:ll - switch { - case ctx.IsSet("age"): - req.TimeSpec = &routerrpc.DeleteForwardingHistoryRequest_DeleteBeforeDuration{ - DeleteBeforeDuration: ctx.String("age"), - } - - case ctx.IsSet("before"): - req.TimeSpec = &routerrpc.DeleteForwardingHistoryRequest_DeleteBeforeTime{ - DeleteBeforeTime: ctx.Uint64("before"), - } - } - - if !ctx.Bool("force") { - if !promptForConfirmation("WARNING: This operation is " + - "irreversible and will permanently delete forwarding " + - "history.\nProceed? (yes/no): ") { - - fmt.Println("Operation cancelled.") - return nil - } - } - - fmt.Println("Deleting forwarding history, this may take a while...") - - resp, err := client.DeleteForwardingHistory(ctxc, req) - if err != nil { - return fmt.Errorf( - "failed to delete forwarding history: %w", err, - ) - } - - printJSON(resp) - - return nil -} - var estimateRouteFeeCommand = cli.Command{ Name: "estimateroutefee", Category: "Payments", @@ -2094,15 +1968,6 @@ var estimateRouteFeeCommand = cli.Command{ "applicable if pay_req is specified.", Value: paymentTimeout, }, - cli.StringSliceFlag{ - Name: "outgoing_chan_id", - Usage: "short channel id of the outgoing channel " + - "to use for the first hop of the fee " + - "estimation; if specified multiple " + - "times, only the listed channels are " + - "considered for the first hop", - Value: &cli.StringSlice{}, - }, }, } @@ -2149,15 +2014,6 @@ func estimateRouteFee(ctx *cli.Context) error { return fmt.Errorf("fee estimation arguments missing") } - var ( - err error - outChanIDs = ctx.StringSlice("outgoing_chan_id") - ) - req.OutgoingChanIds, err = parseChanIDs(outChanIDs) - if err != nil { - return fmt.Errorf("unable to decode outgoing_chan_id: %w", err) - } - resp, err := client.EstimateRouteFee(ctxc, req) if err != nil { return err diff --git a/cmd/commands/cmd_profile.go b/cmd/commands/cmd_profile.go index e8b22ad9d..6767964eb 100644 --- a/cmd/commands/cmd_profile.go +++ b/cmd/commands/cmd_profile.go @@ -6,7 +6,7 @@ import ( "path" "strings" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lncfg" "github.com/urfave/cli" "gopkg.in/macaroon.v2" diff --git a/cmd/commands/cmd_walletunlocker.go b/cmd/commands/cmd_walletunlocker.go index 0c04ce25c..c396a038c 100644 --- a/cmd/commands/cmd_walletunlocker.go +++ b/cmd/commands/cmd_walletunlocker.go @@ -3,11 +3,8 @@ package commands import ( "bufio" "bytes" - "context" "encoding/hex" - "errors" "fmt" - "io" "os" "strconv" "strings" @@ -18,8 +15,6 @@ import ( "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/walletunlocker" "github.com/urfave/cli" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" ) var ( @@ -507,32 +502,11 @@ var unlockCommand = cli.Command{ Action: actionDecorator(unlock), } -// unlock is the lncli entry point for unlocking the wallet using the -// WalletUnlocker service. func unlock(ctx *cli.Context) error { - return unlockWithDeps( - ctx, readPassword, getWalletUnlockerClient, - getStateServiceClient, getContext, os.Stdin, - ) -} - -// unlockWithDeps performs the unlock flow with injected dependencies to -// simplify unit testing. -func unlockWithDeps(ctx *cli.Context, - readPasswordFn func(string) ([]byte, error), - getUnlockerClientFn func(*cli.Context) (lnrpc.WalletUnlockerClient, - func()), - getStateClientFn func(*cli.Context) (lnrpc.StateClient, func()), - getContextFn func() context.Context, stdin io.Reader) error { - - ctxc := getContextFn() - client, cleanUp := getUnlockerClientFn(ctx) + ctxc := getContext() + client, cleanUp := getWalletUnlockerClient(ctx) defer cleanUp() - // Use the always-on state service to wait for unlock readiness. - stateClient, stateCleanUp := getStateClientFn(ctx) - defer stateCleanUp() - var ( pw []byte err error @@ -543,7 +517,7 @@ func unlockWithDeps(ctx *cli.Context, // password manager. If the user types the password instead, it will be // echoed in the console. case ctx.IsSet("stdin"): - reader := bufio.NewReader(stdin) + reader := bufio.NewReader(os.Stdin) pw, err = reader.ReadBytes('\n') // Remove carriage return and newline characters. @@ -553,7 +527,7 @@ func unlockWithDeps(ctx *cli.Context, // terminal to be a real tty and will fail if a string is piped into // lncli. default: - pw, err = readPasswordFn("Input wallet password: ") + pw, err = readPassword("Input wallet password: ") } if err != nil { return err @@ -581,30 +555,11 @@ func unlockWithDeps(ctx *cli.Context, RecoveryWindow: recoveryWindow, StatelessInit: ctx.Bool(statelessInitFlag.Name), } - - // Wait until lnd reports the wallet is locked and ready to accept - // an unlock request. - waitCtx, cancel := context.WithCancel(ctxc) - err = waitForWalletLocked(waitCtx, stateClient) - cancel() - if err != nil { - return err - } - - // Submit the unlock request once the wallet is ready. _, err = client.UnlockWallet(ctxc, req) if err != nil { return err } - // Wait until the wallet is fully unlocked (or RPC/server active). - waitCtx, cancel = context.WithCancel(ctxc) - err = waitForWalletUnlocked(waitCtx, stateClient) - cancel() - if err != nil { - return err - } - fmt.Println("\nlnd successfully unlocked!") // TODO(roasbeef): add ability to accept hex single and multi backups @@ -612,138 +567,6 @@ func unlockWithDeps(ctx *cli.Context, return nil } -// waitForWalletState consumes the StateService stream until the check function -// reports completion or the stream ends. -func waitForWalletState(ctx context.Context, client lnrpc.StateClient, - check func(lnrpc.WalletState) (bool, error)) error { - - stream, err := client.SubscribeState( - ctx, &lnrpc.SubscribeStateRequest{}, - ) - if err != nil { - return err - } - - for { - resp, err := stream.Recv() - if err != nil { - if errors.Is(err, io.EOF) { - return errors.New("lnd shut down before " + - "reaching expected wallet state") - } - - return err - } - - state := resp.GetState() - fmt.Printf("wallet state: %s\n", state) - - done, err := check(state) - if done { - return err - } - } -} - -// waitForWalletLocked blocks until the wallet reaches LOCKED, or errors if the -// wallet is missing or already unlocked. -func waitForWalletLocked(ctx context.Context, client lnrpc.StateClient) error { - check := func(state lnrpc.WalletState) (bool, error) { - switch state { - case lnrpc.WalletState_LOCKED: - return true, nil - - case lnrpc.WalletState_NON_EXISTING: - return true, errors.New("wallet is not initialized - " + - "please run 'lncli create'") - - case lnrpc.WalletState_UNLOCKED, - lnrpc.WalletState_RPC_ACTIVE, - lnrpc.WalletState_SERVER_ACTIVE: - - return true, errors.New("wallet is already unlocked") - - default: - return false, nil - } - } - - err := waitForWalletState(ctx, client, check) - if err == nil { - return nil - } - - if s, ok := status.FromError(err); ok { - switch s.Code() { - case codes.Unimplemented: - fmt.Println("StateService not available, " + - "skipping wait for locked state") - - return nil - - case codes.Unavailable: - // The state service may be temporarily unreachable. - fmt.Println("StateService unavailable, " + - "skipping wait for locked state") - - return nil - - default: - } - } - - return err -} - -// waitForWalletUnlocked blocks until the wallet reaches UNLOCKED or beyond, -// or errors if the wallet is missing. -func waitForWalletUnlocked(ctx context.Context, - client lnrpc.StateClient) error { - - check := func(state lnrpc.WalletState) (bool, error) { - switch state { - case lnrpc.WalletState_UNLOCKED, - lnrpc.WalletState_RPC_ACTIVE, - lnrpc.WalletState_SERVER_ACTIVE: - - return true, nil - - case lnrpc.WalletState_NON_EXISTING: - return true, errors.New("wallet is not initialized - " + - "please run 'lncli create'") - - default: - return false, nil - } - } - - err := waitForWalletState(ctx, client, check) - if err == nil { - return nil - } - - if s, ok := status.FromError(err); ok { - switch s.Code() { - case codes.Unimplemented: - fmt.Println("StateService not available, " + - "skipping wait for unlocked state") - - return nil - - case codes.Unavailable: - // The state service may be temporarily unreachable. - fmt.Println("StateService unavailable, " + - "skipping wait for unlocked state") - - return nil - - default: - } - } - - return err -} - var changePasswordCommand = cli.Command{ Name: "changepassword", Category: "Startup", diff --git a/cmd/commands/cmd_walletunlocker_test.go b/cmd/commands/cmd_walletunlocker_test.go deleted file mode 100644 index 91e4be913..000000000 --- a/cmd/commands/cmd_walletunlocker_test.go +++ /dev/null @@ -1,661 +0,0 @@ -package commands - -import ( - "context" - "errors" - "flag" - "io" - "strings" - "testing" - - "github.com/lightningnetwork/lnd/lnrpc" - "github.com/stretchr/testify/require" - "github.com/urfave/cli" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" -) - -// fakeClientStream implements grpc.ClientStream for tests. -type fakeClientStream struct{} - -// Header returns empty metadata for the fake client stream. -func (fakeClientStream) Header() (metadata.MD, error) { - return nil, nil -} - -// Trailer returns empty metadata for the fake client stream. -func (fakeClientStream) Trailer() metadata.MD { - return nil -} - -// CloseSend is a no-op for the fake client stream. -func (fakeClientStream) CloseSend() error { - return nil -} - -// Context returns a background context for the fake client stream. -func (fakeClientStream) Context() context.Context { - return context.Background() -} - -// SendMsg is a no-op for the fake client stream. -func (fakeClientStream) SendMsg(interface{}) error { - return nil -} - -// RecvMsg is a no-op for the fake client stream. -func (fakeClientStream) RecvMsg(interface{}) error { - return nil -} - -// stateStreamSpec describes the scripted responses for a state stream. -type stateStreamSpec struct { - states []lnrpc.WalletState - err error -} - -// fakeStateStream implements State_SubscribeStateClient with scripted states. -type fakeStateStream struct { - fakeClientStream - states []lnrpc.WalletState - err error - idx int -} - -// Recv returns the next scripted wallet state or the configured error. -func (f *fakeStateStream) Recv() (*lnrpc.SubscribeStateResponse, error) { - if f.idx < len(f.states) { - state := f.states[f.idx] - f.idx++ - - return &lnrpc.SubscribeStateResponse{ - State: state, - }, nil - } - - if f.err != nil { - return nil, f.err - } - - return nil, io.EOF -} - -// fakeStateClient implements lnrpc.StateClient with scripted streams. -type fakeStateClient struct { - streams []stateStreamSpec - subscribeCalls int - subscribeInputs []*lnrpc.SubscribeStateRequest -} - -// SubscribeState returns a scripted stream for the fake state client. -func (f *fakeStateClient) SubscribeState(_ context.Context, - in *lnrpc.SubscribeStateRequest, - _ ...grpc.CallOption) (lnrpc.State_SubscribeStateClient, error) { - - f.subscribeCalls++ - f.subscribeInputs = append(f.subscribeInputs, in) - - if f.subscribeCalls > len(f.streams) { - return nil, errors.New("unexpected SubscribeState call") - } - - streamSpec := f.streams[f.subscribeCalls-1] - - return &fakeStateStream{ - states: streamSpec.states, - err: streamSpec.err, - }, nil -} - -// GetState is unused in tests and returns a sentinel error. -func (f *fakeStateClient) GetState(_ context.Context, - _ *lnrpc.GetStateRequest, - _ ...grpc.CallOption) (*lnrpc.GetStateResponse, error) { - - return nil, errors.New("not implemented") -} - -// Ensure fakeStateClient satisfies the lnrpc.StateClient interface. -var _ lnrpc.StateClient = (*fakeStateClient)(nil) - -// errNotImplemented is returned by fake methods that are unused in tests. -var errNotImplemented = errors.New("not implemented") - -// fakeUnlockerClient implements lnrpc.WalletUnlockerClient for tests. -type fakeUnlockerClient struct { - unlockCalls int - lastReq *lnrpc.UnlockWalletRequest - unlockErr error -} - -// GenSeed is unused in tests and returns a sentinel error. -func (f *fakeUnlockerClient) GenSeed(_ context.Context, _ *lnrpc.GenSeedRequest, - _ ...grpc.CallOption) (*lnrpc.GenSeedResponse, error) { - - return nil, errNotImplemented -} - -// InitWallet is unused in tests and returns a sentinel error. -func (f *fakeUnlockerClient) InitWallet(_ context.Context, - _ *lnrpc.InitWalletRequest, - _ ...grpc.CallOption) (*lnrpc.InitWalletResponse, error) { - - return nil, errNotImplemented -} - -// UnlockWallet records the request and returns the configured response. -func (f *fakeUnlockerClient) UnlockWallet(_ context.Context, - in *lnrpc.UnlockWalletRequest, - _ ...grpc.CallOption) (*lnrpc.UnlockWalletResponse, error) { - - f.unlockCalls++ - f.lastReq = in - - if f.unlockErr != nil { - return nil, f.unlockErr - } - - return &lnrpc.UnlockWalletResponse{}, nil -} - -// ChangePassword is unused in tests and returns a sentinel error. -func (f *fakeUnlockerClient) ChangePassword(_ context.Context, - _ *lnrpc.ChangePasswordRequest, - _ ...grpc.CallOption) (*lnrpc.ChangePasswordResponse, error) { - - return nil, errNotImplemented -} - -// Ensure fakeUnlockerClient satisfies the lnrpc.WalletUnlockerClient interface. -var _ lnrpc.WalletUnlockerClient = (*fakeUnlockerClient)(nil) - -// newUnlockContext builds a cli.Context with unlock flags parsed. -func newUnlockContext(t *testing.T, args []string) *cli.Context { - t.Helper() - - flagSet := flag.NewFlagSet("unlock", flag.ContinueOnError) - flagSet.SetOutput(io.Discard) - flagSet.Bool("stdin", false, "") - flagSet.Int64("recovery_window", 0, "") - flagSet.Bool("stateless_init", false, "") - - err := flagSet.Parse(args) - require.NoError(t, err) - - app := cli.NewApp() - - return cli.NewContext(app, flagSet, nil) -} - -// TestUnlock exercises wallet unlock command across success and error paths. -func TestUnlock(t *testing.T) { - // Shortcut for a long name. - const waitingToString = lnrpc.WalletState_WAITING_TO_START - - // Define table-driven cases for unlockWithDeps behavior and inputs. - testCases := []struct { - name string - args []string - stdinInput string - readPasswordRet []byte - readPasswordErr error - stateStreams []stateStreamSpec - unlockerErr error - expectErr string - expectReadPasswordCalls int - expectUnlockCalls int - expectSubscribeCalls int - expectReq *lnrpc.UnlockWalletRequest - }{ - // Succeeds by waiting for locked then RPC active. - { - name: "success_default", - readPasswordRet: []byte("pw"), - stateStreams: []stateStreamSpec{ - { - states: []lnrpc.WalletState{ - waitingToString, - lnrpc.WalletState_LOCKED, - }, - }, - { - states: []lnrpc.WalletState{ - lnrpc.WalletState_RPC_ACTIVE, - }, - }, - }, - expectReadPasswordCalls: 1, - expectUnlockCalls: 1, - expectSubscribeCalls: 2, - expectReq: &lnrpc.UnlockWalletRequest{ - WalletPassword: []byte("pw"), - RecoveryWindow: 0, - StatelessInit: false, - }, - }, - - // Uses stdin, stateless init, and recovery window flag. - { - name: "success_stdin_flag_recovery_stateless", - args: []string{ - "--stdin", "--stateless_init", - "--recovery_window=50", - }, - stdinInput: "secret\n", - stateStreams: []stateStreamSpec{ - { - states: []lnrpc.WalletState{ - lnrpc.WalletState_LOCKED, - }, - }, - { - states: []lnrpc.WalletState{ - lnrpc.WalletState_UNLOCKED, - }, - }, - }, - expectReadPasswordCalls: 0, - expectUnlockCalls: 1, - expectSubscribeCalls: 2, - expectReq: &lnrpc.UnlockWalletRequest{ - WalletPassword: []byte("secret"), - RecoveryWindow: 50, - StatelessInit: true, - }, - }, - - // Uses positional recovery window argument. - { - name: "success_arg_recovery_window", - args: []string{"25"}, - readPasswordRet: []byte("pw"), - stateStreams: []stateStreamSpec{ - { - states: []lnrpc.WalletState{ - lnrpc.WalletState_LOCKED, - }, - }, - { - states: []lnrpc.WalletState{ - lnrpc.WalletState_SERVER_ACTIVE, - }, - }, - }, - expectReadPasswordCalls: 1, - expectUnlockCalls: 1, - expectSubscribeCalls: 2, - expectReq: &lnrpc.UnlockWalletRequest{ - WalletPassword: []byte("pw"), - RecoveryWindow: 25, - StatelessInit: false, - }, - }, - - // Propagates password read errors. - { - name: "read_password_error", - readPasswordErr: errors.New("read fail"), - expectErr: "read fail", - expectReadPasswordCalls: 1, - }, - - // Fails when positional recovery window is not an int. - { - name: "bad_recovery_arg", - args: []string{"not-int"}, - readPasswordRet: []byte("pw"), - expectErr: "invalid syntax", - expectReadPasswordCalls: 1, - }, - - // EOF while waiting for locked state returns a descriptive - // error. - { - name: "wait_locked_eof", - readPasswordRet: []byte("pw"), - stateStreams: []stateStreamSpec{ - { - err: io.EOF, - }, - }, - expectErr: "lnd shut down before reaching expected " + - "wallet state", - expectReadPasswordCalls: 1, - expectSubscribeCalls: 1, - }, - - // Unimplemented StateService skips lock wait then succeeds. - { - name: "wait_locked_unimplemented", - readPasswordRet: []byte("pw"), - stateStreams: []stateStreamSpec{ - { - err: status.Error( - codes.Unimplemented, "no state", - ), - }, - { - states: []lnrpc.WalletState{ - lnrpc.WalletState_UNLOCKED, - }, - }, - }, - expectReadPasswordCalls: 1, - expectUnlockCalls: 1, - expectSubscribeCalls: 2, - expectReq: &lnrpc.UnlockWalletRequest{ - WalletPassword: []byte("pw"), - RecoveryWindow: 0, - StatelessInit: false, - }, - }, - - // Unavailable StateService skips lock wait then succeeds. - { - name: "wait_locked_unavailable", - readPasswordRet: []byte("pw"), - stateStreams: []stateStreamSpec{ - { - err: status.Error( - codes.Unavailable, "no state", - ), - }, - { - states: []lnrpc.WalletState{ - lnrpc.WalletState_RPC_ACTIVE, - }, - }, - }, - expectReadPasswordCalls: 1, - expectUnlockCalls: 1, - expectSubscribeCalls: 2, - expectReq: &lnrpc.UnlockWalletRequest{ - WalletPassword: []byte("pw"), - RecoveryWindow: 0, - StatelessInit: false, - }, - }, - - // NON_EXISTING during lock wait fails before unlock. - { - name: "wait_locked_non_existing", - readPasswordRet: []byte("pw"), - stateStreams: []stateStreamSpec{ - { - states: []lnrpc.WalletState{ - lnrpc.WalletState_NON_EXISTING, - }, - }, - }, - expectErr: "wallet is not initialized - please run " + - "'lncli create'", - expectReadPasswordCalls: 1, - expectSubscribeCalls: 1, - }, - - // Already unlocked during lock wait fails before unlock. - { - name: "wait_locked_already_unlocked", - readPasswordRet: []byte("pw"), - stateStreams: []stateStreamSpec{ - { - states: []lnrpc.WalletState{ - lnrpc.WalletState_UNLOCKED, - }, - }, - }, - expectErr: "wallet is already unlocked", - expectReadPasswordCalls: 1, - expectSubscribeCalls: 1, - }, - - // Unlock RPC error is returned after lock wait succeeds. - { - name: "unlocker_error", - readPasswordRet: []byte("pw"), - stateStreams: []stateStreamSpec{ - { - states: []lnrpc.WalletState{ - lnrpc.WalletState_LOCKED, - }, - }, - }, - unlockerErr: errors.New("unlock failed"), - expectErr: "unlock failed", - expectReadPasswordCalls: 1, - expectUnlockCalls: 1, - expectSubscribeCalls: 1, - expectReq: &lnrpc.UnlockWalletRequest{ - WalletPassword: []byte("pw"), - RecoveryWindow: 0, - StatelessInit: false, - }, - }, - - // EOF while waiting for unlocked state returns a descriptive - // error. - { - name: "wait_unlocked_eof", - readPasswordRet: []byte("pw"), - stateStreams: []stateStreamSpec{ - { - states: []lnrpc.WalletState{ - lnrpc.WalletState_LOCKED, - }, - }, - { - err: io.EOF, - }, - }, - expectErr: "lnd shut down before reaching expected " + - "wallet state", - expectReadPasswordCalls: 1, - expectUnlockCalls: 1, - expectSubscribeCalls: 2, - expectReq: &lnrpc.UnlockWalletRequest{ - WalletPassword: []byte("pw"), - RecoveryWindow: 0, - StatelessInit: false, - }, - }, - - // NON_EXISTING during unlocked wait fails after unlock attempt. - { - name: "wait_unlocked_non_existing", - readPasswordRet: []byte("pw"), - stateStreams: []stateStreamSpec{ - { - states: []lnrpc.WalletState{ - lnrpc.WalletState_LOCKED, - }, - }, - { - states: []lnrpc.WalletState{ - lnrpc.WalletState_NON_EXISTING, - }, - }, - }, - expectErr: "wallet is not initialized - please run " + - "'lncli create'", - expectReadPasswordCalls: 1, - expectUnlockCalls: 1, - expectSubscribeCalls: 2, - expectReq: &lnrpc.UnlockWalletRequest{ - WalletPassword: []byte("pw"), - RecoveryWindow: 0, - StatelessInit: false, - }, - }, - - // Unimplemented StateService skips unlock wait. - { - name: "wait_unlocked_unimplemented", - readPasswordRet: []byte("pw"), - stateStreams: []stateStreamSpec{ - { - states: []lnrpc.WalletState{ - lnrpc.WalletState_LOCKED, - }, - }, - { - err: status.Error( - codes.Unimplemented, "no state", - ), - }, - }, - expectReadPasswordCalls: 1, - expectUnlockCalls: 1, - expectSubscribeCalls: 2, - expectReq: &lnrpc.UnlockWalletRequest{ - WalletPassword: []byte("pw"), - RecoveryWindow: 0, - StatelessInit: false, - }, - }, - - // Unavailable StateService skips unlock wait. - { - name: "wait_unlocked_unavailable", - readPasswordRet: []byte("pw"), - stateStreams: []stateStreamSpec{ - { - states: []lnrpc.WalletState{ - lnrpc.WalletState_LOCKED, - }, - }, - { - err: status.Error( - codes.Unavailable, "no state", - ), - }, - }, - expectReadPasswordCalls: 1, - expectUnlockCalls: 1, - expectSubscribeCalls: 2, - expectReq: &lnrpc.UnlockWalletRequest{ - WalletPassword: []byte("pw"), - RecoveryWindow: 0, - StatelessInit: false, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Build the CLI context with unlock flags and args. - ctx := newUnlockContext(t, tc.args) - - // Create fake clients with scripted responses. - unlocker := &fakeUnlockerClient{ - unlockErr: tc.unlockerErr, - } - stateClient := &fakeStateClient{ - streams: tc.stateStreams, - } - - // Track cleanup for the unlocker client. - unlockerCleaned := false - getUnlockerClient := func( - *cli.Context) (lnrpc.WalletUnlockerClient, - func()) { - - return unlocker, func() { - unlockerCleaned = true - } - } - - // Track cleanup for the state client. - stateCleaned := false - getStateClient := func(*cli.Context) (lnrpc.StateClient, - func()) { - - return stateClient, func() { - stateCleaned = true - } - } - - // Capture password prompt and inject return values. - readPrompt := "" - readCalls := 0 - readPasswordFn := func(prompt string) ([]byte, error) { - readPrompt = prompt - readCalls++ - - return tc.readPasswordRet, tc.readPasswordErr - } - - // Provide a deterministic context without signal - // handling. - contextCalls := 0 - getContextFn := func() context.Context { - contextCalls++ - - return t.Context() - } - - // Provide stdin input via injected reader. - stdin := strings.NewReader(tc.stdinInput) - - // Execute unlockWithDeps with injected dependencies. - err := unlockWithDeps( - ctx, readPasswordFn, getUnlockerClient, - getStateClient, getContextFn, stdin, - ) - - // Assert error behavior. - if tc.expectErr != "" { - require.ErrorContains(t, err, tc.expectErr) - } else { - require.NoError(t, err) - } - - // Verify password prompt usage. - require.Equal(t, tc.expectReadPasswordCalls, readCalls) - if readCalls > 0 { - require.Equal( - t, "Input wallet password: ", - readPrompt, - ) - } - - // Verify client usage and cleanup behavior. - require.Equal( - t, tc.expectUnlockCalls, unlocker.unlockCalls, - ) - require.Equal( - t, tc.expectSubscribeCalls, - stateClient.subscribeCalls, - ) - require.True(t, unlockerCleaned) - require.True(t, stateCleaned) - require.Equal(t, 1, contextCalls) - - // Verify the unlock request fields when applicable. - if tc.expectReq != nil { - require.NotNil(t, unlocker.lastReq) - require.Equal(t, tc.expectReq.WalletPassword, - unlocker.lastReq.WalletPassword) - require.Equal(t, tc.expectReq.RecoveryWindow, - unlocker.lastReq.RecoveryWindow) - require.Equal(t, tc.expectReq.StatelessInit, - unlocker.lastReq.StatelessInit) - } else { - require.Nil(t, unlocker.lastReq) - } - - // Verify SubscribeState requests were well-formed. - require.Len( - t, stateClient.subscribeInputs, - stateClient.subscribeCalls, - ) - for _, req := range stateClient.subscribeInputs { - require.NotNil(t, req) - require.Equal( - t, &lnrpc.SubscribeStateRequest{}, req, - ) - } - }) - } -} diff --git a/cmd/commands/commands.go b/cmd/commands/commands.go index e2726bb4f..fd8dc2092 100644 --- a/cmd/commands/commands.go +++ b/cmd/commands/commands.go @@ -16,8 +16,8 @@ import ( "strings" "sync" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/jessevdk/go-flags" "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/lnrpc" @@ -402,14 +402,6 @@ var estimateFeeCommand = cli.Command{ "transaction *should* confirm in", }, coinSelectionStrategyFlag, - cli.StringSliceFlag{ - Name: "utxo", - Usage: "a utxo specified as outpoint(tx:idx) which " + - "will be used as input for the transaction " + - "to be estimated. This flag can be " + - "repeatedly used to specify multiple utxos " + - "as inputs.", - }, }, Action: actionDecorator(estimateFees), } @@ -431,21 +423,10 @@ func estimateFees(ctx *cli.Context) error { client, cleanUp := getClient(ctx) defer cleanUp() - var inputs []*lnrpc.OutPoint - if ctx.IsSet("utxo") { - utxos := ctx.StringSlice("utxo") - - inputs, err = lnd.UtxosToOutpoints(utxos) - if err != nil { - return fmt.Errorf("unable to decode utxos: %w", err) - } - } - resp, err := client.EstimateFee(ctxc, &lnrpc.EstimateFeeRequest{ AddrToAmount: amountToAddr, TargetConf: int32(ctx.Int64("conf_target")), CoinSelectionStrategy: coinSelectionStrategy, - Inputs: inputs, }) if err != nil { return err @@ -626,7 +607,7 @@ func sendCoins(ctx *cli.Context) error { if ctx.IsSet("utxo") { utxos := ctx.StringSlice("utxo") - outpoints, err = lnd.UtxosToOutpoints(utxos) + outpoints, err = UtxosToOutpoints(utxos) if err != nil { return fmt.Errorf("unable to decode utxos: %w", err) } @@ -769,6 +750,7 @@ func listUnspent(ctx *cli.Context) error { cli.ShowCommandHelp(ctx, "listunspent") return nil } + args = args.Tail() } unconfirmedOnly := ctx.Bool("unconfirmed_only") @@ -802,12 +784,12 @@ func listUnspent(ctx *cli.Context) error { // to stdout. At the moment, this filters out the raw txid bytes from // each utxo's outpoint and only prints the txid string. var listUnspentResp = struct { - Utxos []*lnd.Utxo `json:"utxos"` + Utxos []*Utxo `json:"utxos"` }{ - Utxos: make([]*lnd.Utxo, 0, len(resp.Utxos)), + Utxos: make([]*Utxo, 0, len(resp.Utxos)), } for _, protoUtxo := range resp.Utxos { - utxo := lnd.NewUtxoFromProto(protoUtxo) + utxo := NewUtxoFromProto(protoUtxo) listUnspentResp.Utxos = append(listUnspentResp.Utxos, utxo) } @@ -2807,14 +2789,12 @@ func updateChannelPolicy(ctx *cli.Context) error { // to stdout. At the moment, this filters out the raw txid bytes from // each failed update's outpoint and only prints the txid string. var listFailedUpdateResp = struct { - FailedUpdates []*lnd.FailedUpdate `json:"failed_updates"` + FailedUpdates []*FailedUpdate `json:"failed_updates"` }{ - FailedUpdates: make( - []*lnd.FailedUpdate, 0, len(resp.FailedUpdates), - ), + FailedUpdates: make([]*FailedUpdate, 0, len(resp.FailedUpdates)), } for _, protoUpdate := range resp.FailedUpdates { - failedUpdate := lnd.NewFailedUpdateFromProto(protoUpdate) + failedUpdate := NewFailedUpdateFromProto(protoUpdate) listFailedUpdateResp.FailedUpdates = append( listFailedUpdateResp.FailedUpdates, failedUpdate) } @@ -2992,6 +2972,19 @@ func exportChanBackup(ctx *cli.Context) error { // TODO(roasbeef): support for export | restore ? + var chanPoints []string + for _, chanPoint := range chanBackup.MultiChanBackup.ChanPoints { + txid, err := chainhash.NewHash(chanPoint.GetFundingTxidBytes()) + if err != nil { + return err + } + + chanPoints = append(chanPoints, wire.OutPoint{ + Hash: *txid, + Index: chanPoint.OutputIndex, + }.String()) + } + printRespJSON(chanBackup) return nil diff --git a/cmd/commands/main.go b/cmd/commands/main.go index 0c0997a2c..a11b63b9d 100644 --- a/cmd/commands/main.go +++ b/cmd/commands/main.go @@ -14,8 +14,8 @@ import ( "strings" "syscall" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/lncfg" @@ -497,7 +497,6 @@ func Main() { feeReportCommand, updateChannelPolicyCommand, forwardingHistoryCommand, - deleteFwdHistoryCommand, exportChanBackupCommand, verifyChanBackupCommand, restoreChanBackupCommand, diff --git a/types.go b/cmd/commands/types.go similarity index 98% rename from types.go rename to cmd/commands/types.go index f5a125b62..2a82e7100 100644 --- a/types.go +++ b/cmd/commands/types.go @@ -1,4 +1,4 @@ -package lnd +package commands import ( "encoding/hex" @@ -7,7 +7,7 @@ import ( "strconv" "strings" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/lnrpc" ) diff --git a/cmd/commands/walletrpc_active.go b/cmd/commands/walletrpc_active.go index 126a1340e..9f955bf61 100644 --- a/cmd/commands/walletrpc_active.go +++ b/cmd/commands/walletrpc_active.go @@ -15,12 +15,11 @@ import ( "strconv" "strings" - "github.com/btcsuite/btcd/address/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/psbt/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chanfunding" @@ -38,7 +37,6 @@ var ( fundPsbtCommand, fundTemplatePsbtCommand, finalizePsbtCommand, - signPsbtCommand, }, } @@ -87,7 +85,6 @@ func walletCommands() []cli.Command { listSweepsCommand, labelTxCommand, publishTxCommand, - submitPackageCommand, getTxCommand, removeTxCommand, releaseOutputCommand, @@ -333,7 +330,7 @@ func bumpFee(ctx *cli.Context) error { } // Validate and parse the relevant arguments/flags. - protoOutPoint, err := lnd.NewProtoOutPoint(ctx.Args().Get(0)) + protoOutPoint, err := NewProtoOutPoint(ctx.Args().Get(0)) if err != nil { return err } @@ -716,77 +713,6 @@ func publishTransaction(ctx *cli.Context) error { return nil } -var submitPackageCommand = cli.Command{ - Name: "submitpackage", - Usage: "Submit a package of related transactions for atomic " + - "validation and acceptance.", - ArgsUsage: "parent_tx_hex... child_tx_hex", - Description: ` - Submit a package of related, topologically-sorted raw transactions - (unconfirmed parents first and the child last) to the chain backend - for atomic validation and acceptance via the submitpackage RPC. - - This allows a zero-fee v3/TRUC parent to be accepted via its - fee-paying CPFP child, which a standalone broadcast would reject. - Each argument is a hex-encoded raw transaction. - `, - Flags: []cli.Flag{ - cli.Uint64Flag{ - Name: "sat_per_vbyte", - Usage: "(optional) the maximum fee rate in sat/vByte " + - "allowed for any transaction in the package; " + - "omit to use the node default, set 0 to " + - "disable the limit", - }, - }, - Action: actionDecorator(submitPackage), -} - -func submitPackage(ctx *cli.Context) error { - ctxc := getContext() - - // Display the command's help message if we do not have at least one - // transaction. - if ctx.NArg() == 0 { - return cli.ShowCommandHelp(ctx, "submitpackage") - } - - walletClient, cleanUp := getWalletClient(ctx) - defer cleanUp() - - rawTxs := make([][]byte, 0, ctx.NArg()) - for _, arg := range ctx.Args() { - tx, err := hex.DecodeString(arg) - if err != nil { - return err - } - - rawTxs = append(rawTxs, tx) - } - - // Only set the max fee rate when explicitly provided; otherwise leave - // it unset so the node applies its default. - var satPerVByte *uint64 - if ctx.IsSet("sat_per_vbyte") { - rate := ctx.Uint64("sat_per_vbyte") - satPerVByte = &rate - } - - resp, err := walletClient.SubmitPackage( - ctxc, &walletrpc.SubmitPackageRequest{ - RawTxs: rawTxs, - SatPerVbyte: satPerVByte, - }, - ) - if err != nil { - return err - } - - printRespJSON(resp) - - return nil -} - var getTxCommand = cli.Command{ Name: "gettx", Usage: "Returns details of a transaction.", @@ -886,11 +812,11 @@ func removeTransaction(ctx *cli.Context) error { // utxoLease contains JSON annotations for a lease on an unspent output. type utxoLease struct { - ID string `json:"id"` - OutPoint lnd.OutPoint `json:"outpoint"` - Expiration uint64 `json:"expiration"` - PkScript []byte `json:"pk_script"` - Value uint64 `json:"value"` + ID string `json:"id"` + OutPoint OutPoint `json:"outpoint"` + Expiration uint64 `json:"expiration"` + PkScript []byte `json:"pk_script"` + Value uint64 `json:"value"` } // fundPsbtResponse is a struct that contains JSON annotations for nice result @@ -1097,7 +1023,7 @@ func fundTemplatePsbt(ctx *cli.Context) error { err) } - addr, err := address.DecodeAddress( + addr, err := btcutil.DecodeAddress( addrStr, chainParams, ) if err != nil { @@ -1432,7 +1358,7 @@ func fundPsbt(ctx *cli.Context) error { } for idx, input := range inputs { - op, err := lnd.NewProtoOutPoint(input) + op, err := NewProtoOutPoint(input) if err != nil { return fmt.Errorf("error parsing "+ "UTXO outpoint %d: %v", idx, @@ -1521,7 +1447,7 @@ func marshallLocks(lockedUtxos []*walletrpc.UtxoLease) []*utxoLease { for idx, lock := range lockedUtxos { jsonLocks[idx] = &utxoLease{ ID: hex.EncodeToString(lock.Id), - OutPoint: lnd.NewOutPointFromProto(lock.Outpoint), + OutPoint: NewOutPointFromProto(lock.Outpoint), Expiration: lock.Expiration, PkScript: lock.PkScript, Value: lock.Value, @@ -1545,9 +1471,7 @@ var finalizePsbtCommand = cli.Command{ Description: ` The finalize command expects a partial transaction with all inputs and outputs fully declared and tries to sign all inputs that belong to - the wallet (only standard, single-signature P2WKH, NP2WKH and P2TR - inputs, for any other use cases use the 'sign' subcommand instead). - Lnd must be the last signer of the transaction. That means, + the wallet. Lnd must be the last signer of the transaction. That means, if there are any unsigned non-witness inputs or inputs without UTXO information attached or inputs without witness data that do not belong to lnd's wallet, this method will fail. If no error is returned, the @@ -1617,85 +1541,6 @@ func finalizePsbt(ctx *cli.Context) error { return nil } -// signPsbtResponse is a struct that contains JSON annotations for nice -// result serialization. -type signPsbtResponse struct { - Psbt string `json:"psbt"` - SignedInputIndexes []uint32 `json:"signed_input_indexes"` -} - -var signPsbtCommand = cli.Command{ - Name: "sign", - Usage: "Sign a Partially Signed Bitcoin Transaction (PSBT).", - ArgsUsage: "funded_psbt", - Description: ` - The sign command expects a partial transaction with all inputs - and outputs fully declared and tries to sign all inputs that can be - identified by the wallet as belonging to it. All fields to identify a - signer, such as root key fingerprints, derivation paths and public keys, - must be set to be able to sign the transaction. - - This method does NOT finalize or publish the transaction after it's been - signed. If lnd was the last signer and all required signatures are - present, use the finalize command to finalize the transaction. - `, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "funded_psbt", - Usage: "the base64 encoded PSBT to sign", - }, - }, - Action: actionDecorator(signPsbt), -} - -func signPsbt(ctx *cli.Context) error { - ctxc := getContext() - - // Display the command's help message if we do not have the expected - // number of arguments/flags. - if ctx.NArg() > 1 || ctx.NumFlags() > 1 { - return cli.ShowCommandHelp(ctx, "sign") - } - - var ( - args = ctx.Args() - psbtBase64 string - ) - switch { - case ctx.IsSet("funded_psbt"): - psbtBase64 = ctx.String("funded_psbt") - case args.Present(): - psbtBase64 = args.First() - default: - return fmt.Errorf("funded_psbt argument missing") - } - - psbtBytes, err := base64.StdEncoding.DecodeString(psbtBase64) - if err != nil { - return err - } - req := &walletrpc.SignPsbtRequest{ - FundedPsbt: psbtBytes, - } - - walletClient, cleanUp := getWalletClient(ctx) - defer cleanUp() - - response, err := walletClient.SignPsbt(ctxc, req) - if err != nil { - return err - } - - printJSON(&signPsbtResponse{ - Psbt: base64.StdEncoding.EncodeToString( - response.SignedPsbt, - ), - SignedInputIndexes: response.SignedInputs, - }) - - return nil -} - var leaseOutputCommand = cli.Command{ Name: "leaseoutput", Usage: "Lease an output.", @@ -1733,7 +1578,7 @@ func leaseOutput(ctx *cli.Context) error { } outpointStr := ctx.String("outpoint") - outpoint, err := lnd.NewProtoOutPoint(outpointStr) + outpoint, err := NewProtoOutPoint(outpointStr) if err != nil { return fmt.Errorf("error parsing outpoint: %w", err) } @@ -1818,7 +1663,7 @@ func releaseOutput(ctx *cli.Context) error { return fmt.Errorf("outpoint argument missing") } - outpoint, err := lnd.NewProtoOutPoint(outpointStr) + outpoint, err := NewProtoOutPoint(outpointStr) if err != nil { return fmt.Errorf("error parsing outpoint: %w", err) } @@ -2085,6 +1930,7 @@ func signMessageWithAddr(ctx *cli.Context) error { case ctx.Args().Present(): msg = []byte(args.First()) + args = args.Tail() default: return fmt.Errorf("msg argument missing") @@ -2192,6 +2038,7 @@ func verifyMessageWithAddr(ctx *cli.Context) error { case ctx.Args().Present(): msg = []byte(args.First()) + args = args.Tail() default: return fmt.Errorf("msg argument missing") diff --git a/cmd/commands/walletrpc_types.go b/cmd/commands/walletrpc_types.go index f3a025c39..790114c77 100644 --- a/cmd/commands/walletrpc_types.go +++ b/cmd/commands/walletrpc_types.go @@ -1,9 +1,6 @@ package commands -import ( - "github.com/lightningnetwork/lnd" - "github.com/lightningnetwork/lnd/lnrpc/walletrpc" -) +import "github.com/lightningnetwork/lnd/lnrpc/walletrpc" // PendingSweep is a CLI-friendly type of the walletrpc.PendingSweep proto. We // use this to show more useful string versions of byte slices and enums. @@ -12,16 +9,16 @@ import ( // here. Instead, we should rely on the struct defined in the proto // `PendingSweepsResponse` only. type PendingSweep struct { - OutPoint lnd.OutPoint `json:"outpoint"` - WitnessType string `json:"witness_type"` - AmountSat uint32 `json:"amount_sat"` - SatPerVByte uint32 `json:"sat_per_vbyte"` - BroadcastAttempts uint32 `json:"broadcast_attempts"` - RequestedSatPerVByte uint32 `json:"requested_sat_per_vbyte"` - Immediate bool `json:"immediate"` - Budget uint64 `json:"budget"` - DeadlineHeight uint32 `json:"deadline_height"` - MaturityHeight uint32 `json:"maturity_height"` + OutPoint OutPoint `json:"outpoint"` + WitnessType string `json:"witness_type"` + AmountSat uint32 `json:"amount_sat"` + SatPerVByte uint32 `json:"sat_per_vbyte"` + BroadcastAttempts uint32 `json:"broadcast_attempts"` + RequestedSatPerVByte uint32 `json:"requested_sat_per_vbyte"` + Immediate bool `json:"immediate"` + Budget uint64 `json:"budget"` + DeadlineHeight uint32 `json:"deadline_height"` + MaturityHeight uint32 `json:"maturity_height"` NextBroadcastHeight uint32 `json:"next_broadcast_height"` RequestedConfTarget uint32 `json:"requested_conf_target"` @@ -32,9 +29,7 @@ type PendingSweep struct { // its corresponding CLI-friendly type. func NewPendingSweepFromProto(pendingSweep *walletrpc.PendingSweep) *PendingSweep { return &PendingSweep{ - OutPoint: lnd.NewOutPointFromProto( - pendingSweep.Outpoint, - ), + OutPoint: NewOutPointFromProto(pendingSweep.Outpoint), WitnessType: pendingSweep.WitnessType.String(), AmountSat: pendingSweep.AmountSat, SatPerVByte: uint32(pendingSweep.SatPerVbyte), diff --git a/config.go b/config.go index 5b69e943a..65454e6b7 100644 --- a/config.go +++ b/config.go @@ -9,7 +9,6 @@ import ( "errors" "fmt" "io" - "math" "net" "os" "os/user" @@ -20,8 +19,8 @@ import ( "strings" "time" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" flags "github.com/jessevdk/go-flags" "github.com/lightninglabs/neutrino" "github.com/lightningnetwork/lnd/autopilot" @@ -42,7 +41,6 @@ import ( "github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/onionmessage" "github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/signal" "github.com/lightningnetwork/lnd/tor" @@ -82,6 +80,7 @@ const ( defaultTorDNSHost = "soa.nodes.lightning.directory" defaultTorDNSPort = 53 defaultTorControlPort = 9051 + defaultTorV2PrivateKeyFilename = "v2_onion_private_key" defaultTorV3PrivateKeyFilename = "v3_onion_private_key" // defaultZMQReadDeadline is the default read deadline to be used for @@ -213,10 +212,6 @@ const ( // commitment. defaultChannelCommitBatchSize = 10 - // defaultFwdHistoryDeleteBatchSize is the default number of forwarding - // events deleted per database transaction when purging history. - defaultFwdHistoryDeleteBatchSize = 10_000 - // defaultCoinSelectionStrategy is the coin selection strategy that is // used by default to fund transactions. defaultCoinSelectionStrategy = "largest" @@ -475,8 +470,6 @@ type Config struct { ChannelCommitBatchSize uint32 `long:"channel-commit-batch-size" description:"The maximum number of channel state updates that is accumulated before signing a new commitment."` - FwdHistoryDeleteBatchSize int `long:"fwd-history-delete-batch-size" description:"The number of forwarding events deleted per database transaction when running deletefwdhistory. Lower this on resource-constrained nodes to reduce lock contention (max: 50000)."` - KeepFailedPaymentAttempts bool `long:"keep-failed-payment-attempts" description:"Keeps persistent record of all failed payment attempts for successfully settled payments."` StoreFinalHtlcResolutions bool `long:"store-final-htlc-resolutions" description:"Persistently store the final resolution of incoming htlcs."` @@ -596,15 +589,6 @@ type Config struct { // NoDisconnectOnPongFailure controls if we'll disconnect if a peer // doesn't respond to a pong in time. NoDisconnectOnPongFailure bool `long:"no-disconnect-on-pong-failure" description:"If true, a peer will *not* be disconnected if a pong is not received in time or is mismatched. Defaults to false, meaning peers *will* be disconnected on pong failure."` - - // UpfrontShutdownAddr specifies an address that our funds will be paid - // out to on cooperative channel close. This applies to all new channel - // opens unless overridden by an option in openchannel or by a channel - // acceptor. - // Note: If this field is set when opening a channel with a peer that - // does not advertise support for the upfront shutdown feature, the - // channel open will fail. - UpfrontShutdownAddr string `long:"upfront-shutdown-address" description:"The address to which funds will be paid out during a cooperative channel close. This applies to all channels opened after this option is set, unless overridden for a specific channel opening. Note: If this option is set, any channel opening will fail if the peer does not explicitly advertise support for the upfront-shutdown feature bit."` } // GRPCConfig holds the configuration options for the gRPC server. @@ -636,43 +620,6 @@ type GRPCConfig struct { ClientAllowPingWithoutStream bool `long:"client-allow-ping-without-stream" description:"If true, the server allows keepalive pings from the client even when there are no active gRPC streams. This might be useful to keep the underlying HTTP/2 connection open for future requests."` } -// maxOnionMsgWireSize is the largest on-the-wire size in bytes, including -// the 2-byte message type prefix, that an OnionMessage can take. This is -// the value the rate limiter charges via OnionMessage.WireSize() for a -// max-sized message and therefore the tightest meaningful lower bound on -// the configured burst: anything smaller would reject every max-sized -// message even though the configured rate is positive. -const maxOnionMsgWireSize = 2 + lnwire.MaxMsgBody - -// validateOnionMsgLimiter validates a single onion message rate limiter -// kbps/burst-bytes pair. Both zero means "disabled"; both strictly positive -// means "enabled"; a mismatched pair is rejected so that operator typos -// surface at startup instead of silently disabling the limiter via the -// constructor fallback path. When enabled, burst-bytes must also be at -// least maxOnionMsgWireSize so that a single max-sized onion message -// (lnwire.MaxMsgBody bytes of body plus the 2-byte message-type prefix -// that WireSize charges for) can always fit in the token bucket; -// otherwise rate.Limiter.AllowN would reject every call and silently -// disable onion message forwarding. -func validateOnionMsgLimiter(name string, kbps, burstBytes uint64) error { - if (kbps > 0) != (burstBytes > 0) { - return fmt.Errorf("%s kbps and burst-bytes must both be "+ - "positive or both be zero; got kbps=%v "+ - "burst-bytes=%v", name, kbps, burstBytes) - } - if burstBytes > 0 && burstBytes < maxOnionMsgWireSize { - return fmt.Errorf("%s burst-bytes=%v must be at least %v "+ - "so a single max-sized onion message can fit in "+ - "the bucket", name, burstBytes, maxOnionMsgWireSize) - } - if burstBytes > uint64(math.MaxInt) { - return fmt.Errorf("%s burst-bytes=%v exceeds maximum %v", - name, burstBytes, math.MaxInt) - } - - return nil -} - // DefaultConfig returns all default values for the Config struct. // //nolint:ll @@ -817,16 +764,6 @@ func DefaultConfig() Config { Backoff: defaultLeaderCheckBackoff, }, }, - // Only the onion message rate limiter fields are explicitly - // initialized here; all other ProtocolOptions fields rely on - // Go zero values, which happen to be the historical defaults - // for those flags. - ProtocolOptions: &lncfg.ProtocolOptions{ - OnionMsgPeerKbps: onionmessage.DefaultPeerOnionMsgKbps, - OnionMsgPeerBurstBytes: onionmessage.DefaultPeerOnionMsgBurstBytes, - OnionMsgGlobalKbps: onionmessage.DefaultGlobalOnionMsgKbps, - OnionMsgGlobalBurstBytes: onionmessage.DefaultGlobalOnionMsgBurstBytes, - }, Gossip: &lncfg.Gossip{ MaxChannelUpdateBurst: discovery.DefaultMaxChannelUpdateBurst, ChannelUpdateInterval: discovery.DefaultChannelUpdateInterval, @@ -861,7 +798,6 @@ func DefaultConfig() Config { ChannelCommitInterval: defaultChannelCommitInterval, PendingCommitInterval: defaultPendingCommitInterval, ChannelCommitBatchSize: defaultChannelCommitBatchSize, - FwdHistoryDeleteBatchSize: defaultFwdHistoryDeleteBatchSize, CoinSelectionStrategy: defaultCoinSelectionStrategy, KeepFailedPaymentAttempts: defaultKeepFailedPaymentAttempts, RemoteSigner: &lncfg.RemoteSigner{ @@ -1176,27 +1112,6 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser, return nil, mkErr("error validating autopilot: %v", err) } - // Validate the onion message rate limiter configuration. We reject - // the mismatched case where one of kbps/burst-bytes is strictly - // positive but the other is zero, which would silently disable the - // limiter and leave the operator unprotected. Both zero is fine and - // explicitly means "disabled"; both positive is fine and enables - // the limiter. - if err := validateOnionMsgLimiter( - "protocol.onion-msg-peer", - cfg.ProtocolOptions.OnionMsgPeerKbps, - cfg.ProtocolOptions.OnionMsgPeerBurstBytes, - ); err != nil { - return nil, mkErr("%s", err) - } - if err := validateOnionMsgLimiter( - "protocol.onion-msg-global", - cfg.ProtocolOptions.OnionMsgGlobalKbps, - cfg.ProtocolOptions.OnionMsgGlobalBurstBytes, - ); err != nil { - return nil, mkErr("%s", err) - } - // Ensure that --maxchansize is properly handled when set by user. // For non-Wumbo channels this limit remains 16777215 satoshis by default // as specified in BOLT-02. For wumbo channels this limit is 1,000,000,000. @@ -1296,22 +1211,41 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser, return nil, mkErr(str) } - if cfg.DisableListen && cfg.Tor.V3 { + switch { + case cfg.Tor.V2 && cfg.Tor.V3: + return nil, mkErr("either tor.v2 or tor.v3 can be set, " + + "but not both") + case cfg.DisableListen && (cfg.Tor.V2 || cfg.Tor.V3): return nil, mkErr("listening must be enabled when enabling " + "inbound connections over Tor") } - if cfg.Tor.PrivateKeyPath == "" && cfg.Tor.V3 { - cfg.Tor.PrivateKeyPath = filepath.Join( - lndDir, defaultTorV3PrivateKeyFilename, - ) + if cfg.Tor.PrivateKeyPath == "" { + switch { + case cfg.Tor.V2: + cfg.Tor.PrivateKeyPath = filepath.Join( + lndDir, defaultTorV2PrivateKeyFilename, + ) + case cfg.Tor.V3: + cfg.Tor.PrivateKeyPath = filepath.Join( + lndDir, defaultTorV3PrivateKeyFilename, + ) + } } - if cfg.Tor.WatchtowerKeyPath == "" && cfg.Tor.V3 { - cfg.Tor.WatchtowerKeyPath = filepath.Join( - cfg.Watchtower.TowerDir, - defaultTorV3PrivateKeyFilename, - ) + if cfg.Tor.WatchtowerKeyPath == "" { + switch { + case cfg.Tor.V2: + cfg.Tor.WatchtowerKeyPath = filepath.Join( + cfg.Watchtower.TowerDir, + defaultTorV2PrivateKeyFilename, + ) + case cfg.Tor.V3: + cfg.Tor.WatchtowerKeyPath = filepath.Join( + cfg.Watchtower.TowerDir, + defaultTorV3PrivateKeyFilename, + ) + } } // Set up the network-related functions that will be used throughout @@ -1837,19 +1771,6 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser, maxPendingCommitInterval) } - // Warn and clamp fwd-history-delete-batch-size if it exceeds the DB - // layer maximum. The DB silently clamps anyway, but surfacing this at - // startup gives the operator immediate feedback that their configured - // value is not being honoured. - if cfg.FwdHistoryDeleteBatchSize > channeldb.MaxResponseEvents { - ltndLog.Warnf("fwd-history-delete-batch-size=%d exceeds "+ - "maximum (%d), clamping to maximum", - cfg.FwdHistoryDeleteBatchSize, - channeldb.MaxResponseEvents) - - cfg.FwdHistoryDeleteBatchSize = channeldb.MaxResponseEvents - } - if err := cfg.Gossip.Parse(); err != nil { return nil, mkErr("error parsing gossip syncer: %v", err) } @@ -1947,15 +1868,6 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser, return nil, mkErr("unable to parse node color: %v", err) } - // Validate TrickleDelay and default to 1ms if non-positive to ensure - // the trickle timer can still function. - if cfg.TrickleDelay <= 0 { - srvrLog.Infof("TrickleDelay is non-positive (%v ms), "+ - "setting to 1ms", cfg.TrickleDelay) - - cfg.TrickleDelay = 1 - } - // All good, return the sanitized result. return &cfg, nil } diff --git a/config_builder.go b/config_builder.go index 25ec8401b..d3ca5e2b2 100644 --- a/config_builder.go +++ b/config_builder.go @@ -15,10 +15,9 @@ import ( "sync/atomic" "time" - "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btclog/v2" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/waddrmgr" @@ -31,15 +30,12 @@ import ( "github.com/lightninglabs/neutrino/pushtx" "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/chainntnfs" - "github.com/lightningnetwork/lnd/chainparams" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/funding" graphdb "github.com/lightningnetwork/lnd/graph/db" - graphdbmig1 "github.com/lightningnetwork/lnd/graph/db/migration1" - graphmig1sqlc "github.com/lightningnetwork/lnd/graph/db/migration1/sqlc" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/keychain" @@ -53,8 +49,6 @@ import ( "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/msgmux" paymentsdb "github.com/lightningnetwork/lnd/payments/db" - paymentsmig1 "github.com/lightningnetwork/lnd/payments/db/migration1" - paymentsmig1sqlc "github.com/lightningnetwork/lnd/payments/db/migration1/sqlc" "github.com/lightningnetwork/lnd/rpcperms" "github.com/lightningnetwork/lnd/signal" "github.com/lightningnetwork/lnd/sqldb" @@ -80,10 +74,6 @@ const ( // graphMigration is the version number for the graph migration // that migrates the KV graph to the native SQL schema. graphMigration = 10 - - // paymentMigration is the version number for the payments migration - // that migrates KV payments to the native SQL schema. - paymentMigration = 14 ) // GrpcRegistrar is an interface that must be satisfied by an external subserver @@ -1058,9 +1048,6 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( chanGraphOpts := []graphdb.ChanGraphOption{ graphdb.WithUseGraphCache(!cfg.DB.NoGraphCache), - graphdb.WithAsyncGraphCachePopulation( - !cfg.DB.SyncGraphCacheLoad, - ), } // We want to pre-allocate the channel graph cache according to what we @@ -1073,15 +1060,6 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( ) } - // KV-over-SQL backends (sqlite, postgres) opt in to closing channels - // via tombstone markers because nested-bucket deletes inside a write - // transaction translate into a long-running ON DELETE CASCADE on the - // kvdb-on-SQL schema, holding the database write-lock for many seconds - // on long-lived channels. bbolt and etcd keep the synchronous one-shot - // close path, where nested-bucket deletion is already cheap. - tombstoneClosedChans := cfg.DB.Backend == lncfg.SqliteBackend || - cfg.DB.Backend == lncfg.PostgresBackend - dbOptions := []channeldb.OptionModifier{ channeldb.OptionDryRunMigration(cfg.DryRunMigration), channeldb.OptionStoreFinalHtlcResolutions( @@ -1091,7 +1069,6 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( channeldb.OptionNoRevLogAmtData(cfg.DB.NoRevLogAmtData), channeldb.OptionGcDecayedLog(cfg.DB.NoGcDecayedLog), channeldb.OptionWithDecayedLogDB(dbs.DecayedLogDB), - channeldb.OptionTombstoneClosedChannels(tombstoneClosedChans), } // Otherwise, we'll open two instances, one for the state we only need @@ -1117,7 +1094,7 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( // The graph store implementation we will use depends on whether // native SQL is enabled or not. - var graphStore graphdb.Store + var graphStore graphdb.V1Store // Instantiate a native SQL store if the flag is set. if d.cfg.DB.UseNativeSQL { @@ -1149,16 +1126,6 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( // Set the invoice bucket tombstone to indicate // that the migration has been completed. - // - // TODO(ziggie): The tombstone is currently - // set inside the SQL transaction callback, - // which is fragile: if the SQL transaction - // is retried (e.g. on a serialization - // error), the KV tombstone is written before - // the SQL commit is confirmed. Move this to - // run after ApplyAllMigrations returns so - // the tombstone is only set once the - // migration is durably committed. d.logger.Debugf("Setting invoice bucket " + "tombstone") @@ -1167,14 +1134,13 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( } graphMig := func(tx *sqlc.Queries) error { - cfg := &graphdbmig1.SQLStoreConfig{ + cfg := &graphdb.SQLStoreConfig{ //nolint:ll ChainHash: *d.cfg.ActiveNetParams.GenesisHash, QueryCfg: queryCfg, } - err := graphdbmig1.MigrateGraphToSQL( - ctx, cfg, dbs.ChanStateDB.Backend, - graphmig1sqlc.New(tx.GetTx()), + err := graphdb.MigrateGraphToSQL( + ctx, cfg, dbs.ChanStateDB.Backend, tx, ) if err != nil { return fmt.Errorf("failed to migrate "+ @@ -1184,23 +1150,6 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( return nil } - paymentMig := func(tx *sqlc.Queries) error { - err := paymentsmig1.MigratePaymentsKVToSQL( - ctx, - dbs.ChanStateDB.Backend, - paymentsmig1sqlc.New(tx.GetTx()), - &paymentsmig1.SQLStoreConfig{ - QueryCfg: queryCfg, - }, - ) - if err != nil { - return fmt.Errorf("failed to migrate "+ - "payments to SQL: %w", err) - } - - return nil - } - // Make sure we attach the custom migration function to // the correct migration version. for i := 0; i < len(migrations); i++ { @@ -1210,17 +1159,11 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( migrations[i].MigrationFn = invoiceMig continue - case graphMigration: migrations[i].MigrationFn = graphMig continue - case paymentMigration: - migrations[i].MigrationFn = paymentMig - - continue - default: } @@ -1249,48 +1192,8 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( // With the DB ready and migrations applied, we can now create // the base DB and transaction executor for the native SQL - // stores. + // invoice store. baseDB := dbs.NativeSQLStore.GetBaseDB() - - // Validate that the database was initialised for the same - // network as the currently active network. This catches cases - // where a user accidentally reuses a database (e.g. via a - // postgres DSN or by copying a file) across different networks - // (e.g. mainnet → testnet), which would otherwise lead to - // silent data corruption. This check applies to all native SQL - // backends. - // - // If migrations are explicitly skipped, we also skip this check - // because the chain_params table may not exist yet. We check - // only the active backend's flag since only one backend is - // used at a time. - var skipMigrations bool - switch d.cfg.DB.Backend { - case lncfg.SqliteBackend: - skipMigrations = d.cfg.DB.Sqlite.SkipMigrations - case lncfg.PostgresBackend: - skipMigrations = d.cfg.DB.Postgres.SkipMigrations - } - - if !skipMigrations { - chainParamsStore := chainparams.NewStore(baseDB) - err = chainParamsStore.ValidateNetwork( - ctx, d.cfg.ActiveNetParams.Params, - ) - if err != nil { - cleanUp() - d.logger.Error(err) - - return nil, nil, err - } - } else { - d.logger.Warnf("Database network validation skipped " + - "because SkipMigrations is enabled; " + - "cross-network database reuse would not be " + - "detected.") - } - - // Create the invoice store. invoiceExecutor := sqldb.NewTransactionExecutor( baseDB, func(tx *sql.Tx) invoices.SQLInvoiceQueries { return baseDB.WithTx(tx) @@ -1303,7 +1206,6 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( dbs.InvoiceDB = sqlInvoiceDB - // Create the graph store. graphExecutor := sqldb.NewTransactionExecutor( baseDB, func(tx *sql.Tx) graphdb.SQLQueries { return baseDB.WithTx(tx) @@ -1318,45 +1220,17 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( graphExecutor, graphDBOptions..., ) if err != nil { - cleanUp() err = fmt.Errorf("unable to get graph store: %w", err) d.logger.Error(err) return nil, nil, err } - - // Create the payments store. - paymentsExecutor := sqldb.NewTransactionExecutor( - baseDB, func(tx *sql.Tx) paymentsdb.SQLQueries { - return baseDB.WithTx(tx) - }, - ) - - sqlPaymentsDB, err := paymentsdb.NewSQLStore( - &paymentsdb.SQLStoreConfig{ - QueryCfg: queryCfg, - }, - paymentsExecutor, - ) - if err != nil { - cleanUp() - err = fmt.Errorf("unable to get payments store: %w", - err) - - return nil, nil, err - } - - dbs.PaymentsDB = sqlPaymentsDB } else { // Check if the invoice bucket tombstone is set. If it is, we // need to return and ask the user switch back to using the // native SQL store. - // - // NOTE: The invoice bucket tombstone acts as the system-wide - // guard against switching back to KV mode. ripInvoices, err := dbs.ChanStateDB.GetInvoiceBucketTombstone() if err != nil { - cleanUp() err = fmt.Errorf("unable to check invoice bucket "+ "tombstone: %w", err) d.logger.Error(err) @@ -1364,7 +1238,6 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( return nil, nil, err } if ripInvoices { - cleanUp() err = fmt.Errorf("invoices bucket tombstoned, please " + "switch back to native SQL") d.logger.Error(err) @@ -1378,25 +1251,8 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( databaseBackends.GraphDB, graphDBOptions..., ) if err != nil { - cleanUp() - return nil, nil, err } - - // Create the payments DB. - kvPaymentsDB, err := paymentsdb.NewKVStore( - dbs.ChanStateDB, - ) - if err != nil { - cleanUp() - - err = fmt.Errorf("unable to open payments DB: %w", err) - d.logger.Error(err) - - return nil, nil, err - } - - dbs.PaymentsDB = kvPaymentsDB } dbs.GraphDB, err = graphdb.NewChannelGraph(graphStore, chanGraphOpts...) @@ -1409,6 +1265,29 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( return nil, nil, err } + // Mount the payments DB which is only KV for now. + // + // TODO(ziggie): Add support for SQL payments DB. + // Mount the payments DB for the KV store. + paymentsDBOptions := []paymentsdb.OptionModifier{ + paymentsdb.WithKeepFailedPaymentAttempts( + cfg.KeepFailedPaymentAttempts, + ), + } + kvPaymentsDB, err := paymentsdb.NewKVStore( + dbs.ChanStateDB, + paymentsDBOptions..., + ) + if err != nil { + cleanUp() + + err = fmt.Errorf("unable to open payments DB: %w", err) + d.logger.Error(err) + + return nil, nil, err + } + dbs.PaymentsDB = kvPaymentsDB + // Wrap the watchtower client DB and make sure we clean up. if cfg.WtClient.Active { dbs.TowerClientDB, err = wtdb.OpenClientDB( @@ -1740,11 +1619,6 @@ func initNeutrinoBackend(ctx context.Context, cfg *Config, chainDir string, } cfg.Routing.AssumeChannelValid = !cfg.NeutrinoMode.ValidateChannels - // Validate neutrino headers import configuration. - if err := cfg.NeutrinoMode.Validate(); err != nil { - return nil, nil, err - } - // First we'll open the database file for neutrino, creating the // database if needed. We append the normalized network name here to // match the behavior of btcwallet. @@ -1846,24 +1720,6 @@ func initNeutrinoBackend(ctx context.Context, cfg *Config, chainDir string, PersistToDisk: cfg.NeutrinoMode.PersistFilters, } - // Configure headers import if both sources are specified. The - // chainimport package handles both HTTP URLs and local file paths - // transparently based on the source string prefix. - blockHdrSrc := cfg.NeutrinoMode.BlockHeadersSource - filterHdrSrc := cfg.NeutrinoMode.FilterHeadersSource - if blockHdrSrc != "" && filterHdrSrc != "" { - importCfg := &neutrino.HeadersImportConfig{ - BlockHeadersSource: blockHdrSrc, - FilterHeadersSource: filterHdrSrc, - } - - importCfg.ValidationFlags = neutrinoHeadersImportValidationFlags( - cfg.Bitcoin, - ) - - config.HeadersImport = importCfg - } - if cfg.NeutrinoMode.MaxPeers <= 0 { return nil, nil, fmt.Errorf("a non-zero number must be set " + "for neutrino max peers") @@ -1880,7 +1736,7 @@ func initNeutrinoBackend(ctx context.Context, cfg *Config, chainDir string, "client: %v", err) } - if err := neutrinoCS.Start(ctx); err != nil { + if err := neutrinoCS.Start(); err != nil { db.Close() return nil, nil, err } @@ -1896,21 +1752,6 @@ func initNeutrinoBackend(ctx context.Context, cfg *Config, chainDir string, return neutrinoCS, cleanUp, nil } -// neutrinoHeadersImportValidationFlags returns the blockchain validation -// flags to use when importing block headers via neutrino's chainimport -// package. Local test networks fall back to BFFastAdd to keep harness -// imports cheap; public networks keep contextual header validation enabled -// so the imported chain is held to the same standard as P2P headers. -func neutrinoHeadersImportValidationFlags( - chainCfg *lncfg.Chain) blockchain.BehaviorFlags { - - if chainCfg.IsLocalNetwork() { - return blockchain.BFFastAdd - } - - return blockchain.BFNone -} - // parseHeaderStateAssertion parses the user-specified neutrino header state // into a headerfs.FilterHeader. func parseHeaderStateAssertion(state string) (*headerfs.FilterHeader, error) { diff --git a/config_onion_ratelimit_test.go b/config_onion_ratelimit_test.go deleted file mode 100644 index 3ac6bd5c1..000000000 --- a/config_onion_ratelimit_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package lnd - -import ( - "testing" - - "github.com/lightningnetwork/lnd/lnwire" - "github.com/stretchr/testify/require" -) - -// TestValidateOnionMsgLimiter exercises every branch of -// validateOnionMsgLimiter: the happy-path cases (both zero, both positive -// with adequate burst) and every rejection branch (mismatched pair and -// undersized burst). Startup config validation is the first line of -// defense against a typo silently disabling the limiter, so every branch -// is exercised explicitly. -func TestValidateOnionMsgLimiter(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - kbps uint64 - burstBytes uint64 - wantErr string - }{ - { - name: "both zero disables", - kbps: 0, - burstBytes: 0, - }, - { - name: "both positive enables", - kbps: 512, - burstBytes: 8 * 32 * 1024, - }, - { - name: "large values pass", - kbps: 1_000_000, - burstBytes: 1_000_000, - }, - { - name: "burst exactly at min allowed", - kbps: 1, - burstBytes: 2 + lnwire.MaxMsgBody, - }, - { - name: "burst one below min max-msg wire size " + - "rejected", - kbps: 1, - burstBytes: 1 + lnwire.MaxMsgBody, - wantErr: "must be at least 65535", - }, - { - name: "positive kbps zero burst rejected", - kbps: 512, - burstBytes: 0, - wantErr: "kbps and burst-bytes must both be " + - "positive or both be zero", - }, - { - name: "zero kbps positive burst rejected", - kbps: 0, - burstBytes: 65_536, - wantErr: "kbps and burst-bytes must both be " + - "positive or both be zero", - }, - { - name: "burst below maxOnionMsgWireSize " + - "rejected", - kbps: 512, - burstBytes: 1024, - wantErr: "burst-bytes=1024 must be at least " + - "65535", - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - err := validateOnionMsgLimiter( - "test", tc.kbps, tc.burstBytes, - ) - if tc.wantErr == "" { - require.NoError(t, err) - return - } - require.Error(t, err) - require.Contains(t, err.Error(), tc.wantErr) - }) - } -} diff --git a/config_prod.go b/config_prod.go index 1b0a5c580..60dba8bb5 100644 --- a/config_prod.go +++ b/config_prod.go @@ -9,16 +9,6 @@ import ( "github.com/lightningnetwork/lnd/sqldb/sqlc" ) -// NOTE: This file (together with config_test_native_sql.go) contains -// build-tag-specific overrides that control which backend is used for certain -// stores. If any function in either file switches a store between KV and native -// SQL depending on the build tag, and the corresponding migration is later -// promoted from sqldb/migrations_dev.go into the mainline sqldb/migrations.go, -// you must also update the UseNativeSQL branch in BuildDatabase -// (config_builder.go) to use the native SQL backend for that store. Promoting -// the migration without updating the store means the production build will -// continue writing to the KV backend instead of SQL. - // RunTestSQLMigration is a build tag that indicates whether the test_native_sql // build tag is set. var RunTestSQLMigration = false diff --git a/config_test.go b/config_test.go index c312e0c16..2136068b5 100644 --- a/config_test.go +++ b/config_test.go @@ -27,12 +27,14 @@ func TestConfigToFlatMap(t *testing.T) { // Set deprecated fields. cfg.Bitcoin.Active = true + cfg.Tor.V2 = true result, deprecated, err := configToFlatMap(cfg) require.NoError(t, err) // Check that the deprecated option has been parsed out. require.Contains(t, deprecated, "bitcoin.active") + require.Contains(t, deprecated, "tor.v2") // Pick a couple of random values to check. require.Equal(t, DefaultLndDir, result["lnddir"]) @@ -117,62 +119,6 @@ func TestSupplyEnvValue(t *testing.T) { } } -// TestValidateConfigTrickleDelay tests that the TrickleDelay configuration -// is properly validated and defaulted in ValidateConfig. This test directly -// verifies the validation logic without going through the full ValidateConfig -// function which has many dependencies. -func TestValidateConfigTrickleDelay(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - trickleDelay int - expectedDelay int - }{ - { - name: "zero delay defaults to 1ms", - trickleDelay: 0, - expectedDelay: 1, - }, - { - name: "negative delay defaults to 1ms", - trickleDelay: -1000, - expectedDelay: 1, - }, - { - name: "positive delay unchanged", - trickleDelay: 5000, - expectedDelay: 5000, - }, - { - name: "minimum valid delay (1ms)", - trickleDelay: 1, - expectedDelay: 1, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - // Create a config with the test's TrickleDelay. - cfg := Config{ - TrickleDelay: tc.trickleDelay, - } - - // Simulate the validation logic from ValidateConfig. - if cfg.TrickleDelay <= 0 { - cfg.TrickleDelay = 1 - } - - // Verify the TrickleDelay was set to the expected - // value. - require.Equal( - t, tc.expectedDelay, cfg.TrickleDelay, - "TrickleDelay mismatch", - ) - }) - } -} - // TestValidateMaxOutgoingCltvExpiry asserts that max-cltv-expiry accepts // values within its supported bounds and rejects values outside them. func TestValidateMaxOutgoingCltvExpiry(t *testing.T) { diff --git a/contractcourt/anchor_resolver.go b/contractcourt/anchor_resolver.go index f0f8df96c..7e2676782 100644 --- a/contractcourt/anchor_resolver.go +++ b/contractcourt/anchor_resolver.go @@ -6,11 +6,10 @@ import ( "io" "sync" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/sweep" @@ -160,7 +159,7 @@ func (c *anchorResolver) Stop() { // state required for the proper resolution of a contract. // // NOTE: Part of the ContractResolver interface. -func (c *anchorResolver) SupplementState(state *chanstate.OpenChannel) { +func (c *anchorResolver) SupplementState(state *channeldb.OpenChannel) { c.chanType = state.ChanType } diff --git a/contractcourt/breach_arbitrator.go b/contractcourt/breach_arbitrator.go index 7b839e67e..d11b725ed 100644 --- a/contractcourt/breach_arbitrator.go +++ b/contractcourt/breach_arbitrator.go @@ -9,12 +9,12 @@ import ( "sync" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/input" @@ -140,9 +140,10 @@ type BreachConfig struct { // a close type to be included in the channel close summary. CloseLink func(*wire.OutPoint, ChannelCloseType) - // DB provides access to the user's closed channels, allowing the breach - // arbiter to determine how it should respond to channel closure. - DB chanstate.ClosedChannelStore + // DB provides access to the user's channels, allowing the breach + // arbiter to determine the current state of a user's channels, and how + // it should respond to channel closure. + DB *channeldb.ChannelStateDB // Estimator is used by the breach arbiter to determine an appropriate // fee level when generating, signing, and broadcasting sweep @@ -654,7 +655,6 @@ func updateBreachInfo(breachInfo *retributionInfo, spends []spend) ( // or an offered HTLC output, its amount contributes to the // value of funds being revoked from the counter party. case input.CommitmentRevoke, input.TaprootCommitmentRevoke, - input.TaprootCommitmentRevokeFinal, input.HtlcSecondLevelRevoke, input.TaprootHtlcSecondLevelRevoke, input.TaprootHtlcOfferedRevoke, input.HtlcOfferedRevoke: @@ -912,6 +912,7 @@ Loop: } for _, tx := range justiceTxs.spendSecondLevelHTLCs { + tx := tx brarLog.Debugf("Broadcasting justice tx "+ "spending second-level HTLC output: %v", @@ -1195,10 +1196,7 @@ func (bo *breachedOutput) BlocksToMaturity() uint32 { // confirmed type (or is a taproot channel that always has the CSV 1), // we must wait one block before claiming it. switch bo.witnessType { - case input.CommitmentToRemoteConfirmed, - input.TaprootRemoteCommitSpend, - input.TaprootRemoteCommitSpendFinal: - + case input.CommitmentToRemoteConfirmed, input.TaprootRemoteCommitSpend: return 1 } @@ -1281,11 +1279,6 @@ func newRetributionInfo(chanPoint *wire.OutPoint, if breachInfo.LocalOutputSignDesc != nil { var witnessType input.StandardWitnessType switch { - // Check the final channel type before the generic taproot case, - // since the pkScript check below is true for both variants. - case breachInfo.ChanType.IsTaprootFinal(): - witnessType = input.TaprootRemoteCommitSpendFinal - case isTaproot: witnessType = input.TaprootRemoteCommitSpend @@ -1325,14 +1318,9 @@ func newRetributionInfo(chanPoint *wire.OutPoint, // the funds from the commitment transaction immediately. if breachInfo.RemoteOutputSignDesc != nil { var witType input.StandardWitnessType - switch { - case breachInfo.ChanType.IsTaprootFinal(): - witType = input.TaprootCommitmentRevokeFinal - - case isTaproot: + if isTaproot { witType = input.TaprootCommitmentRevoke - - default: + } else { witType = input.CommitmentRevoke } @@ -1740,9 +1728,7 @@ func taprootBriefcaseFromRetInfo(retInfo *retributionInfo) *taprootBriefcase { switch bo.WitnessType() { // For spending from our commitment output on the remote // commitment, we'll need to stash the control block. - case input.TaprootRemoteCommitSpend, - input.TaprootRemoteCommitSpendFinal: - + case input.TaprootRemoteCommitSpend: //nolint:ll tapCase.CtrlBlocks.Val.CommitSweepCtrlBlock = bo.signDesc.ControlBlock @@ -1756,9 +1742,7 @@ func taprootBriefcaseFromRetInfo(retInfo *retributionInfo) *taprootBriefcase { // To spend the revoked output again, we'll store the same // control block value as above, but in a different place. - case input.TaprootCommitmentRevoke, - input.TaprootCommitmentRevokeFinal: - + case input.TaprootCommitmentRevoke: //nolint:ll tapCase.CtrlBlocks.Val.RevokeSweepCtrlBlock = bo.signDesc.ControlBlock @@ -1803,9 +1787,7 @@ func applyTaprootRetInfo(tapCase *taprootBriefcase, switch bo.WitnessType() { // For spending from our commitment output on the remote // commitment, we'll apply the control block. - case input.TaprootRemoteCommitSpend, - input.TaprootRemoteCommitSpendFinal: - + case input.TaprootRemoteCommitSpend: //nolint:ll bo.signDesc.ControlBlock = tapCase.CtrlBlocks.Val.CommitSweepCtrlBlock @@ -1817,9 +1799,7 @@ func applyTaprootRetInfo(tapCase *taprootBriefcase, // To spend the revoked output again, we'll apply the same // control block value as above, but to a different place. - case input.TaprootCommitmentRevoke, - input.TaprootCommitmentRevokeFinal: - + case input.TaprootCommitmentRevoke: //nolint:ll bo.signDesc.ControlBlock = tapCase.CtrlBlocks.Val.RevokeSweepCtrlBlock diff --git a/contractcourt/breach_arbitrator_test.go b/contractcourt/breach_arbitrator_test.go index fd0997990..40dad40f4 100644 --- a/contractcourt/breach_arbitrator_test.go +++ b/contractcourt/breach_arbitrator_test.go @@ -16,13 +16,12 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -33,7 +32,6 @@ import ( "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/shachain" - "github.com/lightningnetwork/lnd/tlv" "github.com/stretchr/testify/require" ) @@ -953,8 +951,7 @@ func initBreachedState(t *testing.T) (*BreachArbitrator, contractBreaches := make(chan *ContractBreachEvent) brar, err := createTestArbiter( - t, contractBreaches, - testChannelStateDB(t, alice.State()).GetParentDB(), + t, contractBreaches, alice.State().Db.GetParentDB(), ) require.NoError(t, err, "unable to initialize test breach arbiter") @@ -1120,8 +1117,7 @@ func TestBreachHandoffFail(t *testing.T) { assertNotPendingClosed(t, alice) brar, err := createTestArbiter( - t, contractBreaches, - testChannelStateDB(t, alice.State()).GetParentDB(), + t, contractBreaches, alice.State().Db.GetParentDB(), ) require.NoError(t, err, "unable to initialize test breach arbiter") @@ -1766,9 +1762,7 @@ func testBreachSpends(t *testing.T, test breachTest) { } // Assert that the channel is fully resolved. - assertBrarCleanup( - t, brar, &chanPoint, testChannelStateDB(t, alice.State()), - ) + assertBrarCleanup(t, brar, &chanPoint, alice.State().Db) } // TestBreachDelayedJusticeConfirmation tests that the breach arbiter will @@ -1973,9 +1967,7 @@ func TestBreachDelayedJusticeConfirmation(t *testing.T) { } // Assert that the channel is fully resolved. - assertBrarCleanup( - t, brar, &chanPoint, testChannelStateDB(t, alice.State()), - ) + assertBrarCleanup(t, brar, &chanPoint, alice.State().Db) } // findInputIndex returns the index of the input that spends from the given @@ -2087,9 +2079,7 @@ func assertBrarCleanup(t *testing.T, brar *BreachArbitrator, func assertPendingClosed(t *testing.T, c *lnwallet.LightningChannel) { t.Helper() - closedChans, err := testChannelStateDB( - t, c.State(), - ).FetchClosedChannels(true) + closedChans, err := c.State().Db.FetchClosedChannels(true) require.NoError(t, err, "unable to load pending closed channels") for _, chanSummary := range closedChans { @@ -2106,9 +2096,7 @@ func assertPendingClosed(t *testing.T, c *lnwallet.LightningChannel) { func assertNotPendingClosed(t *testing.T, c *lnwallet.LightningChannel) { t.Helper() - closedChans, err := testChannelStateDB( - t, c.State(), - ).FetchClosedChannels(true) + closedChans, err := c.State().Db.FetchClosedChannels(true) require.NoError(t, err, "unable to load pending closed channels") for _, chanSummary := range closedChans { @@ -2316,7 +2304,7 @@ func createInitChannels(t *testing.T) ( binary.BigEndian.Uint64(chanIDBytes[:]), ) - aliceChannelState := &chanstate.OpenChannel{ + aliceChannelState := &channeldb.OpenChannel{ LocalChanCfg: aliceCfg, RemoteChanCfg: bobCfg, IdentityPub: aliceKeyPub, @@ -2331,9 +2319,10 @@ func createInitChannels(t *testing.T) ( LocalCommitment: aliceCommit, RemoteCommitment: aliceCommit, Db: dbAlice.ChannelStateDB(), + Packager: channeldb.NewChannelPackager(shortChanID), FundingTxn: channels.TestFundingTx, } - bobChannelState := &chanstate.OpenChannel{ + bobChannelState := &channeldb.OpenChannel{ LocalChanCfg: bobCfg, RemoteChanCfg: aliceCfg, IdentityPub: bobKeyPub, @@ -2348,6 +2337,7 @@ func createInitChannels(t *testing.T) ( LocalCommitment: bobCommit, RemoteCommitment: bobCommit, Db: dbBob.ChannelStateDB(), + Packager: channeldb.NewChannelPackager(shortChanID), } aliceSigner := input.NewMockSigner( @@ -2452,183 +2442,3 @@ func createHTLC(data int, amount lnwire.MilliSatoshi) (*lnwire.UpdateAddHTLC, [3 Expiry: uint32(5), }, returnPreimage } - -// testTaprootBreachSignDesc creates a minimal taproot sign descriptor for -// breach-arbitrator unit tests that only need a taproot output script. -func testTaprootBreachSignDesc(t *testing.T) *input.SignDescriptor { - t.Helper() - - pkScript, err := input.PayToTaprootScript(&input.TaprootNUMSKey) - require.NoError(t, err) - - return &input.SignDescriptor{ - Output: &wire.TxOut{ - Value: 1000, - PkScript: pkScript, - }, - } -} - -// TestNewRetributionInfoTaprootFinalWitnessTypes verifies that final taproot -// breaches use the final witness enums for the settled commitment outputs and -// preserve their auxiliary resolution blobs. -func TestNewRetributionInfoTaprootFinalWitnessTypes(t *testing.T) { - t.Parallel() - - // Arrange: Create a final taproot breach with both settled commitment - // outputs present and auxiliary blobs attached. - settledBlob := tlv.Blob("settled-blob") - breachedBlob := tlv.Blob("breached-blob") - chanType := channeldb.SimpleTaprootFeatureBit | - channeldb.TaprootFinalBit - - breachInfo := &lnwallet.BreachRetribution{ - LocalOutpoint: wire.OutPoint{Index: 1}, - RemoteOutpoint: wire.OutPoint{Index: 2}, - LocalOutputSignDesc: testTaprootBreachSignDesc(t), - RemoteOutputSignDesc: testTaprootBreachSignDesc(t), - ChanType: chanType, - LocalResolutionBlob: fn.Some(settledBlob), - RemoteResolutionBlob: fn.Some(breachedBlob), - } - - // Act: Convert the wallet retribution into the breach-arbitrator form. - retInfo := newRetributionInfo(&wire.OutPoint{}, breachInfo) - - // Assert: The final taproot witness enums, blobs, and CSV maturity are - // preserved. - require.Len(t, retInfo.breachedOutputs, 2) - - require.Equal( - t, input.TaprootRemoteCommitSpendFinal, - retInfo.breachedOutputs[0].witnessType, - ) - require.Equal( - t, uint32(1), retInfo.breachedOutputs[0].BlocksToMaturity(), - ) - require.Equal( - t, input.TaprootCommitmentRevokeFinal, - retInfo.breachedOutputs[1].witnessType, - ) - require.Equal( - t, settledBlob, - retInfo.breachedOutputs[0].resolutionBlob.UnsafeFromSome(), - ) - require.Equal( - t, breachedBlob, - retInfo.breachedOutputs[1].resolutionBlob.UnsafeFromSome(), - ) -} - -// TestTaprootBriefcaseRoundTripFinalWitnessTypes verifies that final taproot -// breach outputs survive taproot briefcase encoding and decoding with their -// control blocks and auxiliary blobs intact. -func TestTaprootBriefcaseRoundTripFinalWitnessTypes(t *testing.T) { - t.Parallel() - - // Arrange: Build a retribution with final taproot witness enums and the - // corresponding control blocks/blobs that must survive persistence. - commitCtrlBlock := []byte("commit-ctrl-block") - revokeCtrlBlock := []byte("revoke-ctrl-block") - settledBlob := tlv.Blob("settled-blob") - breachedBlob := tlv.Blob("breached-blob") - - retInfo := &retributionInfo{ - breachedOutputs: []breachedOutput{ - { - outpoint: wire.OutPoint{Index: 1}, - witnessType: input. - TaprootRemoteCommitSpendFinal, - signDesc: input.SignDescriptor{ - ControlBlock: commitCtrlBlock, - }, - resolutionBlob: fn.Some(settledBlob), - }, - { - outpoint: wire.OutPoint{Index: 2}, - witnessType: input.TaprootCommitmentRevokeFinal, - signDesc: input.SignDescriptor{ - ControlBlock: revokeCtrlBlock, - }, - resolutionBlob: fn.Some(breachedBlob), - }, - }, - } - - // Act: Persist the taproot briefcase, decode it again, then apply it - // back to a fresh retribution shell. - tapCase := taprootBriefcaseFromRetInfo(retInfo) - - var b bytes.Buffer - require.NoError(t, tapCase.Encode(&b)) - - decoded := newTaprootBriefcase() - require.NoError(t, decoded.Decode(&b)) - - restored := &retributionInfo{ - breachedOutputs: []breachedOutput{ - { - outpoint: wire.OutPoint{Index: 1}, - witnessType: input. - TaprootRemoteCommitSpendFinal, - }, - { - outpoint: wire.OutPoint{Index: 2}, - witnessType: input.TaprootCommitmentRevokeFinal, - }, - }, - } - - // Assert: The final taproot control blocks and blobs round-trip intact. - require.NoError(t, applyTaprootRetInfo(decoded, restored)) - require.Equal( - t, commitCtrlBlock, - restored.breachedOutputs[0].signDesc.ControlBlock, - ) - require.Equal( - t, revokeCtrlBlock, - restored.breachedOutputs[1].signDesc.ControlBlock, - ) - require.Equal( - t, settledBlob, - restored.breachedOutputs[0].resolutionBlob.UnsafeFromSome(), - ) - require.Equal( - t, breachedBlob, - restored.breachedOutputs[1].resolutionBlob.UnsafeFromSome(), - ) -} - -// TestUpdateBreachInfoCountsFinalTaprootRevokedFunds verifies that final -// taproot revoked commitment outputs are included in the revoked-funds tally. -func TestUpdateBreachInfoCountsFinalTaprootRevokedFunds(t *testing.T) { - t.Parallel() - - const revokedAmt = btcutil.Amount(1234) - - // Arrange: Create a breach with a single final taproot revoked output. - breachInfo := &retributionInfo{ - breachedOutputs: []breachedOutput{ - { - amt: revokedAmt, - outpoint: wire.OutPoint{Index: 1}, - witnessType: input.TaprootCommitmentRevokeFinal, - }, - }, - } - - // Act: Process a spend for that revoked output. - total, revoked := updateBreachInfo(breachInfo, []spend{{ - index: 0, - detail: &chainntnfs.SpendDetail{ - SpendingTx: &wire.MsgTx{TxIn: []*wire.TxIn{{}}}, - SpenderInputIndex: 0, - }, - }}) - - // Assert: The amount contributes to both the total and revoked-funds - // tallies and is removed from the remaining breach set. - require.Equal(t, revokedAmt, total) - require.Equal(t, revokedAmt, revoked) - require.Empty(t, breachInfo.breachedOutputs) -} diff --git a/contractcourt/breach_resolver.go b/contractcourt/breach_resolver.go index 29a7f6bac..f34112800 100644 --- a/contractcourt/breach_resolver.go +++ b/contractcourt/breach_resolver.go @@ -5,7 +5,7 @@ import ( "fmt" "io" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/lightningnetwork/lnd/channeldb" ) // breachResolver is a resolver that will handle breached closes. In the @@ -88,7 +88,7 @@ func (b *breachResolver) Stop() { } // SupplementState adds additional state to the breachResolver. -func (b *breachResolver) SupplementState(_ *chanstate.OpenChannel) { +func (b *breachResolver) SupplementState(_ *channeldb.OpenChannel) { } // Encode encodes the breachResolver to the passed writer. diff --git a/contractcourt/briefcase.go b/contractcourt/briefcase.go index 03337de73..3e58147c6 100644 --- a/contractcourt/briefcase.go +++ b/contractcourt/briefcase.go @@ -6,9 +6,9 @@ import ( "fmt" "io" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" @@ -1582,6 +1582,7 @@ func encodeTaprootAuxData(w io.Writer, c *ContractResolutions) error { htlcBlobs := newAuxHtlcBlobs() for _, htlc := range c.HtlcResolutions.IncomingHTLCs { + htlc := htlc htlcSignDesc := htlc.SweepSignDesc ctrlBlock := htlcSignDesc.ControlBlock @@ -1618,6 +1619,7 @@ func encodeTaprootAuxData(w io.Writer, c *ContractResolutions) error { }) } for _, htlc := range c.HtlcResolutions.OutgoingHTLCs { + htlc := htlc htlcSignDesc := htlc.SweepSignDesc ctrlBlock := htlcSignDesc.ControlBlock diff --git a/contractcourt/briefcase_test.go b/contractcourt/briefcase_test.go index 866c3cbf7..c86bffb38 100644 --- a/contractcourt/briefcase_test.go +++ b/contractcourt/briefcase_test.go @@ -9,9 +9,9 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" diff --git a/contractcourt/chain_arbitrator.go b/contractcourt/chain_arbitrator.go index e31b5a9bd..72b95e5cd 100644 --- a/contractcourt/chain_arbitrator.go +++ b/contractcourt/chain_arbitrator.go @@ -7,14 +7,13 @@ import ( "sync/atomic" "time" - "github.com/btcsuite/btcd/address/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/walletdb" "github.com/lightningnetwork/lnd/chainio" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" @@ -119,7 +118,7 @@ type ChainArbitratorConfig struct { // IsOurAddress is a function that returns true if the passed address // is known to the underlying wallet. Otherwise, false should be // returned. - IsOurAddress func(address.Address) bool + IsOurAddress func(btcutil.Address) bool // IncubateOutputs sends either an incoming HTLC, an outgoing HTLC, or // both to the utxo nursery. Once this function returns, the nursery @@ -130,7 +129,7 @@ type ChainArbitratorConfig struct { IncubateOutputs func(wire.OutPoint, fn.Option[lnwallet.OutgoingHtlcResolution], fn.Option[lnwallet.IncomingHtlcResolution], - uint32, fn.Option[int32], ...IncubateOption) error + uint32, fn.Option[int32]) error // PreimageDB is a global store of all known pre-images. We'll use this // to decide if we should broadcast a commitment transaction to claim @@ -171,15 +170,6 @@ type ChainArbitratorConfig struct { // will use to notify the ChannelNotifier about a newly closed channel. NotifyClosedChannel func(wire.OutPoint) - // NotifyEarlyClosedChannel is invoked by the chain watcher when a - // cooperative close spend is first detected on chain, before the close - // summary has been persisted to the closed-channel bucket. It allows - // the channel notifier to dispatch a CLOSED_CHANNEL event over RPC at - // the same depth it did before the multi-confirmation reorg-aware - // dispatch was introduced. The follow-up persist + state advance still - // waits for the full required confirmation count. - NotifyEarlyClosedChannel func(*channeldb.ChannelCloseSummary) - // NotifyFullyResolvedChannel is a function closure that the // ChainArbitrator will use to notify the ChannelNotifier about a newly // resolved channel. The main difference to NotifyClosedChannel is that @@ -245,16 +235,6 @@ type ChainArbitratorConfig struct { // AuxResolver is an optional interface that can be used to modify the // way contracts are resolved. AuxResolver fn.Option[lnwallet.AuxContractResolver] - - // AuxCloser is an optional interface that can be used to finalize - // cooperative channel closes. - AuxCloser fn.Option[AuxChanCloser] - - // ChannelCloseConfs is an optional override for the number of - // confirmations required for channel closes. When set, this overrides - // the normal capacity-based scaling. This is only available in - // dev/integration builds for testing purposes. - ChannelCloseConfs fn.Option[uint32] } // ChainArbitrator is a sub-system that oversees the on-chain resolution of all @@ -332,7 +312,7 @@ var _ chainio.Consumer = (*ChainArbitrator)(nil) // interact with. type arbChannel struct { // channel is the in-memory channel state. - channel *chanstate.OpenChannel + channel *channeldb.OpenChannel // c references the chain arbitrator and is used by arbChannel // internally. @@ -439,21 +419,9 @@ func (a *arbChannel) ForceCloseChan() (*wire.MsgTx, error) { return closeSummary.CloseTx, nil } -// shouldSuppressClosedChannelNotify reports whether MarkChannelClosed should -// skip firing NotifyClosedChannel because the chain watcher already emitted a -// preliminary CLOSED_CHANNEL via the early-dispatch path. Only the -// cooperative-close path is gated; force, breach, and abandon closes never -// take the early-dispatch path, so their NotifyClosedChannel must always -// fire from MarkChannelClosed. -func shouldSuppressClosedChannelNotify(closeType channeldb.ClosureType, - earlyDispatched bool) bool { - - return closeType == channeldb.CooperativeClose && earlyDispatched -} - // newActiveChannelArbitrator creates a new instance of an active channel // arbitrator given the state of the target channel. -func newActiveChannelArbitrator(channel *chanstate.OpenChannel, +func newActiveChannelArbitrator(channel *channeldb.OpenChannel, c *ChainArbitrator, chanEvents *ChainEventSubscription) (*ChannelArbitrator, error) { // TODO(roasbeef): fetch best height (or pass in) so can ensure block @@ -478,35 +446,7 @@ func newActiveChannelArbitrator(channel *chanstate.OpenChannel, if err != nil { return err } - - // In the async multi-conf path the chain watcher - // already fires a preliminary CLOSED_CHANNEL event - // over the channel notifier as soon as the coop - // close spend lands on chain. Suppressing the - // duplicate notify here keeps the - // SubscribeChannelEvents stream emitting a single - // CLOSED_CHANNEL per close, matching the v0.20.1 - // surface. In the fast path (numConfs == 1) no early - // dispatch fires, so we still need to fire the - // CLOSED_CHANNEL event from here. Force/breach - // closes never take the early-dispatch path and so - // always notify here. - c.Lock() - w := c.activeWatchers[summary.ChanPoint] - c.Unlock() - - earlyDispatched := w != nil && - w.EarlyCoopCloseDispatched() - - if shouldSuppressClosedChannelNotify( - summary.CloseType, earlyDispatched, - ) { - - return nil - } - c.cfg.NotifyClosedChannel(summary.ChanPoint) - return nil }, IsPendingClose: false, @@ -519,7 +459,7 @@ func newActiveChannelArbitrator(channel *chanstate.OpenChannel, tx, c.cfg.ChainHash, &chanPoint, report, ) }, - FetchHistoricalChannel: func() (*chanstate.OpenChannel, error) { + FetchHistoricalChannel: func() (*channeldb.OpenChannel, error) { chanStateDB := c.chanSource.ChannelStateDB() return chanStateDB.FetchHistoricalChannel(&chanPoint) }, @@ -571,7 +511,7 @@ func newActiveChannelArbitrator(channel *chanstate.OpenChannel, // getArbChannel returns an open channel wrapper for use by channel arbitrators. func (c *ChainArbitrator) getArbChannel( - channel *chanstate.OpenChannel) *arbChannel { + channel *channeldb.OpenChannel) *arbChannel { return &arbChannel{ channel: channel, @@ -879,7 +819,7 @@ func (c *ChainArbitrator) notifyChannelResolved(cp wire.OutPoint) { // transactions and republish them. This helps ensure propagation of the // transactions in the event that prior publications failed. func (c *ChainArbitrator) republishClosingTxs( - channel *chanstate.OpenChannel) error { + channel *channeldb.OpenChannel) error { // If the channel has had its unilateral close broadcasted already, // republish it in case it didn't propagate. @@ -911,7 +851,7 @@ func (c *ChainArbitrator) republishClosingTxs( // // NOTE: There is no risk to calling this method if the channel isn't in either // CommitmentBroadcasted or CoopBroadcasted, but the logs will be misleading. -func (c *ChainArbitrator) rebroadcast(channel *chanstate.OpenChannel, +func (c *ChainArbitrator) rebroadcast(channel *channeldb.OpenChannel, state channeldb.ChannelStatus) error { chanPoint := channel.FundingOutpoint @@ -1170,9 +1110,7 @@ func (c *ChainArbitrator) ForceCloseContract(chanPoint wire.OutPoint) (*wire.Msg // ChannelArbitrator tasked with watching over a new channel. Once a new // channel has finished its final funding flow, it should be registered with // the ChainArbitrator so we can properly react to any on-chain events. -func (c *ChainArbitrator) WatchNewChannel( - newChan *chanstate.OpenChannel) error { - +func (c *ChainArbitrator) WatchNewChannel(newChan *channeldb.OpenChannel) error { c.Lock() defer c.Unlock() @@ -1202,12 +1140,9 @@ func (c *ChainArbitrator) WatchNewChannel( chanPoint, retInfo, ) }, - extractStateNumHint: lnwallet.GetStateNumHint, - auxLeafStore: c.cfg.AuxLeafStore, - auxResolver: c.cfg.AuxResolver, - auxCloser: c.cfg.AuxCloser, - chanCloseConfs: c.cfg.ChannelCloseConfs, - notifyEarlyCoopClose: c.cfg.NotifyEarlyClosedChannel, + extractStateNumHint: lnwallet.GetStateNumHint, + auxLeafStore: c.cfg.AuxLeafStore, + auxResolver: c.cfg.AuxResolver, }, ) if err != nil { @@ -1367,6 +1302,7 @@ func (c *ChainArbitrator) loadOpenChannels() error { // ChannelArbitrator. for _, channel := range openChannels { chanPoint := channel.FundingOutpoint + channel := channel // First, we'll create an active chainWatcher for this channel // to ensure that we detect any relevant on chain events. @@ -1374,20 +1310,16 @@ func (c *ChainArbitrator) loadOpenChannels() error { return c.cfg.ContractBreach(chanPoint, ret) } - notifyEarlyClose := c.cfg.NotifyEarlyClosedChannel chainWatcher, err := newChainWatcher( chainWatcherConfig{ - chanState: channel, - notifier: c.cfg.Notifier, - signer: c.cfg.Signer, - isOurAddr: c.cfg.IsOurAddress, - contractBreach: breachClosure, - extractStateNumHint: lnwallet.GetStateNumHint, - auxLeafStore: c.cfg.AuxLeafStore, - auxResolver: c.cfg.AuxResolver, - auxCloser: c.cfg.AuxCloser, - chanCloseConfs: c.cfg.ChannelCloseConfs, - notifyEarlyCoopClose: notifyEarlyClose, + chanState: channel, + notifier: c.cfg.Notifier, + signer: c.cfg.Signer, + isOurAddr: c.cfg.IsOurAddress, + contractBreach: breachClosure, + extractStateNumHint: lnwallet.GetStateNumHint, + auxLeafStore: c.cfg.AuxLeafStore, + auxResolver: c.cfg.AuxResolver, }, ) if err != nil { @@ -1457,7 +1389,7 @@ func (c *ChainArbitrator) loadPendingCloseChannels() error { tx, c.cfg.ChainHash, &chanPoint, report, ) }, - FetchHistoricalChannel: func() (*chanstate.OpenChannel, error) { + FetchHistoricalChannel: func() (*channeldb.OpenChannel, error) { return chanStateDB.FetchHistoricalChannel(&chanPoint) }, FindOutgoingHTLCDeadline: func( diff --git a/contractcourt/chain_arbitrator_test.go b/contractcourt/chain_arbitrator_test.go index 582faf49c..622686f76 100644 --- a/contractcourt/chain_arbitrator_test.go +++ b/contractcourt/chain_arbitrator_test.go @@ -4,11 +4,10 @@ import ( "net" "testing" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lntest/mock" @@ -27,7 +26,7 @@ func TestChainArbitratorRepublishCloses(t *testing.T) { // Create 10 test channels and sync them to the database. const numChans = 10 - var channels []*chanstate.OpenChannel + var channels []*channeldb.OpenChannel for i := 0; i < numChans; i++ { lChannel, _, err := lnwallet.CreateTestChannels( t, channeldb.SingleFunderTweaklessBit, @@ -220,69 +219,3 @@ func TestResolveContract(t *testing.T) { err = chainArb.ResolveContract(channel.FundingOutpoint) require.NoError(t, err, "second resolve call shouldn't fail") } - -// TestShouldSuppressClosedChannelNotify pins down the gate that prevents -// MarkChannelClosed from firing a duplicate NotifyClosedChannel after the -// chain watcher has already emitted a preliminary CLOSED_CHANNEL via the -// early-dispatch path. Only the cooperative-close path can be suppressed; -// every other CloseType (force, breach, abandon) must always notify here -// regardless of the early-dispatched flag. The fast path (numConfs==1) -// never sets the early-dispatched flag, so cooperative closes on that path -// also fall through to NotifyClosedChannel. -func TestShouldSuppressClosedChannelNotify(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - closeType channeldb.ClosureType - earlyDispatched bool - wantSuppress bool - }{ - { - name: "coop close with early dispatch", - closeType: channeldb.CooperativeClose, - earlyDispatched: true, - wantSuppress: true, - }, - { - name: "coop close without early dispatch " + - "(fast path or no watcher)", - closeType: channeldb.CooperativeClose, - earlyDispatched: false, - wantSuppress: false, - }, - { - name: "local force close", - closeType: channeldb.LocalForceClose, - earlyDispatched: true, - wantSuppress: false, - }, - { - name: "remote force close", - closeType: channeldb.RemoteForceClose, - earlyDispatched: true, - wantSuppress: false, - }, - { - name: "breach close", - closeType: channeldb.BreachClose, - earlyDispatched: true, - wantSuppress: false, - }, - { - name: "abandoned close", - closeType: channeldb.Abandoned, - earlyDispatched: true, - wantSuppress: false, - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - got := shouldSuppressClosedChannelNotify( - tc.closeType, tc.earlyDispatched, - ) - require.Equal(t, tc.wantSuppress, got) - }) - } -} diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index ee8f870ed..082b47228 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -8,25 +8,22 @@ import ( "sync/atomic" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/mempool" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainio" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnwallet" - "github.com/lightningnetwork/lnd/lnwallet/types" "github.com/lightningnetwork/lnd/lnwire" ) @@ -40,14 +37,6 @@ const ( maxCommitPointPollTimeout = 10 * time.Minute ) -// AuxChanCloser is used to allow an external caller to finalize a cooperative -// channel close. -type AuxChanCloser interface { - // FinalizeClose is called after the close transaction has been agreed - // upon and confirmed. - FinalizeClose(desc types.AuxCloseDesc, closeTx *wire.MsgTx) error -} - // LocalUnilateralCloseInfo encapsulates all the information we need to act on // a local force close that gets confirmed. type LocalUnilateralCloseInfo struct { @@ -99,38 +88,6 @@ type BreachCloseInfo struct { CloseSummary channeldb.ChannelCloseSummary } -// spendConfirmationState represents the state of spend confirmation tracking -// in the closeObserver state machine. We wait for N confirmations before -// processing any spend to protect against shallow reorgs. -type spendConfirmationState uint8 - -const ( - // spendStateNone indicates no spend has been detected yet. - spendStateNone spendConfirmationState = iota - - // spendStatePending indicates a spend has been detected and we're - // waiting for the required number of confirmations. - spendStatePending - - // spendStateConfirmed indicates the spend has reached the required - // confirmations and has been processed. - spendStateConfirmed -) - -// String returns a human-readable representation of the state. -func (s spendConfirmationState) String() string { - switch s { - case spendStateNone: - return "None" - case spendStatePending: - return "Pending" - case spendStateConfirmed: - return "Confirmed" - default: - return "Unknown" - } -} - // CommitSet is a collection of the set of known valid commitments at a given // instant. If ConfCommitKey is set, then the commitment identified by the // HtlcSetKey has hit the chain. This struct will be used to examine all live @@ -241,7 +198,7 @@ type chainWatcherConfig struct { // chanState is a snapshot of the persistent state of the channel that // we're watching. In the event of an on-chain event, we'll query the // database to ensure that we act using the most up to date state. - chanState *chanstate.OpenChannel + chanState *channeldb.OpenChannel // notifier is a reference to the channel notifier that we'll use to be // notified of output spends and when transactions are confirmed. @@ -260,7 +217,7 @@ type chainWatcherConfig struct { // isOurAddr is a function that returns true if the passed address is // known to us. - isOurAddr func(address.Address) bool + isOurAddr func(btcutil.Address) bool // extractStateNumHint extracts the encoded state hint using the passed // obfuscater. This is used by the chain watcher to identify which @@ -272,25 +229,6 @@ type chainWatcherConfig struct { // auxResolver is used to supplement contract resolution. auxResolver fn.Option[lnwallet.AuxContractResolver] - - // auxCloser is used to finalize cooperative closes. - auxCloser fn.Option[AuxChanCloser] - - // chanCloseConfs is an optional override for the number of - // confirmations required for channel closes. When set, this overrides - // the normal capacity-based scaling. This is only available in - // dev/integration builds for testing purposes. - chanCloseConfs fn.Option[uint32] - - // notifyEarlyCoopClose, if set, is invoked with a synthesized - // ChannelCloseSummary the first time a cooperative close spend is - // detected on chain. It dispatches a CLOSED_CHANNEL event over the - // channel notifier so RPC subscribers see the close at the same - // block depth they did before the multi-confirmation reorg-aware - // dispatch was introduced. The follow-up state transition (DB persist - // + state machine advance + FULLY_RESOLVED_CHANNEL) still waits for - // the full required confirmation depth via the existing async path. - notifyEarlyCoopClose func(*channeldb.ChannelCloseSummary) } // chainWatcher is a system that's assigned to every active channel. The duty @@ -341,26 +279,6 @@ type chainWatcher struct { // ensure that the outpoint+pkscript pair is confirmed before calling // `RegisterSpendNtfn`. fundingConfirmedNtfn *chainntnfs.ConfirmationEvent - - // coopCloseEarlyDispatched is set when we have already insta-dispatched - // a preliminary CLOSED_CHANNEL event for a coop close upon first spend - // detection. It is cleared on a deep reorg of the close so a re-mined - // close still re-fires the early event. The closeObserver goroutine is - // the only writer, but the channel arbitrator's MarkChannelClosed - // callback reads it from its own goroutine to decide whether to - // suppress the duplicate notify at full conf depth, so the field is an - // atomic.Bool to make that cross-goroutine read race-free. - coopCloseEarlyDispatched atomic.Bool -} - -// EarlyCoopCloseDispatched reports whether the chain watcher already fired the -// preliminary CLOSED_CHANNEL event for the in-flight cooperative close. The -// channel arbitrator uses this to gate the duplicate CLOSED_CHANNEL that -// MarkChannelClosed would otherwise fire at full conf depth. The flag is only -// ever set on the async multi-conf path; the fast-path (numConfs == 1) leaves -// it false and so the regular MarkChannelClosed-driven notify still fires. -func (c *chainWatcher) EarlyCoopCloseDispatched() bool { - return c.coopCloseEarlyDispatched.Load() } // newChainWatcher returns a new instance of a chainWatcher for a channel given @@ -659,7 +577,7 @@ type chainSet struct { // newChainSet creates a new chainSet given the current up to date channel // state. -func newChainSet(chanState *chanstate.OpenChannel) (*chainSet, error) { +func newChainSet(chanState *channeldb.OpenChannel) (*chainSet, error) { // First, we'll grab the current unrevoked commitments for ourselves // and the remote party. localCommit, remoteCommit, err := chanState.LatestCommitments() @@ -727,319 +645,52 @@ func newChainSet(chanState *chanstate.OpenChannel) (*chainSet, error) { }, nil } -// spendProcessResult holds the results of processing a detected spend. -type spendProcessResult struct { - // pendingSpend is the spend to track (nil if fast-path was used). - pendingSpend *chainntnfs.SpendDetail - - // confNtfn is the confirmation notification (nil if fast-path or - // error). - confNtfn *chainntnfs.ConfirmationEvent -} - -// processDetectedSpend handles a newly detected spend from either blockbeat or -// spend notification. It determines whether to use the fast-path (single conf) -// or async-path (multiple confs), and returns the updated state. -// -// For single-confirmation mode (numConfs == 1), it immediately dispatches the -// close event and returns empty result. For multi-confirmation mode, it -// registers for confirmations and returns the new pending state. In the -// async path, a coop close also triggers an early CLOSED_CHANNEL event over -// the channel notifier so RPC subscribers see the close at the same depth -// they did before the multi-confirmation reorg-aware dispatch was introduced. -func (c *chainWatcher) processDetectedSpend( - spend *chainntnfs.SpendDetail, source string, - currentPendingSpend *chainntnfs.SpendDetail, - currentConfNtfn *chainntnfs.ConfirmationEvent) spendProcessResult { - - // FAST PATH: Single confirmation mode dispatches immediately. In this - // mode the existing flow already drives MarkChannelClosed at the - // single conf, which fires CLOSED_CHANNEL with a fully populated - // summary (including close initiator from the historical bucket), so - // the early dispatch is not needed and would actually deliver a - // summary with an unknown close initiator to subscribers. - if c.handleSpendDispatch(spend, source) { - if currentConfNtfn != nil { - currentConfNtfn.Cancel() - } - - return spendProcessResult{} - } - - // ASYNC PATH: Multiple confirmations (production). - // - // STATE TRANSITION: None -> Pending. - log.Infof("ChannelPoint(%v): detected spend from %s, "+ - "transitioning to %v", c.cfg.chanState.FundingOutpoint, - source, spendStatePending) - - // Reconcile against any spend we're already tracking *before* firing - // the preliminary CLOSED_CHANNEL. If a replacement coop close arrives - // while the previous spend's NegativeConf has not yet been drained - // (e.g. a deep reorg removed the old spend and a different coop close - // then confirmed), the early-dispatch flag may still be set from the - // stale spend. Clearing it on the replacement path lets the next - // maybeDispatchEarlyCoopClose call fire a fresh event for the new tx, - // so subscribers observe the replacement instead of being left with - // the stale event (which the arbitrator's CloseType-gated suppression - // would otherwise let stand). - if currentPendingSpend != nil { - if *currentPendingSpend.SpenderTxHash == *spend.SpenderTxHash { - log.Debugf("ChannelPoint(%v): ignoring duplicate "+ - "spend detection for tx %v", - c.cfg.chanState.FundingOutpoint, - spend.SpenderTxHash) - - return spendProcessResult{ - pendingSpend: currentPendingSpend, - confNtfn: currentConfNtfn, - } - } - - // Different spend detected (e.g. an RBF replacement). Cancel - // the existing confNtfn so we can re-register for the new tx, - // and clear the early-dispatch flag so the replacement's own - // preliminary CLOSED_CHANNEL event below can fire. - log.Warnf("ChannelPoint(%v): detected different spend tx %v, "+ - "replacing pending tx %v", - c.cfg.chanState.FundingOutpoint, - spend.SpenderTxHash, currentPendingSpend.SpenderTxHash) - - if currentConfNtfn != nil { - currentConfNtfn.Cancel() - } - - c.coopCloseEarlyDispatched.Store(false) - } - - // Fire a preliminary CLOSED_CHANNEL event over the channel notifier - // as soon as the spend is first detected so SubscribeChannelEvents - // subscribers see the close at the same depth they did before the - // multi-confirmation reorg-aware dispatch was introduced. The - // suppression of the duplicate notify at MarkChannelClosed time is - // handled in chain_arbitrator.go via a CloseType check. - c.maybeDispatchEarlyCoopClose(spend) - - numConfs := c.requiredConfsForSpend() - txid := spend.SpenderTxHash - - // Record the close confirmation height. This is the height at which - // the closing tx was first included in a block. We store this so we - // can report the remaining confirmations to the user. - err := c.cfg.chanState.MarkCloseConfirmationHeight( - fn.Some(uint32(spend.SpendingHeight)), - ) - if err != nil { - log.Warnf("ChannelPoint(%v): unable to mark close "+ - "confirmation height: %v", - c.cfg.chanState.FundingOutpoint, err) - } - - newConfNtfn, err := c.cfg.notifier.RegisterConfirmationsNtfn( - txid, spend.SpendingTx.TxOut[0].PkScript, numConfs, - uint32(spend.SpendingHeight), - ) - if err != nil { - log.Errorf("Unable to register confirmations: %v", err) - - return spendProcessResult{ - pendingSpend: currentPendingSpend, - confNtfn: currentConfNtfn, - } - } - - log.Infof("ChannelPoint(%v): waiting for %d confirmations of "+ - "spend tx %v", c.cfg.chanState.FundingOutpoint, numConfs, txid) - - return spendProcessResult{ - pendingSpend: spend, - confNtfn: newConfNtfn, - } -} - // closeObserver is a dedicated goroutine that will watch for any closes of the -// channel that it's watching on chain. It implements a state machine to handle -// spend detection and confirmation with reorg protection. The states are: -// -// - None (confNtfn == nil): No spend detected yet, waiting for spend -// notification -// -// - Pending (confNtfn != nil): Spend detected, waiting for N confirmations -// -// - Confirmed: Spend confirmed with N blocks, close has been processed -// -// For single-confirmation scenarios (numConfs == 1), we bypass the async state -// machine and immediately dispatch close events upon spend detection. This -// provides synchronous behavior for integration tests which expect immediate -// notifications. For multi-confirmation scenarios (production with numConfs -// >= 3), we use the full async state machine with reorg protection. +// channel that it's watching on chain. In the event of an on-chain event, the +// close observer will assembled the proper materials required to claim the +// funds of the channel on-chain (if required), then dispatch these as +// notifications to all subscribers. func (c *chainWatcher) closeObserver() { defer c.wg.Done() - - registerForSpend := func() (*chainntnfs.SpendEvent, error) { - fundingPkScript, err := deriveFundingPkScript(c.cfg.chanState) - if err != nil { - return nil, err - } - - heightHint := c.cfg.chanState.DeriveHeightHint() - - return c.cfg.notifier.RegisterSpendNtfn( - &c.cfg.chanState.FundingOutpoint, - fundingPkScript, - heightHint, - ) - } - - spendNtfn := c.fundingSpendNtfn - defer func() { spendNtfn.Cancel() }() - - // We use these variables to implement a state machine to track the - // state of the spend confirmation process: - // * When confNtfn is nil, we're in state "None" waiting for a spend. - // * When confNtfn is set, we're in state "Pending" waiting for - // confirmations. - // - // After confirmations, we transition to state "Confirmed" and clean up. - var ( - pendingSpend *chainntnfs.SpendDetail - confNtfn *chainntnfs.ConfirmationEvent - ) + defer c.fundingSpendNtfn.Cancel() log.Infof("Close observer for ChannelPoint(%v) active", c.cfg.chanState.FundingOutpoint) for { - // We only listen to confirmation channels when we have a - // pending spend. By setting these to nil when not needed, Go's - // select ignores those cases, effectively implementing our - // state machine. - var ( - confChan <-chan *chainntnfs.TxConfirmation - negativeConfChan <-chan int32 - ) - if confNtfn != nil { - confChan = confNtfn.Confirmed - negativeConfChan = confNtfn.NegativeConf - } - select { - // A new block beat has just arrived, we'll handle the block - // beat, and see if it contains the spend of our funding - // transaction or not. + // A new block is received, we will check whether this block + // contains a spending tx that we are interested in. case beat := <-c.BlockbeatChan: log.Debugf("ChainWatcher(%v) received blockbeat %v", c.cfg.chanState.FundingOutpoint, beat.Height()) - spend := c.handleBlockbeat(beat) - if spend == nil { - continue - } + // Process the block. + c.handleBlockbeat(beat) - result := c.processDetectedSpend( - spend, "blockbeat", pendingSpend, confNtfn, - ) - - pendingSpend = result.pendingSpend - confNtfn = result.confNtfn - - // A direct spend was just detected, we'll process the new spend - // then see if we need to dispatch instantly, or wait around for - // additional confirmations. - case spend, ok := <-spendNtfn.Spend: + // If the funding outpoint is spent, we now go ahead and handle + // it. Note that we cannot rely solely on the `block` event + // above to trigger a close event, as deep down, the receiving + // of block notifications and the receiving of spending + // notifications are done in two different goroutines, so the + // expected order: [receive block -> receive spend] is not + // guaranteed . + case spend, ok := <-c.fundingSpendNtfn.Spend: + // If the channel was closed, then this means that the + // notifier exited, so we will as well. if !ok { return } - result := c.processDetectedSpend( - spend, "spend notification", pendingSpend, - confNtfn, - ) - - pendingSpend = result.pendingSpend - confNtfn = result.confNtfn - - // The spend has reached required confirmations. It's now safe - // to process since we've protected against shallow reorgs. - // - // * STATE TRANSITION: Pending -> Confirmed - case conf, ok := <-confChan: - if !ok { - log.Errorf("Confirmation channel closed " + - "unexpectedly") - return - } - - log.Infof("ChannelPoint(%v): spend confirmed at "+ - "height %d, transitioning to %v", - c.cfg.chanState.FundingOutpoint, - conf.BlockHeight, spendStateConfirmed) - - err := c.handleCommitSpend(pendingSpend) + err := c.handleCommitSpend(spend) if err != nil { - log.Errorf("Failed to handle confirmed "+ - "spend: %v", err) + log.Errorf("Failed to handle commit spend: %v", + err) } - confNtfn.Cancel() - confNtfn = nil - pendingSpend = nil - - // A reorg removed the spend tx. We reset to initial state and - // wait for ANY new spend (could be the same tx re-mined, or a - // different tx like an RBF replacement). - // - // * STATE TRANSITION: Pending -> None - case reorgDepth, ok := <-negativeConfChan: - if !ok { - log.Errorf("Negative conf channel closed " + - "unexpectedly") - return - } - - log.Infof("ChannelPoint(%v): spend reorged out at "+ - "depth %d, transitioning back to %v", - c.cfg.chanState.FundingOutpoint, reorgDepth, - spendStateNone) - - confNtfn.Cancel() - confNtfn = nil - pendingSpend = nil - - // Clear the early-dispatch flag so a re-mined coop - // close re-fires the preliminary CLOSED_CHANNEL event - // with its own close summary. - c.coopCloseEarlyDispatched.Store(false) - - // Reset the close confirmation height since the spend - // was reorged out. - err := c.cfg.chanState.ResetCloseConfirmationHeight() - if err != nil { - log.Warnf("ChannelPoint(%v): unable to reset "+ - "close confirmation height: %v", - c.cfg.chanState.FundingOutpoint, err) - } - - spendNtfn.Cancel() - spendNtfn, err = registerForSpend() - if err != nil { - log.Errorf("Unable to re-register for "+ - "spend: %v", err) - return - } - - c.fundingSpendNtfn = spendNtfn - - log.Infof("ChannelPoint(%v): re-registered for spend "+ - "detection", c.cfg.chanState.FundingOutpoint) - // The chainWatcher has been signalled to exit, so we'll do so // now. case <-c.quit: - if confNtfn != nil { - confNtfn.Cancel() - } - return } } @@ -1335,158 +986,26 @@ func (c *chainWatcher) toSelfAmount(tx *wire.MsgTx) btcutil.Amount { return btcutil.Amount(fn.Sum(vals)) } -// finalizeCoopClose calls the aux closer to finalize a cooperative close -// transaction that has been confirmed on-chain. -func (c *chainWatcher) finalizeCoopClose(aux AuxChanCloser, - closeTx *wire.MsgTx) error { - - chanState := c.cfg.chanState - - // Get the shutdown info to extract the local delivery script. - shutdown, err := chanState.ShutdownInfo() - if err != nil { - return fmt.Errorf("get shutdown info: %w", err) - } - - // Build the AuxShutdownReq. - req := types.AuxShutdownReq{ - ChanPoint: chanState.FundingOutpoint, - ShortChanID: chanState.ShortChanID(), - Initiator: chanState.IsInitiator, - CommitBlob: chanState.LocalCommitment.CustomBlob, - FundingBlob: chanState.CustomBlob, - } - - // Shutdown info must be present in order to continue. - if shutdown.IsNone() { - return fmt.Errorf("failed to finalize coop close, shutdown " + - "info missing") - } - - // Extract close outputs from the transaction. We need to identify - // which outputs belong to local vs remote parties. - var localCloseOutput, remoteCloseOutput fn.Option[types.CloseOutput] - - // Get the delivery scripts for the local party. - var localDeliveryScript lnwire.DeliveryAddress - shutdown.WhenSome(func(s channeldb.ShutdownInfo) { - localDeliveryScript = s.DeliveryScript.Val - }) - - // Scan through the close transaction outputs to identify local and - // remote outputs. - for _, out := range closeTx.TxOut { - if len(localDeliveryScript) > 0 && - slices.Equal(out.PkScript, localDeliveryScript) { - - localCloseOutput = fn.Some(types.CloseOutput{ - Amt: btcutil.Amount(out.Value), - PkScript: out.PkScript, - DustLimit: chanState.LocalChanCfg.DustLimit, - }) - } else { - // This must be the remote output. - remoteCloseOutput = fn.Some(types.CloseOutput{ - Amt: btcutil.Amount(out.Value), - PkScript: out.PkScript, - DustLimit: chanState.RemoteChanCfg.DustLimit, - }) - } - } - - desc := types.AuxCloseDesc{ - AuxShutdownReq: req, - LocalCloseOutput: localCloseOutput, - RemoteCloseOutput: remoteCloseOutput, - } - - return aux.FinalizeClose(desc, closeTx) -} - -// requiredConfsForSpend determines the number of confirmations required before -// processing a spend of the funding output. Uses config override if set -// (typically for testing), otherwise scales with channel capacity to balance -// security vs user experience for channels of different sizes. -func (c *chainWatcher) requiredConfsForSpend() uint32 { - return c.cfg.chanCloseConfs.UnwrapOrFunc(func() uint32 { - return lnwallet.CloseConfsForCapacity( - c.cfg.chanState.Capacity, - ) - }) -} - -// isCoopCloseSpend reports whether the supplied spending tx looks like a -// cooperative close. A coop close has a finalized input sequence number -// (either MaxTxInSequenceNum or MaxRBFSequence); regular commitment txns -// carry an obfuscated state hint in the sequence + locktime fields and -// won't match either constant. -func isCoopCloseSpend(spendingTx *wire.MsgTx) bool { - if len(spendingTx.TxIn) == 0 { - return false - } - - switch spendingTx.TxIn[0].Sequence { - case wire.MaxTxInSequenceNum: - return true - case mempool.MaxRBFSequence: - return true - } - - return false -} - -// maybeDispatchEarlyCoopClose fires a preliminary CLOSED_CHANNEL event over -// the channel notifier the first time a coop close spend is detected on -// chain. It is a no-op if no early-dispatch callback was wired in, the spend -// is not a coop close, or an early dispatch has already happened for this -// close. The flag is cleared on a deep reorg of the close (in the closeObserver -// negativeConfChan handler) so a re-mined close re-fires. -func (c *chainWatcher) maybeDispatchEarlyCoopClose( - spend *chainntnfs.SpendDetail) { - - if c.coopCloseEarlyDispatched.Load() { - return - } - if c.cfg.notifyEarlyCoopClose == nil { - return - } - - // We only insta-dispatch for coop closes. Force-close, breach, and - // abandon spends intentionally remain on the existing N-confirmation - // dispatch contract: their CLOSED_CHANNEL event is driven from the - // channel arbitrator's MarkChannelClosed callback at the required - // confirmation depth, so an early dispatch here would either deliver - // a duplicate event or, worse, surface a "cooperative close" summary - // for a unilateral spend. - if !isCoopCloseSpend(spend.SpendingTx) { - return - } - - summary := c.buildCoopCloseSummary(spend) - - log.Infof("ChannelPoint(%v): dispatching early CLOSED_CHANNEL "+ - "event for coop close tx %v at height %d", - c.cfg.chanState.FundingOutpoint, spend.SpenderTxHash, - spend.SpendingHeight) - - c.cfg.notifyEarlyCoopClose(summary) - c.coopCloseEarlyDispatched.Store(true) -} - -// buildCoopCloseSummary constructs a ChannelCloseSummary for a cooperative -// close from the supplied spend detail. The summary is returned with -// IsPending=true; the channel arbitrator's MarkChannelClosed callback flips -// this to false after the close reaches the required confirmation depth. This -// helper is shared between the early insta-dispatch path (first conf, no DB -// persist) and the post-N-conf dispatch path so both surfaces produce -// equivalent summaries. -func (c *chainWatcher) buildCoopCloseSummary( - commitSpend *chainntnfs.SpendDetail) *channeldb.ChannelCloseSummary { - +// dispatchCooperativeClose processed a detect cooperative channel closure. +// We'll use the spending transaction to locate our output within the +// transaction, then clean up the database state. We'll also dispatch a +// notification to all subscribers that the channel has been closed in this +// manner. +func (c *chainWatcher) dispatchCooperativeClose(commitSpend *chainntnfs.SpendDetail) error { broadcastTx := commitSpend.SpendingTx + + log.Infof("Cooperative closure for ChannelPoint(%v): %v", + c.cfg.chanState.FundingOutpoint, + lnutils.SpewLogClosure(broadcastTx)) + + // If the input *is* final, then we'll check to see which output is + // ours. localAmt := c.toSelfAmount(broadcastTx) - summary := &channeldb.ChannelCloseSummary{ + // Once this is known, we'll mark the state as fully closed in the + // database. We can do this as a cooperatively closed channel has all + // its outputs resolved after only one confirmation. + closeSummary := &channeldb.ChannelCloseSummary{ ChanPoint: c.cfg.chanState.FundingOutpoint, ChainHash: c.cfg.chanState.ChainHash, ClosingTXID: *commitSpend.SpenderTxHash, @@ -1508,45 +1027,15 @@ func (c *chainWatcher) buildCoopCloseSummary( log.Errorf("ChannelPoint(%v): unable to create channel sync "+ "message: %v", c.cfg.chanState.FundingOutpoint, err) } else { - summary.LastChanSyncMsg = chanSync + closeSummary.LastChanSyncMsg = chanSync } - return summary -} - -// dispatchCooperativeClose processed a detect cooperative channel closure. -// We'll use the spending transaction to locate our output within the -// transaction, then clean up the database state. We'll also dispatch a -// notification to all subscribers that the channel has been closed in this -// manner. -func (c *chainWatcher) dispatchCooperativeClose( - commitSpend *chainntnfs.SpendDetail) error { - - broadcastTx := commitSpend.SpendingTx - - log.Infof("Cooperative closure for ChannelPoint(%v): %v", - c.cfg.chanState.FundingOutpoint, - lnutils.SpewLogClosure(broadcastTx)) - - closeSummary := c.buildCoopCloseSummary(commitSpend) - // Create a summary of all the information needed to handle the // cooperative closure. closeInfo := &CooperativeCloseInfo{ ChannelCloseSummary: closeSummary, } - // If we have an aux closer, finalize the cooperative close now that - // it's confirmed. - err := fn.MapOptionZ( - c.cfg.auxCloser, func(aux AuxChanCloser) error { - return c.finalizeCoopClose(aux, broadcastTx) - }, - ) - if err != nil { - return fmt.Errorf("finalize coop close: %w", err) - } - // With the event processed, we'll now notify all subscribers of the // event. c.Lock() @@ -1837,7 +1326,7 @@ func (c *chainWatcher) waitForCommitmentPoint() *btcec.PublicKey { } // deriveFundingPkScript derives the script used in the funding output. -func deriveFundingPkScript(chanState *chanstate.OpenChannel) ([]byte, error) { +func deriveFundingPkScript(chanState *channeldb.OpenChannel) ([]byte, error) { localKey := chanState.LocalChanCfg.MultiSigKey.PubKey remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey @@ -1870,30 +1359,6 @@ func deriveFundingPkScript(chanState *chanstate.OpenChannel) ([]byte, error) { return fundingPkScript, nil } -// handleSpendDispatch processes a detected spend. For single-confirmation -// scenarios (numConfs == 1), it immediately dispatches the close event and -// returns true. For multi-confirmation scenarios, it returns false, indicating -// the caller should proceed with the async state machine. -func (c *chainWatcher) handleSpendDispatch(spend *chainntnfs.SpendDetail, - source string) bool { - - numConfs := c.requiredConfsForSpend() - if numConfs == 1 { - log.Infof("ChannelPoint(%v): single confirmation mode, "+ - "dispatching immediately from %s", - c.cfg.chanState.FundingOutpoint, source) - - err := c.handleCommitSpend(spend) - if err != nil { - log.Errorf("Failed to handle commit spend: %v", err) - } - - return true - } - - return false -} - // handleCommitSpend takes a spending tx of the funding output and handles the // channel close based on the closure type. func (c *chainWatcher) handleCommitSpend( @@ -1949,10 +1414,9 @@ func (c *chainWatcher) handleCommitSpend( case wire.MaxTxInSequenceNum: fallthrough case mempool.MaxRBFSequence: - // This is a cooperative close. Dispatch it directly - the - // confirmation waiting and reorg handling is done in the - // closeObserver state machine before we reach this point. - if err := c.dispatchCooperativeClose(commitSpend); err != nil { + // TODO(roasbeef): rare but possible, need itest case for + err := c.dispatchCooperativeClose(commitSpend) + if err != nil { return fmt.Errorf("handle coop close: %w", err) } @@ -2057,10 +1521,9 @@ func (c *chainWatcher) chanPointConfirmed() bool { } // handleBlockbeat takes a blockbeat and queries for a spending tx for the -// funding output. If found, it returns the spend details so closeObserver can -// process it. Returns nil if no spend was detected. -func (c *chainWatcher) handleBlockbeat( - beat chainio.Blockbeat) *chainntnfs.SpendDetail { +// funding output. If the spending tx is found, it will be handled based on the +// closure type. +func (c *chainWatcher) handleBlockbeat(beat chainio.Blockbeat) { // Notify the chain watcher has processed the block. defer c.NotifyBlockProcessed(beat, nil) @@ -2072,23 +1535,24 @@ func (c *chainWatcher) handleBlockbeat( // If the funding output hasn't confirmed in this block, we // will check it again in the next block. if !c.chanPointConfirmed() { - return nil + return } } // Perform a non-blocking read to check whether the funding output was - // spent. The actual spend handling is done in closeObserver's state - // machine to avoid blocking the block processing pipeline. + // spent. spend := c.checkFundingSpend() if spend == nil { log.Tracef("No spend found for ChannelPoint(%v) in block %v", c.cfg.chanState.FundingOutpoint, beat.Height()) - return nil + return } - log.Debugf("Detected spend of ChannelPoint(%v) in block %v", - c.cfg.chanState.FundingOutpoint, beat.Height()) - - return spend + // The funding output was spent, we now handle it by sending a close + // event to the channel arbitrator. + err := c.handleCommitSpend(spend) + if err != nil { + log.Errorf("Failed to handle commit spend: %v", err) + } } diff --git a/contractcourt/chain_watcher_coop_reorg_test.go b/contractcourt/chain_watcher_coop_reorg_test.go deleted file mode 100644 index 8753a564b..000000000 --- a/contractcourt/chain_watcher_coop_reorg_test.go +++ /dev/null @@ -1,197 +0,0 @@ -package contractcourt - -import ( - "testing" - "time" - - "github.com/btcsuite/btcd/wire/v2" -) - -// TestChainWatcherCoopCloseReorg tests that the chain watcher properly handles -// a reorganization during cooperative close confirmation waiting. When a -// cooperative close transaction is reorganized out, the chain watcher should -// re-register for spend notifications and detect an alternative transaction. -func TestChainWatcherCoopCloseReorg(t *testing.T) { - t.Parallel() - - // Create test harness. - harness := newChainWatcherTestHarness(t) - - // Create two cooperative close transactions with different fees. - tx1 := harness.createCoopCloseTx(5000) - tx2 := harness.createCoopCloseTx(4900) - - // Run cooperative close flow with reorg. - closeInfo := harness.runCoopCloseFlow(tx1, true, 2, tx2) - - // Assert that the second transaction was confirmed. - harness.assertCoopCloseTx(closeInfo, tx2) -} - -// TestChainWatcherCoopCloseSameTransactionAfterReorg tests that if the same -// transaction re-confirms after a reorganization, it is properly handled. -func TestChainWatcherCoopCloseSameTransactionAfterReorg(t *testing.T) { - t.Parallel() - - harness := newChainWatcherTestHarness(t) - - // Create a single cooperative close transaction. - tx := harness.createCoopCloseTx(5000) - - // Run flow with the same tx confirming after the reorg. - closeInfo := harness.runCoopCloseFlow(tx, true, 2, tx) - - harness.assertCoopCloseTx(closeInfo, tx) -} - -// TestChainWatcherCoopCloseMultipleReorgs tests handling of multiple -// consecutive reorganizations during cooperative close confirmation. -func TestChainWatcherCoopCloseMultipleReorgs(t *testing.T) { - t.Parallel() - - // Create test harness. - harness := newChainWatcherTestHarness(t) - - // Create multiple cooperative close transactions with different fees. - txs := []*wire.MsgTx{ - harness.createCoopCloseTx(5000), - harness.createCoopCloseTx(4950), - harness.createCoopCloseTx(4900), - harness.createCoopCloseTx(4850), - } - - // Define reorg depths for each transition. - reorgDepths := []int32{1, 2, 3} - - // Run multiple reorg flow. - closeInfo := harness.runMultipleReorgFlow(txs, reorgDepths) - - // Assert that the final transaction was confirmed. - harness.assertCoopCloseTx(closeInfo, txs[3]) -} - -// TestChainWatcherCoopCloseReorgNoAlternative tests that if a cooperative -// close is reorganized out and no alternative transaction appears, the -// chain watcher continues waiting. -func TestChainWatcherCoopCloseReorgNoAlternative(t *testing.T) { - t.Parallel() - - // Create test harness. - harness := newChainWatcherTestHarness(t) - - // Create a cooperative close transaction. - tx := harness.createCoopCloseTx(5000) - - // Send spend and wait for confirmation registration. - harness.sendSpend(tx) - harness.waitForConfRegistration() - - // Trigger reorg after some confirmations. - harness.mineBlocks(2) - harness.triggerReorg(tx, 2) - - // Assert no cooperative close event is received. - harness.assertNoCoopClose(2 * time.Second) - - // Now send a new transaction after the timeout. - harness.waitForSpendRegistration() - newTx := harness.createCoopCloseTx(4900) - harness.sendSpend(newTx) - harness.waitForConfRegistration() - harness.mineBlocks(1) - harness.confirmTx(newTx, harness.currentHeight) - - // Should receive cooperative close for the new transaction. - closeInfo := harness.waitForCoopClose(5 * time.Second) - harness.assertCoopCloseTx(closeInfo, newTx) -} - -// TestChainWatcherCoopCloseScaledConfirmationsWithReorg tests that scaled -// confirmations (based on channel capacity) work correctly with reorgs. -func TestChainWatcherCoopCloseScaledConfirmationsWithReorg(t *testing.T) { - t.Parallel() - - // Test with different confirmation requirements and reorg depths. - // Note: We start at 3 confirmations because 1-conf uses the fast path - // which bypasses reorg protection (it dispatches immediately). - testCases := []struct { - name string - requiredConfs uint32 - reorgDepth int32 - }{ - { - name: "triple_conf", - requiredConfs: 3, - reorgDepth: 2, - }, - { - name: "six_conf", - requiredConfs: 6, - reorgDepth: 4, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - // Create harness with specific confirmation - // requirements. - harness := newChainWatcherTestHarness( - t, withRequiredConfs(tc.requiredConfs), - ) - - // Create transactions. - tx1 := harness.createCoopCloseTx(5000) - tx2 := harness.createCoopCloseTx(4900) - - // Run with reorg at different depths based on capacity. - closeInfo := harness.runCoopCloseFlow( - tx1, true, tc.reorgDepth, tx2, - ) - - // Verify correct transaction confirmed. - harness.assertCoopCloseTx(closeInfo, tx2) - }) - } -} - -// TestChainWatcherCoopCloseRapidReorgs tests that the chain watcher handles -// multiple rapid reorgs in succession without getting into a broken state. -func TestChainWatcherCoopCloseRapidReorgs(t *testing.T) { - t.Parallel() - - // Create test harness. - harness := newChainWatcherTestHarness(t) - - // Create a cooperative close transaction. - tx := harness.createCoopCloseTx(5000) - - // Send spend notification. - harness.sendSpend(tx) - - // Trigger multiple rapid reorgs to stress the state machine. - for i := 0; i < 5; i++ { - harness.waitForConfRegistration() - harness.mineBlocks(1) - harness.triggerReorg(tx, int32(i+1)) - if i < 4 { - // Re-register for spend after each reorg except the - // last. - harness.waitForSpendRegistration() - harness.sendSpend(tx) - } - } - - // After stress, send a clean transaction. - harness.waitForSpendRegistration() - cleanTx := harness.createCoopCloseTx(4800) - harness.sendSpend(cleanTx) - harness.waitForConfRegistration() - harness.mineBlocks(1) - harness.confirmTx(cleanTx, harness.currentHeight) - - // Should still receive the cooperative close. - closeInfo := harness.waitForCoopClose(10 * time.Second) - harness.assertCoopCloseTx(closeInfo, cleanTx) -} diff --git a/contractcourt/chain_watcher_early_dispatch_test.go b/contractcourt/chain_watcher_early_dispatch_test.go deleted file mode 100644 index 3a71f6858..000000000 --- a/contractcourt/chain_watcher_early_dispatch_test.go +++ /dev/null @@ -1,224 +0,0 @@ -package contractcourt - -import ( - "testing" - "time" - - "github.com/lightningnetwork/lnd/channeldb" - "github.com/stretchr/testify/require" -) - -// TestEarlyDispatchCoopClose verifies the headline behavior: when a -// cooperative close spend is first detected on chain in the async path -// (numConfs > 1), the chain watcher fires the early-notify callback exactly -// once with a summary that carries IsPending=true. The full N-conf flow -// still completes normally and produces the regular CooperativeCloseInfo -// downstream. -func TestEarlyDispatchCoopClose(t *testing.T) { - t.Parallel() - - harness := newChainWatcherTestHarness( - t, withRequiredConfs(3), withEarlyCoopCloseCapture(), - ) - - tx := harness.createCoopCloseTx(5000) - - harness.sendSpend(tx) - harness.waitForConfRegistration() - - // The early-dispatch callback must have fired exactly once with a - // preliminary close summary that identifies the right tx, channel - // point, and close type. - harness.waitForEarlyCoopClose(1, time.Second) - require.Equal(t, 1, harness.earlyCoopCloseCount(), - "exactly one early dispatch expected on first spend detection") - - earlySummary := harness.earlyCoopCloseAt(0) - require.True(t, earlySummary.IsPending, - "early dispatched summary must have IsPending=true") - require.Equal(t, channeldb.CooperativeClose, earlySummary.CloseType) - require.Equal(t, tx.TxHash(), earlySummary.ClosingTXID) - require.Equal(t, harness.aliceChannel.State().FundingOutpoint, - earlySummary.ChanPoint) - - // Drive the close to N confs so the regular post-N-conf dispatch - // path also completes; the resulting CooperativeCloseInfo must - // reference the same tx. - harness.mineBlocks(1) - harness.confirmTx(tx, harness.currentHeight) - - closeInfo := harness.waitForCoopClose(5 * time.Second) - harness.assertCoopCloseTx(closeInfo, tx) -} - -// TestEarlyDispatchForceCloseNotInvoked verifies that force-close spends do -// NOT trigger the early-dispatch callback. Force-close paths intentionally -// stay on the N-confirmation dispatch contract; their CLOSED_CHANNEL event -// fires from the channel arbitrator's MarkChannelClosed callback at N -// confs. -func TestEarlyDispatchForceCloseNotInvoked(t *testing.T) { - t.Parallel() - - harness := newChainWatcherTestHarness( - t, withRequiredConfs(3), withEarlyCoopCloseCapture(), - ) - - tx := harness.createRemoteForceCloseTx() - harness.sendSpend(tx) - - // processDetectedSpend evaluates the early-dispatch path before - // registering the conf ntfn, so once that registration lands the - // decision is final: no need for a separate sleep window. - harness.waitForConfRegistration() - require.Equal(t, 0, harness.earlyCoopCloseCount(), - "remote force close must not trigger early dispatch") -} - -// TestEarlyDispatchSkippedOnFastPath verifies that when the chain watcher is -// in the fast (single-confirmation) path, the early-dispatch callback is NOT -// invoked. The fast-path's existing dispatchCooperativeClose -> -// MarkChannelClosed flow already fires CLOSED_CHANNEL at first conf with a -// fully populated summary (including close initiator from the historical -// bucket), so an early dispatch here would deliver a duplicate event with an -// unknown initiator and break the SubscribeChannelEvents contract. -// EarlyCoopCloseDispatched() must stay false so the channel arbitrator's -// suppression gate does not fire. -func TestEarlyDispatchSkippedOnFastPath(t *testing.T) { - t.Parallel() - - harness := newChainWatcherTestHarness( - t, withRequiredConfs(1), withEarlyCoopCloseCapture(), - ) - - tx := harness.createCoopCloseTx(5000) - - harness.sendSpend(tx) - - // The fast path dispatches the regular CooperativeCloseInfo - // synchronously from processDetectedSpend, so once the coop close - // event lands we know the early-dispatch branch (which runs in the - // same goroutine, just below the fast-path return) was either taken - // or skipped: no sleep window required. - closeInfo := harness.waitForCoopClose(5 * time.Second) - harness.assertCoopCloseTx(closeInfo, tx) - - require.Equal(t, 0, harness.earlyCoopCloseCount(), - "single-conf fast path must not invoke early dispatch") - require.False(t, harness.chainWatcher.EarlyCoopCloseDispatched(), - "EarlyCoopCloseDispatched must stay false on fast path so the "+ - "arbitrator still fires NotifyClosedChannel at "+ - "MarkChannelClosed time") -} - -// TestEarlyDispatchFlagSetAfterAsyncDispatch verifies that -// EarlyCoopCloseDispatched flips to true once the chain watcher fires the -// preliminary CLOSED_CHANNEL on the async multi-conf path. This is the gate -// the channel arbitrator reads in MarkChannelClosed to suppress the duplicate -// notify; if it didn't flip, subscribers would see two CLOSED_CHANNEL events -// for the same close. -func TestEarlyDispatchFlagSetAfterAsyncDispatch(t *testing.T) { - t.Parallel() - - harness := newChainWatcherTestHarness( - t, withRequiredConfs(3), withEarlyCoopCloseCapture(), - ) - - require.False(t, harness.chainWatcher.EarlyCoopCloseDispatched(), - "flag must be false before any spend is observed") - - tx := harness.createCoopCloseTx(5000) - harness.sendSpend(tx) - harness.waitForConfRegistration() - harness.waitForEarlyCoopClose(1, time.Second) - - require.True(t, harness.chainWatcher.EarlyCoopCloseDispatched(), - "flag must flip to true once the early dispatch fires so the "+ - "arbitrator suppresses the duplicate notify at "+ - "MarkChannelClosed time") -} - -// TestEarlyDispatchReorgRefiresOnReReplacement verifies the reorg recovery -// path: once a deep reorg removes the close, the early-dispatch flag is -// cleared, and the next coop close re-fires the early event with its own -// summary. This is the contract that lets a subscriber observe each -// distinct close attempt rather than only the first one. -func TestEarlyDispatchReorgRefiresOnReReplacement(t *testing.T) { - t.Parallel() - - harness := newChainWatcherTestHarness( - t, withRequiredConfs(3), withEarlyCoopCloseCapture(), - ) - - tx1 := harness.createCoopCloseTx(5000) - tx2 := harness.createCoopCloseTx(4900) - - // First close detected → early dispatch #1. - harness.sendSpend(tx1) - harness.waitForConfRegistration() - harness.waitForEarlyCoopClose(1, time.Second) - - // Reorg flushes the conf ntfn and resets the flag. - harness.triggerReorg(tx1, 2) - harness.waitForSpendRegistration() - - // Replacement close detected → early dispatch #2 with the new tx. - harness.sendSpend(tx2) - harness.waitForConfRegistration() - harness.waitForEarlyCoopClose(2, 2*time.Second) - - require.Equal(t, 2, harness.earlyCoopCloseCount(), - "reorg + replacement close must re-fire the early dispatch") - - first := harness.earlyCoopCloseAt(0) - second := harness.earlyCoopCloseAt(1) - require.Equal(t, tx1.TxHash(), first.ClosingTXID, - "first early dispatch must reference tx1") - require.Equal(t, tx2.TxHash(), second.ClosingTXID, - "second early dispatch must reference the replacement tx2") -} - -// TestEarlyDispatchRefiresOnReplacementBeforeNegConf verifies the narrow -// reorg race where a replacement coop close is processed *before* the old -// confirmation subscription's NegativeConf has been drained. Without the -// reconciliation guard in processDetectedSpend, the stale -// coopCloseEarlyDispatched flag from the first spend would suppress the -// second early dispatch, and MarkChannelClosed's CloseType-gated -// suppression would then drop the final notify too — leaving subscribers -// with only the stale event for the no-longer-tracked txid. The test -// simulates that ordering by feeding the replacement spend directly -// without first draining the reorg path. -func TestEarlyDispatchRefiresOnReplacementBeforeNegConf(t *testing.T) { - t.Parallel() - - harness := newChainWatcherTestHarness( - t, withRequiredConfs(3), withEarlyCoopCloseCapture(), - ) - - tx1 := harness.createCoopCloseTx(5000) - tx2 := harness.createCoopCloseTx(4900) - - // First close detected → early dispatch #1 with tx1. - harness.sendSpend(tx1) - harness.waitForConfRegistration() - harness.waitForEarlyCoopClose(1, time.Second) - - // A different coop close lands while the watcher is still tracking - // tx1 — this is the ordering ziggie flagged: the replacement arrives - // before the prior spend's NegativeConf is observed. The watcher - // must clear the stale flag and re-fire the early dispatch so the - // new tx surfaces over the channel notifier. - harness.sendSpend(tx2) - harness.waitForConfRegistration() - harness.waitForEarlyCoopClose(2, 2*time.Second) - - require.Equal(t, 2, harness.earlyCoopCloseCount(), - "replacement spend must re-fire the early dispatch even when "+ - "NegativeConf has not yet drained") - - require.Equal(t, tx2.TxHash(), harness.earlyCoopCloseAt(1).ClosingTXID, - "second early dispatch must reference the replacement tx2") - require.True(t, harness.chainWatcher.EarlyCoopCloseDispatched(), - "flag must remain set after the replacement's early dispatch "+ - "so the arbitrator's MarkChannelClosed suppression "+ - "still fires for the new tx") -} diff --git a/contractcourt/chain_watcher_reorg_test.go b/contractcourt/chain_watcher_reorg_test.go deleted file mode 100644 index bf2edd110..000000000 --- a/contractcourt/chain_watcher_reorg_test.go +++ /dev/null @@ -1,404 +0,0 @@ -package contractcourt - -import ( - "testing" - "time" - - "github.com/btcsuite/btcd/wire/v2" - "pgregory.net/rapid" -) - -// closeType represents the type of channel close for testing purposes. -type closeType int - -const ( - // closeTypeCoop represents a cooperative channel close. - closeTypeCoop closeType = iota - - // closeTypeRemoteUnilateral represents a remote unilateral close - // (remote party broadcasting their commitment). - closeTypeRemoteUnilateral - - // closeTypeLocalForce represents a local force close (us broadcasting - // our commitment). - closeTypeLocalForce - - // closeTypeBreach represents a breach (remote party broadcasting a - // revoked commitment). - closeTypeBreach -) - -// String returns a string representation of the close type. -func (c closeType) String() string { - switch c { - case closeTypeCoop: - return "cooperative" - case closeTypeRemoteUnilateral: - return "remote_unilateral" - case closeTypeLocalForce: - return "local_force" - case closeTypeBreach: - return "breach" - default: - return "unknown" - } -} - -// createCloseTx creates a close transaction of the specified type using the -// harness. -func createCloseTx(h *chainWatcherTestHarness, ct closeType, - outputValue int64) *wire.MsgTx { - - switch ct { - case closeTypeCoop: - return h.createCoopCloseTx(outputValue) - case closeTypeRemoteUnilateral: - return h.createRemoteForceCloseTx() - case closeTypeLocalForce: - return h.createLocalForceCloseTx() - case closeTypeBreach: - return h.createBreachCloseTx() - default: - h.t.Fatalf("unknown close type: %v", ct) - return nil - } -} - -// waitForCloseEvent waits for the appropriate close event based on close type. -func waitForCloseEvent(h *chainWatcherTestHarness, ct closeType, - timeout time.Duration) any { - - switch ct { - case closeTypeCoop: - return h.waitForCoopClose(timeout) - case closeTypeRemoteUnilateral: - return h.waitForRemoteUnilateralClose(timeout) - case closeTypeLocalForce: - return h.waitForLocalUnilateralClose(timeout) - case closeTypeBreach: - return h.waitForBreach(timeout) - default: - h.t.Fatalf("unknown close type: %v", ct) - return nil - } -} - -// assertCloseEventTx asserts that the close event matches the expected -// transaction based on close type. -func assertCloseEventTx(h *chainWatcherTestHarness, ct closeType, - event any, expectedTx *wire.MsgTx) { - - switch ct { - case closeTypeCoop: - coopInfo, ok := event.(*CooperativeCloseInfo) - if !ok { - h.t.Fatalf("expected CooperativeCloseInfo, got %T", - event) - } - h.assertCoopCloseTx(coopInfo, expectedTx) - - case closeTypeRemoteUnilateral: - remoteInfo, ok := event.(*RemoteUnilateralCloseInfo) - if !ok { - h.t.Fatalf("expected RemoteUnilateralCloseInfo, got %T", - event) - } - h.assertRemoteUnilateralCloseTx(remoteInfo, expectedTx) - - case closeTypeLocalForce: - localInfo, ok := event.(*LocalUnilateralCloseInfo) - if !ok { - h.t.Fatalf("expected LocalUnilateralCloseInfo, got %T", - event) - } - h.assertLocalUnilateralCloseTx(localInfo, expectedTx) - - case closeTypeBreach: - breachInfo, ok := event.(*BreachCloseInfo) - if !ok { - h.t.Fatalf("expected BreachCloseInfo, got %T", event) - } - h.assertBreachTx(breachInfo, expectedTx) - - default: - h.t.Fatalf("unknown close type: %v", ct) - } -} - -// generateAltTxsForReorgs generates alternative transactions for reorg -// scenarios. For commitment-based closes (breach, remote/local force), the same -// tx is reused since we can only have one commitment tx per channel state. For -// coop closes, new transactions with different output values are created. -func generateAltTxsForReorgs(h *chainWatcherTestHarness, ct closeType, - originalTx *wire.MsgTx, numReorgs int, sameTxAtEnd bool) []*wire.MsgTx { - - altTxs := make([]*wire.MsgTx, numReorgs) - - for i := 0; i < numReorgs; i++ { - switch ct { - case closeTypeBreach, closeTypeRemoteUnilateral, - closeTypeLocalForce: - - // Non-coop closes can only have one commitment tx, so - // all reorgs use the same transaction. - altTxs[i] = originalTx - - case closeTypeCoop: - if i == numReorgs-1 && sameTxAtEnd { - // Last reorg goes back to original transaction. - altTxs[i] = originalTx - } else { - // Create different coop close tx with different - // output value to make it unique. - outputValue := int64(5000 - (i+1)*100) - altTxs[i] = createCloseTx(h, ct, outputValue) - } - } - } - - return altTxs -} - -// testReorgProperties is the main property-based test for reorg handling -// across all close types. -// -// The testingT parameter is captured from the outer test function and used -// for operations that require *testing.T (like channel creation), while the -// rapid.T is used for all test reporting and property generation. -func testReorgProperties(testingT *testing.T) func(*rapid.T) { - return func(t *rapid.T) { - // Generate random close type. - allCloseTypes := []closeType{ - closeTypeCoop, - closeTypeRemoteUnilateral, - closeTypeLocalForce, - closeTypeBreach, - } - ct := rapid.SampledFrom(allCloseTypes).Draw(t, "closeType") - - // Generate random number of required confirmations (2-6). We - // use at least 2 so we have room for reorgs during - // confirmation. - requiredConfs := rapid.IntRange(2, 6).Draw(t, "requiredConfs") - - // Generate number of reorgs (1-3 to keep test runtime - // reasonable). - numReorgs := rapid.IntRange(1, 3).Draw(t, "numReorgs") - - // Generate whether the final transaction is the same as the - // original. - sameTxAtEnd := rapid.Bool().Draw(t, "sameTxAtEnd") - - // Log test parameters for debugging. - t.Logf("Testing %s close with %d confs, %d reorgs, "+ - "sameTxAtEnd=%v", - ct, requiredConfs, numReorgs, sameTxAtEnd) - - // Create test harness using both the concrete *testing.T for - // channel creation and the rapid.T for test reporting. - harness := newChainWatcherTestHarnessFromReporter( - testingT, t, withRequiredConfs(uint32(requiredConfs)), - ) - - // Create initial transaction. - tx1 := createCloseTx(harness, ct, 5000) - - // Generate alternative transactions for each reorg. - altTxs := generateAltTxsForReorgs( - harness, ct, tx1, numReorgs, sameTxAtEnd, - ) - - // Send the initial spend. - harness.sendSpend(tx1) - harness.waitForConfRegistration() - - // Execute the set of re-orgs, based on our random sample, we'll - // mine N blocks, do a re-org of size N, then wait for - // detection, and repeat. - for i := 0; i < numReorgs; i++ { - // Generate random reorg depth (1 to requiredConfs-1). - // We cap it to avoid reorging too far back. - reorgDepth := rapid.IntRange( - 1, requiredConfs-1, - ).Draw(t, "reorgDepth") - - // Mine some blocks (but less than required confs). - blocksToMine := rapid.IntRange( - 1, requiredConfs-1, - ).Draw(t, "blocksToMine") - harness.mineBlocks(int32(blocksToMine)) - - // Trigger reorg. - if i == 0 { - harness.triggerReorg( - tx1, int32(reorgDepth), - ) - } else { - harness.triggerReorg( - altTxs[i-1], int32(reorgDepth), - ) - } - - harness.waitForSpendRegistration() - - harness.sendSpend(altTxs[i]) - harness.waitForConfRegistration() - } - - // Mine enough blocks to confirm final transaction. - harness.mineBlocks(1) - finalTx := altTxs[numReorgs-1] - harness.confirmTx(finalTx, harness.currentHeight) - - // Wait for and verify close event. - event := waitForCloseEvent(harness, ct, 10*time.Second) - assertCloseEventTx(harness, ct, event, finalTx) - } -} - -// TestChainWatcherReorgAllCloseTypes runs property-based tests for reorg -// handling across all channel close types. It generates random combinations of -// the following: -// - Close type (coop, remote unilateral, local force, breach) -// - Number of confirmations required (2-6) -// - Number of reorgs (1-3) -// - Whether the final tx is same as original or different. -func TestChainWatcherReorgAllCloseTypes(t *testing.T) { - t.Parallel() - - rapid.Check(t, testReorgProperties(t)) -} - -// TestRemoteUnilateralCloseWithSingleReorg tests that a remote unilateral -// close is properly handled when a single reorg occurs during confirmation. -func TestRemoteUnilateralCloseWithSingleReorg(t *testing.T) { - t.Parallel() - - harness := newChainWatcherTestHarness(t) - - // Create two remote unilateral close transactions. - // Since these are commitment transactions, we can only have one per - // state, so we'll use the current one as tx1. - tx1 := harness.createRemoteForceCloseTx() - - // Advance channel state to get a different commitment. - _ = harness.createBreachCloseTx() - tx2 := harness.createRemoteForceCloseTx() - - // Send initial spend. - harness.sendSpend(tx1) - harness.waitForConfRegistration() - - // Mine a block and trigger reorg. - harness.mineBlocks(1) - harness.triggerReorg(tx1, 1) - - // Send alternative transaction after reorg. - harness.waitForSpendRegistration() - harness.sendSpend(tx2) - harness.waitForConfRegistration() - harness.mineBlocks(1) - harness.confirmTx(tx2, harness.currentHeight) - - // Verify correct event. - closeInfo := harness.waitForRemoteUnilateralClose(5 * time.Second) - harness.assertRemoteUnilateralCloseTx(closeInfo, tx2) -} - -// TestLocalForceCloseWithMultipleReorgs tests that a local force close is -// properly handled through multiple consecutive reorgs. -func TestLocalForceCloseWithMultipleReorgs(t *testing.T) { - t.Parallel() - - harness := newChainWatcherTestHarness(t) - - // For local force close, we can only broadcast our current commitment. - // We'll simulate multiple reorgs where the same tx keeps getting - // reorganized out and re-broadcast. - tx := harness.createLocalForceCloseTx() - - // First spend and reorg. - harness.sendSpend(tx) - harness.waitForConfRegistration() - harness.mineBlocks(1) - harness.triggerReorg(tx, 1) - - // Second spend and reorg. - harness.waitForSpendRegistration() - harness.sendSpend(tx) - harness.waitForConfRegistration() - harness.mineBlocks(1) - harness.triggerReorg(tx, 1) - - // Third spend - this one confirms. - harness.waitForSpendRegistration() - harness.sendSpend(tx) - harness.waitForConfRegistration() - harness.mineBlocks(1) - harness.confirmTx(tx, harness.currentHeight) - - // Verify correct event. - closeInfo := harness.waitForLocalUnilateralClose(5 * time.Second) - harness.assertLocalUnilateralCloseTx(closeInfo, tx) -} - -// TestBreachCloseWithDeepReorg tests that a breach (revoked commitment) is -// properly detected after a deep reorganization. -func TestBreachCloseWithDeepReorg(t *testing.T) { - t.Parallel() - - harness := newChainWatcherTestHarness(t) - - // Create a revoked commitment transaction. - revokedTx := harness.createBreachCloseTx() - - // Send spend and wait for confirmation registration. - harness.sendSpend(revokedTx) - harness.waitForConfRegistration() - - // Mine several blocks and then trigger a deep reorg. - harness.mineBlocks(5) - harness.triggerReorg(revokedTx, 5) - - // Re-broadcast same transaction after reorg. - harness.waitForSpendRegistration() - harness.sendSpend(revokedTx) - harness.waitForConfRegistration() - harness.mineBlocks(1) - harness.confirmTx(revokedTx, harness.currentHeight) - - // Verify breach detection. - breachInfo := harness.waitForBreach(5 * time.Second) - harness.assertBreachTx(breachInfo, revokedTx) -} - -// TestCoopCloseReorgToForceClose tests the edge case where a cooperative -// close gets reorged out and is replaced by a force close. -func TestCoopCloseReorgToForceClose(t *testing.T) { - t.Parallel() - - harness := newChainWatcherTestHarness(t) - - // Create a cooperative close and a force close transaction. - coopTx := harness.createCoopCloseTx(5000) - forceTx := harness.createRemoteForceCloseTx() - - // Send cooperative close. - harness.sendSpend(coopTx) - harness.waitForConfRegistration() - - // Trigger reorg that removes coop close. - harness.mineBlocks(1) - harness.triggerReorg(coopTx, 1) - - // Send force close as alternative. - harness.waitForSpendRegistration() - harness.sendSpend(forceTx) - harness.waitForConfRegistration() - harness.mineBlocks(1) - harness.confirmTx(forceTx, harness.currentHeight) - - // Should receive remote unilateral close event, not coop close. - closeInfo := harness.waitForRemoteUnilateralClose(5 * time.Second) - harness.assertRemoteUnilateralCloseTx(closeInfo, forceTx) -} diff --git a/contractcourt/chain_watcher_test.go b/contractcourt/chain_watcher_test.go index 27a810237..2dc3605d3 100644 --- a/contractcourt/chain_watcher_test.go +++ b/contractcourt/chain_watcher_test.go @@ -8,12 +8,10 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainio" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" - "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" lnmock "github.com/lightningnetwork/lnd/lntest/mock" "github.com/lightningnetwork/lnd/lnwallet" @@ -36,19 +34,16 @@ func TestChainWatcherRemoteUnilateralClose(t *testing.T) { // With the channels created, we'll now create a chain watcher instance // which will be watching for any closes of Alice's channel. - confRegistered := make(chan struct{}, 1) aliceNotifier := &lnmock.ChainNotifier{ - SpendChan: make(chan *chainntnfs.SpendDetail, 1), - EpochChan: make(chan *chainntnfs.BlockEpoch), - ConfChan: make(chan *chainntnfs.TxConfirmation, 1), - ConfRegistered: confRegistered, + SpendChan: make(chan *chainntnfs.SpendDetail, 1), + EpochChan: make(chan *chainntnfs.BlockEpoch), + ConfChan: make(chan *chainntnfs.TxConfirmation), } aliceChainWatcher, err := newChainWatcher(chainWatcherConfig{ chanState: aliceChannel.State(), notifier: aliceNotifier, signer: aliceChannel.Signer, extractStateNumHint: lnwallet.GetStateNumHint, - chanCloseConfs: fn.Some(uint32(1)), }) require.NoError(t, err, "unable to create chain watcher") err = aliceChainWatcher.Start() @@ -95,10 +90,6 @@ func TestChainWatcherRemoteUnilateralClose(t *testing.T) { t.Fatalf("unable to send blockbeat") } - // With chanCloseConfs set to 1, the fast-path dispatches immediately - // without confirmation registration. The close event should arrive - // directly after processing the blockbeat. - // We should get a new spend event over the remote unilateral close // event channel. var uniClose *RemoteUnilateralCloseInfo @@ -153,19 +144,16 @@ func TestChainWatcherRemoteUnilateralClosePendingCommit(t *testing.T) { // With the channels created, we'll now create a chain watcher instance // which will be watching for any closes of Alice's channel. - confRegistered := make(chan struct{}, 1) aliceNotifier := &lnmock.ChainNotifier{ - SpendChan: make(chan *chainntnfs.SpendDetail), - EpochChan: make(chan *chainntnfs.BlockEpoch), - ConfChan: make(chan *chainntnfs.TxConfirmation), - ConfRegistered: confRegistered, + SpendChan: make(chan *chainntnfs.SpendDetail), + EpochChan: make(chan *chainntnfs.BlockEpoch), + ConfChan: make(chan *chainntnfs.TxConfirmation), } aliceChainWatcher, err := newChainWatcher(chainWatcherConfig{ chanState: aliceChannel.State(), notifier: aliceNotifier, signer: aliceChannel.Signer, extractStateNumHint: lnwallet.GetStateNumHint, - chanCloseConfs: fn.Some(uint32(1)), }) require.NoError(t, err, "unable to create chain watcher") if err := aliceChainWatcher.Start(); err != nil { @@ -231,10 +219,6 @@ func TestChainWatcherRemoteUnilateralClosePendingCommit(t *testing.T) { t.Fatalf("unable to send blockbeat") } - // With chanCloseConfs set to 1, the fast-path dispatches immediately - // without confirmation registration. The close event should arrive - // directly after processing the blockbeat. - // We should get a new spend event over the remote unilateral close // event channel. var uniClose *RemoteUnilateralCloseInfo @@ -265,11 +249,11 @@ type dlpTestCase struct { // state) are returned. func executeStateTransitions(t *testing.T, htlcAmount lnwire.MilliSatoshi, aliceChannel, bobChannel *lnwallet.LightningChannel, - numUpdates uint8) ([]*chanstate.OpenChannel, error) { + numUpdates uint8) ([]*channeldb.OpenChannel, error) { // We'll make a copy of the channel state before each transition. var ( - chanStates []*chanstate.OpenChannel + chanStates []*channeldb.OpenChannel ) state, err := copyChannelState(t, aliceChannel.State()) @@ -347,12 +331,10 @@ func TestChainWatcherDataLossProtect(t *testing.T) { // With the channels created, we'll now create a chain watcher // instance which will be watching for any closes of Alice's // channel. - confRegistered := make(chan struct{}, 1) aliceNotifier := &lnmock.ChainNotifier{ - SpendChan: make(chan *chainntnfs.SpendDetail), - EpochChan: make(chan *chainntnfs.BlockEpoch), - ConfChan: make(chan *chainntnfs.TxConfirmation), - ConfRegistered: confRegistered, + SpendChan: make(chan *chainntnfs.SpendDetail), + EpochChan: make(chan *chainntnfs.BlockEpoch), + ConfChan: make(chan *chainntnfs.TxConfirmation), } aliceChainWatcher, err := newChainWatcher(chainWatcherConfig{ chanState: aliceChanState, @@ -425,8 +407,6 @@ func TestChainWatcherDataLossProtect(t *testing.T) { t.Fatalf("unable to send blockbeat") } - aliceNotifier.WaitForConfRegistrationAndSend(t) - // We should get a new uni close resolution that indicates we // processed the DLP scenario. var uniClose *RemoteUnilateralCloseInfo @@ -500,6 +480,7 @@ func TestChainWatcherDataLossProtect(t *testing.T) { testName := fmt.Sprintf("num_updates=%v,broadcast_state_num=%v", testCase.NumUpdates, testCase.BroadcastStateNum) + testCase := testCase t.Run(testName, func(t *testing.T) { t.Parallel() @@ -551,12 +532,10 @@ func TestChainWatcherLocalForceCloseDetect(t *testing.T) { // With the channels created, we'll now create a chain watcher // instance which will be watching for any closes of Alice's // channel. - confRegistered := make(chan struct{}, 1) aliceNotifier := &lnmock.ChainNotifier{ - SpendChan: make(chan *chainntnfs.SpendDetail), - EpochChan: make(chan *chainntnfs.BlockEpoch), - ConfChan: make(chan *chainntnfs.TxConfirmation), - ConfRegistered: confRegistered, + SpendChan: make(chan *chainntnfs.SpendDetail), + EpochChan: make(chan *chainntnfs.BlockEpoch), + ConfChan: make(chan *chainntnfs.TxConfirmation), } aliceChainWatcher, err := newChainWatcher(chainWatcherConfig{ chanState: aliceChanState, @@ -625,8 +604,6 @@ func TestChainWatcherLocalForceCloseDetect(t *testing.T) { t.Fatalf("unable to send blockbeat") } - aliceNotifier.WaitForConfRegistrationAndSend(t) - // We should get a local force close event from Alice as she // should be able to detect the close based on the commitment // outputs. @@ -724,6 +701,7 @@ func TestChainWatcherLocalForceCloseDetect(t *testing.T) { testCase.localOutputOnly, ) + testCase := testCase t.Run(testName, func(t *testing.T) { t.Parallel() diff --git a/contractcourt/chain_watcher_test_harness.go b/contractcourt/chain_watcher_test_harness.go deleted file mode 100644 index 06e7e49dd..000000000 --- a/contractcourt/chain_watcher_test_harness.go +++ /dev/null @@ -1,746 +0,0 @@ -package contractcourt - -import ( - "sync" - "testing" - "time" - - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/chainntnfs" - "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/fn/v2" - lnmock "github.com/lightningnetwork/lnd/lntest/mock" - "github.com/lightningnetwork/lnd/lnwallet" - "github.com/lightningnetwork/lnd/lnwire" -) - -// testReporter is a minimal interface for test reporting that is satisfied -// by both *testing.T and *rapid.T, allowing the harness to work with -// property-based tests. -type testReporter interface { - Helper() - Fatalf(format string, args ...any) -} - -// chainWatcherTestHarness provides a test harness for chain watcher tests -// with utilities for simulating spends, confirmations, and reorganizations. -type chainWatcherTestHarness struct { - t testReporter - - // aliceChannel and bobChannel are the test channels. - aliceChannel *lnwallet.LightningChannel - bobChannel *lnwallet.LightningChannel - - // chainWatcher is the chain watcher under test. - chainWatcher *chainWatcher - - // notifier is the mock chain notifier. - notifier *mockChainNotifier - - // chanEvents is the channel event subscription. - chanEvents *ChainEventSubscription - - // currentHeight tracks the current block height. - currentHeight int32 - - // blockbeatProcessed is a channel that signals when a blockbeat has - // been processed. - blockbeatProcessed chan struct{} - - // earlyCoopCloseMu guards earlyCoopCloseSummaries. - earlyCoopCloseMu sync.Mutex - - // earlyCoopCloseSummaries records every invocation of the - // notifyEarlyCoopClose callback when captureEarlyCoopClose was - // enabled. Tests assert against length and contents. - earlyCoopCloseSummaries []*channeldb.ChannelCloseSummary -} - -// mockChainNotifier extends the standard mock with additional channels for -// testing cooperative close reorgs. -type mockChainNotifier struct { - *lnmock.ChainNotifier - - // confEvents tracks active confirmation event subscriptions. - confEvents []*mockConfirmationEvent - - // confRegistered is a channel that signals when a new confirmation - // event has been registered. - confRegistered chan struct{} - - // spendEvents tracks active spend event subscriptions. - spendEvents []*chainntnfs.SpendEvent - - // spendRegistered is a channel that signals when a new spend - // event has been registered. - spendRegistered chan struct{} -} - -// mockConfirmationEvent represents a mock confirmation event subscription. -type mockConfirmationEvent struct { - txid chainhash.Hash - numConfs uint32 - confirmedChan chan *chainntnfs.TxConfirmation - negConfChan chan int32 - cancelled bool -} - -// RegisterSpendNtfn creates a new mock spend event. -func (m *mockChainNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, - pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) { - - // The base mock already has SpendChan, use that. - spendEvent := &chainntnfs.SpendEvent{ - Spend: m.SpendChan, - Cancel: func() { - // No-op for now. - }, - } - - m.spendEvents = append(m.spendEvents, spendEvent) - - // Signal that a new spend event has been registered. - select { - case m.spendRegistered <- struct{}{}: - default: - } - - return spendEvent, nil -} - -// RegisterConfirmationsNtfn creates a new mock confirmation event. -func (m *mockChainNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, - pkScript []byte, numConfs, heightHint uint32, - opts ...chainntnfs.NotifierOption, -) (*chainntnfs.ConfirmationEvent, error) { - - mockEvent := &mockConfirmationEvent{ - txid: *txid, - numConfs: numConfs, - confirmedChan: make(chan *chainntnfs.TxConfirmation, 1), - negConfChan: make(chan int32, 1), - } - - m.confEvents = append(m.confEvents, mockEvent) - - // Signal that a new confirmation event has been registered. - select { - case m.confRegistered <- struct{}{}: - default: - } - - return &chainntnfs.ConfirmationEvent{ - Confirmed: mockEvent.confirmedChan, - NegativeConf: mockEvent.negConfChan, - Cancel: func() { - mockEvent.cancelled = true - }, - }, nil -} - -// harnessOpt is a functional option for configuring the test harness. -type harnessOpt func(*harnessConfig) - -// harnessConfig holds configuration for the test harness. -type harnessConfig struct { - requiredConfs fn.Option[uint32] - - // captureEarlyCoopClose, when true, wires a notifyEarlyCoopClose - // callback into the chain watcher that records each invocation onto - // the harness's earlyCoopCloseSummaries slice for assertions. - captureEarlyCoopClose bool -} - -// withRequiredConfs sets the number of confirmations required for channel -// closes. -func withRequiredConfs(confs uint32) harnessOpt { - return func(cfg *harnessConfig) { - cfg.requiredConfs = fn.Some(confs) - } -} - -// withEarlyCoopCloseCapture enables recording of every invocation of the -// chain watcher's notifyEarlyCoopClose callback on the harness so tests can -// assert when, how often, and with which summary the early-dispatch fires. -func withEarlyCoopCloseCapture() harnessOpt { - return func(cfg *harnessConfig) { - cfg.captureEarlyCoopClose = true - } -} - -// newChainWatcherTestHarness creates a new test harness for chain watcher -// tests. -func newChainWatcherTestHarness(t *testing.T, - opts ...harnessOpt) *chainWatcherTestHarness { - - return newChainWatcherTestHarnessFromReporter(t, t, opts...) -} - -// newChainWatcherTestHarnessFromReporter creates a test harness that works -// with both *testing.T and *rapid.T. The t parameter is used for -// operations that specifically require *testing.T (like CreateTestChannels), -// while reporter is used for all test reporting (Helper, Fatalf). -func newChainWatcherTestHarnessFromReporter(t *testing.T, - reporter testReporter, opts ...harnessOpt) *chainWatcherTestHarness { - - reporter.Helper() - - // Apply options. - cfg := &harnessConfig{ - requiredConfs: fn.None[uint32](), - } - for _, opt := range opts { - opt(cfg) - } - - // Create test channels. - aliceChannel, bobChannel, err := lnwallet.CreateTestChannels( - t, channeldb.SingleFunderTweaklessBit, - ) - if err != nil { - reporter.Fatalf("unable to create test channels: %v", err) - } - - // Create mock notifier. - baseNotifier := &lnmock.ChainNotifier{ - SpendChan: make(chan *chainntnfs.SpendDetail, 1), - EpochChan: make(chan *chainntnfs.BlockEpoch), - ConfChan: make(chan *chainntnfs.TxConfirmation, 1), - } - - notifier := &mockChainNotifier{ - ChainNotifier: baseNotifier, - confEvents: make([]*mockConfirmationEvent, 0), - confRegistered: make(chan struct{}, 10), - spendEvents: make([]*chainntnfs.SpendEvent, 0), - spendRegistered: make(chan struct{}, 10), - } - - harness := &chainWatcherTestHarness{ - t: reporter, - aliceChannel: aliceChannel, - bobChannel: bobChannel, - notifier: notifier, - currentHeight: 100, - blockbeatProcessed: make(chan struct{}), - } - - // If the test wants to observe early-dispatch invocations, install - // a callback that records each summary onto the harness. - var notifyEarlyCoopClose func(*channeldb.ChannelCloseSummary) - if cfg.captureEarlyCoopClose { - notifyEarlyCoopClose = func( - s *channeldb.ChannelCloseSummary) { - - harness.earlyCoopCloseMu.Lock() - harness.earlyCoopCloseSummaries = append( - harness.earlyCoopCloseSummaries, s, - ) - harness.earlyCoopCloseMu.Unlock() - } - } - - // Create chain watcher. - chainWatcher, err := newChainWatcher(chainWatcherConfig{ - chanState: aliceChannel.State(), - notifier: notifier, - signer: aliceChannel.Signer, - extractStateNumHint: lnwallet.GetStateNumHint, - chanCloseConfs: cfg.requiredConfs, - notifyEarlyCoopClose: notifyEarlyCoopClose, - contractBreach: func( - retInfo *lnwallet.BreachRetribution, - ) error { - // In tests, we just need to accept the breach - // notification. - return nil - }, - }) - if err != nil { - reporter.Fatalf("unable to create chain watcher: %v", err) - } - - // Start chain watcher (this will register for spend notification). - err = chainWatcher.Start() - if err != nil { - reporter.Fatalf("unable to start chain watcher: %v", err) - } - - // Subscribe to channel events. - chanEvents := chainWatcher.SubscribeChannelEvents() - - harness.chainWatcher = chainWatcher - harness.chanEvents = chanEvents - - // Wait for the initial spend registration that happens in Start(). - harness.waitForSpendRegistration() - - // Verify BlockbeatChan is initialized. - if chainWatcher.BlockbeatChan == nil { - reporter.Fatalf("BlockbeatChan is nil after initialization") - } - - // Register cleanup. We use the t for Cleanup since rapid.T - // may not have this method in the same way. - t.Cleanup(func() { - _ = chainWatcher.Stop() - }) - - return harness -} - -// createCoopCloseTx creates a cooperative close transaction with the given -// output value. The transaction will have the proper sequence number to -// indicate it's a cooperative close. -func (h *chainWatcherTestHarness) createCoopCloseTx( - outputValue int64) *wire.MsgTx { - - fundingOutpoint := h.aliceChannel.State().FundingOutpoint - - return &wire.MsgTx{ - TxIn: []*wire.TxIn{{ - PreviousOutPoint: fundingOutpoint, - Sequence: wire.MaxTxInSequenceNum, - }}, - TxOut: []*wire.TxOut{{ - Value: outputValue, - // Unique script. - PkScript: []byte{byte(outputValue % 255)}, - }}, - } -} - -// createRemoteForceCloseTx creates a remote force close transaction. -// From Alice's perspective, this is Bob's local commitment transaction. -func (h *chainWatcherTestHarness) createRemoteForceCloseTx() *wire.MsgTx { - return h.bobChannel.State().LocalCommitment.CommitTx -} - -// createLocalForceCloseTx creates a local force close transaction. -// This is Alice's local commitment transaction. -func (h *chainWatcherTestHarness) createLocalForceCloseTx() *wire.MsgTx { - return h.aliceChannel.State().LocalCommitment.CommitTx -} - -// createBreachCloseTx creates a breach (revoked commitment) transaction. -// We advance the channel state, save the commitment, then advance again -// to revoke it. Returns the revoked commitment tx. -func (h *chainWatcherTestHarness) createBreachCloseTx() *wire.MsgTx { - h.t.Helper() - - // To create a revoked commitment, we need to advance the channel state - // at least once. We'll use the test utils helper to add an HTLC and - // force a state transition. - - // Get the current commitment before we advance (this will be revoked). - revokedCommit := h.bobChannel.State().LocalCommitment.CommitTx - - // Add a fake HTLC to advance state. - htlcAmount := lnwire.NewMSatFromSatoshis(10000) - paymentHash := [32]byte{4, 5, 6} - htlc := &lnwire.UpdateAddHTLC{ - ID: 0, - Amount: htlcAmount, - Expiry: uint32(h.currentHeight + 100), - PaymentHash: paymentHash, - } - - // Add HTLC to both channels. - if _, err := h.aliceChannel.AddHTLC(htlc, nil); err != nil { - h.t.Fatalf("unable to add HTLC to alice: %v", err) - } - if _, err := h.bobChannel.ReceiveHTLC(htlc); err != nil { - h.t.Fatalf("unable to add HTLC to bob: %v", err) - } - - // Force state transition using the helper. - err := lnwallet.ForceStateTransition(h.aliceChannel, h.bobChannel) - if err != nil { - h.t.Fatalf("unable to force state transition: %v", err) - } - - // Return the revoked commitment (Bob's previous local commitment). - return revokedCommit -} - -// sendSpend sends a spend notification for the given transaction. -func (h *chainWatcherTestHarness) sendSpend(tx *wire.MsgTx) { - h.t.Helper() - - txHash := tx.TxHash() - spend := &chainntnfs.SpendDetail{ - SpenderTxHash: &txHash, - SpendingTx: tx, - SpendingHeight: h.currentHeight, - } - - select { - case h.notifier.SpendChan <- spend: - case <-time.After(time.Second): - h.t.Fatalf("unable to send spend notification") - } -} - -// confirmTx sends a confirmation notification for the given transaction. -func (h *chainWatcherTestHarness) confirmTx(tx *wire.MsgTx, height int32) { - h.t.Helper() - - // Find the confirmation event for this transaction. - txHash := tx.TxHash() - var confEvent *mockConfirmationEvent - for _, event := range h.notifier.confEvents { - if event.txid == txHash && !event.cancelled { - confEvent = event - break - } - } - - if confEvent == nil { - h.t.Fatalf("no confirmation event registered for tx %v", txHash) - } - - // Send confirmation. - select { - case confEvent.confirmedChan <- &chainntnfs.TxConfirmation{ - Tx: tx, - BlockHeight: uint32(height), - }: - case <-time.After(time.Second): - h.t.Fatalf("unable to send confirmation") - } -} - -// triggerReorg sends a negative confirmation (reorg) notification for the -// given transaction with the specified reorg depth. -func (h *chainWatcherTestHarness) triggerReorg(tx *wire.MsgTx, - reorgDepth int32) { - - h.t.Helper() - - // Find the confirmation event for this transaction. - txHash := tx.TxHash() - var confEvent *mockConfirmationEvent - for _, event := range h.notifier.confEvents { - if event.txid == txHash && !event.cancelled { - confEvent = event - break - } - } - - if confEvent == nil { - // The chain watcher might not have registered for - // confirmations yet. - return - } - - // Send negative confirmation. - select { - case confEvent.negConfChan <- reorgDepth: - case <-time.After(time.Second): - h.t.Fatalf("unable to send negative confirmation") - } -} - -// mineBlocks advances the current block height. -func (h *chainWatcherTestHarness) mineBlocks(n int32) { - h.currentHeight += n -} - -// earlyCoopCloseCount returns how many times the early-dispatch callback has -// fired since the harness started. -func (h *chainWatcherTestHarness) earlyCoopCloseCount() int { - h.earlyCoopCloseMu.Lock() - defer h.earlyCoopCloseMu.Unlock() - - return len(h.earlyCoopCloseSummaries) -} - -// earlyCoopCloseAt returns the early-dispatch summary recorded at the given -// index. The harness fails the test if the index is out of range. -func (h *chainWatcherTestHarness) earlyCoopCloseAt( - idx int) *channeldb.ChannelCloseSummary { - - h.earlyCoopCloseMu.Lock() - defer h.earlyCoopCloseMu.Unlock() - - if idx >= len(h.earlyCoopCloseSummaries) { - h.t.Fatalf("expected early-dispatch index %d, only %d "+ - "summaries recorded", idx, - len(h.earlyCoopCloseSummaries)) - } - - return h.earlyCoopCloseSummaries[idx] -} - -// waitForEarlyCoopClose blocks until at least the supplied count of -// early-dispatch invocations have been recorded, or the timeout elapses. -func (h *chainWatcherTestHarness) waitForEarlyCoopClose(want int, - timeout time.Duration) { - - h.t.Helper() - - deadline := time.Now().Add(timeout) - for { - if h.earlyCoopCloseCount() >= want { - return - } - if time.Now().After(deadline) { - h.t.Fatalf("expected %d early-dispatch invocations, "+ - "got %d after %v", want, - h.earlyCoopCloseCount(), timeout) - - return - } - - time.Sleep(10 * time.Millisecond) - } -} - -// waitForCoopClose waits for a cooperative close event and returns it. -func (h *chainWatcherTestHarness) waitForCoopClose( - timeout time.Duration) *CooperativeCloseInfo { - - h.t.Helper() - - select { - case coopClose := <-h.chanEvents.CooperativeClosure: - return coopClose - case <-time.After(timeout): - h.t.Fatalf("didn't receive cooperative close event") - return nil - } -} - -// waitForConfRegistration waits for the chain watcher to register for -// confirmation notifications. -func (h *chainWatcherTestHarness) waitForConfRegistration() { - h.t.Helper() - - select { - case <-h.notifier.confRegistered: - // Registration complete. - case <-time.After(2 * time.Second): - // Not necessarily a failure - some tests don't register. - } -} - -// waitForSpendRegistration waits for the chain watcher to register for -// spend notifications. -func (h *chainWatcherTestHarness) waitForSpendRegistration() { - h.t.Helper() - - select { - case <-h.notifier.spendRegistered: - // Registration complete. - case <-time.After(2 * time.Second): - // Not necessarily a failure - some tests don't register. - } -} - -// assertCoopCloseTx asserts that the given cooperative close info matches -// the expected transaction. -func (h *chainWatcherTestHarness) assertCoopCloseTx( - closeInfo *CooperativeCloseInfo, expectedTx *wire.MsgTx) { - - h.t.Helper() - - expectedHash := expectedTx.TxHash() - if closeInfo.ClosingTXID != expectedHash { - h.t.Fatalf("wrong tx confirmed: expected %v, got %v", - expectedHash, closeInfo.ClosingTXID) - } -} - -// assertNoCoopClose asserts that no cooperative close event is received -// within the given timeout. -func (h *chainWatcherTestHarness) assertNoCoopClose(timeout time.Duration) { - h.t.Helper() - - select { - case <-h.chanEvents.CooperativeClosure: - h.t.Fatalf("unexpected cooperative close event") - case <-time.After(timeout): - // Expected timeout. - } -} - -// runCoopCloseFlow runs a complete cooperative close flow including spend, -// optional reorg, and confirmation. This helper coordinates the timing -// between the different events. -func (h *chainWatcherTestHarness) runCoopCloseFlow( - tx *wire.MsgTx, shouldReorg bool, reorgDepth int32, - altTx *wire.MsgTx) *CooperativeCloseInfo { - - h.t.Helper() - - // Send initial spend notification. The closeObserver's state machine - // will detect this and register for confirmations. - h.sendSpend(tx) - - // Wait for the chain watcher to register for confirmations. - h.waitForConfRegistration() - - if shouldReorg { - // Trigger reorg which resets the state machine. - h.triggerReorg(tx, reorgDepth) - - // If we have an alternative transaction, send it. - if altTx != nil { - // After reorg, the chain watcher should re-register for - // ANY spend of the funding output. - h.waitForSpendRegistration() - - // Send alternative spend. - h.sendSpend(altTx) - - // Wait for it to register for confirmations. - h.waitForConfRegistration() - - // Confirm alternative transaction to unblock. - h.mineBlocks(1) - h.confirmTx(altTx, h.currentHeight) - } - } else { - // Normal confirmation flow - confirm to unblock - // waitForCoopCloseConfirmation. - h.mineBlocks(1) - h.confirmTx(tx, h.currentHeight) - } - - // Wait for cooperative close event. - return h.waitForCoopClose(5 * time.Second) -} - -// runMultipleReorgFlow simulates multiple consecutive reorganizations with -// different transactions confirming after each reorg. -func (h *chainWatcherTestHarness) runMultipleReorgFlow(txs []*wire.MsgTx, - reorgDepths []int32) *CooperativeCloseInfo { - - h.t.Helper() - - if len(txs) < 2 { - h.t.Fatalf("need at least 2 transactions for reorg flow") - } - if len(reorgDepths) != len(txs)-1 { - h.t.Fatalf("reorg depths must be one less than transactions") - } - - // Send initial spend. - h.sendSpend(txs[0]) - - // Process each reorg. - for i, depth := range reorgDepths { - // Wait for confirmation registration. - h.waitForConfRegistration() - - // Trigger reorg for current transaction. - h.triggerReorg(txs[i], depth) - - // Wait for re-registration for spend. - h.waitForSpendRegistration() - - // Send next transaction. - h.sendSpend(txs[i+1]) - } - - // Wait for final confirmation registration. - h.waitForConfRegistration() - - // Confirm the final transaction. - finalTx := txs[len(txs)-1] - h.mineBlocks(1) - h.confirmTx(finalTx, h.currentHeight) - - // Wait for cooperative close event. - return h.waitForCoopClose(10 * time.Second) -} - -// waitForRemoteUnilateralClose waits for a remote unilateral close event. -func (h *chainWatcherTestHarness) waitForRemoteUnilateralClose( - timeout time.Duration) *RemoteUnilateralCloseInfo { - - h.t.Helper() - - select { - case remoteClose := <-h.chanEvents.RemoteUnilateralClosure: - return remoteClose - case <-time.After(timeout): - h.t.Fatalf("didn't receive remote unilateral close event") - return nil - } -} - -// waitForLocalUnilateralClose waits for a local unilateral close event. -func (h *chainWatcherTestHarness) waitForLocalUnilateralClose( - timeout time.Duration) *LocalUnilateralCloseInfo { - - h.t.Helper() - - select { - case localClose := <-h.chanEvents.LocalUnilateralClosure: - return localClose - case <-time.After(timeout): - h.t.Fatalf("didn't receive local unilateral close event") - return nil - } -} - -// waitForBreach waits for a breach (contract breach) event. -func (h *chainWatcherTestHarness) waitForBreach( - timeout time.Duration) *BreachCloseInfo { - - h.t.Helper() - - select { - case breach := <-h.chanEvents.ContractBreach: - return breach - case <-time.After(timeout): - h.t.Fatalf("didn't receive contract breach event") - return nil - } -} - -// assertRemoteUnilateralCloseTx asserts that the given remote unilateral close -// info matches the expected transaction. -func (h *chainWatcherTestHarness) assertRemoteUnilateralCloseTx( - closeInfo *RemoteUnilateralCloseInfo, expectedTx *wire.MsgTx) { - - h.t.Helper() - - expectedHash := expectedTx.TxHash() - actualHash := closeInfo.UnilateralCloseSummary.SpendDetail.SpenderTxHash - if *actualHash != expectedHash { - h.t.Fatalf("wrong tx confirmed: expected %v, got %v", - expectedHash, *actualHash) - } -} - -// assertLocalUnilateralCloseTx asserts that the given local unilateral close -// info matches the expected transaction. -func (h *chainWatcherTestHarness) assertLocalUnilateralCloseTx( - closeInfo *LocalUnilateralCloseInfo, expectedTx *wire.MsgTx) { - - h.t.Helper() - - expectedHash := expectedTx.TxHash() - actualHash := closeInfo.LocalForceCloseSummary.CloseTx.TxHash() - if actualHash != expectedHash { - h.t.Fatalf("wrong tx confirmed: expected %v, got %v", - expectedHash, actualHash) - } -} - -// assertBreachTx asserts that the given breach info matches the expected -// transaction. -func (h *chainWatcherTestHarness) assertBreachTx( - breachInfo *BreachCloseInfo, expectedTx *wire.MsgTx) { - - h.t.Helper() - - expectedHash := expectedTx.TxHash() - if breachInfo.CommitHash != expectedHash { - h.t.Fatalf("wrong tx confirmed: expected %v, got %v", - expectedHash, breachInfo.CommitHash) - } -} diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 5435b705f..1c4db6fb8 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -10,13 +10,12 @@ import ( "sync/atomic" "time" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainio" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/htlcswitch/hop" @@ -167,7 +166,7 @@ type ChannelArbitratorConfig struct { // FetchHistoricalChannel retrieves the historical state of a channel. // This is mostly used to supplement the ContractResolvers with // additional information required for proper contract resolution. - FetchHistoricalChannel func() (*chanstate.OpenChannel, error) + FetchHistoricalChannel func() (*channeldb.OpenChannel, error) // FindOutgoingHTLCDeadline returns the deadline in absolute block // height for the specified outgoing HTLC. For an outgoing HTLC, its @@ -616,6 +615,7 @@ func maybeAugmentTaprootResolvers(chanType channeldb.ChannelType, //nolint:ll htlcResolutions := contractResolutions.HtlcResolutions.OutgoingHTLCs for _, htlcRes := range htlcResolutions { + htlcRes := htlcRes if r.htlcResolution.ClaimOutpoint == htlcRes.ClaimOutpoint { @@ -628,6 +628,7 @@ func maybeAugmentTaprootResolvers(chanType channeldb.ChannelType, //nolint:ll htlcResolutions := contractResolutions.HtlcResolutions.OutgoingHTLCs for _, htlcRes := range htlcResolutions { + htlcRes := htlcRes if r.htlcResolution.ClaimOutpoint == htlcRes.ClaimOutpoint { @@ -640,6 +641,7 @@ func maybeAugmentTaprootResolvers(chanType channeldb.ChannelType, //nolint:ll htlcResolutions := contractResolutions.HtlcResolutions.IncomingHTLCs for _, htlcRes := range htlcResolutions { + htlcRes := htlcRes if r.htlcResolution.ClaimOutpoint == htlcRes.ClaimOutpoint { @@ -651,6 +653,7 @@ func maybeAugmentTaprootResolvers(chanType channeldb.ChannelType, //nolint:ll htlcResolutions := contractResolutions.HtlcResolutions.IncomingHTLCs for _, htlcRes := range htlcResolutions { + htlcRes := htlcRes if r.htlcResolution.ClaimOutpoint == htlcRes.ClaimOutpoint { @@ -721,6 +724,7 @@ func (c *ChannelArbitrator) relaunchResolvers(commitSet *CommitSet, // order to ensure we have complete coverage. htlcMap := make(map[wire.OutPoint]*channeldb.HTLC) for _, htlc := range confirmedHTLCs { + htlc := htlc outpoint := wire.OutPoint{ Hash: commitHash, Index: uint32(htlc.OutputIndex), @@ -731,7 +735,7 @@ func (c *ChannelArbitrator) relaunchResolvers(commitSet *CommitSet, // We'll also fetch the historical state of this channel, as it should // have been marked as closed by now, and supplement it to each resolver // such that we can properly resolve our pending contracts. - var chanState *chanstate.OpenChannel + var chanState *channeldb.OpenChannel chanState, err = c.cfg.FetchHistoricalChannel() switch { // If we don't find this channel, then it may be the case that it @@ -2360,7 +2364,7 @@ func (c *ChannelArbitrator) prepContractResolutions( // We'll also fetch the historical state of this channel, as it should // have been marked as closed by now, and supplement it to each resolver // such that we can properly resolve our pending contracts. - var chanState *chanstate.OpenChannel + var chanState *channeldb.OpenChannel chanState, err := c.cfg.FetchHistoricalChannel() switch { // If we don't find this channel, then it may be the case that it @@ -2430,13 +2434,6 @@ func (c *ChannelArbitrator) prepContractResolutions( return htlcResolvers, nil } - // Determine the channel type once before the resolution loop so we - // don't repeat the nil check on every iteration. - var chanType channeldb.ChannelType - if chanState != nil { - chanType = chanState.ChanType - } - // For each HTLC, we'll either act immediately, meaning we'll instantly // fail the HTLC, or we'll act only once the transaction has been // confirmed, in which case we'll need an HTLC resolver. @@ -2446,6 +2443,7 @@ func (c *ChannelArbitrator) prepContractResolutions( // claim the HTLC (second-level or directly), then add the pre case HtlcClaimAction: for _, htlc := range htlcs { + htlc := htlc htlcOp := wire.OutPoint{ Hash: commitHash, @@ -2462,8 +2460,7 @@ func (c *ChannelArbitrator) prepContractResolutions( } resolver := newSuccessResolver( - resolution, height, htlc, chanType, - resolverCfg, + resolution, height, htlc, resolverCfg, ) if chanState != nil { resolver.SupplementState(chanState) @@ -2476,6 +2473,7 @@ func (c *ChannelArbitrator) prepContractResolutions( // backwards. case HtlcTimeoutAction: for _, htlc := range htlcs { + htlc := htlc htlcOp := wire.OutPoint{ Hash: commitHash, @@ -2490,8 +2488,7 @@ func (c *ChannelArbitrator) prepContractResolutions( } resolver := newTimeoutResolver( - resolution, height, htlc, chanType, - resolverCfg, + resolution, height, htlc, resolverCfg, ) if chanState != nil { resolver.SupplementState(chanState) @@ -2512,6 +2509,7 @@ func (c *ChannelArbitrator) prepContractResolutions( // learn of the pre-image, or let the remote party time out. case HtlcIncomingWatchAction: for _, htlc := range htlcs { + htlc := htlc htlcOp := wire.OutPoint{ Hash: commitHash, @@ -2530,7 +2528,7 @@ func (c *ChannelArbitrator) prepContractResolutions( } resolver := newIncomingContestResolver( - resolution, height, htlc, chanType, + resolution, height, htlc, resolverCfg, ) if chanState != nil { @@ -2544,6 +2542,7 @@ func (c *ChannelArbitrator) prepContractResolutions( // backwards), or just timeout. case HtlcOutgoingWatchAction: for _, htlc := range htlcs { + htlc := htlc htlcOp := wire.OutPoint{ Hash: commitHash, @@ -2561,8 +2560,7 @@ func (c *ChannelArbitrator) prepContractResolutions( } resolver := newOutgoingContestResolver( - resolution, height, htlc, chanType, - resolverCfg, + resolution, height, htlc, resolverCfg, ) if chanState != nil { resolver.SupplementState(chanState) diff --git a/contractcourt/channel_arbitrator_test.go b/contractcourt/channel_arbitrator_test.go index 12e4619f3..8f695c531 100644 --- a/contractcourt/channel_arbitrator_test.go +++ b/contractcourt/channel_arbitrator_test.go @@ -10,14 +10,13 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainio" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" @@ -379,8 +378,7 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog, IncubateOutputs: func(wire.OutPoint, fn.Option[lnwallet.OutgoingHtlcResolution], fn.Option[lnwallet.IncomingHtlcResolution], - uint32, fn.Option[int32], - ...IncubateOption) error { + uint32, fn.Option[int32]) error { incubateChan <- struct{}{} return nil @@ -448,8 +446,8 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog, return nil }, - FetchHistoricalChannel: func() (*chanstate.OpenChannel, error) { - return &chanstate.OpenChannel{}, nil + FetchHistoricalChannel: func() (*channeldb.OpenChannel, error) { + return &channeldb.OpenChannel{}, nil }, FindOutgoingHTLCDeadline: func( htlc channeldb.HTLC) fn.Option[int32] { @@ -1679,6 +1677,7 @@ func TestChannelArbitratorCommitFailure(t *testing.T) { } for _, test := range testCases { + test := test log := &mockArbitratorLog{ state: StateDefault, @@ -1889,6 +1888,7 @@ func TestChannelArbitratorDanglingCommitForceClose(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase testName := fmt.Sprintf("testCase: htlcExpired=%v,"+ "remotePendingHTLC=%v,remotePendingCommitConf=%v", testCase.htlcExpired, testCase.remotePendingHTLC, @@ -2162,9 +2162,7 @@ func TestChannelArbitratorPendingExpiredHTLC(t *testing.T) { func TestRemoteCloseInitiator(t *testing.T) { // getCloseSummary returns a unilateral close summary for the channel // provided. - getCloseSummary := func( - channel *chanstate.OpenChannel) *RemoteUnilateralCloseInfo { - + getCloseSummary := func(channel *channeldb.OpenChannel) *RemoteUnilateralCloseInfo { return &RemoteUnilateralCloseInfo{ UnilateralCloseSummary: &lnwallet.UnilateralCloseSummary{ SpendDetail: &chainntnfs.SpendDetail{ @@ -2194,7 +2192,7 @@ func TestRemoteCloseInitiator(t *testing.T) { // is expected to be buffered, as is the default for test // channel arbitrators. notifyClose func(sub *ChainEventSubscription, - channel *chanstate.OpenChannel) + channel *channeldb.OpenChannel) // expectedStates is the set of states we expect the arbitrator // to progress through. @@ -2203,7 +2201,7 @@ func TestRemoteCloseInitiator(t *testing.T) { { name: "force close", notifyClose: func(sub *ChainEventSubscription, - channel *chanstate.OpenChannel) { + channel *channeldb.OpenChannel) { s := getCloseSummary(channel) sub.RemoteUnilateralClosure <- s @@ -2215,6 +2213,7 @@ func TestRemoteCloseInitiator(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -2479,6 +2478,7 @@ func TestFindCommitmentDeadlineAndValue(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { // Mock the method `FindOutgoingHTLCDeadline`. tc.mockFindOutgoingHTLCDeadline() @@ -3073,6 +3073,7 @@ func TestChannelArbitratorStartForceCloseFail(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/contractcourt/commit_sweep_resolver.go b/contractcourt/commit_sweep_resolver.go index fe625ec51..d8c8c3903 100644 --- a/contractcourt/commit_sweep_resolver.go +++ b/contractcourt/commit_sweep_resolver.go @@ -6,13 +6,12 @@ import ( "io" "sync" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" @@ -211,7 +210,7 @@ func (c *commitSweepResolver) Stop() { // state required for the proper resolution of a contract. // // NOTE: Part of the ContractResolver interface. -func (c *commitSweepResolver) SupplementState(state *chanstate.OpenChannel) { +func (c *commitSweepResolver) SupplementState(state *channeldb.OpenChannel) { if state.ChanType.HasLeaseExpiration() { c.leaseExpiry = state.ThawHeight } @@ -471,19 +470,11 @@ func (c *commitSweepResolver) decideWitnessType() (input.WitnessType, error) { // commitment tweak to discern which type of commitment this is. var witnessType input.WitnessType switch { - // The local delayed output for a final taproot channel. - case isLocalCommitTx && c.chanType.IsTaprootFinal(): - witnessType = input.TaprootLocalCommitSpendFinal - - // The local delayed output for a staging taproot channel. + // The local delayed output for a taproot channel. case isLocalCommitTx && c.chanType.IsTaproot(): witnessType = input.TaprootLocalCommitSpend - // The CSV 1 delayed output for a final taproot channel. - case !isLocalCommitTx && c.chanType.IsTaprootFinal(): - witnessType = input.TaprootRemoteCommitSpendFinal - - // The CSV 1 delayed output for a staging taproot channel. + // The CSV 1 delayed output for a taproot channel. case !isLocalCommitTx && c.chanType.IsTaproot(): witnessType = input.TaprootRemoteCommitSpend diff --git a/contractcourt/commit_sweep_resolver_test.go b/contractcourt/commit_sweep_resolver_test.go index ef82c3987..5c660e100 100644 --- a/contractcourt/commit_sweep_resolver_test.go +++ b/contractcourt/commit_sweep_resolver_test.go @@ -4,8 +4,8 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/graph/db/models" @@ -348,6 +348,7 @@ func TestCommitSweepResolverDelay(t *testing.T) { }} for _, tc := range testCases { + tc := tc ok := t.Run(tc.name, func(t *testing.T) { testCommitSweepResolverDelay(t, tc.sweepErr) }) diff --git a/contractcourt/config.go b/contractcourt/config.go index 67c4267b6..7331ceb91 100644 --- a/contractcourt/config.go +++ b/contractcourt/config.go @@ -3,7 +3,7 @@ package contractcourt import ( "fmt" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/contractcourt/config_test.go b/contractcourt/config_test.go index 86e0957d0..e7bc22a7a 100644 --- a/contractcourt/config_test.go +++ b/contractcourt/config_test.go @@ -3,7 +3,7 @@ package contractcourt import ( "testing" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/stretchr/testify/require" ) diff --git a/contractcourt/contract_resolver.go b/contractcourt/contract_resolver.go index 66926b041..d11bd2f59 100644 --- a/contractcourt/contract_resolver.go +++ b/contractcourt/contract_resolver.go @@ -7,10 +7,9 @@ import ( "io" "sync/atomic" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btclog/v2" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/sweep" ) @@ -60,7 +59,7 @@ type ContractResolver interface { // SupplementState allows the user of a ContractResolver to supplement // it with state required for the proper resolution of a contract. - SupplementState(*chanstate.OpenChannel) + SupplementState(*channeldb.OpenChannel) // IsResolved returns true if the stored state in the resolve is fully // resolved. In this case the target output can be forgotten. diff --git a/contractcourt/htlc_incoming_contest_resolver.go b/contractcourt/htlc_incoming_contest_resolver.go index c1a289a8b..d075166c1 100644 --- a/contractcourt/htlc_incoming_contest_resolver.go +++ b/contractcourt/htlc_incoming_contest_resolver.go @@ -7,8 +7,8 @@ import ( "fmt" "io" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/txscript/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/txscript" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" @@ -42,11 +42,10 @@ type htlcIncomingContestResolver struct { // newIncomingContestResolver instantiates a new incoming htlc contest resolver. func newIncomingContestResolver( res lnwallet.IncomingHtlcResolution, broadcastHeight uint32, - htlc channeldb.HTLC, chanType channeldb.ChannelType, - resCfg ResolverConfig) *htlcIncomingContestResolver { + htlc channeldb.HTLC, resCfg ResolverConfig) *htlcIncomingContestResolver { success := newSuccessResolver( - res, broadcastHeight, htlc, chanType, resCfg, + res, broadcastHeight, htlc, resCfg, ) return &htlcIncomingContestResolver{ @@ -84,7 +83,7 @@ func (h *htlcIncomingContestResolver) processFinalHtlcFail() error { func (h *htlcIncomingContestResolver) invalidFinalHtlc( payload *hop.Payload, height uint32) bool { - if !payload.FwdInfo.IsExit() { + if payload.FwdInfo.NextHop != hop.Exit { return false } @@ -312,7 +311,7 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) { hodlChan <-chan interface{} witnessUpdates <-chan lntypes.Preimage ) - if payload.FwdInfo.IsExit() { + if payload.FwdInfo.NextHop == hop.Exit { // Create a buffered hodl chan to prevent deadlock. hodlQueue := queue.NewConcurrentQueue(10) hodlQueue.Start() @@ -701,7 +700,7 @@ func (h *htlcIncomingContestResolver) findAndapplyPreimage() (bool, error) { // Exit early if this is not the exit hop, which means we are not the // payment receiver and don't have the preimage. - if !payload.FwdInfo.IsExit() { + if payload.FwdInfo.NextHop != hop.Exit { return false, nil } diff --git a/contractcourt/htlc_incoming_contest_resolver_test.go b/contractcourt/htlc_incoming_contest_resolver_test.go index 83a97802f..d8ec533da 100644 --- a/contractcourt/htlc_incoming_contest_resolver_test.go +++ b/contractcourt/htlc_incoming_contest_resolver_test.go @@ -5,7 +5,7 @@ import ( "io" "testing" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" diff --git a/contractcourt/htlc_lease_resolver.go b/contractcourt/htlc_lease_resolver.go index 4cda5407a..3002cec0b 100644 --- a/contractcourt/htlc_lease_resolver.go +++ b/contractcourt/htlc_lease_resolver.go @@ -1,9 +1,9 @@ package contractcourt import ( - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/tlv" @@ -76,7 +76,7 @@ func (h *htlcLeaseResolver) makeSweepInput(op *wire.OutPoint, // state required for the proper resolution of a contract. // // NOTE: Part of the ContractResolver interface. -func (h *htlcLeaseResolver) SupplementState(state *chanstate.OpenChannel) { +func (h *htlcLeaseResolver) SupplementState(state *channeldb.OpenChannel) { if state.ChanType.HasLeaseExpiration() { h.leaseExpiry = state.ThawHeight } diff --git a/contractcourt/htlc_outgoing_contest_resolver.go b/contractcourt/htlc_outgoing_contest_resolver.go index ea885b072..9e94587cc 100644 --- a/contractcourt/htlc_outgoing_contest_resolver.go +++ b/contractcourt/htlc_outgoing_contest_resolver.go @@ -3,7 +3,7 @@ package contractcourt import ( "io" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwallet" @@ -24,11 +24,10 @@ type htlcOutgoingContestResolver struct { // resolver. func newOutgoingContestResolver(res lnwallet.OutgoingHtlcResolution, broadcastHeight uint32, htlc channeldb.HTLC, - chanType channeldb.ChannelType, resCfg ResolverConfig) *htlcOutgoingContestResolver { timeout := newTimeoutResolver( - res, broadcastHeight, htlc, chanType, resCfg, + res, broadcastHeight, htlc, resCfg, ) return &htlcOutgoingContestResolver{ @@ -230,14 +229,10 @@ func (h *htlcOutgoingContestResolver) Encode(w io.Writer) error { return h.htlcTimeoutResolver.Encode(w) } -// SupplementDeadline forwards the incoming HTLC's expiry height to the inner -// timeout resolver. This resolver morphs into that timeout resolver once the -// outgoing HTLC expires on-chain, so the deadline is retained across the -// transition. +// SupplementDeadline does nothing for an incoming htlc resolver. // // NOTE: Part of the htlcContractResolver interface. -func (h *htlcOutgoingContestResolver) SupplementDeadline(d fn.Option[int32]) { - h.htlcTimeoutResolver.SupplementDeadline(d) +func (h *htlcOutgoingContestResolver) SupplementDeadline(_ fn.Option[int32]) { } // newOutgoingContestResolverFromReader attempts to decode an encoded ContractResolver diff --git a/contractcourt/htlc_outgoing_contest_resolver_test.go b/contractcourt/htlc_outgoing_contest_resolver_test.go index dbf5e10e4..625df60bf 100644 --- a/contractcourt/htlc_outgoing_contest_resolver_test.go +++ b/contractcourt/htlc_outgoing_contest_resolver_test.go @@ -4,10 +4,9 @@ import ( "fmt" "testing" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/kvdb" @@ -21,10 +20,6 @@ import ( const ( outgoingContestHtlcExpiry = 110 - - // outgoingContestIncomingHtlcExpiry is kept distinct from the outgoing - // HTLC expiry to verify that the supplied value is retained. - outgoingContestIncomingHtlcExpiry = 144 ) // TestHtlcOutgoingResolverTimeout tests resolution of an offered htlc that @@ -121,36 +116,6 @@ type resolveResult struct { nextResolver ContractResolver } -// TestHtlcOutgoingResolverSupplementDeadline checks that the outgoing contest -// resolver forwards the incoming HTLC deadline to the timeout resolver it -// transitions into once the outgoing HTLC expires on-chain. -func TestHtlcOutgoingResolverSupplementDeadline(t *testing.T) { - t.Parallel() - defer timeout()() - - ctx := newOutgoingResolverTestContext(t) - - // Initially the embedded timeout resolver carries no deadline. - require.True(t, ctx.resolver.incomingHTLCExpiryHeight.IsNone()) - - // Supply the deadline through the contest resolver, as the channel - // arbitrator does when constructing the resolver. - deadline := fn.Some(int32(outgoingContestIncomingHtlcExpiry)) - ctx.resolver.SupplementDeadline(deadline) - - // Drive the contest resolver to the point where it returns the embedded - // timeout resolver. - ctx.resolve() - ctx.notifyEpoch(outgoingContestHtlcExpiry) - - result := <-ctx.resolverResultChan - require.NoError(t, result.err) - - timeoutRes, ok := result.nextResolver.(*htlcTimeoutResolver) - require.True(t, ok, "expected htlcTimeoutResolver") - require.Equal(t, deadline, timeoutRes.incomingHTLCExpiryHeight) -} - type outgoingResolverTestContext struct { resolver *htlcOutgoingContestResolver notifier *mock.ChainNotifier diff --git a/contractcourt/htlc_success_resolver.go b/contractcourt/htlc_success_resolver.go index 467641280..a4d27ba4e 100644 --- a/contractcourt/htlc_success_resolver.go +++ b/contractcourt/htlc_success_resolver.go @@ -6,12 +6,11 @@ import ( "io" "sync" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/input" @@ -51,9 +50,6 @@ type htlcSuccessResolver struct { // htlc contains information on the htlc that we are resolving on-chain. htlc channeldb.HTLC - // chanType denotes the type of channel the HTLC belongs to. - chanType channeldb.ChannelType - // currentReport stores the current state of the resolver for reporting // over the rpc interface. This should only be reported in case we have // a non-nil SignDetails on the htlcResolution, otherwise the nursery @@ -71,7 +67,6 @@ type htlcSuccessResolver struct { // newSuccessResolver instanties a new htlc success resolver. func newSuccessResolver(res lnwallet.IncomingHtlcResolution, broadcastHeight uint32, htlc channeldb.HTLC, - chanType channeldb.ChannelType, resCfg ResolverConfig) *htlcSuccessResolver { h := &htlcSuccessResolver{ @@ -79,7 +74,6 @@ func newSuccessResolver(res lnwallet.IncomingHtlcResolution, htlcResolution: res, broadcastHeight: broadcastHeight, htlc: htlc, - chanType: chanType, } h.initReport() @@ -379,17 +373,6 @@ func (h *htlcSuccessResolver) HtlcPoint() wire.OutPoint { return h.htlcResolution.HtlcPoint() } -// SupplementState allows the user of a ContractResolver to supplement it with -// state required for the proper resolution of a contract. This restores the -// channel type which is needed to select the correct witness type for -// production taproot channels after restart. -// -// NOTE: Part of the ContractResolver interface. -func (h *htlcSuccessResolver) SupplementState(state *chanstate.OpenChannel) { - h.htlcLeaseResolver.SupplementState(state) - h.chanType = state.ChanType -} - // SupplementDeadline does nothing for an incoming htlc resolver. // // NOTE: Part of the htlcContractResolver interface. @@ -426,12 +409,6 @@ func (h *htlcSuccessResolver) isTaproot() bool { ) } -// isTaprootFinal returns true if the htlc output is from a final taproot -// channel. -func (h *htlcSuccessResolver) isTaprootFinal() bool { - return h.chanType.IsTaprootFinal() -} - // sweepRemoteCommitOutput creates a sweep request to sweep the HTLC output on // the remote commitment via the direct preimage-spend. func (h *htlcSuccessResolver) sweepRemoteCommitOutput() error { @@ -440,19 +417,7 @@ func (h *htlcSuccessResolver) sweepRemoteCommitOutput() error { // sweeping transaction, and generate a witness. var inp input.Input - switch { - case h.isTaprootFinal(): - inp = lnutils.Ptr(input.MakeTaprootHtlcSucceedInputFinal( - &h.htlcResolution.ClaimOutpoint, - &h.htlcResolution.SweepSignDesc, - h.htlcResolution.Preimage[:], - h.broadcastHeight, - h.htlcResolution.CsvDelay, - input.WithResolutionBlob( - h.htlcResolution.ResolutionBlob, - ), - )) - case h.isTaproot(): + if h.isTaproot() { inp = lnutils.Ptr(input.MakeTaprootHtlcSucceedInput( &h.htlcResolution.ClaimOutpoint, &h.htlcResolution.SweepSignDesc, @@ -463,7 +428,7 @@ func (h *htlcSuccessResolver) sweepRemoteCommitOutput() error { h.htlcResolution.ResolutionBlob, ), )) - default: + } else { inp = lnutils.Ptr(input.MakeHtlcSucceedInput( &h.htlcResolution.ClaimOutpoint, &h.htlcResolution.SweepSignDesc, @@ -597,12 +562,9 @@ func (h *htlcSuccessResolver) sweepSuccessTxOutput() error { // Let the sweeper sweep the second-level output now that the // CSV/CLTV locks have expired. var witType input.StandardWitnessType - switch { - case h.isTaprootFinal(): - witType = input.TaprootHtlcAcceptedSuccessSecondLevelFinal - case h.isTaproot(): + if h.isTaproot() { witType = input.TaprootHtlcAcceptedSuccessSecondLevel - default: + } else { witType = input.HtlcAcceptedSuccessSecondLevel } inp := h.makeSweepInput( @@ -672,7 +634,6 @@ func (h *htlcSuccessResolver) resolveLegacySuccessTx() error { h.ChanPoint, fn.None[lnwallet.OutgoingHtlcResolution](), fn.Some(h.htlcResolution), h.broadcastHeight, fn.Some(int32(h.htlc.RefundTimeout)), - WithChanType(h.chanType), ) if err != nil { return err diff --git a/contractcourt/htlc_success_resolver_test.go b/contractcourt/htlc_success_resolver_test.go index 61777bf41..fe6ee1ad0 100644 --- a/contractcourt/htlc_success_resolver_test.go +++ b/contractcourt/htlc_success_resolver_test.go @@ -7,9 +7,9 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" @@ -81,8 +81,7 @@ func newHtlcResolverTestContext(t *testing.T, IncubateOutputs: func(wire.OutPoint, fn.Option[lnwallet.OutgoingHtlcResolution], fn.Option[lnwallet.IncomingHtlcResolution], - uint32, fn.Option[int32], - ...IncubateOption) error { + uint32, fn.Option[int32]) error { return nil }, diff --git a/contractcourt/htlc_timeout_resolver.go b/contractcourt/htlc_timeout_resolver.go index e6d20bf09..6beafc399 100644 --- a/contractcourt/htlc_timeout_resolver.go +++ b/contractcourt/htlc_timeout_resolver.go @@ -6,13 +6,12 @@ import ( "io" "sync" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntypes" @@ -48,9 +47,6 @@ type htlcTimeoutResolver struct { // htlc contains information on the htlc that we are resolving on-chain. htlc channeldb.HTLC - // chanType denotes the type of channel the HTLC belongs to. - chanType channeldb.ChannelType - // currentReport stores the current state of the resolver for reporting // over the rpc interface. This should only be reported in case we have // a non-nil SignDetails on the htlcResolution, otherwise the nursery @@ -73,7 +69,6 @@ type htlcTimeoutResolver struct { // newTimeoutResolver instantiates a new timeout htlc resolver. func newTimeoutResolver(res lnwallet.OutgoingHtlcResolution, broadcastHeight uint32, htlc channeldb.HTLC, - chanType channeldb.ChannelType, resCfg ResolverConfig) *htlcTimeoutResolver { h := &htlcTimeoutResolver{ @@ -81,7 +76,6 @@ func newTimeoutResolver(res lnwallet.OutgoingHtlcResolution, htlcResolution: res, broadcastHeight: broadcastHeight, htlc: htlc, - chanType: chanType, } h.initReport() @@ -97,12 +91,6 @@ func (h *htlcTimeoutResolver) isTaproot() bool { ) } -// isTaprootFinal returns true if the htlc output is from a final taproot -// channel. -func (h *htlcTimeoutResolver) isTaprootFinal() bool { - return h.chanType.IsTaprootFinal() -} - // outpoint returns the outpoint of the HTLC output we're attempting to sweep. func (h *htlcTimeoutResolver) outpoint() wire.OutPoint { // The primary key for this resolver will be the outpoint of the HTLC @@ -512,7 +500,6 @@ func (h *htlcTimeoutResolver) resolveSecondLevelTxLegacy() error { h.ChanPoint, fn.Some(h.htlcResolution), fn.None[lnwallet.IncomingHtlcResolution](), h.broadcastHeight, h.incomingHTLCExpiryHeight, - WithChanType(h.chanType), ) if err != nil { return err @@ -527,12 +514,9 @@ func (h *htlcTimeoutResolver) resolveSecondLevelTxLegacy() error { // are resolved via this path. func (h *htlcTimeoutResolver) sweepDirectHtlcOutput() error { var htlcWitnessType input.StandardWitnessType - switch { - case h.isTaprootFinal(): - htlcWitnessType = input.TaprootHtlcOfferedRemoteTimeoutFinal - case h.isTaproot(): + if h.isTaproot() { htlcWitnessType = input.TaprootHtlcOfferedRemoteTimeout - default: + } else { htlcWitnessType = input.HtlcOfferedRemoteTimeout } @@ -770,17 +754,6 @@ func (h *htlcTimeoutResolver) HtlcPoint() wire.OutPoint { return h.htlcResolution.HtlcPoint() } -// SupplementState allows the user of a ContractResolver to supplement it with -// state required for the proper resolution of a contract. This restores the -// channel type which is needed to select the correct witness type for -// production taproot channels after restart. -// -// NOTE: Part of the ContractResolver interface. -func (h *htlcTimeoutResolver) SupplementState(state *chanstate.OpenChannel) { - h.htlcLeaseResolver.SupplementState(state) - h.chanType = state.ChanType -} - // SupplementDeadline sets the incomingHTLCExpiryHeight for this outgoing htlc // resolver. // @@ -1056,12 +1029,9 @@ func (h *htlcTimeoutResolver) sweepTimeoutTxOutput() error { } var witType input.StandardWitnessType - switch { - case h.isTaprootFinal(): - witType = input.TaprootHtlcOfferedTimeoutSecondLevelFinal - case h.isTaproot(): + if h.isTaproot() { witType = input.TaprootHtlcOfferedTimeoutSecondLevel - default: + } else { witType = input.HtlcOfferedTimeoutSecondLevel } diff --git a/contractcourt/htlc_timeout_resolver_test.go b/contractcourt/htlc_timeout_resolver_test.go index cf27cece1..017d3d388 100644 --- a/contractcourt/htlc_timeout_resolver_test.go +++ b/contractcourt/htlc_timeout_resolver_test.go @@ -8,10 +8,10 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" @@ -299,8 +299,7 @@ func testHtlcTimeoutResolver(t *testing.T, testCase htlcTimeoutTestCase) { IncubateOutputs: func(wire.OutPoint, fn.Option[lnwallet.OutgoingHtlcResolution], fn.Option[lnwallet.IncomingHtlcResolution], - uint32, fn.Option[int32], - ...IncubateOption) error { + uint32, fn.Option[int32]) error { incubateChan <- struct{}{} return nil @@ -1488,6 +1487,7 @@ func TestCheckSizeAndIndex(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() @@ -1557,6 +1557,7 @@ func TestIsPreimageSpend(t *testing.T) { } for _, tc := range testCases { + tc := tc // Run the test. t.Run(tc.name, func(t *testing.T) { diff --git a/contractcourt/interfaces.go b/contractcourt/interfaces.go index f89de2eaa..e5e55fcfa 100644 --- a/contractcourt/interfaces.go +++ b/contractcourt/interfaces.go @@ -4,7 +4,7 @@ import ( "context" "io" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/htlcswitch/hop" diff --git a/contractcourt/nursery_store.go b/contractcourt/nursery_store.go index 3b9218e1f..428b37f97 100644 --- a/contractcourt/nursery_store.go +++ b/contractcourt/nursery_store.go @@ -5,8 +5,8 @@ import ( "errors" "fmt" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/kvdb" diff --git a/contractcourt/nursery_store_test.go b/contractcourt/nursery_store_test.go index 11f793c29..ca537693c 100644 --- a/contractcourt/nursery_store_test.go +++ b/contractcourt/nursery_store_test.go @@ -4,8 +4,8 @@ import ( "reflect" "testing" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/stretchr/testify/require" ) diff --git a/contractcourt/taproot_briefcase.go b/contractcourt/taproot_briefcase.go index c197c94d2..4b703dd29 100644 --- a/contractcourt/taproot_briefcase.go +++ b/contractcourt/taproot_briefcase.go @@ -167,6 +167,7 @@ func (r *resolverCtrlBlocks) Encode(w io.Writer) error { } for id, ctrlBlock := range *r { + ctrlBlock := ctrlBlock if _, err := w.Write(id[:]); err != nil { return err @@ -485,6 +486,7 @@ func (h *htlcTapTweaks) Encode(w io.Writer) error { } for id, tweak := range *h { + tweak := tweak if _, err := w.Write(id[:]); err != nil { return err diff --git a/contractcourt/utils_test.go b/contractcourt/utils_test.go index aaf17189b..994bc57a8 100644 --- a/contractcourt/utils_test.go +++ b/contractcourt/utils_test.go @@ -10,23 +10,8 @@ import ( "time" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" ) -// testChannelStateDB extracts the ChannelStateDB from the test channel state. -func testChannelStateDB(t testing.TB, - state *chanstate.OpenChannel) *channeldb.ChannelStateDB { - - t.Helper() - - cdb, ok := state.Db.(*channeldb.ChannelStateDB) - if !ok { - t.Fatalf("expected ChannelStateDB, got %T", state.Db) - } - - return cdb -} - // timeout implements a test level timeout. func timeout() func() { done := make(chan struct{}) @@ -67,13 +52,11 @@ func copyFile(dest, src string) error { // copyChannelState copies the OpenChannel state by copying the database and // creating a new struct from it. The copied state is returned. -func copyChannelState(t *testing.T, state *chanstate.OpenChannel) ( - *chanstate.OpenChannel, error) { +func copyChannelState(t *testing.T, state *channeldb.OpenChannel) ( + *channeldb.OpenChannel, error) { // Make a copy of the DB. - dbFile := filepath.Join( - testChannelStateDB(t, state).GetParentDB().Path(), "channel.db", - ) + dbFile := filepath.Join(state.Db.GetParentDB().Path(), "channel.db") tempDbPath := t.TempDir() tempDbFile := filepath.Join(tempDbPath, "channel.db") diff --git a/contractcourt/utxonursery.go b/contractcourt/utxonursery.go index 924437e60..fa0f1868c 100644 --- a/contractcourt/utxonursery.go +++ b/contractcourt/utxonursery.go @@ -9,9 +9,9 @@ import ( "sync" "sync/atomic" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" @@ -399,26 +399,6 @@ func (u *UtxoNursery) Stop() error { return nil } -// IncubateConfig holds optional configuration for IncubateOutputs. -type IncubateConfig struct { - // chanType is the channel type, used to determine which witness type - // to select for taproot channels. - chanType fn.Option[channeldb.ChannelType] -} - -// IncubateOption is a functional option that can be used to modify the behavior -// of IncubateOutputs. -type IncubateOption func(*IncubateConfig) - -// WithChanType returns an IncubateOption that sets the channel type for the -// incubation request, enabling correct witness type selection for production -// taproot channels. -func WithChanType(ct channeldb.ChannelType) IncubateOption { - return func(cfg *IncubateConfig) { - cfg.chanType = fn.Some(ct) - } -} - // IncubateOutputs sends a request to the UtxoNursery to incubate a set of // outputs from an existing commitment transaction. Outputs need to incubate if // they're CLTV absolute time locked, or if they're CSV relative time locked. @@ -426,17 +406,7 @@ func WithChanType(ct channeldb.ChannelType) IncubateOption { func (u *UtxoNursery) IncubateOutputs(chanPoint wire.OutPoint, outgoingHtlc fn.Option[lnwallet.OutgoingHtlcResolution], incomingHtlc fn.Option[lnwallet.IncomingHtlcResolution], - broadcastHeight uint32, deadlineHeight fn.Option[int32], - opts ...IncubateOption) error { - - cfg := IncubateConfig{} - for _, o := range opts { - o(&cfg) - } - - // Determine if this is a production taproot channel based on the - // channel type passed via functional options. - isFinalTaproot := cfg.chanType.UnwrapOr(0).IsTaprootFinal() + broadcastHeight uint32, deadlineHeight fn.Option[int32]) error { // Add to wait group because nursery might shut down during execution of // this function. Otherwise it could happen that nursery thinks it is @@ -478,12 +448,9 @@ func (u *UtxoNursery) IncubateOutputs(chanPoint wire.OutPoint, ) var witType input.StandardWitnessType - switch { - case isFinalTaproot: - witType = input.TaprootHtlcAcceptedSuccessSecondLevelFinal //nolint:ll - case isTaproot: + if isTaproot { witType = input.TaprootHtlcAcceptedSuccessSecondLevel - default: + } else { witType = input.HtlcAcceptedSuccessSecondLevel } @@ -508,7 +475,6 @@ func (u *UtxoNursery) IncubateOutputs(chanPoint wire.OutPoint, if htlcRes.SignedTimeoutTx != nil { htlcOutput := makeBabyOutput( &chanPoint, &htlcRes, deadlineHeight, - isFinalTaproot, ) if htlcOutput.Amount() > 0 { @@ -526,14 +492,12 @@ func (u *UtxoNursery) IncubateOutputs(chanPoint wire.OutPoint, ) var witType input.StandardWitnessType - switch { - case isFinalTaproot: - witType = input.TaprootHtlcOfferedRemoteTimeoutFinal - case isTaproot: + if isTaproot { witType = input.TaprootHtlcOfferedRemoteTimeout - default: + } else { witType = input.HtlcOfferedRemoteTimeout } + // Otherwise, this is actually a kid output as we can sweep it // once the commitment transaction confirms, and the absolute // CLTV lock has expired. We set the CSV delay what the @@ -656,8 +620,6 @@ func (u *UtxoNursery) NurseryReport( switch kid.WitnessType() { //nolint:ll - case input.TaprootHtlcAcceptedSuccessSecondLevelFinal: - fallthrough case input.TaprootHtlcAcceptedSuccessSecondLevel: fallthrough case input.HtlcAcceptedSuccessSecondLevel: @@ -668,7 +630,6 @@ func (u *UtxoNursery) NurseryReport( report.AddLimboStage1SuccessHtlc(&kid) case input.HtlcOfferedRemoteTimeout, - input.TaprootHtlcOfferedRemoteTimeoutFinal, //nolint:ll input.TaprootHtlcOfferedRemoteTimeout: // This is an HTLC output on the // commitment transaction of the remote @@ -685,7 +646,6 @@ func (u *UtxoNursery) NurseryReport( switch kid.WitnessType() { case input.HtlcOfferedRemoteTimeout, - input.TaprootHtlcOfferedRemoteTimeoutFinal, //nolint:ll input.TaprootHtlcOfferedRemoteTimeout: // This is an HTLC output on the // commitment transaction of the remote @@ -699,10 +659,6 @@ func (u *UtxoNursery) NurseryReport( fallthrough case input.TaprootHtlcOfferedTimeoutSecondLevel: fallthrough - case input.TaprootHtlcAcceptedSuccessSecondLevelFinal: //nolint:ll - fallthrough - case input.TaprootHtlcOfferedTimeoutSecondLevelFinal: //nolint:ll - fallthrough case input.HtlcAcceptedSuccessSecondLevel: fallthrough case input.HtlcOfferedTimeoutSecondLevel: @@ -717,24 +673,17 @@ func (u *UtxoNursery) NurseryReport( // been swept back into the wallet. Each output // will contribute towards the recovered // balance. - // - //nolint:ll switch kid.WitnessType() { - case input.TaprootHtlcAcceptedSuccessSecondLevelFinal: - fallthrough + //nolint:ll case input.TaprootHtlcAcceptedSuccessSecondLevel: fallthrough - case input.TaprootHtlcOfferedTimeoutSecondLevelFinal: - fallthrough case input.TaprootHtlcOfferedTimeoutSecondLevel: fallthrough case input.HtlcAcceptedSuccessSecondLevel: fallthrough case input.HtlcOfferedTimeoutSecondLevel: fallthrough - case input.TaprootHtlcOfferedRemoteTimeoutFinal: - fallthrough case input.TaprootHtlcOfferedRemoteTimeout: fallthrough case input.HtlcOfferedRemoteTimeout: @@ -1432,8 +1381,7 @@ type babyOutput struct { // reaches the delay and claim stage. func makeBabyOutput(chanPoint *wire.OutPoint, htlcResolution *lnwallet.OutgoingHtlcResolution, - deadlineHeight fn.Option[int32], - isFinalTaproot bool) babyOutput { + deadlineHeight fn.Option[int32]) babyOutput { htlcOutpoint := htlcResolution.ClaimOutpoint blocksToMaturity := htlcResolution.CsvDelay @@ -1443,14 +1391,12 @@ func makeBabyOutput(chanPoint *wire.OutPoint, ) var witnessType input.StandardWitnessType - switch { - case isFinalTaproot: - witnessType = input.TaprootHtlcOfferedTimeoutSecondLevelFinal - case isTaproot: + if isTaproot { witnessType = input.TaprootHtlcOfferedTimeoutSecondLevel - default: + } else { witnessType = input.HtlcOfferedTimeoutSecondLevel } + kid := makeKidOutput( &htlcOutpoint, chanPoint, blocksToMaturity, witnessType, &htlcResolution.SweepSignDesc, 0, deadlineHeight, @@ -1542,12 +1488,9 @@ func makeKidOutput(outpoint, originChanPoint *wire.OutPoint, // This is an HTLC either if it's an incoming HTLC on our commitment // transaction, or is an outgoing HTLC on the commitment transaction of // the remote peer. - //nolint:ll isHtlc := (witnessType == input.HtlcAcceptedSuccessSecondLevel || witnessType == input.TaprootHtlcAcceptedSuccessSecondLevel || - witnessType == input.TaprootHtlcAcceptedSuccessSecondLevelFinal || witnessType == input.TaprootHtlcOfferedRemoteTimeout || - witnessType == input.TaprootHtlcOfferedRemoteTimeoutFinal || witnessType == input.HtlcOfferedRemoteTimeout) // heightHint can be safely set to zero here, because after this diff --git a/contractcourt/utxonursery_test.go b/contractcourt/utxonursery_test.go index a4b8b00d1..5dcf8c781 100644 --- a/contractcourt/utxonursery_test.go +++ b/contractcourt/utxonursery_test.go @@ -14,10 +14,10 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" @@ -687,6 +687,7 @@ func TestRejectedCribTransaction(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -1385,6 +1386,8 @@ func TestPatchZeroHeightHint(t *testing.T) { } for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { t.Parallel() @@ -1454,178 +1457,3 @@ func TestPatchZeroHeightHint(t *testing.T) { }) } } - -// TestMakeBabyOutputWitnessType verifies that makeBabyOutput selects the -// correct witness type based on the channel type (non-taproot, staging taproot, -// production taproot). -func TestMakeBabyOutputWitnessType(t *testing.T) { - t.Parallel() - - // A P2TR pkscript (OP_1 <32-byte-key>). - taprootPkScript := make([]byte, 34) - taprootPkScript[0] = txscript.OP_1 - taprootPkScript[1] = 32 - - // A non-taproot pkscript (P2WSH). - legacyPkScript := make([]byte, 34) - legacyPkScript[0] = txscript.OP_0 - legacyPkScript[1] = 32 - - chanPoint := wire.OutPoint{} - - tests := []struct { - name string - pkScript []byte - isFinalTaproot bool - expectedWitType input.StandardWitnessType - }{ - { - name: "non-taproot", - pkScript: legacyPkScript, - isFinalTaproot: false, - expectedWitType: input.HtlcOfferedTimeoutSecondLevel, - }, - { - name: "staging taproot", - pkScript: taprootPkScript, - isFinalTaproot: false, - expectedWitType: input.TaprootHtlcOfferedTimeoutSecondLevel, //nolint:ll - }, - { - name: "production taproot final", - pkScript: taprootPkScript, - isFinalTaproot: true, - expectedWitType: input.TaprootHtlcOfferedTimeoutSecondLevelFinal, //nolint:ll - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - htlcRes := &lnwallet.OutgoingHtlcResolution{ - Expiry: 500, - CsvDelay: 144, - SweepSignDesc: input.SignDescriptor{ - Output: &wire.TxOut{ - Value: 10000, - PkScript: tc.pkScript, - }, - }, - SignedTimeoutTx: &wire.MsgTx{ - TxIn: []*wire.TxIn{{ - Witness: [][]byte{{}}, - }}, - TxOut: []*wire.TxOut{{}}, - }, - } - - baby := makeBabyOutput( - &chanPoint, htlcRes, fn.None[int32](), - tc.isFinalTaproot, - ) - - require.Equal( - t, tc.expectedWitType, - baby.WitnessType(), - "wrong witness type for %s", tc.name, - ) - }) - } -} - -// TestIncubateConfigWitnessTypeSelection verifies that the IncubateConfig -// correctly determines isFinalTaproot based on the channel type passed via -// WithChanType, which drives witness type selection in IncubateOutputs. -func TestIncubateConfigWitnessTypeSelection(t *testing.T) { - t.Parallel() - - // A P2TR pkscript (OP_1 <32-byte-key>). - taprootPkScript := make([]byte, 34) - taprootPkScript[0] = txscript.OP_1 - taprootPkScript[1] = 32 - - // Non-taproot pkscript. - legacyPkScript := make([]byte, 34) - legacyPkScript[0] = txscript.OP_0 - legacyPkScript[1] = 32 - - tests := []struct { - name string - - // pkScript determines if the output looks like taproot. - pkScript []byte - - // chanType to pass via WithChanType. - chanType channeldb.ChannelType - - // Expected witness types for incoming and outgoing-remote. - expectedIncoming input.StandardWitnessType - expectedOutgoing input.StandardWitnessType - }{ - { - name: "non-taproot incoming+outgoing", - pkScript: legacyPkScript, - expectedIncoming: input.HtlcAcceptedSuccessSecondLevel, - expectedOutgoing: input.HtlcOfferedRemoteTimeout, - }, - { - name: "staging taproot incoming+outgoing", - pkScript: taprootPkScript, - expectedIncoming: input.TaprootHtlcAcceptedSuccessSecondLevel, //nolint:ll - expectedOutgoing: input.TaprootHtlcOfferedRemoteTimeout, - }, - { - name: "production taproot incoming+outgoing", - pkScript: taprootPkScript, - chanType: channeldb.SimpleTaprootFeatureBit | - channeldb.AnchorOutputsBit | - channeldb.SingleFunderTweaklessBit | - channeldb.TaprootFinalBit, - expectedIncoming: input.TaprootHtlcAcceptedSuccessSecondLevelFinal, //nolint:ll - expectedOutgoing: input.TaprootHtlcOfferedRemoteTimeoutFinal, //nolint:ll - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - // Build the IncubateConfig to check witness type - // selection logic. - cfg := IncubateConfig{} - if tc.chanType != 0 { - opt := WithChanType(tc.chanType) - opt(&cfg) - } - - isFinal := cfg.chanType.UnwrapOr( - 0, - ).IsTaprootFinal() - - // Verify incoming HTLC witness type. - isTaproot := txscript.IsPayToTaproot( - tc.pkScript, - ) - - var inWit input.StandardWitnessType - switch { - case isFinal: - inWit = input.TaprootHtlcAcceptedSuccessSecondLevelFinal //nolint:ll - case isTaproot: - inWit = input.TaprootHtlcAcceptedSuccessSecondLevel //nolint:ll - default: - inWit = input.HtlcAcceptedSuccessSecondLevel //nolint:ll - } - require.Equal(t, tc.expectedIncoming, inWit) - - // Verify outgoing remote HTLC witness type. - var outWit input.StandardWitnessType - switch { - case isFinal: - outWit = input.TaprootHtlcOfferedRemoteTimeoutFinal //nolint:ll - case isTaproot: - outWit = input.TaprootHtlcOfferedRemoteTimeout //nolint:ll - default: - outWit = input.HtlcOfferedRemoteTimeout - } - require.Equal(t, tc.expectedOutgoing, outWit) - }) - } -} diff --git a/dev.Dockerfile b/dev.Dockerfile index d8eaa6824..4d681d8de 100644 --- a/dev.Dockerfile +++ b/dev.Dockerfile @@ -1,6 +1,6 @@ # If you change this please also update GO_VERSION in Makefile (then run # `make lint` to see where else it needs to be updated as well). -FROM golang:1.26.4-alpine AS builder +FROM golang:1.25.5-alpine AS builder LABEL maintainer="Olaoluwa Osuntokun " @@ -18,13 +18,7 @@ RUN apk add --no-cache --update alpine-sdk \ COPY . /go/src/github.com/lightningnetwork/lnd # Install/build lnd. -# Note: When using `docker build`, setting the environmental variable -# `DOCKER_BUILDKIT=1` is required to enable -# [BuildKit](https://docs.docker.com/build/buildkit) -# so that the cache mounts can be used. -RUN --mount=type=cache,target=/go/pkg/mod \ - --mount=type=cache,target=/root/.cache/go-build \ - cd /go/src/github.com/lightningnetwork/lnd \ +RUN cd /go/src/github.com/lightningnetwork/lnd \ && make \ && make install-all tags="signrpc walletrpc chainrpc invoicesrpc peersrpc kvdb_sqlite" diff --git a/discovery/ban.go b/discovery/ban.go index 80f5f8f7e..5229c706c 100644 --- a/discovery/ban.go +++ b/discovery/ban.go @@ -1,7 +1,6 @@ package discovery import ( - "context" "errors" "math" "sync" @@ -10,7 +9,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/lightninglabs/neutrino/cache" "github.com/lightninglabs/neutrino/cache/lru" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwire" ) @@ -56,10 +55,10 @@ type ClosedChannelTracker interface { type GraphCloser interface { // PutClosedScid marks a channel as closed so that we won't validate // channel announcements for it again. - PutClosedScid(context.Context, lnwire.ShortChannelID) error + PutClosedScid(lnwire.ShortChannelID) error // IsClosedScid checks if a short channel id is closed. - IsClosedScid(context.Context, lnwire.ShortChannelID) (bool, error) + IsClosedScid(lnwire.ShortChannelID) (bool, error) } // NodeInfoInquirier handles queries relating to specific nodes and channels @@ -67,7 +66,7 @@ type GraphCloser interface { type NodeInfoInquirer interface { // FetchOpenChannels returns the set of channels that we have with the // peer identified by the passed-in public key. - FetchOpenChannels(*btcec.PublicKey) ([]*chanstate.OpenChannel, error) + FetchOpenChannels(*btcec.PublicKey) ([]*channeldb.OpenChannel, error) } // ScidCloserMan helps the gossiper handle closed channels that are in the @@ -89,18 +88,16 @@ func NewScidCloserMan(graph GraphCloser, // PutClosedScid marks scid as closed so the gossiper can ignore this channel // in the future. -func (s *ScidCloserMan) PutClosedScid(ctx context.Context, - scid lnwire.ShortChannelID) error { - - return s.graph.PutClosedScid(ctx, scid) +func (s *ScidCloserMan) PutClosedScid(scid lnwire.ShortChannelID) error { + return s.graph.PutClosedScid(scid) } // IsClosedScid checks whether scid is closed so that the gossiper can ignore // it. -func (s *ScidCloserMan) IsClosedScid(ctx context.Context, - scid lnwire.ShortChannelID) (bool, error) { +func (s *ScidCloserMan) IsClosedScid(scid lnwire.ShortChannelID) (bool, + error) { - return s.graph.IsClosedScid(ctx, scid) + return s.graph.IsClosedScid(scid) } // IsChannelPeer checks whether we have a channel with the peer. diff --git a/discovery/bootstrapper.go b/discovery/bootstrapper.go index be117d965..0ccec568e 100644 --- a/discovery/bootstrapper.go +++ b/discovery/bootstrapper.go @@ -13,8 +13,8 @@ import ( "strings" "time" - "github.com/btcsuite/btcd/address/v2/bech32" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil/bech32" "github.com/lightningnetwork/lnd/autopilot" "github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnwire" @@ -218,24 +218,12 @@ func (c *ChannelGraphBootstrapper) SampleNodeAddrs(_ context.Context, return nil } - foundAddr := false for _, nodeAddr := range node.Addrs() { // If we haven't yet reached our limit, then // we'll copy over the details of this node // into the set of addresses to be returned. - switch onion := nodeAddr.(type) { - case *net.TCPAddr: - case *tor.OnionAddr: - // Skip persisted Tor v2 .onion - // entries: Tor stopped serving them - // in 2021 and the dial would never - // succeed. Other addresses of the - // same node may still be usable. - if len(onion.OnionService) == - tor.V2Len { - - continue - } + switch nodeAddr.(type) { + case *net.TCPAddr, *tor.OnionAddr: default: // If this isn't a valid address // supported by the protocol, then we'll @@ -257,14 +245,9 @@ func (c *ChannelGraphBootstrapper) SampleNodeAddrs(_ context.Context, IdentityKey: nodePub, Address: nodeAddr, }) - foundAddr = true } - if foundAddr { - return errFound - } - - return nil + return errFound }, func() { clear(a) }) diff --git a/discovery/bootstrapper_test.go b/discovery/bootstrapper_test.go index 67769d1d8..54104bc51 100644 --- a/discovery/bootstrapper_test.go +++ b/discovery/bootstrapper_test.go @@ -1,63 +1,15 @@ package discovery import ( - "context" "fmt" "net" "testing" "time" - "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/autopilot" - "github.com/lightningnetwork/lnd/tor" "github.com/miekg/dns" "github.com/stretchr/testify/require" ) -// stubNode is a minimal autopilot.Node implementation used to drive -// ChannelGraphBootstrapper from a unit test. -type stubNode struct { - pub *btcec.PublicKey - addrs []net.Addr -} - -func (n *stubNode) PubKey() [33]byte { - var out [33]byte - copy(out[:], n.pub.SerializeCompressed()) - return out -} - -func (n *stubNode) Addrs() []net.Addr { return n.addrs } - -// stubChannelGraph yields a fixed list of nodes from ForEachNode and is -// otherwise unused by SampleNodeAddrs. -type stubChannelGraph struct { - nodes []autopilot.Node -} - -// ForEachNode invokes the callback for each node in the stub graph, -// stopping early if the callback returns a non-nil error. -func (s *stubChannelGraph) ForEachNode(ctx context.Context, - cb func(context.Context, autopilot.Node) error, _ func()) error { - - for _, n := range s.nodes { - if err := cb(ctx, n); err != nil { - return err - } - } - - return nil -} - -// ForEachNodesChannels is a no-op stub; SampleNodeAddrs does not exercise -// the channel iteration path. -func (s *stubChannelGraph) ForEachNodesChannels(_ context.Context, - _ func(context.Context, autopilot.NodeID, - []*autopilot.ChannelEdge) error, _ func()) error { - - return nil -} - // fallbackNet is a tor.Net stub used to drive fallBackSRVLookup. LookupHost // returns shimAddrs and Dial serves a single DNS response, written by // serveResp, over an in-memory pipe so the fallback path can be exercised @@ -112,97 +64,6 @@ func (n *fallbackNet) ResolveTCPAddr(_, _ string) (*net.TCPAddr, error) { return nil, fmt.Errorf("unsupported") } -// TestGraphBootstrapperSkipsV2Onion ensures SampleNodeAddrs strips Tor v2 -// .onion entries from the returned candidate set so the connection manager -// never attempts to dial an obsolete v2 hidden service surfaced through the -// channel graph. A node whose remaining addresses include a v3 .onion or -// plain TCP entry is still returned for those addresses, and a node whose -// only address is v2 contributes nothing. -func TestGraphBootstrapperSkipsV2Onion(t *testing.T) { - t.Parallel() - - v2 := &tor.OnionAddr{ - OnionService: "3g2upl4pq6kufc4m.onion", - Port: 9735, - } - v3 := &tor.OnionAddr{ - OnionService: "4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnz" + - "jpbi7utijcltosqemad.onion", - Port: 9735, - } - tcp := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 9735} - - mkPub := func(t *testing.T) *btcec.PublicKey { - t.Helper() - priv, err := btcec.NewPrivateKey() - require.NoError(t, err) - - return priv.PubKey() - } - - mixedPub := mkPub(t) - v2OnlyPub := mkPub(t) - - graph := &stubChannelGraph{ - nodes: []autopilot.Node{ - &stubNode{ - pub: mixedPub, - addrs: []net.Addr{v2, v3, tcp}, - }, - &stubNode{ - pub: v2OnlyPub, - addrs: []net.Addr{v2}, - }, - }, - } - - // Use deterministic sampling so the hash accumulator never skips a - // node — every eligible address must surface on each call. - bs, err := NewGraphBootstrapper(graph, true) - require.NoError(t, err) - - // Ask for more addresses than the graph holds so the bootstrapper - // drains it in a single pass. - got, err := bs.SampleNodeAddrs( - t.Context(), 10, map[autopilot.NodeID]struct{}{}, - ) - require.NoError(t, err) - - // Collect (pubkey, address) pairs we expect — the v2-only node must - // not contribute any NetAddress, and the mixed node contributes only - // v3 and tcp. - type seenAddr struct { - pub [33]byte - addr string - } - seen := make(map[seenAddr]struct{}, len(got)) - for _, na := range got { - var p [33]byte - copy(p[:], na.IdentityKey.SerializeCompressed()) - seen[seenAddr{pub: p, addr: na.Address.String()}] = struct{}{} - } - - var mixedKey, v2OnlyKey [33]byte - copy(mixedKey[:], mixedPub.SerializeCompressed()) - copy(v2OnlyKey[:], v2OnlyPub.SerializeCompressed()) - - // Mixed node yields v3 and tcp entries. - require.Contains(t, seen, seenAddr{ - pub: mixedKey, addr: v3.String(), - }) - require.Contains(t, seen, seenAddr{ - pub: mixedKey, addr: tcp.String(), - }) - - // No v2 entry surfaces, for either node. - for s := range seen { - require.NotEqual(t, v2.String(), s.addr, - "v2 onion %v leaked into candidate set", s.addr) - require.NotEqual(t, v2OnlyKey, s.pub, - "v2-only node %x should contribute nothing", s.pub) - } -} - // TestFallBackSRVLookupSkipsNonSRV ensures a DNS response whose Answer section // contains non-SRV records (which an on-path attacker or malicious seed can // inject, since the response is unauthenticated) is filtered rather than diff --git a/discovery/chan_series.go b/discovery/chan_series.go index 9617528b9..8ecb3a4c8 100644 --- a/discovery/chan_series.go +++ b/discovery/chan_series.go @@ -5,8 +5,7 @@ import ( "iter" "time" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/lightningnetwork/lnd/fn/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/netann" @@ -31,8 +30,8 @@ type ChannelGraphTimeSeries interface { // UpdatesInHorizon returns all known channel and node updates with an // update timestamp between the start time and end time. We'll use this // to catch up a remote node to the set of channel updates that they - // may have missed out on. - UpdatesInHorizon(ctx context.Context, startTime time.Time, + // may have missed out on within the target chain. + UpdatesInHorizon(chain chainhash.Hash, startTime time.Time, endTime time.Time) iter.Seq2[lnwire.Message, error] // FilterKnownChanIDs takes a target chain, and a set of channel ID's, @@ -41,7 +40,7 @@ type ChannelGraphTimeSeries interface { // passed superSet. FilterKnownChanIDs(chain chainhash.Hash, superSet []graphdb.ChannelUpdateInfo, - isZombieChan func(graphdb.ChannelUpdateInfo) bool) ( + isZombieChan func(time.Time, time.Time) bool) ( []lnwire.ShortChannelID, error) // FilterChannelRange returns the set of channels that we created @@ -74,12 +73,12 @@ type ChannelGraphTimeSeries interface { // in-protocol channel range queries to quickly and efficiently synchronize our // channel state with all peers. type ChanSeries struct { - graph *graphdb.VersionedGraph + graph *graphdb.ChannelGraph } // NewChanSeries constructs a new ChanSeries backed by a channeldb.ChannelGraph. // The returned ChanSeries implements the ChannelGraphTimeSeries interface. -func NewChanSeries(graph *graphdb.VersionedGraph) *ChanSeries { +func NewChanSeries(graph *graphdb.ChannelGraph) *ChanSeries { return &ChanSeries{ graph: graph, } @@ -105,20 +104,18 @@ func (c *ChanSeries) HighestChanID(ctx context.Context, // UpdatesInHorizon returns all known channel and node updates with an update // timestamp between the start time and end time. We'll use this to catch up a -// remote node to the set of channel updates that they may have missed out on. +// remote node to the set of channel updates that they may have missed out on +// within the target chain. // // NOTE: This is part of the ChannelGraphTimeSeries interface. -func (c *ChanSeries) UpdatesInHorizon(ctx context.Context, +func (c *ChanSeries) UpdatesInHorizon(chain chainhash.Hash, startTime, endTime time.Time) iter.Seq2[lnwire.Message, error] { return func(yield func(lnwire.Message, error) bool) { // First, we'll query for all the set of channels that have an // update that falls within the specified horizon. chansInHorizon := c.graph.ChanUpdatesInHorizon( - ctx, graphdb.ChanUpdateRange{ - StartTime: fn.Some(startTime), - EndTime: fn.Some(endTime), - }, + startTime, endTime, ) for channel, err := range chansInHorizon { @@ -136,7 +133,8 @@ func (c *ChanSeries) UpdatesInHorizon(ctx context.Context, //nolint:ll chanAnn, edge1, edge2, err := netann.CreateChanAnnouncement( - channel.Info, channel.Policy1, channel.Policy2, + channel.Info.AuthProof, channel.Info, + channel.Policy1, channel.Policy2, ) if err != nil { if !yield(nil, err) { @@ -184,11 +182,7 @@ func (c *ChanSeries) UpdatesInHorizon(ctx context.Context, // update within the horizon as well. We send these second to // ensure that they follow any active channels they have. nodeAnnsInHorizon := c.graph.NodeUpdatesInHorizon( - ctx, graphdb.NodeUpdateRange{ - StartTime: fn.Some(startTime), - EndTime: fn.Some(endTime), - }, - graphdb.WithIterPublicNodesOnly(), + startTime, endTime, graphdb.WithIterPublicNodesOnly(), ) for nodeAnn, err := range nodeAnnsInHorizon { if err != nil { @@ -218,12 +212,10 @@ func (c *ChanSeries) UpdatesInHorizon(ctx context.Context, // NOTE: This is part of the ChannelGraphTimeSeries interface. func (c *ChanSeries) FilterKnownChanIDs(_ chainhash.Hash, superSet []graphdb.ChannelUpdateInfo, - isZombieChan func(graphdb.ChannelUpdateInfo) bool) ( + isZombieChan func(time.Time, time.Time) bool) ( []lnwire.ShortChannelID, error) { - newChanIDs, err := c.graph.FilterKnownChanIDs( - context.TODO(), superSet, isZombieChan, - ) + newChanIDs, err := c.graph.FilterKnownChanIDs(superSet, isZombieChan) if err != nil { return nil, err } @@ -249,7 +241,7 @@ func (c *ChanSeries) FilterChannelRange(_ chainhash.Hash, startHeight, error) { return c.graph.FilterChannelRange( - context.TODO(), startHeight, endHeight, withTimestamps, + startHeight, endHeight, withTimestamps, ) } @@ -269,7 +261,7 @@ func (c *ChanSeries) FetchChanAnns(chain chainhash.Hash, chanIDs = append(chanIDs, chanID.ToUint64()) } - channels, err := c.graph.FetchChanInfos(context.TODO(), chanIDs) + channels, err := c.graph.FetchChanInfos(chanIDs) if err != nil { return nil, err } @@ -289,7 +281,8 @@ func (c *ChanSeries) FetchChanAnns(chain chainhash.Hash, } chanAnn, edge1, edge2, err := netann.CreateChanAnnouncement( - channel.Info, channel.Policy1, channel.Policy2, + channel.Info.AuthProof, channel.Info, channel.Policy1, + channel.Policy2, ) if err != nil { return nil, err @@ -302,7 +295,7 @@ func (c *ChanSeries) FetchChanAnns(chain chainhash.Hash, // If this edge has a validated node announcement, that // we haven't yet sent, then we'll send that as well. nodePub := channel.Node2.PubKeyBytes - hasNodeAnn := channel.Node2.HaveAnnouncement() + hasNodeAnn := channel.Node2.HaveNodeAnnouncement if _, ok := nodePubsSent[nodePub]; !ok && hasNodeAnn { nodeAnn, err := channel.Node2.NodeAnnouncement( true, @@ -328,7 +321,7 @@ func (c *ChanSeries) FetchChanAnns(chain chainhash.Hash, // If this edge has a validated node announcement, that // we haven't yet sent, then we'll send that as well. nodePub := channel.Node1.PubKeyBytes - hasNodeAnn := channel.Node1.HaveAnnouncement() + hasNodeAnn := channel.Node1.HaveNodeAnnouncement if _, ok := nodePubsSent[nodePub]; !ok && hasNodeAnn { nodeAnn, err := channel.Node1.NodeAnnouncement( true, @@ -362,7 +355,7 @@ func (c *ChanSeries) FetchChanUpdates(chain chainhash.Hash, shortChanID lnwire.ShortChannelID) ([]*lnwire.ChannelUpdate1, error) { chanInfo, e1, e2, err := c.graph.FetchChannelEdgesByID( - context.TODO(), shortChanID.ToUint64(), + shortChanID.ToUint64(), ) if err != nil { return nil, err diff --git a/discovery/gossip_result.go b/discovery/gossip_result.go deleted file mode 100644 index 0f661bdfe..000000000 --- a/discovery/gossip_result.go +++ /dev/null @@ -1,33 +0,0 @@ -package discovery - -import ( - "context" - - "github.com/lightningnetwork/lnd/actor" -) - -// completeGossipResult resolves a gossip processing promise with the provided -// error value. A nil error indicates successful processing. This function is -// safe to call multiple times; only the first call takes effect. -// -// NOTE: The error is wrapped via fn.Ok (the "success" side of Result), so -// AwaitGossipResult can distinguish gossip errors from context cancellation. -func completeGossipResult(p actor.Promise[error], err error) { - if p == nil { - return - } - - actor.CompleteWith(p, err) -} - -// AwaitGossipResult blocks until the gossip processing future resolves or the -// provided context is cancelled. It returns the gossip processing error on -// success, or a context cancellation error if the context expired first. -func AwaitGossipResult(ctx context.Context, f actor.Future[error]) error { - gossipErr, ctxErr := actor.AwaitFuture[error](ctx, f) - if ctxErr != nil { - return ctxErr - } - - return gossipErr -} diff --git a/discovery/gossip_result_test.go b/discovery/gossip_result_test.go deleted file mode 100644 index c184877f4..000000000 --- a/discovery/gossip_result_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package discovery - -import ( - "context" - "errors" - "testing" - - "github.com/lightningnetwork/lnd/actor" - "github.com/stretchr/testify/require" -) - -// TestAwaitGossipResultSuccess verifies that AwaitGossipResult returns nil -// when the future resolves with a nil error. -func TestAwaitGossipResultSuccess(t *testing.T) { - t.Parallel() - - promise := actor.NewPromise[error]() - actor.CompleteWith(promise, (error)(nil)) - - err := AwaitGossipResult(t.Context(), promise.Future()) - require.NoError(t, err) -} - -// TestAwaitGossipResultError verifies that AwaitGossipResult returns the -// underlying gossip processing error when the future resolves with one. -func TestAwaitGossipResultError(t *testing.T) { - t.Parallel() - - sentinel := errors.New("gossip validation failed") - promise := actor.NewPromise[error]() - actor.CompleteWith(promise, sentinel) - - err := AwaitGossipResult(t.Context(), promise.Future()) - require.ErrorIs(t, err, sentinel) -} - -// TestAwaitGossipResultContextCancelled verifies that AwaitGossipResult -// returns the context error when the context is cancelled before the future -// resolves. -func TestAwaitGossipResultContextCancelled(t *testing.T) { - t.Parallel() - - // A promise that is never completed simulates a gossiper that has - // shut down before producing a result. - promise := actor.NewPromise[error]() - - ctx, cancel := context.WithCancel(t.Context()) - cancel() // Cancel immediately. - - err := AwaitGossipResult(ctx, promise.Future()) - require.ErrorIs(t, err, context.Canceled) -} - -// TestCompleteGossipResultIdempotent verifies that completeGossipResult can be -// called multiple times without blocking. The second call must be a no-op. -func TestCompleteGossipResultIdempotent(t *testing.T) { - t.Parallel() - - sentinel := errors.New("processing error") - promise := actor.NewPromise[error]() - - // First completion sets the result. - completeGossipResult(promise, sentinel) - - // Second completion with a different value must be a no-op and must - // never block. - completeGossipResult(promise, nil) - - // The future should still contain the original sentinel error. - err := AwaitGossipResult(t.Context(), promise.Future()) - require.ErrorIs(t, err, sentinel) -} diff --git a/discovery/gossiper.go b/discovery/gossiper.go index 7f0874216..5400abf4c 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -12,21 +12,18 @@ import ( "sync/atomic" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/neutrino/cache" "github.com/lightninglabs/neutrino/cache/lru" - "github.com/lightningnetwork/lnd/actor" "github.com/lightningnetwork/lnd/batch" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph" graphdb "github.com/lightningnetwork/lnd/graph/db" @@ -85,10 +82,6 @@ var ( // is in the process of being shut down. ErrGossiperShuttingDown = errors.New("gossiper is shutting down") - // ErrPeerQuitting is returned when the peer that sent a gossip message - // quits before the message could be enqueued for processing. - ErrPeerQuitting = errors.New("peer quitting") - // ErrGossipSyncerNotFound signals that we were unable to find an active // gossip syncer corresponding to a gossip query message received from // the remote peer. @@ -184,7 +177,7 @@ type networkMsg struct { isRemote bool - errPromise actor.Promise[error] + err chan error } // chanPolicyUpdateRequest is a request that is sent to the server when a caller @@ -193,7 +186,7 @@ type networkMsg struct { // updates committed to the lower layer. type chanPolicyUpdateRequest struct { edgesToUpdate []EdgeWithInfo - errPromise actor.Promise[error] + errChan chan error } // PinnedSyncers is a set of node pubkeys for which we will maintain an active @@ -385,11 +378,12 @@ type Config struct { // FindChannel allows the gossiper to find a channel that we're party // to without iterating over the entire set of open channels. FindChannel func(node *btcec.PublicKey, chanID lnwire.ChannelID) ( - *chanstate.OpenChannel, error) + *channeldb.OpenChannel, error) - // IsStillZombieChannel returns true if the channel described by info - // should still be considered a zombie. - IsStillZombieChannel func(graphdb.ChannelUpdateInfo) bool + // IsStillZombieChannel takes the timestamps of the latest channel + // updates for a channel and returns true if the channel should be + // considered a zombie based on these timestamps. + IsStillZombieChannel func(time.Time, time.Time) bool // AssumeChannelValid toggles whether the gossiper will check for // spent-ness of channel outpoints. For neutrino, this saves long @@ -445,19 +439,15 @@ func (c *cachedNetworkMsg) Size() (uint64, error) { // rejectCacheKey is the cache key that we'll use to track announcements we've // recently rejected. type rejectCacheKey struct { - gossipVersion lnwire.GossipVersion - pubkey [33]byte - chanID uint64 + pubkey [33]byte + chanID uint64 } // newRejectCacheKey returns a new cache key for the reject cache. -func newRejectCacheKey(v lnwire.GossipVersion, cid uint64, - pub [33]byte) rejectCacheKey { - +func newRejectCacheKey(cid uint64, pub [33]byte) rejectCacheKey { k := rejectCacheKey{ - gossipVersion: v, - chanID: cid, - pubkey: pub, + chanID: cid, + pubkey: pub, } return k @@ -652,22 +642,19 @@ type EdgeWithInfo struct { func (d *AuthenticatedGossiper) PropagateChanPolicyUpdate( edgesToUpdate []EdgeWithInfo) error { - promise := actor.NewPromise[error]() + errChan := make(chan error, 1) policyUpdate := &chanPolicyUpdateRequest{ edgesToUpdate: edgesToUpdate, - errPromise: promise, + errChan: errChan, } select { case d.chanPolicyUpdates <- policyUpdate: + err := <-errChan + return err case <-d.quit: return fmt.Errorf("AuthenticatedGossiper shutting down") } - - ctx, cancel := lnutils.ContextFromQuit(d.quit) - defer cancel() - - return AwaitGossipResult(ctx, promise.Future()) } // Start spawns network messages handler goroutine and registers on new block @@ -838,10 +825,7 @@ func (d *AuthenticatedGossiper) resendFutureMessages(height uint32) { select { case d.networkMsgs <- msg: case <-d.quit: - completeGossipResult( - msg.errPromise, - ErrGossiperShuttingDown, - ) + msg.err <- ErrGossiperShuttingDown } } } @@ -890,9 +874,16 @@ func (d *AuthenticatedGossiper) stop() { // peers. Remote channel announcements should contain the announcement proof // and be fully validated. func (d *AuthenticatedGossiper) ProcessRemoteAnnouncement(ctx context.Context, - msg lnwire.Message, peer lnpeer.Peer) actor.Future[error] { + msg lnwire.Message, peer lnpeer.Peer) chan error { - promise := actor.NewPromise[error]() + // Buffer up to two messages on errChan since up to two messages may be + // written and not all callers of this function actually read from + // errChan. Without this buffer goroutines end up blocking on writes to + // errChan, which prevents the gossiper from shutting down cleanly. + // + // TODO(ziggie): Redesign this once the actor model pattern becomes + // available. See https://github.com/lightningnetwork/lnd/pull/9820. + errChan := make(chan error, 2) // For messages in the known set of channel series queries, we'll // dispatch the message directly to the GossipSyncer, and skip the main @@ -908,9 +899,8 @@ func (d *AuthenticatedGossiper) ProcessRemoteAnnouncement(ctx context.Context, log.Warnf("Gossip syncer for peer=%x not found", peer.PubKey()) - completeGossipResult(promise, ErrGossipSyncerNotFound) - - return promise.Future() + errChan <- ErrGossipSyncerNotFound + return errChan } // If we've found the message target, then we'll dispatch the @@ -921,9 +911,8 @@ func (d *AuthenticatedGossiper) ProcessRemoteAnnouncement(ctx context.Context, peer.PubKey(), err) } - completeGossipResult(promise, err) - - return promise.Future() + errChan <- err + return errChan // If a peer is updating its current update horizon, then we'll dispatch // that directly to the proper GossipSyncer. @@ -933,9 +922,8 @@ func (d *AuthenticatedGossiper) ProcessRemoteAnnouncement(ctx context.Context, log.Warnf("Gossip syncer for peer=%x not found", peer.PubKey()) - completeGossipResult(promise, ErrGossipSyncerNotFound) - - return promise.Future() + errChan <- ErrGossipSyncerNotFound + return errChan } // Queue the message for asynchronous processing to prevent @@ -947,14 +935,12 @@ func (d *AuthenticatedGossiper) ProcessRemoteAnnouncement(ctx context.Context, // Return nil to indicate we've handled the message, // even though it was dropped. This prevents the peer // from being disconnected. - completeGossipResult(promise, nil) - - return promise.Future() + errChan <- nil + return errChan } - completeGossipResult(promise, nil) - - return promise.Future() + errChan <- nil + return errChan // To avoid inserting edges in the graph for our own channels that we // have already closed, we ignore such channel announcements coming @@ -968,34 +954,35 @@ func (d *AuthenticatedGossiper) ProcessRemoteAnnouncement(ctx context.Context, bytes.Equal(m.NodeID2[:], ownKey) { log.Warn(ownErr) - completeGossipResult(promise, ownErr) - - return promise.Future() + errChan <- ownErr + return errChan } } nMsg := &networkMsg{ - msg: msg, - isRemote: true, - peer: peer, - source: peer.IdentityKey(), - errPromise: promise, + msg: msg, + isRemote: true, + peer: peer, + source: peer.IdentityKey(), + err: errChan, } select { case d.networkMsgs <- nMsg: - // If the peer that sent us this message is quitting, complete the - // promise so any awaiter does not block indefinitely. + // If the peer that sent us this error is quitting, then we don't need + // to send back an error and can return immediately. + // TODO(elle): the peer should now just rely on canceling the passed + // context. case <-peer.QuitSignal(): - completeGossipResult(promise, ErrPeerQuitting) + return nil case <-ctx.Done(): - completeGossipResult(promise, ctx.Err()) + return nil case <-d.quit: - completeGossipResult(promise, ErrGossiperShuttingDown) + nMsg.err <- ErrGossiperShuttingDown } - return promise.Future() + return nMsg.err } // ProcessLocalAnnouncement sends a new remote announcement message along with @@ -1006,7 +993,7 @@ func (d *AuthenticatedGossiper) ProcessRemoteAnnouncement(ctx context.Context, // entire channel announcement and update messages will be re-constructed and // broadcast to the rest of the network. func (d *AuthenticatedGossiper) ProcessLocalAnnouncement(msg lnwire.Message, - optionalFields ...OptionalMsgField) actor.Future[error] { + optionalFields ...OptionalMsgField) chan error { optionalMsgFields := &optionalMsgFields{} optionalMsgFields.apply(optionalFields...) @@ -1016,16 +1003,16 @@ func (d *AuthenticatedGossiper) ProcessLocalAnnouncement(msg lnwire.Message, optionalMsgFields: optionalMsgFields, isRemote: false, source: d.selfKey, - errPromise: actor.NewPromise[error](), + err: make(chan error, 1), } select { case d.networkMsgs <- nMsg: case <-d.quit: - completeGossipResult(nMsg.errPromise, ErrGossiperShuttingDown) + nMsg.err <- ErrGossiperShuttingDown } - return nMsg.errPromise.Future() + return nMsg.err } // channelUpdateID is a unique identifier for ChannelUpdate messages, as @@ -1459,6 +1446,7 @@ func (d *AuthenticatedGossiper) sendRemoteBatch(ctx context.Context, } for _, msgChunk := range annBatch { + msgChunk := msgChunk // With the syncers taken care of, we'll merge the sender map // with the set of syncers, so we don't send out duplicate @@ -1514,7 +1502,7 @@ func (d *AuthenticatedGossiper) networkHandler(ctx context.Context) { newChanUpdates, err := d.processChanPolicyUpdate( ctx, policyUpdate.edgesToUpdate, ) - completeGossipResult(policyUpdate.errPromise, err) + policyUpdate.errChan <- err if err != nil { log.Errorf("Unable to craft policy updates: %v", err) @@ -1573,10 +1561,8 @@ func (d *AuthenticatedGossiper) networkHandler(ctx context.Context) { sourceToPub(announcement.source), ) { - completeGossipResult( - announcement.errPromise, - fmt.Errorf("recently rejected"), - ) + announcement.err <- fmt.Errorf("recently " + + "rejected") continue } @@ -1587,10 +1573,7 @@ func (d *AuthenticatedGossiper) networkHandler(ctx context.Context) { announcement.msg, ) if err != nil { - completeGossipResult( - announcement.errPromise, err, - ) - + announcement.err <- err continue } @@ -1667,7 +1650,7 @@ func (d *AuthenticatedGossiper) handleNetworkMessages(ctx context.Context, log.Warnf("unexpected error during validation "+ "barrier shutdown: %v", err) } - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return } @@ -1689,7 +1672,7 @@ func (d *AuthenticatedGossiper) handleNetworkMessages(ctx context.Context, log.Errorf("SignalDependents returned error for msg=%v with "+ "JobID=%v", lnutils.SpewLogClosure(nMsg.msg), jobID) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return } @@ -1770,12 +1753,16 @@ func (d *AuthenticatedGossiper) finalizeGossipProcessing(logCtx context.Context, } // Send an error back to the caller if possible. - if nMsg != nil { - completeGossipResult( - nMsg.errPromise, - fmt.Errorf("panic while %s gossip message %s: %v", - ctxStr, msgType, r), - ) + if nMsg != nil && nMsg.err != nil { + select { + case nMsg.err <- fmt.Errorf("panic while %s gossip "+ + "message %s: %v", ctxStr, msgType, r): + default: + log.WarnS(logCtx, "Unable to send panic error, "+ + "error channel blocked", nil, + slog.String("msg_type", msgType), + ) + } } } @@ -1801,15 +1788,8 @@ func (d *AuthenticatedGossiper) PruneSyncState(peer route.Vertex) { func (d *AuthenticatedGossiper) isRecentlyRejectedMsg(msg lnwire.Message, peerPub [33]byte) bool { - // We only cache rejections for gossip messages. So if it is not - // a gossip message, we return false. - gMsg, ok := msg.(lnwire.GossipMessage) - if !ok { - return false - } - var scid uint64 - switch m := gMsg.(type) { + switch m := msg.(type) { case *lnwire.ChannelUpdate1: scid = m.ShortChannelID.ToUint64() @@ -1820,11 +1800,8 @@ func (d *AuthenticatedGossiper) isRecentlyRejectedMsg(msg lnwire.Message, return false } - _, err := d.recentRejects.Get(newRejectCacheKey( - gMsg.GossipVersion(), scid, peerPub, - )) - - return !errors.Is(err, cache.ErrElementNotFound) + _, err := d.recentRejects.Get(newRejectCacheKey(scid, peerPub)) + return err != cache.ErrElementNotFound } // retransmitStaleAnns examines all outgoing channels that the source node is @@ -2105,14 +2082,10 @@ func (d *AuthenticatedGossiper) processRejectedEdge(_ context.Context, return nil, nil } - // Attach the proof to the channel info before creating the - // announcement. - chanInfo.AuthProof = proof - // We'll then create then validate the new fully assembled // announcement. chanAnn, e1Ann, e2Ann, err := netann.CreateChanAnnouncement( - chanInfo, e1, e2, + proof, chanInfo, e1, e2, ) if err != nil { return nil, err @@ -2163,7 +2136,7 @@ func (d *AuthenticatedGossiper) processRejectedEdge(_ context.Context, // fetchPKScript fetches the output script for the given SCID. func (d *AuthenticatedGossiper) fetchPKScript(chanID lnwire.ShortChannelID) ( - txscript.ScriptClass, address.Address, error) { + txscript.ScriptClass, btcutil.Address, error) { pkScript, err := lnwallet.FetchPKScriptWithQuit( d.cfg.ChainIO, chanID, d.quit, @@ -2229,16 +2202,15 @@ func (d *AuthenticatedGossiper) isPremature(chanID lnwire.ShortChannelID, // Add the premature message to our future messages which will be // resent once the block height has reached. // - // Copy the networkMsg and allocate a fresh promise for the copy. - // The original message's errPromise is resolved by the caller with nil - // to indicate the message was accepted for deferred processing. + // Copy the networkMsgs since the old message's err chan will be + // consumed. copied := &networkMsg{ peer: msg.peer, source: msg.source, msg: msg.msg, optionalMsgFields: msg.optionalMsgFields, isRemote: msg.isRemote, - errPromise: actor.NewPromise[error](), + err: make(chan error, 1), } // Create the cached message. @@ -2306,7 +2278,7 @@ func (d *AuthenticatedGossiper) processNetworkAnnouncement(ctx context.Context, default: err := errors.New("wrong type of the announcement") - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } } @@ -2350,7 +2322,7 @@ func (d *AuthenticatedGossiper) processZombieUpdate(_ context.Context, // With the signature valid, we'll proceed to mark the // edge as live and wait for the channel announcement to // come through again. - err = d.cfg.Graph.MarkEdgeLive(lnwire.GossipVersion1, scid) + err = d.cfg.Graph.MarkEdgeLive(scid) switch { case errors.Is(err, graphdb.ErrZombieEdgeNotFound): log.Errorf("edge with chan_id=%v was not found in the "+ @@ -2506,13 +2478,44 @@ func (d *AuthenticatedGossiper) updateChannel(ctx context.Context, // have a full channel announcement for this channel. var chanAnn *lnwire.ChannelAnnouncement1 if info.AuthProof != nil { - chanAnn, err = info.ToChannelAnnouncement() + chanID := lnwire.NewShortChanIDFromInt(info.ChannelID) + chanAnn = &lnwire.ChannelAnnouncement1{ + ShortChannelID: chanID, + NodeID1: info.NodeKey1Bytes, + NodeID2: info.NodeKey2Bytes, + ChainHash: info.ChainHash, + BitcoinKey1: info.BitcoinKey1Bytes, + Features: lnwire.NewRawFeatureVector(), + BitcoinKey2: info.BitcoinKey2Bytes, + ExtraOpaqueData: info.ExtraOpaqueData, + } + chanAnn.NodeSig1, err = lnwire.NewSigFromECDSARawSignature( + info.AuthProof.NodeSig1Bytes, + ) + if err != nil { + return nil, nil, err + } + chanAnn.NodeSig2, err = lnwire.NewSigFromECDSARawSignature( + info.AuthProof.NodeSig2Bytes, + ) + if err != nil { + return nil, nil, err + } + chanAnn.BitcoinSig1, err = lnwire.NewSigFromECDSARawSignature( + info.AuthProof.BitcoinSig1Bytes, + ) + if err != nil { + return nil, nil, err + } + chanAnn.BitcoinSig2, err = lnwire.NewSigFromECDSARawSignature( + info.AuthProof.BitcoinSig2Bytes, + ) if err != nil { return nil, nil, err } } - return chanAnn, chanUpdate, nil + return chanAnn, chanUpdate, err } // SyncManager returns the gossiper's SyncManager instance. @@ -2596,7 +2599,7 @@ func (d *AuthenticatedGossiper) handleNodeAnnouncement(ctx context.Context, log.Warnf("Rejecting node announcement from peer=%v: %v", nMsg.peer, err) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -2605,7 +2608,7 @@ func (d *AuthenticatedGossiper) handleNodeAnnouncement(ctx context.Context, // this node so we can skip validating signatures if not required. if d.cfg.Graph.IsStaleNode(ctx, nodeAnn.NodeID, timestamp) { log.Debugf("Skipped processing stale node: %x", nodeAnn.NodeID) - completeGossipResult(nMsg.errPromise, nil) + nMsg.err <- nil return nil, true } @@ -2622,7 +2625,7 @@ func (d *AuthenticatedGossiper) handleNodeAnnouncement(ctx context.Context, log.Error(err) } - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -2633,7 +2636,7 @@ func (d *AuthenticatedGossiper) handleNodeAnnouncement(ctx context.Context, if err != nil { log.Errorf("Unable to determine if node %x is advertised: %v", nodeAnn.NodeID, err) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -2653,7 +2656,7 @@ func (d *AuthenticatedGossiper) handleNodeAnnouncement(ctx context.Context, "due to being unadvertised", nodeAnn.NodeID) } - completeGossipResult(nMsg.errPromise, nil) + nMsg.err <- nil // TODO(roasbeef): get rid of the above log.Debugf("Processed NodeAnnouncement1: peer=%v, timestamp=%v, "+ @@ -2684,13 +2687,12 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, log.Errorf(err.Error()) key := newRejectCacheKey( - ann.GossipVersion(), scid.ToUint64(), sourceToPub(nMsg.source), ) _, _ = d.recentRejects.Put(key, &cachedReject{}) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -2702,13 +2704,12 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, log.Errorf(err.Error()) key := newRejectCacheKey( - ann.GossipVersion(), scid.ToUint64(), sourceToPub(nMsg.source), ) _, _ = d.recentRejects.Put(key, &cachedReject{}) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -2720,7 +2721,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, "advertises height %v, only height %v is known", scid.ToUint64(), scid.BlockHeight, d.bestHeight) d.Unlock() - completeGossipResult(nMsg.errPromise, nil) + nMsg.err <- nil return nil, false } d.Unlock() @@ -2728,17 +2729,17 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, // At this point, we'll now ask the router if this is a zombie/known // edge. If so we can skip all the processing below. if d.cfg.Graph.IsKnownEdge(scid) { - completeGossipResult(nMsg.errPromise, nil) + nMsg.err <- nil return nil, true } // Check if the channel is already closed in which case we can ignore // it. - closed, err := d.cfg.ScidCloser.IsClosedScid(ctx, scid) + closed, err := d.cfg.ScidCloser.IsClosedScid(scid) if err != nil { log.Errorf("failed to check if scid %v is closed: %v", scid, err) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -2748,7 +2749,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, // If this is an announcement from us, we'll just ignore it. if !nMsg.isRemote { - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -2762,7 +2763,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, err = dcErr } - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -2777,51 +2778,41 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, "%v", err) key := newRejectCacheKey( - ann.GossipVersion(), scid.ToUint64(), sourceToPub(nMsg.source), ) _, _ = d.recentRejects.Put(key, &cachedReject{}) log.Error(err) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } // If the proof checks out, then we'll save the proof itself to // the database so we can fetch it later when gossiping with // other nodes. - proof = models.NewV1ChannelAuthProof( - ann.NodeSig1.ToSignatureBytes(), - ann.NodeSig2.ToSignatureBytes(), - ann.BitcoinSig1.ToSignatureBytes(), - ann.BitcoinSig2.ToSignatureBytes(), - ) + proof = &models.ChannelAuthProof{ + NodeSig1Bytes: ann.NodeSig1.ToSignatureBytes(), + NodeSig2Bytes: ann.NodeSig2.ToSignatureBytes(), + BitcoinSig1Bytes: ann.BitcoinSig1.ToSignatureBytes(), + BitcoinSig2Bytes: ann.BitcoinSig2.ToSignatureBytes(), + } } // With the proof validated (if necessary), we can now store it within // the database for our path finding and syncing needs. - edge, err := models.NewV1Channel( - scid.ToUint64(), ann.ChainHash, ann.NodeID1, ann.NodeID2, - &models.ChannelV1Fields{ - BitcoinKey1Bytes: ann.BitcoinKey1, - BitcoinKey2Bytes: ann.BitcoinKey2, - ExtraOpaqueData: ann.ExtraOpaqueData, - }, - models.WithChanProof(proof), models.WithFeatures(ann.Features), - ) - if err != nil { - key := newRejectCacheKey( - ann.GossipVersion(), - scid.ToUint64(), - sourceToPub(nMsg.source), - ) - _, _ = d.recentRejects.Put(key, &cachedReject{}) - - log.Errorf("unable to create channel edge: %v", err) - completeGossipResult(nMsg.errPromise, err) - - return nil, false + edge := &models.ChannelEdgeInfo{ + ChannelID: scid.ToUint64(), + ChainHash: ann.ChainHash, + NodeKey1Bytes: ann.NodeID1, + NodeKey2Bytes: ann.NodeID2, + BitcoinKey1Bytes: ann.BitcoinKey1, + BitcoinKey2Bytes: ann.BitcoinKey2, + AuthProof: proof, + Features: lnwire.NewFeatureVector( + ann.Features, lnwire.Features, + ), + ExtraOpaqueData: ann.ExtraOpaqueData, } // If there were any optional message fields provided, we'll include @@ -2866,7 +2857,6 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, errors.Is(err, ErrInvalidFundingOutput): key := newRejectCacheKey( - ann.GossipVersion(), scid.ToUint64(), sourceToPub(nMsg.source), ) @@ -2876,7 +2866,6 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, case errors.Is(err, ErrChannelSpent): key := newRejectCacheKey( - ann.GossipVersion(), scid.ToUint64(), sourceToPub(nMsg.source), ) @@ -2888,16 +2877,12 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, // expensive validation checks on it again. // TODO: Populate the ScidCloser by using closed // channel notifications. - dbErr := d.cfg.ScidCloser.PutClosedScid( - ctx, scid, - ) + dbErr := d.cfg.ScidCloser.PutClosedScid(scid) if dbErr != nil { log.Errorf("failed to mark scid(%v) "+ "as closed: %v", scid, dbErr) - completeGossipResult( - nMsg.errPromise, dbErr, - ) + nMsg.err <- dbErr return nil, false } @@ -2907,13 +2892,12 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, // edge. We won't increase the ban score for the // remote peer. key := newRejectCacheKey( - ann.GossipVersion(), scid.ToUint64(), sourceToPub(nMsg.source), ) _, _ = d.recentRejects.Put(key, &cachedReject{}) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -2921,7 +2905,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, if !nMsg.isRemote { log.Errorf("failed to add edge for local "+ "channel: %v", err) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -2937,7 +2921,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, err = dcErr } - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -2971,14 +2955,13 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, anns, rErr := d.processRejectedEdge(ctx, ann, proof) if rErr != nil { key := newRejectCacheKey( - ann.GossipVersion(), scid.ToUint64(), sourceToPub(nMsg.source), ) cr := &cachedReject{} _, _ = d.recentRejects.Put(key, cr) - completeGossipResult(nMsg.errPromise, rErr) + nMsg.err <- rErr return nil, false } @@ -2992,14 +2975,13 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, // // NOTE: since this is an ErrIgnored, we can return // true here to signal "allow" to its dependants. - completeGossipResult(nMsg.errPromise, nil) + nMsg.err <- nil return anns, true } // Otherwise, this is just a regular rejected edge. key := newRejectCacheKey( - ann.GossipVersion(), scid.ToUint64(), sourceToPub(nMsg.source), ) @@ -3008,7 +2990,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, if !nMsg.isRemote { log.Errorf("failed to add edge for local channel: %v", err) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -3017,7 +2999,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, if dcErr != nil { log.Errorf("failed to check if we should disconnect "+ "peer: %v", dcErr) - completeGossipResult(nMsg.errPromise, dcErr) + nMsg.err <- dcErr return nil, false } @@ -3026,7 +3008,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, nMsg.peer.Disconnect(ErrPeerBanned) } - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -3078,10 +3060,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, select { case d.networkMsgs <- updMsg: case <-d.quit: - completeGossipResult( - updMsg.errPromise, - ErrGossiperShuttingDown, - ) + updMsg.err <- ErrGossiperShuttingDown } // We don't expect any other message type than @@ -3107,7 +3086,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, }) } - completeGossipResult(nMsg.errPromise, nil) + nMsg.err <- nil log.Debugf("Processed ChannelAnnouncement1: peer=%v, short_chan_id=%v", nMsg.peer, scid.ToUint64()) @@ -3135,13 +3114,12 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context, log.Errorf(err.Error()) key := newRejectCacheKey( - upd.GossipVersion(), upd.ShortChannelID.ToUint64(), sourceToPub(nMsg.source), ) _, _ = d.recentRejects.Put(key, &cachedReject{}) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -3161,7 +3139,7 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context, "premature: advertises height %v, only height %v is "+ "known", shortChanID, blockHeight, d.bestHeight) d.Unlock() - completeGossipResult(nMsg.errPromise, nil) + nMsg.err <- nil return nil, false } d.Unlock() @@ -3187,7 +3165,7 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context, } } - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -3219,7 +3197,7 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context, nMsg.peer, nMsg.msg.MsgType(), nMsg.isRemote, ) - completeGossipResult(nMsg.errPromise, nil) + nMsg.err <- nil return nil, true } @@ -3231,7 +3209,7 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context, // If this is a channel_update from us, we'll just ignore it. if !nMsg.isRemote { - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -3247,7 +3225,7 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context, err = dcErr } - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -3265,7 +3243,7 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context, err = d.processZombieUpdate(ctx, chanInfo, graphScid, upd) if err != nil { log.Debug(err) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -3334,10 +3312,9 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context, err := fmt.Errorf("unable to validate channel update "+ "short_chan_id=%v: %v", shortChanID, err) log.Error(err) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err key := newRejectCacheKey( - upd.GossipVersion(), upd.ShortChannelID.ToUint64(), sourceToPub(nMsg.source), ) @@ -3377,7 +3354,7 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context, lnutils.SpewLogClosure(upd.ShortChannelID), err) log.Error(rErr) - completeGossipResult(nMsg.errPromise, rErr) + nMsg.err <- rErr return nil, false } @@ -3394,7 +3371,7 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context, log.Debugf("Ignoring keep alive update not "+ "within %v period for channel %v", d.cfg.RebroadcastInterval, shortChanID) - completeGossipResult(nMsg.errPromise, nil) + nMsg.err <- nil return nil, false } } else { @@ -3427,7 +3404,7 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context, log.Debugf("Rate limiting update for channel "+ "%v from direction %x", shortChanID, pubKey.SerializeCompressed()) - completeGossipResult(nMsg.errPromise, nil) + nMsg.err <- nil return nil, false } } @@ -3440,12 +3417,19 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context, // different alias. This might mean that SigBytes is incorrect as it // signs a different SCID than the database SCID, but since there will // only be a difference if AuthProof == nil, this is fine. - update, err := models.ChanEdgePolicyFromWire( - chanInfo.ChannelID, upd, - ) - if err != nil { - completeGossipResult(nMsg.errPromise, err) - return nil, false + update := &models.ChannelEdgePolicy{ + SigBytes: upd.Signature.ToSignatureBytes(), + ChannelID: chanInfo.ChannelID, + LastUpdate: timestamp, + MessageFlags: upd.MessageFlags, + ChannelFlags: upd.ChannelFlags, + TimeLockDelta: upd.TimeLockDelta, + MinHTLC: upd.HtlcMinimumMsat, + MaxHTLC: upd.HtlcMaximumMsat, + FeeBaseMSat: lnwire.MilliSatoshi(upd.BaseFee), + FeeProportionalMillionths: lnwire.MilliSatoshi(upd.FeeRate), + InboundFee: upd.InboundFee.ValOpt(), + ExtraOpaqueData: upd.ExtraOpaqueData, } if err := d.cfg.Graph.UpdateEdge(ctx, update, ops...); err != nil { @@ -3460,7 +3444,6 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context, // Since we know the stored SCID in the graph, we'll // cache that SCID. key := newRejectCacheKey( - upd.GossipVersion(), chanInfo.ChannelID, sourceToPub(nMsg.source), ) @@ -3470,7 +3453,7 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context, shortChanID, err) } - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -3494,20 +3477,14 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context, sig, err := d.cfg.SignAliasUpdate(upd) if err != nil { log.Error(err) - completeGossipResult( - nMsg.errPromise, err, - ) - + nMsg.err <- err return nil, false } lnSig, err := lnwire.NewSigFromSignature(sig) if err != nil { log.Error(err) - completeGossipResult( - nMsg.errPromise, err, - ) - + nMsg.err <- err return nil, false } @@ -3531,7 +3508,7 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context, err := fmt.Errorf("unable to reliably send %v for "+ "channel=%v to peer=%x: %v", upd.MsgType(), upd.ShortChannelID, remotePubKey, err) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } } @@ -3551,7 +3528,7 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context, }) } - completeGossipResult(nMsg.errPromise, nil) + nMsg.err <- nil log.Debugf("Processed ChannelUpdate: peer=%v, short_chan_id=%v, "+ "timestamp=%v", nMsg.peer, upd.ShortChannelID.ToUint64(), @@ -3590,7 +3567,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context, "lower than needed: %v < %v", d.bestHeight, needBlockHeight) d.Unlock() - completeGossipResult(nMsg.errPromise, nil) + nMsg.err <- nil return nil, false } d.Unlock() @@ -3613,7 +3590,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context, err := fmt.Errorf("unable to store the proof for "+ "short_chan_id=%v: %v", shortChanID, err) log.Error(err) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -3624,13 +3601,13 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context, err := fmt.Errorf("unable to store the proof for "+ "short_chan_id=%v: %v", shortChanID, err) log.Error(err) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } log.Infof("Orphan %v proof announcement with short_chan_id=%v"+ ", adding to waiting batch", prefix, shortChanID) - completeGossipResult(nMsg.errPromise, nil) + nMsg.err <- nil return nil, false } @@ -3645,7 +3622,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context, "to the peer which sent the proof, short_chan_id=%v", shortChanID) log.Error(err) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -3668,7 +3645,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context, err := fmt.Errorf("unable to reliably send %v for "+ "channel=%v to peer=%x: %v", ann.MsgType(), ann.ShortChannelID, remotePubKey, err) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } } @@ -3693,7 +3670,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context, ann.ChannelID, peerID) ca, _, _, err := netann.CreateChanAnnouncement( - chanInfo, e1, e2, + chanInfo.AuthProof, chanInfo, e1, e2, ) if err != nil { log.Errorf("unable to gen ann: %v", @@ -3715,7 +3692,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context, log.Debugf("Already have proof for channel with chanID=%v", ann.ChannelID) - completeGossipResult(nMsg.errPromise, nil) + nMsg.err <- nil return nil, true } @@ -3730,7 +3707,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context, err := fmt.Errorf("unable to get the opposite proof for "+ "short_chan_id=%v: %v", shortChanID, err) log.Error(err) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -3740,7 +3717,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context, err := fmt.Errorf("unable to store the proof for "+ "short_chan_id=%v: %v", shortChanID, err) log.Error(err) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -3748,54 +3725,32 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context, "short_chan_id=%v, waiting for other half", shortChanID) - completeGossipResult(nMsg.errPromise, nil) + nMsg.err <- nil return nil, false } // We now have both halves of the channel announcement proof, then // we'll reconstruct the initial announcement so we can validate it // shortly below. - // - // NOTE: For now only V1 proofs are supported in the gossiper. V2 - // support will be added when taproot channel announcements are wired - // up. - oppV1, ok := oppProof.WaitingProofInner.(*channeldb.V1WaitingProof) - if !ok { - err := fmt.Errorf("expected V1 waiting proof, got %T", - oppProof.WaitingProofInner) - log.Error(err) - completeGossipResult(nMsg.errPromise, err) - - return nil, false - } - - var dbProof *models.ChannelAuthProof + var dbProof models.ChannelAuthProof if isFirstNode { - dbProof = models.NewV1ChannelAuthProof( - ann.NodeSignature.ToSignatureBytes(), - oppV1.NodeSignature.ToSignatureBytes(), - ann.BitcoinSignature.ToSignatureBytes(), - oppV1.BitcoinSignature.ToSignatureBytes(), - ) + dbProof.NodeSig1Bytes = ann.NodeSignature.ToSignatureBytes() + dbProof.NodeSig2Bytes = oppProof.NodeSignature.ToSignatureBytes() + dbProof.BitcoinSig1Bytes = ann.BitcoinSignature.ToSignatureBytes() + dbProof.BitcoinSig2Bytes = oppProof.BitcoinSignature.ToSignatureBytes() } else { - dbProof = models.NewV1ChannelAuthProof( - oppV1.NodeSignature.ToSignatureBytes(), - ann.NodeSignature.ToSignatureBytes(), - oppV1.BitcoinSignature.ToSignatureBytes(), - ann.BitcoinSignature.ToSignatureBytes(), - ) + dbProof.NodeSig1Bytes = oppProof.NodeSignature.ToSignatureBytes() + dbProof.NodeSig2Bytes = ann.NodeSignature.ToSignatureBytes() + dbProof.BitcoinSig1Bytes = oppProof.BitcoinSignature.ToSignatureBytes() + dbProof.BitcoinSig2Bytes = ann.BitcoinSignature.ToSignatureBytes() } - // Attach the proof to the channel info before creating the - // announcement. - chanInfo.AuthProof = dbProof - chanAnn, e1Ann, e2Ann, err := netann.CreateChanAnnouncement( - chanInfo, e1, e2, + &dbProof, chanInfo, e1, e2, ) if err != nil { log.Error(err) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -3807,7 +3762,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context, "short_chan_id=%v isn't valid: %v", shortChanID, err) log.Error(err) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -3817,12 +3772,12 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context, // attest to the bitcoin keys by validating the signatures of // announcement. If proof is valid then we'll populate the channel edge // with it, so we can announce it on peer connect. - err = d.cfg.Graph.AddProof(ann.ShortChannelID, dbProof) + err = d.cfg.Graph.AddProof(ann.ShortChannelID, &dbProof) if err != nil { err := fmt.Errorf("unable add proof to the channel chanID=%v:"+ " %v", ann.ChannelID, err) log.Error(err) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -3831,7 +3786,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context, err := fmt.Errorf("unable to remove opposite proof for the "+ "channel with chanID=%v: %v", ann.ChannelID, err) log.Error(err) - completeGossipResult(nMsg.errPromise, err) + nMsg.err <- err return nil, false } @@ -3898,7 +3853,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context, } } - completeGossipResult(nMsg.errPromise, nil) + nMsg.err <- nil return announcements, true } diff --git a/discovery/gossiper_test.go b/discovery/gossiper_test.go index eb045e8ed..e8c59fe14 100644 --- a/discovery/gossiper_test.go +++ b/discovery/gossiper_test.go @@ -17,17 +17,15 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" "github.com/lightninglabs/neutrino/cache" - "github.com/lightningnetwork/lnd/actor" "github.com/lightningnetwork/lnd/batch" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/graph" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" @@ -223,12 +221,14 @@ func (r *mockGraphSource) ForAllOutgoingChannels(_ context.Context, chans := make(map[uint64]graphdb.ChannelEdge) for _, info := range r.infos { + info := info edgeInfo := chans[info.ChannelID] edgeInfo.Info = &info chans[info.ChannelID] = edgeInfo } for _, edges := range r.edges { + edges := edges edge := chans[edges[0].ChannelID] edge.Policy1 = &edges[0] @@ -272,15 +272,10 @@ func (r *mockGraphSource) GetChannelByID(chanID lnwire.ShortChannelID) ( return nil, nil, nil, graphdb.ErrEdgeNotFound } - zombieEdge, err := models.NewV1Channel( - 0, chainhash.Hash{}, pubKeys[0], pubKeys[1], - &models.ChannelV1Fields{}, - ) - if err != nil { - return nil, nil, nil, err - } - - return zombieEdge, nil, nil, graphdb.ErrZombieEdge + return &models.ChannelEdgeInfo{ + NodeKey1Bytes: pubKeys[0], + NodeKey2Bytes: pubKeys[1], + }, nil, nil, graphdb.ErrZombieEdge } edges := r.edges[chanID.ToUint64()] @@ -413,9 +408,7 @@ func (r *mockGraphSource) IsStaleEdgePolicy(chanID lnwire.ShortChannelID, // MarkEdgeLive clears an edge from our zombie index, deeming it as live. // // NOTE: This method is part of the ChannelGraphSource interface. -func (r *mockGraphSource) MarkEdgeLive(_ lnwire.GossipVersion, - chanID lnwire.ShortChannelID) error { - +func (r *mockGraphSource) MarkEdgeLive(chanID lnwire.ShortChannelID) error { r.mu.Lock() defer r.mu.Unlock() delete(r.zombies, chanID.ToUint64()) @@ -890,7 +883,7 @@ func (ctx *testCtx) createChannelAnnouncement(blockHeight uint32, key1, } func mockFindChannel(node *btcec.PublicKey, chanID lnwire.ChannelID) ( - *chanstate.OpenChannel, error) { + *channeldb.OpenChannel, error) { return nil, nil } @@ -947,11 +940,7 @@ func createTestCtx(t *testing.T, startHeight uint32, isChanPeer bool) ( return lnwire.ShortChannelID{}, fmt.Errorf("no peer alias") } - hID := lnwire.ShortChannelID{BlockHeight: startHeight} - channelSeries := newMockChannelGraphTimeSeries(hID) - gossiper := New(Config{ - ChanSeries: channelSeries, ChainIO: chain, ChainParams: &chaincfg.MainNetParams, Notifier: notifier, @@ -1061,11 +1050,11 @@ func TestProcessAnnouncement(t *testing.T) { ca, err := tCtx.createRemoteChannelAnnouncement(0) require.NoError(t, err, "can't create channel announcement") - err = mustProcess( - t, tCtx.gossiper.ProcessRemoteAnnouncement( - ctx, ca, nodePeer, - ), - ) + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement(ctx, ca, nodePeer): + case <-time.After(2 * time.Second): + t.Fatal("remote announcement not processed") + } require.NoError(t, err, "can't process remote announcement") // The announcement should be broadcast and included in our local view @@ -1087,11 +1076,11 @@ func TestProcessAnnouncement(t *testing.T) { ua.MessageFlags = 0 // We send an invalid channel update and expect it to fail. - err = mustProcess( - t, tCtx.gossiper.ProcessRemoteAnnouncement( - ctx, ua, nodePeer, - ), - ) + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement(ctx, ua, nodePeer): + case <-time.After(2 * time.Second): + t.Fatal("remote announcement not processed") + } require.ErrorContains(t, err, "max htlc flag not set for channel "+ "update") @@ -1107,11 +1096,11 @@ func TestProcessAnnouncement(t *testing.T) { ua, err = createUpdateAnnouncement(0, 0, remoteKeyPriv1, timestamp) require.NoError(t, err, "can't create update announcement") - err = mustProcess( - t, tCtx.gossiper.ProcessRemoteAnnouncement( - ctx, ua, nodePeer, - ), - ) + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement(ctx, ua, nodePeer): + case <-time.After(2 * time.Second): + t.Fatal("remote announcement not processed") + } require.NoError(t, err, "can't process remote announcement") // The channel policy should be broadcast to the rest of the network. @@ -1130,11 +1119,11 @@ func TestProcessAnnouncement(t *testing.T) { na, err := createNodeAnnouncement(remoteKeyPriv1, timestamp) require.NoError(t, err, "can't create node announcement") - err = mustProcess( - t, tCtx.gossiper.ProcessRemoteAnnouncement( - ctx, na, nodePeer, - ), - ) + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement(ctx, na, nodePeer): + case <-time.After(2 * time.Second): + t.Fatal("remote announcement not processed") + } require.NoError(t, err, "can't process remote announcement") // It should also be broadcast to the network and included in our local @@ -1176,11 +1165,11 @@ func TestPrematureAnnouncement(t *testing.T) { ) require.NoError(t, err, "can't create channel announcement") - _ = mustProcess( - t, tCtx.gossiper.ProcessRemoteAnnouncement( - ctx, ca, nodePeer, - ), - ) + select { + case <-tCtx.gossiper.ProcessRemoteAnnouncement(ctx, ca, nodePeer): + case <-time.After(time.Second): + t.Fatal("announcement was not processed") + } if len(tCtx.router.infos) != 0 { t.Fatal("edge was added to router") @@ -1223,9 +1212,11 @@ func TestSignatureAnnouncementLocalFirst(t *testing.T) { // Recreate lightning network topology. Initialize router with channel // between two nodes. - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( - batch.chanAnn, - )) + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanAnn): + case <-time.After(2 * time.Second): + t.Fatal("did not process local announcement") + } require.NoError(t, err, "unable to process channel ann") select { case <-tCtx.broadcastedMessage: @@ -1233,9 +1224,11 @@ func TestSignatureAnnouncementLocalFirst(t *testing.T) { case <-time.After(2 * trickleDelay): } - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( - batch.chanUpdAnn1, - )) + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanUpdAnn1): + case <-time.After(2 * time.Second): + t.Fatal("did not process local announcement") + } require.NoError(t, err, "unable to process channel update") select { case <-tCtx.broadcastedMessage: @@ -1243,9 +1236,11 @@ func TestSignatureAnnouncementLocalFirst(t *testing.T) { case <-time.After(2 * trickleDelay): } - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( - batch.nodeAnn1, - )) + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.nodeAnn1): + case <-time.After(2 * time.Second): + t.Fatal("did not process local announcement") + } require.NoError(t, err, "unable to process node ann") select { case <-tCtx.broadcastedMessage: @@ -1263,9 +1258,13 @@ func TestSignatureAnnouncementLocalFirst(t *testing.T) { t.Fatal("gossiper did not send channel update to peer") } - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.chanUpdAnn2, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process channel update") select { case <-tCtx.broadcastedMessage: @@ -1273,9 +1272,13 @@ func TestSignatureAnnouncementLocalFirst(t *testing.T) { case <-time.After(2 * trickleDelay): } - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.nodeAnn2, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process node ann") select { case <-tCtx.broadcastedMessage: @@ -1285,9 +1288,13 @@ func TestSignatureAnnouncementLocalFirst(t *testing.T) { // Pretending that we receive local channel announcement from funding // manager, thereby kick off the announcement exchange process. - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( batch.localProofAnn, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process local proof") select { @@ -1313,9 +1320,13 @@ func TestSignatureAnnouncementLocalFirst(t *testing.T) { t.Fatal("wrong number of objects in storage") } - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.remoteProofAnn, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process remote proof") for i := 0; i < 5; i++ { @@ -1382,9 +1393,13 @@ func TestOrphanSignatureAnnouncement(t *testing.T) { // manager, thereby kick off the announcement exchange process, in // this case the announcement should be added in the orphan batch // because we haven't announce the channel yet. - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.remoteProofAnn, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to proceed announcement") number := 0 @@ -1406,9 +1421,11 @@ func TestOrphanSignatureAnnouncement(t *testing.T) { // Recreate lightning network topology. Initialize router with channel // between two nodes. - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( - batch.chanAnn, - )) + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanAnn): + case <-time.After(2 * time.Second): + t.Fatal("did not process local announcement") + } require.NoError(t, err, "unable to process") @@ -1418,9 +1435,11 @@ func TestOrphanSignatureAnnouncement(t *testing.T) { case <-time.After(2 * trickleDelay): } - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( - batch.chanUpdAnn1, - )) + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanUpdAnn1): + case <-time.After(2 * time.Second): + t.Fatal("did not process local announcement") + } require.NoError(t, err, "unable to process") select { @@ -1429,9 +1448,11 @@ func TestOrphanSignatureAnnouncement(t *testing.T) { case <-time.After(2 * trickleDelay): } - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( - batch.nodeAnn1, - )) + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.nodeAnn1): + case <-time.After(2 * time.Second): + t.Fatal("did not process local announcement") + } require.NoError(t, err, "unable to process node ann") select { case <-tCtx.broadcastedMessage: @@ -1449,9 +1470,13 @@ func TestOrphanSignatureAnnouncement(t *testing.T) { t.Fatal("gossiper did not send channel update to peer") } - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.chanUpdAnn2, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process node ann") select { case <-tCtx.broadcastedMessage: @@ -1459,9 +1484,13 @@ func TestOrphanSignatureAnnouncement(t *testing.T) { case <-time.After(2 * trickleDelay): } - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.nodeAnn2, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process") select { case <-tCtx.broadcastedMessage: @@ -1471,9 +1500,13 @@ func TestOrphanSignatureAnnouncement(t *testing.T) { // After that we process local announcement, and waiting to receive // the channel announcement. - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( batch.localProofAnn, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process") // The local proof should be sent to the remote peer. @@ -1547,9 +1580,11 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) { // Recreate lightning network topology. Initialize router with channel // between two nodes. - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( - batch.chanAnn, - )) + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanAnn): + case <-time.After(2 * time.Second): + t.Fatal("did not process local announcement") + } require.NoError(t, err, "unable to process channel ann") select { case <-tCtx.broadcastedMessage: @@ -1559,9 +1594,13 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) { // Pretending that we receive local channel announcement from funding // manager, thereby kick off the announcement exchange process. - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( batch.localProofAnn, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } if err != nil { t.Fatalf("unable to process :%v", err) } @@ -1709,9 +1748,13 @@ out: // Now exchanging the remote channel proof, the channel announcement // broadcast should continue as normal. - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.remoteProofAnn, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } if err != nil { t.Fatalf("unable to process :%v", err) } @@ -1773,9 +1816,13 @@ func TestSignatureAnnouncementFullProofWhenRemoteProof(t *testing.T) { // Recreate lightning network topology. Initialize router with channel // between two nodes. - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( batch.chanAnn, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process local announcement") + } require.NoError(t, err, "unable to process channel ann") select { case <-tCtx.broadcastedMessage: @@ -1783,9 +1830,13 @@ func TestSignatureAnnouncementFullProofWhenRemoteProof(t *testing.T) { case <-time.After(2 * trickleDelay): } - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( batch.chanUpdAnn1, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process local announcement") + } require.NoError(t, err, "unable to process channel update") select { case <-tCtx.broadcastedMessage: @@ -1800,9 +1851,13 @@ func TestSignatureAnnouncementFullProofWhenRemoteProof(t *testing.T) { t.Fatal("gossiper did not send channel update to remove peer") } - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( batch.nodeAnn1, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process local announcement") + } if err != nil { t.Fatalf("unable to process node ann:%v", err) } @@ -1812,18 +1867,26 @@ func TestSignatureAnnouncementFullProofWhenRemoteProof(t *testing.T) { case <-time.After(2 * trickleDelay): } - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.chanUpdAnn2, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process channel update") select { case <-tCtx.broadcastedMessage: t.Fatal("channel update announcement was broadcast") case <-time.After(2 * trickleDelay): } - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.nodeAnn2, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process node ann") select { case <-tCtx.broadcastedMessage: @@ -1833,14 +1896,22 @@ func TestSignatureAnnouncementFullProofWhenRemoteProof(t *testing.T) { // Pretending that we receive local channel announcement from funding // manager, thereby kick off the announcement exchange process. - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( batch.localProofAnn, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process local announcement") + } require.NoError(t, err, "unable to process local proof") - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.remoteProofAnn, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process local announcement") + } require.NoError(t, err, "unable to process remote proof") // We expect the gossiper to send this message to the remote peer. @@ -1879,9 +1950,13 @@ func TestSignatureAnnouncementFullProofWhenRemoteProof(t *testing.T) { // Now give the gossiper the remote proof yet again. This should // trigger a send of the full ChannelAnnouncement. - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.remoteProofAnn, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process local announcement") + } require.NoError(t, err, "unable to process remote proof") // We expect the gossiper to send this message to the remote peer. @@ -2173,9 +2248,13 @@ func TestForwardPrivateNodeAnnouncement(t *testing.T) { ) pubKey := remoteKeyPriv1.PubKey() - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement(chanAnn)) - if err != nil { - t.Fatalf("unable to process local announcement: %v", err) + select { + case err := <-tCtx.gossiper.ProcessLocalAnnouncement(chanAnn): + if err != nil { + t.Fatalf("unable to process local announcement: %v", err) + } + case <-time.After(2 * time.Second): + t.Fatalf("local announcement not processed") } // The gossiper should not broadcast the announcement due to it not @@ -2189,9 +2268,14 @@ func TestForwardPrivateNodeAnnouncement(t *testing.T) { nodeAnn, err := createNodeAnnouncement(remoteKeyPriv1, timestamp) require.NoError(t, err, "unable to create node announcement") - _ = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( - nodeAnn, - )) + select { + case err := <-tCtx.gossiper.ProcessLocalAnnouncement(nodeAnn): + if err != nil { + t.Fatalf("unable to process remote announcement: %v", err) + } + case <-time.After(2 * time.Second): + t.Fatal("remote announcement not processed") + } // The gossiper should also not broadcast the node announcement due to // it not being part of any advertised channels. @@ -2211,11 +2295,15 @@ func TestForwardPrivateNodeAnnouncement(t *testing.T) { require.NoError(t, err, "unable to create remote channel announcement") peer := &mockPeer{pubKey, nil, nil, atomic.Bool{}} - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err := <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, remoteChanAnn, peer, - )) - if err != nil { - t.Fatalf("unable to process remote announcement: %v", err) + ): + if err != nil { + t.Fatalf("unable to process remote announcement: %v", err) + } + case <-time.After(2 * time.Second): + t.Fatal("remote announcement not processed") } select { @@ -2230,11 +2318,15 @@ func TestForwardPrivateNodeAnnouncement(t *testing.T) { nodeAnn, err = createNodeAnnouncement(remoteKeyPriv1, timestamp+1) require.NoError(t, err, "unable to create node announcement") - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err := <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, nodeAnn, peer, - )) - if err != nil { - t.Fatalf("unable to process remote announcement: %v", err) + ): + if err != nil { + t.Fatalf("unable to process remote announcement: %v", err) + } + case <-time.After(2 * time.Second): + t.Fatal("remote announcement not processed") } select { @@ -2268,14 +2360,18 @@ func TestRejectZombieEdge(t *testing.T) { errChan := tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.chanAnn, remotePeer, ) - err := mustProcess(t, errChan) - if isZombie && err != nil { - t.Fatalf("expected to reject live channel "+ - "announcement with nil error: %v", err) - } - if !isZombie && err != nil { - t.Fatalf("expected to process live channel "+ - "announcement: %v", err) + select { + case err := <-errChan: + if isZombie && err != nil { + t.Fatalf("expected to reject live channel "+ + "announcement with nil error: %v", err) + } + if !isZombie && err != nil { + t.Fatalf("expected to process live channel "+ + "announcement: %v", err) + } + case <-time.After(time.Second): + t.Fatal("expected to process channel announcement") } select { case <-tCtx.broadcastedMessage: @@ -2293,14 +2389,18 @@ func TestRejectZombieEdge(t *testing.T) { errChan = tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.chanUpdAnn2, remotePeer, ) - err = mustProcess(t, errChan) - if isZombie && err != nil { - t.Fatalf("expected to reject zombie channel "+ - "update with nil error: %v", err) - } - if !isZombie && err != nil { - t.Fatalf("expected to process live channel "+ - "update: %v", err) + select { + case err := <-errChan: + if isZombie && err != nil { + t.Fatalf("expected to reject zombie channel "+ + "update with nil error: %v", err) + } + if !isZombie && err != nil { + t.Fatalf("expected to process live channel "+ + "update: %v", err) + } + case <-time.After(time.Second): + t.Fatal("expected to process channel update") } select { case <-tCtx.broadcastedMessage: @@ -2331,8 +2431,7 @@ func TestRejectZombieEdge(t *testing.T) { // If we then mark the edge as live, the edge's zombie status should be // overridden and the announcements should be processed. - err = tCtx.router.MarkEdgeLive(lnwire.GossipVersion1, chanID) - if err != nil { + if err := tCtx.router.MarkEdgeLive(chanID); err != nil { t.Fatalf("unable mark channel %v as zombie: %v", chanID, err) } @@ -2369,7 +2468,11 @@ func TestProcessZombieEdgeNowLive(t *testing.T) { ) var err error - err = mustProcess(t, errChan) + select { + case err = <-errChan: + case <-time.After(time.Second): + t.Fatal("expected to process announcement") + } if expectsErr && err == nil { t.Fatal("expected error when processing announcement") } @@ -2474,9 +2577,14 @@ func TestProcessZombieEdgeNowLive(t *testing.T) { // After successfully processing the announcement, the channel update // should have been processed and broadcast successfully as well. - err = mustProcess(t, updateErrChan) - if err != nil { - t.Fatalf("expected to process live channel update: %v", err) + select { + case err := <-updateErrChan: + if err != nil { + t.Fatalf("expected to process live channel update: %v", + err) + } + case <-time.After(time.Second): + t.Fatal("expected to process announcement") } select { @@ -2530,9 +2638,9 @@ func TestReceiveRemoteChannelUpdateFirst(t *testing.T) { case <-time.After(2 * trickleDelay): } - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.nodeAnn2, remotePeer, - )) + ) require.NoError(t, err, "unable to process node ann") select { case <-tCtx.broadcastedMessage: @@ -2562,9 +2670,7 @@ func TestReceiveRemoteChannelUpdateFirst(t *testing.T) { // Recreate lightning network topology. Initialize router with channel // between two nodes. - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( - batch.chanAnn, - )) + err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanAnn) if err != nil { t.Fatalf("unable to process :%v", err) } @@ -2574,9 +2680,7 @@ func TestReceiveRemoteChannelUpdateFirst(t *testing.T) { case <-time.After(2 * trickleDelay): } - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( - batch.chanUpdAnn1, - )) + err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanUpdAnn1) if err != nil { t.Fatalf("unable to process :%v", err) } @@ -2586,9 +2690,7 @@ func TestReceiveRemoteChannelUpdateFirst(t *testing.T) { case <-time.After(2 * trickleDelay): } - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( - batch.nodeAnn1, - )) + err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.nodeAnn1) if err != nil { t.Fatalf("unable to process :%v", err) } @@ -2610,9 +2712,13 @@ func TestReceiveRemoteChannelUpdateFirst(t *testing.T) { // At this point the remote ChannelUpdate we received earlier should // be reprocessed, as we now have the necessary edge entry in the graph. - err = mustProcess(t, errRemoteAnn) - if err != nil { - t.Fatalf("error re-processing remote update: %v", err) + select { + case err := <-errRemoteAnn: + if err != nil { + t.Fatalf("error re-processing remote update: %v", err) + } + case <-time.After(2 * trickleDelay): + t.Fatalf("remote update was not processed") } // Check that the ChannelEdgePolicy was added to the graph. @@ -2632,9 +2738,7 @@ func TestReceiveRemoteChannelUpdateFirst(t *testing.T) { // Pretending that we receive local channel announcement from funding // manager, thereby kick off the announcement exchange process. - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( - batch.localProofAnn, - )) + err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.localProofAnn) if err != nil { t.Fatalf("unable to process :%v", err) } @@ -2662,9 +2766,9 @@ func TestReceiveRemoteChannelUpdateFirst(t *testing.T) { t.Fatal("wrong number of objects in storage") } - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.remoteProofAnn, remotePeer, - )) + ) if err != nil { t.Fatalf("unable to process :%v", err) } @@ -2720,9 +2824,13 @@ func TestExtraDataChannelAnnouncementValidation(t *testing.T) { // We'll now send the announcement to the main gossiper. We should be // able to validate this announcement to problem. - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, ca, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } if err != nil { t.Fatalf("unable to process :%v", err) } @@ -2761,19 +2869,31 @@ func TestExtraDataChannelUpdateValidation(t *testing.T) { // We should be able to properly validate all three messages without // any issue. - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, chanAnn, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process announcement") - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, chanUpdAnn1, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process announcement") - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, chanUpdAnn2, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process announcement") } @@ -2800,9 +2920,13 @@ func TestExtraDataNodeAnnouncementValidation(t *testing.T) { ) require.NoError(t, err, "can't create node announcement") - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, nodeAnn, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process announcement") } @@ -2824,9 +2948,13 @@ func TestZeroTimestampNodeAnnouncementRejection(t *testing.T) { require.NoError(t, err, "can't create node announcement") // Processing the announcement should fail with a zero timestamp error. - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, nodeAnn, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.Error(t, err) require.Contains(t, err.Error(), "zero timestamp") } @@ -2849,9 +2977,13 @@ func TestZeroTimestampChannelUpdateRejection(t *testing.T) { chanAnn, err := tCtx.createRemoteChannelAnnouncement(0) require.NoError(t, err, "unable to create chan ann") - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, chanAnn, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process chan ann") // Now create a channel update with a zero timestamp. @@ -2859,9 +2991,13 @@ func TestZeroTimestampChannelUpdateRejection(t *testing.T) { require.NoError(t, err, "unable to create chan update") // Processing the update should fail with a zero timestamp error. - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, chanUpdAnn, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.Error(t, err) require.Contains(t, err.Error(), "zero timestamp") } @@ -2894,12 +3030,16 @@ func assertBroadcast(t *testing.T, ctx *testCtx, num int) []lnwire.Message { // assertProcessAnnouncement is a helper method that checks that the result of // processing an announcement is successful. -func assertProcessAnnouncement(t *testing.T, result actor.Future[error]) { +func assertProcessAnnouncement(t *testing.T, result chan error) { t.Helper() - err := mustProcess(t, result) - if err != nil { - t.Fatalf("unable to process :%v", err) + select { + case err := <-result: + if err != nil { + t.Fatalf("unable to process :%v", err) + } + case <-time.After(2 * time.Second): + t.Fatal("did not process announcement") } } @@ -3030,9 +3170,13 @@ func TestNodeAnnouncementNoChannels(t *testing.T) { remotePeer := &mockPeer{remoteKey, nil, nil, atomic.Bool{}} // Process the remote node announcement. - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.nodeAnn2, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process announcement") // Since no channels or node announcements were already in the graph, @@ -3045,20 +3189,32 @@ func TestNodeAnnouncementNoChannels(t *testing.T) { // Now add the node's channel to the graph by processing the channel // announcement and channel update. - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.chanAnn, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process announcement") - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.chanUpdAnn2, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process announcement") // Now process the node announcement again. - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.nodeAnn2, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process announcement") // This time the node announcement should be forwarded. The same should @@ -3073,9 +3229,13 @@ func TestNodeAnnouncementNoChannels(t *testing.T) { // Processing the same node announcement again should be ignored, as it // is stale. - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.nodeAnn2, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process announcement") select { @@ -3105,7 +3265,11 @@ func TestOptionalFieldsChannelUpdateValidation(t *testing.T) { chanAnn, err := tCtx.createRemoteChannelAnnouncement(chanUpdateHeight) require.NoError(t, err, "can't create channel announcement") - err = mustProcess(t, processRemoteAnnouncement(ctx, chanAnn, nodePeer)) + select { + case err = <-processRemoteAnnouncement(ctx, chanAnn, nodePeer): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process announcement") // The first update should fail from an invalid max HTLC field, which is @@ -3121,9 +3285,11 @@ func TestOptionalFieldsChannelUpdateValidation(t *testing.T) { t.Fatalf("unable to sign channel update: %v", err) } - err = mustProcess(t, processRemoteAnnouncement( - ctx, chanUpdAnn, nodePeer, - )) + select { + case err = <-processRemoteAnnouncement(ctx, chanUpdAnn, nodePeer): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } if err == nil || !strings.Contains(err.Error(), "invalid max htlc") { t.Fatalf("expected chan update to error, instead got %v", err) } @@ -3136,9 +3302,11 @@ func TestOptionalFieldsChannelUpdateValidation(t *testing.T) { t.Fatalf("unable to sign channel update: %v", err) } - err = mustProcess(t, processRemoteAnnouncement( - ctx, chanUpdAnn, nodePeer, - )) + select { + case err = <-processRemoteAnnouncement(ctx, chanUpdAnn, nodePeer): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } if err == nil || !strings.Contains(err.Error(), "invalid max htlc") { t.Fatalf("expected chan update to error, instead got %v", err) } @@ -3150,9 +3318,11 @@ func TestOptionalFieldsChannelUpdateValidation(t *testing.T) { t.Fatalf("unable to sign channel update: %v", err) } - err = mustProcess(t, processRemoteAnnouncement( - ctx, chanUpdAnn, nodePeer, - )) + select { + case err = <-processRemoteAnnouncement(ctx, chanUpdAnn, nodePeer): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.ErrorContains(t, err, "max htlc flag not set") // The final update should succeed. @@ -3165,9 +3335,11 @@ func TestOptionalFieldsChannelUpdateValidation(t *testing.T) { t.Fatalf("unable to sign channel update: %v", err) } - err = mustProcess(t, processRemoteAnnouncement( - ctx, chanUpdAnn, nodePeer, - )) + select { + case err = <-processRemoteAnnouncement(ctx, chanUpdAnn, nodePeer): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "expected update to be processed") } @@ -3233,9 +3405,11 @@ func TestSendChannelUpdateReliably(t *testing.T) { // Process the channel announcement for which we'll send a channel // update for. - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( - batch.chanAnn, - )) + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanAnn): + case <-time.After(2 * time.Second): + t.Fatal("did not process local channel announcement") + } require.NoError(t, err, "unable to process local channel announcement") // It should not be broadcast due to not having an announcement proof. @@ -3246,9 +3420,11 @@ func TestSendChannelUpdateReliably(t *testing.T) { } // Now, we'll process the channel update. - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( - batch.chanUpdAnn1, - )) + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanUpdAnn1): + case <-time.After(2 * time.Second): + t.Fatal("did not process local channel update") + } require.NoError(t, err, "unable to process local channel update") // It should also not be broadcast due to the announcement not having an @@ -3302,9 +3478,13 @@ func TestSendChannelUpdateReliably(t *testing.T) { } // With the new update created, we'll go ahead and process it. - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( batch.chanUpdAnn1, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process local channel update") + } require.NoError(t, err, "unable to process local channel update") // It should also not be broadcast due to the announcement not having an @@ -3337,9 +3517,13 @@ func TestSendChannelUpdateReliably(t *testing.T) { // We'll then exchange proofs with the remote peer in order to announce // the channel. - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( batch.localProofAnn, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process local channel proof") + } require.NoError(t, err, "unable to process local channel proof") // No messages should be broadcast as we don't have the full proof yet. @@ -3352,9 +3536,13 @@ func TestSendChannelUpdateReliably(t *testing.T) { // Our proof should be sent to the remote peer however. assertMsgSent(batch.localProofAnn) - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.remoteProofAnn, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote channel proof") + } require.NoError(t, err, "unable to process remote channel proof") // Now that we've constructed our full proof, we can assert that the @@ -3382,9 +3570,13 @@ func TestSendChannelUpdateReliably(t *testing.T) { // Process the new channel update. It should not be sent to the peer // directly since the reliable sender only applies when the channel is // not announced. - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( newChannelUpdate, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process local channel update") + } require.NoError(t, err, "unable to process local channel update") select { case <-tCtx.broadcastedMessage: @@ -3455,9 +3647,14 @@ func sendLocalMsg(t *testing.T, ctx *testCtx, msg lnwire.Message, t.Helper() - err := mustProcess(t, ctx.gossiper.ProcessLocalAnnouncement( + var err error + select { + case err = <-ctx.gossiper.ProcessLocalAnnouncement( msg, optionalMsgFields..., - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process local announcement") + } require.NoError(t, err, "unable to process channel msg") } @@ -3466,31 +3663,18 @@ func sendRemoteMsg(t *testing.T, ctx *testCtx, msg lnwire.Message, t.Helper() - err := mustProcess(t, ctx.gossiper.ProcessRemoteAnnouncement( + select { + case err := <-ctx.gossiper.ProcessRemoteAnnouncement( t.Context(), msg, remotePeer, - )) - if err != nil { - t.Fatalf("unable to process channel msg: %v", err) + ): + if err != nil { + t.Fatalf("unable to process channel msg: %v", err) + } + case <-time.After(2 * time.Second): + t.Fatal("did not process local announcement") } } -// mustProcess awaits a gossip future with a 2-second deadline, failing -// the test immediately if the deadline is exceeded. -func mustProcess(t *testing.T, f actor.Future[error]) error { - t.Helper() - - ctx, cancel := context.WithTimeout(t.Context(), 2*time.Second) - defer cancel() - - err := AwaitGossipResult(ctx, f) - if errors.Is(err, context.DeadlineExceeded) { - t.Fatal("gossip message was not processed within deadline") - return nil - } - - return err -} - func assertBroadcastMsg(t *testing.T, ctx *testCtx, predicate func(lnwire.Message) error) { @@ -3898,7 +4082,7 @@ func TestBroadcastAnnsAfterGraphSynced(t *testing.T) { nodePeer := &mockPeer{ remoteKeyPriv1.PubKey(), nil, nil, atomic.Bool{}, } - var errChan actor.Future[error] + var errChan chan error if isRemote { errChan = tCtx.gossiper.ProcessRemoteAnnouncement( ctx, msg, nodePeer, @@ -3907,10 +4091,14 @@ func TestBroadcastAnnsAfterGraphSynced(t *testing.T) { errChan = tCtx.gossiper.ProcessLocalAnnouncement(msg) } - err := mustProcess(t, errChan) - if err != nil { - t.Fatalf("unable to process gossip message: %v", - err) + select { + case err := <-errChan: + if err != nil { + t.Fatalf("unable to process gossip message: %v", + err) + } + case <-time.After(2 * time.Second): + t.Fatal("gossip message not processed") } select { @@ -3996,23 +4184,35 @@ func TestRateLimitDeDup(t *testing.T) { nodePeer1 := &mockPeer{ remoteKeyPriv1.PubKey(), nil, nil, atomic.Bool{}, } - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err := <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.chanAnn, nodePeer1, - )) - require.NoError(t, err) + ): + require.NoError(t, err) + case <-time.After(time.Second): + t.Fatal("remote announcement not processed") + } - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err := <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.chanUpdAnn1, nodePeer1, - )) - require.NoError(t, err) + ): + require.NoError(t, err) + case <-time.After(time.Second): + t.Fatal("remote announcement not processed") + } nodePeer2 := &mockPeer{ remoteKeyPriv2.PubKey(), nil, nil, atomic.Bool{}, } - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err := <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.chanUpdAnn2, nodePeer2, - )) - require.NoError(t, err) + ): + require.NoError(t, err) + case <-time.After(time.Second): + t.Fatal("remote announcement not processed") + } timeout := time.After(2 * trickleDelay) for i := 0; i < 3; i++ { @@ -4109,10 +4309,14 @@ func TestRateLimitDeDup(t *testing.T) { } processUpdate := func(msg lnwire.Message, peer lnpeer.Peer) { - err := mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err := <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, msg, peer, - )) - require.NoError(t, err) + ): + require.NoError(t, err) + case <-time.After(time.Second): + t.Fatal("remote announcement not processed") + } } // Show that the last update was broadcast. @@ -4158,23 +4362,35 @@ func TestRateLimitChannelUpdates(t *testing.T) { nodePeer1 := &mockPeer{ remoteKeyPriv1.PubKey(), nil, nil, atomic.Bool{}, } - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err := <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.chanAnn, nodePeer1, - )) - require.NoError(t, err) + ): + require.NoError(t, err) + case <-time.After(time.Second): + t.Fatal("remote announcement not processed") + } - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err := <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.chanUpdAnn1, nodePeer1, - )) - require.NoError(t, err) + ): + require.NoError(t, err) + case <-time.After(time.Second): + t.Fatal("remote announcement not processed") + } nodePeer2 := &mockPeer{ remoteKeyPriv2.PubKey(), nil, nil, atomic.Bool{}, } - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err := <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.chanUpdAnn2, nodePeer2, - )) - require.NoError(t, err) + ): + require.NoError(t, err) + case <-time.After(time.Second): + t.Fatal("remote announcement not processed") + } timeout := time.After(2 * trickleDelay) for i := 0; i < 3; i++ { @@ -4196,10 +4412,14 @@ func TestRateLimitChannelUpdates(t *testing.T) { t.Helper() - err := mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err := <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, update, peer, - )) - require.NoError(t, err) + ): + require.NoError(t, err) + case <-time.After(time.Second): + t.Fatal("remote announcement not processed") + } select { case <-tCtx.broadcastedMessage: @@ -4219,9 +4439,7 @@ func TestRateLimitChannelUpdates(t *testing.T) { // our rebroadcast interval. rateLimitKeepAliveUpdate := *batch.chanUpdAnn1 rateLimitKeepAliveUpdate.Timestamp++ - require.NoError( - t, signUpdate(remoteKeyPriv1, &rateLimitKeepAliveUpdate), - ) + require.NoError(t, signUpdate(remoteKeyPriv1, &rateLimitKeepAliveUpdate)) assertRateLimit(&rateLimitKeepAliveUpdate, nodePeer1, true) keepAliveUpdate := *batch.chanUpdAnn1 @@ -4241,9 +4459,7 @@ func TestRateLimitChannelUpdates(t *testing.T) { for i := uint32(0); i < uint32(tCtx.gossiper.cfg.MaxChannelUpdateBurst); i++ { //nolint:ll updateSameDirection.Timestamp++ updateSameDirection.BaseFee++ - require.NoError( - t, signUpdate(remoteKeyPriv1, &updateSameDirection), - ) + require.NoError(t, signUpdate(remoteKeyPriv1, &updateSameDirection)) assertRateLimit(&updateSameDirection, nodePeer1, false) } @@ -4291,9 +4507,13 @@ func TestIgnoreOwnAnnouncement(t *testing.T) { remotePeer := &mockPeer{remoteKey, nil, nil, atomic.Bool{}} // Try to let the remote peer tell us about the channel we are part of. - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.chanAnn, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } // It should be ignored, since the gossiper only cares about local // announcements for its own channels. if err == nil || !strings.Contains(err.Error(), "ignoring") { @@ -4303,9 +4523,11 @@ func TestIgnoreOwnAnnouncement(t *testing.T) { // Now do the local channelannouncement, node announcement, and channel // update. No messages should be broadcast yet, since we don't have // the announcement signatures. - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( - batch.chanAnn, - )) + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanAnn): + case <-time.After(2 * time.Second): + t.Fatal("did not process local announcement") + } require.NoError(t, err, "unable to process channel ann") select { case <-tCtx.broadcastedMessage: @@ -4313,9 +4535,11 @@ func TestIgnoreOwnAnnouncement(t *testing.T) { case <-time.After(2 * trickleDelay): } - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( - batch.chanUpdAnn1, - )) + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanUpdAnn1): + case <-time.After(2 * time.Second): + t.Fatal("did not process local announcement") + } require.NoError(t, err, "unable to process channel update") select { case <-tCtx.broadcastedMessage: @@ -4323,9 +4547,11 @@ func TestIgnoreOwnAnnouncement(t *testing.T) { case <-time.After(2 * trickleDelay): } - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( - batch.nodeAnn1, - )) + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.nodeAnn1): + case <-time.After(2 * time.Second): + t.Fatal("did not process local announcement") + } require.NoError(t, err, "unable to process node ann") select { case <-tCtx.broadcastedMessage: @@ -4334,9 +4560,13 @@ func TestIgnoreOwnAnnouncement(t *testing.T) { } // We should accept the remote's channel update and node announcement. - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.chanUpdAnn2, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process channel update") select { case <-tCtx.broadcastedMessage: @@ -4344,9 +4574,13 @@ func TestIgnoreOwnAnnouncement(t *testing.T) { case <-time.After(2 * trickleDelay): } - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.nodeAnn2, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process node ann") select { case <-tCtx.broadcastedMessage: @@ -4356,9 +4590,13 @@ func TestIgnoreOwnAnnouncement(t *testing.T) { // Now we exchange the proofs, the messages will be broadcasted to the // network. - err = mustProcess(t, tCtx.gossiper.ProcessLocalAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( batch.localProofAnn, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process local proof") select { @@ -4367,9 +4605,13 @@ func TestIgnoreOwnAnnouncement(t *testing.T) { case <-time.After(2 * trickleDelay): } - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.remoteProofAnn, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } require.NoError(t, err, "unable to process remote proof") for i := 0; i < 5; i++ { @@ -4382,9 +4624,13 @@ func TestIgnoreOwnAnnouncement(t *testing.T) { // Finally, we again check that we'll ignore the remote giving us // announcements about our own channel. - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.chanAnn, remotePeer, - )) + ): + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } if err == nil || !strings.Contains(err.Error(), "ignoring") { t.Fatalf("expected gossiper to ignore announcement, got: %v", err) } @@ -4415,18 +4661,26 @@ func TestRejectCacheChannelAnn(t *testing.T) { tCtx.router.queueValidationFail(chanID) // If we process the batch the first time we should get an error. - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.chanAnn, remotePeer, - )) - require.NotNil(t, err) + ): + require.NotNil(t, err) + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } // If we process it a *second* time, then we should get an error saying // we rejected it already. - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, batch.chanAnn, remotePeer, - )) - errStr := err.Error() - require.Contains(t, errStr, "recently rejected") + ): + errStr := err.Error() + require.Contains(t, errStr, "recently rejected") + case <-time.After(2 * time.Second): + t.Fatal("did not process remote announcement") + } } // TestFutureMsgCacheEviction checks that when the cache's capacity is reached, @@ -4491,10 +4745,15 @@ func TestChanAnnBanningNonChanPeer(t *testing.T) { ) require.NoError(t, err, "can't create channel announcement") - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, ca, nodePeer1, - )) - require.ErrorIs(t, err, ErrInvalidFundingOutput) + ): + require.ErrorIs(t, err, ErrInvalidFundingOutput) + + case <-time.After(2 * time.Second): + t.Fatalf("remote announcement not processed") + } } // The peer should be banned now. @@ -4510,21 +4769,26 @@ func TestChanAnnBanningNonChanPeer(t *testing.T) { ) require.NoError(t, err, "can't create channel announcement") - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, ca, nodePeer2, - )) - require.ErrorIs(t, err, ErrChannelSpent) + ): + + require.ErrorIs(t, err, ErrChannelSpent) + + case <-time.After(2 * time.Second): + t.Fatalf("remote announcement not processed") + } // Check that the announcement's scid is marked as closed. isClosed, err := tCtx.gossiper.cfg.ScidCloser.IsClosedScid( - ctx, ca.ShortChannelID, + ca.ShortChannelID, ) require.Nil(t, err) require.True(t, isClosed) // Remove the scid from the reject cache. key := newRejectCacheKey( - ca.GossipVersion(), ca.ShortChannelID.ToUint64(), sourceToPub(nodePeer2.IdentityKey()), ) @@ -4535,13 +4799,18 @@ func TestChanAnnBanningNonChanPeer(t *testing.T) { // as a zombie if any error occurs in the chanvalidate.Validate call. // For the sake of the rest of the test, however, we mark it as live // here. - _ = tCtx.router.MarkEdgeLive(lnwire.GossipVersion1, ca.ShortChannelID) + _ = tCtx.router.MarkEdgeLive(ca.ShortChannelID) - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, ca, nodePeer2, - )) - require.ErrorContains(t, err, "ignoring closed channel") + ): + require.ErrorContains(t, err, "ignoring closed channel") + + case <-time.After(2 * time.Second): + t.Fatalf("remote announcement not processed") + } } // TestChanAnnBanningChanPeer asserts that channel peers that are banned don't @@ -4566,11 +4835,15 @@ func TestChanAnnBanningChanPeer(t *testing.T) { ) require.NoError(t, err, "can't create channel announcement") - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( + select { + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( ctx, ca, nodePeer, - )) - require.ErrorIs(t, err, ErrInvalidFundingOutput) + ): + require.ErrorIs(t, err, ErrInvalidFundingOutput) + case <-time.After(2 * time.Second): + t.Fatalf("remote announcement not processed") + } } // The peer should be banned now. @@ -4627,13 +4900,13 @@ func assertChanChainRejection(t *testing.T, ctx *testCtx, t.Helper() nodePeer := &mockPeer{bitcoinKeyPub2, nil, nil, atomic.Bool{}} - errPromise := actor.NewPromise[error]() + errChan := make(chan error, 1) nMsg := &networkMsg{ - msg: edge, - isRemote: true, - peer: nodePeer, - source: nodePeer.IdentityKey(), - errPromise: errPromise, + msg: edge, + isRemote: true, + peer: nodePeer, + source: nodePeer.IdentityKey(), + err: errChan, } _, added := ctx.gossiper.handleChanAnnouncement( @@ -4641,8 +4914,12 @@ func assertChanChainRejection(t *testing.T, ctx *testCtx, ) require.False(t, added) - err := mustProcess(t, errPromise.Future()) - require.ErrorIs(t, err, expectedErr) + select { + case err := <-errChan: + require.ErrorIs(t, err, expectedErr) + case <-time.After(2 * time.Second): + t.Fatal("channel announcement not processed") + } // This channel should now be present in the zombie channel index. isZombie, err := ctx.router.IsZombieEdge(edge.ShortChannelID) @@ -4658,14 +4935,14 @@ func TestRecoverGossipPanic(t *testing.T) { testCases := []struct { name string - setupMsg func() (*networkMsg, actor.Future[error]) + setupMsg func() (*networkMsg, chan error) checkError bool }{ { name: "panic with full message context", - setupMsg: func() (*networkMsg, actor.Future[error]) { - promise := actor.NewPromise[error]() - nMsg := &networkMsg{ + setupMsg: func() (*networkMsg, chan error) { + errChan := make(chan error, 1) + return &networkMsg{ msg: &lnwire.ChannelUpdate1{ Timestamp: testTimestamp, }, @@ -4673,36 +4950,32 @@ func TestRecoverGossipPanic(t *testing.T) { remoteKeyPub1, nil, nil, atomic.Bool{}, }, - errPromise: promise, - } - - return nMsg, promise.Future() + err: errChan, + }, errChan }, checkError: true, }, { name: "panic with nil message", - setupMsg: func() (*networkMsg, actor.Future[error]) { - promise := actor.NewPromise[error]() - nMsg := &networkMsg{ - msg: nil, - peer: nil, - errPromise: promise, - } - - return nMsg, promise.Future() + setupMsg: func() (*networkMsg, chan error) { + errChan := make(chan error, 1) + return &networkMsg{ + msg: nil, + peer: nil, + err: errChan, + }, errChan }, checkError: true, }, { - name: "panic with nil error promise", - setupMsg: func() (*networkMsg, actor.Future[error]) { + name: "panic with nil error channel", + setupMsg: func() (*networkMsg, chan error) { return &networkMsg{ msg: &lnwire.ChannelUpdate1{ Timestamp: testTimestamp, }, - peer: nil, - errPromise: nil, + peer: nil, + err: nil, }, nil }, checkError: false, @@ -4754,10 +5027,18 @@ func TestRecoverGossipPanic(t *testing.T) { "error but errChan is nil") } if tc.checkError && errChan != nil { - err := mustProcess(t, errChan) - require.Error(t, err) - require.Contains(t, err.Error(), "panic while") - require.Contains(t, err.Error(), "test panic") + select { + case err := <-errChan: + require.Error(t, err) + require.Contains( + t, err.Error(), "panic while", + ) + require.Contains( + t, err.Error(), "test panic", + ) + case <-time.After(time.Second): + t.Fatal("timeout waiting for error") + } } }) } @@ -4772,13 +5053,13 @@ func TestRecoverGossipPanicBlockedErrorChannel(t *testing.T) { ctx, err := createTestCtx(t, proofMatureDelta, false) require.NoError(t, err) - // The Promise-based design means Complete() is always non-blocking, - // so panic recovery never hangs regardless of whether the caller - // awaits the result. + // Create an UNBUFFERED channel and don't read from it. + errChan := make(chan error) + nMsg := &networkMsg{ - msg: &lnwire.ChannelUpdate1{Timestamp: testTimestamp}, - peer: &mockPeer{remoteKeyPub1, nil, nil, atomic.Bool{}}, - errPromise: actor.NewPromise[error](), + msg: &lnwire.ChannelUpdate1{Timestamp: testTimestamp}, + peer: &mockPeer{remoteKeyPub1, nil, nil, atomic.Bool{}}, + err: errChan, } // Initialize a proper job so CompleteJob has a slot to return. @@ -4848,13 +5129,13 @@ func TestRecoverGossipPanicSignalsDependents(t *testing.T) { // Now simulate the parent job panicking and recovering. // The recovery should call SignalDependents. - errPromise := actor.NewPromise[error]() + errChan := make(chan error, 1) nMsg := &networkMsg{ msg: chanAnn, peer: &mockPeer{ remoteKeyPub1, nil, nil, atomic.Bool{}, }, - errPromise: errPromise, + err: errChan, } panicked := make(chan struct{}) @@ -4874,11 +5155,15 @@ func TestRecoverGossipPanicSignalsDependents(t *testing.T) { t.Fatal("timeout waiting for panic recovery") } - // Verify error was sent back on the parent's error promise. - err = mustProcess(t, errPromise.Future()) - require.Error(t, err) - require.Contains(t, err.Error(), "panic while") - require.Contains(t, err.Error(), "parent job panic") + // Verify error was sent back on the parent's error channel. + select { + case err := <-errChan: + require.Error(t, err) + require.Contains(t, err.Error(), "panic while") + require.Contains(t, err.Error(), "parent job panic") + case <-time.After(time.Second): + t.Fatal("timeout waiting for error on parent") + } // The child job should now be unblocked because SignalDependents // was called during panic recovery. @@ -4912,13 +5197,13 @@ func TestRecoverGossipPanicNilJobID(t *testing.T) { ShortChannelID: lnwire.NewShortChanIDFromInt(12345), } - errPromise := actor.NewPromise[error]() + errChan := make(chan error, 1) nMsg := &networkMsg{ msg: annSigs, peer: &mockPeer{ remoteKeyPub1, nil, nil, atomic.Bool{}, }, - errPromise: errPromise, + err: errChan, } // Call finalizeGossipProcessing with nil jobID (simulating the @@ -4941,10 +5226,14 @@ func TestRecoverGossipPanicNilJobID(t *testing.T) { } // Verify error was sent back. - err = mustProcess(t, errPromise.Future()) - require.Error(t, err) - require.Contains(t, err.Error(), "panic while") - require.Contains(t, err.Error(), "announce signatures panic") + select { + case err := <-errChan: + require.Error(t, err) + require.Contains(t, err.Error(), "panic while") + require.Contains(t, err.Error(), "announce signatures panic") + case <-time.After(time.Second): + t.Fatal("timeout waiting for error") + } } // TestGossiperShutdownWrongChainAnnouncement tests that the gossiper can shut @@ -5007,202 +5296,3 @@ func TestGossiperShutdownWrongChainAnnouncement(t *testing.T) { // is blocked trying to send to the error channel a second time. require.NoError(t, tCtx.gossiper.Stop()) } - -// TestGossipSyncerRace verifies that there is no race when the gossiper flushes -// a pending batch of new announcements to the network while concurrently -// processing a GossipTimestampRange message from a peer. -func TestGossipSyncerRace(t *testing.T) { - t.Parallel() - - tCtx, err := createTestCtx(t, 0, false) - require.NoError(t, err) - - nodePeer := &mockPeer{remoteKeyPriv1.PubKey(), nil, nil, atomic.Bool{}} - - // Connect the remote peer so it can send us a GossipTimestampRange - // message. - tCtx.gossiper.InitSyncState(nodePeer) - - errCh := make(chan error, 1) - - go func() { - // Wait for the trickle delay to elapse before sending the - // GossipTimestampRange message. - time.Sleep(trickleDelay) - - gossipTimestampRange := &lnwire.GossipTimestampRange{ - ChainHash: tCtx.gossiper.syncMgr.cfg.ChainHash, - FirstTimestamp: uint32(time.Now().Unix()), - TimestampRange: 3600, - } - - err := mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( - t.Context(), gossipTimestampRange, nodePeer, - )) - errCh <- err - }() - - // Send a channel announcement from the remote peer, which will be - // flushed to the network after the trickle delay. - ca, err := tCtx.createRemoteChannelAnnouncement(0) - require.NoError(t, err) - - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( - t.Context(), ca, nodePeer, - )) - require.NoError(t, err) - - // After the trickle delay, the channel announcement is flushed to the - // network. At the same time, the peer sends a GossipTimestampRange - // message, which could trigger a race. - select { - case <-tCtx.broadcastedMessage: - case <-time.After(2 * trickleDelay): - t.Fatal("announcement was not broadcast") - } - - // Ensure the goroutine completed successfully. - select { - case err := <-errCh: - require.NoError(t, err) - case <-time.After(2 * time.Second): - t.Fatal("timeout waiting for gossip message processing") - } -} - -// TestPrematureAnnouncementProcessing checks that a channel announcement -// carrying a future block height is correctly deferred via isPremature and -// then re-processed once the target block arrives. -func TestPrematureAnnouncementProcessing(t *testing.T) { - t.Parallel() - - // Start the gossiper at block height 100. - const startHeight = 100 - tCtx, err := createTestCtx(t, startHeight, false) - require.NoError(t, err) - - nodePeer := &mockPeer{remoteKeyPriv1.PubKey(), nil, nil, atomic.Bool{}} - - // Create a channel announcement at a future block height (200 > 100). - // The default fundingTxPrepTypeGood option pre-registers chain mock - // expectations for height 200, which will be consumed when the - // announcement is re-processed after the block arrives. - futureHeight := uint32(200) - prematureAnn, err := tCtx.createRemoteChannelAnnouncement(futureHeight) - require.NoError(t, err) - - // Submit the premature announcement. The gossiper should accept it - // immediately with a nil error (deferred to future block), not block. - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( - t.Context(), prematureAnn, nodePeer, - )) - require.NoError(t, err) - - // Advance the block height to 200. This triggers resendFutureMessages, - // which re-queues the cached announcement copy into the processing - // pipeline. - tCtx.notifier.notifyBlock(chainhash.Hash{}, futureHeight) - - // Wait for the announcement to be broadcast. This confirms the gossiper - // re-processed the deferred announcement and remains fully operational. - select { - case <-tCtx.broadcastedMessage: - case <-time.After(2 * trickleDelay): - t.Fatal("premature announcement was not " + - "broadcast after block height advanced") - } - - // Verify the gossiper is still live by processing a second normal - // announcement at the current block height. This would time out if - // the gossiper's networkHandler goroutine were blocked. - normalAnn, err := tCtx.createRemoteChannelAnnouncement(startHeight) - require.NoError(t, err) - - err = mustProcess(t, tCtx.gossiper.ProcessRemoteAnnouncement( - t.Context(), normalAnn, nodePeer, - )) - require.NoError(t, err) -} - -// TestProcessRemoteAnnouncementPeerQuit verifies that -// ProcessRemoteAnnouncement completes the returned future with ErrPeerQuitting -// when the peer's quit channel is closed before the message can be enqueued. -func TestProcessRemoteAnnouncementPeerQuit(t *testing.T) { - t.Parallel() - - // Construct a gossiper without starting it so that nobody reads from - // networkMsgs. This forces the send in the select to block, making the - // peer quit signal the only ready case. - gossiper := New(Config{ - ChainParams: &chaincfg.MainNetParams, - }, selfKeyDesc) - - // Create a peer whose quit channel is already closed. - quitChan := make(chan struct{}) - close(quitChan) - peer := &mockPeer{ - pk: remoteKeyPriv1.PubKey(), - quit: quitChan, - } - - f := gossiper.ProcessRemoteAnnouncement( - t.Context(), &lnwire.ChannelUpdate1{}, peer, - ) - - err := mustProcess(t, f) - require.ErrorIs(t, err, ErrPeerQuitting) -} - -// TestProcessRemoteAnnouncementCtxCancel verifies that -// ProcessRemoteAnnouncement completes the returned future with the context -// error when the context is cancelled before the message can be enqueued. -func TestProcessRemoteAnnouncementCtxCancel(t *testing.T) { - t.Parallel() - - gossiper := New(Config{ - ChainParams: &chaincfg.MainNetParams, - }, selfKeyDesc) - - peer := &mockPeer{ - pk: remoteKeyPriv1.PubKey(), - quit: make(chan struct{}), - } - - // Cancel the context before calling ProcessRemoteAnnouncement. - ctx, cancel := context.WithCancel(t.Context()) - cancel() - - f := gossiper.ProcessRemoteAnnouncement( - ctx, &lnwire.ChannelUpdate1{}, peer, - ) - - err := mustProcess(t, f) - require.ErrorIs(t, err, context.Canceled) -} - -// TestProcessRemoteAnnouncementGossiperQuit verifies that -// ProcessRemoteAnnouncement completes the returned future with -// ErrGossiperShuttingDown when the gossiper's quit channel is closed before -// the message can be enqueued. -func TestProcessRemoteAnnouncementGossiperQuit(t *testing.T) { - t.Parallel() - - gossiper := New(Config{ - ChainParams: &chaincfg.MainNetParams, - }, selfKeyDesc) - - // Close the gossiper's quit channel to simulate shutdown. - close(gossiper.quit) - - peer := &mockPeer{ - pk: remoteKeyPriv1.PubKey(), - quit: make(chan struct{}), - } - - f := gossiper.ProcessRemoteAnnouncement( - t.Context(), &lnwire.ChannelUpdate1{}, peer, - ) - - err := mustProcess(t, f) - require.ErrorIs(t, err, ErrGossiperShuttingDown) -} diff --git a/discovery/mock_test.go b/discovery/mock_test.go index ad0ff8cf7..6bd93c29b 100644 --- a/discovery/mock_test.go +++ b/discovery/mock_test.go @@ -1,14 +1,13 @@ package discovery import ( - "context" "errors" "net" "sync" "sync/atomic" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lnwire" ) @@ -177,9 +176,7 @@ func newMockScidCloser(channelPeer bool) *mockScidCloser { } } -func (m *mockScidCloser) PutClosedScid(_ context.Context, - scid lnwire.ShortChannelID) error { - +func (m *mockScidCloser) PutClosedScid(scid lnwire.ShortChannelID) error { m.Lock() m.m[scid] = struct{}{} m.Unlock() @@ -187,8 +184,8 @@ func (m *mockScidCloser) PutClosedScid(_ context.Context, return nil } -func (m *mockScidCloser) IsClosedScid(_ context.Context, - scid lnwire.ShortChannelID) (bool, error) { +func (m *mockScidCloser) IsClosedScid(scid lnwire.ShortChannelID) (bool, + error) { m.Lock() defer m.Unlock() diff --git a/discovery/sync_manager.go b/discovery/sync_manager.go index de071e8fa..56e81e64e 100644 --- a/discovery/sync_manager.go +++ b/discovery/sync_manager.go @@ -7,8 +7,7 @@ import ( "sync/atomic" "time" - "github.com/btcsuite/btcd/chainhash/v2" - graphdb "github.com/lightningnetwork/lnd/graph/db" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" @@ -130,9 +129,10 @@ type SyncManagerCfg struct { // PassiveSync. PinnedSyncers PinnedSyncers - // IsStillZombieChannel returns true if the channel described by info - // should still be considered a zombie. - IsStillZombieChannel func(graphdb.ChannelUpdateInfo) bool + // IsStillZombieChannel takes the timestamps of the latest channel + // updates for a channel and returns true if the channel should be + // considered a zombie based on these timestamps. + IsStillZombieChannel func(time.Time, time.Time) bool // AllotedMsgBytesPerSecond is the allotted bandwidth rate, expressed in // bytes/second that the gossip manager can consume. Once we exceed this diff --git a/discovery/syncer.go b/discovery/syncer.go index d1e5178ea..ce970eeef 100644 --- a/discovery/syncer.go +++ b/discovery/syncer.go @@ -7,19 +7,16 @@ import ( "iter" "math" "math/rand" - "slices" "sort" "sync" "sync/atomic" "time" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/lightningnetwork/lnd/actor" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/lnpeer" - "github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnwire" "golang.org/x/time/rate" ) @@ -172,10 +169,6 @@ const ( // the maximum number of replies allowed for zlib encoded replies. maxQueryChanRangeRepliesZlibFactor = 4 - // maxChanRangeReplySCIDs is the maximum number of short channel IDs - // we'll process for a single QueryChannelRange request. - maxChanRangeReplySCIDs = 100_000 - // chanRangeQueryBuffer is the number of blocks back that we'll go when // asking the remote peer for their any channels they know of beyond // our highest known channel ID. @@ -217,7 +210,7 @@ var ( // syncTransitionReq encapsulates a request for a gossip syncer sync transition. type syncTransitionReq struct { newSyncType SyncerType - errPromise actor.Promise[error] + errChan chan error } // historicalSyncReq encapsulates a request for a gossip syncer to perform a @@ -291,9 +284,10 @@ type gossipSyncerCfg struct { // for a single QueryChannelRange request. maxQueryChanRangeReplies uint32 - // isStillZombieChannel returns true if the channel described by info - // should still be considered a zombie. - isStillZombieChannel func(graphdb.ChannelUpdateInfo) bool + // isStillZombieChannel takes the timestamps of the latest channel + // updates for a channel and returns true if the channel should be + // considered a zombie based on these timestamps. + isStillZombieChannel func(time.Time, time.Time) bool // timestampQueueSize is the size of the timestamp range queue. If not // set, defaults to the global timestampQueueSize constant. @@ -384,10 +378,6 @@ type GossipSyncer struct { // within the waitingQueryChanReply state. numChanRangeRepliesRcvd uint32 - // numChanRangeReplySCIDsRcvd tracks the total number of short channel - // IDs received as part of a QueryChannelRange response. - numChanRangeReplySCIDsRcvd uint32 - // newChansToQuery is used to pass the set of channels we should query // for from the waitingQueryChanReply state to the queryNewChannels // state. @@ -710,10 +700,7 @@ func (g *GossipSyncer) channelGraphSyncer(ctx context.Context) { case syncerIdle: select { case req := <-g.syncTransitionReqs: - completeGossipResult( - req.errPromise, - g.handleSyncTransition(ctx, req), - ) + req.errChan <- g.handleSyncTransition(ctx, req) case req := <-g.historicalSyncReqs: g.handleHistoricalSync(req) @@ -929,41 +916,9 @@ func isLegacyReplyChannelRange(query *lnwire.QueryChannelRange, // processChanRangeReply is called each time the GossipSyncer receives a new // reply to the initial range query to discover new channels that it didn't // previously know of. -func (g *GossipSyncer) processChanRangeReply(ctx context.Context, +func (g *GossipSyncer) processChanRangeReply(_ context.Context, msg *lnwire.ReplyChannelRange) error { - // Any error here terminates the range sync, so we release whatever we - // accumulated to stop the peer from pinning it by deliberately forcing - // an error. Our caller exits the state machine on any error we return, - // and nothing prunes a syncer until its peer disconnects, so otherwise - // the buffer stays reachable from a syncer that will never run again. - err := g.bufferChanRangeReply(ctx, msg) - if err != nil { - g.resetChanRangeReplyState() - } - - return err -} - -// bufferChanRangeReply validates a single ReplyChannelRange against the query -// that prompted it, buffers the channels it announces, and advances the -// syncer's state once the reply stream is complete. -func (g *GossipSyncer) bufferChanRangeReply(_ context.Context, - msg *lnwire.ReplyChannelRange) error { - - // A reply only means anything in the context of the query that - // prompted it, and every check below reads that query. Today this is - // unreachable, as we only accept a reply in waitingQueryRangeReply and - // we always set the query before entering that state. It is worth - // guarding anyway: an error leaves the syncer sitting in - // waitingQueryRangeReply with the query cleared, so any future change - // that recovers the handler instead of tearing it down would turn this - // into a remote panic. - if g.curQueryRangeMsg == nil { - return fmt.Errorf("received channel range reply without an " + - "active query") - } - // isStale returns whether the timestamp is too far into the past. isStale := func(timestamp time.Time) bool { return time.Since(timestamp) > graph.DefaultChannelPruneExpiry @@ -1016,79 +971,42 @@ func (g *GossipSyncer) bufferChanRangeReply(_ context.Context, } } - // Charge the reply budget using the encoding that was actually - // received. The configured encoding is a local preference and does - // not describe the responder's message. - var replyCount uint32 - switch msg.EncodingType { - case lnwire.EncodingSortedPlain: - replyCount = 1 - - case lnwire.EncodingSortedZlib: - replyCount = maxQueryChanRangeRepliesZlibFactor - - default: - return fmt.Errorf( - "unhandled encoding type %v", msg.EncodingType, - ) - } - - numReplySCIDs := uint32(len(msg.ShortChanIDs)) - if g.numChanRangeReplySCIDsRcvd > maxChanRangeReplySCIDs || - numReplySCIDs > maxChanRangeReplySCIDs- - g.numChanRangeReplySCIDsRcvd { - - return fmt.Errorf("channel range reply exceeds maximum "+ - "number of short channel IDs: max=%v", - maxChanRangeReplySCIDs) - } - - g.numChanRangeRepliesRcvd += replyCount - g.numChanRangeReplySCIDsRcvd += numReplySCIDs g.prevReplyChannelRange = msg - // Reserve room for this reply in one shot instead of letting append - // grow the buffer an element at a time. Over a full reply stream this - // cuts the number of reallocations by about 3x. - g.bufferedChanRangeReplies = slices.Grow( - g.bufferedChanRangeReplies, int(numReplySCIDs), - ) - for i, scid := range msg.ShortChanIDs { - info := graphdb.NewV1ChannelUpdateInfo( + info := graphdb.NewChannelUpdateInfo( scid, time.Time{}, time.Time{}, ) if len(msg.Timestamps) != 0 { - info.Node1Freshness = lnwire.UnixTimestamp( - msg.Timestamps[i].Timestamp1, - ) - - info.Node2Freshness = lnwire.UnixTimestamp( - msg.Timestamps[i].Timestamp2, - ) - t1 := time.Unix(int64(msg.Timestamps[i].Timestamp1), 0) + info.Node1UpdateTimestamp = t1 + t2 := time.Unix(int64(msg.Timestamps[i].Timestamp2), 0) + info.Node2UpdateTimestamp = t2 // Sort out all channels with outdated or skewed // timestamps. Both timestamps need to be out of // boundaries for us to skip the channel and not query // it later on. switch { - case isStale(t1) && isStale(t2): + case isStale(info.Node1UpdateTimestamp) && + isStale(info.Node2UpdateTimestamp): continue - case isSkewed(t1) && isSkewed(t2): + case isSkewed(info.Node1UpdateTimestamp) && + isSkewed(info.Node2UpdateTimestamp): continue - case isStale(t1) && isSkewed(t2): + case isStale(info.Node1UpdateTimestamp) && + isSkewed(info.Node2UpdateTimestamp): continue - case isStale(t2) && isSkewed(t1): + case isStale(info.Node2UpdateTimestamp) && + isSkewed(info.Node1UpdateTimestamp): continue } @@ -1099,6 +1017,15 @@ func (g *GossipSyncer) bufferChanRangeReply(_ context.Context, ) } + switch g.cfg.encodingType { + case lnwire.EncodingSortedPlain: + g.numChanRangeRepliesRcvd++ + case lnwire.EncodingSortedZlib: + g.numChanRangeRepliesRcvd += maxQueryChanRangeRepliesZlibFactor + default: + return fmt.Errorf("unhandled encoding type %v", g.cfg.encodingType) + } + log.Infof("GossipSyncer(%x): buffering chan range reply of size=%v", g.cfg.peerPub[:], len(msg.ShortChanIDs)) @@ -1145,7 +1072,10 @@ func (g *GossipSyncer) bufferChanRangeReply(_ context.Context, // As we've received the entirety of the reply, we no longer need to // hold on to the set of buffered replies or the original query that // prompted the replies, so we'll let that be garbage collected now. - g.resetChanRangeReplyState() + g.curQueryRangeMsg = nil + g.prevReplyChannelRange = nil + g.bufferedChanRangeReplies = nil + g.numChanRangeRepliesRcvd = 0 // If there aren't any channels that we don't know of, then we can // switch straight to our terminal state. @@ -1173,16 +1103,6 @@ func (g *GossipSyncer) bufferChanRangeReply(_ context.Context, return nil } -// resetChanRangeReplyState releases all state accumulated while processing a -// ReplyChannelRange stream. -func (g *GossipSyncer) resetChanRangeReplyState() { - g.curQueryRangeMsg = nil - g.prevReplyChannelRange = nil - g.bufferedChanRangeReplies = nil - g.numChanRangeRepliesRcvd = 0 - g.numChanRangeReplySCIDsRcvd = 0 -} - // genChanRangeQuery generates the initial message we'll send to the remote // party when we're kicking off the channel graph synchronization upon // connection. The historicalQuery boolean can be used to generate a query from @@ -1348,11 +1268,11 @@ func (g *GossipSyncer) replyChanRangeQuery(ctx context.Context, } timestamps[i].Timestamp1 = uint32( - info.Node1FreshnessTime().Unix(), + info.Node1UpdateTimestamp.Unix(), ) timestamps[i].Timestamp2 = uint32( - info.Node2FreshnessTime().Unix(), + info.Node2UpdateTimestamp.Unix(), ) } @@ -1555,7 +1475,7 @@ func (g *GossipSyncer) ApplyGossipFilter(ctx context.Context, // Now that the remote peer has applied their filter, we'll query the // database for all the messages that are beyond this filter. newUpdatestoSend := g.cfg.channelSeries.UpdatesInHorizon( - ctx, startTime, endTime, + g.cfg.chainHash, startTime, endTime, ) // Create a pull-based iterator so we can check if there are any @@ -1656,13 +1576,9 @@ func (g *GossipSyncer) ApplyGossipFilter(ctx context.Context, func (g *GossipSyncer) FilterGossipMsgs(ctx context.Context, msgs ...msgWithSenders) { - g.Lock() - filter := g.remoteUpdateHorizon - g.Unlock() - // If the peer doesn't have an update horizon set, then we won't send // it any new update messages. - if filter == nil { + if g.remoteUpdateHorizon == nil { log.Tracef("GossipSyncer(%x): skipped due to nil "+ "remoteUpdateHorizon", g.cfg.peerPub[:]) return @@ -1703,10 +1619,12 @@ func (g *GossipSyncer) FilterGossipMsgs(ctx context.Context, // We'll construct a helper function that we'll us below to determine // if a given messages passes the gossip msg filter. - startTime := time.Unix(int64(filter.FirstTimestamp), 0) + g.Lock() + startTime := time.Unix(int64(g.remoteUpdateHorizon.FirstTimestamp), 0) endTime := startTime.Add( - time.Duration(filter.TimestampRange) * time.Second, + time.Duration(g.remoteUpdateHorizon.TimestampRange) * time.Second, ) + g.Unlock() passesFilter := func(timeStamp uint32) bool { t := time.Unix(int64(timeStamp), 0) @@ -1856,12 +1774,11 @@ func (g *GossipSyncer) ResetSyncedSignal() chan struct{} { // NOTE: This can only be done once the gossip syncer has reached its final // chansSynced state. func (g *GossipSyncer) ProcessSyncTransition(newSyncType SyncerType) error { - promise := actor.NewPromise[error]() - + errChan := make(chan error, 1) select { case g.syncTransitionReqs <- &syncTransitionReq{ newSyncType: newSyncType, - errPromise: promise, + errChan: errChan, }: case <-time.After(syncTransitionTimeout): return ErrSyncTransitionTimeout @@ -1869,25 +1786,12 @@ func (g *GossipSyncer) ProcessSyncTransition(newSyncType SyncerType) error { return ErrGossipSyncerExiting } - // Derive a context from the syncer's quit channel so the await exits - // only when the syncer itself shuts down. This matches the prior - // errChan-based behavior, which had no upper bound on the time spent - // waiting for the syncer to process the transition request once it had - // been accepted onto the queue. The syncTransitionTimeout above bounds - // only the enqueue step, as it did before this migration. - quitCtx, quitCancel := lnutils.ContextFromQuit(g.cg.Done()) - defer quitCancel() - - err := AwaitGossipResult(quitCtx, promise.Future()) - - // Re-map the bridge context cancellation back to the historical - // sentinel so any caller (or third-party fork) using errors.Is to - // detect syncer shutdown continues to match. - if errors.Is(err, context.Canceled) { + select { + case err := <-errChan: + return err + case <-g.cg.Done(): return ErrGossipSyncerExiting } - - return err } // handleSyncTransition handles a new sync type transition request. diff --git a/discovery/syncer_queue_test.go b/discovery/syncer_queue_test.go index 5dee661c4..704328cd1 100644 --- a/discovery/syncer_queue_test.go +++ b/discovery/syncer_queue_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" diff --git a/discovery/syncer_test.go b/discovery/syncer_test.go index da48e6170..2313d1c1d 100644 --- a/discovery/syncer_test.go +++ b/discovery/syncer_test.go @@ -12,8 +12,8 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/davecgh/go-spew/spew" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/lnpeer" @@ -31,6 +31,7 @@ var ( ) type horizonQuery struct { + chain chainhash.Hash start time.Time end time.Time } @@ -86,12 +87,12 @@ func (m *mockChannelGraphTimeSeries) HighestChanID(_ context.Context, return &m.highestID, nil } -func (m *mockChannelGraphTimeSeries) UpdatesInHorizon(_ context.Context, +func (m *mockChannelGraphTimeSeries) UpdatesInHorizon(chain chainhash.Hash, startTime, endTime time.Time) iter.Seq2[lnwire.Message, error] { return func(yield func(lnwire.Message, error) bool) { m.horizonReq <- horizonQuery{ - startTime, endTime, + chain, startTime, endTime, } // We'll get the response from the channel, then yield it @@ -107,7 +108,7 @@ func (m *mockChannelGraphTimeSeries) UpdatesInHorizon(_ context.Context, func (m *mockChannelGraphTimeSeries) FilterKnownChanIDs(chain chainhash.Hash, superSet []graphdb.ChannelUpdateInfo, - isZombieChan func(graphdb.ChannelUpdateInfo) bool) ( + isZombieChan func(time.Time, time.Time) bool) ( []lnwire.ShortChannelID, error) { m.filterReq <- superSet @@ -2296,6 +2297,7 @@ func TestGossipSyncerSyncTransitions(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -2320,8 +2322,7 @@ func TestGossipSyncerSyncTransitions(t *testing.T) { syncer.Start() defer syncer.Stop() - err := syncer.ProcessSyncTransition(test.finalSyncType) - require.NoError(t, err) + syncer.ProcessSyncTransition(test.finalSyncType) // The syncer should now have the expected final // SyncerType that the test expects. @@ -2339,62 +2340,6 @@ func TestGossipSyncerSyncTransitions(t *testing.T) { } } -// TestProcessSyncTransitionShutdown asserts that ProcessSyncTransition -// surfaces a syncer shutdown that occurs while it is awaiting the syncer's -// reply as the historical ErrGossipSyncerExiting sentinel, rather than the -// raw context.Canceled error from the bridge context. This locks in the -// pre-actor.Future error contract for callers using errors.Is to detect -// shutdown. -func TestProcessSyncTransitionShutdown(t *testing.T) { - t.Parallel() - - // Spin up a syncer that is in chansSynced so it is willing to accept - // a transition request, but DON'T call Start so the syncer's - // channelGraphSyncer goroutine will never drain syncTransitionReqs. - // This deterministically forces ProcessSyncTransition into the await - // path with no chance of the request being processed before we close - // the syncer's quit channel. - _, syncer, _ := newTestSyncer( - lnwire.ShortChannelID{BlockHeight: latestKnownHeight}, - defaultEncoding, defaultChunkSize, - ) - syncer.setSyncState(chansSynced) - syncer.setSyncType(PassiveSync) - - // Buffer the request channel so the enqueue select succeeds without - // any consumer present, mirroring how the gossip syncer is wired in - // production (syncTransitionReqs is unbuffered there, but here we - // only need the enqueue arm to win). - syncer.syncTransitionReqs = make(chan *syncTransitionReq, 1) - - errCh := make(chan error, 1) - go func() { - errCh <- syncer.ProcessSyncTransition(ActiveSync) - }() - - // Give the goroutine a moment to enqueue the request and enter the - // await path. We deliberately wait longer than syncTransitionTimeout - // to prove the await is no longer bounded by it. - select { - case err := <-errCh: - t.Fatalf("ProcessSyncTransition returned early before "+ - "shutdown: %v", err) - case <-time.After(syncTransitionTimeout + 100*time.Millisecond): - } - - // Now signal the syncer's quit and assert that the await unblocks - // with the historical sentinel. - syncer.cg.Quit() - - select { - case err := <-errCh: - require.ErrorIs(t, err, ErrGossipSyncerExiting) - case <-time.After(time.Second): - t.Fatal("ProcessSyncTransition did not return after syncer " + - "shutdown") - } -} - // TestGossipSyncerHistoricalSync tests that a gossip syncer can perform a // historical sync with the remote peer. func TestGossipSyncerHistoricalSync(t *testing.T) { @@ -2570,183 +2515,6 @@ func TestGossipSyncerMaxChannelRangeReplies(t *testing.T) { }, nil)) } -// TestGossipSyncerMaxChannelRangeSCIDs ensures that a gossip syncer rejects a -// range response once the aggregate number of short channel IDs exceeds its -// resource limit. -func TestGossipSyncerMaxChannelRangeSCIDs(t *testing.T) { - t.Parallel() - ctx := t.Context() - - _, syncer, _ := newTestSyncer( - lnwire.ShortChannelID{BlockHeight: latestKnownHeight}, - defaultEncoding, defaultChunkSize, - ) - - query, err := syncer.genChanRangeQuery(ctx, true) - require.NoError(t, err) - - scids := make([]lnwire.ShortChannelID, defaultChunkSize) - for i := range scids { - scids[i] = lnwire.NewShortChanIDFromInt(uint64(i)) - } - - reply := &lnwire.ReplyChannelRange{ - ChainHash: query.ChainHash, - FirstBlockHeight: query.FirstBlockHeight, - NumBlocks: query.NumBlocks, - EncodingType: lnwire.EncodingSortedPlain, - ShortChanIDs: scids, - } - - numFullReplies := maxChanRangeReplySCIDs / len(scids) - for i := 0; i < numFullReplies; i++ { - require.NoError(t, syncer.processChanRangeReply(ctx, reply)) - } - - require.Len( - t, syncer.bufferedChanRangeReplies, - numFullReplies*len(scids), - ) - - numRemaining := maxChanRangeReplySCIDs - - numFullReplies*len(scids) - reply.ShortChanIDs = scids[:numRemaining] - require.NoError(t, syncer.processChanRangeReply(ctx, reply)) - require.Len( - t, syncer.bufferedChanRangeReplies, - maxChanRangeReplySCIDs, - ) - - reply.ShortChanIDs = []lnwire.ShortChannelID{ - lnwire.NewShortChanIDFromInt(uint64(len(scids))), - } - err = syncer.processChanRangeReply(ctx, reply) - require.ErrorContains( - t, err, "exceeds maximum number of short channel IDs", - ) - require.Empty(t, syncer.bufferedChanRangeReplies) - require.Zero(t, syncer.numChanRangeReplySCIDsRcvd) - require.Nil(t, syncer.curQueryRangeMsg) -} - -// TestGossipSyncerChanRangeReplyNoQuery ensures that a range reply which -// arrives without an active query is rejected rather than dereferencing the -// nil query. -func TestGossipSyncerChanRangeReplyNoQuery(t *testing.T) { - t.Parallel() - ctx := t.Context() - - _, syncer, _ := newTestSyncer( - lnwire.ShortChannelID{BlockHeight: latestKnownHeight}, - defaultEncoding, defaultChunkSize, - ) - - // Note that we deliberately skip genChanRangeQuery here, so - // curQueryRangeMsg is still nil. - require.Nil(t, syncer.curQueryRangeMsg) - - err := syncer.processChanRangeReply(ctx, &lnwire.ReplyChannelRange{ - FirstBlockHeight: 0, - NumBlocks: 100, - EncodingType: lnwire.EncodingSortedPlain, - ShortChanIDs: []lnwire.ShortChannelID{ - lnwire.NewShortChanIDFromInt(1), - }, - }) - require.ErrorContains(t, err, "without an active query") -} - -// TestGossipSyncerCountsReceivedEncoding ensures that compressed range -// replies consume the larger reply budget even when the local syncer uses -// plain encoding. -func TestGossipSyncerCountsReceivedEncoding(t *testing.T) { - t.Parallel() - ctx := t.Context() - - _, syncer, _ := newTestSyncer( - lnwire.ShortChannelID{BlockHeight: latestKnownHeight}, - defaultEncoding, defaultChunkSize, - ) - - query, err := syncer.genChanRangeQuery(ctx, true) - require.NoError(t, err) - - reply := &lnwire.ReplyChannelRange{ - ChainHash: query.ChainHash, - FirstBlockHeight: query.FirstBlockHeight, - NumBlocks: query.NumBlocks, - EncodingType: lnwire.EncodingSortedZlib, - } - require.NoError(t, syncer.processChanRangeReply(ctx, reply)) - require.Equal( - t, uint32(maxQueryChanRangeRepliesZlibFactor), - syncer.numChanRangeRepliesRcvd, - ) -} - -// deliverOverBudgetRangeReply waits for the syncer to send its initial range -// query, then answers it with a single reply that overruns the aggregate SCID -// budget. Sending the query is what populates curQueryRangeMsg and moves the -// syncer into waitingQueryRangeReply, both of which ProcessQueryMsg requires. -func deliverOverBudgetRangeReply(t *testing.T, syncer *GossipSyncer, - msgChan chan []lnwire.Message) { - - t.Helper() - - var query *lnwire.QueryChannelRange - select { - case msgs := <-msgChan: - require.Len(t, msgs, 1) - - q, ok := msgs[0].(*lnwire.QueryChannelRange) - require.True(t, ok) - query = q - - case <-time.After(time.Second): - t.Fatal("expected query channel range request msg") - } - - scids := make([]lnwire.ShortChannelID, maxChanRangeReplySCIDs+1) - for i := range scids { - scids[i] = lnwire.NewShortChanIDFromInt(uint64(i)) - } - - // Complete is set so that, absent the budget check, this reply would be - // taken as the final one and carry on to the completion path. That is - // what lets assertRangeSyncAborted tell the two apart. - reply := &lnwire.ReplyChannelRange{ - ChainHash: query.ChainHash, - FirstBlockHeight: query.FirstBlockHeight, - NumBlocks: query.NumBlocks, - Complete: 1, - EncodingType: lnwire.EncodingSortedPlain, - ShortChanIDs: scids, - } - require.NoError(t, syncer.ProcessQueryMsg(reply, nil)) -} - -// assertRangeSyncAborted asserts that the syncer bailed out of its range sync -// rather than treating the reply stream as complete. Reaching the completion -// path would filter the buffered SCIDs against our local graph, so the absence -// of that request is what tells us the sync was torn down instead. -// -// NOTE: we cannot instead wait on the syncer's wait group, as ContextGuard -// holds a reference on it until the syncer is signalled to quit. -func assertRangeSyncAborted(t *testing.T, syncer *GossipSyncer) { - t.Helper() - - series, ok := syncer.cfg.channelSeries.(*mockChannelGraphTimeSeries) - require.True(t, ok) - - select { - case <-series.filterReq: - t.Fatal("syncer treated an over-budget reply stream as a " + - "completed response") - - default: - } -} - // TestGossipSyncerStateHandlerErrors tests that errors in state handlers cause // the channelGraphSyncer goroutine to exit cleanly without endless retry loops. // This is a table-driven test covering various error types and states. @@ -2759,16 +2527,6 @@ func TestGossipSyncerStateHandlerErrors(t *testing.T) { setupState func(*GossipSyncer) chunkSize int32 injectedErr error - - // deliverMsg, if set, is run after the syncer has been started - // and is used to drive the syncer into an error through the - // public message path rather than through sendMsg injection. - deliverMsg func(*testing.T, *GossipSyncer, - chan []lnwire.Message) - - // assertOutcome, if set, asserts the terminal state the syncer - // is left in once its goroutine has stopped. - assertOutcome func(*testing.T, *GossipSyncer) }{ { name: "context cancel during syncingChans", @@ -2809,50 +2567,16 @@ func TestGossipSyncerStateHandlerErrors(t *testing.T) { } }, }, - { - // Unlike the cases above, this one drives the error in - // through ProcessQueryMsg so that we exercise the - // syncer's lifecycle rather than calling - // processChanRangeReply directly. The syncer starts in - // syncingChans and moves itself into - // waitingQueryRangeReply once it has sent its query. - name: "SCID budget exceeded while waiting", - state: syncingChans, - chunkSize: defaultChunkSize, - injectedErr: nil, - setupState: func(s *GossipSyncer) {}, - deliverMsg: deliverOverBudgetRangeReply, - assertOutcome: func(t *testing.T, s *GossipSyncer) { - // The budget check must abort the sync rather - // than let the partial stream be taken as a - // completed response. - // - // NOTE: the release of the buffered reply - // state is asserted by - // TestGossipSyncerMaxChannelRangeSCIDs, which - // can read those fields directly without - // racing the syncer's own goroutine. - assertRangeSyncAborted(t, s) - - // NOTE: the syncer is left in - // waitingQueryRangeReply with no live handler. - // That matches how every other terminal error - // in this state machine behaves today. - require.Equal( - t, waitingQueryRangeReply, - s.syncState(), - ) - }, - }, } for _, tt := range tests { + tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() // Create syncer with error injection capability. hID := lnwire.NewShortChanIDFromInt(10) - syncer, errInj, msgChan := newErrorInjectingSyncer( + syncer, errInj, _ := newErrorInjectingSyncer( hID, tt.chunkSize, ) @@ -2868,12 +2592,6 @@ func TestGossipSyncerStateHandlerErrors(t *testing.T) { // goroutine. syncer.Start() - // If this case drives its error in over the wire, do - // so now that the goroutine is running. - if tt.deliverMsg != nil { - tt.deliverMsg(t, syncer, msgChan) - } - // Wait long enough that an endless loop would // accumulate many attempts. With the fix, we should // only see 1-3 attempts. Without the fix, we'd see @@ -2895,12 +2613,6 @@ func TestGossipSyncerStateHandlerErrors(t *testing.T) { attemptCount, ) - // Verify the terminal state, if this case cares about - // it, before we signal the syncer to quit. - if tt.assertOutcome != nil { - tt.assertOutcome(t, syncer) - } - // Verify the syncer exits cleanly without hanging. assertSyncerExitsCleanly(t, syncer, 2*time.Second) }) diff --git a/docker/README.md b/docker/README.md index 6c4dfad46..b2f1d1efc 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,16 +1,18 @@ -This document is intended for those looking to get started with -the Lightning Network Daemon (lnd). This folder uses docker to package lnd and -btcd together, making the deployment of both daemons as simple as running a -few commands. All configuration between lnd and btcd is handled automatically -by the docker-compose.yml file +This document is written for people who are eager to do something with +the Lightning Network Daemon (`lnd`). This folder uses `docker-compose` to +package `lnd` and `btcd` together to make deploying the two daemons as easy as +typing a few commands. All configuration between `lnd` and `btcd` are handled +automatically by their `docker-compose` config file. ### Prerequisites Name | Version --------|--------- -docker | 20.10.13+ +docker-compose | 1.9.0 +docker | 1.13.0 ### Table of content * [Create lightning network cluster](#create-lightning-network-cluster) + * [Connect to faucet lightning node](#connect-to-faucet-lightning-node) * [Building standalone docker images](#building-standalone-docker-images) * [Using bitcoind version](#using-bitcoind-version) * [Start Bitcoin Node with bitcoind using Docker Compose](#start-bitcoin-node-with-bitcoind-using-docker-compose) @@ -28,6 +30,10 @@ possible to spin up an arbitrary number of `lnd` instances within containers to create a mini development cluster. All state is saved between instances using a shared volume. +Current workflow is big because we recreate the whole network by ourselves, +next versions will use the started `btcd` bitcoin node in `testnet` and +`faucet` wallet from which you will get the bitcoins. + In the workflow below, we describe the steps required to recreate the following topology, and send a payment from `Alice` to `Bob`. ```text @@ -56,11 +62,6 @@ topology, and send a payment from `Alice` to `Bob`. * Close the channel between `Alice` and `Bob`. * Check that on-chain `Bob` balance was changed. -> [!IMPORTANT] -> **Prerequisites:** This guide assumes you have **Docker** installed. If not, please follow the [official Docker installation guide](https://docs.docker.com/get-docker/). -> ->All commands should be executed from the `lnd/docker/` directory. Depending on your system's configuration, you may need to prefix **docker** commands with **sudo**. For `Linux` users, we highly recommend following the official [Docker documentation to manage Docker as a non-root](https://docs.docker.com/engine/install/linux-postinstall/) user, which allows you to run commands without sudo safely - Start `btcd`, and then create an address for `Alice` that we'll directly mine bitcoin into. ```shell @@ -72,19 +73,22 @@ $ docker volume create simnet_lnd_alice $ docker volume create simnet_lnd_bob # Run the "Alice" container and log into it: -$ docker compose run -d --name alice --volume simnet_lnd_alice:/root/.lnd lnd -$ docker exec -it alice bash +$ docker-compose run -d --name alice --volume simnet_lnd_alice:/root/.lnd lnd +$ docker exec -i -t alice bash -# Generate a new native SegWit (Bech32) address for Alice: -alice $ lncli --network=simnet newaddress p2wkh +# Generate a new backward compatible nested p2sh address for Alice: +alice $ lncli --network=simnet newaddress np2wkh # Recreate "btcd" node and set Alice's address as mining address: $ export MINING_ADDRESS= -$ docker compose up -d btcd +$ docker-compose up -d btcd # Generate 400 blocks (we need at least "100 >=" blocks because of coinbase # block maturity and "300 ~=" in order to activate segwit): $ docker exec -it btcd /start-btcctl.sh generate 400 + +# Check that segwit is active: +$ docker exec -it btcd /start-btcctl.sh getblockchaininfo | grep -A 1 segwit ``` Check `Alice` balance: @@ -96,8 +100,8 @@ Connect `Bob` node to `Alice` node. ```shell # Run "Bob" node and log into it: -$ docker compose run -d --name bob --volume simnet_lnd_bob:/root/.lnd lnd -$ docker exec -it bob bash +$ docker-compose run -d --name bob --volume simnet_lnd_bob:/root/.lnd lnd +$ docker exec -i -t bob bash # Get the identity pubkey of "Bob" node: bob $ lncli --network=simnet getinfo @@ -263,6 +267,66 @@ bob $ lncli --network=simnet walletbalance } ``` +### Connect to faucet lightning node +In order to be more confident with `lnd` commands I suggest you to try +to create a mini lightning network cluster ([Create lightning network cluster](#create-lightning-network-cluster)). + +In this section we will try to connect our node to the faucet/hub node +which we will create a channel with and send some amount of +bitcoins. The schema will be following: + +```text ++ ----- + + ------ + (1) + --- + +| Alice | <--- channel ---> | Faucet | <--- channel ---> | Bob | ++ ----- + + ------ + + --- + + | | | + | | | <--- (2) + + - - - - - - - - - - - - - + - - - - - - - - - - - - - + + | + + --------------- + + | Bitcoin network | <--- (3) + + --------------- + + + + (1) You may connect an additional node "Bob" and make the multihop + payment Alice->Faucet->Bob + + (2) "Faucet", "Alice" and "Bob" are the lightning network daemons which + create channels to interact with each other using the Bitcoin network + as source of truth. + + (3) In current scenario "Alice" and "Faucet" lightning network nodes + connect to different Bitcoin nodes. If you decide to connect "Bob" + to "Faucet" then the already created "btcd" node would be sufficient. +``` + +First you need to run `btcd` node in `testnet` and wait for it to be +synced with test network (`May the Force and Patience be with you`). +```shell +# Init bitcoin network env variable: +$ NETWORK="testnet" docker-compose up +``` + +After `btcd` synced, connect `Alice` to the `Faucet` node. + +The `Faucet` node address can be found at the [Faucet Lightning Community webpage](https://faucet.lightning.community). + +```shell +# Run "Alice" container and log into it: +$ docker-compose run -d --name alice lnd_btc; docker exec -i -t "alice" bash + +# Connect "Alice" to the "Faucet" node: +alice $ lncli --network=testnet connect @ +``` + +After a connection is achieved, the `Faucet` node should create the channel +and send some amount of bitcoins to `Alice`. + +**What you may do next?:** +- Send some amount to `Faucet` node back. +- Connect `Bob` node to the `Faucet` and make multihop payment (`Alice->Faucet->Bob`) +- Close channel with `Faucet` and check the onchain balance. + ### Building standalone docker images Instructions on how to build standalone docker images (for development or @@ -275,7 +339,7 @@ If you are using the bitcoind version of the compose file i.e. `docker-compose-b #### Start Bitcoin Node with bitcoind using Docker Compose To launch the Bitcoin node using bitcoind in the regtest network using Docker Compose, use the following command: ```shell -$ NETWORK="regtest" docker compose -f docker-compose-bitcoind.yml up +$ NETWORK="regtest" docker-compose -f docker-compose-bitcoind.yml up ``` #### Generating RPCAUTH @@ -313,5 +377,5 @@ Note: The address `2N1NQzFjCy1NnpAH3cT4h4GoByrAAkiH7zu` is just a random example * How to see `alice` | `bob` | `btcd` | `lnd` | `bitcoind` logs? ```shell -$ docker compose logs +$ docker-compose logs ``` diff --git a/docker/btcd/Dockerfile b/docker/btcd/Dockerfile index 9b0575877..97ab32d36 100644 --- a/docker/btcd/Dockerfile +++ b/docker/btcd/Dockerfile @@ -1,6 +1,6 @@ # If you change this please also update GO_VERSION in Makefile (then run # `make lint` to see where else it needs to be updated as well). -FROM golang:1.26.4-alpine AS builder +FROM golang:1.25.5-alpine as builder LABEL maintainer="Olaoluwa Osuntokun " @@ -10,7 +10,7 @@ RUN apk add --no-cache git gcc musl-dev WORKDIR $GOPATH/src/github.com/btcsuite/btcd # Pin down btcd to a version that we know works with lnd. -ARG BTCD_VERSION=v0.25.0 +ARG BTCD_VERSION=v0.23.4 # Grab and install the latest version of of btcd and all related dependencies. RUN git clone https://github.com/btcsuite/btcd.git . \ @@ -18,7 +18,7 @@ RUN git clone https://github.com/btcsuite/btcd.git . \ && go install -v . ./cmd/... # Start a new image -FROM alpine:3.22 AS final +FROM alpine as final # Expose mainnet ports (server, rpc) EXPOSE 8333 8334 @@ -29,7 +29,7 @@ EXPOSE 18333 18334 # Expose simnet ports (server, rpc) EXPOSE 18555 18556 -# Expose signet ports (server, rpc) +# Expose segnet ports (server, rpc) EXPOSE 28901 28902 # Copy the compiled binaries from the builder image. @@ -45,13 +45,13 @@ COPY "start-btcd.sh" . RUN apk add --no-cache \ bash \ ca-certificates \ - && mkdir "/rpc" "/root/.btcd" "/root/.btcctl" \ - && touch "/root/.btcd/btcd.conf" \ - && chmod +x start-btcctl.sh \ - && chmod +x start-btcd.sh \ - # Manually generate certificate and add all domains, it is needed to connect - # "btcctl" and "lnd" to "btcd" over docker links. - && "/bin/gencerts" --host="*" --host="blockchain" --directory="/rpc" --force +&& mkdir "/rpc" "/root/.btcd" "/root/.btcctl" \ +&& touch "/root/.btcd/btcd.conf" \ +&& chmod +x start-btcctl.sh \ +&& chmod +x start-btcd.sh \ +# Manually generate certificate and add all domains, it is needed to connect +# "btcctl" and "lnd" to "btcd" over docker links. +&& "/bin/gencerts" --host="*" --directory="/rpc" --force # Create a volume to house pregenerated RPC credentials. This will be # shared with any lnd, btcctl containers so they can securely query btcd's RPC @@ -60,7 +60,3 @@ RUN apk add --no-cache \ # Otherwise manually generated certificate will be overridden with shared # mounted volume! For more info read dockerfile "VOLUME" documentation. VOLUME ["/rpc"] - -# Health check to ensure btcd is running -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ - CMD pgrep btcd || exit 1 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 7e97ceff3..ade93e072 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,3 +1,4 @@ +version: '2' services: # btc is an image of bitcoin node which used as base image for btcd and # btccli. The environment variables default values determined on stage of @@ -7,7 +8,6 @@ services: container_name: btcd build: context: btcd/ - dockerfile: Dockerfile volumes: - shared:/rpc - bitcoin:/data diff --git a/docker/lnd/start-lnd.sh b/docker/lnd/start-lnd.sh index 98cbb5a37..7b83c2311 100755 --- a/docker/lnd/start-lnd.sh +++ b/docker/lnd/start-lnd.sh @@ -54,7 +54,7 @@ RPCUSER=$(set_default "$RPCUSER" "devuser") RPCPASS=$(set_default "$RPCPASS" "devpass") DEBUG=$(set_default "$LND_DEBUG" "debug") CHAIN=$(set_default "$CHAIN" "bitcoin") -HOSTNAME=$(set_default "$HOSTNAME" "localhost") +HOSTNAME=$(hostname) # CAUTION: DO NOT use the --noseedback for production/mainnet setups, ever! # Also, setting --rpclisten to $HOSTNAME will cause it to listen on an IP @@ -64,6 +64,7 @@ HOSTNAME=$(set_default "$HOSTNAME" "localhost") if [ "$BACKEND" == "bitcoind" ]; then exec lnd \ --noseedbackup \ + "--$CHAIN.active" \ "--$CHAIN.$NETWORK" \ "--$CHAIN.node"="$BACKEND" \ "--$BACKEND.rpchost"="$RPCHOST" \ @@ -72,11 +73,13 @@ if [ "$BACKEND" == "bitcoind" ]; then "--$BACKEND.zmqpubrawblock"="tcp://$RPCHOST:28332" \ "--$BACKEND.zmqpubrawtx"="tcp://$RPCHOST:28333" \ "--rpclisten=$HOSTNAME:10009" \ + "--rpclisten=localhost:10009" \ --debuglevel="$DEBUG" \ "$@" elif [ "$BACKEND" == "btcd" ]; then exec lnd \ --noseedbackup \ + "--$CHAIN.active" \ "--$CHAIN.$NETWORK" \ "--$CHAIN.node"="$BACKEND" \ "--$BACKEND.rpccert"="$RPCCRTPATH" \ @@ -84,6 +87,7 @@ elif [ "$BACKEND" == "btcd" ]; then "--$BACKEND.rpcuser"="$RPCUSER" \ "--$BACKEND.rpcpass"="$RPCPASS" \ "--rpclisten=$HOSTNAME:10009" \ + "--rpclisten=localhost:10009" \ --debuglevel="$DEBUG" \ "$@" else diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 50bab9ffb..a7714c41b 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -93,7 +93,7 @@ following build dependencies are required: ### Installing Go -`lnd` is written in Go, with a minimum version of `1.25.11` (or, in case this +`lnd` is written in Go, with a minimum version of `1.24.11` (or, in case this document gets out of date, whatever the Go version in the main `go.mod` file requires). To install, run one of the following commands for your OS: @@ -101,15 +101,15 @@ requires). To install, run one of the following commands for your OS: Linux (x86-64) ``` - wget https://dl.google.com/go/go1.25.11.linux-amd64.tar.gz - echo "34f14304e856893f4ba30c2cacfe93906e9de7915c5f6aaaf3a81cdccd7ba30b go1.25.11.linux-amd64.tar.gz" | sha256sum --check + wget https://dl.google.com/go/go1.24.11.linux-amd64.tar.gz + echo "bceca00afaac856bc48b4cc33db7cd9eb383c81811379faed3bdbc80edb0af65 go1.24.11.linux-amd64.tar.gz" | sha256sum --check ``` - The command above should output `go1.25.11.linux-amd64.tar.gz: OK`. If it + The command above should output `go1.24.11.linux-amd64.tar.gz: OK`. If it doesn't, then the target REPO HAS BEEN MODIFIED, and you shouldn't install this version of Go. If it matches, then proceed to install Go: ``` - sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.25.11.linux-amd64.tar.gz + sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.24.11.linux-amd64.tar.gz export PATH=$PATH:/usr/local/go/bin ``` @@ -118,15 +118,15 @@ requires). To install, run one of the following commands for your OS: Linux (ARMv6) ``` - wget https://dl.google.com/go/go1.25.11.linux-armv6l.tar.gz - echo "492d69badee59cae12e9a36282dfce94041bd4aac88fdddea575a7d99a2bd05d go1.25.11.linux-armv6l.tar.gz" | sha256sum --check + wget https://dl.google.com/go/go1.24.11.linux-armv6l.tar.gz + echo "24d712a7e8ea2f429c05bc67287249e0291f2fe0ea6d6ff268f11b7343ad0f47 go1.24.11.linux-armv6l.tar.gz" | sha256sum --check ``` - The command above should output `go1.25.11.linux-armv6l.tar.gz: OK`. If it + The command above should output `go1.24.11.linux-armv6l.tar.gz: OK`. If it isn't, then the target REPO HAS BEEN MODIFIED, and you shouldn't install this version of Go. If it matches, then proceed to install Go: ``` - sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.25.11.linux-armv6l.tar.gz + sudo rm -rf /usr/local/go && tar -C /usr/local -xzf go1.24.11.linux-armv6l.tar.gz export PATH=$PATH:/usr/local/go/bin ``` @@ -254,6 +254,7 @@ will have the following tags: - [peersrpc](/lnrpc/peersrpc/peers.proto) - [kvdb_postrgres](/docs/postgres.md) - [kvdb_sqlite](/docs/sqlite.md) +- [kvdb_etcd](/docs/etcd.md) The `dev` tag is used for development builds, and is not included in the release builds & installation. diff --git a/docs/backport-workflow.md b/docs/backport-workflow.md deleted file mode 100644 index bad0ca252..000000000 --- a/docs/backport-workflow.md +++ /dev/null @@ -1,526 +0,0 @@ -# Automated Backport Workflow - -This document describes the automated backport workflow for the LND project. - -## Table of Contents - -1. [Overview](#overview) -2. [How to Use](#how-to-use) -3. [Workflow Triggers](#workflow-triggers) -4. [Label Format](#label-format) -5. [Workflow Steps](#workflow-steps) -6. [Handling Conflicts](#handling-conflicts) -7. [Multiple Backports](#multiple-backports) -8. [Technical Details](#technical-details) -9. [Troubleshooting](#troubleshooting) - -## Overview - -The automated backport workflow simplifies the process of backporting merged PRs from the `master` branch to release branches (e.g., `v0.20.x-branch`, `v0.19.x-branch`). - -Instead of manually creating branches, cherry-picking commits, and creating PRs, maintainers can simply add a label to the master PR, and the workflow handles the rest. - -## How to Use - -### Basic Usage - -1. **Merge a PR to master** (or have it already merged) -2. **Add a backport label** in the format: `backport-v-branch` - - Example: `backport-v0.20.x-branch` -3. **The workflow automatically**: - - Validates the target branch exists - - Cherry-picks the commits - - Creates a new PR targeting the release branch - - Adds the `no-changelog` label (since release notes are in the master PR) - -### Example Scenario - -``` -Day 1, 10:00 - PR #1234 "Fix critical bug" merged to master -Day 1, 10:30 - Add label: backport-v0.20.x-branch -Day 1, 10:31 - Workflow creates PR #1235 automatically - Title: "[v0.20.x-branch] Backport #1234: Fix critical bug" - Base: v0.20.x-branch - Labels: no-changelog -Day 1, 14:00 - Maintainer reviews and merges PR #1235 -``` - -## Workflow Triggers - -The backport workflow triggers in two scenarios: - -### Scenario 1: Label Before Merge - -``` -1. Open PR #1234 -2. Add label: backport-v0.20.x-branch -3. Review and approve PR -4. Merge PR #1234 -5. → Workflow triggers on PR close event -6. → Backport PR #1235 created immediately -``` - -### Scenario 2: Label After Merge - -``` -1. Open PR #1234 -2. Review, approve, and merge PR #1234 -3. Later... decide it needs backporting -4. Add label: backport-v0.20.x-branch -5. → Workflow triggers on label event -6. → Backport PR #1235 created immediately -``` - -Both scenarios work identically. - -## Label Format - -### Valid Labels - -Labels **must** start with `backport-v` to trigger the workflow: - -- ✅ `backport-v0.20.x-branch` → backports to `v0.20.x-branch` -- ✅ `backport-v0.19.x-branch` → backports to `v0.19.x-branch` -- ✅ `backport-v0.18.x-beta-branch` → backports to `v0.18.x-beta-branch` - -### Invalid Labels (Will NOT Trigger) - -These labels are ignored by the workflow: - -- ❌ `backport candidate` - discussion label only -- ❌ `backport-candidate` - doesn't start with `backport-v` -- ❌ `backport-needed` - doesn't start with `backport-v` -- ❌ `needs-backport` - wrong prefix - -This allows you to use discussion labels without accidentally triggering backports. - -### Label to Branch Mapping - -The label format directly maps to the target branch: - -``` -Label: backport-v0.20.x-branch - ↓ (removes "backport-" prefix) -Branch: v0.20.x-branch -``` - -## Workflow Steps - -The workflow executes the following steps when triggered: - -### Step 1: Checkout Repository - -```yaml -- Fetches the full git history -- Checks out the base branch (usually master) -``` - -### Step 2: Validate Target Branches - -```bash -For each backport label: - 1. Extract branch name from label - backport-v0.20.x-branch → v0.20.x-branch - - 2. Check if branch exists in remote repository - git ls-remote --heads origin v0.20.x-branch - - 3. If branch doesn't exist: - - Log error message - - Add branch to missing_branches list - - 4. After checking all labels: - - If any branches are missing → FAIL workflow - - If all branches exist → Continue -``` - -**Example validation output:** - -``` -All labels: ["backport-v0.20.x-branch", "bug-fix", "backport-v0.19.x-branch"] -Found backport labels: -backport-v0.20.x-branch -backport-v0.19.x-branch - -Checking if branch exists: v0.20.x-branch -✓ Branch 'v0.20.x-branch' exists - -Checking if branch exists: v0.19.x-branch -✓ Branch 'v0.19.x-branch' exists - -✓ All target branches validated successfully -``` - -### Step 3: Create Backport PRs - -For each valid backport label, the workflow: - -1. **Creates a new branch** - - Branch name: `backport--to-` - - Example: `backport-1234-to-v0.20.x-branch` - - Based on: the target release branch - -2. **Cherry-picks commits** - - Uses `git cherry-pick` (not merge or rebase) - - Cherry-picks all commits from the original PR - - Preserves commit messages and authorship - - Skips merge commits - -3. **Creates a new PR** - - Title: `[v0.20.x-branch] Backport #1234: ` - - Base branch: `v0.20.x-branch` - - Head branch: `backport-1234-to-v0.20.x-branch` - - Labels: `no-changelog` (automatically added) - -4. **PR Description** - ```markdown - Backport of #1234 - - Original PR: https://github.com/lightningnetwork/lnd/pull/1234 - - --- - - [Original PR description here] - ``` - -## Handling Conflicts - -The workflow handles merge conflicts gracefully using the `draft_commit_conflicts` strategy. - -### When Cherry-pick Succeeds - -``` -1. Cherry-pick completes cleanly -2. Creates regular PR (ready for review) -3. PR is NOT in draft mode -4. Maintainer can review and merge immediately -``` - -### When Cherry-pick Has Conflicts - -``` -1. Cherry-pick encounters conflicts -2. Workflow commits the conflict markers: - <<<<<<< HEAD - [code from release branch] - ======= - [code from master PR] - >>>>>>> commit-hash - -3. Creates DRAFT PR -4. PR description indicates there were conflicts -5. Manual resolution required: - a. git fetch origin - b. git checkout backport-1234-to-v0.20.x-branch - c. Resolve conflicts in affected files - d. git add - e. git commit -m "Resolve backport conflicts" - f. git push origin backport-1234-to-v0.20.x-branch - g. Mark PR as "Ready for review" in GitHub UI -6. Maintainer reviews and merges -``` - -### Conflict Resolution Best Practices - -- **Review the original PR**: Understand what changed -- **Check the release branch**: Understand why conflicts occurred -- **Test after resolving**: Run tests locally before pushing -- **Update commit message**: Explain what conflicts were resolved and how -- **Request review**: Don't merge without review, even after resolving conflicts - -## Multiple Backports - -You can backport to multiple release branches simultaneously by adding multiple labels. - -### Example: Backport to Two Branches - -``` -PR #1234 merged with labels: -- backport-v0.20.x-branch -- backport-v0.19.x-branch - -Workflow creates TWO backport PRs: - -PR #1235: - Title: [v0.20.x-branch] Backport #1234: Original title - Base: v0.20.x-branch - Labels: no-changelog - -PR #1236: - Title: [v0.19.x-branch] Backport #1234: Original title - Base: v0.19.x-branch - Labels: no-changelog -``` - -### Independent Processing - -Each backport is processed independently: - -- One backport may succeed while another has conflicts -- One backport may fail validation while another succeeds -- Each backport creates a separate branch and PR -- Each backport PR is reviewed and merged independently - -### Example with Mixed Results - -``` -PR #1234 with labels: -- backport-v0.20.x-branch → ✓ Clean cherry-pick, regular PR created -- backport-v0.19.x-branch → ✗ Conflicts, draft PR created -- backport-v0.99.x-branch → ✗ Branch doesn't exist, workflow fails -``` - -In this case: -1. PR #1235 to v0.20.x-branch is ready for review -2. PR #1236 to v0.19.x-branch needs conflict resolution -3. No PR created for v0.99.x-branch (validation failed) -4. Remove the incorrect label and add the correct one. The workflow will - re-trigger when you add the new label. - -## Technical Details - -### Workflow File - -Location: `.github/workflows/backport.yml` - -### Trigger Events - -```yaml -on: - pull_request_target: - types: [closed, labeled] -``` - -- **closed**: Triggers when PR is closed (checks if merged) -- **labeled**: Triggers when any label is added - -### Permissions Required - -```yaml -permissions: - contents: write # Create branches and commits - pull-requests: write # Create and manage PRs - issues: read # Read PR metadata -``` - -### Workflow Condition - -```yaml -if: | - github.event.pull_request.merged == true && - contains(join(github.event.pull_request.labels.*.name, ','), 'backport-v') -``` - -Only runs when: -1. PR is actually merged (not just closed) -2. At least one label contains `backport-v` - -### Label Pattern - -```yaml -label_pattern: '^backport-(v.+)$' -``` - -Regex explanation: -- `^` - Start of string -- `backport-` - Literal text -- `(v.+)` - Capture group: "v" followed by one or more characters -- `$` - End of string - -Examples: -- `backport-v0.20.x-branch` → matches, captures `v0.20.x-branch` -- `backport-v0.19.x-branch` → matches, captures `v0.19.x-branch` -- `backport-candidate` → doesn't match (no "v" after dash) - -### Cherry-pick Strategy - -```yaml -merge_commits: skip -``` - -- Uses `git cherry-pick` for clean history -- Skips merge commits (only cherry-picks actual changes) -- Preserves original commit messages and authorship -- Maintains PGP signatures where present - -### Conflict Resolution Strategy - -```yaml -experimental: | - conflict_resolution: draft_commit_conflicts -``` - -- Creates draft PR with conflict markers -- Allows manual resolution -- Preserves all context and metadata - -## Troubleshooting - -### Problem: Workflow Doesn't Trigger - -**Symptoms:** -- Added `backport-v0.20.x-branch` label -- No workflow run appears in Actions tab - -**Possible causes:** - -1. **Label format is wrong** - - ❌ `backport-0.20.x-branch` (missing "v") - - ✅ `backport-v0.20.x-branch` - -2. **PR is not merged** - - Workflow only runs on merged PRs - - Check PR status - -3. **Workflow is disabled** - - Check `.github/workflows/backport.yml` exists - - Check workflow is enabled in Settings → Actions - -### Problem: Workflow Fails with "Branch doesn't exist" - -**Error message:** -``` -Error: Target branch 'v0.21.x-branch' does not exist (from label 'backport-v0.21.x-branch') -Error: The following target branches do not exist: v0.21.x-branch -Error: Please ensure the branch exists before adding the backport label -``` - -**Solution:** - -1. **Verify branch name:** - ```bash - git ls-remote --heads origin | grep v0.21 - ``` - -2. **Check available release branches:** - ```bash - git branch -r | grep origin/v0 | grep -v fork - ``` - -3. **Fix the label:** - - Remove incorrect label - - Add correct label with existing branch name - -### Problem: Cherry-pick Has Conflicts - -**Symptoms:** -- Backport PR created as DRAFT -- PR description mentions conflicts -- Branch has files with conflict markers - -**Solution:** - -1. **Fetch and checkout the branch:** - ```bash - git fetch origin - git checkout backport-1234-to-v0.20.x-branch - ``` - -2. **Find conflicted files:** - ```bash - grep -r "<<<<<<< HEAD" . - ``` - -3. **Resolve each conflict:** - - Open the file in an editor - - Review both versions: - ``` - <<<<<<< HEAD - [Release branch version] - ======= - [Master PR version] - >>>>>>> commit-hash - ``` - - Choose the correct code or merge both - - Remove conflict markers - -4. **Commit the resolution:** - ```bash - git add - git commit -m "Resolve backport conflicts for PR #1234 - - Conflicts occurred due to [explanation]. - Resolution: [describe what you did]" - git push origin backport-1234-to-v0.20.x-branch - ``` - -5. **Mark PR ready for review:** - - Go to PR on GitHub - - Click "Ready for review" - -### Problem: Multiple Labels but Only One Backport Created - -**Symptoms:** -- Added `backport-v0.20.x-branch` and `backport-v0.19.x-branch` -- Only one PR created - -**Possible causes:** - -1. **One label format is wrong** - - Check both labels start with `backport-v` - - Fix incorrect label, workflow will retry - -2. **One branch doesn't exist** - - Check workflow logs for validation errors - - Verify both branches exist - -3. **Workflow still running** - - Check Actions tab for in-progress runs - - Wait for workflow to complete - -### Problem: Backport PR Missing `no-changelog` Label - -**Symptoms:** -- Backport PR created successfully -- CI fails on changelog check - -**Solution:** - -1. **Manually add the label:** - - Add `no-changelog` label to the backport PR - -2. **Check workflow configuration:** - - Verify `.github/workflows/backport.yml` has: - ```yaml - add_labels: no-changelog - ``` - -3. **Re-run the workflow:** - - Remove and re-add the backport label on original PR - - New backport PR will have correct label - -### Getting Help - -If you encounter issues not covered here: - -1. **Check workflow logs:** - - Go to Actions tab - - Click on the failed workflow run - - Review step-by-step logs - -2. **Check workflow file:** - - `.github/workflows/backport.yml` - - Verify configuration matches this documentation - -3. **Manual backport:** - - If automated backport fails repeatedly, you can backport manually: - ```bash - # Create branch from target release branch - git checkout v0.20.x-branch - git checkout -b manual-backport-123-to-v0.20.x - - # Cherry-pick commits from the original PR - git cherry-pick - - # Resolve any conflicts, then: - git add . - git commit - git push origin manual-backport-123-to-v0.20.x - - # Create PR targeting v0.20.x-branch with no-changelog label - gh pr create --base v0.20.x-branch --label no-changelog - ``` - -4. **Report issues:** - - If you find a bug in the workflow - - Open an issue with workflow logs and details diff --git a/docs/code_contribution_guidelines.md b/docs/code_contribution_guidelines.md index 7291ac8de..e17ca66af 100644 --- a/docs/code_contribution_guidelines.md +++ b/docs/code_contribution_guidelines.md @@ -3,17 +3,15 @@ 2. [Minimum Recommended Skillset](#minimum-recommended-skillset) 3. [Required Reading](#required-reading) 4. [Substantial contributions only](#substantial-contributions-only) -5. [New Contributors](#new-contributors) -6. [Development Practices](#development-practices) +5. [Development Practices](#development-practices) 1. [Share Early, Share Often](#share-early-share-often) 1. [Development Guidelines](#development-guidelines) -7. [Code Approval Process](#code-approval-process) +5. [Code Approval Process](#code-approval-process) 1. [Code Review](#code-review) 1. [Rework Code (if needed)](#rework-code-if-needed) 1. [Acceptance](#acceptance) - 1. [Backporting Changes](#backporting-changes) 1. [Review Bot](#review-bot) -8. [Contribution Standards](#contribution-standards) +7. [Contribution Standards](#contribution-standards) 1. [Contribution Checklist](#contribution-checklist) 1. [Licensing of Contributions](#licensing-of-contributions) @@ -115,19 +113,6 @@ Also, consider increasing the test coverage of the code by writing more unit tests first, which is also a very valuable way to contribute and learn more about the code base. -# New Contributors -Due to the ubiquity of LLM coding tools, pull requests -from new contributors are not prioritized for review. If you're a -new contributor with aspirations to contribute to LND, we recommend starting -with issue triage and PR reviews. These are a better avenue to demonstrate -your knowledge and desire to contribute to open source than new code in this -era of easy AI-assisted code generation. This should also help build a track -record that makes your future PRs easier to prioritize. - -If you spot a glaring issue, we may still merge the fix or take it over -ourselves. And if you're a new developer who notices an issue with the code, -consider opening a detailed issue instead of a PR. - # Development Practices Developers are expected to work in their own trees and submit pull requests when @@ -247,23 +232,6 @@ these signatures intact, we prefer using merge commits. PR proposers can use Rejoice as you will now be listed as a [contributor](https://github.com/lightningnetwork/lnd/graphs/contributors)! -## Backporting Changes - -After a PR is merged to master, it may need to be backported to release branches -(e.g., `v0.20.x-branch`) to include the fix or feature in upcoming patch releases. - -The project uses an **automated backport workflow** to simplify this process. Simply -add a label like `backport-v0.20.x-branch` to your merged PR, and a GitHub Action -will automatically create a backport PR for you. - -For complete documentation on the automated backport workflow, including: -- How to use backport labels -- Handling merge conflicts -- Multiple backports -- Troubleshooting - -See [backport-workflow.md](backport-workflow.md) - ## Review Bot In order to keep the review flow going, Lightning Labs uses a bot to remind diff --git a/docs/configuring_tor.md b/docs/configuring_tor.md index 8b166c675..20584124c 100644 --- a/docs/configuring_tor.md +++ b/docs/configuring_tor.md @@ -15,13 +15,13 @@ by using Tor for anonymous networking to establish connections. With widespread usage of Onion Services within the network, concerns about the difficulty of proper NAT traversal are alleviated, as usage of onion services -allows nodes to accept inbound connections even if they're behind a NAT. `lnd` -supports v3 onion services only; legacy v2 onion service support has been -removed. +allows nodes to accept inbound connections even if they're behind a NAT. At the +time of writing this documentation, `lnd` supports both types of onion services: +v2 and v3. -Before following the remainder of this documentation, you should ensure that -you already have Tor installed locally. **Make sure that you run at least -version 0.3.3.6 of Tor in order to use v3 Onion Services.** +Before following the remainder of this documentation, you should ensure that you +already have Tor installed locally. **If you want to run v3 Onion Services, make +sure that you run at least version 0.3.3.6.** Official instructions to install the latest release of Tor can be found [here](https://www.torproject.org/docs/tor-doc-unix.html.en). @@ -81,6 +81,7 @@ Tor: --tor.control= The host:port that Tor is listening on for Tor control connections (default: localhost:9051) --tor.targetipaddress= IP address that Tor should use as the target of the hidden service --tor.password= The password used to arrive at the HashedControlPassword for the control port. If provided, the HASHEDPASSWORD authentication method will be used instead of the SAFECOOKIE one. + --tor.v2 Automatically set up a v2 onion service to listen for inbound connections --tor.v3 Automatically set up a v3 onion service to listen for inbound connections --tor.privatekeypath= The path to the private key of the onion service being created ``` @@ -102,8 +103,11 @@ service. A path to save the onion service's private key can be specified with the `--tor.privatekeypath` flag. Most of these arguments have defaults, so as long as they apply to you, routing -all outbound and inbound connections through Tor can simply be done with v3 -onion services: +all outbound and inbound connections through Tor can simply be done with either +v2 or v3 onion services: +```shell +$ ./lnd --tor.active --tor.v2 +``` ```shell $ ./lnd --tor.active --tor.v3 ``` @@ -155,13 +159,15 @@ authentication methods (arguably, from most to least secure): ## Listening for Inbound Connections In order to listen for inbound connections through Tor, an onion service must be -created. `lnd` supports v3 onion services, the latest generation of onion -services. To learn more about these, see -[Intro to Next Gen Onion Services](https://trac.torproject.org/projects/tor/wiki/doc/NextGenOnions). +created. There are two types of onion services: v2 and v3. v3 onion services +are the latest generation of onion services, and they provide a number of +advantages over the legacy v2 onion services. To learn more about these +benefits, see [Intro to Next Gen Onion Services](https://trac.torproject.org/projects/tor/wiki/doc/NextGenOnions). -v3 onion services are created and used automatically by `lnd` via the `tor.v3` -flag. To prevent unintentional leaking of identifying information, it is also -necessary to add the flag `listen=localhost`. +Both types can be created and used automatically by `lnd`. Specifying which type +should be used can easily be done by either using the `tor.v2` or `tor.v3` flag. +To prevent unintentional leaking of identifying information, it is also necessary +to add the flag `listen=localhost`. For example, v3 onion services can be used with the following flags: ```shell @@ -170,8 +176,9 @@ $ ./lnd --tor.active --tor.v3 --listen=localhost This will automatically create a hidden service for your node to use to listen for inbound connections and advertise itself to the network. The onion service's -private key is saved to a file named `v3_onion_private_key` in `lnd`'s base -directory. This will allow `lnd` to recreate the same hidden service upon +private key is saved to a file named `v2_onion_private_key` or +`v3_onion_private_key` depending on the type of onion service used in `lnd`'s +base directory. This will allow `lnd` to recreate the same hidden service upon restart. If you wish to generate a new onion service, you can simply delete this file. The path to this private key file can also be modified with the `--tor.privatekeypath` argument. diff --git a/docs/db_migration_guide.md b/docs/db_migration_guide.md index 233ef9eb9..297999643 100644 --- a/docs/db_migration_guide.md +++ b/docs/db_migration_guide.md @@ -44,12 +44,10 @@ flowchart TD M1_Sqlite["Migration #1 (lnd v0.19)
Invoices"] M2_Sqlite["Migration #2 (lnd v0.20)
Graph"] M3_Sqlite["Migration #3 (lnd v0.21)
Payments"] - M4_Sqlite["Migration #4 (lnd v0.22)
Btcwallet & Channel State"] M1_Postgres["Migration #1 (lnd v0.19)
Invoices"] M2_Postgres["Migration #2 (lnd v0.20)
Graph"] M3_Postgres["Migration #3 (lnd v0.21)
Payments"] - M4_Postgres["Migration #4 (lnd v0.22)
Btcwallet & Channel State"] %% 2. Define all links (within and between graphs) Bbolt --> SQLite @@ -58,12 +56,10 @@ flowchart TD SQLite --> M1_Sqlite M1_Sqlite --> M2_Sqlite M2_Sqlite --> M3_Sqlite - M3_Sqlite --> M4_Sqlite Postgres --> M1_Postgres M1_Postgres --> M2_Postgres M2_Postgres --> M3_Postgres - M3_Postgres --> M4_Postgres %% 3. Group nodes into subgraphs subgraph "Step 1: Migration via lndinit" @@ -76,11 +72,9 @@ flowchart TD M1_Sqlite M2_Sqlite M3_Sqlite - M4_Sqlite M1_Postgres M2_Postgres M3_Postgres - M4_Postgres end %% 4. Apply Styles @@ -91,8 +85,8 @@ flowchart TD %% Apply classes to nodes class Bbolt bboltNode - class SQLite,M1_Sqlite,M2_Sqlite,M3_Sqlite,M4_Sqlite sqliteNode - class Postgres,M1_Postgres,M2_Postgres,M3_Postgres,M4_Postgres postgresNode + class SQLite,M1_Sqlite,M2_Sqlite,M3_Sqlite sqliteNode + class Postgres,M1_Postgres,M2_Postgres,M3_Postgres postgresNode ``` - **Stage 1**: Migrate from bbolt to a SQL-based **kvdb** backend using the [lndinit](https://github.com/lightninglabs/lndinit/blob/main/docs/data-migration.md) tool. @@ -133,18 +127,15 @@ This will mitigate the poor Postgres performance on kvdb. This stage unlocks true SQL performance by restructuring data into relational tables. Migration is **per-subsystem** and **incremental**. -The migration steps are automatically applied when LND is restarted after step 1 was successfully completed and the config value db.use-native-sql=true is set. -You will see log lines from the `SQLD` subsystem about the migration, such as `Starting migration of invoices from KV to SQL`. - ### Subsystem Readiness | Subsystem | Relational Backend | Migration Script | Status | |---------------------|--------------------|------------------|--------| | Invoices | ✅ Available | ✅ | Available with **v0.19** | | Graph | ✅ Available | ✅ | Available with **v0.20** | -| Payments | ✅ Available | ✅ | Available with **v0.21** | -| Btcwallet | 🚧 In Progress | Planned | Targeted with **v0.22**| -| Channel State | 🚧 In Progress | Planned | Targeted with **v0.22**| +| Payments | 🚧 In Progress | Planned | Targeted with **v0.21**| +| Btcwallet | 🚧 In Progress | Planned | Targeted with **v0.21**| +| Forwarding History | ❌ TBD | ❌ TBD | Future work | --- @@ -156,9 +147,9 @@ both are on the same SQL engine (e.g., SQLite). - **Data loss risk**: Always **back up your `data/` directory** before migration. - **Downtime required**: Stage 1 requires LND to be offline. Stage 2 is done at startup, requiring a LND restart. - **Postgres kvdb performance**: Postgres performance on kvdb is sub-optimal. It is -recommended to make the stage 2 migration immediately to avoid performance bottlenecks. Certain RPCs like `listpayments` may not perform well on Postgres if the node has a lot of payments data. As of **v0.21**, the payments relational -backend is available, so payment-heavy nodes can migrate payments to relational -mode to restore good `listpayments` performance. +recommended to make the stage 2 migration immediately to avoid performance bottlenecks. Certain RPCs like `listpayments` may not perform well on Postgres if the node has a lot of payments data. If your node operation is heavy +on payments and `listpayments` performance is critical for you, we'd recommend +not doing any migration and wait till version 0.21.0 is released. - **No migration path between SQL backend**: Once migrated to either Postgres or SQLite, it is not possible to switch to the other, so choose your target backend carefully. --- @@ -168,8 +159,8 @@ SQLite, it is not possible to switch to the other, so choose your target backend ### Choosing the Right Path - **For most users**: Choose SQLite, then migrate. Later, adopt relational backends subsystem-by-subsystem. -- **Enterprise/Postgres users**: With the **payments relational backend** available -as of **v0.21**, you can now perform **Stage 1 + Stage 2 in quick succession**. +- **Enterprise/Postgres users**: It is recommened to wait to start the migration +until **payments relational backend** is ready, then perform **Stage 1 + Stage 2 in quick succession**. ### Timing Your Migration @@ -232,8 +223,9 @@ Invoice migration completed successfully. The LND team is actively working on: +- **Payments relational backend** and migration tooling (Stage 2) - **Btcwallet relational backend** and migration tooling (Stage 2) -- **Channel state relational backend** and migration tooling (Stage 2) +- **Forwarding history** relational schema (long-term) - **Automatic detection** of migration readiness in `lnd` Node operators should monitor: diff --git a/docs/etcd.md b/docs/etcd.md new file mode 100644 index 000000000..bf1a82998 --- /dev/null +++ b/docs/etcd.md @@ -0,0 +1,85 @@ +# Experimental etcd support in LND + +With the recent introduction of the `kvdb` interface LND can support multiple +database backends allowing experimentation with the storage model as well as +improving robustness through e.g. replicating essential data. + +Building on `kvdb` in v0.11.0 we're adding experimental [etcd](https://etcd.io) +support to LND. As this is an unstable feature heavily in development, it still +has *many* rough edges for the time being. It is therefore highly recommended to +not use LND on `etcd` in any kind of production environment especially not +on bitcoin mainnet. + +## Building LND with etcd support + +To create a dev build of LND with etcd support use the following command: + +```shell +$ make tags="kvdb_etcd" +``` + +The important tag is the `kvdb_etcd`, without which the binary is built without +the etcd driver. + +For development, it is advised to set the `GOFLAGS` environment variable to +`"-tags=test"` otherwise `gopls` won't work on code in `channeldb/kvdb/etcd` +directory. + +## Running a local etcd instance for testing + +To start your local etcd instance for testing run: + +```shell +$ ./etcd \ + --auto-tls \ + --advertise-client-urls=https://127.0.0.1:2379 \ + --listen-client-urls=https://0.0.0.0:2379 \ + --max-txn-ops=16384 \ + --max-request-bytes=104857600 +``` + +The large `max-txn-ops` and `max-request-bytes` values are currently required in +case of running LND with the full graph in etcd. These parameters have been +tested to work with testnet LND. + +## Configuring LND to run on etcd + +To run LND with etcd, additional configuration is needed, specified either +through command line flags or in `lnd.conf`. + +Sample command line: + +```shell +$ ./lnd-debug \ + --db.backend=etcd \ + --db.etcd.host=127.0.0.1:2379 \ + --db.etcd.certfile=/home/user/etcd/bin/default.etcd/fixtures/client/cert.pem \ + --db.etcd.keyfile=/home/user/etcd/bin/default.etcd/fixtures/client/key.pem \ + --db.etcd.insecure_skip_verify +``` + +Sample `lnd.conf` (with other setting omitted): + +```text +[db] +db.backend=etcd +db.etcd.host=127.0.0.1:2379 +db.etcd.cerfile=/home/user/etcd/bin/default.etcd/fixtures/client/cert.pem +db.etcd.keyfile=/home/user/etcd/bin/default.etcd/fixtures/client/key.pem +db.etcd.insecure_skip_verify=true +``` + +Optionally users can specify `db.etcd.user` and `db.etcd.pass` for db user +authentication. If the database is shared, it is possible to separate our data +from other users by setting `db.etcd.namespace` to an (already existing) etcd +namespace. In order to test without TLS, users are able to set `db.etcd.disabletls` +flag to `true`. + +## Migrating existing channel.db to etcd + +This is currently not supported. + +## Disclaimer + +As mentioned before this is an experimental feature, and with that your data +may be lost. Use at your own risk! diff --git a/docs/forwarding_history_privacy.md b/docs/forwarding_history_privacy.md deleted file mode 100644 index bb8ce5db0..000000000 --- a/docs/forwarding_history_privacy.md +++ /dev/null @@ -1,374 +0,0 @@ -# Forwarding History Privacy Management - -## Introduction - -The Lightning Network excels at providing fast, low-cost payments with strong -privacy properties. However, routing nodes and Lightning Service Providers -(LSPs) face a unique challenge: their operational databases accumulate -forwarding history that, if compromised or subpoenaed, could reveal sensitive -information about payment flows across the network. This document explores the -privacy implications of forwarding logs and introduces LND's solution for -implementing data retention policies without migrating to a new node instance. - -## Understanding Forwarding History - -When your LND node routes a payment between two other nodes, it records detailed -information about that forwarding event in its database. This serves several -important operational purposes, including fee accounting, channel performance -analysis, and troubleshooting. Each forwarding event captures the incoming and -outgoing channels, amounts transferred, fees earned, and precise timestamps. - -Over months or years of operation, a busy routing node accumulates millions of -these records. While this historical data provides valuable insights into node -performance, it also creates a potential privacy liability. An attacker who -gains access to this database—whether through a security breach, or physical -seizure—could potentially reconstruct payment paths across the network by -correlating forwarding events across multiple compromised nodes. - -## Privacy Implications for Operators - -For individual routing node operators, the privacy risks of retaining unlimited -forwarding history are modest but real. If an adversary gains access to your -node's database, they could analyze your forwarding patterns to infer -information about the network topology you participate in and potentially -identify payment patterns involving your channels. - -### The Traditional Dilemma - -Prior to this feature, routing node operators faced an uncomfortable tradeoff. -To implement a data retention policy and purge old forwarding logs, the only -practical option was to shut down the node, reset the database, and restore -channels from backups—effectively migrating to a fresh node instance. This -process carries significant operational risks, including potential channel -closures, loss of channel state, and extended downtime. For LSPs serving -customers around the clock, such maintenance windows are highly disruptive. - -## The DeleteForwardingHistory Solution - -LND's `DeleteForwardingHistory` RPC addresses this challenge by providing a -safe, reversible-only-forward way to implement data retention policies. The -feature allows operators to specify a time threshold—either as a relative -duration or an absolute timestamp—and permanently delete all forwarding events -older than that threshold. The deletion operation executes in configurable -batches to avoid holding large database locks, and it returns statistics about -the deleted events, including the total fees earned during that period for -accounting purposes. - -### How It Works - -The deletion mechanism operates at the database layer, directly manipulating the -forwarding log bucket in LND's embedded bbolt database. The forwarding log -stores events using nanosecond-precision timestamps as keys, which enables -efficient time-based range queries. When you invoke a deletion, LND constructs a -cursor-based iteration that walks through events in chronological order, -collecting keys for events older than your specified cutoff time. It then -deletes these events in batches, with each batch executed within its own -database transaction. - -```mermaid -sequenceDiagram - participant User - participant CLI - participant Router RPC - participant ForwardingLog - participant Database - - User->>CLI: deletefwdhistory --age="-720h" - CLI->>Router RPC: DeleteForwardingHistory(duration: "-720h") - - Router RPC->>Router RPC: Parse duration → absolute time - Router RPC->>Router RPC: Validate minimum age (1 hour) - - loop For each batch (default: 10,000 events) - Router RPC->>ForwardingLog: DeleteForwardingEvents(endTime, batchSize) - ForwardingLog->>Database: Begin transaction - ForwardingLog->>Database: Iterate events <= endTime - ForwardingLog->>ForwardingLog: Calculate fees for batch - ForwardingLog->>Database: Delete batch of keys - ForwardingLog->>Database: Commit transaction - end - - ForwardingLog->>Router RPC: Return stats (deleted count, total fees) - Router RPC->>CLI: DeleteForwardingHistoryResponse - CLI->>User: Display deletion results -``` - -This batched approach ensures that even nodes with millions of forwarding events -can safely purge old data without causing database performance issues. Each -batch completes within a separate transaction, limiting lock contention and -allowing other database operations to proceed between batches. - -### Security Considerations - -The implementation includes several safeguards to prevent accidental data loss. -First, the RPC enforces a minimum age requirement: you cannot delete events less -than one hour old. This prevents mishaps where an operator accidentally deletes -recent forwarding history due to a timestamp parsing error or misunderstanding -the time format. The CLI command additionally requires explicit confirmation -before proceeding with the deletion. - -Second, the RPC requires the "offchain:write" macaroon permission, treating -forwarding history deletion as a sensitive write operation similar to payment -deletion. This ensures that only authorized users can purge forwarding data. - -Third, the operation is logged extensively. LND writes detailed log messages -before and after each deletion operation, recording the time threshold, batch -size, number of events deleted, and total fees from the deleted period. These -audit trails help operators verify that deletions executed as intended. - -### Fee Accounting - -One critical requirement for LSPs implementing data retention policies is -maintaining accurate accounting records. Even after purging old forwarding -events for privacy reasons, operators need to know how much revenue their node -generated during those periods for tax reporting and business analytics. - -The deletion operation addresses this by calculating and returning the sum of -all fees earned from the deleted events. For each event, LND computes the fee as -the difference between the incoming and outgoing amounts, then aggregates these -fees across all deleted events. The response includes this total in -millisatoshis, allowing operators to record their earnings before purging the -detailed records. - -```mermaid -graph TD - A[Forwarding Event] --> B{Calculate Fee} - B --> C[Fee = AmtIn - AmtOut] - C --> D[Accumulate to TotalFees] - D --> E{More Events?} - E -->|Yes| A - E -->|No| F[Return Total to User] - F --> G[Operator Records
for Accounting] - G --> H[Delete Detailed Events] -``` - -This approach separates accounting data from operational surveillance data. You -can maintain aggregate financial records while minimizing the detailed -forwarding logs that pose privacy risks. - -## Usage Guide - -### Command Line Interface - -The `lncli deletefwdhistory` command provides the primary interface for -operators. The command accepts time specifications in two formats: relative -durations for convenience, or absolute Unix timestamps for precision. - -For most use cases, relative durations offer the most intuitive interface. To -implement a 90-day retention policy, you would periodically run: - -```bash -lncli deletefwdhistory --age="-90d" -``` - -The supported time units cover a wide range of retention policies: - -- Seconds (`s`) and minutes (`m`) for testing or very short-term retention -- Hours (`h`) and days (`d`) for common operational timeframes -- Weeks (`w`) for weekly cleanup schedules -- Months (`M`, averaged to 30.44 days) for typical retention policies -- Years (`y`, averaged to 365.25 days) for long-term archives - -The minus sign prefix indicates you're specifying how far back in time to -delete. This convention matches the relative time syntax used elsewhere in LND -and makes the intent clear: "delete events from more than X time ago." - -For precise control, you can specify an absolute Unix timestamp: - -```bash -lncli deletefwdhistory --before=1704067200 -``` - -This deletes all events before January 1, 2024 00:00:00 UTC. Absolute timestamps -are particularly useful when implementing policies tied to specific dates, such -as calendar year boundaries for accounting purposes or regulatory compliance -deadlines. - -### Batch Size Tuning - -The `--batch_size` flag controls how many events are deleted per database -transaction. The default value of 10,000 provides a good balance for most nodes, -but you may want to adjust this based on your node's characteristics. - -For nodes with slower disk I/O or running on resource-constrained hardware, -reducing the batch size decreases the duration of each database lock, improving -responsiveness to concurrent operations: - -```bash -lncli deletefwdhistory --age="-1M" --batch_size=5000 -``` - -Conversely, for nodes with fast SSDs and low concurrent load, increasing the -batch size can speed up the overall deletion process: - -```bash -lncli deletefwdhistory --age="-1M" --batch_size=25000 -``` - -The implementation caps the maximum batch size at 50,000 to prevent excessively -large transactions from degrading database performance. - -### Automation and Scheduling - -Most operators will want to automate forwarding history cleanup rather than -running deletions manually. The command integrates naturally with cron jobs or -systemd timers. For a monthly cleanup maintaining a 90-day retention window: - -```bash -# Run at 3 AM on the first day of each month -0 3 1 * * /usr/local/bin/lncli deletefwdhistory --age="-90d" --force >> /var/log/lnd/fwdhistory_cleanup.log 2>&1 -``` - -The `--force` flag skips the interactive confirmation prompt, which is -required for unattended automation. In production you should implement -additional safeguards such as pre-deletion validation checks and alerting -on unexpected results. - -For more sophisticated automation, consider implementing a script that: - -1. Queries current forwarding history statistics -2. Calculates the appropriate deletion threshold based on database size and growth rate -3. Executes the deletion -4. Records the fees returned for accounting -5. Monitors the resulting database size and alerts if disk space isn't reclaimed as expected - -### Database Compaction - -Deleting forwarding events frees space within LND's bbolt database, but this -space isn't immediately returned to the operating system. bbolt uses a -copy-on-write structure where deleted data leaves "free pages" that can be -reused for future writes, but the overall file size doesn't shrink until you -compact the database. - -LND supports automatic compaction via the configuration option: - -``` -db.bolt.auto-compact=true -``` - -With auto-compaction enabled, LND periodically performs compaction during normal -operation, typically triggered when the amount of free space exceeds a -threshold. However, after a large deletion operation, you may want to trigger -compaction immediately to reclaim disk space. - -The recommended approach is to schedule compaction shortly after your regular -deletion operations: - -1. Run `deletefwdhistory` to purge old events -2. Restart LND with `--db.bolt.auto-compact=true` if not already enabled -3. Monitor database file size to confirm space reclamation - -Be aware that database compaction requires free disk space equal to the current -database size during the operation, as it creates a new, compacted copy of the -database before replacing the original. - -## Integration with Existing Tools - -### Forwarding History Analysis - -The deletion operation doesn't interfere with LND's existing `forwardinghistory` -RPC, which allows you to query and analyze forwarding events. After a deletion, -queries for time ranges that have been purged will simply return no events for -those periods, while more recent events remain accessible. - -This means you can continue using analytical tools and scripts that query -forwarding history, but you should design them to handle sparse historical data -gracefully. Tools should not assume that forwarding history extends back to the -node's inception date. - -### Channel Analytics - -Similarly, channel performance analysis tools that rely on forwarding history -will only have access to events within your retention window. When evaluating -channel performance metrics like forwarding frequency or fee revenue, be mindful -that historical data before your retention cutoff is no longer available. - -For long-term performance tracking, consider aggregating statistics before -purging detailed events. You might maintain summary records showing weekly or -monthly aggregate forwarding counts and fees per channel, even after deleting -the individual event records. - -## Privacy Best Practices - -While the deletion feature provides operators with a mechanism to implement data -retention policies, it's important to understand what it does and doesn't -protect against. - -### What Deletion Protects - -Deleting old forwarding history reduces your node's exposure if the database is -compromised in the future. An attacker who gains access to your node after -you've implemented a 90-day retention policy can only observe the last 90 days -of forwarding activity, not the entire operational history. This limits the -window during which surveillance or correlation attacks could be performed using -your node's data. - -### What Deletion Doesn't Protect - -The revocation log for _active_ channels contains information that can be used -to reconstruct transaction flows. Once channels are closed, this data is -automatically deleted. - -The normal logs of a node also contain information that can be used to correlate -transactions. Users can set up automated systems to manually purge logs, or -configure the logging directory to a purely in-memory file system. - -### Defense in Depth - -Forwarding history deletion should be one component of a comprehensive privacy -strategy, not your only defense. Other important measures include: - -- Restricting physical and network access to the node -- Implementing strong authentication and access controls -- Regularly auditing who has access to the node and its backups -- Using channel aliases and avoiding personally identifiable information in - channel names -- Running your node over Tor to hide the network-level correlation between node - identity and IP address - - -The deletion feature gives you control over how long your node retains detailed -forwarding records, but it doesn't eliminate all privacy risks inherent in -operating a Lightning Network routing node. - -## Troubleshooting - -### Database Lock Timeouts - -During deletion of very large numbers of events, you might encounter database -lock timeout errors if other operations are trying to access the database -concurrently. If this occurs: - -1. Reduce the batch size to shorten each transaction -2. Schedule deletions during low-traffic periods -3. Temporarily pause other operations that query forwarding history frequently - -### Insufficient Disk Space for Compaction - -Database compaction requires temporary free space roughly equal to the size of -your database. If compaction fails due to insufficient disk space, you'll need -to free up space before the compaction can proceed: - -1. Delete other unnecessary files from the disk -2. Move log files or other non-critical data to alternate storage -3. Consider whether you can safely delete older database backups - -## Performance Considerations - -Deletion performance scales linearly with the number of events being deleted. -Performance varies significantly depending on storage hardware and database -size; operators should benchmark on their own hardware before relying on -specific throughput estimates. - -The operation's impact on node performance during deletion is minimal. Each -batch executes quickly, and the gaps between batches allow other database -operations to proceed. You can safely run deletions while the node is actively -routing payments, though you may want to avoid doing so during peak traffic -times on very busy nodes. - -Database compaction has a more significant performance impact, as it requires -LND to copy the entire database. During compaction, expect elevated CPU and disk -I/O, and budget several minutes for the operation to complete depending on your -database size. LND remains operational during compaction, but you may observe -increased latency for database-heavy operations. - diff --git a/docs/nat_traversal.md b/docs/nat_traversal.md new file mode 100644 index 000000000..8493a379b --- /dev/null +++ b/docs/nat_traversal.md @@ -0,0 +1,23 @@ +# NAT Traversal + +`lnd` has support for NAT traversal using a number of different techniques. At +the time of writing this documentation, UPnP and NAT-PMP are supported. NAT +traversal can be enabled through `lnd`'s `--nat` flag. + +```shell +$ lnd ... --nat +``` + +On startup, `lnd` will try the different techniques until one is found that's +supported by your hardware. The underlying dependencies used for these +techniques rely on using system-specific binaries in order to detect your +gateway device's address. This is needed because we need to be able to reach the +gateway device to determine if it supports the specific NAT traversal technique +currently being tried. Because of this, due to uncommon setups, it is possible +that these binaries are not found in your system. If this is case, `lnd` will +exit stating such error. + +As a bonus, `lnd` spawns a background thread that automatically detects IP +address changes and propagates the new address update to the rest of the +network. This is especially beneficial for users who were provided dynamic IP +addresses from their internet service provider. diff --git a/docs/neutrino_headers_import.md b/docs/neutrino_headers_import.md deleted file mode 100644 index f7364ee57..000000000 --- a/docs/neutrino_headers_import.md +++ /dev/null @@ -1,279 +0,0 @@ -# Neutrino Fast Sync via Headers Import - -When LND is configured to use the neutrino (light client) backend, the initial -sync requires downloading every block header and compact filter header from -the P2P network. On mainnet, that historical fetch dominates time-to-sync on a -fresh install and can take hours. - -The headers import feature lets neutrino bootstrap from a pre-built header -file or HTTP endpoint, dramatically reducing initial sync time. After the -import completes, neutrino transitions to normal P2P sync to catch up from -the import target to the current chain tip. - -## How It Works - -1. On startup, if header import sources are configured, neutrino downloads - (or reads from disk) the block headers and compact filter headers from - the configured sources. - -2. Each import file begins with a 10-byte metadata header: - - **Network magic** (4 bytes, little-endian): Identifies the target network - (mainnet, testnet, etc.). - - **Version** (1 byte): Format version (currently `0`). - - **Header type** (1 byte): `0` for block headers, `1` for filter headers. - - **Start height** (4 bytes, little-endian): The block height of the first - header in the file. - -3. Following the metadata, the file contains consecutive raw headers: - - **Block headers**: 80 bytes each (standard Bitcoin block header). - - **Filter headers**: 32 bytes each (BIP 158 compact filter header hash). - -4. During import on public networks (mainnet/testnet), neutrino runs the - full contextual header validation pipeline — proof-of-work, median-time- - past, and relative-ancestor checks — so the imported chain is held to - the same standard as headers fetched over P2P. Local networks - (regtest/simnet) fall back to `BFFastAdd` so the harness can ingest - rapidly-mined timestamps without churn. - -5. After the import completes, neutrino resumes normal P2P sync to fetch - any headers beyond the import target, ensuring the node catches up to - the chain tip. - -## Configuration - -Both `neutrino.blockheaderssource` and `neutrino.filterheaderssource` must -be specified together. Setting only one will cause LND to fail at startup -with a configuration error. - -Sources are auto-detected as either HTTP URLs or local file paths based on -whether the value starts with `http`. - -### Using block-dn.org (Recommended for Production) - -The [block-dn.org](https://github.com/guggero/block-dn) service publishes -pre-built header files for multiple Bitcoin networks. Each network exposes -four endpoints: - -- `/headers/import/` — block headers up to a specific block. -- `/headers/import/latest` — block headers up to the latest block. -- `/filter-headers/import/` — compact filter headers up to a specific - block. -- `/filter-headers/import/latest` — compact filter headers up to the latest - block. - -`` is **non-inclusive** and must be divisible by the service's -`entries_per_header_file` (currently `100,000`). It identifies the highest -such boundary at or below the current chain tip. - -Each service also publishes a `/status` JSON endpoint that reports -`best_block_height` and `entries_per_header_file`. - -#### Mainnet - -Mainnet imports also require `fee.url`, since the neutrino backend has no -mempool to derive fee estimates from. A full runnable CLI invocation looks -like: - -```sh -lnd \ - --bitcoin.mainnet \ - --bitcoin.node=neutrino \ - --fee.url=https://nodes.lightning.computer/fees/v1/btc-fee-estimates.json \ - --neutrino.blockheaderssource=https://block-dn.org/headers/import/latest \ - --neutrino.filterheaderssource=https://block-dn.org/filter-headers/import/latest -``` - -The equivalent `lnd.conf` stanza: - -```ini -[Application Options] -fee.url=https://nodes.lightning.computer/fees/v1/btc-fee-estimates.json - -[Bitcoin] -bitcoin.mainnet=true -bitcoin.node=neutrino - -[neutrino] -neutrino.blockheaderssource=https://block-dn.org/headers/import/latest -neutrino.filterheaderssource=https://block-dn.org/filter-headers/import/latest -``` - -Without `fee.url`, lnd will complete the header import and then exit with -`--fee.url parameter required when running neutrino on mainnet`. - -#### Testnet3 - -```sh -lnd \ - --bitcoin.testnet \ - --bitcoin.node=neutrino \ - --neutrino.blockheaderssource=https://testnet3.block-dn.org/headers/import/latest \ - --neutrino.filterheaderssource=https://testnet3.block-dn.org/filter-headers/import/latest -``` - -#### Testnet4 - -```sh -lnd \ - --bitcoin.testnet4 \ - --bitcoin.node=neutrino \ - --neutrino.blockheaderssource=https://testnet4.block-dn.org/headers/import/latest \ - --neutrino.filterheaderssource=https://testnet4.block-dn.org/filter-headers/import/latest -``` - -#### Signet - -```sh -lnd \ - --bitcoin.signet \ - --bitcoin.node=neutrino \ - --neutrino.blockheaderssource=https://signet.block-dn.org/headers/import/latest \ - --neutrino.filterheaderssource=https://signet.block-dn.org/filter-headers/import/latest -``` - -Current service status and available end-block targets per network: -- Mainnet: https://block-dn.org/status -- Testnet3: https://testnet3.block-dn.org/status -- Testnet4: https://testnet4.block-dn.org/status -- Signet: https://signet.block-dn.org/status - -### Using Local Files - -If you have pre-built header files on disk (for example, copied from an -existing neutrino data directory), you can point LND at them directly: - -```ini -[neutrino] -neutrino.blockheaderssource=/path/to/block_headers.bin -neutrino.filterheaderssource=/path/to/filter_headers.bin -``` - -Local files must include the 10-byte import metadata prefix. Raw header -files from neutrino's data directory (`block_headers.bin` and -`reg_filter_headers.bin`) do not include this metadata by default. You can -add it programmatically using neutrino's `chainimport.AddHeadersImportMetadata()` -utility. - -### Test-Only / Throwaway Validation Runs - -When validating import end-to-end against a clean temp state — for example -to time a fresh sync, or to confirm the import path before committing to a -real `lnddir` — the following flags are useful: - -```sh -lnd \ - --configfile=/dev/null \ - --lnddir=/tmp/lnd-mainnet-import \ - --no-macaroons \ - --noseedbackup \ - --bitcoin.mainnet \ - --bitcoin.node=neutrino \ - --fee.url=https://nodes.lightning.computer/fees/v1/btc-fee-estimates.json \ - --neutrino.blockheaderssource=https://block-dn.org/headers/import/latest \ - --neutrino.filterheaderssource=https://block-dn.org/filter-headers/import/latest -``` - -> ⚠️ `--configfile=/dev/null`, `--lnddir=/tmp/...`, `--no-macaroons`, and -> `--noseedbackup` are **test/dev shortcuts**. They bypass the wallet -> seed prompt, disable authentication on the RPC, and use throwaway state -> that will be discarded on the next boot. They are **not** production -> defaults — production deployments should retain macaroons, seed -> backup, and a persistent `lnddir`. - -## Validation - -Header import shares the same validation pipeline as P2P headers, scoped -per network: - -| Network | Validation flags | Notes | -|------------------|------------------|----------------------------------------------| -| mainnet, testnet | `BFNone` | Full contextual validation (PoW + MTP + ancestor checks). | -| simnet, regtest | `BFFastAdd` | Skip contextual checks so the harness can ingest rapidly-mined timestamps. | - -Together with the protections below, this means importing from block-dn.org -on a public network does not require trusting the host: the chain is -fully validated locally, and the P2P catch-up step provides an independent -cross-check against the honest network. - -- **Proof-of-work validation**: All imported block headers must satisfy the - network's PoW target. An attacker cannot serve invalid headers without - finding the cumulative work to back them. - -- **Network magic check**: The import file's network magic must match the - configured Bitcoin network, preventing accidental cross-network imports. - -- **Filter header consistency**: Filter headers are validated against the - block headers to ensure consistency. - -- **P2P fallback**: After import, neutrino continues syncing via P2P. The - P2P network provides an independent check — if the imported headers - diverge from the honest chain, the P2P sync will detect and correct - this. - -For additional assurance, you can combine header import with neutrino's -existing `assertfilterheader` option to checkpoint a known-good filter -header hash at a specific height: - -```ini -[neutrino] -neutrino.assertfilterheader=800000:0123456789abcdef... -``` - -## What Happens After Import - -The import target is only the start of the chain neutrino has on disk — -it is not the chain tip. After the metadata + raw headers are ingested, -neutrino: - -1. Connects to peers and announces the imported height. -2. Fetches the remaining block headers from the import target up to the - current tip over P2P. -3. Fetches the remaining compact filter headers in the same range. -4. Marks the backend as synced. - -For a 900,000-block mainnet import on a typical residential connection, -step 1 takes seconds, the import itself takes single-digit seconds, and -steps 2–4 add roughly the time required to fetch the remaining -~50,000-block tail. - -## Troubleshooting - -### `both neutrino.blockheaderssource and neutrino.filterheaderssource must be specified together` - -Both options must be set together. If you only need block headers, you -still must provide a filter headers source, and vice versa. - -### `--fee.url parameter required when running neutrino on mainnet` - -Mainnet neutrino has no mempool to derive fee estimates from. Add -`--fee.url` (or `fee.url=...` in `lnd.conf`) pointing at a fee estimate -JSON endpoint such as `https://nodes.lightning.computer/fees/v1/btc-fee-estimates.json`. - -### HTTP download failures - -If using HTTP sources, ensure the URL is reachable and the `end_block` -value in the URL is divisible by `entries_per_header_file` (currently -`100,000`). Check the service's `/status` page to confirm the highest -valid target. - -### `failed to deserialize import metadata` - -The import file is missing or has a corrupt metadata prefix. Ensure the -file includes the 10-byte metadata header. Files copied directly from -neutrino's data directory need metadata added via -`chainimport.AddHeadersImportMetadata()`. - -### `network magic mismatch` - -The header file was built for a different Bitcoin network than what LND -is configured to use. Ensure the header file matches your -`bitcoin.network` setting (mainnet, testnet, signet, etc.). - -### Import succeeds but P2P catch-up stalls or hangs - -This is the symptom of a broken upstream import file: the import -chain-links cleanly within the file, but the file's terminal header is -inconsistent with what live peers serve. The current known case is -testnet3 `4900000` (see the warning above). Pin to a known-good -end-block target (`4700000` for testnet3 at present) until the source -is regenerated. diff --git a/docs/onion_message_rate_limiting.md b/docs/onion_message_rate_limiting.md deleted file mode 100644 index c683650a4..000000000 --- a/docs/onion_message_rate_limiting.md +++ /dev/null @@ -1,221 +0,0 @@ -# Onion Message Rate Limiting - -Your node forwards an onion message. The peer on the other side paid -nothing. You paid for the bandwidth that carried it, the CPU that peeled -one Sphinx layer off it, and the disk I/O that checked its HMAC against -the replay database. That asymmetry is the whole problem this document -is about. - -This guide explains how lnd bounds the cost of forwarded onion messages -and why each piece of the defense exists. The audience is operators who -want to understand what the knobs do before turning them, and -contributors who want the shape of the system before they read the code. - -## The adversary - -Picture a peer that sends you a maximum-size onion message as fast as -its link allows. Nothing in the protocol asks it to pay per message, so -it keeps going. Forwarding is the escape hatch a spammer wants: free -bandwidth, reachable anywhere on the network, impossible to charge for -individually. - -Now picture a peer that does this from a thousand identities at once. A -single-peer bandwidth cap does not stop it. Ten thousand peers each -sending a modest trickle can still saturate any aggregate cap you -choose, if new identities are free to mint. - -lnd's defense runs in two layers. The first turns peer identity into a -capital cost so that mint-at-will stops working. The second caps the -bandwidth any one peer and the node as a whole can burn on onion -messages. The two layers compose: cheap Sybils hit the first gate, a -legitimate peer that goes off the rails hits the second. - -## Layer one: the channel-presence gate - -Before the rate limiters see an incoming onion message, lnd asks a -blunt question about the peer that sent it: does this peer have at -least one fully open channel with us? If not, the message is dropped. -No tokens debited, no Sphinx work done, no replay lookup issued. - -A funded channel is the cheapest thing lnd has that an attacker cannot -fake. Opening one costs an on-chain transaction fee and locks up -capital in a funding output. That cost is small for an honest peer -that plans to use the channel; it is prohibitive for an attacker that -wants ten thousand disposable identities. The gate inherits that -economic property for free. - -Pending channels do not satisfy the gate. They are deliberately -excluded: a pending channel is one where we have sent or received a -funding transaction but the channel is not yet confirmed, so the -capital is not yet locked in. Pending channels are also cheap to open -and, under adversarial conditions, easy to leave stuck. Counting them -would hand the attacker the same free-identity primitive the gate -exists to remove. - -The gate runs on a hot path — every onion message pays its cost. It -reads one atomic counter that shadows the count of fully-open channels -for the peer, which keeps the check to a single `Load` instruction. No -map iteration, no lock. - -## Layer two: the rate limiters - -Past the gate, two token-bucket limiters bound the bandwidth onion -messages can consume. - -- The **per-peer** limiter draws from one bucket per peer key. A peer - that opens its throttle to the wall hits this first. -- The **global** limiter draws from a single bucket shared across the - whole node. A thousand peers each sending just under their per-peer - cap hit this. - -The two run in series: per-peer first, global second. A peer whose -own bucket is empty never gets to debit the global bucket on a -rejected attempt, so a hostile peer cannot drain the shared budget by -sending messages it knows will be dropped. - -### Tokens are bytes, not messages - -The buckets hold bytes. Each onion message debits its on-the-wire -size. A small control message pays proportionally less of the budget -than a spec-maximum 32 KiB one. The configured limits therefore -reflect bandwidth, not message counts — which is the thing operators -actually care about when they decide how much of their pipe to give -away. - -The alternative — counting raw messages — lets an attacker choose the -worst cost profile and pay a fixed count for it. Byte accounting -denies the attacker that optimization. - -### Per-peer state lives across disconnect - -The per-peer bucket does not reset when the peer disconnects. If it -did, a hostile peer could cycle the connection to roll its bucket -back to full. Instead, the bucket is keyed by the peer's compressed -public key and retained. The memory footprint is bounded by the -channel-presence gate: only peers with a channel ever allocate a -per-peer bucket in the first place, so the set cannot grow -unboundedly through connection churn. - -## The escape hatch: `protocol.onion-msg-relay-all` - -Some operators run nodes that are supposed to accept onion messages -from everywhere: test nodes, public relays, research nodes. For those -cases `protocol.onion-msg-relay-all=true` disables the -channel-presence gate. Messages from peers with no channel are -admitted into the rate-limiter pipeline as if the gate were not -there. The rate limiters still apply. - -The tradeoff is explicit: you trade the Sybil-resistance property of -the gate for reachability. With `relay-all=true`, a peer that costs -nothing to spin up can now burn a full per-peer byte budget on each -identity. The global limiter is the only line of defense left. Only -enable this if you understand that and are willing to sit behind the -global cap alone. - -The default is `false`. For an ordinary routing or personal node, -leaving it off is the right choice. - -## The knobs - -All four onion-message rate-limit settings live under the `protocol` -section. - -| Flag | Default | Meaning | -| --- | --- | --- | -| `protocol.onion-msg-peer-kbps` | `512` | Per-peer sustained rate in decimal kilobits per second. | -| `protocol.onion-msg-peer-burst-bytes` | `262144` | Per-peer token bucket depth in bytes. | -| `protocol.onion-msg-global-kbps` | `5120` | Global sustained rate in decimal kilobits per second. | -| `protocol.onion-msg-global-burst-bytes` | `1638400` | Global token bucket depth in bytes. | -| `protocol.onion-msg-relay-all` | `false` | If true, skip the channel-presence gate. | - -### Rules at startup - -A few configurations are invalid and rejected before the node starts: - -- A rate with a zero burst, or a burst with a zero rate, is rejected. - Either both are positive (the limiter is enabled) or both are zero - (the limiter is disabled). Mixing the two would silently disable - the limiter and leave the operator thinking they had protection. -- A burst smaller than the maximum-sized onion message wire size - (65535 bytes) is rejected. A bucket that cannot fit a single valid - message would reject every call, regardless of the rate. - -Both of these are startup errors, not runtime ones — the node fails -fast so that the operator sees the mistake immediately. - -### Default sizing - -The defaults assume a routing node that wants onion-message forwarding -to be present but not dominant. - -- `0.5 Mbps` per peer, `5 Mbps` globally. An individual peer is - capped well below a typical residential uplink; the aggregate is - sized so onion message forwarding cannot dwarf the bandwidth a - routing node uses for payments. -- `256 KiB` per-peer burst, `1.6 MiB` global burst. Both are many - multiples of a spec-max message, so short bursts of legitimate - traffic are absorbed without rejection, and the long-term rate - remains bounded by the kbps settings. -- At default rates, ten peers pushing their per-peer allowance - simultaneously exactly fills the global budget. Push the per-peer - rate down or the global rate up if you expect many onion-active - peers. - -## Operator recipes - -**I want to disable the per-peer limiter, keep only the global cap.** -Set both per-peer values to zero: - -``` -protocol.onion-msg-peer-kbps=0 -protocol.onion-msg-peer-burst-bytes=0 -``` - -The global limiter still runs. Be aware you have given up the per-peer -fairness: one loud peer can consume the entire global budget. - -**I want to disable all rate limiting on this feature.** Set all four -values to zero. Read the adversary section above first; if you are not -sure whether you want this, you do not want this. - -**I want to run a public relay that accepts onion messages from -anyone.** Enable the relay-all flag and raise the global cap: - -``` -protocol.onion-msg-relay-all=true -protocol.onion-msg-global-kbps=51200 -protocol.onion-msg-global-burst-bytes=16384000 -``` - -You are now defended by the global cap alone. Monitor traffic. - -**I run a hobbyist node and never use onion messages myself.** -Leave the defaults. Onion-message traffic will land below the caps -and you will never notice them. - -## What happens when a limiter trips - -The first time either limiter trips, lnd emits a one-shot info log so -you can tell that a cap has become load-bearing. - -- Per-peer trips log with the peer's own log prefix, so you know - which peer drove it. -- Global trips log without a peer prefix — the shared bucket is not - attributable to any single peer. - -Subsequent drops are logged at trace level. A sustained attack -therefore does not flood your logs; the single info line already tells -you the system is engaged. - -The limiter tracks a per-limiter drop counter you can read from the -logs or through diagnostics. Rising drop counts on the per-peer side -usually mean you should look at which peer is responsible; rising -drops on the global side usually mean the aggregate cap is too tight -for your traffic. - -## Further reading - -- Release notes for the feature: - [#10713](https://github.com/lightningnetwork/lnd/pull/10713). -- Onion message specification in the BOLTs: - [BOLT 4](https://github.com/lightning/bolts/blob/master/04-onion-routing.md). diff --git a/docs/release-notes/release-notes-0.20.0.md b/docs/release-notes/release-notes-0.20.0.md index 53a788bb2..e01f6cfb0 100644 --- a/docs/release-notes/release-notes-0.20.0.md +++ b/docs/release-notes/release-notes-0.20.0.md @@ -283,7 +283,7 @@ reader of a payment request. * [Require invoices to include a payment address or blinded paths](https://github.com/lightningnetwork/lnd/pull/9752) to comply with updated BOLT 11 specifications before sending payments. -* [LND can now recognize DNS address type in node +* [LND can now recgonize DNS address type in node announcement msg](https://github.com/lightningnetwork/lnd/pull/9455). This allows users to forward node announcement with valid DNS address types. The validity aligns with Bolt 07 DNS constraints. diff --git a/docs/release-notes/release-notes-0.20.1.md b/docs/release-notes/release-notes-0.20.1.md index 1c5cb7eea..02dd849f6 100644 --- a/docs/release-notes/release-notes-0.20.1.md +++ b/docs/release-notes/release-notes-0.20.1.md @@ -64,16 +64,16 @@ * [Fix potential sql tx exhaustion issue](https://github.com/lightningnetwork/lnd/pull/10428) in LND which might happen when running postgres with a limited number of connections configured. + +* Fix a bug where [missing edges for own channels could not be added to the + graph DB](https://github.com/lightningnetwork/lnd/pull/10443) + due to validation checks in the graph Builder that were resurfaced after the + graph refactor work. * [Add missing payment address/secret when probing an invoice](https://github.com/lightningnetwork/lnd/pull/10439). This makes sure the EstimateRouteFee API can probe Eclair and LDK nodes which enforce the payment address/secret. - -* Fix a bug where [missing edges for own channels could not be added to the - graph DB](https://github.com/lightningnetwork/lnd/pull/10410) - due to validation checks in the graph Builder that were resurfaced after the - graph refactor work. * [Fix backwards compatibility for channel edge feature deserialization](https://github.com/lightningnetwork/lnd/pull/10529). Nodes @@ -159,5 +159,4 @@ * Abdulkbk * bitromortac * Matt Morehouse -* Olaoluwa Osuntokun * Ziggie diff --git a/docs/release-notes/release-notes-0.20.3.md b/docs/release-notes/release-notes-0.20.3.md deleted file mode 100644 index 82ed80798..000000000 --- a/docs/release-notes/release-notes-0.20.3.md +++ /dev/null @@ -1,116 +0,0 @@ -# Release Notes -- [Bug Fixes](#bug-fixes) -- [New Features](#new-features) - - [Functional Enhancements](#functional-enhancements) - - [RPC Additions](#rpc-additions) - - [lncli Additions](#lncli-additions) -- [Improvements](#improvements) - - [Functional Updates](#functional-updates) - - [RPC Updates](#rpc-updates) - - [lncli Updates](#lncli-updates) - - [Breaking Changes](#breaking-changes) - - [Performance Improvements](#performance-improvements) - - [Deprecations](#deprecations) -- [Technical and Architectural Updates](#technical-and-architectural-updates) - - [BOLT Spec Updates](#bolt-spec-updates) - - [Testing](#testing) - - [Database](#database) - - [Code Health](#code-health) - - [Tooling and Documentation](#tooling-and-documentation) -- [Contributors (Alphabetical Order)](#contributors-alphabetical-order) - -# Bug Fixes - -* [Bounded the memory used while syncing the channel - graph](https://github.com/lightningnetwork/lnd/pull/10992). A peer replying - to our `query_channel_range` could previously make us buffer an - unpredictable number of short channel IDs, as the only limit was a coarse - 67MB cap on the bytes a single zlib-compressed reply could decompress to. - Replies are now capped at a precise number of short channel IDs, both - per-message and in aggregate across a single query, and the accumulated - reply state is released as soon as any reply fails validation so that a - peer cannot pin it by deliberately forcing an error. - -* [Refined invoice update - handling](https://github.com/lightningnetwork/lnd/pull/11024) across MPP, AMP, - and legacy payment paths, including keysend records and preimage-dependent - settlement outcomes. - -* [Fixed a data race](https://github.com/lightningnetwork/lnd/pull/11019) in the - legacy cooperative close state machine, which was advanced from both the link - goroutine and the peer goroutine with nothing synchronizing the two. The link - now reports a flushed channel to the peer's channel manager instead of driving - the closer itself, so every step of a close runs on a single goroutine. The - same change has the RBF closer validate the remote party's delivery script in - all cases, rather than only when an upfront shutdown script was on record for - that peer, and rejects an absent script instead of treating it as nothing to - check. - -* Outgoing contest resolvers now [retain the corresponding incoming HTLC - expiry](https://github.com/lightningnetwork/lnd/pull/11032) when transitioning - to timeout resolution, allowing the sweeper to continue using an - expiry-aware confirmation target. - -# New Features - -## Functional Enhancements - -## RPC Additions - -* The [HTLC interceptor](https://github.com/lightningnetwork/lnd/pull/10942) now - exposes the next hop of a blinded route that identifies it by node ID - (`next_node_id`) rather than by channel. - -## lncli Additions - -# Improvements - -## Functional Updates - -* [The HTLC forward interceptor now validates](https://github.com/lightningnetwork/lnd/pull/11028) - that derived auto-fail heights are within the supported range before they are - exposed through the interceptor API. - -## RPC Updates - -* `ForwardHtlcInterceptRequest.outgoing_requested_chan_id` now holds a reserved - sentinel value (`18446744073709551615`, all bits set) when the - [HTLC interceptor](https://github.com/lightningnetwork/lnd/pull/10942) reports - a blinded forward that identifies the next hop by node ID. The sender of such - a forward requests no channel, so a zero value here would make a client that - detects the exit hop by a zero channel ID classify the forward as a final - receive. Clients that switch on this field must handle the sentinel and read - `outgoing_requested_node_id` for the next hop. - -## lncli Updates - -## Breaking Changes - -## Performance Improvements - -## Deprecations - -# Technical and Architectural Updates - -## BOLT Spec Updates - -* [Fixed an issue](https://github.com/lightningnetwork/lnd/pull/10942) where an - lnd node acting as a relaying node (including the introduction node) in a - blinded path failed to forward the payment when the next hop was identified by - node ID (`next_node_id`) rather than a short channel ID. The next hop's public - key is now resolved to one of our channels with that peer using non-strict - forwarding. - -## Testing - -## Database - -## Code Health - -## Tooling and Documentation - -# Contributors (Alphabetical Order) - -* bitromortac -* Olaoluwa Osuntokun -* Ziggie diff --git a/docs/release-notes/release-notes-0.21.0.md b/docs/release-notes/release-notes-0.21.0.md index 2daafe61e..9749b5807 100644 --- a/docs/release-notes/release-notes-0.21.0.md +++ b/docs/release-notes/release-notes-0.21.0.md @@ -16,623 +16,40 @@ - [Testing](#testing) - [Database](#database) - [Code Health](#code-health) - - [Robustness](#robustness) - [Tooling and Documentation](#tooling-and-documentation) -- [Contributors (Alphabetical Order)](#contributors-alphabetical-order) +- [Contributors (Alphabetical Order)](#contributors) # Bug Fixes -* The [remote-signer PSBT prep](https://github.com/lightningnetwork/lnd/pull/10815) - now accepts zero-value entries from a sign descriptor's - `PrevOutputFetcher` when populating `WitnessUtxo` on non-signed - inputs. This unblocks signing flows that reference virtual prev - outputs whose value is mandated to be zero, most notably BIP-322's - `to_spend` output (the prev of input 0 of every BIP-322 `to_sign` - transaction). Previously the prep stage silently skipped such - inputs and the resulting PSBT was rejected downstream by - `walletkit.SignPsbt` with `input (index=N) doesn't specify any - UTXO info`. - -* [Fixed `OpenChannel` with - `fund_max`](https://github.com/lightningnetwork/lnd/pull/10488) to use the - protocol-level maximum channel size instead of the user-configured - `maxchansize`. The `maxchansize` config option is intended only for limiting - incoming channel requests from peers, not outgoing ones. - -- Chain notifier RPCs now [return the gRPC `Unavailable` - status](https://github.com/lightningnetwork/lnd/pull/10352) while the - sub-server is still starting. This allows clients to reliably detect the - transient condition and retry without brittle string matching. - -- [Fixed TLV decoders to reject malformed records with incorrect lengths](https://github.com/lightningnetwork/lnd/pull/10249). - TLV decoders now strictly enforce fixed-length requirements for Fee (8 bytes), - Musig2Nonce (66 bytes), ShortChannelID (8 bytes), Vertex (33 bytes), and - DBytes33 (33 bytes) records, preventing malformed TLV data from being - accepted. - -- [Fixed `MarkCoopBroadcasted` to correctly use the `local` - parameter](https://github.com/lightningnetwork/lnd/pull/10532). The method was - ignoring the `local` parameter and always marking cooperative close - transactions as locally initiated, even when they were initiated by the remote - peer. - -- [Fixed a panic in the gossiper](https://github.com/lightningnetwork/lnd/pull/10463) - when `TrickleDelay` is configured with a non-positive value. The configuration - validation now checks `TrickleDelay` at startup and defaults it to 1 - millisecond if set to zero or a negative value, preventing `time.NewTicker` - from panicking. - -* [Fixed `lncli unlock` to wait until the wallet is ready to be - unlocked](https://github.com/lightningnetwork/lnd/pull/10536) - before sending the unlock request. The command now reports wallet state - transitions during startup, avoiding lost unlocks during slow database - initialization. - -* [Fixed handling of BOLT 1 pings requesting 65532 or more pong - bytes](https://github.com/lightningnetwork/lnd/pull/10674). LND now ignores - these valid no-reply pings instead of disconnecting peers, restoring - compatibility with implementations that pad `channel_reestablish` messages - with them. - -* [Fixed `FundingPKScript` to honor the taproot feature bit on v1 channel - edges](https://github.com/lightningnetwork/lnd/pull/10672). Private taproot - channels stored as v1 gossip objects with the taproot staging feature bit - were having their funding scripts incorrectly reconstructed as legacy P2WSH - multisig. This affected read paths such as `ChannelView`, which rebuilds - the chain watch filter on restart. This was a pre-existing bug since - private taproot channels were first introduced. - -* [Fixed a shutdown race in the - channel link](https://github.com/lightningnetwork/lnd/pull/10719) - that could deadlock the invoice registry during concurrent peer disconnect. - The link now waits for `htlcManager` to fully exit before tearing down hodl - subscriptions and the hodl queue, preventing orphaned subscriptions from - blocking invoice resolution. - -* [Fixed two follow-ups to the production taproot channels - work](https://github.com/lightningnetwork/lnd/pull/10763). The RPC channel - acceptor switch now maps `SIMPLE_TAPROOT_FINAL` (with every combination of - the `scid-alias` / `zero-conf` modifiers) so final-taproot opens are - reported to external acceptor clients with the correct commitment type - instead of `UNKNOWN_COMMITMENT_TYPE`. The taproot RBF cooperative-close - auto-enable is also narrowed to skip taproot-overlay channels, since the - RBF close state machine does not yet thread through the `AuxCloser` hook - that overlay channels rely on to build aux-aware close transactions. - -* [Fixed `EstimateRouteFee`](https://github.com/lightningnetwork/lnd/pull/10771) - to use independent probe payment hashes when probing multiple LSPs, preventing - later probes from reusing the first probe's CLTV delta. - -* [Restored insta-dispatch of `CLOSED_CHANNEL` on the first confirmation of a - cooperative close](https://github.com/lightningnetwork/lnd/pull/10794). - After the multi-conf reorg-aware close dispatch landed, - `SubscribeChannelEvents` no longer emitted `CLOSED_CHANNEL` until the full - required confirmation depth was reached. The chain watcher now fires an - early `CLOSED_CHANNEL` event over the channel notifier as soon as the coop - close spend lands on chain, restoring the v0.20.1 behavior, while the - channel arbitrator suppresses the duplicate event that would otherwise be - emitted from `MarkChannelClosed` at the final confirmation depth. - # New Features - -- [Basic Support](https://github.com/lightningnetwork/lnd/pull/9868) for onion - [messaging forwarding](https://github.com/lightningnetwork/lnd/pull/10089). - This adds a new message type, `OnionMessage`, comprising a path key and an - onion blob. It includes the necessary serialization and deserialization logic - for peer-to-peer communication. - ## Functional Enhancements -* Added [pathfinding support](https://github.com/lightningnetwork/lnd/pull/10612) - for routing onion messages. The router can now find paths through the channel - graph specifically filtered for nodes that advertise support for onion - messaging (feature bit 38/39). - -* [Added fast initial sync for `neutrino`-backed nodes via header - import](https://github.com/lightningnetwork/lnd/pull/10552). On first startup, - the neutrino backend can now bootstrap its block and filter header chains - from a local file or HTTP(S) URL instead of fetching them over P2P, - dramatically reducing the time-to-sync on fresh installs (minutes instead of - hours on mainnet). After the import completes, the node transitions to the - normal P2P sync path to catch up to chain tip and stay current. The feature - is gated on the two new `neutrino.blockheaderssource` and - `neutrino.filterheaderssource` options, which must be specified together; - the import source already validates header linkage and proof-of-work, so - contextual timestamp checks are skipped during import to accommodate - rapidly-mined regtest/simnet headers. See - [docs/neutrino_headers_import.md](../neutrino_headers_import.md) for the - supported source URLs (e.g. `block-dn.org` for mainnet), file format, and - operator guidance. - -* [Added reorg protection for channel - closes](https://github.com/lightningnetwork/lnd/pull/10331). Previously, - channel closes were considered final immediately on spend detection with no - confirmation waiting. Now, all channel closes require between 3 and 6 - confirmations, scaled linearly with channel capacity up to the maximum - non-wumbo channel size (~0.168 BTC), with wumbo channels always requiring - 6 confirmations. - -* [Added support for production (final) simple taproot - channels](https://github.com/lightningnetwork/lnd/pull/9985) using the - finalized taproot channel scripts with feature bits 80/81. Production taproot - channels use optimized scripts (`OP_CHECKSIGVERIFY` instead of `OP_CHECKSIG` + - `OP_DROP`) and a map-based nonce encoding in `channel_reestablish` and - `revoke_and_ack` keyed by funding TXID, laying the groundwork for splice - support. The nonce type is now auto-detected from the negotiated channel type - rather than peer feature bits, ensuring correct behavior across all recovery - and resynchronization paths. Taproot channels must be requested explicitly - with `lncli openchannel --channel_type=taproot` (the bare `taproot` string - now selects the production variant; `taproot-staging` opens the legacy - staging variant, and `taproot-final` is kept as a deprecated alias for - `taproot`), and must remain private until announced taproot channels are - supported. The RPC `CommitmentType` enum gains a `TAPROOT` alias for - `SIMPLE_TAPROOT_FINAL` so new RPC clients can use the same short name. - -* [Added taproot channel support for RBF cooperative - close](https://github.com/lightningnetwork/lnd/pull/10063). The new RBF-based - cooperative close protocol (enabled with `--protocol.rbf-coop-close`) now - fully supports simple taproot channels. This includes MuSig2 partial signature - handling with the JIT (just-in-time) nonce pattern, where closer nonces are - bundled with signatures in `ClosingComplete` and closee nonces are rotated via - `NextCloseeNonce` in `ClosingSig` for each RBF iteration. The implementation - prevents nonce reuse across RBF rounds by storing the `MusigPartialSig` in the - protocol state machine and invalidating nonces after each signing round - completes. - -* [Added rate limiting and a channel-presence gate for incoming onion - messages](https://github.com/lightningnetwork/lnd/pull/10713). Two new - byte-denominated token-bucket limiters run at ingress — one per peer, one - global — so small onion messages pay proportionally less of the budget - than spec-max ones. Defaults are `0.5 Mbps` (512 Kbps, 256 KiB burst) per - peer and `5 Mbps` (5120 Kbps, 1600 KiB burst) globally, tunable via - `protocol.onion-msg-peer-kbps`, - `protocol.onion-msg-peer-burst-bytes`, - `protocol.onion-msg-global-kbps`, and - `protocol.onion-msg-global-burst-bytes`. Setting both the rate and the - burst of a given limiter to `0` disables it; setting only one to `0`, or - a burst smaller than a maximum-sized onion message, is rejected at - startup. Incoming onion messages from peers with no fully open channel - are also dropped at ingress as a Sybil-resistance layer; pending - channels are excluded. Operators who want to accept onion messages - from peers regardless of channel state can set - `protocol.onion-msg-relay-all=true` to skip the channel-presence gate; - the rate limiters still apply. See - [docs/onion_message_rate_limiting.md](../onion_message_rate_limiting.md) - for the adversary model, the layers, the defaults, and operator - recipes. ## RPC Additions -* [Added `DeleteForwardingHistory` - RPC](https://github.com/lightningnetwork/lnd/pull/10666) to the router - sub-server, allowing operators to selectively purge old forwarding events from - the database. Deletion requires the target cutoff timestamp to be at least 1 - hour in the past, preventing accidental removal of recent data. - -* The `WaitingCloseChannel` response in `PendingChannels` now includes two - new fields via [#10509](https://github.com/lightningnetwork/lnd/pull/10509): - `blocks_til_close_confirmed`, showing the remaining confirmations until a - closed channel is considered fully resolved, and `close_height`, the block - height at which the closing transaction was first confirmed. These build on - the reorg-safe confirmation logic introduced in - [#10331](https://github.com/lightningnetwork/lnd/pull/10331), where the - required number of confirmations scales with channel capacity. - -* [Added support for coordinator-based MuSig2 signing - patterns](https://github.com/lightningnetwork/lnd/pull/10436) with two new - RPCs: `MuSig2RegisterCombinedNonce` allows registering a pre-aggregated - combined nonce for a session (useful when a coordinator aggregates all nonces - externally), and `MuSig2GetCombinedNonce` retrieves the combined nonce after - it becomes available. These methods provide an alternative to the standard - `MuSig2RegisterNonces` workflow and are only supported in MuSig2 v1.0.0rc2. - -* The `EstimateFee` RPC now supports [explicit input - selection](https://github.com/lightningnetwork/lnd/pull/10296). Users can - specify a list of inputs to use as transaction inputs via the new - `inputs` field in `EstimateFeeRequest`. - ## lncli Additions -* The `estimatefee` command now supports the `--utxos` flag to specify explicit - inputs for fee estimation. -* The `walletrpc.SignPsbt` now has a [corresponding `lncli wallet psbt sign` - command, and can be used to sign a - PSBT](https://github.com/lightningnetwork/lnd/pull/10659). - - # Improvements ## Functional Updates -* [Allow multiple read-only RPC middleware - interceptors](https://github.com/lightningnetwork/lnd/pull/10611) to be - registered simultaneously. - -* [Added support](https://github.com/lightningnetwork/lnd/pull/9432) for the - `upfront-shutdown-address` configuration in `lnd.conf`, allowing users to - specify an address for cooperative channel closures where funds will be sent. - This applies to both funders and fundees, with the ability to override the - value during channel opening or acceptance. - -* Rename [experimental endorsement signal](https://github.com/lightning/blips/blob/a833e7b49f224e1240b5d669e78fa950160f5a06/blip-0004.md) - to [accountable](https://github.com/lightningnetwork/lnd/pull/10367) to match - the latest [proposal](https://github.com/lightning/blips/pull/67). - ## RPC Updates -* routerrpc HTLC event subscribers now receive specific failure details for - invoice-level validation failures, avoiding ambiguous `UNKNOWN` results. [#10520](https://github.com/lightningnetwork/lnd/pull/10520) - -* [A new `wallet_synced` field has been - added](https://github.com/lightningnetwork/lnd/pull/10507) to the `GetInfo` - RPC response. This field indicates whether the wallet is fully synced to the - best chain, providing the wallet's internal sync state independently from the - composite `synced_to_chain` field which also considers router and blockbeat - dispatcher states. - -* SubscribeChannelEvents [now emits channel update - events](https://github.com/lightningnetwork/lnd/pull/10543) to be able to - subscribe to state changes. - -* The [`GetDebugInfo`](https://github.com/lightningnetwork/lnd/pull/10613) RPC - request now accepts an `include_log` flag. By default, only the configuration - map is returned. When `include_log` is set to `true`, the log file content is - also included in the response. - ## lncli Updates -* The `getdebuginfo` command now supports an `--include_log` flag. By default, - only the daemon's configuration is returned. When set, the log file content is - also included in the response. - -* The `encryptdebugpackage` command now supports an `--include_log` flag. When - set, the log file content is included in the encrypted debug package. - ## Breaking Changes -* [Increased MinCLTVDelta from 18 to - 24](https://github.com/lightningnetwork/lnd/pull/10331) to provide a larger - safety margin above the `DefaultFinalCltvRejectDelta` (19 blocks). This - affects users who create invoices with custom `cltv_expiry_delta` values - between 18-23, which will now require a minimum of 24. The default value of - 80 blocks for invoice creation remains unchanged, so most users will not be - affected. Existing invoices created before the upgrade will continue to work - normally. - -* The [`GetDebugInfo`](https://github.com/lightningnetwork/lnd/pull/10613) RPC - no longer returns log file content by default. Clients that rely on the `log` - field must now explicitly set `include_log` to `true` in the request. The - `lncli getdebuginfo` and `lncli encryptdebugpackage` commands similarly - require the `--include_log` flag to include logs in the output. - -* [Removed the deprecated payment RPCs and `outgoing_chan_id` - field](https://github.com/lightningnetwork/lnd/pull/10814) that were - [announced for removal in 0.21](https://github.com/lightningnetwork/lnd/blob/master/docs/release-notes/release-notes-0.20.0.md#deprecations) - via the 0.20 release notes. Callers must migrate to the V2 equivalents: - - | Removed RPC | Replacement | - |-------------|-------------| - | `lnrpc.SendPayment` (streaming) | `routerrpc.SendPaymentV2` | - | `lnrpc.SendPaymentSync` | `routerrpc.SendPaymentV2` | - | `lnrpc.SendToRoute` (streaming) | `routerrpc.SendToRouteV2` | - | `lnrpc.SendToRouteSync` | `routerrpc.SendToRouteV2` | - | `routerrpc.SendPayment` (streaming) | `routerrpc.SendPaymentV2` | - | `routerrpc.SendToRoute` | `routerrpc.SendToRouteV2` | - | `routerrpc.TrackPayment` (streaming) | `routerrpc.TrackPaymentV2` | - - This also removes the corresponding REST routes - `POST /v1/channels/transaction-stream`, `POST /v1/channels/transactions`, - and `POST /v1/channels/transactions/route`. The orphan - `routerrpc.SendToRouteResponse` message (only used by the removed - `routerrpc.SendToRoute` RPC) is also dropped. - - In addition, the deprecated `outgoing_chan_id` field is removed from - `lnrpc.QueryRoutesRequest` and `routerrpc.SendPaymentRequest` (proto tags - 14 and 8 respectively, now reserved). Callers must use the multi-channel - `outgoing_chan_ids` field introduced in 0.20. - -* [Removed the deprecated `--tor.v2` configuration - flag](https://github.com/lightningnetwork/lnd/pull/10813). Tor stopped - serving v2 onion services in October 2021, and lnd no longer produces - v2 on any code path; `tor.OnionHostToFakeIP` is also gone. Operator - input is rejected at the boundary: `--externalip`, `--listen`, - `lncli connect`, and `lncli wtclient towers add` fail fast on a v2 - `.onion` string, so operators upgrading with a v2 entry in - `lnd.conf` must remove it before lnd will start. Persisted state - carried over from a previous version is also filtered before use: - the self-node announcement strips any v2 entry from the source-node - record before signing; the watchtower client drops v2 entries from - each persisted tower's address list (skipping the tower entirely if - no non-v2 address remains, so the operator can attach a fresh v3 - address); the autopilot connector, graph bootstrapper, and - static-channel backup restore paths skip v2 entries before attempting - outbound dials. The Tor controller's `ADD_ONION` path is restricted - to v3 keys, including the encrypted on-disk legacy-key fallback. The - on-disk records themselves are left intact. - - Peer-signed announcements that still carry v2 are handled - byte-for-byte: the `lnwire` and `graph/db` codecs round-trip v2 so - `DataToSign` reproduces the signed bytes, signatures validate, and - the announcement is persisted and re-broadcast unchanged. RPCs like - `GetNodeInfo` and `DescribeGraph` still expose the full address - set. - ## Performance Improvements -* Let the [channel graph cache be populated - asynchronously](https://github.com/lightningnetwork/lnd/pull/10065) on - startup. While the cache is being populated, the graph is still available for - queries, but all read queries will be served from the database until the cache - is fully populated. This new behaviour can be opted out of via the new - `--db.sync-graph-cache-load` option. - -* Autopilot's graph-wide channel scoring traversal [no longer requests node - addresses](https://github.com/lightningnetwork/lnd/pull/10796) from the - graph backend, since the scoring code does not consume them. This removes - an unnecessary address batch-load on the SQL backend, and lets the kvdb - backend serve the traversal from the in-memory graph cache when it is - loaded. - -* [Invoice pagination queries no longer use - `OFFSET`](https://github.com/lightningnetwork/lnd/pull/10700). The five - invoice filter queries previously used `LIMIT+OFFSET` for internal batching, - which requires the database to scan and discard all preceding rows on every - page. All pagination is now cursor-based (`WHERE id >= cursor`), making every - page an efficient primary-key range scan regardless of how deep into the - result set the query is. - -* [Replace the catch-all `FilterInvoices` SQL query with five focused, - index-friendly queries](https://github.com/lightningnetwork/lnd/pull/10601) - (`FetchPendingInvoices`, `FilterInvoicesBySettleIndex`, - `FilterInvoicesByAddIndex`, `FilterInvoicesForward`, - `FilterInvoicesReverse`). The old query used `col >= $param OR $param IS - NULL` predicates and a `CASE`-based `ORDER BY` that prevented SQLite's query - planner from using indexes, causing full table scans. Each new query carries - only the parameters it actually needs and uses a direct `ORDER BY`, allowing - the planner to perform efficient index range scans on the invoice table. - -* [Fix full table scans on the HTLC settlement - hot path](https://github.com/lightningnetwork/lnd/pull/10619). - Replace the catch-all `GetInvoice` query (which used `OR $1 IS NULL` - predicates that forced full table scans) with three dedicated queries - targeting uniquely-constrained columns. Also drop four redundant indexes - that duplicated UNIQUE constraints or were never used as query filters. - -* [Optimize the v1 node horizon - query](https://github.com/lightningnetwork/lnd/pull/10692). Split the - `GetNodesByLastUpdateRange` query into separate all-nodes and public-only - variants, removing a dynamic `COALESCE`/`OR` branch that defeated the query - planner. The public-only `EXISTS` check is rewritten as two direct index - probes instead of `node_id_1 OR node_id_2`. Supporting indexes are upgraded - to composite keys matching the full query shapes. On SQLite, the hot - public-only path sees a ~42% speedup; on the previous code it could stall - for minutes. - -* [Tombstone closed channels on KV-over-SQL - backends](https://github.com/lightningnetwork/lnd/pull/10780). Closing a - long-lived channel previously issued a single `DeleteNestedBucket` inside - the close transaction. On the kvdb-on-SQL schema (sqlite, postgres) that - delete fans out into a row-by-row `ON DELETE CASCADE` over the channel's - revocation log and forwarding-package bucket, holding the database - write-lock for many seconds — long enough on channels with millions of - states to stall HTLC forwarding, time out htlcswitch retries, and trigger - force-close cycles. `CloseChannel` now skips the cascading delete on - these backends; the outpoint-index flip from `outpointOpen` to - `outpointClosed` (already performed by the existing close path) is the - authoritative closed-channel marker, and every reader of the open-channel - bucket consults it before treating a channel as open. The bulk historical - state — the chanBucket itself, the revocation log, and the per-channel - forwarding-package bucket — remains on disk for the channel's lifetime in - this database and is reclaimed wholesale by the upcoming native-SQL - channel-state migration. bbolt and etcd retain the synchronous one-shot - close path, where nested-bucket deletion is already cheap. - - > ⚠️ **Downgrade warning.** On sqlite/postgres, once a channel is - > closed under this build the chanBucket and its nested state remain - > on disk; the close is signalled only by the `outpointClosed` flip - > in the outpoint index. Earlier `lnd` releases do not consult that - > flip when iterating `openChannelBucket`, so downgrading to a - > pre-0.21 binary after closing channels on these backends will - > resurrect those channels as open in `listchannels`, - > `pendingchannels`, and the chain-watch path. Operators who close - > channels on sqlite/postgres after upgrading should treat the - > upgrade as one-way for that database; bbolt and etcd users are unaffected - > because the close path on those backends still deletes the chanBucket. - ## Deprecations -### ⚠️ **Warning:** Deprecated fields in `lnrpc.Hop` will be removed in release version **0.22** - - The following deprecated fields in the [`lnrpc.Hop`](https://lightning.engineering/api-docs/api/lnd/lightning/query-routes/#lnrpchop) - message will be removed: - - | Field | Deprecated Since | Replacement | - |-------|------------------|-------------| - | `chan_capacity` | 0.7.1 | None | - | `amt_to_forward` | 0.7.1 | `amt_to_forward_msat` | - | `fee` | 0.7.1 | `fee_msat` | - -### ⚠️ **Warning:** The deprecated fee rate option `--sat_per_byte` will be removed in release version **0.22** - - The deprecated `--sat_per_byte` option will be fully removed. This flag was - originally deprecated and hidden from the lncli commands in v0.13.0 - ([PR#4704](https://github.com/lightningnetwork/lnd/pull/4704)). Users should - migrate to the `--sat_per_vbyte` option, which correctly represents fee rates - in terms of virtual bytes (vbytes). - - Internally `--sat_per_byte` was treated as sat/vbyte, this meant the option - name was misleading and could result in unintended fee calculations. To avoid - further confusion and to align with ecosystem terminology, the option will be - removed. - - The following RPCs will be impacted: - - | RPC Method | Messages | Removed Option | - |----------------------|----------------|-------------| -| [`lnrpc.CloseChannel`](https://lightning.engineering/api-docs/api/lnd/lightning/close-channel/) | [`lnrpc.CloseChannelRequest`](https://lightning.engineering/api-docs/api/lnd/lightning/close-channel/#lnrpcclosechannelrequest) | sat_per_byte -| [`lnrpc.OpenChannelSync`](https://lightning.engineering/api-docs/api/lnd/lightning/open-channel-sync/) | [`lnrpc.OpenChannelRequest`](https://lightning.engineering/api-docs/api/lnd/lightning/open-channel-sync/#lnrpcopenchannelrequest) | sat_per_byte -| [`lnrpc.OpenChannel`](https://lightning.engineering/api-docs/api/lnd/lightning/open-channel/) | [`lnrpc.OpenChannelRequest`](https://lightning.engineering/api-docs/api/lnd/lightning/open-channel/#lnrpcopenchannelrequest) | sat_per_byte -| [`lnrpc.SendCoins`](https://lightning.engineering/api-docs/api/lnd/lightning/send-coins/) | [`lnrpc.SendCoinsRequest`](https://lightning.engineering/api-docs/api/lnd/lightning/send-coins/#lnrpcsendcoinsrequest) | sat_per_byte -| [`lnrpc.SendMany`](https://lightning.engineering/api-docs/api/lnd/lightning/send-many/) | [`lnrpc.SendManyRequest`](https://lightning.engineering/api-docs/api/lnd/lightning/send-many/#lnrpcsendmanyrequest) | sat_per_byte -| [`walletrpc.BumpFee`](https://lightning.engineering/api-docs/api/lnd/wallet-kit/bump-fee/) | [`walletrpc.BumpFeeRequest`](walletrpc.BumpFeeRequest) | sat_per_byte - # Technical and Architectural Updates ## BOLT Spec Updates ## Testing -* [Added unit tests for TLV length validation across multiple packages](https://github.com/lightningnetwork/lnd/pull/10249). - New tests ensure that fixed-size TLV decoders reject malformed records with - invalid lengths, including roundtrip tests for Fee, Musig2Nonce, - ShortChannelID and Vertex records. - -* [Added a bitcoind-backed miner backend to `lntest`](https://github.com/lightningnetwork/lnd/pull/10481). - Integration tests can now select the miner backend independently from the - chain backend, and CI now covers the `backend=bitcoind - minerbackend=bitcoind` path. - ## Database -* [Prevent silent data corruption](https://github.com/lightningnetwork/lnd/pull/10684) - when reusing the same database across different Bitcoin networks. On first - startup the active network is persisted in a new `chain_params` table; on - every subsequent restart lnd compares the stored value against the configured - network and refuses to start if they differ, printing a clear error message - with remediation steps. This safeguard applies to both the PostgreSQL and - SQLite native-SQL backends when running with `--db.use-native-sql`. - -* Freeze the [graph SQL migration - code](https://github.com/lightningnetwork/lnd/pull/10338) to prevent the - need for maintenance as the sqlc code evolves. -* Prepare the graph DB for handling gossip V2 - nodes and channels [1](https://github.com/lightningnetwork/lnd/pull/10339) - [2](https://github.com/lightningnetwork/lnd/pull/10379) - [3](https://github.com/lightningnetwork/lnd/pull/10380) - [4](https://github.com/lightningnetwork/lnd/pull/10542), - [5](https://github.com/lightningnetwork/lnd/pull/10572), - [6](https://github.com/lightningnetwork/lnd/pull/10582). -* [Version the graph horizon queries (`NodeUpdatesInHorizon`, - `ChanUpdatesInHorizon`)](https://github.com/lightningnetwork/lnd/pull/10691) - to support both v1 (time-based) and v2 (block-height-based) gossip ranges. - The v1 end-time bound is corrected from inclusive to exclusive to match the - BOLT 07 `gossip_timestamp_filter` spec. New SQL queries and composite indexes - are added for efficient v2 block-height range scans. -* [Version `FilterKnownChanIDs` and fix `FetchChannelEdgesByID` zombie - fallback](https://github.com/lightningnetwork/lnd/pull/10717) so that gossip - channel filtering and zombie edge lookups use the correct gossip version - instead of hardcoding v1. -* Updated waiting proof persistence for gossip upgrades by introducing typed - waiting proof keys and payloads, with a DB migration to rewrite legacy - waiting proof records to the new key/value format - ([#10633](https://github.com/lightningnetwork/lnd/pull/10633)). - -* Payment Store SQL implementation and migration project: - * Introduce an [abstract payment - store](https://github.com/lightningnetwork/lnd/pull/10153) interface and - refacotor the payment related LND code to make it more modular. - * Implement the SQL backend for the [payments - database](https://github.com/lightningnetwork/lnd/pull/9147) - * Implement query methods (QueryPayments,FetchPayment) for the [payments db - SQL Backend](https://github.com/lightningnetwork/lnd/pull/10287) - * Implement insert methods for the [payments db - SQL Backend](https://github.com/lightningnetwork/lnd/pull/10291) - * Implement third(final) Part of SQL backend [payment - functions](https://github.com/lightningnetwork/lnd/pull/10368) - * Finalize SQL payments implementation [enabling unit and itests - for SQL backend](https://github.com/lightningnetwork/lnd/pull/10292) - * [Thread context through payment - db functions Part 1](https://github.com/lightningnetwork/lnd/pull/10307) - * [Thread context through payment - db functions Part 2](https://github.com/lightningnetwork/lnd/pull/10308) - * [Finalize SQL implementation for - payments db](https://github.com/lightningnetwork/lnd/pull/10373) - * [Add the KV-to-SQL payment - migration](https://github.com/lightningnetwork/lnd/pull/10485) with - comprehensive tests. The migration is currently dev-only, compiled behind - the `test_db_postgres`, `test_db_sqlite`, or `test_native_sql` build tags. - * Various [SQL payment store - improvements](https://github.com/lightningnetwork/lnd/pull/10535): - optimize schema indexes, improve query performance for payment filtering - and failed attempt cleanup, fix cross-database timestamp handling, add - `omit_hops` option to `ListPayments` to reduce response size, and increase - the default SQLite cache size. - * The [SQL payments migration is promoted to production - code](https://github.com/lightningnetwork/lnd/pull/10627). Previously the - migration was hidden behind the `test_native_sql` build tag; it is now - compiled into mainline builds and available to all users who have the - `native-sql` setting enabled. - - ## Code Health -* [Update taproot detection](https://github.com/lightningnetwork/lnd/pull/10683) - to accommodate buried activation (and modified RPC `getdeploymentinfo` - response) beginning in Bitcoin Core v32. - -* [Migrated gossip result handling from `chan error` to - `actor.Future[error]`](https://github.com/lightningnetwork/lnd/pull/10589). - The three buffered-channel patterns in the discovery package are replaced - with idempotent promises, eliminating a class of latent deadlock bugs when a - deferred message copy was re-enqueued and processed a second time. A new - `lnutils.ContextFromQuit` helper bridges the existing `quit` channels to - `context.Context`, so all gossip awaits now respect shutdown uniformly. - -## Robustness - -* [Drop onion messages that would cycle back to the sending - peer](https://github.com/lightningnetwork/lnd/pull/10754). When the - resolved next hop of an incoming onion message is the same peer that - delivered it, the message is now dropped instead of being forwarded - back over the connection it arrived on. This closes a trivial - traffic-amplification vector and covers both the direct next-node-ID - and SCID-resolved paths. - ## Tooling and Documentation -* [Added missing `lncli:` tags](https://github.com/lightningnetwork/lnd/pull/10658) - for `SendPaymentV2`, `SendToRouteV2`, and `EstimateRouteFee` in the - `routerrpc` proto definitions so that the generated API documentation - correctly links to their corresponding `lncli` commands (`sendpayment`, - `sendtoroute`, `estimateroutefee`). - -* [Overhauled Docker documentation and environment](https://github.com/lightningnetwork/lnd/pull/10461) - to modernize the developer onboarding flow. Key updates include migrating - to Docker Compose V2, updating base images (btcd v0.25.0, Go 1.25.5), - and transitioning the documentation to focus on a more reliable "Simnet" - workflow while removing obsolete faucet references. - -* [Android Lndmobile 16 KB page size for native libraries](https://github.com/lightningnetwork/lnd/pull/10517) - The Android `Lndmobile.aar` build now passes `-Wl,-z,max-page-size=16384` to - the linker, keeping the generated native library compatible with newer - Android devices that use 16 KB memory pages while preserving compatibility - with existing 4 KB page-size devices. - # Contributors (Alphabetical Order) - -* Abdulkbk -* AbelLykens -* ajaysehwal -* Andras Banki-Horvath -* bitromortac -* Boris Nagaev -* Calvin Zachman -* Dario Anongba -* Elle Mouton -* elnosh -* Erick Cestari -* Euler-B -* ffranr -* George Tsagkarelis -* Gijs van Dam -* hieblmi -* Liongrass -* Matt Morehouse -* Matthew Zipkin -* Mohamed Awnallah -* Nishant Bansal -* Olaoluwa Osuntokun -* Oliver Gugger -* Pins -* Suheb -* ViktorT-11 -* Yash Bhutwala -* Yong Yu -* Ziggie diff --git a/docs/release-notes/release-notes-0.21.1.md b/docs/release-notes/release-notes-0.21.1.md deleted file mode 100644 index efb05cd0e..000000000 --- a/docs/release-notes/release-notes-0.21.1.md +++ /dev/null @@ -1,101 +0,0 @@ -# Release Notes -- [Bug Fixes](#bug-fixes) -- [New Features](#new-features) - - [Functional Enhancements](#functional-enhancements) - - [RPC Additions](#rpc-additions) - - [lncli Additions](#lncli-additions) -- [Improvements](#improvements) - - [Functional Updates](#functional-updates) - - [RPC Updates](#rpc-updates) - - [lncli Updates](#lncli-updates) - - [Breaking Changes](#breaking-changes) - - [Performance Improvements](#performance-improvements) - - [Deprecations](#deprecations) -- [Technical and Architectural Updates](#technical-and-architectural-updates) - - [BOLT Spec Updates](#bolt-spec-updates) - - [Testing](#testing) - - [Database](#database) - - [Code Health](#code-health) - - [Robustness](#robustness) - - [Tooling and Documentation](#tooling-and-documentation) -- [Contributors (Alphabetical Order)](#contributors-alphabetical-order) - -# Bug Fixes - -* [Updated the `tor` module to - `v1.1.7`](https://github.com/lightningnetwork/lnd/pull/10907) so fresh - nodes started with `--tor.active --tor.v3` create v3 onion services with - `NEW:ED25519-V3`. Previously, the root module still resolved `tor v1.1.6`, - which could default new onion service creation to the retired v2 - `NEW:RSA1024` key type that modern Tor rejects with `513 Invalid key type`. - -* [Fixed a panic](https://github.com/lightningnetwork/lnd/pull/10914) in the - DNS fallback SRV lookup, which unconditionally type-asserted each DNS Answer - record to `*dns.SRV` and crashed the daemon when the response contained a - non-SRV record. Non-SRV records are now skipped, and an empty `LookupHost` - result for the shim no longer triggers an out-of-bounds index. - -- [Fixed on-chain forward interceptor - settlement](https://github.com/lightningnetwork/lnd/pull/10895) after the - incoming channel force closes. Held forwards are now tracked as off-chain or - on-chain entries, allowing an on-chain re-offer to replace the old off-chain - hold so settlement reaches the witness beacon. Go callers of the exported - `htlcswitch.InterceptedPacket` type should use the new `Deadline` field to - distinguish off-chain auto-fail heights from on-chain settlement deadlines, - or `AutoFailHeight()` if they only need the legacy flattened value. - -# New Features - -## Functional Enhancements - -## RPC Additions - -## lncli Additions - -# Improvements - -## Functional Updates - -* lnd now [validates the CLTV expiry of HTLCs at the final -hop](https://github.com/lightningnetwork/lnd/pull/10927). A final HTLC whose -CLTV expiry falls outside the node's receive policy is failed back, bringing -the final hop in line with the CLTV delta limits already enforced on the -forwarding path. -As part of this change, the channel policy `TimeLockDelta` is -now validated against LND's supported forwarding bounds: any node that -previously set a per-channel `TimeLockDelta` greater than `2016` (the maximum -default value) will now have its `UpdateChannelPolicy` request -rejected, and must lower the value accordingly below the specified maximum. - -## RPC Updates - -## lncli Updates - -## Breaking Changes - -## Performance Improvements - -## Deprecations - -### ⚠️ **Warning:** Deprecated fields in `lnrpc.Hop` will be removed in release version **0.22** - -### ⚠️ **Warning:** The deprecated fee rate option `--sat_per_byte` will be removed in release version **0.22** - -# Technical and Architectural Updates - -## BOLT Spec Updates - -## Testing - -## Database - -## Code Health - -## Robustness - -## Tooling and Documentation - -# Contributors (Alphabetical Order) - -* Erick Cestari -* Ziggie diff --git a/docs/release-notes/release-notes-0.21.2.md b/docs/release-notes/release-notes-0.21.2.md deleted file mode 100644 index 5d991c4c3..000000000 --- a/docs/release-notes/release-notes-0.21.2.md +++ /dev/null @@ -1,145 +0,0 @@ -# Release Notes -- [Bug Fixes](#bug-fixes) -- [New Features](#new-features) - - [Functional Enhancements](#functional-enhancements) - - [RPC Additions](#rpc-additions) - - [lncli Additions](#lncli-additions) -- [Improvements](#improvements) - - [Functional Updates](#functional-updates) - - [RPC Updates](#rpc-updates) - - [lncli Updates](#lncli-updates) - - [Breaking Changes](#breaking-changes) - - [Performance Improvements](#performance-improvements) - - [Deprecations](#deprecations) -- [Technical and Architectural Updates](#technical-and-architectural-updates) - - [BOLT Spec Updates](#bolt-spec-updates) - - [Testing](#testing) - - [Database](#database) - - [Code Health](#code-health) - - [Robustness](#robustness) - - [Tooling and Documentation](#tooling-and-documentation) -- [Contributors (Alphabetical Order)](#contributors-alphabetical-order) - -# Bug Fixes - -* [Fixed several bugs](https://github.com/lightningnetwork/lnd/pull/10948) - in onion message decoding where messages that should have been rejected - per BOLT 4 were instead accepted, or a valid TLV was dropped. - -* [Fixes a bug](https://github.com/lightningnetwork/lnd/pull/10962) that - could allow the RBF closer to be used with incompatible aux channels. - -* [Fixes a payment migration failure](https://github.com/lightningnetwork/lnd/pull/10982) - caused by historical routes containing a blinded total amount without - encrypted recipient data. The migration now normalizes the orphaned total, - and `SendToRouteV2` rejects new routes with the same invalid field - combination. This also affects callers replaying an affected historical - route returned by `ListPayments` or `TrackPayment`. - -* [Fixed a channeldb migration - bug](https://github.com/lightningnetwork/lnd/pull/10985) where databases - initialized without a persisted `metadata/dbp` version key could skip later - mandatory migrations. This recovers such databases from the last known - v0.20-era mandatory version so the v0.21 waiting proof migration runs - without replaying older migrations against an already-initialized database. - -* [Bounded the memory used while syncing the channel - graph](https://github.com/lightningnetwork/lnd/pull/10992). A peer replying - to our `query_channel_range` could previously make us buffer an - unpredictable number of short channel IDs, as the only limit was a coarse - 67MB cap on the bytes a single zlib-compressed reply could decompress to. - Replies are now capped at a precise number of short channel IDs, both - per-message and in aggregate across a single query, and the accumulated - reply state is released as soon as any reply fails validation so that a - peer cannot pin it by deliberately forcing an error. - -* [Refined invoice update - handling](https://github.com/lightningnetwork/lnd/pull/11024) across MPP, AMP, - and legacy payment paths, including keysend records and preimage-dependent - settlement outcomes. - -* [Fixed a data race](https://github.com/lightningnetwork/lnd/pull/11019) in the - legacy cooperative close state machine, which was advanced from both the link - goroutine and the peer goroutine with nothing synchronizing the two. The link - now reports a flushed channel to the peer's channel manager instead of driving - the closer itself, so every step of a close runs on a single goroutine. The - same change has the RBF closer validate the remote party's delivery script in - all cases, rather than only when an upfront shutdown script was on record for - that peer, and rejects an absent script instead of treating it as nothing to - check. - -* Outgoing contest resolvers now [retain the corresponding incoming HTLC - expiry](https://github.com/lightningnetwork/lnd/pull/11032) when transitioning - to timeout resolution, allowing the sweeper to continue using an - expiry-aware confirmation target. - -# New Features - -## Functional Enhancements - -## RPC Additions - -* The [HTLC interceptor](https://github.com/lightningnetwork/lnd/pull/10942) now - exposes the next hop of a blinded route that identifies it by node ID - (`next_node_id`) rather than by channel. - -## lncli Additions - -# Improvements - -## Functional Updates - -* [The HTLC forward interceptor now validates](https://github.com/lightningnetwork/lnd/pull/11028) - that derived auto-fail heights are within the supported range before they are - exposed through the interceptor API. - -## RPC Updates - -* `ForwardHtlcInterceptRequest.outgoing_requested_chan_id` now holds a reserved - sentinel value (`18446744073709551615`, all bits set) when the - [HTLC interceptor](https://github.com/lightningnetwork/lnd/pull/10942) reports - a blinded forward that identifies the next hop by node ID. The sender of such - a forward requests no channel, so a zero value here would make a client that - detects the exit hop by a zero channel ID classify the forward as a final - receive. Clients that switch on this field must handle the sentinel and read - `outgoing_requested_node_id` for the next hop. - -## lncli Updates - -## Breaking Changes - -## Performance Improvements - -## Deprecations - -### ⚠️ **Warning:** Deprecated fields in `lnrpc.Hop` will be removed in release version **0.22** - -### ⚠️ **Warning:** The deprecated fee rate option `--sat_per_byte` will be removed in release version **0.22** - -# Technical and Architectural Updates - -## BOLT Spec Updates - -* [Fixed an issue](https://github.com/lightningnetwork/lnd/pull/10942) where an - lnd node acting as a relaying node (including the introduction node) in a - blinded path failed to forward the payment when the next hop was identified by - node ID (`next_node_id`) rather than a short channel ID. The next hop's public - key is now resolved to one of our channels with that peer using non-strict - forwarding. - -## Testing - -## Database - -## Code Health - -## Robustness - -## Tooling and Documentation - -# Contributors (Alphabetical Order) - -* bitromortac -* Jared Tobin -* Olaoluwa Osuntokun -* Ziggie diff --git a/docs/release-notes/release-notes-0.22.0.md b/docs/release-notes/release-notes-0.22.0.md deleted file mode 100644 index 750a20069..000000000 --- a/docs/release-notes/release-notes-0.22.0.md +++ /dev/null @@ -1,161 +0,0 @@ -# Release Notes -- [Bug Fixes](#bug-fixes) -- [New Features](#new-features) - - [Functional Enhancements](#functional-enhancements) - - [RPC Additions](#rpc-additions) - - [lncli Additions](#lncli-additions) -- [Improvements](#improvements) - - [Functional Updates](#functional-updates) - - [RPC Updates](#rpc-updates) - - [lncli Updates](#lncli-updates) - - [Breaking Changes](#breaking-changes) - - [Performance Improvements](#performance-improvements) - - [Deprecations](#deprecations) -- [Technical and Architectural Updates](#technical-and-architectural-updates) - - [BOLT Spec Updates](#bolt-spec-updates) - - [BOLT 12 (Offers)](#bolt-12-offers) - - [Testing](#testing) - - [Database](#database) - - [Code Health](#code-health) - - [Tooling and Documentation](#tooling-and-documentation) -- [Contributors (Alphabetical Order)](#contributors-alphabetical-order) - -# Bug Fixes - -* Bitcoind outbound peer health checks [now use](https://github.com/lightningnetwork/lnd/pull/10686) - `getnetworkinfo.connections_out` instead of `getpeerinfo`. The same PR also - [clarifies](https://github.com/lightningnetwork/lnd/issues/10568) the ZMQ - port-mismatch warnings so they no longer suggest that the connection failed. - -* [Fixed a bug](https://github.com/lightningnetwork/lnd/pull/10782) - that could be encountered during co-op closes whereby - `ChanStatusCoopBroadcasted` was set before a close transaction - actually existed. As a side effect, channels in shutdown - negotiation now remain in `ListChannels` (as inactive) until - the close transaction is actually broadcast, and - `WaitingCloseChannel.ClosingTx` is never empty. - -* [Fixed a bug](https://github.com/lightningnetwork/lnd/pull/10890) - where `ListChannels` reported 100% `uptime` for channels whose peer - was offline. The channel fitness store assumed a peer was online when - it first started tracking it, but channels are loaded on startup - regardless of peer connectivity. Uptime is now seeded from the peer's - actual connection state. - -* [Fixed a bug](https://github.com/lightningnetwork/lnd/pull/10897) in the - sweeper whereby inputs that receive an extra budget from an aux sweeper - (such as custom channel outputs, whose value is mostly carried off-chain) - were filtered against their own budget alone. This could permanently - exclude such inputs from sweeping even though their input set could - comfortably pay its fees. - -* [Fixed a bug](https://github.com/lightningnetwork/lnd/pull/10963) in - `GetNetworkInfo` where encountering an already-seen channel skipped the - rest of that node's channels instead of just that channel, undercounting - the reported network statistics such as total network capacity, channel - count and max out degree. - -# New Features - -## Functional Enhancements - -## RPC Additions - -* The `routerrpc.EstimateRouteFee` RPC now supports [restricting fee estimates - to specific first-hop outgoing - channels](https://github.com/lightningnetwork/lnd/pull/10501) via the new - `outgoing_chan_ids` field in `RouteFeeRequest`. - -* A new - [`walletrpc.SubmitPackage`](https://github.com/lightningnetwork/lnd/pull/10900) - RPC submits a package of related transactions (parents first, child last) to - the chain backend via bitcoind's `submitpackage`, allowing a zero-fee v3/TRUC - parent to be accepted together with a fee-paying CPFP child. - -## lncli Additions - -* The `estimateroutefee` command now supports [restricting fee estimates to - specific first-hop outgoing - channels](https://github.com/lightningnetwork/lnd/pull/10501) via the new - `--outgoing_chan_id` flag. - -* A new - [`wallet submitpackage`](https://github.com/lightningnetwork/lnd/pull/10900) - command submits a package of hex-encoded transactions via the new - `SubmitPackage` RPC. - -# Improvements - -## Functional Updates - -## RPC Updates - -## lncli Updates - -## Breaking Changes - -## Performance Improvements - -## Deprecations - -# Technical and Architectural Updates - -## BOLT Spec Updates - -* The fundee now [enforces the BOLT-02 bound on - `push_msat`](https://github.com/lightningnetwork/lnd/pull/10765), - rejecting incoming `open_channel` messages where `push_msat` exceeds - `1000 * funding_satoshis`. Oversized pushes were previously caught - later in the reservation flow as a funder-balance-dust error; they now - surface a clearer, spec-aligned error string up front. - -## BOLT 12 (Offers) - -* [Initial BOLT 12 Offer codec](https://github.com/lightningnetwork/lnd/pull/10789): - add a new `bolt12/` package with the BOLT 12 `offer` TLV codec and full - reader/writer validation, plus a typed `lnwire.BlindedPath` introduction-node - codec shared by HTLC routing and onion messaging. - -* [BOLT 12 invoice request - codec](https://github.com/lightningnetwork/lnd/pull/10832): add the - `invoice_request` TLV message to the `bolt12/` package with structural - reader/writer validation. This includes an observable RPC behavior change - in `SubscribeOnionMessages`, ensuring a nil reply path remains nil in the - RPC response rather than being emitted as an empty struct. - -* [BOLT 12 invoice - codec](https://github.com/lightningnetwork/lnd/pull/10941): add the - `invoice` TLV message to the `bolt12/` package with structural - reader/writer validation. Schnorr signature verification is not yet - performed; callers must verify the signature independently until the - Merkle and signing primitives land. - -* [BOLT 12 invoice_error - codec](https://github.com/lightningnetwork/lnd/pull/10958): add the - `invoice_error` TLV message to `bolt12/` for onion-message replies. - -## Testing - -## Database - -## Code Health - -## Tooling and Documentation - -* [`dev.Dockerfile` now uses](https://github.com/lightningnetwork/lnd/pull/10903) - [cache mounts](https://docs.docker.com/build/cache/optimize/#use-cache-mounts) - to cache the `GOMODCACHE` and `GOCACHE` directories so that dependencies don't - need to be re-downloaded and re-built every time the image is re-created. - As a result of this change, `dev.Dockerfile` now requires - [BuildKit](https://docs.docker.com/build/buildkit) to build. When using - `docker build`, this can be enabled by setting the environmental variable - `DOCKER_BUILDKIT=1`. BuildKit also does not unnecessarily rebuild images when - the build context is a remote git repository because COPY layers are more - smartly compared to cache. - -# Contributors (Alphabetical Order) - -* bitromortac -* Boris Nagaev -* Erick Cestari -* Jared Tobin diff --git a/docs/release_branch_management.md b/docs/release_branch_management.md deleted file mode 100644 index cd24ebf11..000000000 --- a/docs/release_branch_management.md +++ /dev/null @@ -1,355 +0,0 @@ -# Release Branch Management - -## Overview - -This document describes the branch management workflow for lnd releases. The -master branch remains open for merges at all times. Release stabilization -happens on dedicated release branches, with CI automation handling backports of -milestone-tagged changes. This approach maintains continuous development -velocity while ensuring stable releases. - -## Branch Model Principles - -The release process operates on four core principles: - -**Master is always open.** Developers merge approved pull requests at any time -without coordination around release windows. No merge freezes occur. - -**Each major release gets a dedicated branch.** When cutting a new major -version, create a release branch from master. This branch handles all release -candidates and subsequent patch releases for that version series. - -**CI automation handles backports.** Pull requests merged to master and tagged -with a release milestone are automatically backported to the corresponding -release branch. The automation creates backport PRs when conflicts occur. - -**Changes flow one direction only.** Changes move from master to release -branches, never in reverse. Master always represents the latest development -state. - -## Master Branch - -The master branch contains ongoing development work for future releases. It -never freezes for releases. - -### Master Version Convention - -Master uses a `.99` patch version to indicate unreleased development work. After -creating the `v0.21.x-branch` branch, update master's version in -`build/version.go` to `0.21.99-beta`. This clearly signals post-0.21 but -pre-0.22 code. - -When creating the next release branch (`v0.22.x-branch`), update master to -`0.22.99-beta`. This pattern sorts correctly and is immediately recognizable as -a development build. - -### Merging to Master - -Developers merge to master following normal review processes. If a change should -be included in an active or upcoming release, tag the pull request with the -appropriate milestone (`v0.21.0`, `v0.21.1`, etc.). The CI automation handles -backporting after merge. - -No special coordination is required. Merge whenever the PR is approved, -regardless of ongoing release activities. Initially manual input may be required -to resolve conflicts that may arise. In the future LLM bots can help alleviate -this manual work. - -## Major Release Process - -A major release introduces new features and represents a new minor version -(e.g., 0.21.0, 0.22.0). - -### Creating the Release Branch - -When ready to begin a major release: - -1. Create a release branch from master: `git checkout -b v0.21.x-branch master` -2. Push the branch: `git push origin v0.21.x-branch` -3. Update `build/version.go` on the release branch to `0.21.0-beta.rc1` -4. Commit the version bump: `git commit -am "build: bump version to v0.21.0-beta.rc1"` -5. Update master's version to `0.21.99-beta` via a pull request -6. Configure branch protection for `v0.21.x-branch` on GitHub - -### Release Candidate Cycle - -Create the first release candidate by tagging the version bump commit with the -release tagging helper: - -```bash -./scripts/tag-release.sh v0.21.0-beta.rc1 --branch v0.21.x-branch -git push v0.21.0-beta.rc1 -``` - -This triggers CI to build release artifacts and Docker images. - -As testing uncovers issues, develop fixes on master and tag them with the -`v0.21.0` milestone. Once merged, CI automation backports them to -`v0.21.x-branch`. If backports apply cleanly, they merge automatically. If -conflicts occur, CI creates backport PRs for manual resolution. - -When ready for the next release candidate: - -1. Create a pull request against the release branch to update `build/version.go` to `0.21.0-beta.rc2` -2. After merging the PR, check out the release branch and tag the merge commit: - `./scripts/tag-release.sh v0.21.0-beta.rc2 --branch v0.21.x-branch` -3. Push the new tag to the upstream remote printed by the helper: - `git push v0.21.0-beta.rc2` - -Repeat this cycle (rc3, rc4, etc.) until the release is stable. - -### Final Release - -For the final release, remove the RC suffix: - -1. Create a pull request against the release branch to update `build/version.go` to `0.21.0-beta` -2. After merging the PR, check out the release branch and tag the merge commit: - `./scripts/tag-release.sh v0.21.0-beta --branch v0.21.x-branch` -3. Push the new tag to the upstream remote printed by the helper: - `git push v0.21.0-beta` - -The `v0.21.x-branch` branch now enters maintenance mode for future patch -releases. - -### Release Tagging Helper - -Use `scripts/tag-release.sh` whenever creating release tags. The helper performs -the required safety checks before creating the signed tag, then prints the push -command to run as the final explicit maintainer step. - -## Minor Release Process - -Minor (patch) releases fix bugs or security issues in released versions. They -reuse the existing release branch for that version series. - -### Creating a Patch Release - -When a critical fix is needed for version 0.21.0: - -1. Develop and merge the fix to master -2. Tag the PR with the `v0.21.1` milestone -3. CI automation backports to `v0.21.x-branch` -4. Create a pull request against the release branch to update `build/version.go` to `0.21.1-beta.rc1` -5. After merging the PR, check out the release branch and tag the merge commit: - `./scripts/tag-release.sh v0.21.1-beta.rc1 --branch v0.21.x-branch` -6. Push the new tag to the upstream remote printed by the helper: - `git push v0.21.1-beta.rc1` - -If additional fixes are needed, follow the same process, incrementing through -rc2, rc3, etc. - -For the final patch release: - -1. Create a pull request against the release branch to update `build/version.go` to `0.21.1-beta` -2. After merging the PR, check out the release branch and tag the merge commit: - `./scripts/tag-release.sh v0.21.1-beta --branch v0.21.x-branch` -3. Push the new tag to the upstream remote printed by the helper: - `git push v0.21.1-beta` - -Multiple patch releases (0.21.1, 0.21.2, 0.21.3) can be created on the same -`v0.21.x-branch` branch throughout the version's lifetime. - -## Manual Cherry-Picking - -Occasionally, a fix may be needed on a release branch that doesn't apply to -master (release-specific issues, backports to older versions where master has -diverged significantly, etc.). - -### When to Cherry-Pick Manually - -Cherry-pick directly to a release branch when: - -- The issue only exists on the release branch, not on master -- Master's code has changed significantly, making a direct backport impractical -- An urgent hotfix is needed before CI automation completes - -### Cherry-Pick Process - -```bash -# Switch to the release branch -git checkout v0.21.x-branch - -# Cherry-pick the commit from master -git cherry-pick - -# If conflicts occur, resolve them and continue -git cherry-pick --continue -``` - -Cherry-picks still follow the normal PR flow, so a PR should be made only into -the target release branch for normal review and CI. - -When manually cherry-picking, document why the normal backport flow was -bypassed. If a corresponding change is needed on master (to prevent the bug from -reappearing in future releases), ensure it's merged there as well. - -## Pull Request Milestones - -Developers use GitHub milestones to indicate which releases should include their -changes. - -### Assigning Milestones - -When opening a PR, consider whether it should be backported to an active -release: - -- **Bug fixes for active releases:** Assign the major release milestone (e.g., -`v0.21.0`) - -- **Critical fixes for older versions:** Assign the patch release milestone -(e.g., `v0.20.3`) - -- **Features for future releases only:** No milestone, or assign the next major -release milestone - -Milestones can be assigned at any time, even after merge. CI automation -processes milestone-tagged PRs whenever they're detected. - -### Multiple Milestones - -If a fix needs to go into multiple release branches, assign multiple milestones -to the PR. CI handles each backport independently. For example, a security fix -might get both `v0.21.0` and `v0.20.3` milestones. - -## Backport Automation - -CI automation monitors merged PRs and backports milestone-tagged changes to the -appropriate release branches. This section describes the automation's behavior. -Implementation details are tracked in separate GitHub issues. - -### Automatic Backports - -When a PR with a release milestone merges to master: - -1. CI detects the milestone and identifies the target release branch -2. CI attempts a three-way merge onto the release branch -3. If successful, CI commits directly with a reference to the original PR -4. The backported change appears in the next release candidate - -Developers don't need to take any action for successful backports. - -### Conflict Resolution - -When a backport conflicts: - -1. CI creates a new PR against the release branch -2. The PR contains the attempted backport with conflict markers -3. CI assigns the PR to the original author and notifies via GitHub mentions -4. The author or maintainers resolve conflicts and merge the backport PR - -Backport PRs follow the normal review process and must pass all CI checks. - -### Monitoring Backports - -Track backport status through GitHub Projects or by filtering PRs. Backport PRs -include labels indicating the original PR and milestone. Successfully backported -commits reference the original PR in their commit messages. - -## Version Bump Timing - -Version numbers in `build/version.go` must be updated at specific points in the -release process. - -**On release branches:** Update immediately before tagging. The commit that -updates the version is the commit that gets tagged. This ensures built binaries -report the correct version. - -**On master:** Update when creating a new release branch. Master moves from -`0.20.99-beta` to `0.21.99-beta` when `v0.21-release` is created. - -**For each RC:** Increment the RC number before tagging. `0.21.0-beta.rc1` → -`0.21.0-beta.rc2` → `0.21.0-beta.rc3`, etc. - -**For final releases:** Remove the RC suffix. `0.21.0-beta.rc5` → `0.21.0-beta`. - -## Branch Model Visualization - -The following diagrams illustrate the branch workflow and change flow. - -### Timeline View: Major Release Branch Lifecycle - -```mermaid -gitGraph - commit id: "0.20.0 released" - commit id: "Feature A" - commit id: "Feature B" - branch v0.21.x-branch - commit id: "Version → 0.21.0-rc1" tag: "v0.21.0-beta.rc1" - checkout main - commit id: "Feature C (for 0.22)" - commit id: "Feature D (for 0.22)" - checkout v0.21.x-branch - commit id: "Backport: Fix X" - commit id: "Version → 0.21.0-rc2" tag: "v0.21.0-beta.rc2" - checkout main - commit id: "Feature E (for 0.22)" - commit id: "Fix Y (backport to 0.21)" - checkout v0.21.x-branch - commit id: "Backport: Fix Y" - commit id: "Version → 0.21.0" tag: "v0.21.0-beta" - checkout main - commit id: "Feature F (for 0.22)" - commit id: "Feature G (for 0.22)" - checkout v0.21.x-branch - commit id: "Backport: Critical fix Z" - commit id: "Version → 0.21.1-rc1" tag: "v0.21.1-beta.rc1" - commit id: "Version → 0.21.1" tag: "v0.21.1-beta" - checkout main - commit id: "Continue development" -``` - -After the v0.21.x-branch branch is created, both branches evolve independently. -Master continues with features for future releases while the release branch -focuses solely on stabilization and bug fixes. - -### Pull Request Flow with Milestone-Based Backports - -```mermaid -flowchart TD - A[PR Merged to Master] --> B{Has Release
Milestone?} - B -->|No| C[Done - Stays in Master Only] - B -->|Yes| D{Release Branch
Exists?} - D -->|No| E[Queued for Future
Release Branch] - D -->|Yes| F{First RC
Tagged?} - F -->|No| G[Queued Until
RC1 Tagged] - F -->|Yes| H[CI: Attempt
Backport] - H --> I{Clean
Apply?} - I -->|Yes| J[Auto-merge to
Release Branch] - I -->|No| K[Create Backport PR
for Manual Resolution] - J --> L[Done] - K --> M[Maintainer Resolves
Conflicts] - M --> L -``` - -The milestone tag triggers the backport process. CI validates that the target -release branch exists and has entered the RC phase before attempting backports. - -### Major vs Minor Release Branching - -```mermaid -gitGraph - commit id: "Development" - commit id: "More work" - branch v0.21.x-branch - commit id: "v0.21.0-rc1" tag: "v0.21.0-beta.rc1" - commit id: "v0.21.0-rc2" tag: "v0.21.0-beta.rc2" - commit id: "v0.21.0 final" tag: "v0.21.0-beta" - checkout main - commit id: "Continue dev" - commit id: "More features" - checkout v0.21.x-branch - commit id: "Patch fix 1" - commit id: "v0.21.1" tag: "v0.21.1-beta" - commit id: "Patch fix 2" - commit id: "v0.21.2" tag: "v0.21.2-beta" - checkout main - commit id: "Keep developing" - branch v0.22.x-branch - commit id: "v0.22.0-rc1" tag: "v0.22.0-beta.rc1" - checkout main - commit id: "Future work" -``` - -The v0.21.x-branch branch serves both the initial 0.21.0 release and subsequent -patch releases (0.21.1, 0.21.2). When 0.22 development is ready, a new -v0.22.x-branch branch is created, and the cycle repeats. diff --git a/docs/rest/websockets.md b/docs/rest/websockets.md index ec328ca7f..ff4d4a3c3 100644 --- a/docs/rest/websockets.md +++ b/docs/rest/websockets.md @@ -130,8 +130,8 @@ ws.on('open', function() { // This empty message will be ignored by the channel acceptor though, this // is just for telling the grpc-gateway library that it can forward the // request to the gRPC interface now. If this were an RPC where the client - // always sends the first message (for example the HTLC interceptor RPC - // /v2/router/htlcinterceptor), we'd simply send the first "real" + // always sends the first message (for example the streaming payment RPC + // /v1/channels/transaction-stream), we'd simply send the first "real" // message here when needed. ws.send('{}'); }); diff --git a/docs/ruby-thing.rb b/docs/ruby-thing.rb new file mode 100644 index 000000000..922201fe3 --- /dev/null +++ b/docs/ruby-thing.rb @@ -0,0 +1,12 @@ +#!/usr/bin/env ruby + +File.open("INSTALL.md", 'r') do |f| + f.each_line do |line| + forbidden_words = ['Table of contents', 'define', 'pragma'] + next if !line.start_with?("#") || forbidden_words.any? { |w| line =~ /#{w}/ } + + title = line.gsub("#", "").strip + href = title.gsub(" ", "-").downcase + puts " " * (line.count("#")-1) + "* [#{title}](\##{href})" + end +end diff --git a/docs/testing-guides/v0.21.0/README.md b/docs/testing-guides/v0.21.0/README.md deleted file mode 100644 index 61d627cca..000000000 --- a/docs/testing-guides/v0.21.0/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# LND v0.21.0 — Release Candidate Testing Guide - -This directory contains structured testing guides for the v0.21.0 -release candidate. Each guide targets one feature or one high-risk -regression surface, and follows the same template so both human RC -testers and automated agents can work through them predictably. - -The RC announcement is [discussion -#10766](https://github.com/lightningnetwork/lnd/discussions/10766). -The full release notes are at -[`docs/release-notes/release-notes-0.21.0.md`](../../release-notes/release-notes-0.21.0.md). - -## How to use this directory - -Each guide is self-contained and follows the layout in -[`_template.md`](./_template.md): - -1. **Prerequisites** — what to build, which backend, which network, - which peers and config flags. -2. **Setup** — copy-pasteable commands to reach the starting state. -3. **Scenarios** — numbered cases, each with a deterministic - pass/fail signal (an exact RPC field value, log line, or exit - code — not "should succeed"). -4. **Failure investigation** — logs and RPCs to query when a scenario - fails. - -**Humans:** pick guides matching the surface you care about and run -through the scenarios. Report results on -[discussion #10766](https://github.com/lightningnetwork/lnd/discussions/10766) -or open an issue if you find a regression. - -**Agents:** the fixed section order is the contract. The Pass/Fail -signal line in each scenario is the verification target. - -## Guides - -Ordered by risk to RC testers. Start at the top. - -### Headline features - -| # | Guide | Summary | -|---|---|---| -| 1 | [Production simple taproot channels](./production-taproot-channels.md) | Feature bits 80/81, optimized scripts, map-based nonce encoding. | -| 2 | [RBF cooperative close for taproot channels](./rbf-taproot-coop-close.md) | MuSig2 JIT nonces, nonce-reuse prevention, `--protocol.rbf-coop-close`. | -| 3 | [Payment store KV→SQL migration](./payment-sql-migration.md) | Automatic migration for `--db.use-native-sql` nodes; bbolt users must `lndinit` first. | -| 4 | [Onion messaging + rate limiting](./onion-messaging.md) | Basic onion message forwarding, pathfinding, per-peer/global rate limiters, channel-presence gate. | - -### High-risk regressions and breaking changes - -| # | Guide | Why it's risky | -|---|---|---| -| 5 | [Closed-channel tombstone (sqlite/postgres downgrade trap)](./closed-channel-tombstone.md) | One-way upgrade on KV-over-SQL backends; downgrading after closes resurrects channels as open. | -| 6 | [Reorg-safe channel closes + MinCLTVDelta change](./reorg-safe-closes.md) | Closes now require 3–6 confs scaled to capacity; `MinCLTVDelta` raised 18→24 (breaking for custom-CLTV invoices). | -| 7 | [`chain_params` network-mismatch DB guard](./chain-params-guard.md) | Native-SQL nodes refuse to start if the DB was previously used on a different network. | -| 8 | [`GetDebugInfo` log opt-in breaking change](./getdebuginfo-log-optin.md) | Clients relying on the `log` field break unless they pass `include_log=true`. | - -### New RPCs / operator features - -| # | Guide | Summary | -|---|---|---| -| 9 | [New payment-adjacent RPCs](./payment-rpcs.md) | `DeleteForwardingHistory`, MuSig2 coordinator nonces, `EstimateFee` inputs, HTLC event invoice failures, `SubscribeChannelEvents` updates. | -| 10 | [Multiple read-only middleware interceptors](./middleware-multiple-readonly.md) | More than one read-only RPC middleware interceptor can register at once. | - -## Reporting results - -- Working as expected: a 👍 reaction on the RC discussion is fine. -- Regression or unexpected behavior: open an issue with the guide - name, scenario number, and the captured output. Link to the issue - in the discussion thread. - -## Authoring new guides - -Copy [`_template.md`](./_template.md) to `.md`, fill in -every section, and add an entry to the table above. Keep the section -order intact. If a section genuinely doesn't apply, write `n/a` — -don't delete the heading. diff --git a/docs/testing-guides/v0.21.0/_template.md b/docs/testing-guides/v0.21.0/_template.md deleted file mode 100644 index 2cc8d7c69..000000000 --- a/docs/testing-guides/v0.21.0/_template.md +++ /dev/null @@ -1,140 +0,0 @@ - - -# — v0.21.0 RC Testing Guide - -**PRs:** #XXXX, #YYYY -**Risk:** headline | high-regression | new-rpc | operator-feature -**Audience:** node operators | RPC clients | LSPs | wallet integrators -**Backends affected:** bbolt | sqlite | postgres | all -**Networks:** regtest | signet | testnet | mainnet - -## What this feature does - -One to three sentences in plain English. No marketing language. State -what changed in observable behavior, not internal refactors. - -## Why it matters / what could break - -Concrete failure modes a tester should look for. Examples: -- "If X is wrong, channel force-closes." -- "If Y is wrong, payments stall in `IN_FLIGHT`." -- "If Z is wrong, the node refuses to start after upgrade." - -## Prerequisites - -- **lnd build:** v0.21.0-beta.rc1 or newer, built with ``. -- **Backend:** `bitcoind` / `btcd` / `neutrino`. -- **Network:** regtest unless noted. -- **Peers:** N nodes (Alice, Bob, Carol). State counts, channels, - balances at the start of the scenarios. -- **Config flags:** - ``` - protocol.option-name=value - db.option-name=value - ``` -- **Tools:** `lncli`, `bitcoin-cli`, `jq`, ... - -Define every shell variable used in the Setup and Scenarios blocks: -``` -ALICE_RPC=localhost:10001 -BOB_PUBKEY=03... -``` - -## Setup - -Numbered, copy-pasteable steps to get from "fresh nodes" to the -starting state for the scenarios. End with a single command whose -output proves setup succeeded. - -```bash -# 1. Start nodes -... - -# 2. Fund Alice -... - -# 3. Open Alice→Bob channel -... - -# Setup verification: -lncli --rpcserver=$ALICE_RPC listchannels | jq '.channels | length' -# Expected: 1 -``` - -## Scenarios - -### S1: - -**Goal:** What this scenario proves. - -**Steps:** -```bash -# 1. ... -# 2. ... -``` - -**Expected:** -- Concrete observable 1 (RPC field = value, log contains line, etc.) -- Concrete observable 2. - -**Pass/Fail signal:** -- **PASS** if `lncli ... | jq '.field'` returns `"expected_value"`. -- **FAIL** if the command errors, returns a different value, or the - log shows ``. - ---- - -### S2: - -**Goal:** ... - -**Steps:** ... - -**Expected:** ... - -**Pass/Fail signal:** ... - ---- - -(Add 2–5 scenarios. Cover at least one happy path, one edge case, and -one negative-path / misconfiguration scenario.) - -## Failure investigation - -When a scenario fails, here's where to look first: - -- **Logs:** - - `grep -i "" ~/.lnd/logs/bitcoin/mainnet/lnd.log` - - Subsystems to enable at `debug`: ``, ``. -- **RPCs to query for state:** - - `lncli ` — what to look at and what value indicates the bug. -- **Common bugs / prior regressions:** brief pointers, ideally with - PR / issue numbers. - -## Related itests - -Point to itest cases that exercise this code path. They're not a -substitute for manual scenarios but are useful executable references: -- `itest/lnd__test.go::Test` - -## Out of scope - -What this guide does not test (to prevent scope creep and to direct -testers to the right guide). diff --git a/docs/testing-guides/v0.21.0/chain-params-guard.md b/docs/testing-guides/v0.21.0/chain-params-guard.md deleted file mode 100644 index 38f6eccc9..000000000 --- a/docs/testing-guides/v0.21.0/chain-params-guard.md +++ /dev/null @@ -1,188 +0,0 @@ -# `chain_params` Network-Mismatch DB Guard — v0.21.0 RC Testing Guide - -**PR:** #10684 -**Risk:** high-regression -**Audience:** node operators running native-SQL backends, anyone with a multi-network setup -**Backends affected:** sqlite, postgres (with `--db.use-native-sql`) -**Networks:** all - -## What this feature does - -On first startup against v0.21.0, the daemon writes the active -Bitcoin network (mainnet, testnet, signet, regtest) into a new -`chain_params` row in the SQL database. On every subsequent startup, -the daemon compares the stored value against the configured network -and refuses to start if they differ, printing a clear error and -remediation steps. - -This closes a silent-data-corruption hole: previously, accidentally -pointing the same DB at a different network would proceed and start -writing mismatched chain state. - -Applies to PostgreSQL and SQLite native-SQL backends when running -with `--db.use-native-sql=true`. bbolt is unaffected. - -## Why it matters / what could break - -- The guard must fire on **every** network change, not just - mainnet/testnet. Regtest ↔ signet ↔ testnet swaps need to be - caught too. -- The guard must fire **before** any chain-touching subsystem - initializes; otherwise the data corruption it's meant to prevent - has already started. -- The error message must be actionable — operators need to know - how to recover (reset the DB? change the config back?). -- The guard must not interfere with the first-ever startup - (network is unset, so any value is allowed and persisted). - -## Prerequisites - -- **lnd build:** v0.21.0-beta.rc1 or newer. -- **Backend:** sqlite (easiest) with `--db.use-native-sql=true`. -- **Two networks reachable** in the same machine, e.g. regtest and - signet, or regtest with two different `--regtest` instances using - distinct genesis hashes. (The latter requires - `chainparams.go`-level tweaks; using `regtest` and `signet` is - more practical.) - -## Setup - -```bash -# 1. Fresh data directory. -ALICE_DIR=/tmp/alice-chainparams-test -rm -rf $ALICE_DIR -mkdir -p $ALICE_DIR - -# 2. lnd.conf: -# bitcoin.regtest=1 -# db.use-native-sql=true -# db.backend=sqlite - -# 3. Start lnd, wait for it to come up, then `lncli stop`. -``` - -## Scenarios - -### S1: First startup persists the active network - -**Goal:** A fresh DB plus a configured network results in a -populated `chain_params` row. - -**Steps:** -```bash -# Start lnd on regtest. Wait until lncli getinfo returns OK. -# Stop cleanly. -$LNCLI_A stop - -# Inspect the chain_params table. -sqlite3 $DB "SELECT * FROM chain_params;" -``` - -**Pass/Fail signal:** -- **PASS** if `chain_params` contains exactly one row identifying - regtest (by name or genesis hash, depending on the schema). -- **FAIL** if the table is empty, missing, or has more than one row. - ---- - -### S2: Same network restart proceeds without warning - -**Steps:** Restart lnd against the same DB with the same -configuration. - -**Pass/Fail signal:** -- **PASS** if lnd starts normally, `getinfo` returns OK, and the - startup log contains no chain-params warnings. -- **FAIL** if startup logs a warning or error about chain params - even though nothing changed. - ---- - -### S3: Different network startup is refused with a clear error - -**Goal:** Swap the configured network to `signet` while pointing at -the same SQL DB. lnd must refuse to start. - -**Steps:** -```bash -# Edit lnd.conf: replace bitcoin.regtest=1 with bitcoin.signet=1. -# Start lnd and capture the exit status + stderr. -lnd --lnddir=$ALICE_DIR ...; echo "exit=$?" -``` - -**Pass/Fail signal:** -- **PASS** if all of: - - exit code is non-zero, - - the error message names both the stored network (regtest) and - the configured network (signet), - - the error suggests a remediation (e.g. "use a fresh data - directory, or change your configured network back"), - - no chain-touching subsystems were initialized (search the log - for evidence of chain RPC calls — none should have happened). -- **FAIL** if lnd starts despite the mismatch, exits without a - clear message, or partially initializes before exiting. - ---- - -### S4: Reverting the network restores normal startup - -**Goal:** After hitting the guard, switching the config back to the -stored network must let the node start again — i.e. the guard is -non-destructive. - -**Steps:** Revert `lnd.conf` back to `bitcoin.regtest=1` and start -lnd. - -**Pass/Fail signal:** -- **PASS** if lnd starts normally and `getinfo` returns OK. -- **FAIL** if lnd still refuses (the failed attempt left state - behind that should not have). - ---- - -### S5: bbolt-backed node is unaffected - -**Goal:** Confirm the guard only applies to native-SQL backends. -bbolt operators get the existing behavior (no guard, no false alarm). - -**Steps:** -- Spin up Bob on bbolt (no `db.use-native-sql=true`). -- Repeat S1–S3 against him. - -**Pass/Fail signal:** -- **PASS** if all three startups succeed on bbolt, including the - network swap. (Operators on bbolt still need to know swapping - networks corrupts data — but the guard is not their tool.) -- **FAIL** if the guard fires on bbolt despite the feature being - scoped to native-SQL. - -## Failure investigation - -- **Subsystems:** `LNDB`, `CONF`, `RPCS` at `debug`. -- **Useful log lines:** grep for `chain_params`, `network mismatch`, - `configured network`. -- **Direct SQL:** - ```sql - SELECT * FROM chain_params; - ``` -- **Remediation if a real operator hits this:** the documented - guidance should be "you almost certainly want to revert the - config change; if you genuinely meant to switch networks, point - lnd at a fresh data directory". Verify the error message says - this or something equivalent. - -## Related itests - -- A startup-failure itest for chain-params mismatch should exist - in v0.21.0 — verify it does. If not, this guide highlights a - coverage gap worth filling. - -## Out of scope - -- Re-using a bbolt database across networks (not covered by this - guard). -- Postgres-specific schema differences — assume the guard behaves - identically across sqlite and postgres; spot-check the postgres - side if available. -- Recovery tooling for an operator who already corrupted their DB - before v0.21.0 — not something this guide can fix. diff --git a/docs/testing-guides/v0.21.0/closed-channel-tombstone.md b/docs/testing-guides/v0.21.0/closed-channel-tombstone.md deleted file mode 100644 index 8665cc5d4..000000000 --- a/docs/testing-guides/v0.21.0/closed-channel-tombstone.md +++ /dev/null @@ -1,259 +0,0 @@ -# Closed-Channel Tombstone on KV-over-SQL Backends — v0.21.0 RC Testing Guide - -**PR:** #10780 -**Risk:** high-regression (one-way upgrade) -**Audience:** node operators on `sqlite` or `postgres` backends with channels they may close -**Backends affected:** sqlite, postgres (kvdb-on-SQL schema) -**Networks:** all - -> ⚠️ **Downgrade warning.** On sqlite and postgres, once a channel -> is closed under v0.21.0+, the underlying `chanBucket` (revocation -> log, forwarding-package state) **remains on disk**. The close is -> signalled by an `outpointClosed` flip in the outpoint index. -> **Pre-0.21 binaries do not consult that flip when iterating the -> open-channel bucket.** Downgrading to a pre-0.21 binary after -> closing channels on these backends will resurrect those channels -> as "open" in `listchannels`, `pendingchannels`, and the chain-watch -> path. Treat the v0.21.0 upgrade as one-way on sqlite/postgres if -> you close any channels on it. -> -> bbolt and etcd users are unaffected — the close path on those -> backends still deletes the `chanBucket` synchronously. - -## What this feature does - -Before v0.21.0, `CloseChannel` issued a single `DeleteNestedBucket` -for the channel inside the close transaction. On the kvdb-on-SQL -schema (sqlite, postgres) that delete fans out into a row-by-row -`ON DELETE CASCADE` over the channel's revocation log and -forwarding-package bucket. On channels with millions of states this -held the database write-lock for many seconds — long enough to stall -HTLC forwarding, time out `htlcswitch` retries, and trigger -force-close cycles on adjacent channels. - -v0.21.0 changes `CloseChannel` on the kvdb-on-SQL backends to **skip -the cascading delete**. The bulk historical state stays on disk for -the lifetime of the database; the authoritative closed-channel -marker is the existing outpoint-index flip from `outpointOpen` to -`outpointClosed`. Every reader of the open-channel bucket has been -updated to consult the outpoint index before treating a channel as -open. bbolt and etcd retain the synchronous one-shot close path (the -cascade is cheap there). - -The bulk historical state is reclaimed wholesale by the upcoming -native-SQL channel-state migration in a future release. - -## Why it matters / what could break - -- **The downgrade trap above.** This is the one to make sure - operators see in the release notes. -- A reader that forgot to consult the outpoint index → a closed - channel reappears as open in `listchannels`, `pendingchannels`, or - the chain-watch filter. Find it now, not in the wild. -- A new code path (post-v0.21.0) that creates a channel whose - outpoint is in the closed-index but whose chanBucket still has the - old state → potential state confusion on funding-output collision. -- bbolt/etcd path **must** keep the synchronous delete; if a future - refactor moves them onto the tombstone path, those operators lose - the data-reclaim property silently. - -## Prerequisites - -- **lnd build:** v0.21.0-beta.rc1 or newer. -- **Two pairs of test environments:** one on `sqlite` (or `postgres`), - one on `bbolt`. The cross-check between them is the point. -- **Tools:** `lncli`, `bitcoin-cli`, `sqlite3` (or `psql`), `jq`. -- **A pre-0.21 lnd binary** in reach for the downgrade scenario - (S5). Only run S5 against a throwaway copy of the database — the - whole point is that it corrupts the state. - -## Setup - -```bash -# 1. Start Alice on sqlite (or postgres). Start Bob on bbolt -# (so we can cross-check against the other backend). -# 2. Open and confirm a channel between Alice and Bob (any commitment -# type). Push a handful of small payments to populate the -# revocation log. -# 3. Record the channel point. -CP=$($LNCLI_A listchannels | jq -r '.channels[0].channel_point') - -# 4. Note the sqlite DB path on Alice. -DB_A=$ALICE_DIR/data/chain/bitcoin/regtest/channel.db # or your sqlite filename -``` - -## Scenarios - -### S1: Coop close on sqlite/postgres completes quickly (no stall) - -**Goal:** Confirm the regression fix. Closing a channel with a -non-trivial revocation log no longer holds the write-lock for -seconds. - -**Steps:** -```bash -# Time the close. -time $LNCLI_A closechannel \ - --funding_txid=${CP%:*} --output_index=${CP##*:} --block - -bitcoin-cli -regtest generatetoaddress 6 $ADDR -``` - -**Pass/Fail signal:** -- **PASS** if the `closechannel --block` returns in under 1 second - after the close-tx is broadcast (the `--block` wait for 1 conf - dominates the wall-clock; what we care about is no DB-side stall - during close-state-transition). -- **FAIL** if the daemon log shows a `DB write took NNs` warning, or - the close noticeably lags adjacent HTLC traffic. - ---- - -### S2: Closed channel no longer appears in `listchannels` / `pendingchannels` - -**Goal:** Despite the tombstone leaving rows on disk, every reader -must treat the channel as closed. - -**Steps:** -```bash -$LNCLI_A listchannels | jq '.channels | length' -$LNCLI_A pendingchannels | jq '.waiting_close_channels | length' -$LNCLI_A pendingchannels | jq '.pending_force_closing_channels | length' -$LNCLI_A closedchannels | jq '.channels | length' -``` - -**Pass/Fail signal:** -- **PASS** if all of `listchannels`, `waiting_close_channels`, and - `pending_force_closing_channels` return 0, and `closedchannels` - contains the closed channel. -- **FAIL** if `listchannels` still shows the closed channel — the - outpoint-index gate regressed. - ---- - -### S3: The `chanBucket` remains on disk after close (sqlite) - -**Goal:** Verify the tombstone actually skipped the delete. This is -the property that creates the downgrade trap; we want to *see* it, -not assume it. - -**Steps:** -```bash -sqlite3 $DB_A "SELECT COUNT(*) FROM kvstore WHERE key LIKE '%openChannelBucket%';" -sqlite3 $DB_A "SELECT COUNT(*) FROM kvstore WHERE key LIKE '%revocationLog%';" -``` - -(The exact bucket prefix and table layout depends on the kvdb-on-SQL -schema; consult `channeldb` / `kvdb/sqlbase` for the right -predicate. The point is: rows exist for the closed channel.) - -**Pass/Fail signal:** -- **PASS** if both counts are > 0 *and* the corresponding outpoint - appears in the `outpointClosed` index (verify with a second - query). -- **FAIL** if the chanBucket rows are gone (the tombstone behavior - didn't apply on this backend), or if the outpoint flip is missing - (the channel is in limbo). - ---- - -### S4: bbolt control — chanBucket IS deleted - -**Goal:** Cross-check that bbolt/etcd retain the synchronous delete. - -**Steps:** -- Repeat S1+S3 on Bob (bbolt). Verify with the equivalent bbolt - introspection (e.g. `bbolt` CLI or `lndinit dump` against the - channel.db). - -**Pass/Fail signal:** -- **PASS** if on bbolt the chanBucket for the closed channel is - gone (delete still happened) and `closedchannels` still records - the close. Both backends should expose the same operator-facing - state via RPC; only the on-disk representation differs. -- **FAIL** if bbolt left chanBucket rows behind (the change leaked - to the wrong backend) or removed the closed-channel summary. - ---- - -### S5: Downgrade resurrects closed channels (DESTRUCTIVE — copy first) - -**Goal:** Demonstrate the documented downgrade trap on a throwaway -DB copy, so we can confirm the warning is accurate and the surface -is exactly as advertised. - -> Only run this against a copy of the sqlite/postgres database. The -> downgrade is a one-way corruption. - -**Steps:** -```bash -$LNCLI_A stop -cp -r $ALICE_DIR ${ALICE_DIR}.tombstone-test -# Swap the binary back to a pre-0.21 release. -lnd-pre-021 --lnddir=${ALICE_DIR}.tombstone-test ... -``` - -**Pass/Fail signal:** -- **PASS** if `listchannels` against the downgraded daemon shows - the previously-closed channel as open (confirming the warning is - real), and there are no surprises beyond the documented - resurrection (no panics, no force-closes attempted on the - resurrected channel). -- **FAIL** if the downgraded daemon panics, force-closes on - startup, or behaves differently than the release-notes warning - predicts. The warning needs updating. - -After this scenario, **discard the copy**. - ---- - -### S6: HTLC forwarding does not stall during a close - -**Goal:** The original motivation for #10780 — the write-lock held -during the cascade used to stall HTLC forwarding. Confirm it -doesn't anymore. - -**Steps:** -- Open a second channel Alice ↔ Carol so Alice has two channels. -- Start a steady stream of payments through Alice→Bob→Carol (or any - two-channel routing path). -- Mid-stream, close Alice's other channel (the one not on the - payment path). - -**Pass/Fail signal:** -- **PASS** if no payment fails with a routing-layer timeout during - the close, and Alice's log shows no `htlcswitch retry timed out` - or `db write took` warnings. -- **FAIL** if any in-flight payment fails or retries during the - close window. - -## Failure investigation - -- **Subsystems:** `LNDB`, `CRTR`, `HSWC`, `RPCS` at `debug`. -- **Key log lines to watch for:** - - `tombstoning channel` (or whatever the v0.21.0 code emits when - taking the new path) - - `db write took NNms` warnings - - `outpointClosed` index transitions -- **DB introspection:** `sqlite3` direct queries for the channel's - outpoint index entries and chanBucket presence. -- **Cross-reference:** if S2 reports a closed channel still listed - as open, search the readers of `openChannelBucket` for any path - that doesn't consult `outpointIndex` — that's where the bug is. - -## Related itests - -- `itest/lnd_channel_force_close_test.go` and - `itest/lnd_channel_open_test.go` — exercise close paths but may - not specifically cover the tombstone behavior. Worth adding an - itest if one doesn't exist. -- `channeldb` package tests for outpoint-index transitions. - -## Out of scope - -- The native-SQL channel-state migration that will reclaim the - tombstoned data — not in v0.21.0. -- Performance benchmarks of the close path on multi-million-state - channels — qualitative confirmation (no stall) suffices for the - RC. -- bbolt-to-sqlite conversion (use `lndinit`). diff --git a/docs/testing-guides/v0.21.0/getdebuginfo-log-optin.md b/docs/testing-guides/v0.21.0/getdebuginfo-log-optin.md deleted file mode 100644 index dd2ecadb5..000000000 --- a/docs/testing-guides/v0.21.0/getdebuginfo-log-optin.md +++ /dev/null @@ -1,140 +0,0 @@ -# `GetDebugInfo` Log Opt-In Breaking Change — v0.21.0 RC Testing Guide - -**PR:** #10613 -**Risk:** high-regression (breaking) -**Audience:** RPC clients calling `GetDebugInfo`, anyone scripting `lncli getdebuginfo` or `lncli encryptdebugpackage` -**Backends affected:** all -**Networks:** all - -## What this feature does - -`GetDebugInfo` previously returned both the daemon's configuration -map and the contents of the log file by default. v0.21.0 makes the -log content **opt-in**: - -- Default response: configuration only. The `log` field is empty/omitted. -- With `include_log=true` on the gRPC request (or `--include_log` on - `lncli getdebuginfo` / `lncli encryptdebugpackage`): the log file - is included as before. - -This is a real breaking change for any client that consumed the `log` -field without setting the new flag. - -## Why it matters / what could break - -- Monitoring tools or support-package generators that called - `GetDebugInfo` and uploaded the response now upload an empty log - silently. They will not error — they will look healthy while - shipping useless debug bundles. -- Scripts that parsed `lncli getdebuginfo` output for log lines - will start matching nothing. -- The `--include_log` flag must propagate cleanly into the encrypted - debug package; otherwise support-flow encrypted bundles will be - log-free. - -## Prerequisites - -- **lnd build:** v0.21.0-beta.rc1 or newer. -- **A running lnd node** with some log activity (any regtest setup - with a few RPCs called against it works). -- **Tools:** `lncli`, `jq`, `grpcurl` (to confirm raw gRPC behavior). - -## Scenarios - -### S1: Default `GetDebugInfo` omits the log - -**Goal:** A plain `GetDebugInfo` call returns config only, no log. - -**Steps:** -```bash -# Via lncli: -$LNCLI_A getdebuginfo | jq '.log | length' - -# Via raw gRPC: -grpcurl ... -d '{}' lnrpc.Lightning/GetDebugInfo | jq '.log | length' -``` - -**Pass/Fail signal:** -- **PASS** if both queries return `0` or `null` for the `log` - field, and `config` is populated. -- **FAIL** if `log` is non-empty (the breaking change didn't land), - or if `config` is empty (regression). - ---- - -### S2: `--include_log` opts the log content back in - -**Steps:** -```bash -$LNCLI_A getdebuginfo --include_log | jq '.log | length' -grpcurl ... -d '{"include_log": true}' lnrpc.Lightning/GetDebugInfo | jq '.log | length' -``` - -**Pass/Fail signal:** -- **PASS** if both return a length > 0 and the content matches the - daemon's actual log file (compare against the file on disk). -- **FAIL** if `log` is empty despite `include_log=true`, or if the - content is truncated unexpectedly. - ---- - -### S3: `encryptdebugpackage --include_log` includes the log - -**Goal:** The opt-in flag propagates through the encrypt path. - -**Steps:** -```bash -# Without the flag. -$LNCLI_A encryptdebugpackage --pubkey > /tmp/pkg-nolog.bin - -# With the flag. -$LNCLI_A encryptdebugpackage --pubkey --include_log > /tmp/pkg-withlog.bin - -# Compare sizes. -ls -la /tmp/pkg-nolog.bin /tmp/pkg-withlog.bin -``` - -**Pass/Fail signal:** -- **PASS** if `pkg-withlog.bin` is noticeably larger than - `pkg-nolog.bin` (the log is in there), and decrypting both with - the corresponding private key shows the log section present / - absent respectively. -- **FAIL** if they're the same size (the flag is being ignored), or - if the no-log package still contains log content. - ---- - -### S4: Existing clients that don't set the flag get no log silently - -**Goal:** Confirm the breaking-change behavior matches the -documented contract — no spurious errors, just a quiet omission. - -**Steps:** Call `GetDebugInfo` from a client written against the -v0.20 proto / SDK (i.e. one that doesn't know about `include_log`). - -**Pass/Fail signal:** -- **PASS** if the call succeeds, returns `config` populated, and - `log` empty. No `unknown field` errors, no panics. -- **FAIL** if the call errors out due to the new field, or - surprisingly returns the log anyway. - -## Failure investigation - -- **Subsystems:** `RPCS` at `debug`. -- **What to check if `--include_log` returns empty log:** - - The daemon's `logfile` config — is the log being written to the - expected path? - - File permissions on the log file from the daemon process. - - The proto generation — `git diff` against the proto regenerate - pipeline to confirm `include_log` is wired both ways. - -## Related itests - -- `itest/lnd_macaroons_test.go` or a dedicated debug-info itest if - one exists. Worth adding a unit/integration test if missing. - -## Out of scope - -- Encryption details of `encryptdebugpackage` — see existing docs - for the format. This guide tests the log-inclusion flag only. -- Migrating clients to the new flag — that's a downstream task. diff --git a/docs/testing-guides/v0.21.0/middleware-multiple-readonly.md b/docs/testing-guides/v0.21.0/middleware-multiple-readonly.md deleted file mode 100644 index 0d5b6cc2f..000000000 --- a/docs/testing-guides/v0.21.0/middleware-multiple-readonly.md +++ /dev/null @@ -1,175 +0,0 @@ -# Multiple Read-Only RPC Middleware Interceptors — v0.21.0 RC Testing Guide - -**PR:** #10611 -**Risk:** operator-feature -**Audience:** integrators running RPC middleware (audit logging, metrics, policy) -**Backends affected:** all -**Networks:** all - -## What this feature does - -Pre-v0.21.0, only a single read-only RPC middleware interceptor could -register at a time. v0.21.0 lifts that restriction: multiple -clients can register simultaneously with `read_only_mode=true` on -`MiddlewareRegistration`. Each registered middleware receives every -intercepted request/response, none can alter responses. - -The custom-macaroon-caveat middleware mode is unchanged — there can -still be at most one middleware per distinct caveat name, and those -remain mutually exclusive with read-only mode for the same client. - -## Why it matters / what could break - -- Two read-only middlewares connect → both receive intercepts. - Regression: only the first registers, the second errors with the - pre-v0.21 "already registered" message. -- Order-of-delivery to multiple middlewares should be deterministic - (per the middleware-pipeline contract, not arbitrary). -- A read-only middleware disconnecting mid-stream must not break - the pipeline for the others. -- A custom-caveat middleware registered alongside read-only ones - should still work; verify the caveat-vs-read-only mutex is - per-client and not global. -- Registration cleanup on disconnect — if a middleware drops without - unregistering, its slot must be freed so the next attempt can - register. - -## Prerequisites - -- **lnd build:** v0.21.0-beta.rc1 or newer, with macaroons enabled - (default). -- **A middleware client** that opens the bidi stream on - `lnrpc.Lightning.RegisterRPCMiddleware`, sends a - `MiddlewareRegistration` with `read_only_mode=true`, and logs - every intercept it receives. The example in - [`docs/macaroons.md`](../../macaroons.md) or the - `lnrpc/lightning.proto` `RegisterRPCMiddleware` description is - the reference. For testing it's enough to write a 50-line - grpcurl wrapper or a small Go program. -- **Tools:** `lncli`, `grpcurl`, `jq`. - -## Scenarios - -### S1: Two read-only middlewares register and both observe an RPC - -**Goal:** Confirm the registration limit is gone and both clients -see the same intercepts. - -**Steps:** -1. Start middleware client A — register with - `middleware_name="mw-a"`, `read_only_mode=true`. Log every - intercept it receives. -2. Start middleware client B — register with - `middleware_name="mw-b"`, `read_only_mode=true`. Log every - intercept it receives. -3. From a separate client, run `lncli getinfo`. - -**Pass/Fail signal:** -- **PASS** if both A and B log a `GetInfo` intercept (request and - response), and `lncli getinfo` returns successfully. -- **FAIL** if B's registration is rejected with an - "already-registered" error, or B never receives any intercepts. - ---- - -### S2: A third read-only middleware can register too - -**Goal:** No magic-number-two limit hidden anywhere. - -**Steps:** Add a third middleware client C with the same -configuration. Issue another `lncli getinfo`. - -**Pass/Fail signal:** -- **PASS** if A, B, and C all log the intercept. -- **FAIL** if registration is rejected at any specific count, or - if any of the three stops receiving intercepts. - ---- - -### S3: One middleware disconnects without affecting the others - -**Goal:** Cleanup-on-disconnect works and the pipeline keeps -intercepting for the remaining clients. - -**Steps:** -1. With A and B registered, drop A's stream (Ctrl-C the client). -2. Wait 2–3 seconds. -3. Run another `lncli getinfo`. -4. Re-register a new A' under the same name. - -**Pass/Fail signal:** -- **PASS** if (a) B still receives the post-disconnect intercept, - (b) lnd's log records A's cleanup (`middleware ... disconnected` - or similar), and (c) the new A' registration succeeds. -- **FAIL** if B stops receiving intercepts after A drops, or if - A''s re-registration is rejected because the slot wasn't freed. - ---- - -### S4: A read-only middleware cannot alter a response - -**Goal:** Property still holds — read-only is read-only. - -**Steps:** From middleware client A, intercept a `GetInfo` -response and try to mutate it (e.g. change `identity_pubkey`) -before sending the `InterceptFeedback`. The framework must reject -the mutation. - -**Pass/Fail signal:** -- **PASS** if `lncli getinfo` returns the unmodified value, and - the daemon log records a rejection (`middleware attempted to - alter response` or similar). The mutating middleware can - optionally be disconnected by the daemon — verify that matches - the contract. -- **FAIL** if the mutation goes through (broken invariant), or - if a benign read-only intercept is incorrectly flagged as a - mutation. - ---- - -### S5: Read-only + custom-caveat middlewares coexist - -**Goal:** A read-only middleware and an independent -custom-caveat middleware can both register at the same time -without interfering. - -**Steps:** -1. Register middleware A with `read_only_mode=true`. -2. Bake a macaroon with caveat name `my-caveat`. -3. Register middleware B with - `custom_macaroon_caveat_name="my-caveat"` (and - `read_only_mode=false`). -4. Call `lncli getinfo` with the caveat-bearing macaroon. - -**Pass/Fail signal:** -- **PASS** if both A (read-only) and B (caveat) receive the - intercept, and the response is returned to the caller. A call - *without* the caveat macaroon must reach A but not B. -- **FAIL** if either registration is rejected with a "mutual - exclusion" error, or if the caveat-targeted middleware - receives intercepts that don't carry the caveat. - -## Failure investigation - -- **Subsystems:** `RPCS`, `RPCSV` (depending on which subsystem - owns the middleware pipeline in v0.21.0). -- **Useful greps:** `middleware`, `register`, `intercept`, - `read_only_mode`. -- **Registration-state check:** if lnd exposes a status RPC for - registered middlewares, query it before/after each scenario. - Otherwise, rely on log lines. - -## Related itests - -- `itest/lnd_macaroons_test.go` and any - `itest/lnd_middleware_test.go` — verify the multi-registration - test exists; add one if not. -- Unit tests in `rpcperms/`. - -## Out of scope - -- Caveat-based macaroons themselves — see - [`docs/macaroons.md`](../../macaroons.md). -- Performance of fan-out to many middlewares — qualitative - confirmation (it works) is sufficient for the RC; sustained - load testing is a separate effort. diff --git a/docs/testing-guides/v0.21.0/onion-messaging.md b/docs/testing-guides/v0.21.0/onion-messaging.md deleted file mode 100644 index d8d11160d..000000000 --- a/docs/testing-guides/v0.21.0/onion-messaging.md +++ /dev/null @@ -1,329 +0,0 @@ -# Onion Messaging + Rate Limiting — v0.21.0 RC Testing Guide - -**PRs:** #9868, #10089 (basic forwarding), #10612 (pathfinding), #10713 (rate limiting + channel-presence gate), #10754 (loopback drop) -**Risk:** headline -**Audience:** node operators, routing-node operators -**Backends affected:** all -**Networks:** regtest (primary), signet - -For the design and configuration model behind onion-message rate -limiting, read -[`docs/onion_message_rate_limiting.md`](../../onion_message_rate_limiting.md) -first. This guide tests the operational behavior it describes. - -## What this feature does - -v0.21.0 adds basic support for peer-to-peer onion message -**forwarding**. lnd does not yet ship a user-facing tool for -**constructing** onion messages from the operator side — the -`SendOnionMessage` RPC exists but takes pre-built `path_key`/`onion` -bytes and has no `lncli` wrapper. End-to-end RC testing of onion -messaging therefore relies on placing an lnd node **between two -non-lnd nodes** (Core Lightning, Eclair) that *do* expose -construct-and-send commands. The lnd node is the system under test; -the non-lnd nodes are drivers. - -Incoming onion messages on lnd pass through three defenses, in -order: - -1. **Loopback drop** (#10754): if the resolved next hop is the same - peer the message arrived from, drop it. -2. **Channel-presence gate** (#10713): drop messages from peers - without at least one fully open channel, unless - `protocol.onion-msg-relay-all=true`. -3. **Token-bucket rate limiters** (#10713): per-peer and global, - byte-denominated, applied in series (per-peer first). - -## Why it matters / what could break - -- **Channel-presence gate** is the Sybil defense. A regression - here lets cheap-identity attackers burn forwarding resources for - free. -- **Rate limiters** cap operator-borne bandwidth cost. A - regression in the byte-denominated accounting or the per-peer / - global ordering reintroduces the asymmetry the feature exists to - prevent. -- **Loopback drop** closes a traffic-amplification vector — a - missed drop means a hostile peer can bounce messages back at us. -- **Startup validation** (`burst < 65535 bytes`, partial-zero - configurations rejected) — silently accepting an invalid config - would leave operators thinking they had protection. - -## Prerequisites - -- **lnd build:** v0.21.0-beta.rc1 or newer. -- **Topology:** - ``` - Eclair (A, sender) ── lnd (SUT) ── Eclair / CLN (C, recipient) - ``` - with optional **Eclair (B, no-channel peer)** connected to lnd - for the channel-presence gate scenarios. -- **Primary driver: Eclair**, because `sendonionmessage` is a - single high-level call that takes a route + hex message and - builds the onion internally: - ```bash - eclair-cli sendonionmessage \ - --nodeIds=, \ - --message= - ``` - Reference: . -- **Alternative driver: Core Lightning (v24.11+).** Two-step: - 1. `lightning-cli createonion --hops='[...]' --assocdata=00...0 - --onion_size=` builds a Sphinx packet for the route. - Reference: - . - 2. `lightning-cli injectonionmessage --path_key= - --message=` causes CLN to behave as if it had just - received the onion from a peer, unwrapping and forwarding it - to the next hop (lnd, in our topology). Reference: - . -- **Tools:** `lncli`, `eclair-cli`, `lightning-cli` (optional), - `bitcoin-cli`, `jq`. - -Shell variables: -``` -LND_PUBKEY=$(lncli getinfo | jq -r '.identity_pubkey') -A_PUBKEY=$(eclair-cli getinfo | jq -r '.nodeId') # sender -B_PUBKEY=... # no-channel sender -C_PUBKEY=... # recipient -``` - -## Setup - -```bash -# 1. Start all nodes. lnd with default onion-msg limits. -# 2. Connect peers: -# Eclair A ↔ lnd -# Eclair B ↔ lnd (no channel — for S2) -# lnd ↔ Recipient C -# Use eclair-cli connect / lncli connect. -# 3. Open and confirm channels A↔lnd and lnd↔C. -# Do NOT open B↔lnd. - -# Setup verification on lnd: -lncli listchannels | jq '.channels | length' # ≥ 2 -lncli listpeers | jq '.peers | length' # ≥ 3 (A, B, C) -grep -E "onion-msg|OnionMsg" ~/.lnd/logs/*/lnd.log | head # confirm config loaded -``` - -## Scenarios - -### S1: Forward an onion message through lnd — happy path - -**Goal:** A driver-built onion message from Eclair A travels A → -lnd → C and is received by C with no drops on lnd. - -**Steps:** -```bash -# Eclair builds and sends a 2-hop onion message: A → lnd → C. -eclair-cli sendonionmessage \ - --nodeIds=$LND_PUBKEY,$C_PUBKEY \ - --message=$(printf 'hello' | xxd -p) -``` - -**Pass/Fail signal:** -- **PASS** if recipient C logs receipt of an onion message in the - same time window (Eclair logs `Received onion message`; for CLN - recipients, the `onion_message_recv` hook fires). lnd's log - shows no drop or rate-limit events. -- **FAIL** if C receives nothing, or lnd logs a - `channel-presence-gate` drop (A has a channel — the gate must - not trip), or a rate-limit log appears under default - settings for a single small message. - ---- - -### S2: Channel-presence gate drops a no-channel sender - -**Goal:** Eclair B is connected to lnd but has no channel; its -onion message is dropped at lnd's gate. - -**Steps:** -```bash -# From Eclair B (no channel with lnd), attempt a send through lnd. -eclair-cli -a $B_AUTH sendonionmessage \ - --nodeIds=$LND_PUBKEY,$C_PUBKEY \ - --message=$(printf 'should-drop' | xxd -p) -``` - -**Pass/Fail signal:** -- **PASS** if (a) C does not receive the message and (b) lnd's log - records a drop attributable to the channel-presence gate (search - for `no fully open channel` or the equivalent log key — verify - exact wording against the v0.21.0 build). -- **FAIL** if C receives the message (gate broken) or no drop log - appears for B's pubkey (silent drop with no audit trail). - ---- - -### S3: `relay-all` bypasses the channel-presence gate - -**Goal:** With `protocol.onion-msg-relay-all=true`, the no-channel -sender from S2 is no longer gated. Rate limiters still apply. - -**Steps:** -```bash -# Restart lnd with: protocol.onion-msg-relay-all=true -# Re-run the S2 send. -eclair-cli -a $B_AUTH sendonionmessage \ - --nodeIds=$LND_PUBKEY,$C_PUBKEY \ - --message=$(printf 'now-allowed' | xxd -p) -``` - -**Pass/Fail signal:** -- **PASS** if C receives the message and lnd's log no longer - records a channel-presence drop. A per-peer rate-limiter - bucket should now exist for B's pubkey (verify by tripping it, - per S4, with B as the sender). -- **FAIL** if lnd still drops at the gate (escape hatch broken) or - if rate limiting is also bypassed when only the gate should be. - ---- - -### S4: Per-peer rate limiter trips - -**Goal:** Eclair A hammering lnd over its per-peer cap should get -dropped once the bucket empties, with a one-shot info log. - -**Steps:** -```bash -# Restart lnd with a tight per-peer cap: -# protocol.onion-msg-peer-kbps=100 -# protocol.onion-msg-peer-burst-bytes=65540 - -# Fire onion messages near the spec maximum as fast as the script -# can drive Eclair. Use a payload that pads close to 32 KiB so each -# message debits the bucket substantially. -PAYLOAD=$(head -c 32000 /dev/urandom | xxd -p | tr -d '\n') -for i in $(seq 1 50); do - eclair-cli sendonionmessage \ - --nodeIds=$LND_PUBKEY,$C_PUBKEY \ - --message=$PAYLOAD & -done -wait -``` - -**Pass/Fail signal:** -- **PASS** if (a) lnd's log contains exactly **one** - `per-peer onion message rate limit engaged` info line (or the - v0.21.0 equivalent — verify the exact wording) for A's pubkey, - (b) subsequent drops are at trace level only, and (c) C's - receive count is bounded by the configured rate over the test - window. -- **FAIL** if no drops occur (limiter disabled) or the info log is - emitted repeatedly (log-flooding regression). - ---- - -### S5: Global rate limiter trips - -**Goal:** Multiple senders, each under their per-peer cap, -collectively trip the global cap. - -**Steps:** -```bash -# Restart lnd with the per-peer limiter loose and the global tight: -# protocol.onion-msg-peer-kbps=1024 -# protocol.onion-msg-peer-burst-bytes=262144 -# protocol.onion-msg-global-kbps=200 -# protocol.onion-msg-global-burst-bytes=131080 - -# Drive sends from A, B (relay-all enabled to allow B), and a third -# peer if available — all simultaneously, each under its per-peer -# budget. -``` - -**Pass/Fail signal:** -- **PASS** if lnd's log contains exactly one - `global onion message rate limit engaged` info line (no peer - prefix), and subsequent drops are at trace level. -- **FAIL** if no global drops occur, or the line is emitted with a - peer prefix (mis-attribution). - ---- - -### S6: Startup rejects invalid limiter configs - -**Goal:** Mixed-zero (rate=0 with burst>0, or vice versa) and -undersized-burst configs fail startup, as documented in the -rate-limiting design doc. **No driver required.** - -**Steps:** Start lnd with each of these in turn and capture exit -status: - -| Config | Expected to reject? | -|---|---| -| `peer-kbps=0`, `peer-burst-bytes=262144` | yes (rate 0, burst > 0) | -| `peer-kbps=100`, `peer-burst-bytes=0` | yes (rate > 0, burst 0) | -| `peer-kbps=100`, `peer-burst-bytes=32768`| yes (burst < 65535) | -| `peer-kbps=0`, `peer-burst-bytes=0` | no (cleanly disabled) | - -**Pass/Fail signal:** -- **PASS** if the three reject rows fail startup with a clear - error message naming the misconfigured option **before** the - gRPC endpoint comes up, and the disabled row starts cleanly. -- **FAIL** if any of the three invalid configs starts (silent - misconfiguration), or the disabled config errors out. - ---- - -### S7: Loopback drop - -**Goal:** An onion message whose resolved next hop is the sending -peer is dropped at lnd, not forwarded back. - -**Steps:** -```bash -# Eclair A constructs a route lnd → A — i.e. A is the recipient, -# lnd is the only intermediate hop. lnd will receive from A and -# resolve A as the next hop. -eclair-cli sendonionmessage \ - --nodeIds=$LND_PUBKEY,$A_PUBKEY \ - --message=$(printf 'loopback' | xxd -p) -``` - -**Pass/Fail signal:** -- **PASS** if lnd's log records a loopback drop (search for - `next hop is sending peer` or the v0.21.0 equivalent), and A - does not receive the message back over the inbound connection. -- **FAIL** if A receives the message back from lnd (the loopback - drop did not engage) or lnd silently drops with no audit trail. - -## Failure investigation - -- **Subsystems:** `PEER`, `DISC`, `CRTR`, and the onion-message - subsystem (verify exact name in v0.21.0 — probably `ONMSG` or - similar). Set to `debug` for diagnosis. -- **Useful greps:** - - `grep -i "onion message" lnd.log` - - `grep -i "rate limit" lnd.log` - - `grep -i "channel-presence" lnd.log` -- **Driver-side observability:** Eclair logs at `INFO` show - outbound `sendonionmessage` calls and inbound message events. CLN - surfaces inbound messages via the `onion_message_recv` hook. - -## Related itests (not the RC test surface, but useful references) - -- `itest/lnd_onion_message_test.go` — `testOnionMessage` -- `itest/lnd_onion_message_forward_test.go` — - `testOnionMessageForwarding` with `buildForwardNextNodePath`, - `buildForwardSCIDPath`, `buildConcatenatedPath` -- `itest/config_onion_ratelimit_test.go` — limiter config -- `onionmessage/test_utils.go` — `BuildOnionMessage` helper - (`*testing.T`-only) - -These exercise the same behaviors via Go-side construction and are -the maintainers' authoritative test. RC testers shouldn't need to -run them; this guide covers what's observable from a real -deployment with mixed-implementation drivers. - -## Out of scope - -- Pathfinding for onion messages (#10612) — used internally by lnd - but not exposed via a user-facing RPC in v0.21.0, so not - directly testable from outside this release. Confirm via itests. -- BOLT-12 offers / blinded-payment-route construction — separate - feature, not in v0.21.0. -- lnd-as-sender of onion messages — v0.21.0 is forwarding-only - from the operator's perspective. The `SendOnionMessage` RPC is - low-level and has no `lncli` wrapper. diff --git a/docs/testing-guides/v0.21.0/payment-rpcs.md b/docs/testing-guides/v0.21.0/payment-rpcs.md deleted file mode 100644 index 38f54fcc1..000000000 --- a/docs/testing-guides/v0.21.0/payment-rpcs.md +++ /dev/null @@ -1,221 +0,0 @@ -# New Payment-Adjacent RPCs — v0.21.0 RC Testing Guide - -**PRs:** #10666 (`DeleteForwardingHistory`), #10436 (MuSig2 coordinator nonces), #10296 (`EstimateFee` inputs), #10520 (HTLC event invoice failures), #10543 (`SubscribeChannelEvents` update events) -**Risk:** new-rpc -**Audience:** RPC clients, routing nodes, MuSig2 coordinator integrators, LSPs -**Backends affected:** all -**Networks:** regtest (primary) - -## What this feature does - -v0.21.0 ships five small RPC additions/updates relevant to payment -and channel flows. They are bundled here because each is too narrow -for its own guide, but each has a clear pass/fail signal worth -checking on the RC. - -1. **`router.DeleteForwardingHistory`** (#10666). Operator RPC to - purge old forwarding events. Cutoff timestamp must be at least 1 - hour in the past. -2. **`MuSig2RegisterCombinedNonce` / `MuSig2GetCombinedNonce`** - (#10436). Lets a coordinator pre-aggregate MuSig2 nonces - externally and register the result. MuSig2 v1.0.0rc2 only. -3. **`EstimateFee` with `inputs`** (#10296). Explicit input - selection for fee estimation; new `inputs` field on - `EstimateFeeRequest`, new `--utxos` flag on - `lncli estimatefee`. -4. **HTLC event invoice-level failure detail** (#10520). routerrpc - HTLC event subscribers now receive specific failure causes for - invoice-validation failures instead of `UNKNOWN`. -5. **`SubscribeChannelEvents` update events** (#10543). - `SubscribeChannelEvents` now emits a channel update event for - state changes, not only open/close/active/inactive. - -## Why each matters / what could break - -- **`DeleteForwardingHistory`** is destructive. The 1-hour guard - must hold; off-by-one or misinterpreted timestamps could wipe - recent data. -- **MuSig2 combined-nonce RPCs** are signing-protocol territory. - Wrong nonce aggregation produces invalid signatures. -- **`EstimateFee` inputs**: a request that names a non-existent or - spent UTXO must fail clearly, not silently produce a meaningless - estimate. -- **HTLC invoice failure detail**: routing nodes that grep failures - out of subscriber streams will mis-classify if `UNKNOWN` still - surfaces for invoice-validation cases. -- **`SubscribeChannelEvents` update events**: any client iterating - over event-type values may need to handle a new variant. If the - daemon emits a malformed event, subscribers can disconnect or - crash. - -## Prerequisites - -- **lnd build:** v0.21.0-beta.rc1 or newer. -- **Peers:** Alice and Bob (with a channel for the payment-adjacent - scenarios); Carol for forwarding-history scenarios so Alice can - forward Bob → Carol traffic. -- **MuSig2 coordinator client** for S2 — typically the `signrpc` - test client from `signer/musig2_test.go` or your own. -- **Tools:** `lncli`, `grpcurl`, `jq`, `bitcoin-cli`. - -## Scenarios - -### S1: `DeleteForwardingHistory` deletes old events; 1-hour guard rejects recent cutoffs - -**Goal:** Confirm both the success path and the safety guard. - -**Steps:** -```bash -# Drive some forwards through Alice (need a 3-node setup). -# Wait a bit, then query forwarding history. -$LNCLI_A fwdinghistory | jq '.forwarding_events | length' > /tmp/fwd-pre.txt - -# Capture a timestamp at least 1h in the past. -CUTOFF_OK=$(date -d '2 hours ago' +%s) # GNU date -# macOS: CUTOFF_OK=$(date -v-2H +%s) -CUTOFF_BAD=$(date -d '5 minutes ago' +%s) - -# Delete old events (success). -grpcurl ... -d "{\"end_time_ns\": \"$((CUTOFF_OK*1000000000))\"}" \ - routerrpc.Router/DeleteForwardingHistory - -# Try a recent cutoff (should error). -grpcurl ... -d "{\"end_time_ns\": \"$((CUTOFF_BAD*1000000000))\"}" \ - routerrpc.Router/DeleteForwardingHistory -echo $? -``` - -(Replace the field name with whatever the proto actually exposes; -verify against `routerrpc.proto` for v0.21.0.) - -**Pass/Fail signal:** -- **PASS** if (a) the first call succeeds and `fwdinghistory` - afterward shows fewer events than before, and (b) the second call - fails with a clear error message about the 1-hour minimum. -- **FAIL** if the recent-cutoff call succeeds (the safety guard is - broken). - ---- - -### S2: `MuSig2RegisterCombinedNonce` / `MuSig2GetCombinedNonce` round-trip - -**Goal:** A coordinator can register a pre-aggregated combined nonce -and later retrieve it for a session. - -**Steps:** Run the coordinator-based MuSig2 flow end-to-end with at -least two signing participants. After the coordinator aggregates -nonces externally, call `MuSig2RegisterCombinedNonce` with the -session ID and combined nonce. From a participant, call -`MuSig2GetCombinedNonce` and verify the returned value. - -**Pass/Fail signal:** -- **PASS** if `MuSig2GetCombinedNonce` returns the same bytes - registered, and the subsequent partial-sign / finalize completes - with a valid signature. -- **FAIL** if the combined nonce roundtrips wrong, if the finalize - produces an invalid signature, or if the call rejects MuSig2 - v1.0.0rc2 sessions. - ---- - -### S3: `EstimateFee` with explicit `inputs` - -**Goal:** A fee estimate that names specific UTXOs uses those UTXOs; -naming a spent or non-existent UTXO errors cleanly. - -**Steps:** -```bash -# Pick two confirmed UTXOs on Alice. -$LNCLI_A listunspent --min_confs=1 | jq '.utxos[] | .outpoint' - -# Estimate fee using --utxos. -$LNCLI_A estimatefee --conf_target=6 \ - --utxos="$UTXO1" --utxos="$UTXO2" \ - --addr_to_amount='{"": 50000}' - -# Try a bogus UTXO. -$LNCLI_A estimatefee --conf_target=6 \ - --utxos="0000000000000000000000000000000000000000000000000000000000000000:0" \ - --addr_to_amount='{"": 50000}' -echo $? -``` - -**Pass/Fail signal:** -- **PASS** if the first call returns a fee estimate consistent with - using exactly those two UTXOs, and the second call fails with a - clear "input not found" or "not spendable" message. -- **FAIL** if the second call silently returns an estimate - (ignoring the bad input), or the first uses different UTXOs than - requested. - ---- - -### S4: HTLC event subscribers see invoice-level failure detail (not `UNKNOWN`) - -**Goal:** routerrpc HTLC event stream now provides specific reasons -for invoice-validation failures. - -**Steps:** -- Subscribe to `routerrpc.SubscribeHtlcEvents` on Bob (the - recipient). -- From Alice, attempt to pay a Bob invoice in a way that - invoice-validation rejects (e.g. expired invoice, wrong - preimage attempt, amount mismatch). Repeat for each failure mode - you want to test. -- Capture the failure-detail field from each emitted event. - -**Pass/Fail signal:** -- **PASS** if every invoice-validation failure surfaces a specific - reason (e.g. `INVOICE_EXPIRED`, `INCORRECT_PAYMENT_AMOUNT`, - `INVOICE_ALREADY_CANCELED`), not the legacy `UNKNOWN`. -- **FAIL** if any of these still emit `UNKNOWN`. - ---- - -### S5: `SubscribeChannelEvents` emits update events on state changes - -**Goal:** Confirm the new event variant fires for the -state-change cases it covers. - -**Steps:** -- Subscribe to `lnrpc.SubscribeChannelEvents` on Alice via a - long-running grpcurl session. -- Drive state changes: - - Open a channel → expect existing `pending_open_channel` and - `open_channel` events. - - Push a few payments → expect the new update event(s). - - Coop-close → expect existing close events. -- Inspect every emitted event's `type` field. - -**Pass/Fail signal:** -- **PASS** if at least one event with the new `update` type fires - during the test window, and the payload references the correct - channel. -- **FAIL** if no update event fires, or if a malformed event causes - the subscriber stream to error / disconnect. - -## Failure investigation - -- **Subsystems:** `RPCS`, `ROUTING`, `CRTR`, `SIGN`. -- **Useful greps:** `DeleteForwardingHistory`, - `MuSig2RegisterCombinedNonce`, `EstimateFee`, `HtlcEvent`, - `ChannelEvent`. -- **Proto-level surface:** check - `lnrpc/routerrpc/router.proto` (forwarding history, HTLC events), - `lnrpc/signrpc/signer.proto` (MuSig2 coordinator), and - `lnrpc/lightning.proto` (`EstimateFee`, `SubscribeChannelEvents`) - to confirm field names match the calls above before recording a - FAIL. - -## Related itests - -- Each of these RPCs typically has a corresponding itest. Verify in - `itest/` (e.g. `itest/lnd_forward_test.go`, - `itest/lnd_musig2_test.go`). - -## Out of scope - -- Payment SQL migration — separate guide - ([`payment-sql-migration.md`](./payment-sql-migration.md)). -- BOLT-12 / offers — not in v0.21.0. -- Performance characteristics of `EstimateFee` with many inputs. diff --git a/docs/testing-guides/v0.21.0/payment-sql-migration.md b/docs/testing-guides/v0.21.0/payment-sql-migration.md deleted file mode 100644 index 1ac1fca2a..000000000 --- a/docs/testing-guides/v0.21.0/payment-sql-migration.md +++ /dev/null @@ -1,262 +0,0 @@ -# Payment Store KV → SQL Migration — v0.21.0 RC Testing Guide - -**PRs:** #10153, #9147, #10287, #10291, #10368, #10292, #10307, #10308, #10373, #10485 (migration), #10535, #10627 (mainline promotion) -**Risk:** headline -**Audience:** node operators on `sqlite` or `postgres` backends with `--db.use-native-sql` -**Backends affected:** sqlite, postgres -**Networks:** all - -> ⚠️ **TBD — pending developer confirmation.** The behavior of -> `--db.skip-native-sql-migration=true` for **payments** specifically -> is under verification. The flag's description in `lncfg/db.go` -> (and S6 / the rescue-path bullet in this guide) implies the SQL -> payment tables are used empty after the flag is set; the intent -> may instead be that lnd continues reading payments from the KV -> store. Treat the rescue-path scenario as unconfirmed until this -> note is removed. - -## What this feature does - -v0.21.0 finishes the payments store migration from `kvdb` to native -SQL and promotes it to mainline. Nodes running with -`--db.use-native-sql=true` on a `sqlite` or `postgres` backend will, -on their first startup against this build, run a migration that -copies every payment row, attempt, and route hop from the embedded -`kvdb`-on-SQL tables into a normalized SQL schema. All subsequent -`ListPayments`, `QueryPayments`, `FetchPayment`, and the new -`omit_hops` / cursor-paginated query variants run directly against -the SQL schema. - -Nodes still on `bbolt` are unaffected by this migration (they don't -have `--db.use-native-sql`). Operators wanting to move from bbolt to -sqlite or postgres should use -[`lndinit`](https://github.com/lightninglabs/lndinit/blob/main/docs/data-migration.md) -*before* upgrading to v0.21.0, so the SQL payment migration sees a -populated source. - -## Why it matters / what could break - -This is a one-shot migration over potentially very large tables -(some nodes have millions of payment rows). The blast radius: - -- **Migration fails mid-run** → lnd refuses to start. The standard - recovery is to fix the underlying cause and restart; the last - resort is `--db.skip-native-sql-migration=true`, which abandons - partial migration progress and **loses payment history**. -- **Migration completes but data is corrupted** → silent. Surfaces - later as `ListPayments` rows missing, attempts attributed to the - wrong payment, or settled payments that report as failed. -- **Performance regression** → `ListPayments` slower than pre-migration - for users with deep history, or specific filters (date range, by - payment-hash) regressing. -- **bbolt user accidentally enables `--db.use-native-sql`** → empty - payment history because the SQL tables are empty and no KV source - exists to migrate from. (Documentation guards against this; verify - the failure mode is clean.) - -## Prerequisites - -- **lnd build:** v0.21.0-beta.rc1 or newer. -- **Existing v0.20.x node with payment history** on `sqlite` or - `postgres`, with `--db.use-native-sql=true` already enabled in v0.20 - (so invoices and graph are already SQL; payments are the new - addition). - - If you don't have one, build a fresh `sqlite` v0.20.x node and - push a few hundred payments through it before upgrading. -- **A backup of the v0.20 database** (`.bak` of the sqlite file or a - postgres `pg_dump`). Required — this migration is not reversible. -- **Tools:** `lncli`, `sqlite3` (or `psql`), `jq`. - -Shell variables: -``` -ALICE_DIR=~/.alice -LNCLI_A="lncli --lnddir=$ALICE_DIR" -DB=$ALICE_DIR/data/chain/bitcoin//lnd.db # or your sqlite path -``` - -## Setup - -```bash -# 1. On the v0.20.x build, capture a baseline of payment data. -$LNCLI_A listpayments --max_payments=0 --reversed | \ - jq '{count: (.payments | length), total_sat: ([.payments[].value_sat | tonumber] | add)}' > /tmp/payments-pre.json - -$LNCLI_A listpayments --max_payments=5 --reversed | \ - jq '[.payments[] | {payment_hash, status, value_sat, creation_date}]' > /tmp/payments-sample-pre.json - -# 2. Stop lnd cleanly. -$LNCLI_A stop - -# 3. Backup the database. -cp $DB $DB.pre-v0.21.bak # sqlite -# OR: pg_dump ... > /tmp/lnd-pre-v0.21.sql - -# 4. Swap the binary to v0.21.0-rc1 and start lnd back up. -# Leave --db.use-native-sql=true in lnd.conf (or on the CLI). -``` - -## Scenarios - -### S1: Migration runs and lnd starts cleanly - -**Goal:** First startup against v0.21.0 runs the payment migration -to completion and the node becomes operational. - -**Steps:** Start lnd with the existing v0.20 database and watch the -log. - -**Expected:** -- Log lines indicating the payment migration started, e.g. - `Running migration: payments KV -> SQL`. -- Log lines indicating completion (no error). -- `lncli getinfo` returns successfully. - -**Pass/Fail signal:** -```bash -$LNCLI_A getinfo | jq -r '.identity_pubkey' -``` -- **PASS** if a valid pubkey is returned within 60s of startup (or - longer, scaled to your payment history; document the time). -- **FAIL** if lnd exits with a migration error, or if `getinfo` - hangs past 5 minutes (the migration is stuck — capture the log). - ---- - -### S2: Post-migration payment count matches pre-migration - -**Goal:** No rows lost during migration. - -**Steps:** -```bash -$LNCLI_A listpayments --max_payments=0 --reversed | \ - jq '{count: (.payments | length), total_sat: ([.payments[].value_sat | tonumber] | add)}' > /tmp/payments-post.json - -diff /tmp/payments-pre.json /tmp/payments-post.json -``` - -**Pass/Fail signal:** -- **PASS** if `diff` shows no output (`count` and `total_sat` match). -- **FAIL** if either field differs. Capture both files and the - log. - ---- - -### S3: Spot-check individual payments by hash - -**Goal:** Per-row data is preserved (not just aggregate counts). - -**Steps:** -```bash -# Pick 5 payments from the pre-migration sample. -for ph in $(jq -r '.[].payment_hash' /tmp/payments-sample-pre.json); do - $LNCLI_A trackpayment $ph 2>/dev/null || \ - $LNCLI_A listpayments --max_payments=1 | \ - jq --arg ph "$ph" '.payments[] | select(.payment_hash==$ph)' -done > /tmp/payments-sample-post.json -``` - -**Pass/Fail signal:** -- **PASS** if every sampled payment matches its pre-migration - record on `status`, `value_sat`, `creation_date`, and the route - hops (if you didn't request `omit_hops`). -- **FAIL** if any row differs or is missing. - ---- - -### S4: `ListPayments` with `omit_hops=true` excludes hop data - -**Goal:** The new `omit_hops` filter (introduced in #10535) works on -the new SQL store. - -**Steps:** -```bash -# Older lncli builds may not expose this flag yet; fall back to gRPC. -$LNCLI_A listpayments --max_payments=10 --include_incomplete=false 2>/dev/null | \ - jq '.payments[0].htlcs[0].route | length' > /tmp/with-hops.txt - -# Then call with omit_hops=true (via gRPC, e.g. grpcurl): -grpcurl ... -d '{"max_payments": 10, "omit_hops": true}' \ - lnrpc.Lightning/ListPayments | \ - jq '.payments[0].htlcs[0].route' > /tmp/no-hops.txt -``` - -**Pass/Fail signal:** -- **PASS** if `with-hops.txt` shows a positive number and `no-hops.txt` - is `null` or has an empty `hops` list. -- **FAIL** if `omit_hops=true` still returns hops, or if the call errors. - ---- - -### S5: `bbolt + --db.use-native-sql` user is warned cleanly - -**Goal:** Confirm that a user who enables `--db.use-native-sql` on a -bbolt backend either hits a clean refusal at startup, or sees -documented behavior — not silent data loss. - -**Steps:** -- Take a fresh bbolt-backed v0.20 node with some payment history. -- Edit `lnd.conf` to add `db.use-native-sql=true` (without using - lndinit first). -- Start v0.21.0-rc1. - -**Pass/Fail signal:** -- **PASS** if lnd either (a) refuses to start with a clear message - pointing operators at `lndinit`, or (b) starts but logs a clear - warning that bbolt history is not migrated by this flag. -- **FAIL** if lnd starts, `getinfo` succeeds, and `listpayments` - returns an empty list with no warning — that's a silent data-loss - footgun for operators. - ---- - -### S6: `--db.skip-native-sql-migration` rescue path - -**Goal:** The skip-migration flag works as the documented last -resort. Only run this on a copy of the database. - -**Steps:** -```bash -# Restore the backup, then start v0.21 with the skip flag. -cp $DB.pre-v0.21.bak $DB -# Add: db.skip-native-sql-migration=true to lnd.conf -``` - -**Pass/Fail signal:** -- **PASS** if lnd starts, logs a clear warning that payment history - has been abandoned, and `listpayments` returns an empty list (the - intended behavior of the rescue flag — payments are sacrificed to - keep channels working). -- **FAIL** if lnd refuses to start, or if it starts but silently - retains stale KV payment data. - -## Failure investigation - -- **Subsystems:** `LNDB`, `RPCS`, `CRTR`. -- **Migration log lines:** grep for `MigratePaymentsKVToSQL`, - `migration version=`, `payment migration`. -- **Direct SQL inspection (sqlite):** - ```sql - -- Count rows in the new SQL tables. - SELECT COUNT(*) FROM payments; - SELECT COUNT(*) FROM payment_attempts; - ``` -- **Common past issues / regressions:** - - Cross-database timestamp handling — #10535 fixed a class of bugs - where postgres and sqlite stored creation timestamps with - different precisions. Watch `creation_date` mismatches. - - Schema indexes — verify that the indexes added in #10535 exist - after migration (`.indices payments` in sqlite). - -## Related itests - -- `payments/db/migration1/sql_migration_test.go` — migration unit/integration tests. -- `itest/lnd_payment_test.go` — end-to-end payment behavior. - -## Out of scope - -- Channel-state SQL migration (separate effort, not in v0.21.0). -- bbolt → sqlite/postgres backup migration — handled by - [`lndinit`](https://github.com/lightninglabs/lndinit/blob/main/docs/data-migration.md), not this guide. -- Performance benchmarking — call out qualitative regressions - (`listpayments` taking many seconds when it was sub-second on - v0.20), but exhaustive benchmarking is a separate effort. diff --git a/docs/testing-guides/v0.21.0/production-taproot-channels.md b/docs/testing-guides/v0.21.0/production-taproot-channels.md deleted file mode 100644 index 1c851de17..000000000 --- a/docs/testing-guides/v0.21.0/production-taproot-channels.md +++ /dev/null @@ -1,256 +0,0 @@ -# Production Simple Taproot Channels — v0.21.0 RC Testing Guide - -**PRs:** #9985 (production support), #10763 (acceptor + RBF coop follow-up), #10672 (private-taproot funding script bug fix) -**Risk:** headline -**Audience:** node operators, LSPs, wallet integrators, channel-acceptor clients -**Backends affected:** all -**Networks:** regtest (primary), signet, mainnet - -## What this feature does - -v0.21.0 adds the production (final) variant of simple taproot -channels, negotiated via feature bits 80/81. Production taproot -channels use a more optimized commitment script -(`OP_CHECKSIGVERIFY` instead of `OP_CHECKSIG` + `OP_DROP`) and encode -MuSig2 nonces in `channel_reestablish` and `revoke_and_ack` as a map -keyed by the funding TXID. - -The nonce type used by a channel is auto-detected from the negotiated -channel type, not the peer's advertised feature bits. The RPC -channel acceptor now also reports production taproot opens with the -`SIMPLE_TAPROOT_FINAL` commitment type across every combination of -the `scid-alias` and `zero-conf` modifiers. - -## Why it matters / what could break - -- Misnegotiation between staging-taproot (existing) and final-taproot - (new) peers → channel open fails or, worse, opens with mismatched - script versions and force-closes at first commitment. -- Wrong nonce encoding on `channel_reestablish` after a reconnect → - peers cannot resume the channel; surface as repeated reconnect - loops with `channel_reestablish` errors in the log. -- Channel-acceptor clients seeing `UNKNOWN_COMMITMENT_TYPE` instead - of `SIMPLE_TAPROOT_FINAL` for production taproot opens with - `scid-alias` or `zero-conf` modifiers (the bug #10763 fixed — - guard against regression). -- Private taproot channels with a v1 gossip entry whose funding - script gets reconstructed as legacy P2WSH on restart (#10672). - Surfaces as missed-spend detection. - -## Prerequisites - -- **lnd build:** v0.21.0-beta.rc1 or newer. -- **Backend:** `bitcoind` (regtest). -- **Peers:** Alice and Bob, both started with `--protocol.simple-taproot-chans`. -- **Tools:** `lncli`, `bitcoin-cli`, `jq`. - -Shell variables used below: -``` -ALICE_RPC=localhost:10001 -BOB_RPC=localhost:10002 -ALICE_MAC=~/.alice/data/chain/bitcoin/regtest/admin.macaroon -BOB_MAC=~/.bob/data/chain/bitcoin/regtest/admin.macaroon -ALICE_DIR=~/.alice -BOB_DIR=~/.bob -LNCLI_A="lncli --rpcserver=$ALICE_RPC --macaroonpath=$ALICE_MAC --lnddir=$ALICE_DIR" -LNCLI_B="lncli --rpcserver=$BOB_RPC --macaroonpath=$BOB_MAC --lnddir=$BOB_DIR" -``` - -Both nodes must run with at least: -``` -protocol.simple-taproot-chans=1 -``` - -## Setup - -```bash -# 1. Start a clean bitcoind in regtest and mine a few blocks. -bitcoin-cli -regtest createwallet test -ADDR=$(bitcoin-cli -regtest getnewaddress) -bitcoin-cli -regtest generatetoaddress 200 $ADDR - -# 2. Start Alice and Bob with --protocol.simple-taproot-chans. -# (Use your usual two-node regtest setup. The flag matters.) - -# 3. Connect Alice to Bob. -BOB_PUB=$($LNCLI_B getinfo | jq -r '.identity_pubkey') -$LNCLI_A connect $BOB_PUB@127.0.0.1:9736 - -# 4. Fund Alice's on-chain wallet. -ALICE_ADDR=$($LNCLI_A newaddress p2tr | jq -r '.address') -bitcoin-cli -regtest sendtoaddress $ALICE_ADDR 1 -bitcoin-cli -regtest generatetoaddress 6 $ADDR - -# Setup verification: -$LNCLI_A walletbalance | jq '.confirmed_balance' -# Expected: "100000000" (1 BTC in sats) -``` - -## Scenarios - -### S1: Open a production (final) taproot channel — happy path - -**Goal:** Verify that a `taproot-final` channel opens, confirms, and -reports `SIMPLE_TAPROOT_FINAL` as its commitment type. - -**Steps:** -```bash -$LNCLI_A openchannel \ - --node_key=$BOB_PUB \ - --local_amt=5000000 \ - --channel_type=taproot-final - -# Mine to confirm. -bitcoin-cli -regtest generatetoaddress 6 $ADDR -``` - -**Expected:** -- `openchannel` returns a funding-txid; no error. -- After 6 confirmations, the channel appears in `listchannels` on - both sides with `commitment_type == "SIMPLE_TAPROOT_FINAL"`. - -**Pass/Fail signal:** -```bash -$LNCLI_A listchannels | \ - jq '.channels[] | select(.remote_pubkey=="'$BOB_PUB'") | .commitment_type' -``` -- **PASS** if the output is `"SIMPLE_TAPROOT_FINAL"`. -- **FAIL** if `"SIMPLE_TAPROOT"` (staging), `"ANCHORS"`, or any - other value — that means negotiation fell back to the wrong type. - ---- - -### S2: Reconnect a production taproot channel — `channel_reestablish` round-trip - -**Goal:** Confirm the map-based nonce encoding keyed by funding TXID -survives a reconnect. This is the most likely place for production -taproot to regress, because nonce-type detection now flows from the -negotiated channel type instead of peer feature bits. - -**Steps:** -```bash -# With the S1 channel up, disconnect and reconnect Bob. -$LNCLI_A disconnect $BOB_PUB -sleep 2 -$LNCLI_A connect $BOB_PUB@127.0.0.1:9736 -sleep 3 -``` - -**Expected:** -- Reconnection completes. -- `listpeers` shows Bob back as connected. -- The channel from S1 still reports `active: true`. - -**Pass/Fail signal:** -```bash -$LNCLI_A listchannels | \ - jq '.channels[] | select(.remote_pubkey=="'$BOB_PUB'") | .active' -``` -- **PASS** if the output is `true`. -- **FAIL** if `false`, or if Alice's log contains - `unable to handle upstream reestablish msg` or - `received nonce of wrong type` — the nonce-type auto-detection - regressed. - ---- - -### S3: Send a payment over a production taproot channel - -**Goal:** End-to-end HTLC settlement on the new commitment type. - -**Steps:** -```bash -INV=$($LNCLI_B addinvoice --amt=10000 | jq -r '.payment_request') -$LNCLI_A payinvoice --force $INV -``` - -**Pass/Fail signal:** -```bash -$LNCLI_A listpayments | jq '.payments[-1].status' -``` -- **PASS** if the output is `"SUCCEEDED"`. -- **FAIL** otherwise (in particular `"IN_FLIGHT"` for more than ~10s - on regtest indicates a stuck HTLC). - ---- - -### S4: RPC channel acceptor reports `SIMPLE_TAPROOT_FINAL` - -**Goal:** Regression guard for #10763 — the acceptor must report -production taproot opens with the correct commitment type for every -combination of scid-alias and zero-conf modifiers, not -`UNKNOWN_COMMITMENT_TYPE`. - -**Steps:** -- Register an RPC channel acceptor against Bob (any external client - using `lnrpc.Lightning.ChannelAcceptor` bidi stream). Have it log - the `ChannelAcceptRequest.commitment_type` field and accept. -- From Alice, open four channels in turn, each with a different - combination of flags on `openchannel`: - 1. `--channel_type=taproot-final` - 2. `--channel_type=taproot-final --zero_conf` - 3. `--channel_type=taproot-final --scid_alias` - 4. `--channel_type=taproot-final --zero_conf --scid_alias` - - (Zero-conf and SCID-alias also require the relevant `--protocol.*` - flags and `--protocol.option-scid-alias` on both nodes; consult - [`docs/zero_conf_channels.md`](../../zero_conf_channels.md).) - -**Pass/Fail signal:** -- **PASS** if the acceptor logs `commitment_type == SIMPLE_TAPROOT_FINAL` - for all four opens. -- **FAIL** if any open shows `UNKNOWN_COMMITMENT_TYPE`, - `SIMPLE_TAPROOT` (staging), or anything else. - ---- - -### S5: Cooperative close (non-RBF) of a production taproot channel - -**Goal:** Plain coop close still works on `taproot-final`. RBF -coop-close is covered separately in -[`rbf-taproot-coop-close.md`](./rbf-taproot-coop-close.md). - -**Steps:** -```bash -CP=$($LNCLI_A listchannels | \ - jq -r '.channels[] | select(.remote_pubkey=="'$BOB_PUB'") | .channel_point') -$LNCLI_A closechannel --funding_txid=${CP%:*} --output_index=${CP##*:} -bitcoin-cli -regtest generatetoaddress 6 $ADDR -``` - -**Pass/Fail signal:** -- **PASS** if the channel disappears from `listchannels` on both - sides and the close-tx is mined. -- **FAIL** if the close hangs, force-closes instead of cooperating, - or the close transaction fails to relay. - -## Failure investigation - -- **Logs to grep on either side:** - - `grep -iE "taproot|musig|reestablish" ~/.alice/logs/bitcoin/regtest/lnd.log` - - Subsystems to set to `debug`: `PEER`, `CNCT`, `HSWC`, `LNWL`. -- **Channel-state introspection:** - - `lncli listchannels --include_channel_status_flags` — look at - `commitment_type` and `local_chan_reserve_sat`. - - `lncli pendingchannels` — for in-flight opens, check - `commitment_type` matches what was requested. -- **Prior regressions to watch for:** - - #10672 — private taproot channels with v1 gossip rebuilding - their funding script as legacy P2WSH on restart. Surfaces as - failed spend detection during force-close. - - #10763 — acceptor `UNKNOWN_COMMITMENT_TYPE` for production - taproot + scid-alias/zero-conf combinations. - -## Related itests - -- `itest/lnd_open_channel_test.go` — taproot open paths. -- `itest/lnd_taproot_test.go` — taproot-specific HTLC and close flows. -- `itest/lnd_channel_force_close_test.go` — force-close on taproot. - -## Out of scope - -- RBF cooperative close on taproot — see - [`rbf-taproot-coop-close.md`](./rbf-taproot-coop-close.md). -- Splice on taproot channels — not in v0.21.0. -- Taproot overlay channels (`--protocol.simple-taproot-overlay-chans`) - — separate commitment type, not the focus of this guide. diff --git a/docs/testing-guides/v0.21.0/rbf-taproot-coop-close.md b/docs/testing-guides/v0.21.0/rbf-taproot-coop-close.md deleted file mode 100644 index ee1dd6450..000000000 --- a/docs/testing-guides/v0.21.0/rbf-taproot-coop-close.md +++ /dev/null @@ -1,232 +0,0 @@ -# RBF Cooperative Close on Taproot Channels — v0.21.0 RC Testing Guide - -**PRs:** #10063 (RBF coop close + taproot/MuSig2), #10763 (overlay narrowing) -**Risk:** headline -**Audience:** node operators, channel-acceptor clients -**Backends affected:** all -**Networks:** regtest (primary), signet - -> ⚠️ **Authoring note:** Please verify the exact CLI mechanism for -> triggering successive RBF iterations on a coop close (re-running -> `closechannel` with a higher fee vs. a dedicated bump RPC) against -> the latest behavior before publishing. The scenarios below assume -> re-running `closechannel` re-enters the state machine and produces -> a new `ClosingComplete`. - -## What this feature does - -v0.21.0 extends the RBF cooperative close protocol -(`--protocol.rbf-coop-close`, introduced earlier) to simple taproot -channels. Each RBF iteration produces a fresh `ClosingComplete` / -`ClosingSig` pair with new MuSig2 partial signatures, using the JIT -(just-in-time) nonce pattern: the closer's nonce is bundled with its -signature in `ClosingComplete`, and the closee rotates its nonce via -`NextCloseeNonce` in `ClosingSig` for every round. The state machine -stores the `MusigPartialSig` and invalidates nonces after each -signing round to prevent reuse. - -A follow-up (#10763) narrows the RBF coop-close auto-enable to -*skip* taproot-overlay channels, since the RBF close state machine -does not yet thread through the `AuxCloser` hook overlay channels -rely on. - -## Why it matters / what could break - -- **Nonce reuse across RBF rounds** is a hard MuSig2 violation — if - it happens, signatures become forgeable. Watch for it. -- A regression on the JIT nonce ordering surfaces as repeated - `ClosingComplete` round-trips that never produce a confirming - transaction. -- Taproot-overlay channels accidentally entering the RBF flow will - produce nil-pointer dereferences or aux-close build failures. -- A non-taproot peer that signals `--protocol.rbf-coop-close` should - still complete a normal (non-MuSig2) RBF coop close; regressions - in the channel-type dispatch could break that path too. - -## Prerequisites - -- **lnd build:** v0.21.0-beta.rc1 or newer, on both peers. -- **Backend:** `bitcoind` (regtest); a real fee market makes this - easier to test on signet. -- **Peers:** Alice and Bob, both started with: - ``` - protocol.simple-taproot-chans=1 - protocol.rbf-coop-close=1 - ``` -- **Tools:** `lncli`, `bitcoin-cli`, `jq`. - -Shell variables: same as -[`production-taproot-channels.md`](./production-taproot-channels.md). - -## Setup - -```bash -# 1. With both nodes started under the prerequisites, open a -# production taproot channel from Alice to Bob. -$LNCLI_A openchannel \ - --node_key=$BOB_PUB \ - --local_amt=5000000 \ - --channel_type=taproot-final - -bitcoin-cli -regtest generatetoaddress 6 $ADDR - -CP=$($LNCLI_A listchannels | \ - jq -r '.channels[] | select(.remote_pubkey=="'$BOB_PUB'") | .channel_point') -echo "channel_point=$CP" - -# Setup verification: -$LNCLI_A listchannels | \ - jq '.channels[] | select(.remote_pubkey=="'$BOB_PUB'") | .commitment_type' -# Expected: "SIMPLE_TAPROOT_FINAL" -``` - -## Scenarios - -### S1: First-round RBF coop close on a taproot channel - -**Goal:** A single `closechannel` call on a `taproot-final` channel -produces a `ClosingComplete` / `ClosingSig` exchange with MuSig2 -partial signatures and a valid closing tx in the mempool. - -**Steps:** -```bash -# Stop generating blocks; we want the close tx to sit in mempool. -$LNCLI_A closechannel \ - --funding_txid=${CP%:*} --output_index=${CP##*:} \ - --sat_per_vbyte=2 \ - --max_fee_rate=200 & - -# Wait briefly for the exchange. -sleep 5 - -# Inspect mempool for the closing tx. -bitcoin-cli -regtest getrawmempool | jq 'length' -``` - -**Pass/Fail signal:** -- **PASS** if `getrawmempool` shows exactly 1 transaction and Alice's - log contains a `ClosingComplete` message sent to Bob. -- **FAIL** if no tx in mempool after 10s, or Alice's log shows - `unable to derive musig partial sig` or - `received nonce of wrong type`. - ---- - -### S2: RBF bump produces a new closing tx with a higher fee - -**Goal:** Trigger a second round. The new closing tx must -(a) double-spend the first, (b) use a higher fee, and (c) be signed -with **different** MuSig2 nonces. - -**Steps:** -```bash -# Capture the first closing tx fee. -TXID1=$(bitcoin-cli -regtest getrawmempool | jq -r '.[0]') -FEE1=$(bitcoin-cli -regtest getmempoolentry $TXID1 | jq -r '.fees.base') - -# Trigger a second round at a higher fee rate. -$LNCLI_A closechannel \ - --funding_txid=${CP%:*} --output_index=${CP##*:} \ - --sat_per_vbyte=10 \ - --max_fee_rate=200 & - -sleep 5 - -TXID2=$(bitcoin-cli -regtest getrawmempool | jq -r '.[0]') -FEE2=$(bitcoin-cli -regtest getmempoolentry $TXID2 | jq -r '.fees.base') -``` - -**Pass/Fail signal:** -- **PASS** if all three hold: - - `$TXID2 != $TXID1` (new tx), - - `$FEE2 > $FEE1` (higher fee), - - Bob's log contains a `NextCloseeNonce` field on the new `ClosingSig` - that differs from the previous round's nonce. -- **FAIL** if the txid is unchanged, fee did not increase, or the - nonce on `ClosingSig` is reused across rounds (this is the - critical bug to catch — search Bob's log for the previous-round - nonce hex and confirm it does *not* reappear). - ---- - -### S3: Final close confirms - -**Steps:** -```bash -bitcoin-cli -regtest generatetoaddress 6 $ADDR -sleep 2 -$LNCLI_A listchannels | \ - jq '.channels[] | select(.remote_pubkey=="'$BOB_PUB'")' | jq length -``` - -**Pass/Fail signal:** -- **PASS** if the channel is no longer in `listchannels` on either - side and the second-round tx confirmed on chain. -- **FAIL** if the channel is still listed, or the closing tx in the - mined block does not match `$TXID2` (an older round confirmed — - fee-bump replacement failed). - ---- - -### S4: Taproot-overlay channel must NOT auto-enable RBF coop close - -**Goal:** Regression guard for #10763. If a taproot-overlay channel -enters the RBF coop close path, the auxiliary close hook will not -fire and the close will misbehave. - -**Steps:** -- Start Alice and Bob with - `protocol.simple-taproot-overlay-chans=1` (in addition to RBF - coop), and an aux-close client registered. -- Open a taproot-overlay channel. -- Initiate a coop close. - -**Pass/Fail signal:** -- **PASS** if the close completes via the legacy (non-RBF) coop close - path — verified by log line `using legacy coop close` (or similar) - on the closer side, and the `AuxCloser` hook being invoked. -- **FAIL** if Alice's log shows the RBF state machine being entered - for an overlay channel, or the close transaction is built without - the aux-close additions. - ---- - -### S5: Non-taproot peer over RBF coop close (cross-check) - -**Goal:** Confirm RBF coop close still works on a non-taproot channel -when both peers signal `--protocol.rbf-coop-close`. Catches dispatch -regressions in the taproot/MuSig2 vs. legacy code split. - -**Steps:** -- Open an `anchors` channel (default) between Alice and Bob. -- Run S1 + S2 again on this channel. - -**Pass/Fail signal:** -- **PASS** if both rounds complete and the fee-bumped tx replaces - the original in mempool — same as S2, but no MuSig2 logs expected. -- **FAIL** if the close hangs or errors with a MuSig2-related - message on a non-taproot channel. - -## Failure investigation - -- **Logs (Alice and Bob):** set `PEER`, `LNWL`, `CRTR` to `debug`. - Grep for `closing_complete`, `closing_sig`, `musig`, - `NextCloseeNonce`, `ClosingComplete`. -- **State machine state:** `lncli pendingchannels` — - `pending_force_closing_channels` and `waiting_close_channels` reflect - intermediate states. -- **Nonce reuse detection:** dump each round's nonce hex from logs; - any repeat across rounds for the same channel is a bug. - -## Related itests - -- `itest/lnd_rbf_coop_test.go` (or equivalent — verify the exact file - in v0.21.0). -- `peer/musig_nonce_order_test.go` for nonce-ordering unit coverage. - -## Out of scope - -- Plain (non-RBF) cooperative close on taproot — see - [`production-taproot-channels.md` scenario S5](./production-taproot-channels.md). -- Force close (unilateral) on taproot. -- Splice — not in v0.21.0. diff --git a/docs/testing-guides/v0.21.0/reorg-safe-closes.md b/docs/testing-guides/v0.21.0/reorg-safe-closes.md deleted file mode 100644 index 2afcaa1cb..000000000 --- a/docs/testing-guides/v0.21.0/reorg-safe-closes.md +++ /dev/null @@ -1,245 +0,0 @@ -# Reorg-Safe Channel Closes + `MinCLTVDelta` Change — v0.21.0 RC Testing Guide - -**PRs:** #10331 (reorg-safe closes + MinCLTVDelta raise), #10509 (new PendingChannels fields) -**Risk:** high-regression -**Audience:** node operators, integrators with custom CLTV invoice flows, RPC clients tracking close progress -**Backends affected:** all -**Networks:** regtest (primary), signet, mainnet - -## What this feature does - -Two coupled changes in v0.21.0 alter the close lifecycle: - -1. **Reorg protection on channel closes** (#10331). Previously, any - channel close was considered final the moment the spending - transaction was detected. v0.21.0 now waits between 3 and 6 - confirmations before resolving a channel as closed, scaled - linearly with channel capacity up to the non-wumbo maximum - (~0.168 BTC). Wumbo channels always require 6 confirmations. - -2. **New `PendingChannels` fields** (#10509). `WaitingCloseChannel` - now exposes `blocks_til_close_confirmed` (countdown) and - `close_height` (block height the close-tx was first confirmed - at), so clients can render progress. - -The MinCLTVDelta bump (also in #10331) is the breaking part: - -3. **`MinCLTVDelta` raised from 18 to 24**, providing more safety - margin above `DefaultFinalCltvRejectDelta` (19 blocks). Custom - CLTV deltas in the 18–23 range on `addinvoice` are now rejected. - The default of 80 is unchanged. Existing invoices created on - prior versions continue to work normally. - -## Why it matters / what could break - -- A client that polls `closedchannels` and expects entries to - appear immediately on spend will now see a multi-block lag. -- A client that uses `WaitingCloseChannel` but doesn't render the - new fields will under-inform users (cosmetic). -- An RPC client or wallet that always passes a custom - `cltv_expiry_delta` (e.g. 20) on `addinvoice` will now get - rejection errors at the daemon. Watch for surprised integrators. -- Wallets that decode incoming invoices created before the upgrade - with `cltv_expiry_delta < 24` must still honor them — verify the - payee/sender side is permissive even though the issuer side is now - strict. -- If the scaling math regresses (wrong conf count chosen for a given - capacity), the close finalizes too early or too late. - -## Prerequisites - -- **lnd build:** v0.21.0-beta.rc1 or newer. -- **Backend:** `bitcoind` regtest. Reorgs on regtest are scripted - via `invalidateblock`/`reconsiderblock`. -- **Peers:** Alice and Bob; a third node Carol for the MinCLTV - test (so the rejection error path is reachable end-to-end). -- **Tools:** `lncli`, `bitcoin-cli`, `jq`. - -## Setup - -```bash -# 1. Open three channels Alice ↔ Bob of different capacities to -# exercise the scaling logic: -# - small: 500_000 sat -# - medium: 5_000_000 sat -# - wumbo: 20_000_000 sat (requires --protocol.wumbo-channels) -$LNCLI_A openchannel --node_key=$BOB_PUB --local_amt=500000 -$LNCLI_A openchannel --node_key=$BOB_PUB --local_amt=5000000 -$LNCLI_A openchannel --node_key=$BOB_PUB --local_amt=20000000 # wumbo - -bitcoin-cli -regtest generatetoaddress 6 $ADDR -``` - -## Scenarios - -### S1: Small channel — close requires ~3 confirmations - -**Goal:** A 500k-sat channel close lingers in `waiting_close_channels` -until the scaled-conf threshold is reached. - -**Steps:** -```bash -# Pick the 500k channel. -CP=$($LNCLI_A listchannels | \ - jq -r '.channels[] | select(.capacity=="500000") | .channel_point') - -$LNCLI_A closechannel \ - --funding_txid=${CP%:*} --output_index=${CP##*:} \ - --sat_per_vbyte=2 - -# Mine 1 block (close-tx confirms). -bitcoin-cli -regtest generatetoaddress 1 $ADDR - -# Inspect waiting-close state. -$LNCLI_A pendingchannels | jq '.waiting_close_channels[]' -``` - -**Pass/Fail signal:** -- **PASS** if (a) the channel is in `waiting_close_channels`, (b) - `close_height` equals the block height the close-tx confirmed at, - (c) `blocks_til_close_confirmed` equals roughly 3 (the lower - scaled-conf bound for a small channel), and (d) after mining 2 - more blocks the channel moves to `closedchannels`. -- **FAIL** if the channel finalizes immediately (no waiting state) - or if `blocks_til_close_confirmed` is missing/zero from the start. - ---- - -### S2: Mid-size channel — close requires more confs than S1 - -**Steps:** Same as S1 against the 5M-sat channel. - -**Pass/Fail signal:** -- **PASS** if `blocks_til_close_confirmed` is strictly greater than - S1's value (the scaling actually scales) and bounded above by 6. -- **FAIL** if it's identical to S1's value (no scaling), or > 6. - ---- - -### S3: Wumbo channel — close always requires 6 confirmations - -**Steps:** Same as S1 against the 20M-sat channel. - -**Pass/Fail signal:** -- **PASS** if `blocks_til_close_confirmed` is exactly 6 the moment - the close-tx confirms. -- **FAIL** if anything other than 6. - ---- - -### S4: Force-close also respects the new conf requirement - -**Goal:** Reorg protection applies to unilateral closes too, not -just coop closes. - -**Steps:** -- Open another small channel; have Alice force-close it. -- Inspect `pending_force_closing_channels` after 1 conf. - -**Pass/Fail signal:** -- **PASS** if the force-close also stays pending for the scaled - number of confs. -- **FAIL** if force-close finalizes immediately on first - confirmation (regression — half the fix doesn't help if the other - half is missing). - ---- - -### S5: Reorg before close-conf threshold rewinds the close - -**Goal:** A reorg that re-disconfirms the close-tx before the -threshold should rewind the channel to "still open" rather than -incorrectly considering it closed. - -**Steps:** -```bash -# Coop-close a channel; mine 1 confirmation only. -$LNCLI_A closechannel --funding_txid=... --output_index=... --sat_per_vbyte=2 -bitcoin-cli -regtest generatetoaddress 1 $ADDR - -# Reorg the block out. -TIP=$(bitcoin-cli -regtest getbestblockhash) -bitcoin-cli -regtest invalidateblock $TIP - -# Mine a competing block (no close-tx). -bitcoin-cli -regtest generatetoaddress 1 $OTHER_ADDR -``` - -**Pass/Fail signal:** -- **PASS** if Alice's daemon notices the disconfirmation (log - contains `reorg detected` or similar) and `pendingchannels` - reports the close as no longer confirmed (e.g. `close_height` - cleared or the channel reverts to active). Then re-mining the - close-tx re-progresses the close. -- **FAIL** if the close stays "almost final" despite the reorg - invalidating it, or if Alice's chain-watch panics. - ---- - -### S6: `MinCLTVDelta` rejects new invoices with custom delta 18–23 - -**Goal:** The breaking-change side of #10331. Verify both the -rejection path and the error message. - -**Steps:** -```bash -# Default delta: should succeed. -$LNCLI_A addinvoice --amt=1000 --cltv_expiry_delta=80 | jq -r '.payment_request' > /tmp/inv-ok.txt -echo $? - -# Custom delta in the new-rejected band: should fail. -$LNCLI_A addinvoice --amt=1000 --cltv_expiry_delta=20 -echo $? -``` - -**Pass/Fail signal:** -- **PASS** if delta=80 succeeds and delta=20 fails with a clear - error message naming the new minimum (24). Non-zero exit code on - the 20 call. -- **FAIL** if delta=20 silently accepts (the breaking change didn't - land), or if the error message is opaque (doesn't help operators - fix their config). - ---- - -### S7: Existing pre-upgrade invoices with delta < 24 still pay - -**Goal:** Backwards-compatibility on the receiving side — a node -running v0.21 must still settle an invoice with `cltv_expiry_delta=20` -that was issued by a pre-0.21 node (or by an external invoice -generator). - -**Steps:** -- From a pre-0.21 build of Bob (or from any node still on v0.20), - generate an invoice with `cltv_expiry_delta=20`. -- Pay it from Alice (running v0.21). - -**Pass/Fail signal:** -- **PASS** if the payment succeeds; final hop accepts the HTLC. -- **FAIL** if Alice's payer side rejects, or if Bob's older node - fails to accept the inbound HTLC due to a v0.21-injected delta. - -## Failure investigation - -- **Subsystems:** `CRTR`, `CNCT`, `HSWC`, `BCST`. -- **Useful log lines:** `reorg`, `close confirmation`, `cltv_expiry`, - `blocks_til`. -- **State to query:** `pendingchannels`, `closedchannels`, - `decodepayreq ` to inspect a captured invoice's delta. -- **Scaling math regression:** if S1/S2/S3 don't show monotonically - increasing conf counts, dump the capacity-to-conf mapping from - the daemon's log at close time and cross-check against #10331. - -## Related itests - -- `itest/lnd_channel_force_close_test.go` and - `itest/lnd_channel_open_test.go` — close path coverage. -- `itest/lnd_payment_test.go` — invoice-delta validation. - -## Out of scope - -- Force-close fee-bumping behavior — see `bumpforceclosefee` flows, - unrelated to this guide. -- The closed-channel tombstone on sqlite/postgres — separate guide - ([`closed-channel-tombstone.md`](./closed-channel-tombstone.md)). -- The legacy non-scaled close logic (no longer present in v0.21.0). diff --git a/feature/default_sets.go b/feature/default_sets.go index 9b1f42407..fcb53b666 100644 --- a/feature/default_sets.go +++ b/feature/default_sets.go @@ -60,7 +60,7 @@ var defaultSetDesc = setDesc{ lnwire.AMPRequired: { SetInvoiceAmp: {}, // 9A }, - lnwire.ExplicitChannelTypeRequired: { + lnwire.ExplicitChannelTypeOptional: { SetInit: {}, // I SetNodeAnn: {}, // N }, @@ -96,15 +96,11 @@ var defaultSetDesc = setDesc{ SetInit: {}, // I SetNodeAnn: {}, // N }, - lnwire.SimpleTaprootChannelsOptionalFinal: { - SetInit: {}, // I - SetNodeAnn: {}, // N - }, lnwire.SimpleTaprootOverlayChansOptional: { SetInit: {}, // I SetNodeAnn: {}, // N }, - lnwire.ExperimentalAccountabilityOptional: { + lnwire.ExperimentalEndorsementOptional: { SetNodeAnn: {}, // N }, lnwire.RbfCoopCloseOptionalStaging: { @@ -115,8 +111,4 @@ var defaultSetDesc = setDesc{ SetInit: {}, // I SetNodeAnn: {}, // N }, - lnwire.OnionMessagesOptional: { - SetInit: {}, // I - SetNodeAnn: {}, // N - }, } diff --git a/feature/deps.go b/feature/deps.go index 5b10f7c7c..0a2701e45 100644 --- a/feature/deps.go +++ b/feature/deps.go @@ -79,10 +79,6 @@ var deps = depDesc{ lnwire.AnchorsZeroFeeHtlcTxOptional: {}, lnwire.ExplicitChannelTypeOptional: {}, }, - lnwire.SimpleTaprootChannelsOptionalFinal: { - lnwire.AnchorsZeroFeeHtlcTxOptional: {}, - lnwire.ExplicitChannelTypeOptional: {}, - }, lnwire.SimpleTaprootOverlayChansOptional: { lnwire.SimpleTaprootChannelsOptionalStaging: {}, lnwire.TLVOnionPayloadOptional: {}, diff --git a/feature/deps_test.go b/feature/deps_test.go index 34766eb45..9b6b02fa0 100644 --- a/feature/deps_test.go +++ b/feature/deps_test.go @@ -150,6 +150,7 @@ var depTests = []depTest{ // dependencies. func TestValidateDeps(t *testing.T) { for _, test := range depTests { + test := test t.Run(test.name, func(t *testing.T) { testValidateDeps(t, test) }) diff --git a/feature/manager.go b/feature/manager.go index b1fff33c6..862880f3b 100644 --- a/feature/manager.go +++ b/feature/manager.go @@ -69,18 +69,14 @@ type Config struct { // NoTaprootOverlay unsets the taproot overlay channel feature bits. NoTaprootOverlay bool - // NoExperimentalAccountability unsets any bits that signal support for - // forwarding experimental accountability. - NoExperimentalAccountability bool + // NoExperimentalEndorsement unsets any bits that signal support for + // forwarding experimental endorsement. + NoExperimentalEndorsement bool // NoRbfCoopClose unsets any bits that signal support for using RBF for // coop close. NoRbfCoopClose bool - // NoOnionMessages unsets any bits that signal support for onion - // messaging. - NoOnionMessages bool - // CustomFeatures is a set of custom features to advertise in each // set. CustomFeatures map[Set][]lnwire.FeatureBit @@ -203,8 +199,6 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) { if cfg.NoTaprootChans { raw.Unset(lnwire.SimpleTaprootChannelsOptionalStaging) raw.Unset(lnwire.SimpleTaprootChannelsRequiredStaging) - raw.Unset(lnwire.SimpleTaprootChannelsOptionalFinal) - raw.Unset(lnwire.SimpleTaprootChannelsRequiredFinal) } if cfg.NoRouteBlinding { raw.Unset(lnwire.RouteBlindingOptional) @@ -219,18 +213,14 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) { raw.Unset(lnwire.SimpleTaprootOverlayChansOptional) raw.Unset(lnwire.SimpleTaprootOverlayChansRequired) } - if cfg.NoExperimentalAccountability { - raw.Unset(lnwire.ExperimentalAccountabilityOptional) - raw.Unset(lnwire.ExperimentalAccountabilityRequired) + if cfg.NoExperimentalEndorsement { + raw.Unset(lnwire.ExperimentalEndorsementOptional) + raw.Unset(lnwire.ExperimentalEndorsementRequired) } if cfg.NoRbfCoopClose { raw.Unset(lnwire.RbfCoopCloseOptionalStaging) raw.Unset(lnwire.RbfCoopCloseOptional) } - if cfg.NoOnionMessages { - raw.Unset(lnwire.OnionMessagesOptional) - raw.Unset(lnwire.OnionMessagesRequired) - } for _, custom := range cfg.CustomFeatures[set] { if custom > set.Maximum() { diff --git a/feature/manager_internal_test.go b/feature/manager_internal_test.go index c110fc7a9..683b4dfa3 100644 --- a/feature/manager_internal_test.go +++ b/feature/manager_internal_test.go @@ -65,6 +65,7 @@ var managerTests = []managerTest{ // including that the proper features are removed in response to config changes. func TestManager(t *testing.T) { for _, test := range managerTests { + test := test t.Run(test.name, func(t *testing.T) { testManager(t, test) }) @@ -259,6 +260,7 @@ func TestUpdateFeatureSets(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.name, func(t *testing.T) { t.Parallel() diff --git a/fn/go.mod b/fn/go.mod index 7be39f643..adb56f814 100644 --- a/fn/go.mod +++ b/fn/go.mod @@ -1,6 +1,6 @@ module github.com/lightningnetwork/lnd/fn/v2 -go 1.25.11 +go 1.24.11 require ( github.com/stretchr/testify v1.8.1 diff --git a/fn/result.go b/fn/result.go index 39f867481..37958f26c 100644 --- a/fn/result.go +++ b/fn/result.go @@ -149,10 +149,10 @@ func FlattenResult[A any](r Result[Result[A]]) Result[A] { // success value if it exists. func (r Result[T]) FlatMap(f func(T) Result[T]) Result[T] { if r.IsOk() { - return f(r.left) + return r } - return r + return f(r.left) } // AndThen is an alias for FlatMap. This along with OrElse can be used to diff --git a/fn/result_test.go b/fn/result_test.go index 7a4f86a34..2b5d942a4 100644 --- a/fn/result_test.go +++ b/fn/result_test.go @@ -96,130 +96,3 @@ func TestSinkOnOkContinuationCall(t *testing.T) { require.True(t, called) require.Nil(t, res) } - -var errFlatMap = errors.New("fail") -var errFlatMapOrig = errors.New("original") - -var flatMapTestCases = []struct { - name string - input Result[int] - fnA func(int) Result[int] - fnB func(int) Result[string] - expectedA Result[int] - expectedB Result[string] -}{ - { - name: "Ok to Ok", - input: Ok(1), - fnA: func(i int) Result[int] { return Ok(i + 1) }, - fnB: func(i int) Result[string] { - return Ok(fmt.Sprintf("%d", i+1)) - }, - expectedA: Ok(2), - expectedB: Ok("2"), - }, - { - name: "Ok to Err", - input: Ok(1), - fnA: func(i int) Result[int] { - return Err[int](errFlatMap) - }, - fnB: func(i int) Result[string] { - return Err[string](errFlatMap) - }, - expectedA: Err[int](errFlatMap), - expectedB: Err[string](errFlatMap), - }, - { - name: "Err to Err (function not called)", - input: Err[int](errFlatMapOrig), - fnA: func(i int) Result[int] { return Ok(i + 1) }, - fnB: func(i int) Result[string] { - return Ok("should not happen") - }, - expectedA: Err[int](errFlatMapOrig), - expectedB: Err[string](errFlatMapOrig), - }, -} - -var orElseTestCases = []struct { - name string - input Result[int] - fn func(error) Result[int] - expected Result[int] -}{ - { - name: "Ok to Ok (function not called)", - input: Ok(1), - fn: func(err error) Result[int] { return Ok(2) }, - expected: Ok(1), - }, - { - name: "Err to Ok", - input: Err[int](errFlatMapOrig), - fn: func(err error) Result[int] { return Ok(2) }, - expected: Ok(2), - }, - { - name: "Err to Err", - input: Err[int](errFlatMapOrig), - fn: func(err error) Result[int] { - return Err[int](errFlatMap) - }, - expected: Err[int](errFlatMap), - }, -} - -func TestFlatMap(t *testing.T) { - for _, tc := range flatMapTestCases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - actual := tc.input.FlatMap(tc.fnA) - require.Equal(t, tc.expectedA, actual) - }) - } -} - -func TestAndThenMethod(t *testing.T) { - // Since AndThen is just an alias for FlatMap, we can reuse the same - // test cases. - for _, tc := range flatMapTestCases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - actual := tc.input.AndThen(tc.fnA) - require.Equal(t, tc.expectedA, actual) - }) - } -} - -func TestOrElseMethod(t *testing.T) { - for _, tc := range orElseTestCases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - actual := tc.input.OrElse(tc.fn) - require.Equal(t, tc.expected, actual) - }) - } -} - -func TestFlatMapResult(t *testing.T) { - for _, tc := range flatMapTestCases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - actual := FlatMapResult(tc.input, tc.fnB) - require.Equal(t, tc.expectedB, actual) - }) - } -} - -func TestAndThenFunc(t *testing.T) { - // Since AndThen is just an alias for FlatMapResult, we can reuse the - // same test cases. - for _, tc := range flatMapTestCases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - actual := AndThen(tc.input, tc.fnB) - require.Equal(t, tc.expectedB, actual) - }) - } -} diff --git a/funding/aux_funding.go b/funding/aux_funding.go index 9a300ba01..c7ef653f4 100644 --- a/funding/aux_funding.go +++ b/funding/aux_funding.go @@ -1,7 +1,7 @@ package funding import ( - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwallet" diff --git a/funding/batch.go b/funding/batch.go index 796c06ad0..d95941e84 100644 --- a/funding/batch.go +++ b/funding/batch.go @@ -8,11 +8,11 @@ import ( "errors" "fmt" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/psbt/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/labels" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" @@ -301,6 +301,7 @@ func (b *Batcher) BatchFund(ctx context.Context, // Launch a goroutine that waits for the initial response on // either the update or error chan. + channel := channel eg.Go(func() error { return b.waitForUpdate(channel, true) }) @@ -414,6 +415,7 @@ func (b *Batcher) BatchFund(ctx context.Context, for _, channel := range b.channels { // Launch another goroutine that waits for the channel pending // response on the update chan. + channel := channel eg.Go(func() error { return b.waitForUpdate(channel, false) }) diff --git a/funding/batch_test.go b/funding/batch_test.go index 0f89158e4..7a674d60b 100644 --- a/funding/batch_test.go +++ b/funding/batch_test.go @@ -9,10 +9,10 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/psbt/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnwallet/chainfee" @@ -343,6 +343,7 @@ func TestBatchFund(t *testing.T) { }} for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() diff --git a/funding/commitment_type_negotiation.go b/funding/commitment_type_negotiation.go index 080f4c869..817709c73 100644 --- a/funding/commitment_type_negotiation.go +++ b/funding/commitment_type_negotiation.go @@ -240,22 +240,7 @@ func explicitNegotiateCommitmentType(channelType lnwire.ChannelType, local, } return lnwallet.CommitmentTypeTweakless, nil - // Simple taproot channels only (final feature bits). - case channelFeatures.OnlyContains( - lnwire.SimpleTaprootChannelsRequiredFinal, - ): - - if !hasFeatures( - local, remote, - lnwire.SimpleTaprootChannelsOptionalFinal, - ) { - - return 0, errUnsupportedChannelType - } - - return lnwallet.CommitmentTypeSimpleTaprootFinal, nil - - // Simple taproot channels only (staging feature bits). + // Simple taproot channels only. case channelFeatures.OnlyContains( lnwire.SimpleTaprootChannelsRequiredStaging, ): @@ -270,24 +255,7 @@ func explicitNegotiateCommitmentType(channelType lnwire.ChannelType, local, return lnwallet.CommitmentTypeSimpleTaproot, nil - // Simple taproot channels with scid only (final feature bits). - case channelFeatures.OnlyContains( - lnwire.SimpleTaprootChannelsRequiredFinal, - lnwire.ScidAliasRequired, - ): - - if !hasFeatures( - local, remote, - lnwire.SimpleTaprootChannelsOptionalFinal, - lnwire.ScidAliasOptional, - ) { - - return 0, errUnsupportedChannelType - } - - return lnwallet.CommitmentTypeSimpleTaprootFinal, nil - - // Simple taproot channels with scid only (staging feature bits). + // Simple taproot channels with scid only. case channelFeatures.OnlyContains( lnwire.SimpleTaprootChannelsRequiredStaging, lnwire.ScidAliasRequired, @@ -304,24 +272,7 @@ func explicitNegotiateCommitmentType(channelType lnwire.ChannelType, local, return lnwallet.CommitmentTypeSimpleTaproot, nil - // Simple taproot channels with zero conf only (final feature bits). - case channelFeatures.OnlyContains( - lnwire.SimpleTaprootChannelsRequiredFinal, - lnwire.ZeroConfRequired, - ): - - if !hasFeatures( - local, remote, - lnwire.SimpleTaprootChannelsOptionalFinal, - lnwire.ZeroConfOptional, - ) { - - return 0, errUnsupportedChannelType - } - - return lnwallet.CommitmentTypeSimpleTaprootFinal, nil - - // Simple taproot channels with zero conf only (staging feature bits). + // Simple taproot channels with zero conf only. case channelFeatures.OnlyContains( lnwire.SimpleTaprootChannelsRequiredStaging, lnwire.ZeroConfRequired, @@ -338,27 +289,7 @@ func explicitNegotiateCommitmentType(channelType lnwire.ChannelType, local, return lnwallet.CommitmentTypeSimpleTaproot, nil - // Simple taproot channels with scid and zero conf (final feature bits). - case channelFeatures.OnlyContains( - lnwire.SimpleTaprootChannelsRequiredFinal, - lnwire.ZeroConfRequired, - lnwire.ScidAliasRequired, - ): - - if !hasFeatures( - local, remote, - lnwire.SimpleTaprootChannelsOptionalFinal, - lnwire.ZeroConfOptional, - lnwire.ScidAliasOptional, - ) { - - return 0, errUnsupportedChannelType - } - - return lnwallet.CommitmentTypeSimpleTaprootFinal, nil - - // Simple taproot channels with scid and zero conf (staging feature - // bits). + // Simple taproot channels with scid and zero conf. case channelFeatures.OnlyContains( lnwire.SimpleTaprootChannelsRequiredStaging, lnwire.ZeroConfRequired, @@ -369,7 +300,6 @@ func explicitNegotiateCommitmentType(channelType lnwire.ChannelType, local, local, remote, lnwire.SimpleTaprootChannelsOptionalStaging, lnwire.ZeroConfOptional, - lnwire.ScidAliasOptional, ) { return 0, errUnsupportedChannelType @@ -455,13 +385,8 @@ func explicitNegotiateCommitmentType(channelType lnwire.ChannelType, local, } // implicitNegotiateCommitmentType negotiates the commitment type of a channel -// implicitly by choosing the latest non-taproot type supported by the local and -// remote features. Taproot channels must be requested explicitly, keeping -// implicit opens on channel types that can be used for both public and private -// channels. -// -// TODO(yy): Revisit implicit taproot negotiation once public taproot channel -// announcements are supported. +// implicitly by choosing the latest type supported by the local and remote +// features. func implicitNegotiateCommitmentType(local, remote *lnwire.FeatureVector) (*lnwire.ChannelType, lnwallet.CommitmentType) { diff --git a/funding/commitment_type_negotiation_test.go b/funding/commitment_type_negotiation_test.go index 75907dfce..b9e9f59f0 100644 --- a/funding/commitment_type_negotiation_test.go +++ b/funding/commitment_type_negotiation_test.go @@ -307,198 +307,10 @@ func TestCommitmentTypeNegotiation(t *testing.T) { expectsChanType: nil, expectsErr: nil, }, - - // Test cases for final taproot channels with explicit - // negotiation. - { - name: "explicit simple taproot final only", - channelFeatures: lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsRequiredFinal, - ), - localFeatures: lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsOptionalFinal, - lnwire.ExplicitChannelTypeOptional, - ), - remoteFeatures: lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsOptionalFinal, - lnwire.ExplicitChannelTypeOptional, - ), - expectsCommitType: lnwallet.CommitmentTypeSimpleTaprootFinal, //nolint:ll - expectsChanType: (*lnwire.ChannelType)( - lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsRequiredFinal, //nolint:ll - ), - ), - expectsErr: nil, - }, - { - name: "explicit simple taproot final with scid alias", - channelFeatures: lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsRequiredFinal, - lnwire.ScidAliasRequired, - ), - localFeatures: lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsOptionalFinal, - lnwire.ScidAliasOptional, - lnwire.ExplicitChannelTypeOptional, - ), - remoteFeatures: lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsOptionalFinal, - lnwire.ScidAliasOptional, - lnwire.ExplicitChannelTypeOptional, - ), - expectsCommitType: lnwallet.CommitmentTypeSimpleTaprootFinal, //nolint:ll - expectsChanType: (*lnwire.ChannelType)( - lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsRequiredFinal, //nolint:ll - lnwire.ScidAliasRequired, - ), - ), - scidAlias: true, - expectsErr: nil, - }, - { - name: "explicit simple taproot final with zero conf", - channelFeatures: lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsRequiredFinal, - lnwire.ZeroConfRequired, - ), - localFeatures: lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsOptionalFinal, - lnwire.ZeroConfOptional, - lnwire.ExplicitChannelTypeOptional, - ), - remoteFeatures: lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsOptionalFinal, - lnwire.ZeroConfOptional, - lnwire.ExplicitChannelTypeOptional, - ), - expectsCommitType: lnwallet.CommitmentTypeSimpleTaprootFinal, //nolint:ll - expectsChanType: (*lnwire.ChannelType)( - lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsRequiredFinal, //nolint:ll - lnwire.ZeroConfRequired, - ), - ), - zeroConf: true, - expectsErr: nil, - }, - { - name: "explicit simple taproot final with scid alias " + - "and zero conf", - channelFeatures: lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsRequiredFinal, - lnwire.ScidAliasRequired, - lnwire.ZeroConfRequired, - ), - localFeatures: lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsOptionalFinal, - lnwire.ScidAliasOptional, - lnwire.ZeroConfOptional, - lnwire.ExplicitChannelTypeOptional, - ), - remoteFeatures: lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsOptionalFinal, - lnwire.ScidAliasOptional, - lnwire.ZeroConfOptional, - lnwire.ExplicitChannelTypeOptional, - ), - expectsCommitType: lnwallet.CommitmentTypeSimpleTaprootFinal, //nolint:ll - expectsChanType: (*lnwire.ChannelType)( - lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsRequiredFinal, //nolint:ll - lnwire.ScidAliasRequired, - lnwire.ZeroConfRequired, - ), - ), - scidAlias: true, - zeroConf: true, - expectsErr: nil, - }, - { - name: "explicit simple taproot final missing " + - "remote support", - channelFeatures: lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsRequiredFinal, - ), - localFeatures: lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsOptionalFinal, - lnwire.ExplicitChannelTypeOptional, - ), - remoteFeatures: lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsOptionalStaging, - lnwire.ExplicitChannelTypeOptional, - ), - expectsErr: errUnsupportedChannelType, - }, - - // Test cases for implicit negotiation ignoring taproot feature - // bits. Taproot channels require an explicit channel type. - { - //nolint:ll - name: "implicit anchors preferred over taproot", - channelFeatures: nil, - localFeatures: lnwire.NewRawFeatureVector( - lnwire.AnchorsZeroFeeHtlcTxOptional, - lnwire.SimpleTaprootChannelsOptionalFinal, - lnwire.SimpleTaprootChannelsOptionalStaging, - lnwire.ExplicitChannelTypeOptional, - ), - remoteFeatures: lnwire.NewRawFeatureVector( - lnwire.AnchorsZeroFeeHtlcTxOptional, - lnwire.SimpleTaprootChannelsOptionalFinal, - lnwire.SimpleTaprootChannelsOptionalStaging, - lnwire.ExplicitChannelTypeOptional, - ), - expectsCommitType: lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, //nolint:ll - expectsChanType: (*lnwire.ChannelType)( - lnwire.NewRawFeatureVector( - lnwire.StaticRemoteKeyRequired, - lnwire.AnchorsZeroFeeHtlcTxRequired, - ), - ), - expectsErr: nil, - }, - { - //nolint:ll - name: "implicit ignores staging taproot without anchors", - channelFeatures: nil, - localFeatures: lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsOptionalFinal, - lnwire.SimpleTaprootChannelsOptionalStaging, - lnwire.ExplicitChannelTypeOptional, - ), - remoteFeatures: lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsOptionalStaging, - lnwire.ExplicitChannelTypeOptional, - ), - expectsCommitType: lnwallet.CommitmentTypeLegacy, - expectsChanType: (*lnwire.ChannelType)( - lnwire.NewRawFeatureVector(), - ), - expectsErr: nil, - }, - { - //nolint:ll - name: "implicit ignores final taproot without anchors", - channelFeatures: nil, - localFeatures: lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsOptionalFinal, - lnwire.ExplicitChannelTypeOptional, - ), - remoteFeatures: lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsOptionalFinal, - lnwire.ExplicitChannelTypeOptional, - ), - expectsCommitType: lnwallet.CommitmentTypeLegacy, - expectsChanType: (*lnwire.ChannelType)( - lnwire.NewRawFeatureVector(), - ), - expectsErr: nil, - }, } for _, testCase := range testCases { + testCase := testCase ok := t.Run(testCase.name, func(t *testing.T) { localFeatures := lnwire.NewFeatureVector( testCase.localFeatures, lnwire.Features, diff --git a/funding/manager.go b/funding/manager.go index 40ac99aba..616ddd83a 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -2,7 +2,6 @@ package funding import ( "bytes" - "context" "encoding/binary" "errors" "fmt" @@ -15,15 +14,13 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/actor" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chanacceptor" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/discovery" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph" @@ -385,9 +382,8 @@ type Config struct { // so that the channel creation process can be completed. Notifier chainntnfs.ChainNotifier - // ChannelDB is the database that keeps track of channel state used by - // the funding flow. - ChannelDB chanstate.Store + // ChannelDB is the database that keeps track of all channel state. + ChannelDB *channeldb.ChannelStateDB // SignMessage signs an arbitrary message with a given public key. The // actual digest signed is the double sha-256 of the message. In the @@ -410,8 +406,7 @@ type Config struct { // any information within the graph that is not included in the gossip // message. SendAnnouncement func(msg lnwire.Message, - optionalFields ...discovery.OptionalMsgField, - ) actor.Future[error] + optionalFields ...discovery.OptionalMsgField) chan error // NotifyWhenOnline allows the FundingManager to register with a // subsystem that will notify it when the peer comes online. This is @@ -425,7 +420,7 @@ type Config struct { // channel ID. Providing the node's public key is an optimization that // prevents deserializing and scanning through all possible channels. FindChannel func(node *btcec.PublicKey, - chanID lnwire.ChannelID) (*chanstate.OpenChannel, error) + chanID lnwire.ChannelID) (*channeldb.OpenChannel, error) // TempChanIDSeed is a cryptographically random string of bytes that's // used as a seed to generate pending channel ID's. @@ -475,7 +470,7 @@ type Config struct { // the channel to the ChainArbitrator so it can watch for any on-chain // events related to the channel. We also provide the public key of the // node we're establishing a channel with for reconnection purposes. - WatchNewChannel func(*chanstate.OpenChannel, *btcec.PublicKey) error + WatchNewChannel func(*channeldb.OpenChannel, *btcec.PublicKey) error // ReportShortChanID allows the funding manager to report the confirmed // short channel ID of a formerly pending zero-conf channel to outside @@ -525,7 +520,7 @@ type Config struct { // NotifyPendingOpenChannelEvent informs the ChannelNotifier when // channels enter a pending state. NotifyPendingOpenChannelEvent func(wire.OutPoint, - *chanstate.OpenChannel, *btcec.PublicKey) + *channeldb.OpenChannel, *btcec.PublicKey) // NotifyFundingTimeout informs the ChannelNotifier when a pending-open // channel times out because the funding transaction hasn't confirmed. @@ -578,10 +573,6 @@ type Config struct { // implementations to inject and process custom records over channel // related wire messages. AuxChannelNegotiator fn.Option[lnwallet.AuxChannelNegotiator] - - // ShutdownScript is an optional upfront-shutdown script to which our - // funds should be paid on a cooperative close. - ShutdownScript fn.Option[lnwire.DeliveryAddress] } // Manager acts as an orchestrator/bridge between the wallet's @@ -811,7 +802,7 @@ func (f *Manager) Stop() error { // rebroadcastFundingTx publishes the funding tx on startup for each // unconfirmed channel. -func (f *Manager) rebroadcastFundingTx(c *chanstate.OpenChannel) { +func (f *Manager) rebroadcastFundingTx(c *channeldb.OpenChannel) { var fundingTxBuf bytes.Buffer err := c.FundingTxn.Serialize(&fundingTxBuf) if err != nil { @@ -1062,7 +1053,8 @@ func (f *Manager) reservationCoordinator() { f.funderProcessFundingSigned(fmsg.peer, msg) case *lnwire.ChannelReady: - f.handleChannelReady(fmsg.peer, msg) + f.wg.Add(1) + go f.handleChannelReady(fmsg.peer, msg) case *lnwire.Warning: f.handleWarningMsg(fmsg.peer, msg) @@ -1090,7 +1082,7 @@ func (f *Manager) reservationCoordinator() { // OpenStatusUpdates. // // NOTE: This MUST be run as a goroutine. -func (f *Manager) advanceFundingState(channel *chanstate.OpenChannel, +func (f *Manager) advanceFundingState(channel *channeldb.OpenChannel, pendingChanID PendingChanID, updateChan chan<- *lnrpc.OpenStatusUpdate) { @@ -1171,7 +1163,7 @@ func (f *Manager) advanceFundingState(channel *chanstate.OpenChannel, // machine. This method is synchronous and the new channel opening state will // have been written to the database when it successfully returns. The // updateChan can be set non-nil to get OpenStatusUpdates. -func (f *Manager) stateStep(channel *chanstate.OpenChannel, +func (f *Manager) stateStep(channel *channeldb.OpenChannel, lnChannel *lnwallet.LightningChannel, shortChanID *lnwire.ShortChannelID, pendingChanID PendingChanID, channelState channelOpeningState, @@ -1296,7 +1288,7 @@ func (f *Manager) stateStep(channel *chanstate.OpenChannel, // advancePendingChannelState waits for a pending channel's funding tx to // confirm, and marks it open in the database when that happens. -func (f *Manager) advancePendingChannelState(channel *chanstate.OpenChannel, +func (f *Manager) advancePendingChannelState(channel *channeldb.OpenChannel, pendingChanID PendingChanID) error { if channel.IsZeroConf() { @@ -1440,64 +1432,14 @@ func (f *Manager) ProcessFundingMsg(msg lnwire.Message, peer lnpeer.Peer) { func (f *Manager) fundeeProcessOpenChannel(peer lnpeer.Peer, msg *lnwire.OpenChannel) { - amt := msg.FundingAmount - - // Create the channel identifier. - cid := newChanIdentifier(msg.PendingChannelID) - - // Enforce BOLT-02: push_msat MUST be <= 1000 * funding_satoshis. We - // compare in satoshi space so neither side can overflow uint64 for - // any non-negative funding amount: split push_msat into its - // integer-satoshi and sub-satoshi parts and reject when it strictly - // exceeds the funding amount. - pushSat := uint64(msg.PushAmount) / 1000 - pushSubSat := uint64(msg.PushAmount) % 1000 - fundingSat := uint64(msg.FundingAmount) - if pushSat > fundingSat || (pushSat == fundingSat && pushSubSat > 0) { - f.failFundingFlow( - peer, cid, - lnwallet.ErrPushAmountTooLarge( - msg.PushAmount, msg.FundingAmount, - ), - ) - - return - } - - // If request specifies non-zero push amount and 'rejectpush' is set, - // signal an error. - if f.cfg.RejectPush && msg.PushAmount > 0 { - f.failFundingFlow(peer, cid, lnwallet.ErrNonZeroPushAmount()) - return - } - - // Ensure that the remote party respects our maximum channel size. - if amt > f.cfg.MaxChanSize { - f.failFundingFlow( - peer, cid, - lnwallet.ErrChanTooLarge(amt, f.cfg.MaxChanSize), - ) - - return - } - - // We'll, also ensure that the remote party isn't attempting to propose - // a channel that's below our current min channel size. - if amt < f.cfg.MinChanSize { - f.failFundingFlow( - peer, cid, - lnwallet.ErrChanTooSmall(amt, f.cfg.MinChanSize), - ) - - return - } - // Check number of pending channels to be smaller than maximum allowed // number and send ErrorGeneric to remote peer if condition is // violated. peerPubKey := peer.IdentityKey() peerIDKey := newSerializedKey(peerPubKey) + amt := msg.FundingAmount + // We get all pending channels for this peer. This is the list of the // active reservations and the channels pending open in the database. f.resMtx.RLock() @@ -1514,6 +1456,9 @@ func (f *Manager) fundeeProcessOpenChannel(peer lnpeer.Peer, } f.resMtx.RUnlock() + // Create the channel identifier. + cid := newChanIdentifier(msg.PendingChannelID) + // Also count the channels that are already pending. There we don't know // the underlying intent anymore, unfortunately. channels, err := f.cfg.ChannelDB.FetchOpenChannels(peerPubKey) @@ -1567,6 +1512,32 @@ func (f *Manager) fundeeProcessOpenChannel(peer lnpeer.Peer, return } + // Ensure that the remote party respects our maximum channel size. + if amt > f.cfg.MaxChanSize { + f.failFundingFlow( + peer, cid, + lnwallet.ErrChanTooLarge(amt, f.cfg.MaxChanSize), + ) + return + } + + // We'll, also ensure that the remote party isn't attempting to propose + // a channel that's below our current min channel size. + if amt < f.cfg.MinChanSize { + f.failFundingFlow( + peer, cid, + lnwallet.ErrChanTooSmall(amt, f.cfg.MinChanSize), + ) + return + } + + // If request specifies non-zero push amount and 'rejectpush' is set, + // signal an error. + if f.cfg.RejectPush && msg.PushAmount > 0 { + f.failFundingFlow(peer, cid, lnwallet.ErrNonZeroPushAmount()) + return + } + // Send the OpenChannel request to the ChannelAcceptor to determine // whether this node will accept the channel. chanReq := &chanacceptor.ChannelAcceptRequest{ @@ -1789,24 +1760,12 @@ func (f *Manager) fundeeProcessOpenChannel(peer lnpeer.Peer, return } - // If the fundee didn't provide an upfront-shutdown address via - // the channel acceptor, fall back to the configured shutdown - // script (if any). - shutdownScript := acceptorResp.UpfrontShutdown - if len(shutdownScript) == 0 { - f.cfg.ShutdownScript.WhenSome( - func(script lnwire.DeliveryAddress) { - shutdownScript = script - }, - ) - } - // Check whether the peer supports upfront shutdown, and get a new // wallet address if our node is configured to set shutdown addresses by // default. We use the upfront shutdown script provided by our channel // acceptor (if any) in lieu of user input. shutdown, err := getUpfrontShutdownScript( - f.cfg.EnableUpfrontShutdown, peer, shutdownScript, + f.cfg.EnableUpfrontShutdown, peer, acceptorResp.UpfrontShutdown, f.selectShutdownScript, ) if err != nil { @@ -2962,7 +2921,7 @@ type confirmedChannel struct { // an ErrConfirmationTimeout. It is used to clean-up channel state and mark the // channel as closed. The error is only returned for the responder of the // channel flow. -func (f *Manager) fundingTimeout(c *chanstate.OpenChannel, +func (f *Manager) fundingTimeout(c *channeldb.OpenChannel, pendingID PendingChanID) error { // We'll get a timeout if the number of blocks mined since the channel @@ -3039,7 +2998,7 @@ func (f *Manager) fundingTimeout(c *chanstate.OpenChannel, // funding broadcast height. In case of confirmation, the short channel ID of // the channel and the funding transaction will be returned. func (f *Manager) waitForFundingWithTimeout( - ch *chanstate.OpenChannel) (*confirmedChannel, error) { + ch *channeldb.OpenChannel) (*confirmedChannel, error) { confChan := make(chan *confirmedChannel) timeoutChan := make(chan error, 1) @@ -3080,7 +3039,7 @@ func (f *Manager) waitForFundingWithTimeout( // MakeFundingScript re-creates the funding script for the funding transaction // of the target channel. -func MakeFundingScript(channel *chanstate.OpenChannel) ([]byte, error) { +func MakeFundingScript(channel *channeldb.OpenChannel) ([]byte, error) { localKey := channel.LocalChanCfg.MultiSigKey.PubKey remoteKey := channel.RemoteChanCfg.MultiSigKey.PubKey @@ -3118,7 +3077,7 @@ func MakeFundingScript(channel *chanstate.OpenChannel) ([]byte, error) { // // NOTE: This MUST be run as a goroutine. func (f *Manager) waitForFundingConfirmation( - completeChan *chanstate.OpenChannel, cancelChan <-chan struct{}, + completeChan *channeldb.OpenChannel, cancelChan <-chan struct{}, confChan chan<- *confirmedChannel) { defer f.wg.Done() @@ -3283,7 +3242,7 @@ func (f *Manager) waitForFundingConfirmation( // based on the confirmation details and sends this information, along with the // funding transaction, to the provided confirmation channel. func (f *Manager) handleConfirmation(confDetails *chainntnfs.TxConfirmation, - completeChan *chanstate.OpenChannel, + completeChan *channeldb.OpenChannel, confChan chan<- *confirmedChannel) error { fundingPoint := completeChan.FundingOutpoint @@ -3318,7 +3277,7 @@ func (f *Manager) handleConfirmation(confDetails *chainntnfs.TxConfirmation, // // NOTE: timeoutChan MUST be buffered. // NOTE: This MUST be run as a goroutine. -func (f *Manager) waitForTimeout(completeChan *chanstate.OpenChannel, +func (f *Manager) waitForTimeout(completeChan *channeldb.OpenChannel, cancelChan <-chan struct{}, timeoutChan chan<- error) { defer f.wg.Done() @@ -3390,7 +3349,7 @@ func (f *Manager) waitForTimeout(completeChan *chanstate.OpenChannel, // our short channel ID, which is known now that our funding transaction has // confirmed. We do not label transactions we did not publish, because our // wallet has no knowledge of them. -func (f *Manager) makeLabelForTx(c *chanstate.OpenChannel) { +func (f *Manager) makeLabelForTx(c *channeldb.OpenChannel) { if c.IsInitiator && c.ChanType.HasFundingTx() { shortChanID := c.ShortChanID() @@ -3416,7 +3375,7 @@ func (f *Manager) makeLabelForTx(c *chanstate.OpenChannel) { // decided short channel ID to the switch, and close the local discovery signal // for this channel. func (f *Manager) handleFundingConfirmation( - completeChan *chanstate.OpenChannel, + completeChan *channeldb.OpenChannel, confChannel *confirmedChannel) error { fundingPoint := completeChan.FundingOutpoint @@ -3495,7 +3454,7 @@ func (f *Manager) handleFundingConfirmation( // sendChannelReady creates and sends the channelReady message. // This should be called after the funding transaction has been confirmed, // and the channelState is 'markedOpen'. -func (f *Manager) sendChannelReady(completeChan *chanstate.OpenChannel, +func (f *Manager) sendChannelReady(completeChan *channeldb.OpenChannel, channel *lnwallet.LightningChannel) error { chanID := lnwire.NewChanIDFromOutPoint(completeChan.FundingOutpoint) @@ -3675,7 +3634,7 @@ func (f *Manager) receivedChannelReady(node *btcec.PublicKey, } // Finally, the barrier signal is removed once we finish - // `processChannelReady`. If we can still find the signal, we haven't + // `handleChannelReady`. If we can still find the signal, we haven't // finished processing it yet. _, loaded := f.handleChannelReadyBarriers.Load(chanID) @@ -3685,7 +3644,7 @@ func (f *Manager) receivedChannelReady(node *btcec.PublicKey, // extractAnnounceParams extracts the various channel announcement and update // parameters that will be needed to construct a ChannelAnnouncement and a // ChannelUpdate. -func (f *Manager) extractAnnounceParams(c *chanstate.OpenChannel) ( +func (f *Manager) extractAnnounceParams(c *channeldb.OpenChannel) ( lnwire.MilliSatoshi, lnwire.MilliSatoshi) { // We'll obtain the min HTLC value we can forward in our direction, as @@ -3712,30 +3671,6 @@ func (f *Manager) extractAnnounceParams(c *chanstate.OpenChannel) ( return fwdMinHTLC, fwdMaxHTLC } -// mapGossipError inspects a gossip result error and translates shutdown -// signals into ErrFundingManagerShuttingDown. Graph-rejected errors (outdated, -// ignored) are logged at debug level and treated as non-fatal (nil is -// returned). All other non-nil errors are returned as-is for the caller to -// handle. -func mapGossipError(err error, msgType string) error { - if err == nil { - return nil - } - - if errors.Is(err, context.Canceled) || - errors.Is(err, discovery.ErrGossiperShuttingDown) { - - return ErrFundingManagerShuttingDown - } - - if graph.IsError(err, graph.ErrOutdated, graph.ErrIgnored) { - log.Debugf("Graph rejected %s: %v", msgType, err) - return nil - } - - return err -} - // addToGraph sends a ChannelAnnouncement and a ChannelUpdate to the // gossiper so that the channel is added to the graph builder's internal graph. // These announcement messages are NOT broadcasted to the greater network, @@ -3744,7 +3679,7 @@ func mapGossipError(err error, msgType string) error { // The peerAlias is used for zero-conf channels to give the counter-party a // ChannelUpdate they understand. ourPolicy may be set for various // option-scid-alias channels to re-use the same policy. -func (f *Manager) addToGraph(completeChan *chanstate.OpenChannel, +func (f *Manager) addToGraph(completeChan *channeldb.OpenChannel, shortChanID *lnwire.ShortChannelID, peerAlias *lnwire.ShortChannelID, ourPolicy *models.ChannelEdgePolicy) error { @@ -3765,34 +3700,48 @@ func (f *Manager) addToGraph(completeChan *chanstate.OpenChannel, "announcement: %v", err) } - // Create a context tied to the manager's quit channel so that both - // gossip awaits below respect shutdown. - ctx, cancel := lnutils.ContextFromQuit(f.quit) - defer cancel() - // Send ChannelAnnouncement and ChannelUpdate to the gossiper to add // to the Router's topology. - err = mapGossipError(discovery.AwaitGossipResult(ctx, - f.cfg.SendAnnouncement( - ann.chanAnn, - discovery.ChannelCapacity(completeChan.Capacity), - discovery.ChannelPoint(completeChan.FundingOutpoint), - discovery.TapscriptRoot(completeChan.TapscriptRoot), - ), - ), "ChannelAnnouncement") - if err != nil { - return fmt.Errorf("error sending channel announcement: %w", - err) + errChan := f.cfg.SendAnnouncement( + ann.chanAnn, discovery.ChannelCapacity(completeChan.Capacity), + discovery.ChannelPoint(completeChan.FundingOutpoint), + discovery.TapscriptRoot(completeChan.TapscriptRoot), + ) + select { + case err := <-errChan: + if err != nil { + if graph.IsError(err, graph.ErrOutdated, + graph.ErrIgnored) { + + log.Debugf("Graph rejected "+ + "ChannelAnnouncement: %v", err) + } else { + return fmt.Errorf("error sending channel "+ + "announcement: %v", err) + } + } + case <-f.quit: + return ErrFundingManagerShuttingDown } - err = mapGossipError(discovery.AwaitGossipResult(ctx, - f.cfg.SendAnnouncement( - ann.chanUpdateAnn, - discovery.RemoteAlias(peerAlias), - ), - ), "ChannelUpdate") - if err != nil { - return fmt.Errorf("error sending channel update: %w", err) + errChan = f.cfg.SendAnnouncement( + ann.chanUpdateAnn, discovery.RemoteAlias(peerAlias), + ) + select { + case err := <-errChan: + if err != nil { + if graph.IsError(err, graph.ErrOutdated, + graph.ErrIgnored) { + + log.Debugf("Graph rejected "+ + "ChannelUpdate: %v", err) + } else { + return fmt.Errorf("error sending channel "+ + "update: %v", err) + } + } + case <-f.quit: + return ErrFundingManagerShuttingDown } return nil @@ -3804,7 +3753,7 @@ func (f *Manager) addToGraph(completeChan *chanstate.OpenChannel, // 'addedToGraph') and the channel is ready to be used. This is the last // step in the channel opening process, and the opening state will be deleted // from the database if successful. -func (f *Manager) annAfterSixConfs(completeChan *chanstate.OpenChannel, +func (f *Manager) annAfterSixConfs(completeChan *channeldb.OpenChannel, shortChanID *lnwire.ShortChannelID) error { // If this channel is not meant to be announced to the greater network, @@ -3954,7 +3903,7 @@ func (f *Manager) annAfterSixConfs(completeChan *chanstate.OpenChannel, // waitForZeroConfChannel is called when the state is addedToGraph with // a zero-conf channel. This will wait for the real confirmation, add the // confirmed SCID to the router graph, and then announce after six confs. -func (f *Manager) waitForZeroConfChannel(c *chanstate.OpenChannel) error { +func (f *Manager) waitForZeroConfChannel(c *channeldb.OpenChannel) error { // First we'll check whether the channel is confirmed on-chain. If it // is already confirmed, the chainntnfs subsystem will return with the // confirmed tx. Otherwise, we'll wait here until confirmation occurs. @@ -3992,22 +3941,6 @@ func (f *Manager) waitForZeroConfChannel(c *chanstate.OpenChannel) error { // Six confirmations have been reached. If this channel is public, // we'll delete some of the alias mappings the gossiper uses. - // - // Tell the Switch to refresh the relevant ChannelLink so that forwards - // under the confirmed SCID are possible. We do this BEFORE updating the - // graph to avoid a race where other nodes learn about the confirmed - // SCID from gossip before our switch is ready to handle forwards using - // it. This is especially important for integration tests. - err = f.cfg.ReportShortChanID(c.FundingOutpoint) - if err != nil { - // This should only fail if the link is not found in the - // Switch's linkIndex map. If this is the case, then the peer - // has gone offline and the next time the link is loaded, it - // will have a refreshed state. Just log an error here. - log.Errorf("unable to report scid for zero-conf channel "+ - "channel: %v", err) - } - isPublic := c.ChannelFlags&lnwire.FFAnnounceChannel != 0 if isPublic { err = f.cfg.AliasManager.DeleteSixConfs(c.ShortChannelID) @@ -4036,6 +3969,19 @@ func (f *Manager) waitForZeroConfChannel(c *chanstate.OpenChannel) error { } } + // Since we have now marked down the confirmed SCID, we'll also need to + // tell the Switch to refresh the relevant ChannelLink so that forwards + // under the confirmed SCID are possible if this is a public channel. + err = f.cfg.ReportShortChanID(c.FundingOutpoint) + if err != nil { + // This should only fail if the link is not found in the + // Switch's linkIndex map. If this is the case, then the peer + // has gone offline and the next time the link is loaded, it + // will have a refreshed state. Just log an error here. + log.Errorf("unable to report scid for zero-conf channel "+ + "channel: %v", err) + } + // Update the confirmed transaction's label. f.makeLabelForTx(c) @@ -4045,7 +3991,7 @@ func (f *Manager) waitForZeroConfChannel(c *chanstate.OpenChannel) error { // genFirstStateMusigNonce generates a nonces for the "first" local state. This // is the verification nonce for the state created for us after the initial // commitment transaction signed as part of the funding flow. -func genFirstStateMusigNonce(channel *chanstate.OpenChannel, +func genFirstStateMusigNonce(channel *channeldb.OpenChannel, ) (*musig2.Nonces, error) { musig2ShaChain, err := channeldb.DeriveMusig2Shachain( @@ -4073,9 +4019,11 @@ func genFirstStateMusigNonce(channel *chanstate.OpenChannel, // handleChannelReady finalizes the channel funding process and enables the // channel to enter normal operating mode. -func (f *Manager) handleChannelReady(peer lnpeer.Peer, +func (f *Manager) handleChannelReady(peer lnpeer.Peer, //nolint:funlen msg *lnwire.ChannelReady) { + defer f.wg.Done() + // Notify the aux hook that the specified peer just established a // channel with us, identified by the given channel ID. f.cfg.AuxChannelNegotiator.WhenSome( @@ -4084,118 +4032,6 @@ func (f *Manager) handleChannelReady(peer lnpeer.Peer, }, ) - log.Debugf("Received ChannelReady for ChannelID(%v) from "+ - "peer %x", msg.ChanID, - peer.IdentityKey().SerializeCompressed()) - - // We now load or create a new channel barrier for this channel. If - // we are currently in the process of handling a channel_ready message - // for this channel, ignore the duplicate. - _, loaded := f.handleChannelReadyBarriers.LoadOrStore( - msg.ChanID, struct{}{}, - ) - if loaded { - log.Infof("Already handling channelReady for "+ - "ChannelID(%v), ignoring.", msg.ChanID) - return - } - - // Check whether we need to wait for the local funding confirmation flow - // to finish before we can proceed with this message. The - // localDiscoverySignal is only present for channels that we are - // actively funding and is bounded by the maximum number of pending - // channels. - localDiscoverySignal, ok := f.localDiscoverySignals.Load(msg.ChanID) - if ok { - f.wg.Add(1) - go func() { - defer f.wg.Done() - defer f.handleChannelReadyBarriers.Delete( - msg.ChanID, - ) - - // Wait for the local waitForFundingConfirmation - // goroutine to signal that it has the necessary state - // in place. Otherwise, we may be missing critical - // information required to handle forwarded HTLC's. - select { - case <-localDiscoverySignal: - case <-f.quit: - return - } - - f.localDiscoverySignals.Delete(msg.ChanID) - f.processChannelReady(peer, msg) - }() - - return - } - - // No signal wait needed. Perform a lightweight channel lookup inline - // to short-circuit bogus or already-established channels without - // blocking the coordinator on heavier processing. - chanID := msg.ChanID - channel, err := f.cfg.FindChannel(peer.IdentityKey(), chanID) - if err != nil { - f.handleChannelReadyBarriers.Delete(msg.ChanID) - - log.Errorf("Unable to locate ChannelID(%v), cannot "+ - "complete funding", chanID) - - return - } - - // If the RemoteNextRevocation is non-nil, then the channel has - // already been fully established and we've processed channel_ready - // for it at least once. We short-circuit inline to avoid redoing the - // heavy work in processChannelReady (DB writes, nonce generation, - // AddNewChannel) on every duplicate channel_ready the peer sends. - // Note that the happy path where the channel is actively being - // funded goes through the localDiscoverySignal branch above. - if channel.RemoteNextRevocation != nil { - // Even though we're ignoring the rest of the message, we - // still need to refresh the peer's alias if they negotiated - // the option_scid_alias feature and sent a (possibly updated) - // AliasScid. The peer may resend channel_ready to rotate or - // update their alias for invoice route hints. - if channel.NegotiatedAliasFeature() && msg.AliasScid != nil { - err := f.cfg.AliasManager.PutPeerAlias( - chanID, *msg.AliasScid, - ) - if err != nil { - log.Errorf("unable to store peer's alias: "+ - "%v", err) - } - } - - f.handleChannelReadyBarriers.Delete(msg.ChanID) - - log.Infof("Received duplicate channelReady for "+ - "ChannelID(%v), ignoring.", chanID) - - return - } - - // Channel exists and hasn't been fully established yet — this is a - // legitimate first channel_ready. Dispatch the remaining work (DB - // writes, nonce generation, AddNewChannel) in a goroutine to avoid - // blocking the coordinator. - f.wg.Add(1) - go func() { - defer f.wg.Done() - defer f.handleChannelReadyBarriers.Delete(msg.ChanID) - - f.processChannelReady(peer, msg) - }() -} - -// processChannelReady completes the channel_ready handling after any required -// signal waits. It looks up the channel in the database and finalizes the -// funding flow by inserting the remote party's next revocation point and -// handing the channel off to the peer for normal operation. -func (f *Manager) processChannelReady(peer lnpeer.Peer, - msg *lnwire.ChannelReady) { - // If we are in development mode, we'll wait for specified duration // before processing the channel ready message. if f.cfg.Dev != nil { @@ -4213,10 +4049,51 @@ func (f *Manager) processChannelReady(peer lnpeer.Peer, } } - // We'll attempt to locate the channel whose funding workflow is being - // finalized by this message. We go to the database rather than our - // reservation map as we may have restarted mid funding flow. The - // node's public key is provided to scope the search. + log.Debugf("Received ChannelReady for ChannelID(%v) from "+ + "peer %x", msg.ChanID, + peer.IdentityKey().SerializeCompressed()) + + // We now load or create a new channel barrier for this channel. + _, loaded := f.handleChannelReadyBarriers.LoadOrStore( + msg.ChanID, struct{}{}, + ) + + // If we are currently in the process of handling a channel_ready + // message for this channel, ignore. + if loaded { + log.Infof("Already handling channelReady for "+ + "ChannelID(%v), ignoring.", msg.ChanID) + return + } + + // If not already handling channelReady for this channel, then the + // `LoadOrStore` has set up a barrier, and it will be removed once this + // function exits. + defer f.handleChannelReadyBarriers.Delete(msg.ChanID) + + localDiscoverySignal, ok := f.localDiscoverySignals.Load(msg.ChanID) + if ok { + // Before we proceed with processing the channel_ready + // message, we'll wait for the local waitForFundingConfirmation + // goroutine to signal that it has the necessary state in + // place. Otherwise, we may be missing critical information + // required to handle forwarded HTLC's. + select { + case <-localDiscoverySignal: + // Fallthrough + case <-f.quit: + return + } + + // With the signal received, we can now safely delete the entry + // from the map. + f.localDiscoverySignals.Delete(msg.ChanID) + } + + // First, we'll attempt to locate the channel whose funding workflow is + // being finalized by this message. We go to the database rather than + // our reservation map as we may have restarted, mid funding flow. Also + // provide the node's public key to make the search faster. chanID := msg.ChanID channel, err := f.cfg.FindChannel(peer.IdentityKey(), chanID) if err != nil { @@ -4241,7 +4118,7 @@ func (f *Manager) processChannelReady(peer lnpeer.Peer, // during invoice creation. In the zero-conf case, it is also used to // provide a ChannelUpdate to the remote peer. This is done before the // call to InsertNextRevocation in case the call to PutPeerAlias fails. - // If it were to fail on the first call to processChannelReady, we + // If it were to fail on the first call to handleChannelReady, we // wouldn't want the channel to be usable yet. if channel.NegotiatedAliasFeature() { // If the AliasScid field is nil, we must fail out. We will @@ -4420,7 +4297,7 @@ func (f *Manager) processChannelReady(peer lnpeer.Peer, // channelReady message, once the remote's channelReady is processed, the // channel is now active, thus we change its state to `addedToGraph` to // let the channel start handling routing. -func (f *Manager) handleChannelReadyReceived(channel *chanstate.OpenChannel, +func (f *Manager) handleChannelReadyReceived(channel *channeldb.OpenChannel, scid *lnwire.ShortChannelID, pendingChanID PendingChanID, updateChan chan<- *lnrpc.OpenStatusUpdate) error { @@ -4516,7 +4393,7 @@ func (f *Manager) handleChannelReadyReceived(channel *chanstate.OpenChannel, // policy set for the given channel. If we don't, we'll fall back to the default // values. func (f *Manager) ensureInitialForwardingPolicy(chanID lnwire.ChannelID, - channel *chanstate.OpenChannel) error { + channel *channeldb.OpenChannel) error { // Before we can add the channel to the peer, we'll need to ensure that // we have an initial forwarding policy set. This should always be the @@ -4805,21 +4682,28 @@ func (f *Manager) announceChannel(localIDKey, remoteIDKey *btcec.PublicKey, return err } - // Create a context tied to the manager's quit channel so that both - // gossip awaits below respect shutdown. - ctx, cancel := lnutils.ContextFromQuit(f.quit) - defer cancel() - // We only send the channel proof announcement and the node announcement // because addToGraph previously sent the ChannelAnnouncement and // the ChannelUpdate announcement messages. The channel proof and node // announcements are broadcast to the greater network. - err = mapGossipError(discovery.AwaitGossipResult( - ctx, f.cfg.SendAnnouncement(ann.chanProof), - ), "AnnounceSignatures") - if err != nil { - log.Errorf("Unable to send channel proof: %v", err) - return err + errChan := f.cfg.SendAnnouncement(ann.chanProof) + select { + case err := <-errChan: + if err != nil { + if graph.IsError(err, graph.ErrOutdated, + graph.ErrIgnored) { + + log.Debugf("Graph rejected "+ + "AnnounceSignatures: %v", err) + } else { + log.Errorf("Unable to send channel "+ + "proof: %v", err) + return err + } + } + + case <-f.quit: + return ErrFundingManagerShuttingDown } // Now that the channel is announced to the network, we will also @@ -4832,12 +4716,24 @@ func (f *Manager) announceChannel(localIDKey, remoteIDKey *btcec.PublicKey, return err } - err = mapGossipError(discovery.AwaitGossipResult( - ctx, f.cfg.SendAnnouncement(&nodeAnn), - ), "NodeAnnouncement") - if err != nil { - log.Errorf("Unable to send node announcement: %v", err) - return err + errChan = f.cfg.SendAnnouncement(&nodeAnn) + select { + case err := <-errChan: + if err != nil { + if graph.IsError(err, graph.ErrOutdated, + graph.ErrIgnored) { + + log.Debugf("Graph rejected "+ + "NodeAnnouncement1: %v", err) + } else { + log.Errorf("Unable to send node "+ + "announcement: %v", err) + return err + } + } + + case <-f.quit: + return ErrFundingManagerShuttingDown } return nil @@ -4953,23 +4849,12 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) { } } - // If the funder did not provide an upfront-shutdown address, fall back - // to the configured shutdown script (if any). - shutdownScript := msg.ShutdownScript - if len(shutdownScript) == 0 { - f.cfg.ShutdownScript.WhenSome( - func(script lnwire.DeliveryAddress) { - shutdownScript = script - }, - ) - } - // Check whether the peer supports upfront shutdown, and get an address // which should be used (either a user specified address or a new // address from the wallet if our node is configured to set shutdown // address by default). shutdown, err := getUpfrontShutdownScript( - f.cfg.EnableUpfrontShutdown, msg.Peer, shutdownScript, + f.cfg.EnableUpfrontShutdown, msg.Peer, msg.ShutdownScript, f.selectShutdownScript, ) if err != nil { @@ -5018,16 +4903,6 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) { } } - // The current variant of taproot channels can only be used with - // unadvertised channels for now. - if commitType.IsTaproot() && !msg.Private { - err = fmt.Errorf("taproot channel type for public channel") - log.Error(err) - msg.Err <- err - - return - } - // First, we'll query the fee estimator for a fee that should get the // commitment transaction confirmed by the next few blocks (conf target // of 3). We target the near blocks here to ensure that we'll be able diff --git a/funding/manager_test.go b/funding/manager_test.go index ae7058c03..45b847ffd 100644 --- a/funding/manager_test.go +++ b/funding/manager_test.go @@ -2,8 +2,6 @@ package funding import ( "bytes" - "context" - "crypto/rand" "encoding/hex" "errors" "fmt" @@ -13,28 +11,24 @@ import ( "reflect" "runtime" "strings" - "sync/atomic" "testing" "time" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/wallet" - "github.com/lightningnetwork/lnd/actor" "github.com/lightningnetwork/lnd/aliasmgr" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainreg" acpt "github.com/lightningnetwork/lnd/chanacceptor" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channelnotifier" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/discovery" "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/graph" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -251,7 +245,7 @@ func (m *mockChanEvent) NotifyOpenChannelEvent(outpoint wire.OutPoint, } func (m *mockChanEvent) NotifyPendingOpenChannelEvent(outpoint wire.OutPoint, - pendingChannel *chanstate.OpenChannel, + pendingChannel *channeldb.OpenChannel, remotePub *btcec.PublicKey) { m.pendingOpenEvent <- channelnotifier.PendingOpenChannelEvent{ @@ -480,18 +474,16 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey, return testSig, nil }, SendAnnouncement: func(msg lnwire.Message, - _ ...discovery.OptionalMsgField) actor.Future[error] { + _ ...discovery.OptionalMsgField) chan error { - promise := actor.NewPromise[error]() - var sendErr error + errChan := make(chan error, 1) select { case sentAnnouncements <- msg: + errChan <- nil case <-shutdownChan: - sendErr = fmt.Errorf("shutting down") + errChan <- fmt.Errorf("shutting down") } - actor.CompleteWith(promise, sendErr) - - return promise.Future() + return errChan }, CurrentNodeAnnouncement: func() (lnwire.NodeAnnouncement1, error) { @@ -500,7 +492,7 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey, }, TempChanIDSeed: chanIDSeed, FindChannel: func(node *btcec.PublicKey, - chanID lnwire.ChannelID) (*chanstate.OpenChannel, + chanID lnwire.ChannelID) (*channeldb.OpenChannel, error) { nodeChans, err := cdb.FetchOpenChannels(node) @@ -550,7 +542,7 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey, RequiredRemoteMaxHTLCs: func(chanAmt btcutil.Amount) uint16 { return uint16(input.MaxHTLCNumber / 2) }, - WatchNewChannel: func(*chanstate.OpenChannel, + WatchNewChannel: func(*channeldb.OpenChannel, *btcec.PublicKey) error { return nil @@ -657,18 +649,16 @@ func recreateAliceFundingManager(t *testing.T, alice *testNode) { return testSig, nil }, SendAnnouncement: func(msg lnwire.Message, - _ ...discovery.OptionalMsgField) actor.Future[error] { + _ ...discovery.OptionalMsgField) chan error { - promise := actor.NewPromise[error]() - var sendErr error + errChan := make(chan error, 1) select { case aliceAnnounceChan <- msg: + errChan <- nil case <-shutdownChan: - sendErr = fmt.Errorf("shutting down") + errChan <- fmt.Errorf("shutting down") } - actor.CompleteWith(promise, sendErr) - - return promise.Future() + return errChan }, CurrentNodeAnnouncement: func() (lnwire.NodeAnnouncement1, error) { @@ -1010,8 +1000,6 @@ func assertFundingMsgSent(t *testing.T, msgChan chan lnwire.Message, ok bool ) switch msgType { - case "OpenChannel": - sentMsg, ok = msg.(*lnwire.OpenChannel) case "AcceptChannel": sentMsg, ok = msg.(*lnwire.AcceptChannel) case "FundingCreated": @@ -1121,7 +1109,7 @@ func assertConfirmationHeight(t *testing.T, node *testNode, err := wait.NoError(func() error { pendingChannel, err := node.fundingMgr.cfg.Wallet.Cfg.Database. - FetchChannelByID(chanID) + FetchChannelByID(nil, chanID) if err != nil { return fmt.Errorf("unable to fetch pending channel: %w", err) @@ -1992,15 +1980,11 @@ func TestFundingManagerRestartBehavior(t *testing.T) { // Intentionally make the channel announcements fail alice.fundingMgr.cfg.SendAnnouncement = func(msg lnwire.Message, - _ ...discovery.OptionalMsgField) actor.Future[error] { + _ ...discovery.OptionalMsgField) chan error { - promise := actor.NewPromise[error]() - actor.CompleteWith( - promise, - fmt.Errorf("intentional error in SendAnnouncement"), - ) - - return promise.Future() + errChan := make(chan error, 1) + errChan <- fmt.Errorf("intentional error in SendAnnouncement") + return errChan } channelReadyAlice, ok := assertFundingMsgSent( @@ -3610,6 +3594,7 @@ func TestFundingManagerInvalidChanReserve(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -3889,198 +3874,6 @@ func TestFundingManagerRejectPush(t *testing.T) { ) } -// TestFundingManagerPushAmountExceedsCapacity asserts that the fundee -// rejects an incoming OpenChannel whose push_msat exceeds -// 1000 * funding_satoshis, as required by BOLT-02. -func TestFundingManagerPushAmountExceedsCapacity(t *testing.T) { - t.Parallel() - - alice, bob := setupFundingManagers(t) - t.Cleanup(func() { - tearDownFundingManagers(t, alice, bob) - }) - - // Build an OpenChannel directly with a push amount that strictly - // exceeds 1000 * funding_satoshis. We only need the fields that - // Bob's fundeeProcessOpenChannel inspects before the BOLT-02 bound - // check, so other fields are left zero. - const fundingAmt = btcutil.Amount(500000) - openChannelReq := &lnwire.OpenChannel{ - ChainHash: *fundingNetParams.GenesisHash, - PendingChannelID: [32]byte{0x01}, - FundingAmount: fundingAmt, - PushAmount: lnwire.NewMSatFromSatoshis(fundingAmt) + 1, - } - - bob.fundingMgr.ProcessFundingMsg(openChannelReq, alice) - - // Bob should respond with an Error that carries the - // ErrPushAmountTooLarge message. - msg := assertFundingMsgSent(t, bob.msgChan, "Error") - err, ok := msg.(*lnwire.Error) - require.True(t, ok, "expected *lnwire.Error, got %T", msg) - - expected := lnwallet.ErrPushAmountTooLarge( - openChannelReq.PushAmount, openChannelReq.FundingAmount, - ) - require.Equal(t, expected.Error(), string(err.Data)) -} - -// TestFundingManagerPushAmountAtCapacity asserts that the fundee does NOT -// reject an incoming OpenChannel with the BOLT-02 push-bound error when -// push_msat exactly equals 1000 * funding_satoshis. The spec permits this -// boundary (push_msat MUST be <= 1000 * funding_satoshis), so the check -// added in fundeeProcessOpenChannel must not fire on equality. The flow -// may still fail downstream for unrelated reasons (e.g. funder balance -// dust after fees), but never with ErrPushAmountTooLarge. -func TestFundingManagerPushAmountAtCapacity(t *testing.T) { - t.Parallel() - - alice, bob := setupFundingManagers(t) - t.Cleanup(func() { - tearDownFundingManagers(t, alice, bob) - }) - - const fundingAmt = btcutil.Amount(500000) - openChannelReq := &lnwire.OpenChannel{ - ChainHash: *fundingNetParams.GenesisHash, - PendingChannelID: [32]byte{0x01}, - FundingAmount: fundingAmt, - PushAmount: lnwire.NewMSatFromSatoshis(fundingAmt), - } - - bob.fundingMgr.ProcessFundingMsg(openChannelReq, alice) - - // Whatever response Bob produces, it must not be the BOLT-02 - // push-bound error: the boundary is spec-legal. - forbidden := lnwallet.ErrPushAmountTooLarge( - openChannelReq.PushAmount, openChannelReq.FundingAmount, - ).Error() - - select { - case msg := <-bob.msgChan: - errMsg, ok := msg.(*lnwire.Error) - if !ok { - return - } - require.NotEqual(t, forbidden, string(errMsg.Data), - "fundee rejected spec-legal boundary push_msat == "+ - "1000 * funding_satoshis") - case <-time.After(time.Second): - } -} - -// TestFundingManagerRejectPublicTaprootInitiator checks that a public taproot -// channel request is rejected by the initiator before an OpenChannel message is -// sent to the peer. -func TestFundingManagerRejectPublicTaprootInitiator(t *testing.T) { - t.Parallel() - - alice, bob := setupFundingManagers(t) - t.Cleanup(func() { - tearDownFundingManagers(t, alice, bob) - }) - - featureBits := []lnwire.FeatureBit{ - lnwire.ExplicitChannelTypeOptional, - lnwire.SimpleTaprootChannelsOptionalFinal, - } - alice.localFeatures = featureBits - alice.remoteFeatures = featureBits - bob.localFeatures = featureBits - bob.remoteFeatures = featureBits - - chanType := lnwire.ChannelType(*lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsRequiredFinal, - )) - - updateChan := make(chan *lnrpc.OpenStatusUpdate) - errChan := make(chan error, 1) - initReq := &InitFundingMsg{ - Peer: bob, - TargetPubkey: bob.privKey.PubKey(), - ChainHash: *fundingNetParams.GenesisHash, - LocalFundingAmt: 500000, - Private: false, - ChannelType: &chanType, - Updates: updateChan, - Err: errChan, - } - - alice.fundingMgr.InitFundingWorkflow(initReq) - - select { - case err := <-errChan: - require.ErrorContains( - t, err, "taproot channel type for public channel", - ) - - case msg := <-bob.msgChan: - t.Fatalf("expected local error, got %T", msg) - - case <-time.After(time.Second * 5): - t.Fatalf("timed out waiting for public taproot error") - } -} - -// TestFundingManagerRejectPublicTaprootResponder checks that the responder -// rejects a public taproot OpenChannel message. -func TestFundingManagerRejectPublicTaprootResponder(t *testing.T) { - t.Parallel() - - alice, bob := setupFundingManagers(t) - t.Cleanup(func() { - tearDownFundingManagers(t, alice, bob) - }) - - featureBits := []lnwire.FeatureBit{ - lnwire.ExplicitChannelTypeOptional, - lnwire.SimpleTaprootChannelsOptionalFinal, - } - alice.localFeatures = featureBits - alice.remoteFeatures = featureBits - bob.localFeatures = featureBits - bob.remoteFeatures = featureBits - - chanType := lnwire.ChannelType(*lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsRequiredFinal, - )) - - updateChan := make(chan *lnrpc.OpenStatusUpdate) - errChan := make(chan error, 1) - initReq := &InitFundingMsg{ - Peer: bob, - TargetPubkey: bob.privKey.PubKey(), - ChainHash: *fundingNetParams.GenesisHash, - LocalFundingAmt: 500000, - Private: true, - ChannelType: &chanType, - Updates: updateChan, - Err: errChan, - } - - alice.fundingMgr.InitFundingWorkflow(initReq) - - msg := assertFundingMsgSent(t, alice.msgChan, "OpenChannel") - openChannelReq, ok := msg.(*lnwire.OpenChannel) - require.True(t, ok) - - // Flip the captured wire message to public so the responder path is - // exercised without being blocked by the initiator-side guard. - openChannelReq.ChannelFlags = lnwire.FFAnnounceChannel - bob.fundingMgr.ProcessFundingMsg(openChannelReq, alice) - - // The specific taproot/public failure is logged locally; the wire error - // carries the generic message used for non-whitelisted funding errors. - errMsg := assertFundingMsgSent(t, bob.msgChan, "Error") - err, ok := errMsg.(*lnwire.Error) - require.True(t, ok) - require.ErrorContains( - t, err, "funding failed due to internal error", - ) - assertNumPendingReservations(t, bob, alicePubKey, 0) -} - // TestFundingManagerMaxConfs ensures that we don't accept a funding proposal // that proposes a MinAcceptDepth greater than the maximum number of // confirmations we're willing to accept. @@ -4355,6 +4148,7 @@ func TestFundingManagerFundMax(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -4463,6 +4257,7 @@ func TestGetUpfrontShutdownScript(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { var mockPeer testNode @@ -4740,6 +4535,7 @@ func TestFundingManagerUpfrontShutdown(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { testUpfrontFailure(t, test.pkscript, test.expectErr) @@ -4930,9 +4726,14 @@ func testZeroConf(t *testing.T, chanType *lnwire.ChannelType) { assertConfirmationHeight(t, alice, chanID, 1) assertConfirmationHeight(t, bob, chanID, 1) - // Both Alice and Bob should call ReportShortChanID first (before - // sending announcements) to avoid a race where other nodes learn about - // the confirmed SCID before the switch is ready. + // For taproot channels, we don't expect them to be announced atm. + if !isTaprootChanType(chanType) { + assertChannelAnnouncements( + t, alice, bob, fundingAmt, nil, nil, nil, nil, + ) + } + + // Both Alice and Bob should send on reportScidChan. select { case <-alice.reportScidChan: case <-time.After(time.Second * 5): @@ -4945,13 +4746,6 @@ func testZeroConf(t *testing.T, chanType *lnwire.ChannelType) { t.Fatalf("did not call ReportShortChanID in time") } - // For taproot channels, we don't expect them to be announced atm. - if !isTaprootChanType(chanType) { - assertChannelAnnouncements( - t, alice, bob, fundingAmt, nil, nil, nil, nil, - ) - } - // Send along the 6-confirmation channel so that announcement sigs can // be exchanged. alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{ @@ -5008,25 +4802,14 @@ func TestCommitmentTypeFundmaxSanityCheck(t *testing.T) { "SCRIPT_ENFORCED_LEASE": 4, "SIMPLE_TAPROOT": 5, "SIMPLE_TAPROOT_OVERLAY": 6, - "TAPROOT": 7, - "SIMPLE_TAPROOT_FINAL": 7, } - for commitmentType, protoValue := range lnrpc.CommitmentType_value { - expectedValue, ok := allCommitmentTypes[commitmentType] - if !ok { + for commitmentType := range lnrpc.CommitmentType_value { + if _, ok := allCommitmentTypes[commitmentType]; !ok { t.Fatalf("Commitment type %s hasn't been considered "+ "in the context of the --fundmax flag for "+ "channel openings.", commitmentType) } - - // Verify the proto enum integer values match to catch - // accidental renumbering. - if int(protoValue) != expectedValue { - t.Fatalf("Commitment type %s has proto value %d "+ - "but expected %d", commitmentType, - protoValue, expectedValue) - } } } @@ -5318,132 +5101,3 @@ func TestFundingManagerCoinbase(t *testing.T) { // channel. assertHandleChannelReady(t, alice, bob) } - -// TestMapGossipError verifies that mapGossipError correctly translates gossip -// result errors into funding manager errors. -func TestMapGossipError(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - inErr error - wantErr error - }{ - { - name: "nil error", - inErr: nil, - wantErr: nil, - }, - { - name: "context canceled maps to shutdown", - inErr: context.Canceled, - wantErr: ErrFundingManagerShuttingDown, - }, - { - name: "gossiper shutting down maps to shutdown", - inErr: discovery.ErrGossiperShuttingDown, - wantErr: ErrFundingManagerShuttingDown, - }, - { - name: "graph outdated treated as non-fatal", - inErr: graph.NewErrf(graph.ErrOutdated, "outdated"), - wantErr: nil, - }, - { - name: "graph ignored treated as non-fatal", - inErr: graph.NewErrf(graph.ErrIgnored, "ignored"), - wantErr: nil, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - got := mapGossipError(tc.inErr, "TestMsg") - - if tc.wantErr == nil { - require.NoError(t, got) - return - } - - require.Error(t, got) - require.ErrorIs(t, got, tc.wantErr) - }) - } - - // Verify that unrecognized errors pass through unchanged. - t.Run("other errors passed through", func(t *testing.T) { - t.Parallel() - - sentinel := errors.New("unexpected failure") - got := mapGossipError(sentinel, "TestMsg") - require.ErrorIs(t, got, sentinel) - }) -} - -// TestChannelReadyUnknownChannelID verifies that channel_ready messages -// referencing ChannelIDs unknown to the funding manager are consumed without -// stalling the coordinator. After a batch of such messages drains through, -// the manager must still be able to process a legitimate channel-open flow. -func TestChannelReadyUnknownChannelID(t *testing.T) { - t.Parallel() - - // Count FindChannel invocations so we can wait for every message to - // actually reach the coordinator's handler (ProcessFundingMsg is - // buffered and returns before processing). - var findChannelCalls atomic.Uint64 - - alice, bob := setupFundingManagers( - t, func(cfg *Config) { - origFindChannel := cfg.FindChannel - cfg.FindChannel = func( - node *btcec.PublicKey, - chanID lnwire.ChannelID, - ) (*chanstate.OpenChannel, error) { - - findChannelCalls.Add(1) - - return origFindChannel(node, chanID) - } - }, - ) - t.Cleanup(func() { - tearDownFundingManagers(t, alice, bob) - }) - - // Send a batch of channel_ready messages with random (unknown) - // ChannelIDs to Alice from Bob. - const numUnknownMessages = 100 - for i := 0; i < numUnknownMessages; i++ { - var randomChanID lnwire.ChannelID - _, err := rand.Read(randomChanID[:]) - require.NoError(t, err) - - unknownMsg := &lnwire.ChannelReady{ - ChanID: randomChanID, - NextPerCommitmentPoint: bobAddr.IdentityKey, - } - alice.fundingMgr.ProcessFundingMsg(unknownMsg, bob) - } - - // Wait for every message to flow through the coordinator's handler. - err := wait.NoError(func() error { - calls := findChannelCalls.Load() - if calls < numUnknownMessages { - return fmt.Errorf("FindChannel called %d times, "+ - "want %d", calls, numUnknownMessages) - } - - return nil - }, time.Second*15) - require.NoError(t, err) - - // Confirm the coordinator is still able to drive a real funding - // flow. If any of the earlier messages had wedged the coordinator, - // this call would hang. - updateChan := make(chan *lnrpc.OpenStatusUpdate) - openChannel( - t, alice, bob, 500000, 0, 1, updateChan, true, nil, - ) -} diff --git a/go.mod b/go.mod index 16e0922c6..f365f3ed5 100644 --- a/go.mod +++ b/go.mod @@ -4,76 +4,66 @@ require ( github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82 github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 github.com/andybalholm/brotli v1.0.4 - github.com/btcsuite/btcd v0.26.0 - github.com/btcsuite/btcd/address/v2 v2.0.0 - github.com/btcsuite/btcd/btcec/v2 v2.5.0 - github.com/btcsuite/btcd/btcutil/v2 v2.0.0 - github.com/btcsuite/btcd/chaincfg/v2 v2.0.0 - github.com/btcsuite/btcd/chainhash/v2 v2.0.0 - github.com/btcsuite/btcd/psbt/v2 v2.0.0 - github.com/btcsuite/btcd/txscript/v2 v2.0.0 - github.com/btcsuite/btcd/wire/v2 v2.0.0 - github.com/btcsuite/btclog v1.0.0 + github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6 + github.com/btcsuite/btcd/btcec/v2 v2.3.4 + github.com/btcsuite/btcd/btcutil v1.1.5 + github.com/btcsuite/btcd/btcutil/psbt v1.1.8 + github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 + github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b - github.com/btcsuite/btcwallet v0.18.0 - github.com/btcsuite/btcwallet/wallet/txauthor v1.4.0 - github.com/btcsuite/btcwallet/wallet/txrules v1.3.0 - github.com/btcsuite/btcwallet/walletdb v1.6.0 - github.com/btcsuite/btcwallet/wtxmgr v1.6.0 + github.com/btcsuite/btcwallet v0.16.17 + github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 + github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 + github.com/btcsuite/btcwallet/walletdb v1.5.1 + github.com/btcsuite/btcwallet/wtxmgr v1.5.6 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f github.com/davecgh/go-spew v1.1.1 - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 - github.com/gorilla/websocket v1.5.3 + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 + github.com/gorilla/websocket v1.5.0 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 - github.com/jackc/pgx/v5 v5.9.2 + github.com/jackc/pgx/v4 v4.18.3 github.com/jackpal/gateway v1.0.5 github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad github.com/jedib0t/go-pretty/v6 v6.2.7 - github.com/jessevdk/go-flags v1.6.1 + github.com/jessevdk/go-flags v1.4.0 github.com/jrick/logrotate v1.1.2 github.com/kkdai/bstream v1.0.0 - github.com/lightninglabs/neutrino v0.18.0 - github.com/lightninglabs/neutrino/cache v1.1.4 - github.com/lightningnetwork/lightning-onion v1.4.0 - github.com/lightningnetwork/lnd/actor v0.0.6 + github.com/lightninglabs/neutrino v0.16.1 + github.com/lightninglabs/neutrino/cache v1.1.2 + github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9 github.com/lightningnetwork/lnd/cert v1.2.2 github.com/lightningnetwork/lnd/clock v1.1.1 github.com/lightningnetwork/lnd/fn/v2 v2.0.9 github.com/lightningnetwork/lnd/healthcheck v1.2.6 - github.com/lightningnetwork/lnd/kvdb v1.5.1 - github.com/lightningnetwork/lnd/queue v1.2.0 - github.com/lightningnetwork/lnd/sqldb v1.0.13 + github.com/lightningnetwork/lnd/kvdb v1.4.16 + github.com/lightningnetwork/lnd/queue v1.1.1 + github.com/lightningnetwork/lnd/sqldb v1.0.12-0.20260113193010-8565d12e40b1 github.com/lightningnetwork/lnd/ticker v1.1.1 - github.com/lightningnetwork/lnd/tlv v1.4.0 - github.com/lightningnetwork/lnd/tor v1.2.0 + github.com/lightningnetwork/lnd/tlv v1.3.2 + github.com/lightningnetwork/lnd/tor v1.1.6 github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 github.com/miekg/dns v1.1.43 - github.com/prometheus/client_golang v1.23.2 - github.com/stretchr/testify v1.11.1 + github.com/prometheus/client_golang v1.11.1 + github.com/stretchr/testify v1.10.0 github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 - github.com/urfave/cli v1.22.14 + github.com/urfave/cli v1.22.9 go.etcd.io/etcd/client/pkg/v3 v3.5.12 go.etcd.io/etcd/client/v3 v3.5.12 - golang.org/x/crypto v0.46.0 - golang.org/x/exp v0.0.0-20250811191247-51f88131bc50 + golang.org/x/crypto v0.37.0 + golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 - golang.org/x/sync v0.19.0 - golang.org/x/term v0.38.0 + golang.org/x/sync v0.13.0 + golang.org/x/term v0.31.0 golang.org/x/time v0.3.0 - google.golang.org/grpc v1.79.3 - google.golang.org/protobuf v1.36.11 + google.golang.org/grpc v1.59.0 + google.golang.org/protobuf v1.33.0 gopkg.in/macaroon-bakery.v2 v2.0.1 gopkg.in/macaroon.v2 v2.0.0 pgregory.net/rapid v1.2.0 ) -require ( - github.com/felixge/httpsnoop v1.1.0 // indirect - github.com/kcalvinalvin/anet v0.0.0-20251112173137-d8ddc1f6dbee // indirect -) - require ( dario.cat/mergo v1.0.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect @@ -83,34 +73,34 @@ require ( github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/aead/siphash v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/btcsuite/btcd/v2transport v1.0.1 // indirect - github.com/btcsuite/btcwallet/wallet/txsizes v1.3.0 // indirect + github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5 // indirect github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect github.com/btcsuite/winsvc v1.0.0 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/containerd/continuity v0.3.0 // indirect github.com/coreos/go-semver v0.3.0 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect - github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect - github.com/decred/dcrd/lru v1.1.3 // indirect + github.com/coreos/go-systemd/v22 v22.3.2 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect + github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect + github.com/decred/dcrd/lru v1.1.2 // indirect github.com/docker/cli v28.1.1+incompatible // indirect github.com/docker/docker v28.1.1+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fergusstrange/embedded-postgres v1.25.0 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect - github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/go-viper/mapstructure/v2 v2.3.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang-migrate/migrate/v4 v4.17.0 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/golang/snappy v1.0.0 // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.0.1 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect @@ -118,42 +108,49 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.3 // indirect github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect + github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.3 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgtype v1.14.4 // indirect + github.com/jackc/pgx/v5 v5.7.4 // indirect + github.com/jackc/puddle v1.3.0 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jonboulle/clockwork v0.2.2 // indirect - github.com/json-iterator/go v1.1.12 // indirect + github.com/json-iterator/go v1.1.11 // indirect github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4 // indirect github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494 // indirect - github.com/klauspost/compress v1.18.0 + github.com/klauspost/compress v1.17.9 github.com/lib/pq v1.10.9 // indirect github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/sys/user v0.3.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/onsi/gomega v1.26.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/opencontainers/runc v1.2.8 // indirect + github.com/opencontainers/runc v1.1.14 // indirect github.com/ory/dockertest/v3 v3.10.0 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 - github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.26.0 // indirect + github.com/prometheus/procfs v0.6.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/fastuuid v1.2.0 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/russross/blackfriday/v2 v2.0.1 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/sirupsen/logrus v1.9.2 // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/stretchr/objx v0.5.2 // indirect @@ -171,27 +168,26 @@ require ( go.etcd.io/etcd/pkg/v3 v3.5.12 // indirect go.etcd.io/etcd/raft/v3 v3.5.12 // indirect go.etcd.io/etcd/server/v3 v3.5.12 // indirect - go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 // indirect - go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 // indirect - go.opentelemetry.io/otel/metric v1.44.0 // indirect - go.opentelemetry.io/otel/sdk v1.44.0 // indirect - go.opentelemetry.io/otel/trace v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.35.0 // indirect + go.opentelemetry.io/otel/sdk v1.35.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.17.0 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/mod v0.30.0 // indirect - golang.org/x/net v0.48.0 // indirect - golang.org/x/sys v0.45.0 // indirect - golang.org/x/text v0.32.0 - golang.org/x/tools v0.39.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.39.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect gopkg.in/errgo.v1 v1.0.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect @@ -206,32 +202,20 @@ require ( sigs.k8s.io/yaml v1.2.0 // indirect ) -// TODO(elle): remove once the gossip V2 sqldb changes have been made. -replace github.com/lightningnetwork/lnd/sqldb => ./sqldb +// This replace is for https://github.com/advisories/GHSA-25xm-hr59-7c27 +replace github.com/ulikunitz/xz => github.com/ulikunitz/xz v0.5.11 + +// This replace is for +// https://deps.dev/advisory/OSV/GO-2021-0053?from=%2Fgo%2Fgithub.com%252Fgogo%252Fprotobuf%2Fv1.3.1 +replace github.com/gogo/protobuf => github.com/gogo/protobuf v1.3.2 // We want to format raw bytes as hex instead of base64. The forked version // allows us to specify that as an option. -replace google.golang.org/protobuf => github.com/lightninglabs/protobuf-go-hex-display v1.36.11-hex-display +replace google.golang.org/protobuf => github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display -// If you change this please also update docs/INSTALL.md and all other go.mod -// files. The release build toolchain version is tracked separately by -// GO_VERSION in Makefile. -go 1.25.11 +// If you change this please also update docs/INSTALL.md and GO_VERSION in +// Makefile (then run `make lint` to see where else it needs to be updated as +// well). +go 1.24.11 retract v0.0.2 - -replace github.com/lightningnetwork/lnd/actor => ./actor - -replace github.com/lightningnetwork/lnd/cert => ./cert - -replace github.com/lightningnetwork/lnd/clock => ./clock - -replace github.com/lightningnetwork/lnd/fn/v2 => ./fn - -replace github.com/lightningnetwork/lnd/healthcheck => ./healthcheck - -replace github.com/lightningnetwork/lnd/kvdb => ./kvdb - -replace github.com/lightningnetwork/lnd/queue => ./queue - -replace github.com/lightningnetwork/lnd/ticker => ./ticker diff --git a/go.sum b/go.sum index b0772e0a9..318c75694 100644 --- a/go.sum +++ b/go.sum @@ -2,15 +2,17 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= -cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= -cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e h1:n+DcnTNkQnHlwpsrHoQtkrJIO7CBx029fw6oR4vIob4= @@ -25,53 +27,62 @@ github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmH github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/btcsuite/btcd v0.26.0 h1:yntnSshlG3+H7dTwIOR4LTFXDPojVBsFORBNN5y5c/c= -github.com/btcsuite/btcd v0.26.0/go.mod h1:7ft7+a/MoJHFouFopCb1zyiR9IWPlrcPVn6K/lJ1dcA= -github.com/btcsuite/btcd/address/v2 v2.0.0 h1:UVu8Hal6Siu4XastFe+JX5JkeBYONbDUIY5E+SVTs6I= -github.com/btcsuite/btcd/address/v2 v2.0.0/go.mod h1:htJK1AtaeK3bKNfZY63ep2oN8LbrI6qvmPGe1vekb3I= -github.com/btcsuite/btcd/btcec/v2 v2.5.0 h1:KioMXOWa76b86sTZZOmbzv/ldaQCmB8KFAyn5PbB8E8= -github.com/btcsuite/btcd/btcec/v2 v2.5.0/go.mod h1:+K/MYXcLBtHEQjRbjHuJChuybk4LCgjdjgRwil+e+Kk= -github.com/btcsuite/btcd/btcutil/v2 v2.0.0 h1:77pgf/4tjWaSBLdos8yiWVWL3rSphxWNqkLwcyONExA= -github.com/btcsuite/btcd/btcutil/v2 v2.0.0/go.mod h1:ZF8MMdsx1JGgvHJUanxbigekSO+8bN/ai34LBk/lg3c= -github.com/btcsuite/btcd/chaincfg/v2 v2.0.0 h1:M/RTtXfXA9odC1RUEOyZFXj/NXKVHPYZXVjb60xTOok= -github.com/btcsuite/btcd/chaincfg/v2 v2.0.0/go.mod h1:rHgHIXYYfn70m25a+BJ9f9z7VZAsTiDQGB2XYaippGQ= -github.com/btcsuite/btcd/chainhash/v2 v2.0.0 h1:PMLlSloHJuEeB80XG9EjpXWNEKAZAMLl6YHZ6YsEuoA= -github.com/btcsuite/btcd/chainhash/v2 v2.0.0/go.mod h1:mKxcZ7oGTXE7IRV+sS9hP4EVBwc/SzfNR+52IsOP9j8= -github.com/btcsuite/btcd/psbt/v2 v2.0.0 h1:vKBfHGkxsplVExwCozfK9VuioZ6D6cRpAhcMia2kgrQ= -github.com/btcsuite/btcd/psbt/v2 v2.0.0/go.mod h1:WdHfpXJDXklnMU8u22YXz8o12q8hPYr4b8CxpKdjGYs= -github.com/btcsuite/btcd/txscript/v2 v2.0.0 h1:pEmmHaC8eRx6KSB63zSVJD7qrit9/c9cLSrw++XrYP8= -github.com/btcsuite/btcd/txscript/v2 v2.0.0/go.mod h1:pZXabc11Xr9nz/18kXY3yErdAajYc3gi28Zqb3KqlFo= -github.com/btcsuite/btcd/v2transport v1.0.1 h1:pIyyyBCPwd087K3Wdb/9tIvUubAQdzTJghjPgzTQVsE= -github.com/btcsuite/btcd/v2transport v1.0.1/go.mod h1:N6H0HGSElVVJKntzaYHYVbW71DtWDLMw2yhwVRO3ZOE= -github.com/btcsuite/btcd/wire/v2 v2.0.0 h1:mYSKzZZ0a1sK+aMhXzfDSVsSzRkWkU3x2U04TFRS2z8= -github.com/btcsuite/btcd/wire/v2 v2.0.0/go.mod h1:bGxkPkk8IiDvUo1D96wE03llBIk7p2MdWYRyAQwLmqM= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= +github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6 h1:8n9k3I7e8DkpdQ5YAP4j8ly/LSsbe6qX9vmVbrUGvVw= +github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6/go.mod h1:OmM4kFtB0klaG/ZqT86rQiyw/1iyXlJgc3UHClPhhbs= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= +github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= +github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/btcutil/psbt v1.1.8 h1:4voqtT8UppT7nmKQkXV+T9K8UyQjKOn2z/ycpmJK8wg= +github.com/btcsuite/btcd/btcutil/psbt v1.1.8/go.mod h1:kA6FLH/JfUx++j9pYU0pyu+Z8XGBQuuTmuKYUf6q7/U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/btcsuite/btclog v1.0.0 h1:sEkpKJMmfGiyZjADwEIgB1NSwMyfdD1FB8v6+w1T0Ns= -github.com/btcsuite/btclog v1.0.0/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ= +github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c h1:4HxD1lBUGUddhzgaNgrCPsFWd7cGYNpeFUgd9ZIgyM0= +github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ= github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b h1:MQ+Q6sDy37V1wP1Yu79A5KqJutolqUGwA99UZWQDWZM= github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE= -github.com/btcsuite/btcwallet v0.18.0 h1:VSRClNLT7NX0wmJEGALz3jOZRRjWPpUdp7VI1Akie1o= -github.com/btcsuite/btcwallet v0.18.0/go.mod h1:1ZMc1EEskov+AKKv4kCMZqN8BwVh9rpXwEyxbeWy2A4= -github.com/btcsuite/btcwallet/wallet/txauthor v1.4.0 h1:oIkGj32YK1CvWaJGlVwZA1f+y/KVHkfrd2PoST0ZpQs= -github.com/btcsuite/btcwallet/wallet/txauthor v1.4.0/go.mod h1:sGrBjcqQ8UPexuRajFs72+o544CJn3Pavv/5H0VAWVk= -github.com/btcsuite/btcwallet/wallet/txrules v1.3.0 h1:D5aGMwWIxdqek3xEJs4eOdMoh6iga2EI2xSlaXCdnNo= -github.com/btcsuite/btcwallet/wallet/txrules v1.3.0/go.mod h1:ZzSdn2XrsUDPa193Q/su1sJY+716rlFK2H1mYwbY/18= -github.com/btcsuite/btcwallet/wallet/txsizes v1.3.0 h1:2W9qt0edMoX8crx0Wm4Cv+eAj4B3jlbn0N/5fckLHSU= -github.com/btcsuite/btcwallet/wallet/txsizes v1.3.0/go.mod h1:42aE6+LMZSSEisQAa15Xml25ncuJFfhCrkcpB5OmkZk= -github.com/btcsuite/btcwallet/walletdb v1.6.0 h1:Yund5XbdqFxNW7+R2Sxs02bMC5fMrmORj4GN8MV55no= -github.com/btcsuite/btcwallet/walletdb v1.6.0/go.mod h1:q9xif0Csp52GVb3l252BbHCuyiCnuEbrPWu/HAsvaYc= -github.com/btcsuite/btcwallet/wtxmgr v1.6.0 h1:ivSSnYCD4Kb5yAMZVyBA1VMYABIFcopPEcmHCrRZXcE= -github.com/btcsuite/btcwallet/wtxmgr v1.6.0/go.mod h1:Raor7IBIwHSIKE9Lr5o+R9rwX7sRMHU1zjxgEQgn9h8= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcwallet v0.16.17 h1:1N6lHznRdcjDopBvcofxaIHknArkJ/EcVKgLKfGL4Dg= +github.com/btcsuite/btcwallet v0.16.17/go.mod h1:YO+W745BAH8n/Rpgj68QsLR6eLlgM4W2do4RejT0buo= +github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 h1:Rr0njWI3r341nhSPesKQ2JF+ugDSzdPoeckS75SeDZk= +github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5/go.mod h1:+tXJ3Ym0nlQc/iHSwW1qzjmPs3ev+UVWMbGgfV1OZqU= +github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 h1:YEO+Lx1ZJJAtdRrjuhXjWrYsmAk26wLTlNzxt2q0lhk= +github.com/btcsuite/btcwallet/wallet/txrules v1.2.2/go.mod h1:4v+grppsDpVn91SJv+mZT7B8hEV4nSmpREM4I8Uohws= +github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5 h1:93o5Xz9dYepBP4RMFUc9RGIFXwqP2volSWRkYJFrNtI= +github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5/go.mod h1:lQ+e9HxZ85QP7r3kdxItkiMSloSLg1PEGis5o5CXUQw= +github.com/btcsuite/btcwallet/walletdb v1.5.1 h1:HgMhDNCrtEFPC+8q0ei5DQ5U9Tl4RCspA22DEKXlopI= +github.com/btcsuite/btcwallet/walletdb v1.5.1/go.mod h1:jk/hvpLFINF0C1kfTn0bfx2GbnFT+Nvnj6eblZALfjs= +github.com/btcsuite/btcwallet/wtxmgr v1.5.6 h1:Zwvr/rrJYdOLqdBCSr4eICEstnEA+NBUvjIWLkrXaYI= +github.com/btcsuite/btcwallet/wtxmgr v1.5.6/go.mod h1:lzVbDkk/jRao2ib5kge46aLZW1yFc8RFNycdYpnsmZA= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk= @@ -79,37 +90,45 @@ github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46f github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= -github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= +github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY= +github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= -github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= -github.com/decred/dcrd/lru v1.1.3 h1:w9EAbvGLyzm6jTjF83UKuqZEiUtJmvRhQDOCEIvSuE0= -github.com/decred/dcrd/lru v1.1.3/go.mod h1:Tw0i0pJyiLEx/oZdHLe1Wdv/Y7EGzAX+sYftnmxBR4o= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/decred/dcrd/lru v1.1.2 h1:KdCzlkxppuoIDGEvCGah1fZRicrDH36IipvlB1ROkFY= +github.com/decred/dcrd/lru v1.1.2/go.mod h1:gEdCVgXs1/YoBvFWt7Scgknbhwik3FgVSzlnCcXL2N8= github.com/dhui/dktest v0.4.0 h1:z05UmuXZHO/bgj/ds2bGMBu8FI4WA+Ag/m3ghL+om7M= github.com/dhui/dktest v0.4.0/go.mod h1:v/Dbz1LgCBOi2Uki2nUqLBGa83hWBGFMu5MrgMDCc78= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= @@ -128,10 +147,10 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= -github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= -github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= -github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= +github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fergusstrange/embedded-postgres v1.25.0 h1:sa+k2Ycrtz40eCRPOzI7Ry7TtkWXXJ+YRsxpKMDhxK0= github.com/fergusstrange/embedded-postgres v1.25.0/go.mod h1:t/MLs0h9ukYM6FSt99R7InCHs1nW0ordoVCcnzmpTYw= github.com/frankban/quicktest v1.2.2 h1:xfmOhhoH5fGPgbEAlhLpJH9p0z/0Qizio9osmvn9IUY= @@ -141,19 +160,25 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= +github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= @@ -161,37 +186,41 @@ github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w github.com/golang-migrate/migrate/v4 v4.17.0 h1:rd40H3QXU0AA4IoLllFcEAEo9dYKRHYND2gB4p7xcaU= github.com/golang-migrate/migrate/v4 v4.17.0/go.mod h1:+Cp2mtLP4/aXDTKb9wmXYitdrNx2HGs45rbWAo6OsKM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I= -github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= -github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= @@ -208,14 +237,62 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9 github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= -github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.14.4 h1:fKuNiCumbKTAIxQwXfB/nsrnkEI6bPJrrSiMKgbJ2j8= +github.com/jackc/pgtype v1.14.4/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg= +github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= +github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc= @@ -225,15 +302,18 @@ github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad/go.mod h1:QPH04 github.com/jedib0t/go-pretty/v6 v6.2.7 h1:4823Lult/tJ0VI1PgW3aSKw59pMWQ6Kzv9b3Bj6MwY0= github.com/jedib0t/go-pretty/v6 v6.2.7/go.mod h1:FMkOpgGD3EZ91cW8g/96RfxoV7bdeJyzXPYgz1L1ln0= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4= -github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc= +github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/jrick/logrotate v1.1.2 h1:6ePk462NCX7TfKtNp5JJ7MbA2YIslkpfgP03TlTYMN0= github.com/jrick/logrotate v1.1.2/go.mod h1:f9tdWggSVK3iqavGpyvegq5IhNois7KXmasU6/N96OQ= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c h1:3UvYABOQRhJAApj9MdCN+Ydv841ETSoy6xLzdmmr/9A= github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA= @@ -253,79 +333,109 @@ github.com/juju/utils/v3 v3.0.0-20220130232349-cd7ecef0e94a h1:5ZWDCeCF0RaITrZGe github.com/juju/utils/v3 v3.0.0-20220130232349-cd7ecef0e94a/go.mod h1:LzwbbEN7buYjySp4nqnti6c6olSqRXUk6RkbSUUP1n8= github.com/juju/version/v2 v2.0.0-20211007103408-2e8da085dc23 h1:wtEPbidt1VyHlb8RSztU6ySQj29FLsOQiI9XiJhXDM4= github.com/juju/version/v2 v2.0.0-20211007103408-2e8da085dc23/go.mod h1:Ljlbryh9sYaUSGXucslAEDf0A2XUSGvDbHJgW8ps6nc= -github.com/kcalvinalvin/anet v0.0.0-20251112173137-d8ddc1f6dbee h1:FPP9HDkBbPyniu+u7FHZg+kKFX1WW0gxOGteJ0h3AJk= -github.com/kcalvinalvin/anet v0.0.0-20251112173137-d8ddc1f6dbee/go.mod h1:N6sz6HwJAenJ6d+/xmSl0ikfV05ZrVGmjt1ryy/WOtE= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kkdai/bstream v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8= github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc= github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= -github.com/lightninglabs/neutrino v0.18.0 h1:UsyeU3twkCSAXxUssVg8vGXcHkWKzHZ3xQCej57t5t0= -github.com/lightninglabs/neutrino v0.18.0/go.mod h1:TL52mV5nxeAN//WFHrU8XV9GZKwnrrNFL9/nKMX8sl4= -github.com/lightninglabs/neutrino/cache v1.1.4 h1:KVtvUmBwYr7eKtjfjHG/q6/cQlcrjHl23Gag+VdxF2g= -github.com/lightninglabs/neutrino/cache v1.1.4/go.mod h1:ZqLzxghPggIRfNXeAZN1VtTKofMyK/ALI6niYsPH6OE= -github.com/lightninglabs/protobuf-go-hex-display v1.36.11-hex-display h1:fjePiMfYbg3KodAiSXwkpGZpYdPeNd9VnggJWiY6AaY= -github.com/lightninglabs/protobuf-go-hex-display v1.36.11-hex-display/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -github.com/lightningnetwork/lightning-onion v1.4.0 h1:qWE1icOH4AKXRcq1KCzt6P/TesqptgBTP++V7wowTc0= -github.com/lightningnetwork/lightning-onion v1.4.0/go.mod h1:YDPkvVTVQ6FBBE6Yj93tDd7zA3iTSrryi9xq46i7bKE= -github.com/lightningnetwork/lnd/tlv v1.4.0 h1:qNGymEkOZsHbp4h8VjnkRGgLfmh+eJP1JQtz2/mJX2M= -github.com/lightningnetwork/lnd/tlv v1.4.0/go.mod h1:oL5WIFd3ZoEwh3oH1xzizeUl6pq3DIhx9ljDvRdvI3Q= -github.com/lightningnetwork/lnd/tor v1.2.0 h1:Xled9KE+rdTPEMDVJE5VrSoMNkJq6EfH3NCF6pLP4h4= -github.com/lightningnetwork/lnd/tor v1.2.0/go.mod h1:tB6/Hsk5nIm6xkOPaidd4q1at4x4gF6Bpu/t2UQxYgM= +github.com/lightninglabs/neutrino v0.16.1 h1:5Kz4ToxncEVkpKC6fwUjXKtFKJhuxlG3sBB3MdJTJjs= +github.com/lightninglabs/neutrino v0.16.1/go.mod h1:L+5UAccpUdyM7yDgmQySgixf7xmwBgJtOfs/IP26jCs= +github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3shmlu5hIQ798g= +github.com/lightninglabs/neutrino/cache v1.1.2/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo= +github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display h1:Y2WiPkBS/00EiEg0qp0FhehxnQfk3vv8U6Xt3nN+rTY= +github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9 h1:6D3LrdagJweLLdFm1JNodZsBk6iU4TTsBBFLQ4yiXfI= +github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9/go.mod h1:EDqJ3MuZIbMq0QI1czTIKDJ/GS8S14RXPwapHw8cw6w= +github.com/lightningnetwork/lnd/cert v1.2.2 h1:71YK6hogeJtxSxw2teq3eGeuy4rHGKcFf0d0Uy4qBjI= +github.com/lightningnetwork/lnd/cert v1.2.2/go.mod h1:jQmFn/Ez4zhDgq2hnYSw8r35bqGVxViXhX6Cd7HXM6U= +github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0= +github.com/lightningnetwork/lnd/clock v1.1.1/go.mod h1:mGnAhPyjYZQJmebS7aevElXKTFDuO+uNFFfMXK1W8xQ= +github.com/lightningnetwork/lnd/fn/v2 v2.0.9 h1:ZytG4ltPac/sCyg1EJDn10RGzPIDJeyennUMRdOw7Y8= +github.com/lightningnetwork/lnd/fn/v2 v2.0.9/go.mod h1:aPUJHJ31S+Lgoo8I5SxDIjnmeCifqujaiTXKZqpav3w= +github.com/lightningnetwork/lnd/healthcheck v1.2.6 h1:1sWhqr93GdkWy4+6U7JxBfcyZIE78MhIHTJZfPx7qqI= +github.com/lightningnetwork/lnd/healthcheck v1.2.6/go.mod h1:Mu02um4CWY/zdTOvFje7WJgJcHyX2zq/FG3MhOAiGaQ= +github.com/lightningnetwork/lnd/kvdb v1.4.16 h1:9BZgWdDfjmHRHLS97cz39bVuBAqMc4/p3HX1xtUdbDI= +github.com/lightningnetwork/lnd/kvdb v1.4.16/go.mod h1:HW+bvwkxNaopkz3oIgBV6NEnV4jCEZCACFUcNg4xSjM= +github.com/lightningnetwork/lnd/queue v1.1.1 h1:99ovBlpM9B0FRCGYJo6RSFDlt8/vOkQQZznVb18iNMI= +github.com/lightningnetwork/lnd/queue v1.1.1/go.mod h1:7A6nC1Qrm32FHuhx/mi1cieAiBZo5O6l8IBIoQxvkz4= +github.com/lightningnetwork/lnd/sqldb v1.0.12-0.20260113193010-8565d12e40b1 h1:PkEppKL17cZh0Dr9h/T9BEVJUbd/p2tjJ/x8ffG3R0M= +github.com/lightningnetwork/lnd/sqldb v1.0.12-0.20260113193010-8565d12e40b1/go.mod h1:tB2jlqu79TIOR9uhAZOmPxpVFUhB2s+oxKnqRRL1oc0= +github.com/lightningnetwork/lnd/ticker v1.1.1 h1:J/b6N2hibFtC7JLV77ULQp++QLtCwT6ijJlbdiZFbSM= +github.com/lightningnetwork/lnd/ticker v1.1.1/go.mod h1:waPTRAAcwtu7Ji3+3k+u/xH5GHovTsCoSVpho0KDvdA= +github.com/lightningnetwork/lnd/tlv v1.3.2 h1:MO4FCk7F4k5xPMqVZF6Nb/kOpxlwPrUQpYjmyKny5s0= +github.com/lightningnetwork/lnd/tlv v1.3.2/go.mod h1:pJuiBj1ecr1WWLOtcZ+2+hu9Ey25aJWFIsjmAoPPnmc= +github.com/lightningnetwork/lnd/tor v1.1.6 h1:WHUumk7WgU6BUFsqHuqszI9P6nfhMeIG+rjJBlVE6OE= +github.com/lightningnetwork/lnd/tor v1.1.6/go.mod h1:qSRB8llhAK+a6kaTPWOLLXSZc6Hg8ZC0mq1sUQ/8JfI= github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw= github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY= github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA= github.com/lunixbochs/vtclean v0.0.0-20160125035106-4fbf7632a2c6/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/mattn/go-colorable v0.0.6/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= -github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= @@ -334,45 +444,70 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v1.2.8 h1:RnEICeDReapbZ5lZEgHvj7E9Q3Eex9toYmaGBsbvU5Q= -github.com/opencontainers/runc v1.2.8/go.mod h1:cC0YkmZcuvr+rtBZ6T7NBoVbMGNAdLa/21vIElJDOzI= +github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w= +github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= -github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= -github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= +github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= @@ -380,20 +515,21 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 h1:tcJ6OjwOMvExLlzrAVZute09ocAGa7KqOON60++Gz4E= github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY= -github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= -github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= +github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw= +github.com/urfave/cli v1.22.9/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= @@ -406,6 +542,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= @@ -424,167 +562,244 @@ go.etcd.io/etcd/raft/v3 v3.5.12 h1:7r22RufdDsq2z3STjoR7Msz6fYH8tmbkdheGfwJNRmU= go.etcd.io/etcd/raft/v3 v3.5.12/go.mod h1:ERQuZVe79PI6vcC3DlKBukDCLja/L7YMu29B74Iwj4U= go.etcd.io/etcd/server/v3 v3.5.12 h1:EtMjsbfyfkwZuA2JlKOiBfuGkFCekv5H178qjXypbG8= go.etcd.io/etcd/server/v3 v3.5.12/go.mod h1:axB0oCjMy+cemo5290/CutIjoxlfA6KVYKD1w0uue10= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 h1:PzIubN4/sjByhDRHLviCjJuweBXWFZWhghjg7cS28+M= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= -go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= -go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 h1:DeFD0VgTZ+Cj6hxravYYZE2W4GlneVH81iAOPjZkzk8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0/go.mod h1:GijYcYmNpX1KazD5JmWGsi4P7dDTTTnfv1UbGn84MnU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 h1:gvmNvqrPYovvyRmCSygkUDyL8lC5Tl845MLEwqpxhEU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0/go.mod h1:vNUq47TGFioo+ffTSnKNdob241vePmtNZnAODKapKd0= -go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= -go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= -go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= -go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= -go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20250811191247-51f88131bc50 h1:3yiSh9fhy5/RhCSntf4Sy0Tnx50DmMpQ4MQdKKk4yg4= -golang.org/x/exp v0.0.0-20250811191247-51f88131bc50/go.mod h1:rT6SFzZ7oxADUDx58pcaKFTcZ+inxAa9fTrYx/uVYwg= +golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= +golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0= +golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= -golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= +google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= -google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v1 v1.0.1 h1:oQFRXzZ7CkBGdm1XZm/EbQYaYNNEElNBOd09M6cqNso= gopkg.in/errgo.v1 v1.0.1/go.mod h1:3NjfXwocQRYAPTq4/fzX+CwUhPRcR/azYRhj8G+LqMo= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/macaroon-bakery.v2 v2.0.1 h1:0N1TlEdfLP4HXNCg7MQUMp5XwvOoxk+oe9Owr2cpvsc= gopkg.in/macaroon-bakery.v2 v2.0.1/go.mod h1:B4/T17l+ZWGwxFSZQmlBwp25x+og7OkhETfr3S9MbIA= gopkg.in/macaroon.v2 v2.0.0 h1:LVWycAfeJBUjCIqfR9gqlo7I8vmiXRr51YEOZ1suop8= @@ -593,9 +808,11 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXL gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -608,6 +825,7 @@ gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk= modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA= diff --git a/graph/builder.go b/graph/builder.go index c8e8d09f2..59e9b19d5 100644 --- a/graph/builder.go +++ b/graph/builder.go @@ -9,10 +9,9 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/batch" "github.com/lightningnetwork/lnd/chainntnfs" - "github.com/lightningnetwork/lnd/fn/v2" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lnutils" @@ -30,11 +29,6 @@ const ( // if a channel should be pruned or not. DefaultChannelPruneExpiry = time.Hour * 24 * 14 - // avgBitcoinBlockTime is the approximate time between Bitcoin blocks, - // used to convert a time-based channel prune expiry into a - // block-height-based expiry for v2 gossip channels. - avgBitcoinBlockTime = 10 * time.Minute - // DefaultFirstTimePruneDelay is the time we'll wait after startup // before attempting to prune the graph for zombie channels. We don't // do it immediately after startup to allow lnd to start up without @@ -61,7 +55,7 @@ type Config struct { // Graph is the channel graph that the ChannelRouter will use to gather // metrics from and also to carry out path finding queries. - Graph *graphdb.ChannelGraph + Graph DB // Chain is the router's source to the most up-to-date blockchain data. // All incoming advertised channels will be checked against the chain @@ -117,8 +111,7 @@ type Builder struct { bestHeight atomic.Uint32 - cfg *Config - v1Graph *graphdb.VersionedGraph + cfg *Config // newBlocks is a channel in which new blocks connected to the end of // the main chain are sent over, and blocks updated after a call to @@ -153,11 +146,7 @@ var _ ChannelGraphSource = (*Builder)(nil) // NewBuilder constructs a new Builder. func NewBuilder(cfg *Config) (*Builder, error) { return &Builder{ - cfg: cfg, - // For now, we'll just use V1 graph reader. - v1Graph: graphdb.NewVersionedGraph( - cfg.Graph, lnwire.GossipVersion1, - ), + cfg: cfg, channelEdgeMtx: multimutex.NewMutex[uint64](), statTicker: ticker.New(defaultStatInterval), stats: new(builderStats), @@ -181,7 +170,7 @@ func (b *Builder) Start() error { // If the graph has never been pruned, or hasn't fully been created yet, // then we don't treat this as an explicit error. - if _, _, err := b.cfg.Graph.PruneTip(context.TODO()); err != nil { + if _, _, err := b.cfg.Graph.PruneTip(); err != nil { switch { case errors.Is(err, graphdb.ErrGraphNeverPruned): fallthrough @@ -191,8 +180,7 @@ func (b *Builder) Start() error { // the prune height to the current best height of the // chain backend. _, err = b.cfg.Graph.PruneGraph( - context.TODO(), nil, bestHash, - uint32(bestHeight), + nil, bestHash, uint32(bestHeight), ) if err != nil { return err @@ -237,7 +225,7 @@ func (b *Builder) Start() error { // FilteredChainView instance. We do this before, as otherwise // we may miss on-chain events as the filter hasn't properly // been applied. - channelView, err := b.v1Graph.ChannelView(context.TODO()) + channelView, err := b.cfg.Graph.ChannelView() if err != nil && !errors.Is( err, graphdb.ErrGraphNoEdgesFound, ) { @@ -275,7 +263,7 @@ func (b *Builder) Start() error { // Finally, before we proceed, we'll prune any unconnected nodes // from the graph in order to ensure we maintain a tight graph // of "useful" nodes. - err = b.cfg.Graph.PruneGraphNodes(context.TODO()) + err = b.cfg.Graph.PruneGraphNodes() if err != nil && !errors.Is(err, graphdb.ErrGraphNodesNotFound) { @@ -330,7 +318,7 @@ func (b *Builder) syncGraphWithChain() error { } b.bestHeight.Store(uint32(bestHeight)) - pruneHash, pruneHeight, err := b.cfg.Graph.PruneTip(context.TODO()) + pruneHash, pruneHeight, err := b.cfg.Graph.PruneTip() if err != nil { switch { // If the graph has never been pruned, or hasn't fully been @@ -372,16 +360,12 @@ func (b *Builder) syncGraphWithChain() error { "(hash=%v)", pruneHeight, pruneHash) // Prune the graph for every channel that was opened at height // >= pruneHeight. - _, err := b.cfg.Graph.DisconnectBlockAtHeight( - context.TODO(), pruneHeight, - ) + _, err := b.cfg.Graph.DisconnectBlockAtHeight(pruneHeight) if err != nil { return err } - pruneHash, pruneHeight, err = b.cfg.Graph.PruneTip( - context.TODO(), - ) + pruneHash, pruneHeight, err = b.cfg.Graph.PruneTip() switch { // If at this point the graph has never been pruned, we can exit // as this entails we are back to the point where it hasn't seen @@ -454,7 +438,7 @@ func (b *Builder) syncGraphWithChain() error { // With the spent outputs gathered, attempt to prune the channel graph, // also passing in the best hash+height so the prune tip can be updated. closedChans, err := b.cfg.Graph.PruneGraph( - context.TODO(), spentOutputs, bestHash, uint32(bestHeight), + spentOutputs, bestHash, uint32(bestHeight), ) if err != nil { return err @@ -466,57 +450,6 @@ func (b *Builder) syncGraphWithChain() error { return nil } -// isTimestampStale returns true if the given freshness timestamp is considered -// stale based on the gossip version. For v1, staleness is determined by -// wall-clock time since the unix timestamp. For v2, staleness is determined by -// how many blocks have elapsed since the block height timestamp. -func (b *Builder) isTimestampStale(v lnwire.GossipVersion, - freshness lnwire.Timestamp) bool { - - chanExpiry := b.cfg.ChannelPruneExpiry - - switch v { - case lnwire.GossipVersion1: - ts, ok := freshness.(lnwire.UnixTimestamp) - if !ok || ts.IsZero() { - return true - } - - t := time.Unix(int64(ts), 0) - - return time.Since(t) >= chanExpiry - - default: - h, ok := freshness.(lnwire.BlockHeightTimestamp) - if !ok || uint32(h) == 0 { - return true - } - - expiryBlocks := uint32(chanExpiry / avgBitcoinBlockTime) - currentHeight := b.bestHeight.Load() - height := uint32(h) - - if height > currentHeight { - return false - } - - return currentHeight-height >= expiryBlocks - } -} - -// isPolicyZombie returns true if the given edge policy is considered stale -// based on version-specific freshness criteria. -func (b *Builder) isPolicyZombie(e *models.ChannelEdgePolicy) bool { - var freshness lnwire.Timestamp - if e.Version == lnwire.GossipVersion1 { - freshness = lnwire.UnixTimestamp(e.LastUpdate.Unix()) - } else { - freshness = lnwire.BlockHeightTimestamp(e.LastBlockHeight) - } - - return b.isTimestampStale(e.Version, freshness) -} - // isZombieChannel takes two edge policy updates and determines if the // corresponding channel should be considered a zombie. The first boolean is // true if the policy update from node 1 is considered a zombie, the second @@ -525,30 +458,44 @@ func (b *Builder) isPolicyZombie(e *models.ChannelEdgePolicy) bool { func (b *Builder) isZombieChannel(e1, e2 *models.ChannelEdgePolicy) (bool, bool, bool) { - e1Zombie := e1 == nil || b.isPolicyZombie(e1) - e2Zombie := e2 == nil || b.isPolicyZombie(e2) + chanExpiry := b.cfg.ChannelPruneExpiry - // If strict zombie pruning is enabled, a channel is a zombie if - // either edge is stale. - if b.cfg.StrictZombiePruning { - return e1Zombie, e2Zombie, e1Zombie || e2Zombie + e1Zombie := e1 == nil || time.Since(e1.LastUpdate) >= chanExpiry + e2Zombie := e2 == nil || time.Since(e2.LastUpdate) >= chanExpiry + + var e1Time, e2Time time.Time + if e1 != nil { + e1Time = e1.LastUpdate + } + if e2 != nil { + e2Time = e2.LastUpdate } - // Otherwise a channel is only a zombie if both edges are stale. - return e1Zombie, e2Zombie, e1Zombie && e2Zombie + return e1Zombie, e2Zombie, b.IsZombieChannel(e1Time, e2Time) } -// IsZombieChannel returns true if the channel described by info should be -// considered a zombie. For v1 channels, freshness is a unix timestamp; for v2+ -// channels it is a block height. -func (b *Builder) IsZombieChannel(info graphdb.ChannelUpdateInfo) bool { - e1Zombie := b.isTimestampStale(info.Version, info.Node1Freshness) - e2Zombie := b.isTimestampStale(info.Version, info.Node2Freshness) +// IsZombieChannel takes the timestamps of the latest channel updates for a +// channel and returns true if the channel should be considered a zombie based +// on these timestamps. +func (b *Builder) IsZombieChannel(updateTime1, + updateTime2 time.Time) bool { + chanExpiry := b.cfg.ChannelPruneExpiry + + e1Zombie := updateTime1.IsZero() || + time.Since(updateTime1) >= chanExpiry + + e2Zombie := updateTime2.IsZero() || + time.Since(updateTime2) >= chanExpiry + + // If we're using strict zombie pruning, then a channel is only + // considered live if both edges have a recent update we know of. if b.cfg.StrictZombiePruning { return e1Zombie || e2Zombie } + // Otherwise, if we're using the less strict variant, then a channel is + // considered live if either of the edges have a recent update. return e1Zombie && e2Zombie } @@ -621,16 +568,14 @@ func (b *Builder) pruneZombieChans() error { // both edges. If they're both disabled, then we can interpret this as // the channel being closed and can prune it from our graph. if b.cfg.AssumeChannelValid { - disabledChanIDs, err := b.cfg.Graph.DisabledChannelIDs( - context.TODO(), lnwire.GossipVersion1, - ) + disabledChanIDs, err := b.cfg.Graph.DisabledChannelIDs() if err != nil { return fmt.Errorf("unable to get disabled channels "+ "ids chans: %v", err) } - disabledEdges, err := b.v1Graph.FetchChanInfos( - context.TODO(), disabledChanIDs, + disabledEdges, err := b.cfg.Graph.FetchChanInfos( + disabledChanIDs, ) if err != nil { return fmt.Errorf("unable to fetch disabled channels "+ @@ -648,12 +593,7 @@ func (b *Builder) pruneZombieChans() error { startTime := time.Unix(0, 0) endTime := time.Now().Add(-1 * chanExpiry) - oldEdgesIter := b.v1Graph.ChanUpdatesInHorizon( - context.TODO(), graphdb.ChanUpdateRange{ - StartTime: fn.Some(startTime), - EndTime: fn.Some(endTime), - }, - ) + oldEdgesIter := b.cfg.Graph.ChanUpdatesInHorizon(startTime, endTime) for u, err := range oldEdgesIter { if err != nil { @@ -680,8 +620,8 @@ func (b *Builder) pruneZombieChans() error { toPrune = append(toPrune, chanID) log.Tracef("Pruning zombie channel with ChannelID(%v)", chanID) } - err := b.v1Graph.DeleteChannelEdges( - context.TODO(), b.cfg.StrictZombiePruning, true, toPrune..., + err := b.cfg.Graph.DeleteChannelEdges( + b.cfg.StrictZombiePruning, true, toPrune..., ) if err != nil { return fmt.Errorf("unable to delete zombie channels: %w", err) @@ -689,7 +629,7 @@ func (b *Builder) pruneZombieChans() error { // With the channels pruned, we'll also attempt to prune any nodes that // were a part of them. - err = b.cfg.Graph.PruneGraphNodes(context.TODO()) + err = b.cfg.Graph.PruneGraphNodes() if err != nil && !errors.Is(err, graphdb.ErrGraphNodesNotFound) { return fmt.Errorf("unable to prune graph nodes: %w", err) } @@ -735,7 +675,7 @@ func (b *Builder) networkHandler() { // Update the channel graph to reflect that this block // was disconnected. _, err := b.cfg.Graph.DisconnectBlockAtHeight( - context.TODO(), blockHeight, + blockHeight, ) if err != nil { log.Errorf("unable to prune graph with stale "+ @@ -908,10 +848,8 @@ func (b *Builder) updateGraphWithClosedChannels( // With the spent outputs gathered, attempt to prune the channel graph, // also passing in the hash+height of the block being pruned so the // prune tip can be updated. - chansClosed, err := b.cfg.Graph.PruneGraph( - context.TODO(), spentOutputs, &chainUpdate.Hash, - chainUpdate.Height, - ) + chansClosed, err := b.cfg.Graph.PruneGraph(spentOutputs, + &chainUpdate.Hash, chainUpdate.Height) if err != nil { log.Errorf("unable to prune routing table: %v", err) return err @@ -936,7 +874,7 @@ func (b *Builder) assertNodeAnnFreshness(ctx context.Context, node route.Vertex, // node announcements, we will ignore such nodes. If we do know about // this node, check that this update brings info newer than what we // already have. - lastUpdate, exists, err := b.cfg.Graph.HasV1Node(ctx, node) + lastUpdate, exists, err := b.cfg.Graph.HasNode(ctx, node) if err != nil { return fmt.Errorf("unable to query for the "+ "existence of node: %w", err) @@ -966,9 +904,7 @@ func (b *Builder) MarkZombieEdge(chanID uint64) error { // so we don't continue to request it. We use the "zero key" for both // node pubkeys so this edge can't be resurrected. var zeroKey [33]byte - err := b.cfg.Graph.MarkEdgeZombie( - context.TODO(), lnwire.GossipVersion1, chanID, zeroKey, zeroKey, - ) + err := b.cfg.Graph.MarkEdgeZombie(chanID, zeroKey, zeroKey) if err != nil { return fmt.Errorf("unable to mark spent chan(id=%v) as a "+ "zombie: %w", chanID, err) @@ -1011,12 +947,19 @@ func (b *Builder) ApplyChannelUpdate(msg *lnwire.ChannelUpdate1) bool { return false } - update, err := models.ChanEdgePolicyFromWire( - msg.ShortChannelID.ToUint64(), msg, - ) - if err != nil { - log.Errorf("Unable to parse channel update: %v", err) - return false + update := &models.ChannelEdgePolicy{ + SigBytes: msg.Signature.ToSignatureBytes(), + ChannelID: msg.ShortChannelID.ToUint64(), + LastUpdate: time.Unix(int64(msg.Timestamp), 0), + MessageFlags: msg.MessageFlags, + ChannelFlags: msg.ChannelFlags, + TimeLockDelta: msg.TimeLockDelta, + MinHTLC: msg.HtlcMinimumMsat, + MaxHTLC: msg.HtlcMaximumMsat, + FeeBaseMSat: lnwire.MilliSatoshi(msg.BaseFee), + FeeProportionalMillionths: lnwire.MilliSatoshi(msg.FeeRate), + InboundFee: msg.InboundFee.ValOpt(), + ExtraOpaqueData: msg.ExtraOpaqueData, } err = b.UpdateEdge(ctx, update) @@ -1102,8 +1045,8 @@ func (b *Builder) addEdge(ctx context.Context, edge *models.ChannelEdgeInfo, // Prior to processing the announcement we first check if we // already know of this channel, if so, then we can exit early. - exists, isZombie, err := b.cfg.Graph.HasChannelEdge( - ctx, edge.Version, edge.ChannelID, + _, _, exists, isZombie, err := b.cfg.Graph.HasChannelEdge( + edge.ChannelID, ) if err != nil && !errors.Is(err, graphdb.ErrGraphNoEdgesFound) { return fmt.Errorf("unable to check for edge existence: %w", @@ -1204,7 +1147,7 @@ func (b *Builder) updateEdge(ctx context.Context, defer b.channelEdgeMtx.Unlock(policy.ChannelID) edge1Timestamp, edge2Timestamp, exists, isZombie, err := - b.cfg.Graph.HasV1ChannelEdge(ctx, policy.ChannelID) + b.cfg.Graph.HasChannelEdge(policy.ChannelID) if err != nil && !errors.Is(err, graphdb.ErrGraphNoEdgesFound) { return fmt.Errorf("unable to check for edge existence: %w", err) } @@ -1312,9 +1255,7 @@ func (b *Builder) GetChannelByID(chanID lnwire.ShortChannelID) ( *models.ChannelEdgePolicy, *models.ChannelEdgePolicy, error) { - return b.cfg.Graph.FetchChannelEdgesByID( - context.TODO(), chanID.ToUint64(), - ) + return b.cfg.Graph.FetchChannelEdgesByID(chanID.ToUint64()) } // FetchNode attempts to look up a target node by its identity public @@ -1325,7 +1266,7 @@ func (b *Builder) GetChannelByID(chanID lnwire.ShortChannelID) ( func (b *Builder) FetchNode(ctx context.Context, node route.Vertex) (*models.Node, error) { - return b.v1Graph.FetchNode(ctx, node) + return b.cfg.Graph.FetchNode(ctx, node) } // ForAllOutgoingChannels is used to iterate over all outgoing channels owned by @@ -1337,7 +1278,7 @@ func (b *Builder) ForAllOutgoingChannels(ctx context.Context, reset func()) error { return b.cfg.Graph.ForEachNodeChannel( - ctx, lnwire.GossipVersion1, b.cfg.SelfNode, + ctx, b.cfg.SelfNode, func(c *models.ChannelEdgeInfo, e *models.ChannelEdgePolicy, _ *models.ChannelEdgePolicy) error { @@ -1358,7 +1299,7 @@ func (b *Builder) ForAllOutgoingChannels(ctx context.Context, func (b *Builder) AddProof(chanID lnwire.ShortChannelID, proof *models.ChannelAuthProof) error { - return b.cfg.Graph.AddEdgeProof(context.TODO(), chanID, proof) + return b.cfg.Graph.AddEdgeProof(chanID, proof) } // IsStaleNode returns true if the graph source has a node announcement for the @@ -1384,7 +1325,7 @@ func (b *Builder) IsStaleNode(ctx context.Context, node route.Vertex, // // NOTE: This method is part of the ChannelGraphSource interface. func (b *Builder) IsPublicNode(node route.Vertex) (bool, error) { - return b.v1Graph.IsPublicNode(context.TODO(), node) + return b.cfg.Graph.IsPublicNode(node) } // IsKnownEdge returns true if the graph source already knows of the passed @@ -1392,8 +1333,8 @@ func (b *Builder) IsPublicNode(node route.Vertex) (bool, error) { // // NOTE: This method is part of the ChannelGraphSource interface. func (b *Builder) IsKnownEdge(chanID lnwire.ShortChannelID) bool { - exists, isZombie, _ := b.cfg.Graph.HasChannelEdge( - context.TODO(), lnwire.GossipVersion1, chanID.ToUint64(), + _, _, exists, isZombie, _ := b.cfg.Graph.HasChannelEdge( + chanID.ToUint64(), ) return exists || isZombie @@ -1404,9 +1345,7 @@ func (b *Builder) IsKnownEdge(chanID lnwire.ShortChannelID) bool { // // NOTE: This method is part of the ChannelGraphSource interface. func (b *Builder) IsZombieEdge(chanID lnwire.ShortChannelID) (bool, error) { - _, isZombie, err := b.cfg.Graph.HasChannelEdge( - context.TODO(), lnwire.GossipVersion1, chanID.ToUint64(), - ) + _, _, _, isZombie, err := b.cfg.Graph.HasChannelEdge(chanID.ToUint64()) return isZombie, err } @@ -1419,9 +1358,7 @@ func (b *Builder) IsStaleEdgePolicy(chanID lnwire.ShortChannelID, timestamp time.Time, flags lnwire.ChanUpdateChanFlags) bool { edge1Timestamp, edge2Timestamp, exists, isZombie, err := - b.cfg.Graph.HasV1ChannelEdge( - context.TODO(), chanID.ToUint64(), - ) + b.cfg.Graph.HasChannelEdge(chanID.ToUint64()) if err != nil { log.Debugf("Check stale edge policy got error: %v", err) return false @@ -1470,14 +1407,9 @@ func (b *Builder) IsStaleEdgePolicy(chanID lnwire.ShortChannelID, return false } -// MarkEdgeLive clears an edge from our zombie index for the given gossip -// version, deeming it as live. +// MarkEdgeLive clears an edge from our zombie index, deeming it as live. // // NOTE: This method is part of the ChannelGraphSource interface. -func (b *Builder) MarkEdgeLive(v lnwire.GossipVersion, - chanID lnwire.ShortChannelID) error { - - return b.cfg.Graph.MarkEdgeLive( - context.TODO(), v, chanID.ToUint64(), - ) +func (b *Builder) MarkEdgeLive(chanID lnwire.ShortChannelID) error { + return b.cfg.Graph.MarkEdgeLive(chanID.ToUint64()) } diff --git a/graph/builder_test.go b/graph/builder_test.go index 2102f966b..0461c4fdc 100644 --- a/graph/builder_test.go +++ b/graph/builder_test.go @@ -16,11 +16,11 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/fn/v2" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/htlcswitch" @@ -54,27 +54,28 @@ func TestAddProof(t *testing.T) { // In order to be able to add the edge we should have a valid funding // UTXO within the blockchain. - script, fundingTx, _, chanID := createChannelEdge( - t, bitcoinKey1.SerializeCompressed(), + script, fundingTx, _, chanID, err := createChannelEdge( + bitcoinKey1.SerializeCompressed(), bitcoinKey2.SerializeCompressed(), 100, 0, ) + require.NoError(t, err, "unable create channel edge") fundingBlock := &wire.MsgBlock{ Transactions: []*wire.MsgTx{fundingTx}, } ctx.chain.addBlock(fundingBlock, chanID.BlockHeight, chanID.BlockHeight) // After utxo was recreated adding the edge without the proof. - btcKey1 := route.NewVertex(bitcoinKey1) - btcKey2 := route.NewVertex(bitcoinKey2) + edge := &models.ChannelEdgeInfo{ + ChannelID: chanID.ToUint64(), + NodeKey1Bytes: node1.PubKeyBytes, + NodeKey2Bytes: node2.PubKeyBytes, + AuthProof: nil, + Features: lnwire.EmptyFeatureVector(), + FundingScript: fn.Some(script), + } + copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) + copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) - edge, err := models.NewV1Channel( - chanID.ToUint64(), *chaincfg.SimNetParams.GenesisHash, - node1.PubKeyBytes, node2.PubKeyBytes, &models.ChannelV1Fields{ - BitcoinKey1Bytes: btcKey1, - BitcoinKey2Bytes: btcKey2, - }, models.WithFundingScript(script), - ) - require.NoError(t, err) require.NoError(t, ctx.builder.AddEdge(ctxb, edge)) // Now we'll attempt to update the proof and check that it has been @@ -96,22 +97,21 @@ func TestIgnoreNodeAnnouncement(t *testing.T) { ctx := createTestCtxFromFile(t, startingBlockHeight, basicGraphFilePath) pub := priv1.PubKey() - node := models.NewV1Node( - route.NewVertex(pub), &models.NodeV1Fields{ - Addresses: testAddrs, - AuthSigBytes: testSig.Serialize(), - Features: testFeatures.RawFeatureVector, - LastUpdate: time.Unix(123, 0), - Color: color.RGBA{1, 2, 3, 0}, - Alias: "node11", - }, - ) + node := &models.Node{ + HaveNodeAnnouncement: true, + LastUpdate: time.Unix(123, 0), + Addresses: testAddrs, + Color: color.RGBA{1, 2, 3, 0}, + Alias: "node11", + AuthSigBytes: testSig.Serialize(), + Features: testFeatures, + } + copy(node.PubKeyBytes[:], pub.SerializeCompressed()) err := ctx.builder.AddNode(t.Context(), node) - require.Truef( - t, IsError(err, ErrIgnored), - "expected to get ErrIgnore, instead got: %v", err, - ) + if !IsError(err, ErrIgnored) { + t.Fatalf("expected to get ErrIgnore, instead got: %v", err) + } } // TestIgnoreChannelEdgePolicyForUnknownChannel checks that a router will @@ -141,31 +141,27 @@ func TestIgnoreChannelEdgePolicyForUnknownChannel(t *testing.T) { // Add the edge between the two unknown nodes to the graph, and check // that the nodes are found after the fact. - script, fundingTx, _, chanID := createChannelEdge( - t, bitcoinKey1.SerializeCompressed(), + script, fundingTx, _, chanID, err := createChannelEdge( + bitcoinKey1.SerializeCompressed(), bitcoinKey2.SerializeCompressed(), 10000, 500, ) + require.NoError(t, err, "unable to create channel edge") fundingBlock := &wire.MsgBlock{ Transactions: []*wire.MsgTx{fundingTx}, } ctx.chain.addBlock(fundingBlock, chanID.BlockHeight, chanID.BlockHeight) - pub1Vertex, err := route.NewVertexFromBytes(pub1[:]) - require.NoError(t, err) - pub2Vertex, err := route.NewVertexFromBytes(pub2[:]) - require.NoError(t, err) - - edge, err := models.NewV1Channel( - chanID.ToUint64(), *chaincfg.SimNetParams.GenesisHash, - pub1Vertex, pub2Vertex, &models.ChannelV1Fields{ - BitcoinKey1Bytes: pub1Vertex, - BitcoinKey2Bytes: pub2Vertex, - }, models.WithFundingScript(script), - ) - require.NoError(t, err) - + edge := &models.ChannelEdgeInfo{ + ChannelID: chanID.ToUint64(), + NodeKey1Bytes: pub1, + NodeKey2Bytes: pub2, + BitcoinKey1Bytes: pub1, + BitcoinKey2Bytes: pub2, + AuthProof: nil, + Features: lnwire.EmptyFeatureVector(), + FundingScript: fn.Some(script), + } edgePolicy := &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: edge.ChannelID, LastUpdate: testTime, @@ -178,17 +174,14 @@ func TestIgnoreChannelEdgePolicyForUnknownChannel(t *testing.T) { // Attempt to update the edge. This should be ignored, since the edge // is not yet added to the router. err = ctx.builder.UpdateEdge(ctxb, edgePolicy) - require.Truef( - t, IsError(err, ErrIgnored), - "expected to get ErrIgnore, instead got: %v", err, - ) + if !IsError(err, ErrIgnored) { + t.Fatalf("expected to get ErrIgnore, instead got: %v", err) + } // Add the edge. - require.NoErrorf( - t, ctx.builder.AddEdge(ctxb, edge), + require.NoErrorf(t, ctx.builder.AddEdge(ctxb, edge), "expected to be able to add edge to the channel graph, even "+ - "though the vertexes were unknown: %v.", err, - ) + "though the vertexes were unknown: %v.", err) // Now updating the edge policy should succeed. require.NoError(t, ctx.builder.UpdateEdge(ctxb, edgePolicy)) @@ -221,11 +214,14 @@ func TestWakeUpOnStaleBranch(t *testing.T) { } height := startingBlockHeight + i if i == 5 { - script, fundingTx, _, chanID := createChannelEdge( - t, bitcoinKey1.SerializeCompressed(), + script, fundingTx, _, chanID, err := createChannelEdge( + bitcoinKey1.SerializeCompressed(), bitcoinKey2.SerializeCompressed(), chanValue, height, ) + if err != nil { + t.Fatalf("unable create channel edge: %v", err) + } block.Transactions = append(block.Transactions, fundingTx) chanID1 = chanID.ToUint64() @@ -251,10 +247,13 @@ func TestWakeUpOnStaleBranch(t *testing.T) { } height := uint32(forkHeight) + i if i == 5 { - script, fundingTx, _, chanID := createChannelEdge( - t, bitcoinKey1.SerializeCompressed(), + script, fundingTx, _, chanID, err := createChannelEdge( + bitcoinKey1.SerializeCompressed(), bitcoinKey2.SerializeCompressed(), chanValue, height) + if err != nil { + t.Fatalf("unable create channel edge: %v", err) + } block.Transactions = append(block.Transactions, fundingTx) chanID2 = chanID.ToUint64() @@ -273,56 +272,73 @@ func TestWakeUpOnStaleBranch(t *testing.T) { node1 := createTestNode(t) node2 := createTestNode(t) - btcKey1, err := route.NewVertexFromBytes( - bitcoinKey1.SerializeCompressed(), - ) - require.NoError(t, err) - btcKey2, err := route.NewVertexFromBytes( - bitcoinKey2.SerializeCompressed(), - ) - require.NoError(t, err) + edge1 := &models.ChannelEdgeInfo{ + ChannelID: chanID1, + NodeKey1Bytes: node1.PubKeyBytes, + NodeKey2Bytes: node2.PubKeyBytes, + AuthProof: &models.ChannelAuthProof{ + NodeSig1Bytes: testSig.Serialize(), + NodeSig2Bytes: testSig.Serialize(), + BitcoinSig1Bytes: testSig.Serialize(), + BitcoinSig2Bytes: testSig.Serialize(), + }, + Features: lnwire.EmptyFeatureVector(), + FundingScript: fn.Some(fundingScript1), + } + copy(edge1.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) + copy(edge1.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) - edge1, err := models.NewV1Channel( - chanID1, *chaincfg.SimNetParams.GenesisHash, - node1.PubKeyBytes, node2.PubKeyBytes, &models.ChannelV1Fields{ - BitcoinKey1Bytes: btcKey1, - BitcoinKey2Bytes: btcKey2, - }, models.WithChanProof(models.NewV1ChannelAuthProof( - testSig.Serialize(), testSig.Serialize(), - testSig.Serialize(), testSig.Serialize(), - )), models.WithFundingScript(fundingScript1), - ) - require.NoError(t, err) + if err := ctx.builder.AddEdge(ctxb, edge1); err != nil { + t.Fatalf("unable to add edge: %v", err) + } - require.NoError(t, ctx.builder.AddEdge(ctxb, edge1)) + edge2 := &models.ChannelEdgeInfo{ + ChannelID: chanID2, + NodeKey1Bytes: node1.PubKeyBytes, + NodeKey2Bytes: node2.PubKeyBytes, + AuthProof: &models.ChannelAuthProof{ + NodeSig1Bytes: testSig.Serialize(), + NodeSig2Bytes: testSig.Serialize(), + BitcoinSig1Bytes: testSig.Serialize(), + BitcoinSig2Bytes: testSig.Serialize(), + }, + Features: lnwire.EmptyFeatureVector(), + FundingScript: fn.Some(fundingScript2), + } + copy(edge2.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) + copy(edge2.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) - edge2, err := models.NewV1Channel( - chanID2, *chaincfg.SimNetParams.GenesisHash, node1.PubKeyBytes, - node2.PubKeyBytes, &models.ChannelV1Fields{ - BitcoinKey1Bytes: btcKey1, - BitcoinKey2Bytes: btcKey2, - }, models.WithChanProof(models.NewV1ChannelAuthProof( - testSig.Serialize(), testSig.Serialize(), - testSig.Serialize(), testSig.Serialize(), - )), models.WithFundingScript(fundingScript2), - ) - require.NoError(t, err) - - require.NoError(t, ctx.builder.AddEdge(ctxb, edge2)) + if err := ctx.builder.AddEdge(ctxb, edge2); err != nil { + t.Fatalf("unable to add edge: %v", err) + } // Check that the fundingTxs are in the graph db. - has, isZombie, err := ctx.graph.HasChannelEdge(t.Context(), chanID1) - require.NoError(t, err) - require.True(t, has) - require.False(t, isZombie) + _, _, has, isZombie, err := ctx.graph.HasChannelEdge(chanID1) + if err != nil { + t.Fatalf("error looking for edge: %v", chanID1) + } + if !has { + t.Fatalf("could not find edge in graph") + } + if isZombie { + t.Fatal("edge was marked as zombie") + } - has, isZombie, err = ctx.graph.HasChannelEdge(t.Context(), chanID2) - require.NoError(t, err) - require.True(t, has) - require.False(t, isZombie) + _, _, has, isZombie, err = ctx.graph.HasChannelEdge(chanID2) + if err != nil { + t.Fatalf("error looking for edge: %v", chanID2) + } + if !has { + t.Fatalf("could not find edge in graph") + } + if isZombie { + t.Fatal("edge was marked as zombie") + } // Stop the router, so we can reorg the chain while its offline. - require.NoError(t, ctx.builder.Stop()) + if err := ctx.builder.Stop(); err != nil { + t.Fatalf("unable to stop router: %v", err) + } // Create a 15 block fork. for i := uint32(1); i <= 15; i++ { @@ -343,7 +359,7 @@ func TestWakeUpOnStaleBranch(t *testing.T) { // Create new router with same graph database. router, err := NewBuilder(&Config{ SelfNode: selfNode.PubKeyBytes, - Graph: ctx.graph.ChannelGraph, + Graph: ctx.graph, Chain: ctx.chain, ChainView: ctx.chainView, ChannelPruneExpiry: time.Hour * 24, @@ -358,20 +374,33 @@ func TestWakeUpOnStaleBranch(t *testing.T) { require.NoError(t, err) // It should resync to the longer chain on startup. - require.NoError(t, router.Start()) + if err := router.Start(); err != nil { + t.Fatalf("unable to start router: %v", err) + } // The channel with chanID2 should not be in the database anymore, // since it is not confirmed on the longest chain. chanID1 should // still be. - has, isZombie, err = ctx.graph.HasChannelEdge(t.Context(), chanID1) + _, _, has, isZombie, err = ctx.graph.HasChannelEdge(chanID1) require.NoError(t, err) - require.True(t, has) - require.False(t, isZombie) - has, isZombie, err = ctx.graph.HasChannelEdge(t.Context(), chanID2) - require.NoError(t, err) - require.False(t, has) - require.False(t, isZombie) + if !has { + t.Fatalf("did not find edge in graph") + } + if isZombie { + t.Fatal("edge was marked as zombie") + } + + _, _, has, isZombie, err = ctx.graph.HasChannelEdge(chanID2) + if err != nil { + t.Fatalf("error looking for edge: %v", chanID2) + } + if has { + t.Fatalf("found edge in graph") + } + if isZombie { + t.Fatal("reorged edge should not be marked as zombie") + } } // TestDisconnectedBlocks checks that the router handles a reorg happening when @@ -395,11 +424,14 @@ func TestDisconnectedBlocks(t *testing.T) { } height := startingBlockHeight + i if i == 5 { - _, fundingTx, _, chanID := createChannelEdge( - t, bitcoinKey1.SerializeCompressed(), + _, fundingTx, _, chanID, err := createChannelEdge( + bitcoinKey1.SerializeCompressed(), bitcoinKey2.SerializeCompressed(), chanValue, height, ) + if err != nil { + t.Fatalf("unable create channel edge: %v", err) + } block.Transactions = append(block.Transactions, fundingTx) chanID1 = chanID.ToUint64() @@ -424,11 +456,14 @@ func TestDisconnectedBlocks(t *testing.T) { } height := uint32(forkHeight) + i if i == 5 { - _, fundingTx, _, chanID := createChannelEdge( - t, bitcoinKey1.SerializeCompressed(), + _, fundingTx, _, chanID, err := createChannelEdge( + bitcoinKey1.SerializeCompressed(), bitcoinKey2.SerializeCompressed(), chanValue, height, ) + if err != nil { + t.Fatalf("unable create channel edge: %v", err) + } block.Transactions = append(block.Transactions, fundingTx) chanID2 = chanID.ToUint64() @@ -447,50 +482,72 @@ func TestDisconnectedBlocks(t *testing.T) { node1 := createTestNode(t) node2 := createTestNode(t) - btcKey1 := route.NewVertex(bitcoinKey1) - btcKey2 := route.NewVertex(bitcoinKey2) + edge1 := &models.ChannelEdgeInfo{ + ChannelID: chanID1, + NodeKey1Bytes: node1.PubKeyBytes, + NodeKey2Bytes: node2.PubKeyBytes, + BitcoinKey1Bytes: node1.PubKeyBytes, + BitcoinKey2Bytes: node2.PubKeyBytes, + AuthProof: &models.ChannelAuthProof{ + NodeSig1Bytes: testSig.Serialize(), + NodeSig2Bytes: testSig.Serialize(), + BitcoinSig1Bytes: testSig.Serialize(), + BitcoinSig2Bytes: testSig.Serialize(), + }, + Features: lnwire.EmptyFeatureVector(), + FundingScript: fn.Some([]byte{}), + } + copy(edge1.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) + copy(edge1.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) - proof := models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ) + if err := ctx.builder.AddEdge(ctxb, edge1); err != nil { + t.Fatalf("unable to add edge: %v", err) + } - edge1, err := models.NewV1Channel( - chanID1, *chaincfg.SimNetParams.GenesisHash, node1.PubKeyBytes, - node2.PubKeyBytes, &models.ChannelV1Fields{ - BitcoinKey1Bytes: btcKey1, - BitcoinKey2Bytes: btcKey2, - }, models.WithChanProof(proof), - models.WithFundingScript([]byte{}), - ) - require.NoError(t, err) + edge2 := &models.ChannelEdgeInfo{ + ChannelID: chanID2, + NodeKey1Bytes: node1.PubKeyBytes, + NodeKey2Bytes: node2.PubKeyBytes, + BitcoinKey1Bytes: node1.PubKeyBytes, + BitcoinKey2Bytes: node2.PubKeyBytes, + AuthProof: &models.ChannelAuthProof{ + NodeSig1Bytes: testSig.Serialize(), + NodeSig2Bytes: testSig.Serialize(), + BitcoinSig1Bytes: testSig.Serialize(), + BitcoinSig2Bytes: testSig.Serialize(), + }, + Features: lnwire.EmptyFeatureVector(), + FundingScript: fn.Some([]byte{}), + } + copy(edge2.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) + copy(edge2.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) - require.NoError(t, ctx.builder.AddEdge(ctxb, edge1)) - - edge2, err := models.NewV1Channel( - chanID2, *chaincfg.SimNetParams.GenesisHash, node1.PubKeyBytes, - node2.PubKeyBytes, &models.ChannelV1Fields{ - BitcoinKey1Bytes: btcKey1, - BitcoinKey2Bytes: btcKey2, - }, models.WithChanProof(proof), - models.WithFundingScript([]byte{}), - ) - require.NoError(t, err) - - require.NoError(t, ctx.builder.AddEdge(ctxb, edge2)) + if err := ctx.builder.AddEdge(ctxb, edge2); err != nil { + t.Fatalf("unable to add edge: %v", err) + } // Check that the fundingTxs are in the graph db. - has, isZombie, err := ctx.graph.HasChannelEdge(t.Context(), chanID1) - require.NoError(t, err) - require.True(t, has) - require.False(t, isZombie) + _, _, has, isZombie, err := ctx.graph.HasChannelEdge(chanID1) + if err != nil { + t.Fatalf("error looking for edge: %v", chanID1) + } + if !has { + t.Fatalf("could not find edge in graph") + } + if isZombie { + t.Fatal("edge was marked as zombie") + } - has, isZombie, err = ctx.graph.HasChannelEdge(t.Context(), chanID2) - require.NoError(t, err) - require.True(t, has) - require.False(t, isZombie) + _, _, has, isZombie, err = ctx.graph.HasChannelEdge(chanID2) + if err != nil { + t.Fatalf("error looking for edge: %v", chanID2) + } + if !has { + t.Fatalf("could not find edge in graph") + } + if isZombie { + t.Fatal("edge was marked as zombie") + } // Create a 15 block fork. We first let the chainView notify the router // about stale blocks, before sending the now connected blocks. We do @@ -523,15 +580,27 @@ func TestDisconnectedBlocks(t *testing.T) { // chanID2 should not be in the database anymore, since it is not // confirmed on the longest chain. chanID1 should still be. - has, isZombie, err = ctx.graph.HasChannelEdge(t.Context(), chanID1) - require.NoError(t, err) - require.True(t, has) - require.False(t, isZombie) + _, _, has, isZombie, err = ctx.graph.HasChannelEdge(chanID1) + if err != nil { + t.Fatalf("error looking for edge: %v", chanID1) + } + if !has { + t.Fatalf("did not find edge in graph") + } + if isZombie { + t.Fatal("edge was marked as zombie") + } - has, isZombie, err = ctx.graph.HasChannelEdge(t.Context(), chanID2) - require.NoError(t, err) - require.False(t, has) - require.False(t, isZombie) + _, _, has, isZombie, err = ctx.graph.HasChannelEdge(chanID2) + if err != nil { + t.Fatalf("error looking for edge: %v", chanID2) + } + if has { + t.Fatalf("found edge in graph") + } + if isZombie { + t.Fatal("reorged edge should not be marked as zombie") + } } // TestChansClosedOfflinePruneGraph tests that if channels we know of are @@ -551,11 +620,12 @@ func TestChansClosedOfflinePruneGraph(t *testing.T) { Transactions: []*wire.MsgTx{}, } nextHeight := startingBlockHeight + 1 - script, fundingTx1, chanUTXO, chanID1 := createChannelEdge( - t, bitcoinKey1.SerializeCompressed(), + script, fundingTx1, chanUTXO, chanID1, err := createChannelEdge( + bitcoinKey1.SerializeCompressed(), bitcoinKey2.SerializeCompressed(), chanValue, uint32(nextHeight), ) + require.NoError(t, err, "unable create channel edge") block102.Transactions = append(block102.Transactions, fundingTx1) ctx.chain.addBlock(block102, uint32(nextHeight), rand.Uint32()) ctx.chain.setBestBlock(int32(nextHeight)) @@ -568,37 +638,40 @@ func TestChansClosedOfflinePruneGraph(t *testing.T) { node1 := createTestNode(t) node2 := createTestNode(t) - btcKey1 := route.NewVertex(bitcoinKey1) - btcKey2 := route.NewVertex(bitcoinKey2) - - proof := models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ) - - edge1, err := models.NewV1Channel( - chanID1.ToUint64(), *chaincfg.SimNetParams.GenesisHash, - node1.PubKeyBytes, node2.PubKeyBytes, &models.ChannelV1Fields{ - BitcoinKey1Bytes: btcKey1, - BitcoinKey2Bytes: btcKey2, - }, models.WithChanProof(proof), - models.WithCapacity(chanValue), - models.WithChannelPoint(*chanUTXO), - models.WithFundingScript(script), - ) - require.NoError(t, err) - - require.NoError(t, ctx.builder.AddEdge(ctxb, edge1)) + edge1 := &models.ChannelEdgeInfo{ + ChannelID: chanID1.ToUint64(), + NodeKey1Bytes: node1.PubKeyBytes, + NodeKey2Bytes: node2.PubKeyBytes, + AuthProof: &models.ChannelAuthProof{ + NodeSig1Bytes: testSig.Serialize(), + NodeSig2Bytes: testSig.Serialize(), + BitcoinSig1Bytes: testSig.Serialize(), + BitcoinSig2Bytes: testSig.Serialize(), + }, + ChannelPoint: *chanUTXO, + Capacity: chanValue, + Features: lnwire.EmptyFeatureVector(), + FundingScript: fn.Some(script), + } + copy(edge1.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) + copy(edge1.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + if err := ctx.builder.AddEdge(ctxb, edge1); err != nil { + t.Fatalf("unable to add edge: %v", err) + } // The router should now be aware of the channel we created above. - hasChan, isZombie, err := ctx.graph.HasChannelEdge( - t.Context(), chanID1.ToUint64(), + _, _, hasChan, isZombie, err := ctx.graph.HasChannelEdge( + chanID1.ToUint64(), ) - require.NoError(t, err) - require.True(t, hasChan) - require.False(t, isZombie) + if err != nil { + t.Fatalf("error looking for edge: %v", chanID1) + } + if !hasChan { + t.Fatalf("could not find edge in graph") + } + if isZombie { + t.Fatal("edge was marked as zombie") + } // With the transaction included, and the router's database state // updated, we'll now mine 5 additional blocks on top of it. @@ -617,10 +690,15 @@ func TestChansClosedOfflinePruneGraph(t *testing.T) { // At this point, our starting height should be 107. _, chainHeight, err := ctx.chain.GetBestBlock() require.NoError(t, err, "unable to get best block") - require.EqualValues(t, 107, chainHeight) + if chainHeight != 107 { + t.Fatalf("incorrect chain height: expected %v, got %v", + 107, chainHeight) + } // Next, we'll "shut down" the router in order to simulate downtime. - require.NoError(t, ctx.builder.Stop()) + if err := ctx.builder.Stop(); err != nil { + t.Fatalf("unable to shutdown router: %v", err) + } // While the router is "offline" we'll mine 5 additional blocks, with // the second block closing the channel we created above. @@ -652,7 +730,10 @@ func TestChansClosedOfflinePruneGraph(t *testing.T) { // At this point, our starting height should be 112. _, chainHeight, err = ctx.chain.GetBestBlock() require.NoError(t, err, "unable to get best block") - require.EqualValues(t, 112, chainHeight) + if chainHeight != 112 { + t.Fatalf("incorrect chain height: expected %v, got %v", + 112, chainHeight) + } // Now we'll re-start the ChannelRouter. It should recognize that it's // behind the main chain and prune all the blocks that it missed while @@ -661,12 +742,18 @@ func TestChansClosedOfflinePruneGraph(t *testing.T) { // At this point, the channel that was pruned should no longer be known // by the router. - hasChan, isZombie, err = ctx.graph.HasChannelEdge( - t.Context(), chanID1.ToUint64(), + _, _, hasChan, isZombie, err = ctx.graph.HasChannelEdge( + chanID1.ToUint64(), ) - require.NoError(t, err) - require.False(t, hasChan) - require.False(t, isZombie) + if err != nil { + t.Fatalf("error looking for edge: %v", chanID1) + } + if hasChan { + t.Fatalf("channel was found in graph but shouldn't have been") + } + if isZombie { + t.Fatal("closed channel should not be marked as zombie") + } } // TestPruneChannelGraphStaleEdges ensures that we properly prune stale edges @@ -765,7 +852,9 @@ func TestPruneChannelGraphStaleEdges(t *testing.T) { testGraph, err := createTestGraphFromChannels( t, true, testChannels, "a", ) - require.NoError(t, err) + if err != nil { + t.Fatalf("unable to create test graph: %v", err) + } const startingHeight = 100 ctx := createTestCtxFromGraphInstance( @@ -777,7 +866,9 @@ func TestPruneChannelGraphStaleEdges(t *testing.T) { // Proceed to prune the channels - only the last one should be // pruned. - require.NoError(t, ctx.builder.pruneZombieChans()) + if err := ctx.builder.pruneZombieChans(); err != nil { + t.Fatalf("unable to prune zombie channels: %v", err) + } // We expect channels that have either both edges stale, or one // edge stale with both known. @@ -926,7 +1017,9 @@ func testPruneChannelGraphDoubleDisabled(t *testing.T, assumeValid bool) { assertChannelsPruned(t, ctx.graph, testChannels, prunedChannel) } - require.NoError(t, ctx.builder.pruneZombieChans()) + if err := ctx.builder.pruneZombieChans(); err != nil { + t.Fatalf("unable to prune zombie channels: %v", err) + } // If we attempted to prune them without AssumeChannelValid being set, // none should be pruned. Otherwise the last channel should still be @@ -939,193 +1032,6 @@ func testPruneChannelGraphDoubleDisabled(t *testing.T, assumeValid bool) { } } -// TestIsPolicyZombie verifies that isPolicyZombie correctly classifies edge -// policies as stale or fresh for both gossip versions. -func TestIsPolicyZombie(t *testing.T) { - t.Parallel() - - const ( - pruneExpiry = time.Hour - currentHeight = uint32(1000) - ) - - // expiryBlocks is the number of blocks equivalent to pruneExpiry using - // the approximate block time. - expiryBlocks := uint32(pruneExpiry / avgBitcoinBlockTime) - - b := &Builder{ - cfg: &Config{ - ChannelPruneExpiry: pruneExpiry, - }, - } - b.bestHeight.Store(currentHeight) - - tests := []struct { - name string - policy *models.ChannelEdgePolicy - zombie bool - }{ - { - // A v1 policy updated half an expiry ago is fresh. - name: "v1 fresh", - policy: &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, - LastUpdate: time.Now().Add(-(pruneExpiry / 2)), - }, - zombie: false, - }, - { - // A v1 policy with a zero timestamp is stale. - name: "v1 stale", - policy: &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, - LastUpdate: time.Unix(0, 0), - }, - zombie: true, - }, - { - // A v2 policy updated one block before the - // expiry threshold is still fresh. - name: "v2 fresh", - policy: &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion2, - LastBlockHeight: currentHeight - - expiryBlocks + 1, - }, - zombie: false, - }, - { - // A v2 policy exactly at the expiry boundary - // is stale. - name: "v2 stale at boundary", - policy: &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion2, - LastBlockHeight: currentHeight - - expiryBlocks, - }, - zombie: true, - }, - { - // A v2 policy older than the expiry threshold - // is stale. - name: "v2 stale", - policy: &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion2, - LastBlockHeight: currentHeight - - expiryBlocks - 10, - }, - zombie: true, - }, - { - // A v2 policy with a future block height is - // never stale. - name: "v2 future block", - policy: &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion2, - LastBlockHeight: currentHeight + 1, - }, - zombie: false, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - require.Equal(t, tc.zombie, b.isPolicyZombie(tc.policy)) - }) - } -} - -// TestIsZombieChannel verifies that IsZombieChannel uses version-aware -// freshness checks and applies strict zombie pruning correctly. -func TestIsZombieChannel(t *testing.T) { - t.Parallel() - - const ( - pruneExpiry = time.Hour - currentHeight = uint32(1000) - ) - - tests := []struct { - name string - strictZombiePruning bool - info graphdb.ChannelUpdateInfo - zombie bool - }{ - { - name: "v1 both stale", - info: graphdb.NewV1ChannelUpdateInfo( - lnwire.ShortChannelID{}, - time.Now().Add(-2*pruneExpiry), - time.Now().Add(-2*pruneExpiry), - ), - zombie: true, - }, - { - name: "v1 one stale not strict", - info: graphdb.NewV1ChannelUpdateInfo( - lnwire.ShortChannelID{}, - time.Now().Add(-2*pruneExpiry), - time.Now(), - ), - zombie: false, - }, - { - name: "v1 one stale strict", - strictZombiePruning: true, - info: graphdb.NewV1ChannelUpdateInfo( - lnwire.ShortChannelID{}, - time.Now().Add(-2*pruneExpiry), - time.Now(), - ), - zombie: true, - }, - { - name: "v2 both stale", - info: graphdb.NewV2ChannelUpdateInfo( - lnwire.ShortChannelID{}, 987, 988, - ), - zombie: true, - }, - { - name: "v2 one stale not strict", - info: graphdb.NewV2ChannelUpdateInfo( - lnwire.ShortChannelID{}, 987, 995, - ), - zombie: false, - }, - { - name: "v2 one stale strict", - strictZombiePruning: true, - info: graphdb.NewV2ChannelUpdateInfo( - lnwire.ShortChannelID{}, 987, 995, - ), - zombie: true, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - strictPruning := test.strictZombiePruning - b := &Builder{ - cfg: &Config{ - ChannelPruneExpiry: pruneExpiry, - StrictZombiePruning: strictPruning, - }, - } - b.bestHeight.Store(currentHeight) - - require.Equal( - t, test.zombie, - b.IsZombieChannel(test.info), - ) - }) - } -} - // TestIsStaleNode tests that the IsStaleNode method properly detects stale // node announcements. func TestIsStaleNode(t *testing.T) { @@ -1144,54 +1050,66 @@ func TestIsStaleNode(t *testing.T) { copy(pub1[:], priv1.PubKey().SerializeCompressed()) copy(pub2[:], priv2.PubKey().SerializeCompressed()) - script, fundingTx, _, chanID := createChannelEdge( - t, bitcoinKey1.SerializeCompressed(), + script, fundingTx, _, chanID, err := createChannelEdge( + bitcoinKey1.SerializeCompressed(), bitcoinKey2.SerializeCompressed(), 10000, 500, ) + require.NoError(t, err, "unable to create channel edge") fundingBlock := &wire.MsgBlock{ Transactions: []*wire.MsgTx{fundingTx}, } ctx.chain.addBlock(fundingBlock, chanID.BlockHeight, chanID.BlockHeight) - edge, err := models.NewV1Channel( - chanID.ToUint64(), *chaincfg.SimNetParams.GenesisHash, - pub1, pub2, &models.ChannelV1Fields{ - BitcoinKey1Bytes: pub1, - BitcoinKey2Bytes: pub2, - }, models.WithFundingScript(script), - ) - require.NoError(t, err) - - require.NoError(t, ctx.builder.AddEdge(ctxb, edge)) + edge := &models.ChannelEdgeInfo{ + ChannelID: chanID.ToUint64(), + NodeKey1Bytes: pub1, + NodeKey2Bytes: pub2, + BitcoinKey1Bytes: pub1, + BitcoinKey2Bytes: pub2, + AuthProof: nil, + Features: lnwire.EmptyFeatureVector(), + FundingScript: fn.Some(script), + } + if err := ctx.builder.AddEdge(ctxb, edge); err != nil { + t.Fatalf("unable to add edge: %v", err) + } // Before we add the node, if we query for staleness, we should get // false, as we haven't added the full node. updateTimeStamp := time.Unix(123, 0) - require.False(t, ctx.builder.IsStaleNode(ctxb, pub1, updateTimeStamp)) + if ctx.builder.IsStaleNode(ctxb, pub1, updateTimeStamp) { + t.Fatalf("incorrectly detected node as stale") + } // With the node stub in the database, we'll add the fully node // announcement to the database. - n1 := models.NewV1Node( - route.NewVertex(priv1.PubKey()), &models.NodeV1Fields{ - LastUpdate: updateTimeStamp, - Addresses: testAddrs, - Color: color.RGBA{1, 2, 3, 0}, - Alias: "node11", - AuthSigBytes: testSig.Serialize(), - Features: testFeatures.RawFeatureVector, - }, - ) - require.NoError(t, ctx.builder.AddNode(t.Context(), n1)) + n1 := &models.Node{ + HaveNodeAnnouncement: true, + LastUpdate: updateTimeStamp, + Addresses: testAddrs, + Color: color.RGBA{1, 2, 3, 0}, + Alias: "node11", + AuthSigBytes: testSig.Serialize(), + Features: testFeatures, + } + copy(n1.PubKeyBytes[:], priv1.PubKey().SerializeCompressed()) + if err := ctx.builder.AddNode(t.Context(), n1); err != nil { + t.Fatalf("could not add node: %v", err) + } // If we use the same timestamp and query for staleness, we should get // true. - require.True(t, ctx.builder.IsStaleNode(ctxb, pub1, updateTimeStamp)) + if !ctx.builder.IsStaleNode(ctxb, pub1, updateTimeStamp) { + t.Fatalf("failure to detect stale node update") + } // If we update the timestamp and once again query for staleness, it // should report false. newTimeStamp := time.Unix(1234, 0) - require.False(t, ctx.builder.IsStaleNode(ctxb, pub1, newTimeStamp)) + if ctx.builder.IsStaleNode(ctxb, pub1, newTimeStamp) { + t.Fatalf("incorrectly detected node as stale") + } } // TestIsKnownEdge tests that the IsKnownEdge method properly detects stale @@ -1212,31 +1130,36 @@ func TestIsKnownEdge(t *testing.T) { copy(pub1[:], priv1.PubKey().SerializeCompressed()) copy(pub2[:], priv2.PubKey().SerializeCompressed()) - script, fundingTx, _, chanID := createChannelEdge( - t, bitcoinKey1.SerializeCompressed(), + script, fundingTx, _, chanID, err := createChannelEdge( + bitcoinKey1.SerializeCompressed(), bitcoinKey2.SerializeCompressed(), 10000, 500, ) + require.NoError(t, err, "unable to create channel edge") fundingBlock := &wire.MsgBlock{ Transactions: []*wire.MsgTx{fundingTx}, } ctx.chain.addBlock(fundingBlock, chanID.BlockHeight, chanID.BlockHeight) - edge, err := models.NewV1Channel( - chanID.ToUint64(), *chaincfg.SimNetParams.GenesisHash, pub1, - pub2, &models.ChannelV1Fields{ - BitcoinKey1Bytes: pub1, - BitcoinKey2Bytes: pub2, - }, - models.WithFundingScript(script), - ) - require.NoError(t, err) - - require.NoError(t, ctx.builder.AddEdge(ctxb, edge)) + edge := &models.ChannelEdgeInfo{ + ChannelID: chanID.ToUint64(), + NodeKey1Bytes: pub1, + NodeKey2Bytes: pub2, + BitcoinKey1Bytes: pub1, + BitcoinKey2Bytes: pub2, + AuthProof: nil, + FundingScript: fn.Some(script), + Features: lnwire.EmptyFeatureVector(), + } + if err := ctx.builder.AddEdge(ctxb, edge); err != nil { + t.Fatalf("unable to add edge: %v", err) + } // Now that the edge has been inserted, query is the router already // knows of the edge should return true. - require.True(t, ctx.builder.IsKnownEdge(*chanID)) + if !ctx.builder.IsKnownEdge(*chanID) { + t.Fatalf("router should detect edge as known") + } } // TestIsStaleEdgePolicy tests that the IsStaleEdgePolicy properly detects @@ -1257,11 +1180,12 @@ func TestIsStaleEdgePolicy(t *testing.T) { copy(pub1[:], priv1.PubKey().SerializeCompressed()) copy(pub2[:], priv2.PubKey().SerializeCompressed()) - script, fundingTx, _, chanID := createChannelEdge( - t, bitcoinKey1.SerializeCompressed(), + script, fundingTx, _, chanID, err := createChannelEdge( + bitcoinKey1.SerializeCompressed(), bitcoinKey2.SerializeCompressed(), 10000, 500, ) + require.NoError(t, err, "unable to create channel edge") fundingBlock := &wire.MsgBlock{ Transactions: []*wire.MsgTx{fundingTx}, } @@ -1270,31 +1194,29 @@ func TestIsStaleEdgePolicy(t *testing.T) { // If we query for staleness before adding the edge, we should get // false. updateTimeStamp := time.Unix(123, 0) - require.False( - t, ctx.builder.IsStaleEdgePolicy( - *chanID, updateTimeStamp, 0, - ), - ) - require.False( - t, ctx.builder.IsStaleEdgePolicy( - *chanID, updateTimeStamp, 1, - ), - ) + if ctx.builder.IsStaleEdgePolicy(*chanID, updateTimeStamp, 0) { + t.Fatalf("router failed to detect fresh edge policy") + } + if ctx.builder.IsStaleEdgePolicy(*chanID, updateTimeStamp, 1) { + t.Fatalf("router failed to detect fresh edge policy") + } - edge, err := models.NewV1Channel( - chanID.ToUint64(), *chaincfg.SimNetParams.GenesisHash, pub1, - pub2, &models.ChannelV1Fields{ - BitcoinKey1Bytes: pub1, - BitcoinKey2Bytes: pub2, - }, models.WithFundingScript(script), - ) - require.NoError(t, err) - - require.NoError(t, ctx.builder.AddEdge(ctxb, edge)) + edge := &models.ChannelEdgeInfo{ + ChannelID: chanID.ToUint64(), + NodeKey1Bytes: pub1, + NodeKey2Bytes: pub2, + BitcoinKey1Bytes: pub1, + BitcoinKey2Bytes: pub2, + AuthProof: nil, + Features: lnwire.EmptyFeatureVector(), + FundingScript: fn.Some(script), + } + if err := ctx.builder.AddEdge(ctxb, edge); err != nil { + t.Fatalf("unable to add edge: %v", err) + } // We'll also add two edge policies, one for each direction. edgePolicy := &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: edge.ChannelID, LastUpdate: updateTimeStamp, @@ -1304,10 +1226,11 @@ func TestIsStaleEdgePolicy(t *testing.T) { FeeProportionalMillionths: 10000, } edgePolicy.ChannelFlags = 0 - require.NoError(t, ctx.builder.UpdateEdge(ctxb, edgePolicy)) + if err := ctx.builder.UpdateEdge(ctxb, edgePolicy); err != nil { + t.Fatalf("unable to update edge policy: %v", err) + } edgePolicy = &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: edge.ChannelID, LastUpdate: updateTimeStamp, @@ -1317,34 +1240,28 @@ func TestIsStaleEdgePolicy(t *testing.T) { FeeProportionalMillionths: 10000, } edgePolicy.ChannelFlags = 1 - require.NoError(t, ctx.builder.UpdateEdge(ctxb, edgePolicy)) + if err := ctx.builder.UpdateEdge(ctxb, edgePolicy); err != nil { + t.Fatalf("unable to update edge policy: %v", err) + } // Now that the edges have been added, an identical (chanID, flag, // timestamp) tuple for each edge should be detected as a stale edge. - require.True( - t, ctx.builder.IsStaleEdgePolicy( - *chanID, updateTimeStamp, 0, - ), - ) - require.True( - t, ctx.builder.IsStaleEdgePolicy( - *chanID, updateTimeStamp, 1, - ), - ) + if !ctx.builder.IsStaleEdgePolicy(*chanID, updateTimeStamp, 0) { + t.Fatalf("router failed to detect stale edge policy") + } + if !ctx.builder.IsStaleEdgePolicy(*chanID, updateTimeStamp, 1) { + t.Fatalf("router failed to detect stale edge policy") + } // If we now update the timestamp for both edges, the router should // detect that this tuple represents a fresh edge. updateTimeStamp = time.Unix(9999, 0) - require.False( - t, ctx.builder.IsStaleEdgePolicy( - *chanID, updateTimeStamp, 0, - ), - ) - require.False( - t, ctx.builder.IsStaleEdgePolicy( - *chanID, updateTimeStamp, 1, - ), - ) + if ctx.builder.IsStaleEdgePolicy(*chanID, updateTimeStamp, 0) { + t.Fatalf("router failed to detect fresh edge policy") + } + if ctx.builder.IsStaleEdgePolicy(*chanID, updateTimeStamp, 1) { + t.Fatalf("router failed to detect fresh edge policy") + } } // TestBlockDifferenceFix tests if when the router is behind on blocks, the @@ -1484,16 +1401,15 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( return nil, err } - pubKey, err := route.NewVertexFromBytes(pubBytes) - require.NoError(t, err) - - dbNode := models.NewV1Node(pubKey, &models.NodeV1Fields{ - AuthSigBytes: testSig.Serialize(), - LastUpdate: testTime, - Addresses: testAddrs, - Alias: node.Alias, - Features: testFeatures.RawFeatureVector, - }) + dbNode := &models.Node{ + HaveNodeAnnouncement: true, + AuthSigBytes: testSig.Serialize(), + LastUpdate: testTime, + Addresses: testAddrs, + Alias: node.Alias, + Features: testFeatures, + } + copy(dbNode.PubKeyBytes[:], pubBytes) // We require all aliases within the graph to be unique for our // tests. @@ -1596,28 +1512,19 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( // We first insert the existence of the edge between the two // nodes. - var node1Vertex, node2Vertex route.Vertex - copy(node1Vertex[:], node1Bytes) - copy(node2Vertex[:], node2Bytes) - - var btcKey1, btcKey2 route.Vertex - copy(btcKey1[:], node1Bytes) - copy(btcKey2[:], node2Bytes) - - edgeInfo, err := models.NewV1Channel( - edge.ChannelID, *chaincfg.SimNetParams.GenesisHash, - node1Vertex, node2Vertex, &models.ChannelV1Fields{ - BitcoinKey1Bytes: btcKey1, - BitcoinKey2Bytes: btcKey2, - }, - models.WithChanProof(&testAuthProof), - models.WithChannelPoint(fundingPoint), - models.WithCapacity(btcutil.Amount(edge.Capacity)), - ) - if err != nil { - return nil, err + edgeInfo := models.ChannelEdgeInfo{ + ChannelID: edge.ChannelID, + AuthProof: &testAuthProof, + ChannelPoint: fundingPoint, + Capacity: btcutil.Amount(edge.Capacity), + Features: lnwire.EmptyFeatureVector(), } + copy(edgeInfo.NodeKey1Bytes[:], node1Bytes) + copy(edgeInfo.NodeKey2Bytes[:], node2Bytes) + copy(edgeInfo.BitcoinKey1Bytes[:], node1Bytes) + copy(edgeInfo.BitcoinKey2Bytes[:], node2Bytes) + shortID := lnwire.NewShortChanIDFromInt(edge.ChannelID) links[shortID] = &mockLink{ bandwidth: lnwire.MilliSatoshi( @@ -1625,7 +1532,7 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( ), } - err = graph.AddChannelEdge(ctx, edgeInfo) + err = graph.AddChannelEdge(ctx, &edgeInfo) if err != nil && !errors.Is(err, graphdb.ErrEdgeAlreadyExist) { return nil, err } @@ -1638,7 +1545,6 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( } edgePolicy := &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), MessageFlags: lnwire.ChanUpdateMsgFlags( edge.MessageFlags, @@ -1666,12 +1572,12 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( } // We also store the channel IDs info for each of the node. - node1Vertex, err = route.NewVertexFromBytes(node1Bytes) + node1Vertex, err := route.NewVertexFromBytes(node1Bytes) if err != nil { return nil, err } - node2Vertex, err = route.NewVertexFromBytes(node2Bytes) + node2Vertex, err := route.NewVertexFromBytes(node2Bytes) if err != nil { return nil, err } @@ -1688,9 +1594,7 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( } return &testGraphInstance{ - graph: graphdb.NewVersionedGraph( - graph, lnwire.GossipVersion1, - ), + graph: graph, aliasMap: aliasMap, privKeyMap: privKeyMap, channelIDs: channelIDs, @@ -1785,7 +1689,7 @@ func asymmetricTestChannel(alias1, alias2 string, capacity btcutil.Amount, // assertChannelsPruned ensures that only the given channels are pruned from the // graph out of the set of all channels. -func assertChannelsPruned(t *testing.T, graph *graphdb.VersionedGraph, +func assertChannelsPruned(t *testing.T, graph *graphdb.ChannelGraph, channels []*testChannel, prunedChanIDs ...uint64) { t.Helper() @@ -1797,28 +1701,30 @@ func assertChannelsPruned(t *testing.T, graph *graphdb.VersionedGraph, for _, channel := range channels { _, shouldPrune := pruned[channel.ChannelID] - exists, isZombie, err := graph.HasChannelEdge( - t.Context(), channel.ChannelID, + _, _, exists, isZombie, err := graph.HasChannelEdge( + channel.ChannelID, ) - require.NoError(t, err) - if shouldPrune { - require.Falsef(t, exists, - "expected channel=%v to not exist within "+ - "the graph", - channel.ChannelID) - require.Truef(t, isZombie, - "expected channel=%v to be marked as zombie", - channel.ChannelID) - - continue + if err != nil { + t.Fatalf("unable to determine existence of "+ + "channel=%v in the graph: %v", + channel.ChannelID, err) + } + if !shouldPrune && !exists { + t.Fatalf("expected channel=%v to exist within "+ + "the graph", channel.ChannelID) + } + if shouldPrune && exists { + t.Fatalf("expected channel=%v to not exist "+ + "within the graph", channel.ChannelID) + } + if !shouldPrune && isZombie { + t.Fatalf("expected channel=%v to not be marked "+ + "as zombie", channel.ChannelID) + } + if shouldPrune && !isZombie { + t.Fatalf("expected channel=%v to be marked as "+ + "zombie", channel.ChannelID) } - - require.Truef(t, exists, - "expected channel=%v to exist within the graph", - channel.ChannelID) - require.Falsef(t, isZombie, - "expected channel=%v to not be marked as zombie", - channel.ChannelID) } } @@ -1881,15 +1787,16 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, features = lnwire.EmptyFeatureVector() } - dbNode := models.NewV1Node( - route.NewVertex(pubKey), &models.NodeV1Fields{ - AuthSigBytes: testSig.Serialize(), - LastUpdate: testTime, - Addresses: testAddrs, - Alias: alias, - Features: features.RawFeatureVector, - }, - ) + dbNode := &models.Node{ + HaveNodeAnnouncement: true, + AuthSigBytes: testSig.Serialize(), + LastUpdate: testTime, + Addresses: testAddrs, + Alias: alias, + Features: features, + } + + copy(dbNode.PubKeyBytes[:], pubKey.SerializeCompressed()) privKeyMap[alias] = privKey @@ -1975,21 +1882,20 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, // We first insert the existence of the edge between the two // nodes. - edgeInfo, err := models.NewV1Channel( - channelID, *chaincfg.SimNetParams.GenesisHash, - node1Vertex, node2Vertex, &models.ChannelV1Fields{ - BitcoinKey1Bytes: node1Vertex, - BitcoinKey2Bytes: node2Vertex, - }, - models.WithChanProof(&testAuthProof), - models.WithChannelPoint(*fundingPoint), - models.WithCapacity(testChannel.Capacity), - ) - if err != nil { - return nil, err + edgeInfo := models.ChannelEdgeInfo{ + ChannelID: channelID, + AuthProof: &testAuthProof, + ChannelPoint: *fundingPoint, + Capacity: testChannel.Capacity, + + NodeKey1Bytes: node1Vertex, + BitcoinKey1Bytes: node1Vertex, + NodeKey2Bytes: node2Vertex, + BitcoinKey2Bytes: node2Vertex, + Features: lnwire.EmptyFeatureVector(), } - err = graph.AddChannelEdge(ctx, edgeInfo) + err = graph.AddChannelEdge(ctx, &edgeInfo) if err != nil && !errors.Is(err, graphdb.ErrEdgeAlreadyExist) { @@ -2019,9 +1925,7 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, channelFlags |= lnwire.ChanUpdateDisabled } - //nolint:ll edgePolicy := &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), MessageFlags: msgFlags, ChannelFlags: channelFlags, @@ -2052,9 +1956,7 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, } channelFlags |= lnwire.ChanUpdateDirection - //nolint:ll edgePolicy := &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), MessageFlags: msgFlags, ChannelFlags: channelFlags, @@ -2074,13 +1976,11 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, } } - channelID++ //nolint:ineffassign,wastedassign + channelID++ //nolint:ineffassign } return &testGraphInstance{ - graph: graphdb.NewVersionedGraph( - graph, lnwire.GossipVersion1, - ), + graph: graph, aliasMap: aliasMap, privKeyMap: privKeyMap, links: links, diff --git a/graph/db/addr.go b/graph/db/addr.go index e06665d15..836d516b0 100644 --- a/graph/db/addr.go +++ b/graph/db/addr.go @@ -99,8 +99,7 @@ func encodeTCPAddr(w io.Writer, addr *net.TCPAddr) error { } // encodeOnionAddr serializes an onion address into its compact raw bytes -// representation. v2 round-trips for wire fidelity even though lnd no longer -// produces it. +// representation. func encodeOnionAddr(w io.Writer, addr *tor.OnionAddr) error { var suffixIndex int hostLen := len(addr.OnionService) diff --git a/graph/db/addr_test.go b/graph/db/addr_test.go index 4e0e53d72..d3c3700a3 100644 --- a/graph/db/addr_test.go +++ b/graph/db/addr_test.go @@ -3,6 +3,7 @@ package graphdb import ( "bytes" "net" + "strings" "testing" "github.com/lightningnetwork/lnd/lnwire" @@ -147,15 +148,29 @@ func TestAddrSerialization(t *testing.T) { var b bytes.Buffer for _, test := range addrTests { err := SerializeAddr(&b, test.expAddr) - if test.serErr != "" { - require.Error(t, err) - require.ErrorContains(t, err, test.serErr) + switch { + case err == nil && test.serErr != "": + t.Fatalf("expected serialization err for addr %v", + test.expAddr) + + case err != nil && test.serErr == "": + t.Fatalf("unexpected serialization err for addr %v: %v", + test.expAddr, err) + + case err != nil && !strings.Contains(err.Error(), test.serErr): + t.Fatalf("unexpected serialization err for addr %v, "+ + "want: %v, got %v", test.expAddr, test.serErr, + err) + + case err != nil: continue } - require.NoError(t, err) addr, err := DeserializeAddr(&b) - require.NoError(t, err) + if err != nil { + t.Fatalf("unable to deserialize address: %v", err) + } + require.Equal(t, test.expAddr, addr) } } diff --git a/graph/db/benchmark_test.go b/graph/db/benchmark_test.go index 4b6bf201d..7c94db2d4 100644 --- a/graph/db/benchmark_test.go +++ b/graph/db/benchmark_test.go @@ -5,12 +5,15 @@ import ( "database/sql" "errors" "fmt" + "net" + "os" "path" "sync" "testing" "time" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btclog/v2" "github.com/lightningnetwork/lnd/batch" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" @@ -71,19 +74,19 @@ var ( // and a function to open the connection. type dbConnection struct { name string - open func(testing.TB) Store + open func(testing.TB) V1Store } // This var block defines the various database connections that we will use // for testing. Each connection is defined as a dbConnection struct that // contains a name and an open function. The open function is used to create -// a new Store instance for the given database type. +// a new V1Store instance for the given database type. var ( // kvdbBBoltConn is a connection to a kvdb-bbolt database called // channel.db. kvdbBBoltConn = dbConnection{ name: "kvdb-bbolt", - open: func(b testing.TB) Store { + open: func(b testing.TB) V1Store { return connectBBoltDB(b, bboltDBPath, kvdbBBoltFile) }, } @@ -92,7 +95,7 @@ var ( // channel.sqlite. kvdbSqliteConn = dbConnection{ name: "kvdb-sqlite", - open: func(b testing.TB) Store { + open: func(b testing.TB) V1Store { return connectKVDBSqlite( b, kvdbSqlitePath, kvdbSqliteFile, ) @@ -103,7 +106,7 @@ var ( // called lnd.sqlite. nativeSQLSqliteConn = dbConnection{ name: "native-sqlite", - open: func(b testing.TB) Store { + open: func(b testing.TB) V1Store { return connectNativeSQLite( b, sqldb.DefaultSQLiteConfig(), nativeSQLSqlitePath, nativeSQLSqliteFile, @@ -115,7 +118,7 @@ var ( // using a postgres connection string. kvdbPostgresConn = dbConnection{ name: "kvdb-postgres", - open: func(b testing.TB) Store { + open: func(b testing.TB) V1Store { return connectKVDBPostgres(b, kvdbPostgresDNS) }, } @@ -124,7 +127,7 @@ var ( // database using a postgres connection string. nativeSQLPostgresConn = dbConnection{ name: "native-postgres", - open: func(b testing.TB) Store { + open: func(b testing.TB) V1Store { return connectNativePostgres( b, sqldb.DefaultPostgresConfig(), nativeSQLPostgresDNS, @@ -133,10 +136,10 @@ var ( } ) -// connectNativePostgres creates a Store instance backed by a native Postgres +// connectNativePostgres creates a V1Store instance backed by a native Postgres // database for testing purposes. func connectNativePostgres(t testing.TB, cfg *sqldb.QueryConfig, - dsn string) Store { + dsn string) V1Store { return newSQLStore(t, cfg, sqlPostgres(t, dsn)) } @@ -156,10 +159,10 @@ func sqlPostgres(t testing.TB, dsn string) BatchedSQLQueries { return newSQLExecutor(t, store) } -// connectNativeSQLite creates a Store instance backed by a native SQLite +// connectNativeSQLite creates a V1Store instance backed by a native SQLite // database for testing purposes. func connectNativeSQLite(t testing.TB, cfg *sqldb.QueryConfig, dbPath, - file string) Store { + file string) V1Store { return newSQLStore(t, cfg, sqlSQLite(t, dbPath, file)) } @@ -204,9 +207,9 @@ func kvdbPostgres(t testing.TB, dsn string) kvdb.Backend { return kvStore } -// connectKVDBPostgres creates a Store instance backed by a kvdb-postgres +// connectKVDBPostgres creates a V1Store instance backed by a kvdb-postgres // database for testing purposes. -func connectKVDBPostgres(t testing.TB, dsn string) Store { +func connectKVDBPostgres(t testing.TB, dsn string) V1Store { return newKVStore(t, kvdbPostgres(t, dsn)) } @@ -230,14 +233,14 @@ func kvdbSqlite(t testing.TB, dbPath, fileName string) kvdb.Backend { return kvStore } -// connectKVDBSqlite creates a Store instance backed by a kvdb-sqlite +// connectKVDBSqlite creates a V1Store instance backed by a kvdb-sqlite // database for testing purposes. -func connectKVDBSqlite(t testing.TB, dbPath, fileName string) Store { +func connectKVDBSqlite(t testing.TB, dbPath, fileName string) V1Store { return newKVStore(t, kvdbSqlite(t, dbPath, fileName)) } // connectBBoltDB creates a new BBolt database connection for testing. -func connectBBoltDB(t testing.TB, dbPath, fileName string) Store { +func connectBBoltDB(t testing.TB, dbPath, fileName string) V1Store { return newKVStore(t, kvdbBBolt(t, dbPath, fileName)) } @@ -260,7 +263,7 @@ func kvdbBBolt(t testing.TB, dbPath, fileName string) kvdb.Backend { // newKVStore creates a new KVStore instance for testing using a provided // kvdb.Backend instance. -func newKVStore(t testing.TB, backend kvdb.Backend) Store { +func newKVStore(t testing.TB, backend kvdb.Backend) V1Store { store, err := NewKVStore(backend, testStoreOptions...) require.NoError(t, err) @@ -285,7 +288,7 @@ func newSQLExecutor(t testing.TB, db sqldb.DB) BatchedSQLQueries { // newSQLStore creates a new SQLStore instance for testing using a provided // sqldb.DB instance. func newSQLStore(t testing.TB, cfg *sqldb.QueryConfig, - db BatchedSQLQueries) Store { + db BatchedSQLQueries) V1Store { store, err := NewSQLStore( &SQLStoreConfig{ @@ -347,10 +350,8 @@ func TestPopulateDBs(t *testing.T) { // graph. countNodes := func(graph *ChannelGraph) int { numNodes := 0 - v1Graph := NewVersionedGraph(graph, lnwire.GossipVersion1) - err := v1Graph.ForEachNode( - ctx, - func(node *models.Node) error { + err := graph.ForEachNode( + ctx, func(node *models.Node) error { numNodes++ return nil @@ -371,8 +372,8 @@ func TestPopulateDBs(t *testing.T) { numPolicies = 0 ) err := graph.ForEachChannel( - ctx, lnwire.GossipVersion1, - func(info *models.ChannelEdgeInfo, policy, + ctx, func(info *models.ChannelEdgeInfo, + policy, policy2 *models.ChannelEdgePolicy) error { numChans++ @@ -429,6 +430,137 @@ func TestPopulateDBs(t *testing.T) { } } +// TestPopulateViaMigration is a helper test that can be used to populate a +// local native SQL graph from a kvdbgraph using the migration logic. +// +// NOTE: the testPostgres variable can be set to true to test with a +// postgres backend instead of the kvdb-sqlite backend. +// +// NOTE: you will need to set the following build tags in order to run this +// test: +// +// test_native_sql +// kvdb_sqlite // If your source is kvdb-sqlite +// kvdb_postgres // If your source is kvdb-postgres +// +// NOTE: this is a helper test and is not run by default. +func TestPopulateViaMigration(t *testing.T) { + // ======= STEP 0 =========== + // Comment out this SKipf line. + t.Skipf("Skipping local helper test") + + const ( + srcBBolt = "kvdb-bbolt" + srcSQLite = "kvdb-sqlite" + srcPostgres = "kvdb-postgres" + ) + + // ======= STEP 1 =========== + // Set your chosen SOURCE type by uncommenting the corresponding line + // below. By default, a kvdb-sqlite source is chosen. + srcDB := srcSQLite + // srcDB := srcBBolt + // srcDB := srcPostgres + + // ======= STEP 2 ============ + // Set this variable to the correct genesis hash of the source + // DB. By default, mainnet is assumed. + chain := *chaincfg.MainNetParams.GenesisHash + + // ======= STEP 3 (ignore if source is postgres) ============== + // If your source destination is bbolt or sqlite, then set this to the + // path where your source database can be found. + const sourceDBPath = "testdata" + + // ======= STEP 4 (only if source is bbolt!) ============ + // If your source destination is bbolt, then set this to the name of + // the bbolt file that contains the channel graph data. + const sourceBBoltName = "channel.db" + + // ======= STEP 5 (only if source is sqlite!) ============ + // If your source destination is sqlite, then set this to the name of + // the sqlite file that contains the channel graph data. + const sourceSQLiteName = "channel.sqlite" + + // ======= STEP 6 (only if source is postgres!) ============ + // Set the DNS of your kvdb postgres instance below. This should be the + // same as what you have set in the config of the LND node that + // populated the instance (ie, whatever your --db.postgres.dsn is set + // to). + const kvdbPostgresDNS = "postgres://user@host/db_name" + + // ======== STEP 7 ======================== + // Finally, pick your destination DB! You can choose either SQLite or + // Postgres. + testSQLite := true + + // ======== STEP 8 (only if destination is sqlite) ======== + // Set the path where you want to create the destination SQLite + // database. This should be a directory that exists and is writable. + const destSQLitePath = "testdata" + + // ======== STEP 9 (only if destination is sqlite) ======== + // Pick a name for your destination SQLite database file. + // NOTE: if you run this test again, delete the previously created + // file first. + const destSQLiteFile = "lnd-graph-test.sqlite" + + // ======== STEP 10 (only if destination is postgres) ======== + // NB: this has some additional steps: + // 1. First, connect to your destination postgres instance: example: + // $ psql -U ellemouton -d postgres + // 2. Now, create the test database: + // CREATE DATABASE graphtest; + // NOTE: if you restart this test for postgres, it helps to first drop + // the new database & recreate it. + // NOTE: the database name that you use above must be whatever you will + // use in the DNS you set below. + const sqlPostgresDNS = "postgres://user@host/graphtest" + + // ======= YOUR WORK IS DONE ============= + + // Connect to source database. + var srcKVDB kvdb.Backend + switch srcDB { + case srcBBolt: + srcKVDB = kvdbBBolt(t, sourceDBPath, sourceBBoltName) + case srcSQLite: + srcKVDB = kvdbSqlite(t, sourceDBPath, sourceSQLiteName) + case srcPostgres: + srcKVDB = kvdbPostgres(t, kvdbPostgresDNS) + default: + t.Fatalf("Unsupported source database backend: %s", srcDB) + } + + // Connect to destination database. + cfg := sqldb.DefaultSQLiteConfig() + dstSQL := sqlSQLite(t, destSQLitePath, destSQLiteFile) + if !testSQLite { + cfg = sqldb.DefaultPostgresConfig() + dstSQL = sqlPostgres(t, sqlPostgresDNS) + } + + // Set up a logger so we can see the migration progress. + logger := btclog.NewDefaultHandler(os.Stdout) + UseLogger(btclog.NewSLogger(logger)) + log.SetLevel(btclog.LevelDebug) + + // Use the graph migration to populate the SQL graph from the + // kvdb graph. + ctx := t.Context() + err := dstSQL.ExecTx( + ctx, sqldb.WriteTxOpt(), func(queries SQLQueries) error { + return MigrateGraphToSQL( + ctx, &SQLStoreConfig{ + QueryCfg: cfg, + ChainHash: chain, + }, srcKVDB, queries, + ) + }, func() {}, + ) + require.NoError(t, err) +} + // syncGraph synchronizes the source graph with the destination graph by // copying all nodes and channels from the source to the destination. func syncGraph(t *testing.T, src, dest *ChannelGraph) { @@ -456,8 +588,7 @@ func syncGraph(t *testing.T, src, dest *ChannelGraph) { } var wgNodes sync.WaitGroup - v1Src := NewVersionedGraph(src, lnwire.GossipVersion1) - err := v1Src.ForEachNode(ctx, func(node *models.Node) error { + err := src.ForEachNode(ctx, func(node *models.Node) error { wgNodes.Add(1) go func() { defer wgNodes.Done() @@ -499,49 +630,48 @@ func syncGraph(t *testing.T, src, dest *ChannelGraph) { } var wgChans sync.WaitGroup - err = src.ForEachChannel(ctx, lnwire.GossipVersion1, - func(info *models.ChannelEdgeInfo, - policy1, policy2 *models.ChannelEdgePolicy) error { + err = src.ForEachChannel(ctx, func(info *models.ChannelEdgeInfo, + policy1, policy2 *models.ChannelEdgePolicy) error { - // Add each channel & policy. We do this in a goroutine - // to take advantage of batch processing. - wgChans.Add(1) - go func() { - defer wgChans.Done() + // Add each channel & policy. We do this in a goroutine to + // take advantage of batch processing. + wgChans.Add(1) + go func() { + defer wgChans.Done() - err := dest.AddChannelEdge( - ctx, info, batch.LazyAdd(), + err := dest.AddChannelEdge( + ctx, info, batch.LazyAdd(), + ) + if !errors.Is(err, ErrEdgeAlreadyExist) { + require.NoError(t, err) + } + + if policy1 != nil { + err = dest.UpdateEdgePolicy( + ctx, policy1, batch.LazyAdd(), ) - if !errors.Is(err, ErrEdgeAlreadyExist) { - require.NoError(t, err) - } + require.NoError(t, err) + } - if policy1 != nil { - err = dest.UpdateEdgePolicy( - ctx, policy1, batch.LazyAdd(), - ) - require.NoError(t, err) - } + if policy2 != nil { + err = dest.UpdateEdgePolicy( + ctx, policy2, batch.LazyAdd(), + ) + require.NoError(t, err) + } - if policy2 != nil { - err = dest.UpdateEdgePolicy( - ctx, policy2, batch.LazyAdd(), - ) - require.NoError(t, err) - } + mu.Lock() + total++ + chunk++ + s.Do(func() { + reportChanStats() + chunk = 0 + }) + mu.Unlock() + }() - mu.Lock() - total++ - chunk++ - s.Do(func() { - reportChanStats() - chunk = 0 - }) - mu.Unlock() - }() - - return nil - }, func() {}) + return nil + }, func() {}) require.NoError(t, err) wgChans.Wait() @@ -590,7 +720,7 @@ func BenchmarkCacheLoading(b *testing.B) { } } -// BenchmarkGraphReadMethods benchmarks various read calls of various Store +// BenchmarkGraphReadMethods benchmarks various read calls of various V1Store // implementations. // // NOTE: this is to be run against a local graph database. It can be run @@ -617,13 +747,13 @@ func BenchmarkGraphReadMethods(b *testing.B) { tests := []struct { name string - fn func(b testing.TB, store Store) + fn func(b testing.TB, store V1Store) }{ { name: "ForEachNode", - fn: func(b testing.TB, store Store) { + fn: func(b testing.TB, store V1Store) { err := store.ForEachNode( - ctx, lnwire.GossipVersion1, + ctx, func(_ *models.Node) error { // Increment the counter to // ensure the callback is doing @@ -638,11 +768,10 @@ func BenchmarkGraphReadMethods(b *testing.B) { }, { name: "ForEachChannel", - fn: func(b testing.TB, store Store) { + fn: func(b testing.TB, store V1Store) { //nolint:ll err := store.ForEachChannel( - ctx, lnwire.GossipVersion1, - func(_ *models.ChannelEdgeInfo, + ctx, func(_ *models.ChannelEdgeInfo, _ *models.ChannelEdgePolicy, _ *models.ChannelEdgePolicy) error { @@ -659,15 +788,9 @@ func BenchmarkGraphReadMethods(b *testing.B) { }, { name: "NodeUpdatesInHorizon", - fn: func(b testing.TB, store Store) { + fn: func(b testing.TB, store V1Store) { iter := store.NodeUpdatesInHorizon( - ctx, lnwire.GossipVersion1, - NodeUpdateRange{ - StartTime: fn.Some( - time.Unix(0, 0), - ), - EndTime: fn.Some(time.Now()), - }, + time.Unix(0, 0), time.Now(), ) _, err := fn.CollectErr(iter) require.NoError(b, err) @@ -675,10 +798,9 @@ func BenchmarkGraphReadMethods(b *testing.B) { }, { name: "ForEachNodeCacheable", - fn: func(b testing.TB, store Store) { + fn: func(b testing.TB, store V1Store) { err := store.ForEachNodeCacheable( - ctx, lnwire.GossipVersion1, - func(_ route.Vertex, + ctx, func(_ route.Vertex, _ *lnwire.FeatureVector) error { // Increment the counter to @@ -694,12 +816,12 @@ func BenchmarkGraphReadMethods(b *testing.B) { }, { name: "ForEachNodeCached", - fn: func(b testing.TB, store Store) { + fn: func(b testing.TB, store V1Store) { //nolint:ll err := store.ForEachNodeCached( - ctx, lnwire.GossipVersion1, - func(context.Context, + ctx, false, func(context.Context, route.Vertex, + []net.Addr, map[uint64]*DirectedChannel) error { // Increment the counter to @@ -715,15 +837,9 @@ func BenchmarkGraphReadMethods(b *testing.B) { }, { name: "ChanUpdatesInHorizon", - fn: func(b testing.TB, store Store) { + fn: func(b testing.TB, store V1Store) { iter := store.ChanUpdatesInHorizon( - ctx, lnwire.GossipVersion1, - ChanUpdateRange{ - StartTime: fn.Some( - time.Unix(0, 0), - ), - EndTime: fn.Some(time.Now()), - }, + time.Unix(0, 0), time.Now(), ) _, err := fn.CollectErr(iter) require.NoError(b, err) @@ -748,173 +864,6 @@ func BenchmarkGraphReadMethods(b *testing.B) { } } -// BenchmarkNodeHorizonIndex benchmarks the NodeUpdatesInHorizon query under -// different index configurations to measure the performance impact of the -// composite (version, last_update, pub_key) index vs the old single-column -// (last_update) index. -// -// NOTE: this is to be run against a local native SQL database. The -// TestPopulateDBs test helper can be used to populate the test DB. -func BenchmarkNodeHorizonIndex(b *testing.B) { - ctx := b.Context() - - // NOTE: uncomment the line below to run this benchmark locally. - b.Skipf("Skipping local benchmark test") - - // NOTE: Set this to true to also benchmark against postgres. - testPostgres := false - - // sqlBackend holds a Store for queries and a raw *sql.DB handle for - // index DDL manipulation between benchmark runs. - type sqlBackend struct { - name string - rawDB *sql.DB - store Store - } - - // openRawDB opens a raw *sql.DB connection to the same database that - // the given dbConnection targets. This is used for DDL operations - // (DROP/CREATE INDEX) that are not exposed through the Store interface. - openSQLiteRawDB := func(b testing.TB) *sql.DB { - sqliteStore, err := sqldb.NewSqliteStore( - &sqldb.SqliteConfig{ - MaxConnections: testMaxSQLiteConnections, - BusyTimeout: testSQLBusyTimeout, - PragmaOptions: testSqlitePragmaOpts, - }, - path.Join(nativeSQLSqlitePath, nativeSQLSqliteFile), - ) - require.NoError(b, err) - b.Cleanup(func() { - require.NoError(b, sqliteStore.Close()) - }) - - return sqliteStore.GetBaseDB().DB - } - - openPostgresRawDB := func(b testing.TB) *sql.DB { - pgStore, err := sqldb.NewPostgresStore( - &sqldb.PostgresConfig{ - Dsn: nativeSQLPostgresDNS, - MaxConnections: testMaxPostgresConnections, - }, - ) - require.NoError(b, err) - b.Cleanup(func() { - require.NoError(b, pgStore.Close()) - }) - - return pgStore.GetBaseDB().DB - } - - backends := []sqlBackend{ - { - name: nativeSQLSqliteConn.name, - rawDB: openSQLiteRawDB(b), - store: nativeSQLSqliteConn.open(b), - }, - } - if testPostgres { - backends = append(backends, sqlBackend{ - name: nativeSQLPostgresConn.name, - rawDB: openPostgresRawDB(b), - store: nativeSQLPostgresConn.open(b), - }) - } - - // Index configurations to compare. - type indexConfig struct { - name string - setup string - } - - configs := []indexConfig{ - { - name: "old-indexes", - setup: ` -DROP INDEX IF EXISTS graph_node_last_update_idx; -CREATE INDEX IF NOT EXISTS graph_node_last_update_idx - ON graph_nodes(last_update); -DROP INDEX IF EXISTS graph_channels_node_id_1_idx; -DROP INDEX IF EXISTS graph_channels_node_id_2_idx; -CREATE INDEX IF NOT EXISTS graph_channels_node_id_1_idx - ON graph_channels(node_id_1); -CREATE INDEX IF NOT EXISTS graph_channels_node_id_2_idx - ON graph_channels(node_id_2); -`, - }, - { - name: "new-indexes", - setup: ` -DROP INDEX IF EXISTS graph_node_last_update_idx; -CREATE INDEX IF NOT EXISTS graph_node_last_update_idx - ON graph_nodes(version, last_update, pub_key); -DROP INDEX IF EXISTS graph_channels_node_id_1_idx; -DROP INDEX IF EXISTS graph_channels_node_id_2_idx; -CREATE INDEX IF NOT EXISTS graph_channels_node_id_1_idx - ON graph_channels(node_id_1, version); -CREATE INDEX IF NOT EXISTS graph_channels_node_id_2_idx - ON graph_channels(node_id_2, version); -`, - }, - } - - // Query variants to benchmark. - type queryVariant struct { - name string - opts []IteratorOption - } - - variants := []queryVariant{ - { - name: "all-nodes", - }, - { - name: "public-only", - opts: []IteratorOption{WithIterPublicNodesOnly()}, - }, - } - - for _, backend := range backends { - for _, cfg := range configs { - for _, variant := range variants { - name := fmt.Sprintf("%s/%s/%s", - backend.name, cfg.name, - variant.name, - ) - b.Run(name, func(b *testing.B) { - // Apply the index configuration. - _, err := backend.rawDB.ExecContext( - ctx, cfg.setup, - ) - require.NoError(b, err) - - b.ResetTimer() - - //nolint:ll - for i := 0; i < b.N; i++ { - iter := backend.store.NodeUpdatesInHorizon( - ctx, - lnwire.GossipVersion1, - NodeUpdateRange{ - StartTime: fn.Some(time.Unix(0, 0)), - EndTime: fn.Some(time.Now()), - }, - variant.opts..., - ) - nodes, err := fn.CollectErr(iter) - require.NoError(b, err) - - // Prevent the compiler from - // optimizing away the result. - _ = len(nodes) - } - }) - } - } - } -} - // BenchmarkFindOptimalSQLQueryConfig uses the ForEachNode and ForEachChannel // methods to find the optimal maximum sqldb QueryConfig values for a given // database backend. This is useful for determining the best default values for @@ -994,7 +943,7 @@ func BenchmarkFindOptimalSQLQueryConfig(b *testing.B) { ) err := store.ForEachNode( - ctx, lnwire.GossipVersion1, + ctx, func(_ *models.Node) error { numNodes++ @@ -1005,7 +954,7 @@ func BenchmarkFindOptimalSQLQueryConfig(b *testing.B) { //nolint:ll err = store.ForEachChannel( - ctx, lnwire.GossipVersion1, + ctx, func(_ *models.ChannelEdgeInfo, _, _ *models.ChannelEdgePolicy) error { diff --git a/graph/db/channel_cache.go b/graph/db/channel_cache.go index b65a5ab5e..b50bbf498 100644 --- a/graph/db/channel_cache.go +++ b/graph/db/channel_cache.go @@ -1,20 +1,11 @@ package graphdb -import "github.com/lightningnetwork/lnd/lnwire" - -// channelCacheKey uniquely identifies a channel entry in the channel cache by -// gossip version and channel ID. -type channelCacheKey struct { - version lnwire.GossipVersion - chanID uint64 -} - // channelCache is an in-memory cache used to improve the performance of // ChanUpdatesInHorizon. It caches the chan info and edge policies for a // particular channel. type channelCache struct { n int - channels map[channelCacheKey]ChannelEdge + channels map[uint64]ChannelEdge } // newChannelCache creates a new channelCache with maximum capacity of n @@ -22,18 +13,13 @@ type channelCache struct { func newChannelCache(n int) *channelCache { return &channelCache{ n: n, - channels: make(map[channelCacheKey]ChannelEdge), + channels: make(map[uint64]ChannelEdge), } } // get returns the channel from the cache, if it exists. -func (c *channelCache) get(version lnwire.GossipVersion, - chanid uint64) (ChannelEdge, bool) { - - channel, ok := c.channels[channelCacheKey{ - version: version, - chanID: chanid, - }] +func (c *channelCache) get(chanid uint64) (ChannelEdge, bool) { + channel, ok := c.channels[chanid] return channel, ok } @@ -41,17 +27,10 @@ func (c *channelCache) get(version lnwire.GossipVersion, // exists, it will be replaced with the new entry. If the entry doesn't exist, // it will be inserted to the cache, performing a random eviction if the cache // is at capacity. -func (c *channelCache) insert(version lnwire.GossipVersion, chanid uint64, - channel ChannelEdge) { - - key := channelCacheKey{ - version: version, - chanID: chanid, - } - +func (c *channelCache) insert(chanid uint64, channel ChannelEdge) { // If entry exists, replace it. - if _, ok := c.channels[key]; ok { - c.channels[key] = channel + if _, ok := c.channels[chanid]; ok { + c.channels[chanid] = channel return } @@ -62,13 +41,10 @@ func (c *channelCache) insert(version lnwire.GossipVersion, chanid uint64, break } } - c.channels[key] = channel + c.channels[chanid] = channel } // remove deletes an edge for chanid from the cache, if it exists. -func (c *channelCache) remove(version lnwire.GossipVersion, chanid uint64) { - delete(c.channels, channelCacheKey{ - version: version, - chanID: chanid, - }) +func (c *channelCache) remove(chanid uint64) { + delete(c.channels, chanid) } diff --git a/graph/db/channel_cache_test.go b/graph/db/channel_cache_test.go index 4f61def20..767958d9a 100644 --- a/graph/db/channel_cache_test.go +++ b/graph/db/channel_cache_test.go @@ -1,13 +1,10 @@ package graphdb import ( + "reflect" "testing" - "github.com/btcsuite/btcd/chainhash/v2" "github.com/lightningnetwork/lnd/graph/db/models" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/routing/route" - "github.com/stretchr/testify/require" ) // TestChannelCache checks the behavior of the channelCache with respect to @@ -15,37 +12,37 @@ import ( func TestChannelCache(t *testing.T) { const cacheSize = 100 - v := lnwire.GossipVersion1 - // Create a new channel cache with the configured max size. c := newChannelCache(cacheSize) // As a sanity check, assert that querying the empty cache does not // return an entry. - _, ok := c.get(v, 0) - require.False(t, ok) + _, ok := c.get(0) + if ok { + t.Fatalf("channel cache should be empty") + } // Now, fill up the cache entirely. for i := uint64(0); i < cacheSize; i++ { - c.insert(v, i, channelForInt(i)) + c.insert(i, channelForInt(i)) } // Assert that the cache has all of the entries just inserted, since no // eviction should occur until we try to surpass the max size. - assertHasChanEntries(t, c, v, 0, cacheSize) + assertHasChanEntries(t, c, 0, cacheSize) // Now, insert a new element that causes the cache to evict an element. - c.insert(v, cacheSize, channelForInt(cacheSize)) + c.insert(cacheSize, channelForInt(cacheSize)) // Assert that the cache has this last entry, as the cache should evict // some prior element and not the newly inserted one. - assertHasChanEntries(t, c, v, cacheSize, cacheSize) + assertHasChanEntries(t, c, cacheSize, cacheSize) // Iterate over all inserted elements and construct a set of the evicted // elements. evicted := make(map[uint64]struct{}) for i := uint64(0); i < cacheSize+1; i++ { - _, ok := c.get(v, i) + _, ok := c.get(i) if !ok { evicted[i] = struct{}{} } @@ -53,58 +50,58 @@ func TestChannelCache(t *testing.T) { // Assert that exactly one element has been evicted. numEvicted := len(evicted) - require.Equal(t, 1, numEvicted) + if numEvicted != 1 { + t.Fatalf("expected one evicted entry, got: %d", numEvicted) + } // Remove the highest item which initially caused the eviction and // reinsert the element that was evicted prior. - c.remove(v, cacheSize) + c.remove(cacheSize) for i := range evicted { - c.insert(v, i, channelForInt(i)) + c.insert(i, channelForInt(i)) } // Since the removal created an extra slot, the last insertion should // not have caused an eviction and the entries for all channels in the // original set that filled the cache should be present. - assertHasChanEntries(t, c, v, 0, cacheSize) + assertHasChanEntries(t, c, 0, cacheSize) // Finally, reinsert the existing set back into the cache and test that // the cache still has all the entries. If the randomized eviction were // happening on inserts for existing cache items, we expect this to fail // with high probability. for i := uint64(0); i < cacheSize; i++ { - c.insert(v, i, channelForInt(i)) + c.insert(i, channelForInt(i)) } - assertHasChanEntries(t, c, v, 0, cacheSize) + assertHasChanEntries(t, c, 0, cacheSize) } // assertHasEntries queries the edge cache for all channels in the range [start, // end), asserting that they exist and their value matches the entry produced by // entryForInt. -func assertHasChanEntries(t *testing.T, c *channelCache, - v lnwire.GossipVersion, start, end uint64) { - +func assertHasChanEntries(t *testing.T, c *channelCache, start, end uint64) { t.Helper() for i := start; i < end; i++ { - entry, ok := c.get(v, i) - require.True(t, ok) + entry, ok := c.get(i) + if !ok { + t.Fatalf("channel cache should contain chan %d", i) + } expEntry := channelForInt(i) - require.Equal(t, expEntry, entry) + if !reflect.DeepEqual(entry, expEntry) { + t.Fatalf("entry mismatch, want: %v, got: %v", + expEntry, entry) + } } } // channelForInt generates a unique ChannelEdge given an integer. func channelForInt(i uint64) ChannelEdge { - info, err := models.NewV1Channel( - i, chainhash.Hash{}, route.Vertex{}, route.Vertex{}, - &models.ChannelV1Fields{}, - ) - if err != nil { - panic(err) - } return ChannelEdge{ - Info: info, + Info: &models.ChannelEdgeInfo{ + ChannelID: i, + }, } } diff --git a/graph/db/codec.go b/graph/db/codec.go index a3981a603..029f9b93d 100644 --- a/graph/db/codec.go +++ b/graph/db/codec.go @@ -4,7 +4,7 @@ import ( "encoding/binary" "io" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" ) var ( diff --git a/graph/db/graph.go b/graph/db/graph.go index c84ed364d..08e287238 100644 --- a/graph/db/graph.go +++ b/graph/db/graph.go @@ -4,18 +4,15 @@ import ( "context" "errors" "fmt" - "iter" "net" "sync" "sync/atomic" "testing" "time" - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/batch" - "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" @@ -26,45 +23,26 @@ import ( // busy shutting down. var ErrChanGraphShuttingDown = fmt.Errorf("ChannelGraph shutting down") -// GraphCacheStatus describes the current state of the in-memory graph cache. -type GraphCacheStatus uint8 - -const ( - // GraphCacheStatusDisabled indicates that the graph cache is disabled. - GraphCacheStatusDisabled GraphCacheStatus = iota - - // GraphCacheStatusLoading indicates that the graph cache is still - // being populated from the DB and is not yet serving reads. - GraphCacheStatusLoading - - // GraphCacheStatusLoaded indicates that the graph cache has - // completed its initial population and is serving reads. - GraphCacheStatusLoaded - - // GraphCacheStatusFailed indicates that the initial population of - // the graph cache failed. Reads fall back to the database. - GraphCacheStatusFailed -) - // ChannelGraph is a layer above the graph's CRUD layer. +// +// NOTE: currently, this is purely a pass-through layer directly to the backing +// KVStore. Upcoming commits will move the graph cache out of the KVStore and +// into this layer so that the KVStore is only responsible for CRUD operations. type ChannelGraph struct { started atomic.Bool stopped atomic.Bool - opts *chanGraphOptions + graphCache *GraphCache - cache *graphCacheState - - db Store + V1Store *topologyManager - quit chan struct{} - wg sync.WaitGroup - cancel fn.Option[context.CancelFunc] + quit chan struct{} + wg sync.WaitGroup } // NewChannelGraph creates a new ChannelGraph instance with the given backend. -func NewChannelGraph(v1Store Store, +func NewChannelGraph(v1Store V1Store, options ...ChanGraphOption) (*ChannelGraph, error) { opts := defaultChanGraphOptions() @@ -73,8 +51,7 @@ func NewChannelGraph(v1Store Store, } g := &ChannelGraph{ - opts: opts, - db: v1Store, + V1Store: v1Store, topologyManager: newTopologyManager(), quit: make(chan struct{}), } @@ -82,29 +59,12 @@ func NewChannelGraph(v1Store Store, // The graph cache can be turned off (e.g. for mobile users) for a // speed/memory usage tradeoff. if opts.useGraphCache { - g.cache = newGraphCacheState(opts.preAllocCacheNumNodes) + g.graphCache = NewGraphCache(opts.preAllocCacheNumNodes) } return g, nil } -// GraphCacheStatus returns the current state of the in-memory graph cache. -func (c *ChannelGraph) GraphCacheStatus() GraphCacheStatus { - switch { - case c.cache == nil: - return GraphCacheStatusDisabled - - case c.cache.isLoaded(): - return GraphCacheStatusLoaded - - case c.cache.isFailed(): - return GraphCacheStatusFailed - - default: - return GraphCacheStatusLoading - } -} - // Start kicks off any goroutines required for the ChannelGraph to function. // If the graph cache is enabled, then it will be populated with the contents of // the database. @@ -115,28 +75,15 @@ func (c *ChannelGraph) Start() error { log.Debugf("ChannelGraph starting") defer log.Debug("ChannelGraph started") - ctx, cancel := context.WithCancel(context.Background()) - c.cancel = fn.Some(cancel) - - if c.opts.asyncGraphCachePopulation { - c.wg.Add(1) - go func() { - defer c.wg.Done() - - if err := c.populateCache(ctx); err != nil { - log.Criticalf("Could not populate the "+ - "graph cache: %v", err) - } - }() - } else { - if err := c.populateCache(ctx); err != nil { + if c.graphCache != nil { + if err := c.populateCache(context.TODO()); err != nil { return fmt.Errorf("could not populate the graph "+ "cache: %w", err) } } c.wg.Add(1) - go c.handleTopologySubscriptions(ctx) + go c.handleTopologySubscriptions() return nil } @@ -150,7 +97,6 @@ func (c *ChannelGraph) Stop() error { log.Debugf("ChannelGraph shutting down...") defer log.Debug("ChannelGraph shutdown complete") - c.cancel.WhenSome(func(fn context.CancelFunc) { fn() }) close(c.quit) c.wg.Wait() @@ -162,7 +108,7 @@ func (c *ChannelGraph) Stop() error { // synchronously. // // NOTE: this MUST be run in a goroutine. -func (c *ChannelGraph) handleTopologySubscriptions(ctx context.Context) { +func (c *ChannelGraph) handleTopologySubscriptions() { defer c.wg.Done() for { @@ -174,7 +120,7 @@ func (c *ChannelGraph) handleTopologySubscriptions(ctx context.Context) { // synchronously so that we can guarantee the order of // notification delivery. c.wg.Add(1) - go c.handleTopologyUpdate(ctx, update) + go c.handleTopologyUpdate(update) // TODO(roasbeef): remove all unconnected vertexes // after N blocks pass with no corresponding @@ -205,9 +151,6 @@ func (c *ChannelGraph) handleTopologySubscriptions(ctx context.Context) { exit: make(chan struct{}), }) - case <-ctx.Done(): - return - case <-c.quit: return } @@ -215,68 +158,39 @@ func (c *ChannelGraph) handleTopologySubscriptions(ctx context.Context) { } // populateCache loads the entire channel graph into the in-memory graph cache. +// +// NOTE: This should only be called if the graphCache has been constructed. func (c *ChannelGraph) populateCache(ctx context.Context) error { - if c.cache == nil { - log.Info("In-memory channel graph cache disabled") - - return nil - } - - c.cache.beginPopulation() - - loaded := false - defer func() { - c.cache.finishPopulation(loaded) - }() - - cache := c.cache.graphCache - startTime := time.Now() log.Info("Populating in-memory channel graph, this might take a " + "while...") - for _, v := range []lnwire.GossipVersion{ - gossipV1, gossipV2, - } { - // TODO(elle): If we have both v1 and v2 entries for the same - // node/channel, prefer v2 when merging. - err := c.db.ForEachNodeCacheable(ctx, v, - func(node route.Vertex, - features *lnwire.FeatureVector) error { + err := c.V1Store.ForEachNodeCacheable(ctx, func(node route.Vertex, + features *lnwire.FeatureVector) error { - cache.AddNodeFeatures(node, features) + c.graphCache.AddNodeFeatures(node, features) - return nil - }, func() {}, - ) - if err != nil && !errors.Is( - err, ErrVersionNotSupportedForKVDB, - ) { - - return err - } - - err = c.db.ForEachChannelCacheable( - ctx, v, func(info *models.CachedEdgeInfo, - policy1, - policy2 *models.CachedEdgePolicy) error { - - cache.AddChannel(info, policy1, policy2) - - return nil - }, func() {}, - ) - if err != nil && - !errors.Is(err, ErrVersionNotSupportedForKVDB) { - - return err - } + return nil + }, func() {}) + if err != nil { + return err } - loaded = true + err = c.V1Store.ForEachChannelCacheable( + func(info *models.CachedEdgeInfo, + policy1, policy2 *models.CachedEdgePolicy) error { + + c.graphCache.AddChannel(info, policy1, policy2) + + return nil + }, func() {}, + ) + if err != nil { + return err + } log.Infof("Finished populating in-memory channel graph (took %v, %s)", - time.Since(startTime), cache.Stats()) + time.Since(startTime), c.graphCache.Stats()) return nil } @@ -291,20 +205,14 @@ func (c *ChannelGraph) populateCache(ctx context.Context) error { // Unknown policies are passed into the callback as nil values. // // NOTE: this is part of the graphdb.NodeTraverser interface. -func (c *ChannelGraph) ForEachNodeDirectedChannel(ctx context.Context, - node route.Vertex, cb func(channel *DirectedChannel) error, - reset func()) error { +func (c *ChannelGraph) ForEachNodeDirectedChannel(node route.Vertex, + cb func(channel *DirectedChannel) error, reset func()) error { - if c.cache != nil && c.cache.isLoaded() { - return c.cache.graphCache.ForEachChannel(node, cb) + if c.graphCache != nil { + return c.graphCache.ForEachChannel(node, cb) } - // TODO(elle): once the no-cache path needs to support - // pathfinding across gossip versions, this should iterate - // across all versions rather than defaulting to v1. - return c.db.ForEachNodeDirectedChannel( - ctx, gossipV1, node, cb, reset, - ) + return c.V1Store.ForEachNodeDirectedChannel(node, cb, reset) } // FetchNodeFeatures returns the features of the given node. If no features are @@ -313,50 +221,49 @@ func (c *ChannelGraph) ForEachNodeDirectedChannel(ctx context.Context, // features instead of the database. // // NOTE: this is part of the graphdb.NodeTraverser interface. -func (c *ChannelGraph) FetchNodeFeatures(ctx context.Context, - node route.Vertex) (*lnwire.FeatureVector, error) { +func (c *ChannelGraph) FetchNodeFeatures(node route.Vertex) ( + *lnwire.FeatureVector, error) { - if c.cache != nil && c.cache.isLoaded() { - return c.cache.graphCache.GetFeatures(node), nil + if c.graphCache != nil { + return c.graphCache.GetFeatures(node), nil } - return c.db.FetchNodeFeatures(ctx, lnwire.GossipVersion1, node) + return c.V1Store.FetchNodeFeatures(node) } // GraphSession will provide the call-back with access to a NodeTraverser // instance which can be used to perform queries against the channel graph. If // the graph cache is not enabled, then the call-back will be provided with // access to the graph via a consistent read-only transaction. -func (c *ChannelGraph) GraphSession(ctx context.Context, - cb func(graph NodeTraverser) error, reset func()) error { +func (c *ChannelGraph) GraphSession(cb func(graph NodeTraverser) error, + reset func()) error { - if c.cache != nil && c.cache.isLoaded() { + if c.graphCache != nil { return cb(c) } - return c.db.GraphSession(ctx, cb, reset) + return c.V1Store.GraphSession(cb, reset) } // ForEachNodeCached iterates through all the stored vertices/nodes in the // graph, executing the passed callback with each node encountered. // // NOTE: The callback contents MUST not be modified. -func (c *ChannelGraph) ForEachNodeCached(ctx context.Context, - v lnwire.GossipVersion, - cb func(ctx context.Context, node route.Vertex, +func (c *ChannelGraph) ForEachNodeCached(ctx context.Context, withAddrs bool, + cb func(ctx context.Context, node route.Vertex, addrs []net.Addr, chans map[uint64]*DirectedChannel) error, reset func()) error { - if c.cache != nil && c.cache.isLoaded() { - return c.cache.graphCache.ForEachNode( + if !withAddrs && c.graphCache != nil { + return c.graphCache.ForEachNode( func(node route.Vertex, channels map[uint64]*DirectedChannel) error { - return cb(ctx, node, channels) + return cb(ctx, node, nil, channels) }, ) } - return c.db.ForEachNodeCached(ctx, v, cb, reset) + return c.V1Store.ForEachNodeCached(ctx, withAddrs, cb, reset) } // AddNode adds a vertex/node to the graph database. If the node is not @@ -368,17 +275,15 @@ func (c *ChannelGraph) ForEachNodeCached(ctx context.Context, func (c *ChannelGraph) AddNode(ctx context.Context, node *models.Node, op ...batch.SchedulerOption) error { - err := c.db.AddNode(ctx, node, op...) + err := c.V1Store.AddNode(ctx, node, op...) if err != nil { return err } - if c.cache != nil { - c.cache.applyUpdate(func(cache *GraphCache) { - cache.AddNodeFeatures( - node.PubKeyBytes, node.Features, - ) - }) + if c.graphCache != nil { + c.graphCache.AddNodeFeatures( + node.PubKeyBytes, node.Features, + ) } select { @@ -390,6 +295,23 @@ func (c *ChannelGraph) AddNode(ctx context.Context, return nil } +// DeleteNode starts a new database transaction to remove a vertex/node +// from the database according to the node's public key. +func (c *ChannelGraph) DeleteNode(ctx context.Context, + nodePub route.Vertex) error { + + err := c.V1Store.DeleteNode(ctx, nodePub) + if err != nil { + return err + } + + if c.graphCache != nil { + c.graphCache.RemoveNode(nodePub) + } + + return nil +} + // AddChannelEdge adds a new (undirected, blank) edge to the graph database. An // undirected edge from the two target nodes are created. The information stored // denotes the static attributes of the channel, such as the channelID, the keys @@ -399,15 +321,13 @@ func (c *ChannelGraph) AddNode(ctx context.Context, func (c *ChannelGraph) AddChannelEdge(ctx context.Context, edge *models.ChannelEdgeInfo, op ...batch.SchedulerOption) error { - err := c.db.AddChannelEdge(ctx, edge, op...) + err := c.V1Store.AddChannelEdge(ctx, edge, op...) if err != nil { return err } - if c.cache != nil { - c.cache.applyUpdate(func(cache *GraphCache) { - cache.AddChannel(models.NewCachedEdge(edge), nil, nil) - }) + if c.graphCache != nil { + c.graphCache.AddChannel(models.NewCachedEdge(edge), nil, nil) } select { @@ -419,21 +339,19 @@ func (c *ChannelGraph) AddChannelEdge(ctx context.Context, return nil } -// MarkEdgeLive clears an edge from our zombie index for the given gossip -// version, deeming it as live. If the cache is enabled, the edge will be added -// back to the graph cache if we still have a record of this channel in the DB. -func (c *ChannelGraph) MarkEdgeLive(ctx context.Context, - v lnwire.GossipVersion, chanID uint64) error { - - err := c.db.MarkEdgeLive(ctx, v, chanID) +// MarkEdgeLive clears an edge from our zombie index, deeming it as live. +// If the cache is enabled, the edge will be added back to the graph cache if +// we still have a record of this channel in the DB. +func (c *ChannelGraph) MarkEdgeLive(chanID uint64) error { + err := c.V1Store.MarkEdgeLive(chanID) if err != nil { return err } - if c.cache != nil { + if c.graphCache != nil { // We need to add the channel back into our graph cache, // otherwise we won't use it for path finding. - infos, err := c.db.FetchChanInfos(ctx, v, []uint64{chanID}) + infos, err := c.V1Store.FetchChanInfos([]uint64{chanID}) if err != nil { return err } @@ -452,12 +370,9 @@ func (c *ChannelGraph) MarkEdgeLive(ctx context.Context, policy2 = models.NewCachedPolicy(info.Policy2) } - c.cache.applyUpdate(func(cache *GraphCache) { - cache.AddChannel( - models.NewCachedEdge(info.Info), - policy1, policy2, - ) - }) + c.graphCache.AddChannel( + models.NewCachedEdge(info.Info), policy1, policy2, + ) } return nil @@ -471,26 +386,23 @@ func (c *ChannelGraph) MarkEdgeLive(ctx context.Context, // that we require the node that failed to send the fresh update to be the one // that resurrects the channel from its zombie state. The markZombie bool // denotes whether to mark the channel as a zombie. -func (c *ChannelGraph) DeleteChannelEdges(ctx context.Context, - v lnwire.GossipVersion, strictZombiePruning, markZombie bool, +func (c *ChannelGraph) DeleteChannelEdges(strictZombiePruning, markZombie bool, chanIDs ...uint64) error { - infos, err := c.db.DeleteChannelEdges( - ctx, v, strictZombiePruning, markZombie, chanIDs..., + infos, err := c.V1Store.DeleteChannelEdges( + strictZombiePruning, markZombie, chanIDs..., ) if err != nil { return err } - if c.cache != nil { - c.cache.applyUpdate(func(cache *GraphCache) { - for _, info := range infos { - cache.RemoveChannel( - info.NodeKey1Bytes, info.NodeKey2Bytes, - info.ChannelID, - ) - } - }) + if c.graphCache != nil { + for _, info := range infos { + c.graphCache.RemoveChannel( + info.NodeKey1Bytes, info.NodeKey2Bytes, + info.ChannelID, + ) + } } return err @@ -503,23 +415,21 @@ func (c *ChannelGraph) DeleteChannelEdges(ctx context.Context, // set to the last prune height valid for the remaining chain. // Channels that were removed from the graph resulting from the // disconnected block are returned. -func (c *ChannelGraph) DisconnectBlockAtHeight(ctx context.Context, - height uint32) ([]*models.ChannelEdgeInfo, error) { +func (c *ChannelGraph) DisconnectBlockAtHeight(height uint32) ( + []*models.ChannelEdgeInfo, error) { - edges, err := c.db.DisconnectBlockAtHeight(ctx, height) + edges, err := c.V1Store.DisconnectBlockAtHeight(height) if err != nil { return nil, err } - if c.cache != nil { - c.cache.applyUpdate(func(cache *GraphCache) { - for _, edge := range edges { - cache.RemoveChannel( - edge.NodeKey1Bytes, edge.NodeKey2Bytes, - edge.ChannelID, - ) - } - }) + if c.graphCache != nil { + for _, edge := range edges { + c.graphCache.RemoveChannel( + edge.NodeKey1Bytes, edge.NodeKey2Bytes, + edge.ChannelID, + ) + } } return edges, nil @@ -532,34 +442,31 @@ func (c *ChannelGraph) DisconnectBlockAtHeight(ctx context.Context, // prune the graph is stored so callers can ensure the graph is fully in sync // with the current UTXO state. A slice of channels that have been closed by // the target block are returned if the function succeeds without error. -func (c *ChannelGraph) PruneGraph(ctx context.Context, - spentOutputs []*wire.OutPoint, +func (c *ChannelGraph) PruneGraph(spentOutputs []*wire.OutPoint, blockHash *chainhash.Hash, blockHeight uint32) ( []*models.ChannelEdgeInfo, error) { - edges, nodes, err := c.db.PruneGraph( - ctx, spentOutputs, blockHash, blockHeight, + edges, nodes, err := c.V1Store.PruneGraph( + spentOutputs, blockHash, blockHeight, ) if err != nil { return nil, err } - if c.cache != nil { - c.cache.applyUpdate(func(cache *GraphCache) { - for _, edge := range edges { - cache.RemoveChannel( - edge.NodeKey1Bytes, edge.NodeKey2Bytes, - edge.ChannelID, - ) - } - for _, node := range nodes { - cache.RemoveNode(node) - } - }) - - if stats, ok := c.cache.stats(); ok { - log.Debugf("Pruned graph, cache now has %s", stats) + if c.graphCache != nil { + for _, edge := range edges { + c.graphCache.RemoveChannel( + edge.NodeKey1Bytes, edge.NodeKey2Bytes, + edge.ChannelID, + ) } + + for _, node := range nodes { + c.graphCache.RemoveNode(node) + } + + log.Debugf("Pruned graph, cache now has %s", + c.graphCache.Stats()) } if len(edges) != 0 { @@ -583,39 +490,86 @@ func (c *ChannelGraph) PruneGraph(ctx context.Context, // any nodes from the channel graph that are currently unconnected. This ensure // that we only maintain a graph of reachable nodes. In the event that a pruned // node gains more channels, it will be re-added back to the graph. -func (c *ChannelGraph) PruneGraphNodes(ctx context.Context) error { - nodes, err := c.db.PruneGraphNodes(ctx) +func (c *ChannelGraph) PruneGraphNodes() error { + nodes, err := c.V1Store.PruneGraphNodes() if err != nil { return err } - if c.cache != nil { - c.cache.applyUpdate(func(cache *GraphCache) { - for _, node := range nodes { - cache.RemoveNode(node) - } - }) + if c.graphCache != nil { + for _, node := range nodes { + c.graphCache.RemoveNode(node) + } } return nil } +// FilterKnownChanIDs takes a set of channel IDs and return the subset of chan +// ID's that we don't know and are not known zombies of the passed set. In other +// words, we perform a set difference of our set of chan ID's and the ones +// passed in. This method can be used by callers to determine the set of +// channels another peer knows of that we don't. +func (c *ChannelGraph) FilterKnownChanIDs(chansInfo []ChannelUpdateInfo, + isZombieChan func(time.Time, time.Time) bool) ([]uint64, error) { + + unknown, knownZombies, err := c.V1Store.FilterKnownChanIDs(chansInfo) + if err != nil { + return nil, err + } + + for _, info := range knownZombies { + // TODO(ziggie): Make sure that for the strict pruning case we + // compare the pubkeys and whether the right timestamp is not + // older than the `ChannelPruneExpiry`. + // + // NOTE: The timestamp data has no verification attached to it + // in the `ReplyChannelRange` msg so we are trusting this data + // at this point. However it is not critical because we are just + // removing the channel from the db when the timestamps are more + // recent. During the querying of the gossip msg verification + // happens as usual. However we should start punishing peers + // when they don't provide us honest data ? + isStillZombie := isZombieChan( + info.Node1UpdateTimestamp, info.Node2UpdateTimestamp, + ) + + if isStillZombie { + continue + } + + // If we have marked it as a zombie but the latest update + // timestamps could bring it back from the dead, then we mark it + // alive, and we let it be added to the set of IDs to query our + // peer for. + err := c.V1Store.MarkEdgeLive( + info.ShortChannelID.ToUint64(), + ) + // Since there is a chance that the edge could have been marked + // as "live" between the FilterKnownChanIDs call and the + // MarkEdgeLive call, we ignore the error if the edge is already + // marked as live. + if err != nil && !errors.Is(err, ErrZombieEdgeNotFound) { + return nil, err + } + } + + return unknown, nil +} + // MarkEdgeZombie attempts to mark a channel identified by its channel ID as a -// zombie for the given gossip version. This method is used on an ad-hoc basis, -// when channels need to be marked as zombies outside the normal pruning cycle. -func (c *ChannelGraph) MarkEdgeZombie(ctx context.Context, - v lnwire.GossipVersion, chanID uint64, +// zombie. This method is used on an ad-hoc basis, when channels need to be +// marked as zombies outside the normal pruning cycle. +func (c *ChannelGraph) MarkEdgeZombie(chanID uint64, pubKey1, pubKey2 [33]byte) error { - err := c.db.MarkEdgeZombie(ctx, v, chanID, pubKey1, pubKey2) + err := c.V1Store.MarkEdgeZombie(chanID, pubKey1, pubKey2) if err != nil { return err } - if c.cache != nil { - c.cache.applyUpdate(func(cache *GraphCache) { - cache.RemoveChannel(pubKey1, pubKey2, chanID) - }) + if c.graphCache != nil { + c.graphCache.RemoveChannel(pubKey1, pubKey2, chanID) } return nil @@ -631,17 +585,15 @@ func (c *ChannelGraph) MarkEdgeZombie(ctx context.Context, func (c *ChannelGraph) UpdateEdgePolicy(ctx context.Context, edge *models.ChannelEdgePolicy, op ...batch.SchedulerOption) error { - from, to, err := c.db.UpdateEdgePolicy(ctx, edge, op...) + from, to, err := c.V1Store.UpdateEdgePolicy(ctx, edge, op...) if err != nil { return err } - if c.cache != nil { - c.cache.applyUpdate(func(cache *GraphCache) { - cache.UpdatePolicy( - models.NewCachedPolicy(edge), from, to, - ) - }) + if c.graphCache != nil { + c.graphCache.UpdatePolicy( + models.NewCachedPolicy(edge), from, to, + ) } select { @@ -653,522 +605,12 @@ func (c *ChannelGraph) UpdateEdgePolicy(ctx context.Context, return nil } -// ForEachNodeChannel iterates through all channels of the given node. -func (c *ChannelGraph) ForEachNodeChannel(ctx context.Context, - v lnwire.GossipVersion, nodePub route.Vertex, - cb func(*models.ChannelEdgeInfo, - *models.ChannelEdgePolicy, - *models.ChannelEdgePolicy) error, reset func()) error { - - return c.db.ForEachNodeChannel(ctx, v, nodePub, cb, reset) -} - -// ForEachNodeCacheable iterates through all stored vertices/nodes in the graph. -func (c *ChannelGraph) ForEachNodeCacheable(ctx context.Context, - v lnwire.GossipVersion, cb func(route.Vertex, - *lnwire.FeatureVector) error, reset func()) error { - - return c.db.ForEachNodeCacheable(ctx, v, cb, reset) -} - -// HasV1Node determines if the graph has a vertex identified by the target node -// in the V1 graph. -func (c *ChannelGraph) HasV1Node(ctx context.Context, - nodePub [33]byte) (time.Time, bool, error) { - - return c.db.HasV1Node(ctx, nodePub) -} - -// ForEachChannel iterates through all channel edges stored within the graph. -func (c *ChannelGraph) ForEachChannel(ctx context.Context, - v lnwire.GossipVersion, cb func(*models.ChannelEdgeInfo, - *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error, - reset func()) error { - - return c.db.ForEachChannel(ctx, v, cb, reset) -} - -// DisabledChannelIDs returns the channel ids of disabled channels. -func (c *ChannelGraph) DisabledChannelIDs(ctx context.Context, - v lnwire.GossipVersion) ( - []uint64, error) { - - return c.db.DisabledChannelIDs(ctx, v) -} - -// HasV1ChannelEdge returns true if the database knows of a channel edge. -func (c *ChannelGraph) HasV1ChannelEdge(ctx context.Context, - chanID uint64) (time.Time, time.Time, bool, bool, error) { - - return c.db.HasV1ChannelEdge(ctx, chanID) -} - -// HasChannelEdge returns true if the database knows of a channel edge. -func (c *ChannelGraph) HasChannelEdge(ctx context.Context, - v lnwire.GossipVersion, chanID uint64) (bool, bool, error) { - - return c.db.HasChannelEdge(ctx, v, chanID) -} - -// AddEdgeProof sets the proof of an existing edge in the graph database. -func (c *ChannelGraph) AddEdgeProof(ctx context.Context, - chanID lnwire.ShortChannelID, proof *models.ChannelAuthProof) error { - - return c.db.AddEdgeProof(ctx, chanID, proof) -} - -// HighestChanID returns the "highest" known channel ID in the channel graph. -func (c *ChannelGraph) HighestChanID(ctx context.Context, - v lnwire.GossipVersion) (uint64, error) { - - return c.db.HighestChanID(ctx, v) -} - -// FilterChannelRange returns channel IDs within the passed block height range -// for the given gossip version. -func (c *ChannelGraph) FilterChannelRange(ctx context.Context, - v lnwire.GossipVersion, startHeight, endHeight uint32, - withTimestamps bool) ([]BlockChannelRange, error) { - - return c.db.FilterChannelRange( - ctx, v, startHeight, endHeight, withTimestamps, - ) -} - -// FilterChannelRange returns channel IDs within the passed block height range -// for this graph's gossip version. -func (c *VersionedGraph) FilterChannelRange(ctx context.Context, - startHeight, endHeight uint32, - withTimestamps bool) ([]BlockChannelRange, error) { - - return c.db.FilterChannelRange( - ctx, c.v, startHeight, endHeight, withTimestamps, - ) -} - -// FilterKnownChanIDs takes a set of channel IDs and returns the subset of chan -// ID's that we don't know and are not known zombies of the passed set. In other -// words, we perform a set difference of our set of chan ID's and the ones -// passed in. This method can be used by callers to determine the set of -// channels another peer knows of that we don't. -func (c *VersionedGraph) FilterKnownChanIDs(ctx context.Context, - chansInfo []ChannelUpdateInfo, - isZombieChan func(ChannelUpdateInfo) bool) ([]uint64, error) { - - unknown, knownZombies, err := c.db.FilterKnownChanIDs( - ctx, c.v, chansInfo, - ) - if err != nil { - return nil, err - } - - for _, info := range knownZombies { - // Sanity check that the returned zombie channels are on the - // same gossip version as the one we passed in. - if info.Version != c.v { - return nil, fmt.Errorf("expected zombie channel's "+ - "gossip version to be %v, got %v", c.v, - info.Version) - } - - // TODO(ziggie): Make sure that for the strict pruning case - // we compare the pubkeys and whether the right timestamp - // is not older than the `ChannelPruneExpiry`. - // - // NOTE: The timestamp data has no verification attached - // to it in the `ReplyChannelRange` msg so we are trusting - // this data at this point. However it is not critical - // because we are just removing the channel from the db - // when the timestamps are more recent. During the querying - // of the gossip msg verification happens as usual. However - // we should start punishing peers when they don't provide - // us honest data? - if isZombieChan(info) { - continue - } - - // If we have marked it as a zombie but the latest update - // info could bring it back from the dead, then we mark it - // alive, and we let it be added to the set of IDs to - // query our peer for. - err := c.db.MarkEdgeLive( - ctx, info.Version, - info.ShortChannelID.ToUint64(), - ) - // Since there is a chance that the edge could have been - // marked as "live" between the FilterKnownChanIDs call - // and the MarkEdgeLive call, we ignore the error if the - // edge is already marked as live. - if err != nil && - !errors.Is(err, ErrZombieEdgeNotFound) { - - return nil, err - } - } - - return unknown, nil -} - -// FetchChanInfos returns the set of channel edges for the passed channel IDs. -func (c *ChannelGraph) FetchChanInfos(ctx context.Context, - v lnwire.GossipVersion, chanIDs []uint64) ([]ChannelEdge, error) { - - return c.db.FetchChanInfos(ctx, v, chanIDs) -} - -// FetchChannelEdgesByOutpoint attempts to lookup directed edges by funding -// outpoint. -func (c *ChannelGraph) FetchChannelEdgesByOutpoint(ctx context.Context, - op *wire.OutPoint) ( - *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, - *models.ChannelEdgePolicy, error) { - - return c.db.FetchChannelEdgesByOutpoint( - ctx, lnwire.GossipVersion1, op, - ) -} - -// FetchChannelEdgesByID attempts to lookup directed edges by channel ID. -func (c *ChannelGraph) FetchChannelEdgesByID(ctx context.Context, - chanID uint64) ( - *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, - *models.ChannelEdgePolicy, error) { - - return c.db.FetchChannelEdgesByID( - ctx, lnwire.GossipVersion1, chanID, - ) -} - -// PutClosedScid stores a SCID for a closed channel in the database. -func (c *ChannelGraph) PutClosedScid(ctx context.Context, - scid lnwire.ShortChannelID) error { - - return c.db.PutClosedScid(ctx, scid) -} - -// IsClosedScid checks whether a channel identified by the scid is closed. -func (c *ChannelGraph) IsClosedScid(ctx context.Context, - scid lnwire.ShortChannelID) (bool, error) { - - return c.db.IsClosedScid(ctx, scid) -} - -// SetSourceNode sets the source node within the graph database. -func (c *ChannelGraph) SetSourceNode(ctx context.Context, - node *models.Node) error { - - return c.db.SetSourceNode(ctx, node) -} - -// PruneTip returns the block height and hash of the latest pruning block. -func (c *ChannelGraph) PruneTip(ctx context.Context) (*chainhash.Hash, - uint32, error) { - - return c.db.PruneTip(ctx) -} - -// VersionedGraph is a wrapper around ChannelGraph that will call underlying -// Store methods with a specific gossip version. -type VersionedGraph struct { - *ChannelGraph - v lnwire.GossipVersion -} - -// NewVersionedGraph creates a new VersionedGraph. -func NewVersionedGraph(c *ChannelGraph, - v lnwire.GossipVersion) *VersionedGraph { - - return &VersionedGraph{ - ChannelGraph: c, - v: v, - } -} - -// FetchNodeFeatures returns the features of the given node. If no features are -// known for the node, an empty feature vector is returned. If the graphCache is -// available, it will be used instead of the database. -// -// NOTE: This is part of the graphdb.NodeTraverser interface. -func (c *VersionedGraph) FetchNodeFeatures(ctx context.Context, - node route.Vertex) (*lnwire.FeatureVector, error) { - - if c.cache != nil && c.cache.isLoaded() { - return c.cache.graphCache.GetFeatures(node), nil - } - - return c.db.FetchNodeFeatures(ctx, c.v, node) -} - -// ForEachNodeDirectedChannel iterates through all channels of a given node, -// executing the passed callback on the directed edge representing the channel -// and its incoming policy. If the graphCache is available, it will be used -// instead of the database. -// -// NOTE: This is part of the graphdb.NodeTraverser interface. -func (c *VersionedGraph) ForEachNodeDirectedChannel(ctx context.Context, - node route.Vertex, cb func(channel *DirectedChannel) error, - reset func()) error { - - if c.cache != nil && c.cache.isLoaded() { - return c.cache.graphCache.ForEachChannel(node, cb) - } - - return c.db.ForEachNodeDirectedChannel(ctx, c.v, node, cb, reset) -} - -// ForEachNodeCached iterates through all stored vertices/nodes in the graph, -// delegating to the embedded ChannelGraph. -func (c *VersionedGraph) ForEachNodeCached(ctx context.Context, - cb func(ctx context.Context, node route.Vertex, - chans map[uint64]*DirectedChannel) error, - reset func()) error { - - return c.ChannelGraph.ForEachNodeCached(ctx, c.v, cb, reset) -} - -// ForEachNode iterates through all stored vertices/nodes in the graph. -func (c *VersionedGraph) ForEachNode(ctx context.Context, - cb func(*models.Node) error, reset func()) error { - - return c.db.ForEachNode(ctx, c.v, cb, reset) -} - -// NumZombies returns the current number of zombie channels in the graph. -func (c *VersionedGraph) NumZombies(ctx context.Context) (uint64, error) { - return c.db.NumZombies(ctx, c.v) -} - -// NodeUpdatesInHorizon returns all known lightning nodes with updates within -// the passed range. The version is supplied by the embedded field. -func (c *VersionedGraph) NodeUpdatesInHorizon(ctx context.Context, - r NodeUpdateRange, - opts ...IteratorOption) iter.Seq2[*models.Node, error] { - - return c.db.NodeUpdatesInHorizon(ctx, c.v, r, opts...) -} - -// ChanUpdatesInHorizon returns all known channel edges with at least one -// policy update within the specified range. The version is supplied by the -// embedded field. -func (c *VersionedGraph) ChanUpdatesInHorizon(ctx context.Context, - r ChanUpdateRange, - opts ...IteratorOption) iter.Seq2[ChannelEdge, error] { - - return c.db.ChanUpdatesInHorizon(ctx, c.v, r, opts...) -} - -// ChannelView returns the verifiable edge information for each active channel. -func (c *VersionedGraph) ChannelView(ctx context.Context) ([]EdgePoint, - error) { - - return c.db.ChannelView(ctx, c.v) -} - -// GraphSession provides the callback with access to a NodeTraverser instance -// for performing queries against the channel graph. If the graph cache is -// enabled, the callback receives the VersionedGraph directly (which implements -// NodeTraverser using the cache). Otherwise a read-only database session is -// used. -func (c *VersionedGraph) GraphSession(ctx context.Context, - cb func(graph NodeTraverser) error, reset func()) error { - - if c.cache != nil && c.cache.isLoaded() { - return cb(c) - } - - // TODO(elle): the underlying GraphSession currently creates a - // NodeTraverser that is hardcoded to GossipVersion1. This needs to be - // updated to pass the version through for v2 support. - return c.db.GraphSession(ctx, cb, reset) -} - -// FetchNode attempts to look up a target node by its identity public key. -func (c *VersionedGraph) FetchNode(ctx context.Context, - nodePub route.Vertex) (*models.Node, error) { - - return c.db.FetchNode(ctx, c.v, nodePub) -} - -// FetchChannelEdgesByID attempts to lookup directed edges by channel ID. -func (c *VersionedGraph) FetchChannelEdgesByID(ctx context.Context, - chanID uint64) ( - *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, - *models.ChannelEdgePolicy, error) { - - return c.db.FetchChannelEdgesByID(ctx, c.v, chanID) -} - -// FetchChannelEdgesByOutpoint attempts to lookup directed edges by funding -// outpoint. -func (c *VersionedGraph) FetchChannelEdgesByOutpoint(ctx context.Context, - op *wire.OutPoint) ( - *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, - *models.ChannelEdgePolicy, error) { - - return c.db.FetchChannelEdgesByOutpoint(ctx, c.v, op) -} - -// IsZombieEdge returns whether the edge is considered zombie for this version. -func (c *VersionedGraph) IsZombieEdge(ctx context.Context, - chanID uint64) (bool, [33]byte, [33]byte, error) { - - return c.db.IsZombieEdge(ctx, c.v, chanID) -} - -// AddrsForNode returns all known addresses for the target node public key. -func (c *VersionedGraph) AddrsForNode(ctx context.Context, - nodePub *btcec.PublicKey) (bool, []net.Addr, error) { - - return c.db.AddrsForNode(ctx, c.v, nodePub) -} - -// DeleteNode starts a new database transaction to remove a vertex/node -// from the database according to the node's public key. -func (c *VersionedGraph) DeleteNode(ctx context.Context, - nodePub route.Vertex) error { - - err := c.db.DeleteNode(ctx, c.v, nodePub) - if err != nil { - return err - } - - if c.cache != nil { - c.cache.applyUpdate(func(cache *GraphCache) { - cache.RemoveNode(nodePub) - }) - } - - return nil -} - -// HasNode determines if the graph has a vertex identified by the target node -// in the V1 graph. -func (c *VersionedGraph) HasNode(ctx context.Context, nodePub [33]byte) (bool, - error) { - - return c.db.HasNode(ctx, c.v, nodePub) -} - -// LookupAlias attempts to return the alias as advertised by the target node. -func (c *VersionedGraph) LookupAlias(ctx context.Context, - pub *btcec.PublicKey) (string, error) { - - return c.db.LookupAlias(ctx, c.v, pub) -} - -// SourceNode returns the source node of the graph. -func (c *VersionedGraph) SourceNode(ctx context.Context) (*models.Node, - error) { - - return c.db.SourceNode(ctx, c.v) -} - -// DeleteChannelEdges removes edges with the given channel IDs from the -// database and marks them as zombies. This ensures that we're unable to re-add -// it to our database once again. If an edge does not exist within the -// database, then ErrEdgeNotFound will be returned. If strictZombiePruning is -// true, then when we mark these edges as zombies, we'll set up the keys such -// that we require the node that failed to send the fresh update to be the one -// that resurrects the channel from its zombie state. The markZombie bool -// denotes whether to mark the channel as a zombie. -func (c *VersionedGraph) DeleteChannelEdges(ctx context.Context, - strictZombiePruning, markZombie bool, chanIDs ...uint64) error { - - return c.ChannelGraph.DeleteChannelEdges( - ctx, c.v, strictZombiePruning, markZombie, chanIDs..., - ) -} - -// HasChannelEdge returns true if the database knows of a channel edge with the -// passed channel ID and this graph's gossip version, and false otherwise. If it -// is not found, then the zombie index is checked and its result is returned as -// the second boolean. -func (c *VersionedGraph) HasChannelEdge(ctx context.Context, - chanID uint64) (bool, bool, error) { - - return c.db.HasChannelEdge(ctx, c.v, chanID) -} - -// ForEachSourceNodeChannel iterates through all channels of the source node. -func (c *VersionedGraph) ForEachSourceNodeChannel(ctx context.Context, - cb func(chanPoint wire.OutPoint, havePolicy bool, - otherNode *models.Node) error, reset func()) error { - - return c.db.ForEachSourceNodeChannel(ctx, c.v, cb, reset) -} - -// ForEachNodeChannel iterates through all channels of the given node. -func (c *VersionedGraph) ForEachNodeChannel(ctx context.Context, - nodePub route.Vertex, cb func(*models.ChannelEdgeInfo, - *models.ChannelEdgePolicy, - *models.ChannelEdgePolicy) error, reset func()) error { - - return c.db.ForEachNodeChannel(ctx, c.v, nodePub, cb, reset) -} - -// ForEachChannel iterates through all channel edges stored within the graph. -func (c *VersionedGraph) ForEachChannel(ctx context.Context, - cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, - *models.ChannelEdgePolicy) error, reset func()) error { - - return c.db.ForEachChannel(ctx, c.v, cb, reset) -} - -// ForEachNodeCacheable iterates through all stored vertices/nodes in the graph. -func (c *VersionedGraph) ForEachNodeCacheable(ctx context.Context, - cb func(route.Vertex, *lnwire.FeatureVector) error, - reset func()) error { - - return c.db.ForEachNodeCacheable(ctx, c.v, cb, reset) -} - -// ForEachChannelCacheable iterates through all channel edges for the cache. -func (c *VersionedGraph) ForEachChannelCacheable(ctx context.Context, - cb func(*models.CachedEdgeInfo, *models.CachedEdgePolicy, - *models.CachedEdgePolicy) error, reset func()) error { - - return c.db.ForEachChannelCacheable(ctx, c.v, cb, reset) -} - -// DisabledChannelIDs returns the channel ids of disabled channels. -func (c *VersionedGraph) DisabledChannelIDs( - ctx context.Context) ([]uint64, error) { - - return c.db.DisabledChannelIDs(ctx, c.v) -} - -// FetchChanInfos returns the set of channel edges for the passed channel IDs. -func (c *VersionedGraph) FetchChanInfos(ctx context.Context, - chanIDs []uint64) ([]ChannelEdge, error) { - - return c.db.FetchChanInfos(ctx, c.v, chanIDs) -} - -// HighestChanID returns the "highest" known channel ID in the channel graph. -func (c *VersionedGraph) HighestChanID(ctx context.Context) (uint64, error) { - return c.db.HighestChanID(ctx, c.v) -} - -// ChannelID attempts to lookup the 8-byte compact channel ID. -func (c *VersionedGraph) ChannelID(ctx context.Context, - chanPoint *wire.OutPoint) (uint64, error) { - - return c.db.ChannelID(ctx, c.v, chanPoint) -} - -// IsPublicNode determines whether the node is seen as public in the graph. -func (c *VersionedGraph) IsPublicNode(ctx context.Context, - pubKey [33]byte) (bool, error) { - - return c.db.IsPublicNode(ctx, c.v, pubKey) -} - // MakeTestGraph creates a new instance of the ChannelGraph for testing -// purposes. The backing Store implementation depends on the version of +// purposes. The backing V1Store implementation depends on the version of // NewTestDB included in the current build. // // NOTE: this is currently unused, but is left here for future use to show how -// NewTestDB can be used. As the SQL implementation of the Store is +// NewTestDB can be used. As the SQL implementation of the V1Store is // implemented, unit tests will be switched to use this function instead of // the existing MakeTestGraph helper. Once only this function is used, the // existing MakeTestGraph function will be removed and this one will be renamed. @@ -1179,13 +621,7 @@ func MakeTestGraph(t testing.TB, store := NewTestDB(t) - // Default to synchronous cache population in tests so that the - // cache is fully loaded before the test proceeds. - allOpts := append( - []ChanGraphOption{WithSyncGraphCachePopulation()}, opts..., - ) - - graph, err := NewChannelGraph(store, allOpts...) + graph, err := NewChannelGraph(store, opts...) require.NoError(t, err) require.NoError(t, graph.Start()) diff --git a/graph/db/graph_cache.go b/graph/db/graph_cache.go index 4343be8ac..4a3a3b0f9 100644 --- a/graph/db/graph_cache.go +++ b/graph/db/graph_cache.go @@ -4,7 +4,7 @@ import ( "fmt" "sync" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" @@ -142,8 +142,8 @@ func (c *GraphCache) AddChannel(info *models.CachedEdgeInfo, // Skip adding policies if both are disabled, as the channel is // currently unusable for routing. However, we still add the channel // structure above so that policy updates can later enable it. - if policy1 != nil && policy1.IsDisabled && - policy2 != nil && policy2.IsDisabled { + if policy1 != nil && policy1.IsDisabled() && + policy2 != nil && policy2.IsDisabled() { log.Debugf("Skipping policies for channel %v: both "+ "policies are disabled (channel structure still "+ @@ -156,14 +156,14 @@ func (c *GraphCache) AddChannel(info *models.CachedEdgeInfo, // of node 2 then we have the policy 1 as seen from node 1. if policy1 != nil { fromNode, toNode := info.NodeKey1Bytes, info.NodeKey2Bytes - if !policy1.IsNode1 { + if !policy1.IsNode1() { fromNode, toNode = toNode, fromNode } c.UpdatePolicy(policy1, fromNode, toNode) } if policy2 != nil { fromNode, toNode := info.NodeKey2Bytes, info.NodeKey1Bytes - if policy2.IsNode1 { + if policy2.IsNode1() { fromNode, toNode = toNode, fromNode } c.UpdatePolicy(policy2, fromNode, toNode) @@ -210,7 +210,7 @@ func (c *GraphCache) UpdatePolicy(policy *models.CachedEdgePolicy, fromNode, switch { // This is node 1, and it is edge 1, so this is the outgoing // policy for node 1. - case channel.IsNode1 && policy.IsNode1: + case channel.IsNode1 && policy.IsNode1(): channel.OutPolicySet = true policy.InboundFee.WhenSome(func(fee lnwire.Fee) { channel.InboundFee = fee @@ -218,7 +218,7 @@ func (c *GraphCache) UpdatePolicy(policy *models.CachedEdgePolicy, fromNode, // This is node 2, and it is edge 2, so this is the outgoing // policy for node 2. - case !channel.IsNode1 && !policy.IsNode1: + case !channel.IsNode1 && !policy.IsNode1(): channel.OutPolicySet = true policy.InboundFee.WhenSome(func(fee lnwire.Fee) { channel.InboundFee = fee diff --git a/graph/db/graph_cache_state.go b/graph/db/graph_cache_state.go deleted file mode 100644 index 716d770ce..000000000 --- a/graph/db/graph_cache_state.go +++ /dev/null @@ -1,105 +0,0 @@ -package graphdb - -import ( - "sync" - "sync/atomic" -) - -// pendingUpdatesWarnThreshold is the number of buffered cache mutations at -// which a warning is logged. A large buffer indicates that cache population is -// taking a long time relative to the incoming gossip rate. -const pendingUpdatesWarnThreshold = 10_000 - -// graphCacheState tracks the in-memory graph cache together with its -// population state. The underlying GraphCache is independently thread-safe, so -// once reads are allowed to use it, they do not need to hold updateMtx. -type graphCacheState struct { - graphCache *GraphCache - loaded atomic.Bool - failed atomic.Bool - - updateMtx sync.Mutex - loading bool - - pendingUpdates []func(*GraphCache) -} - -// newGraphCacheState constructs a graph cache state with a new cache instance. -func newGraphCacheState(preAllocNumNodes int) *graphCacheState { - return &graphCacheState{ - graphCache: NewGraphCache(preAllocNumNodes), - } -} - -// isLoaded reports whether the cache has finished its initial population and -// is safe to serve reads from. -func (s *graphCacheState) isLoaded() bool { - return s.loaded.Load() -} - -// isFailed reports whether the cache population attempt has failed. -func (s *graphCacheState) isFailed() bool { - return s.failed.Load() -} - -// stats returns the cache stats if the cache has finished its initial -// population. -func (s *graphCacheState) stats() (string, bool) { - if !s.isLoaded() { - return "", false - } - - return s.graphCache.Stats(), true -} - -// beginPopulation marks the cache as loading and starts buffering concurrent -// cache mutations until the population pass completes. -func (s *graphCacheState) beginPopulation() { - s.updateMtx.Lock() - defer s.updateMtx.Unlock() - - s.loading = true - s.pendingUpdates = nil -} - -// finishPopulation replays any buffered mutations and marks the cache as ready -// when the initial population completed successfully. If population failed, -// buffered mutations are discarded since the cache won't be used for reads. -func (s *graphCacheState) finishPopulation(loaded bool) { - s.updateMtx.Lock() - defer s.updateMtx.Unlock() - - if loaded { - for _, update := range s.pendingUpdates { - update(s.graphCache) - } - - s.loaded.Store(true) - } else { - s.failed.Store(true) - } - - s.pendingUpdates = nil - s.loading = false -} - -// applyUpdate applies a cache mutation immediately or buffers it when the -// cache is still being populated. -func (s *graphCacheState) applyUpdate(update func(cache *GraphCache)) { - s.updateMtx.Lock() - defer s.updateMtx.Unlock() - - if s.loading { - s.pendingUpdates = append(s.pendingUpdates, update) - - if len(s.pendingUpdates)%pendingUpdatesWarnThreshold == 0 { - log.Warnf("Graph cache has %d pending updates "+ - "buffered during population", - len(s.pendingUpdates)) - } - - return - } - - update(s.graphCache) -} diff --git a/graph/db/graph_cache_test.go b/graph/db/graph_cache_test.go index 3d5fba85d..89e3a7e87 100644 --- a/graph/db/graph_cache_test.go +++ b/graph/db/graph_cache_test.go @@ -33,9 +33,9 @@ func TestGraphCacheAddNode(t *testing.T) { runTest := func(nodeA, nodeB route.Vertex) { t.Helper() - isNode1A, isNode1B := true, false + channelFlagA, channelFlagB := 0, 1 if nodeA == pubKey2 { - isNode1A, isNode1B = false, true + channelFlagA, channelFlagB = 1, 0 } inboundFee := lnwire.Fee{ @@ -44,9 +44,8 @@ func TestGraphCacheAddNode(t *testing.T) { } outPolicy1 := &models.CachedEdgePolicy{ - ChannelID: 1000, - IsNode1: isNode1A, - IsDisabled: false, + ChannelID: 1000, + ChannelFlags: lnwire.ChanUpdateChanFlags(channelFlagA), ToNodePubKey: func() route.Vertex { return nodeB }, @@ -54,9 +53,8 @@ func TestGraphCacheAddNode(t *testing.T) { InboundFee: fn.Some(inboundFee), } inPolicy1 := &models.CachedEdgePolicy{ - ChannelID: 1000, - IsNode1: isNode1B, - IsDisabled: false, + ChannelID: 1000, + ChannelFlags: lnwire.ChanUpdateChanFlags(channelFlagB), ToNodePubKey: func() route.Vertex { return nodeA }, @@ -127,9 +125,8 @@ func assertCachedPolicyEqual(t *testing.T, original, cached *models.CachedEdgePolicy) { require.Equal(t, original.ChannelID, cached.ChannelID) - require.Equal(t, original.HasMaxHTLC, cached.HasMaxHTLC) - require.Equal(t, original.IsNode1, cached.IsNode1) - require.Equal(t, original.IsDisabled, cached.IsDisabled) + require.Equal(t, original.MessageFlags, cached.MessageFlags) + require.Equal(t, original.ChannelFlags, cached.ChannelFlags) require.Equal(t, original.TimeLockDelta, cached.TimeLockDelta) require.Equal(t, original.MinHTLC, cached.MinHTLC) require.Equal(t, original.MaxHTLC, cached.MaxHTLC) @@ -174,14 +171,13 @@ func TestGraphCacheDisabledPoliciesRegression(t *testing.T) { // Create two disabled policies. disabledPolicy1 := &models.CachedEdgePolicy{ - ChannelID: chanID, - IsNode1: true, - IsDisabled: true, + ChannelID: chanID, + ChannelFlags: lnwire.ChanUpdateDisabled, } disabledPolicy2 := &models.CachedEdgePolicy{ - ChannelID: chanID, - IsNode1: false, - IsDisabled: true, + ChannelID: chanID, + ChannelFlags: lnwire.ChanUpdateDisabled | + lnwire.ChanUpdateDirection, } // Add the channel with both policies disabled (simulating @@ -211,8 +207,7 @@ func TestGraphCacheDisabledPoliciesRegression(t *testing.T) { // Now simulate receiving a fresh update enabling one direction. enabledPolicy1 := &models.CachedEdgePolicy{ ChannelID: chanID, - IsNode1: true, - IsDisabled: false, + ChannelFlags: 0, // NOT disabled anymore TimeLockDelta: 40, MinHTLC: lnwire.MilliSatoshi(1000), } diff --git a/graph/db/graph_test.go b/graph/db/graph_test.go index 46d61866d..b5e7a99eb 100644 --- a/graph/db/graph_test.go +++ b/graph/db/graph_test.go @@ -11,22 +11,21 @@ import ( "math" prand "math/rand" "net" + "reflect" + "runtime" "sync" "testing" "time" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" - "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/kvdb" - "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" "github.com/stretchr/testify/require" @@ -70,248 +69,61 @@ var ( } ) -func createNode(t testing.TB, v lnwire.GossipVersion, - priv *btcec.PrivateKey) *models.Node { - - pubKey := route.NewVertex(priv.PubKey()) - - switch v { - case lnwire.GossipVersion1: - return models.NewV1Node( - pubKey, &models.NodeV1Fields{ - LastUpdate: nextUpdateTime(), - Color: color.RGBA{1, 2, 3, 0}, - Alias: "kek" + hex.EncodeToString( - pubKey[:], - ), - Addresses: testAddrs, - Features: testFeatures.RawFeatureVector, - AuthSigBytes: testSig.Serialize(), - }, - ) - case lnwire.GossipVersion2: - return models.NewV2Node( - pubKey, &models.NodeV2Fields{ - Signature: testSig.Serialize(), - LastBlockHeight: nextBlockHeight(), - Color: fn.Some( - color.RGBA{1, 2, 3, 0}, - ), - Alias: fn.Some( - "kek" + hex.EncodeToString(pubKey[:]), - ), - Features: testFeatures. - RawFeatureVector, - Addresses: testAddrs, - }, - ) +func createNode(priv *btcec.PrivateKey) *models.Node { + pub := priv.PubKey().SerializeCompressed() + n := &models.Node{ + HaveNodeAnnouncement: true, + AuthSigBytes: testSig.Serialize(), + LastUpdate: nextUpdateTime(), + Color: color.RGBA{1, 2, 3, 0}, + Alias: "kek" + hex.EncodeToString(pub), + Features: testFeatures, + Addresses: testAddrs, } + copy(n.PubKeyBytes[:], priv.PubKey().SerializeCompressed()) - t.Fatalf("unknown gossip version: %v", v) - - return nil + return n } -func createTestVertex(t testing.TB, v lnwire.GossipVersion) *models.Node { +func createTestVertex(t testing.TB) *models.Node { t.Helper() priv, err := btcec.NewPrivateKey() require.NoError(t, err) - return createNode(t, v, priv) + return createNode(priv) } -type versionedTest struct { - name string - test func(t *testing.T, v lnwire.GossipVersion) -} - -var versionedTests = []versionedTest{ - { - name: "node crud", - test: testNodeInsertionAndDeletion, - }, - { - name: "source node", - test: testSourceNode, - }, - { - name: "alias lookup", - test: testAliasLookup, - }, - { - name: "add edge proof", - test: testAddEdgeProof, - }, - { - name: "edge insertion deletion", - test: testEdgeInsertionDeletion, - }, - { - name: "edge policy crud", - test: testEdgePolicyCRUD, - }, - { - name: "incomplete channel policies", - test: testIncompleteChannelPolicies, - }, - { - name: "add channel edge shell nodes", - test: testAddChannelEdgeShellNodes, - }, - { - name: "for each source node channel", - test: testForEachSourceNodeChannel, - }, - { - name: "graph traversal cacheable", - test: testGraphTraversalCacheable, - }, - { - name: "partial node", - test: testPartialNode, - }, - { - name: "node is public", - test: testNodeIsPublic, - }, - { - name: "node is public empty channel signature", - test: testIsPublicNodeEmptyChannelSignature, - }, - { - name: "edge info updates", - test: testEdgeInfoUpdates, - }, - { - name: "batched update edge policy", - test: testBatchedUpdateEdgePolicy, - }, - { - name: "disabled channel ids", - test: testDisabledChannelIDs, - }, - { - name: "batched add channel edge", - test: testBatchedAddChannelEdge, - }, - { - name: "graph cache for each node channel", - test: testGraphCacheForEachNodeChannel, - }, - { - name: "highest chan id", - test: testHighestChanID, - }, - { - name: "fetch chan infos", - test: testFetchChanInfos, - }, - { - name: "channel view", - test: testChannelView, - }, - { - name: "channel view taproot v1 round trip", - test: testChannelViewTaprootV1RoundTrip, - }, - { - name: "node pruning update index deletion", - test: testNodePruningUpdateIndexDeletion, - }, - { - name: "lightning node sig verification", - test: testLightningNodeSigVerification, - }, - { - name: "graph zombie index", - test: testGraphZombieIndex, - }, - { - name: "disconnect block at height", - test: testDisconnectBlockAtHeight, - }, - { - name: "filter known chan ids zombie revival", - test: testFilterKnownChanIDsZombieRevival, - }, - { - name: "filter known chan ids", - test: testFilterKnownChanIDs, - }, - { - name: "fetch zombie edge versioning", - test: testFetchZombieEdgeVersioning, - }, -} - -// TestVersionedDBs runs various tests against both v1 and v2 versioned -// backends. -func TestVersionedDBs(t *testing.T) { +// TestNodeInsertionAndDeletion tests the CRUD operations for a Node. +func TestNodeInsertionAndDeletion(t *testing.T) { t.Parallel() - - // Run all v1 tests. - for _, vt := range versionedTests { - t.Run(vt.name+"/v1", func(t *testing.T) { - vt.test(t, lnwire.GossipVersion1) - }) - - if !isSQLDB { - continue - } - - t.Run(vt.name+"/v2", func(t *testing.T) { - vt.test(t, lnwire.GossipVersion2) - }) - } -} - -// testNodeInsertionAndDeletion tests the CRUD operations for a Node. -func testNodeInsertionAndDeletion(t *testing.T, v lnwire.GossipVersion) { - nodeWithAddrs := func(addrs []net.Addr) *models.Node { - return models.NewV1Node( - testPub, &models.NodeV1Fields{ - AuthSigBytes: testSig.Serialize(), - LastUpdate: nextUpdateTime(), - Color: color.RGBA{1, 2, 3, 0}, - Alias: "kek", - Features: testFeatures.RawFeatureVector, - Addresses: addrs, - ExtraOpaqueData: []byte{1, 1, 1, 2, 2, 2, 2}, - }, - ) - } - - if v == lnwire.GossipVersion2 { - nodeWithAddrs = func(addrs []net.Addr) *models.Node { - return models.NewV2Node( - testPub, &models.NodeV2Fields{ - Signature: testSig.Serialize(), - LastBlockHeight: nextBlockHeight(), - Color: fn.Some( - color.RGBA{1, 2, 3, 0}, - ), - Alias: fn.Some("kek"), - Features: testFeatures. - RawFeatureVector, - Addresses: addrs, - ExtraSignedFields: map[uint64][]byte{ - 20: {0x1, 0x2, 0x3}, - 21: {0x4, 0x5, 0x6, 0x7}, - }, - }, - ) - } - } - ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), v) + + graph := MakeTestGraph(t) + + // We'd like to test basic insertion/deletion for vertexes from the + // graph, so we'll create a test vertex to start with. + timeStamp := int64(1232342) + nodeWithAddrs := func(addrs []net.Addr) *models.Node { + timeStamp++ + return &models.Node{ + HaveNodeAnnouncement: true, + AuthSigBytes: testSig.Serialize(), + LastUpdate: time.Unix(timeStamp, 0), + Color: color.RGBA{1, 2, 3, 0}, + Alias: "kek", + Features: testFeatures, + Addresses: addrs, + ExtraOpaqueData: []byte{1, 1, 1, 2, 2, 2, 2}, + PubKeyBytes: testPub, + } + } // First, insert the node into the graph DB. This should succeed // without any errors. node := nodeWithAddrs(testAddrs) require.NoError(t, graph.AddNode(ctx, node)) - assertNodeInCache(t, graph.ChannelGraph, node, testFeatures) + assertNodeInCache(t, graph, node, testFeatures) // Our AddNode implementation uses the batcher meaning that it is // possible that two updates for the same node announcement may be @@ -326,7 +138,7 @@ func testNodeInsertionAndDeletion(t *testing.T, v lnwire.GossipVersion) { dbNode, err := graph.FetchNode(ctx, testPub) require.NoError(t, err, "unable to locate node") - exists, err := graph.HasNode(ctx, dbNode.PubKeyBytes) + _, exists, err := graph.HasNode(ctx, dbNode.PubKeyBytes) require.NoError(t, err) require.True(t, exists) @@ -335,20 +147,20 @@ func testNodeInsertionAndDeletion(t *testing.T, v lnwire.GossipVersion) { // Check that the node's features are fetched correctly. This check // will use the graph cache to fetch the features. - features, err := graph.FetchNodeFeatures(ctx, node.PubKeyBytes) + features, err := graph.FetchNodeFeatures(node.PubKeyBytes) require.NoError(t, err) require.Equal(t, testFeatures, features) // Check that the node's features are fetched correctly. This check // will check the database directly. - features, err = graph.FetchNodeFeatures(ctx, node.PubKeyBytes) + features, err = graph.V1Store.FetchNodeFeatures(node.PubKeyBytes) require.NoError(t, err) require.Equal(t, testFeatures, features) // Next, delete the node from the graph, this should purge all data // related to the node. require.NoError(t, graph.DeleteNode(ctx, testPub)) - assertNodeNotInCache(t, graph.ChannelGraph, testPub) + assertNodeNotInCache(t, graph, testPub) // Attempting to delete the node again should return an error since // the node is no longer known. @@ -447,62 +259,88 @@ func testNodeInsertionAndDeletion(t *testing.T, v lnwire.GossipVersion) { dbNode, err = graph.FetchNode(ctx, testPub) require.NoError(t, err) require.Equal(t, expAddrs, dbNode.Addresses) + + // Also check that the withAddr param of ForEachNodeCached correctly + // returns the addresses we expect for this node. + err = graph.ForEachNodeCached( + ctx, true, func(ctx context.Context, node route.Vertex, + addrs []net.Addr, + chans map[uint64]*DirectedChannel) error { + + if node != dbNode.PubKeyBytes { + return nil + } + + require.Equal(t, expAddrs, addrs) + + return nil + }, func() {}, + ) + require.NoError(t, err) } -// testPartialNode tests that partial/shell nodes are correctly created when -// a channel edge is added referencing nodes we are not yet aware of. -func testPartialNode(t *testing.T, v lnwire.GossipVersion) { +// TestPartialNode checks that we can add and retrieve a Node where +// only the pubkey is known to the database. +func TestPartialNode(t *testing.T) { t.Parallel() ctx := t.Context() - graph := NewVersionedGraph( - MakeTestGraph(t, WithSyncGraphCachePopulation()), v, - ) + graph := MakeTestGraph(t) // To insert a partial node, we need to add a channel edge that has - // node keys for nodes we are not yet aware of. + // node keys for nodes we are not yet aware var node1, node2 models.Node copy(node1.PubKeyBytes[:], pubKey1Bytes) copy(node2.PubKeyBytes[:], pubKey2Bytes) // Create an edge attached to these nodes and add it to the graph. - edgeInfo, _ := createEdge(v, 140, 0, 0, 0, &node1, &node2) - require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo)) + edgeInfo, _ := createEdge(140, 0, 0, 0, &node1, &node2) + require.NoError(t, graph.AddChannelEdge(ctx, &edgeInfo)) // Both of the nodes should now be in both the graph (as partial/shell) // nodes _and_ the cache should also have an awareness of both nodes. - assertNodeInCache(t, graph.ChannelGraph, &node1, nil) - assertNodeInCache(t, graph.ChannelGraph, &node2, nil) + assertNodeInCache(t, graph, &node1, nil) + assertNodeInCache(t, graph, &node2, nil) - // Next, fetch the nodes from the database to ensure everything was + // Next, fetch the node2 from the database to ensure everything was // serialized properly. dbNode1, err := graph.FetchNode(ctx, pubKey1) require.NoError(t, err) dbNode2, err := graph.FetchNode(ctx, pubKey2) require.NoError(t, err) - exists, err := graph.HasNode(ctx, dbNode1.PubKeyBytes) + _, exists, err := graph.HasNode(ctx, dbNode1.PubKeyBytes) require.NoError(t, err) require.True(t, exists) // The two nodes should match exactly! (with default values for // LastUpdate and db set to satisfy compareNodes()) - expectedNode1 := models.NewShellNode(v, pubKey1) + expectedNode1 := &models.Node{ + HaveNodeAnnouncement: false, + LastUpdate: time.Unix(0, 0), + PubKeyBytes: pubKey1, + Features: lnwire.EmptyFeatureVector(), + } compareNodes(t, expectedNode1, dbNode1) - exists, err = graph.HasNode(ctx, dbNode2.PubKeyBytes) + _, exists, err = graph.HasNode(ctx, dbNode2.PubKeyBytes) require.NoError(t, err) require.True(t, exists) // The two nodes should match exactly! (with default values for // LastUpdate and db set to satisfy compareNodes()) - expectedNode2 := models.NewShellNode(v, pubKey2) + expectedNode2 := &models.Node{ + HaveNodeAnnouncement: false, + LastUpdate: time.Unix(0, 0), + PubKeyBytes: pubKey2, + Features: lnwire.EmptyFeatureVector(), + } compareNodes(t, expectedNode2, dbNode2) // Next, delete the node from the graph, this should purge all data // related to the node. require.NoError(t, graph.DeleteNode(ctx, pubKey1)) - assertNodeNotInCache(t, graph.ChannelGraph, testPub) + assertNodeNotInCache(t, graph, testPub) // Finally, attempt to fetch the node again. This should fail as the // node should have been deleted from the database. @@ -510,15 +348,16 @@ func testPartialNode(t *testing.T, v lnwire.GossipVersion) { require.ErrorIs(t, err, ErrGraphNodeNotFound) } -// testAliasLookup tests the alias lookup functionality of the graph store. -func testAliasLookup(t *testing.T, v lnwire.GossipVersion) { +// TestAliasLookup tests the alias lookup functionality of the graph store. +func TestAliasLookup(t *testing.T) { + t.Parallel() ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), v) + graph := MakeTestGraph(t) // We'd like to test the alias index within the database, so first // create a new test node. - testNode := createTestVertex(t, v) + testNode := createTestVertex(t) // Add the node to the graph's database, this should also insert an // entry into the alias index for this node. @@ -530,26 +369,26 @@ func testAliasLookup(t *testing.T, v lnwire.GossipVersion) { require.NoError(t, err, "unable to generate pubkey") dbAlias, err := graph.LookupAlias(ctx, nodePub) require.NoError(t, err, "unable to find alias") - require.Equal(t, testNode.Alias.UnwrapOr(""), dbAlias) + require.Equal(t, testNode.Alias, dbAlias) // Ensure that looking up a non-existent alias results in an error. - node := createTestVertex(t, v) + node := createTestVertex(t) nodePub, err = node.PubKey() require.NoError(t, err, "unable to generate pubkey") _, err = graph.LookupAlias(ctx, nodePub) require.ErrorIs(t, err, ErrNodeAliasNotFound) } -// testSourceNode tests the source node functionality of the graph store. -func testSourceNode(t *testing.T, v lnwire.GossipVersion) { +// TestSourceNode tests the source node functionality of the graph store. +func TestSourceNode(t *testing.T) { t.Parallel() ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), v) + graph := MakeTestGraph(t) // We'd like to test the setting/getting of the source node, so we // first create a fake node to use within the test. - testNode := createTestVertex(t, v) + testNode := createTestVertex(t) // Attempt to fetch the source node, this should return an error as the // source node hasn't yet been set. @@ -578,10 +417,10 @@ func TestSetSourceNodeSameTimestamp(t *testing.T) { t.Parallel() ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), lnwire.GossipVersion1) + graph := MakeTestGraph(t) // Create and set the initial source node. - testNode := createTestVertex(t, lnwire.GossipVersion1) + testNode := createTestVertex(t) require.NoError(t, graph.SetSourceNode(ctx, testNode)) // Verify the source node was set correctly. @@ -594,18 +433,18 @@ func TestSetSourceNodeSameTimestamp(t *testing.T) { // simulates the race condition where multiple goroutines read the // same old timestamp, independently increment it, and try to update // with different changes. - modifiedNode := models.NewV1Node( - testNode.PubKeyBytes, &models.NodeV1Fields{ - // Same timestamp. - LastUpdate: testNode.LastUpdate, - // Different alias. - Alias: "different-alias", - Color: color.RGBA{R: 100, G: 200, B: 50, A: 0}, - Addresses: testNode.Addresses, - Features: testNode.Features.RawFeatureVector, - AuthSigBytes: testNode.AuthSigBytes, - }, - ) + modifiedNode := &models.Node{ + PubKeyBytes: testNode.PubKeyBytes, + HaveNodeAnnouncement: true, + // Same timestamp. + LastUpdate: testNode.LastUpdate, + // Different alias. + Alias: "different-alias", + Color: color.RGBA{R: 100, G: 200, B: 50, A: 0}, + Addresses: testNode.Addresses, + Features: testNode.Features, + AuthSigBytes: testNode.AuthSigBytes, + } // Attempt to set the source node with the same timestamp but // different parameters. This should now succeed for both SQL and KV @@ -616,146 +455,102 @@ func TestSetSourceNodeSameTimestamp(t *testing.T) { // Verify that the parameter changes actually persisted. updatedNode, err := graph.SourceNode(ctx) require.NoError(t, err) - require.Equal(t, "different-alias", updatedNode.Alias.UnwrapOr("")) + require.Equal(t, "different-alias", updatedNode.Alias) require.Equal( t, color.RGBA{R: 100, G: 200, B: 50, A: 0}, - updatedNode.Color.UnwrapOr(color.RGBA{}), + updatedNode.Color, ) require.Equal(t, testNode.LastUpdate, updatedNode.LastUpdate) } -// testEdgeInsertionDeletion tests the basic CRUD operations for channel edges. -func testEdgeInsertionDeletion(t *testing.T, v lnwire.GossipVersion) { +// TestEdgeInsertionDeletion tests the basic CRUD operations for channel edges. +func TestEdgeInsertionDeletion(t *testing.T) { t.Parallel() ctx := t.Context() - graph := NewVersionedGraph( - MakeTestGraph(t, WithSyncGraphCachePopulation()), v, - ) + graph := MakeTestGraph(t) // We'd like to test the insertion/deletion of edges, so we create two // vertexes to connect. - node1 := createTestVertex(t, v) - node2 := createTestVertex(t, v) + node1 := createTestVertex(t) + node2 := createTestVertex(t) - // Create a fake channel and add it to the graph. - const ( - blockHeight = 1234 - txIndex = 1 - txPosition = 0 - outPointIndex = 9 - ) - - edgeInfo, shortChanID := createEdge( - v, blockHeight, txIndex, txPosition, outPointIndex, node1, - node2, - ) - chanID := shortChanID.ToUint64() + // In addition to the fake vertexes we create some fake channel + // identifiers. + chanID := uint64(prand.Int63()) outpoint := wire.OutPoint{ Hash: rev, - Index: outPointIndex, + Index: 9, } - require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo)) - assertEdgeWithNoPoliciesInCache(t, graph.ChannelGraph, edgeInfo) + // Add the new edge to the database, this should proceed without any + // errors. + node1Pub, err := node1.PubKey() + require.NoError(t, err, "unable to generate node key") + node2Pub, err := node2.PubKey() + require.NoError(t, err, "unable to generate node key") + edgeInfo := models.ChannelEdgeInfo{ + ChannelID: chanID, + ChainHash: *chaincfg.MainNetParams.GenesisHash, + AuthProof: &models.ChannelAuthProof{ + NodeSig1Bytes: testSig.Serialize(), + NodeSig2Bytes: testSig.Serialize(), + BitcoinSig1Bytes: testSig.Serialize(), + BitcoinSig2Bytes: testSig.Serialize(), + }, + Features: lnwire.EmptyFeatureVector(), + ChannelPoint: outpoint, + Capacity: 9000, + } + copy(edgeInfo.NodeKey1Bytes[:], node1Pub.SerializeCompressed()) + copy(edgeInfo.NodeKey2Bytes[:], node2Pub.SerializeCompressed()) + copy(edgeInfo.BitcoinKey1Bytes[:], node1Pub.SerializeCompressed()) + copy(edgeInfo.BitcoinKey2Bytes[:], node2Pub.SerializeCompressed()) + + require.NoError(t, graph.AddChannelEdge(ctx, &edgeInfo)) + assertEdgeWithNoPoliciesInCache(t, graph, &edgeInfo) // Show that trying to insert the same channel again will return the // expected error. - err := graph.AddChannelEdge(ctx, edgeInfo) + err = graph.AddChannelEdge(ctx, &edgeInfo) require.ErrorIs(t, err, ErrEdgeAlreadyExist) - // Ensure that both policies are returned as unknown (nil) and that - // the edge info round-trips correctly. - dbEdge, e1, e2, err := graph.FetchChannelEdgesByID(ctx, chanID) + // Ensure that both policies are returned as unknown (nil). + _, e1, e2, err := graph.FetchChannelEdgesByID(chanID) require.NoError(t, err) require.Nil(t, e1) require.Nil(t, e2) - // Verify core fields match. - require.Equal(t, edgeInfo.ChannelID, dbEdge.ChannelID) - require.Equal(t, edgeInfo.Version, dbEdge.Version) - require.Equal(t, edgeInfo.NodeKey1Bytes, dbEdge.NodeKey1Bytes) - require.Equal(t, edgeInfo.NodeKey2Bytes, dbEdge.NodeKey2Bytes) - require.Equal(t, edgeInfo.ChainHash, dbEdge.ChainHash) - require.Equal(t, edgeInfo.ChannelPoint, dbEdge.ChannelPoint) - require.Equal(t, edgeInfo.Capacity, dbEdge.Capacity) - - // Verify auth proof round-trips. - require.NotNil(t, dbEdge.AuthProof) - require.Equal(t, edgeInfo.AuthProof.Version, dbEdge.AuthProof.Version) - - // Verify version-specific fields. - switch v { - case lnwire.GossipVersion1: - require.Equal(t, - edgeInfo.BitcoinKey1Bytes, dbEdge.BitcoinKey1Bytes, - ) - require.Equal(t, - edgeInfo.BitcoinKey2Bytes, dbEdge.BitcoinKey2Bytes, - ) - require.Equal(t, - edgeInfo.ExtraOpaqueData, dbEdge.ExtraOpaqueData, - ) - - case lnwire.GossipVersion2: - require.Equal(t, - edgeInfo.BitcoinKey1Bytes, dbEdge.BitcoinKey1Bytes, - ) - require.Equal(t, - edgeInfo.BitcoinKey2Bytes, dbEdge.BitcoinKey2Bytes, - ) - require.Equal(t, - edgeInfo.MerkleRootHash, dbEdge.MerkleRootHash, - ) - require.Equal(t, - edgeInfo.FundingScript, dbEdge.FundingScript, - ) - require.Equal(t, - edgeInfo.ExtraSignedFields, dbEdge.ExtraSignedFields, - ) - } - - // Also verify fetching by outpoint returns the same data. - dbEdge2, _, _, err := graph.FetchChannelEdgesByOutpoint( - ctx, &outpoint, - ) - require.NoError(t, err) - require.Equal(t, dbEdge.ChannelID, dbEdge2.ChannelID) - // Next, attempt to delete the edge from the database, again this // should proceed without any issues. - require.NoError(t, graph.DeleteChannelEdges( - ctx, false, true, chanID, - )) - assertNoEdge(t, graph.ChannelGraph, chanID) + require.NoError(t, graph.DeleteChannelEdges(false, true, chanID)) + assertNoEdge(t, graph, chanID) // Ensure that any query attempts to lookup the delete channel edge are // properly deleted. - _, _, _, err = graph.FetchChannelEdgesByOutpoint(ctx, &outpoint) + _, _, _, err = graph.FetchChannelEdgesByOutpoint(&outpoint) require.ErrorIs(t, err, ErrEdgeNotFound) // Assert that if the edge is a zombie, then FetchChannelEdgesByID // still returns a populated models.ChannelEdgeInfo as its comment // description promises. - edge, _, _, err := graph.FetchChannelEdgesByID(ctx, chanID) + edge, _, _, err := graph.FetchChannelEdgesByID(chanID) require.ErrorIs(t, err, ErrZombieEdge) require.NotNil(t, edge) - isZombie, _, _, err := graph.IsZombieEdge(ctx, chanID) + isZombie, _, _, err := graph.IsZombieEdge(chanID) require.NoError(t, err) require.True(t, isZombie) // Finally, attempt to delete a (now) non-existent edge within the // database, this should result in an error. - err = graph.DeleteChannelEdges(ctx, false, true, chanID) + err = graph.DeleteChannelEdges(false, true, chanID) require.ErrorIs(t, err, ErrEdgeNotFound) } -func createEdge(version lnwire.GossipVersion, height, txIndex uint32, - txPosition uint16, outPointIndex uint32, node1, node2 *models.Node, - skipProof ...bool) (*models.ChannelEdgeInfo, lnwire.ShortChannelID) { - - shouldSkipProof := len(skipProof) > 0 && skipProof[0] +func createEdge(height, txIndex uint32, txPosition uint16, outPointIndex uint32, + node1, node2 *models.Node) (models.ChannelEdgeInfo, + lnwire.ShortChannelID) { shortChanID := lnwire.ShortChannelID{ BlockHeight: height, @@ -769,114 +564,46 @@ func createEdge(version lnwire.GossipVersion, height, txIndex uint32, node1Pub, _ := node1.PubKey() node2Pub, _ := node2.PubKey() - - node1Vertex, _ := route.NewVertexFromBytes( - node1Pub.SerializeCompressed(), - ) - node2Vertex, _ := route.NewVertexFromBytes( - node2Pub.SerializeCompressed(), - ) - - var edgeInfo *models.ChannelEdgeInfo - switch version { - case lnwire.GossipVersion1: - btcKey1, _ := route.NewVertexFromBytes( - node1Pub.SerializeCompressed(), - ) - btcKey2, _ := route.NewVertexFromBytes( - node2Pub.SerializeCompressed(), - ) - - opts := []models.EdgeModifier{ - models.WithChannelPoint(outpoint), - models.WithCapacity(9000), - } - if !shouldSkipProof { - proof := models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ) - opts = append(opts, models.WithChanProof(proof)) - } - - edgeInfo, _ = models.NewV1Channel( - shortChanID.ToUint64(), - *chaincfg.MainNetParams.GenesisHash, - node1Vertex, - node2Vertex, - &models.ChannelV1Fields{ - BitcoinKey1Bytes: btcKey1, - BitcoinKey2Bytes: btcKey2, - ExtraOpaqueData: make([]byte, 0), - }, - opts..., - ) - - case lnwire.GossipVersion2: - btcKey1, _ := route.NewVertexFromBytes( - node1Pub.SerializeCompressed(), - ) - btcKey2, _ := route.NewVertexFromBytes( - node2Pub.SerializeCompressed(), - ) - - // Create a test merkle root hash. - var merkleRoot chainhash.Hash - copy(merkleRoot[:], bytes.Repeat([]byte{0xaa}, 32)) - - // Create a test funding script. - fundingScript := []byte{0x00, 0x20} - fundingScript = append( - fundingScript, bytes.Repeat([]byte{0xbb}, 32)..., - ) - - opts := []models.EdgeModifier{ - models.WithChannelPoint(outpoint), - models.WithCapacity(9000), - } - if !shouldSkipProof { - proof := models.NewV2ChannelAuthProof( - testSig.Serialize(), - ) - opts = append(opts, models.WithChanProof(proof)) - } - - edgeInfo, _ = models.NewV2Channel( - shortChanID.ToUint64(), - *chaincfg.MainNetParams.GenesisHash, - node1Vertex, - node2Vertex, - &models.ChannelV2Fields{ - BitcoinKey1Bytes: fn.Some(btcKey1), - BitcoinKey2Bytes: fn.Some(btcKey2), - MerkleRootHash: fn.Some(merkleRoot), - FundingScript: fn.Some(fundingScript), - ExtraSignedFields: make(map[uint64][]byte), - }, - opts..., - ) + edgeInfo := models.ChannelEdgeInfo{ + ChannelID: shortChanID.ToUint64(), + ChainHash: *chaincfg.MainNetParams.GenesisHash, + AuthProof: &models.ChannelAuthProof{ + NodeSig1Bytes: testSig.Serialize(), + NodeSig2Bytes: testSig.Serialize(), + BitcoinSig1Bytes: testSig.Serialize(), + BitcoinSig2Bytes: testSig.Serialize(), + }, + ChannelPoint: outpoint, + Capacity: 9000, + ExtraOpaqueData: make([]byte, 0), + Features: lnwire.EmptyFeatureVector(), } + copy(edgeInfo.NodeKey1Bytes[:], node1Pub.SerializeCompressed()) + copy(edgeInfo.NodeKey2Bytes[:], node2Pub.SerializeCompressed()) + copy(edgeInfo.BitcoinKey1Bytes[:], node1Pub.SerializeCompressed()) + copy(edgeInfo.BitcoinKey2Bytes[:], node2Pub.SerializeCompressed()) + return edgeInfo, shortChanID } -// testDisconnectBlockAtHeight checks that the pruned state of the channel +// TestDisconnectBlockAtHeight checks that the pruned state of the channel // database is what we expect after calling DisconnectBlockAtHeight. -func testDisconnectBlockAtHeight(t *testing.T, v lnwire.GossipVersion) { +func TestDisconnectBlockAtHeight(t *testing.T) { t.Parallel() ctx := t.Context() - graph := MakeTestGraph(t, WithSyncGraphCachePopulation()) + graph := MakeTestGraph(t) - sourceNode := createTestVertex(t, v) - require.NoError(t, graph.SetSourceNode(ctx, sourceNode)) + sourceNode := createTestVertex(t) + if err := graph.SetSourceNode(ctx, sourceNode); err != nil { + t.Fatalf("unable to set source node: %v", err) + } // We'd like to test the insertion/deletion of edges, so we create two // vertexes to connect. - node1 := createTestVertex(t, v) - node2 := createTestVertex(t, v) + node1 := createTestVertex(t) + node2 := createTestVertex(t) // In addition to the fake vertexes we create some fake channel // identifiers. @@ -886,75 +613,102 @@ func testDisconnectBlockAtHeight(t *testing.T, v lnwire.GossipVersion) { // Prune the graph a few times to make sure we have entries in the // prune log. - _, err := graph.PruneGraph(ctx, spendOutputs, &blockHash, 155) + _, err := graph.PruneGraph(spendOutputs, &blockHash, 155) require.NoError(t, err, "unable to prune graph") var blockHash2 chainhash.Hash copy(blockHash2[:], bytes.Repeat([]byte{2}, 32)) - _, err = graph.PruneGraph(ctx, spendOutputs, &blockHash2, 156) + _, err = graph.PruneGraph(spendOutputs, &blockHash2, 156) require.NoError(t, err, "unable to prune graph") + // We'll create 3 almost identical edges, so first create a helper + // method containing all logic for doing so. + // Create an edge which has its block height at 156. height := uint32(156) - edgeInfo, _ := createEdge(v, height, 0, 0, 0, node1, node2) + edgeInfo, _ := createEdge(height, 0, 0, 0, node1, node2) - // Create an edge with block height 157. We give it maximum values for - // tx index and position, to make sure our database range scan gets - // edges from the entire range. + // Create an edge with block height 157. We give it + // maximum values for tx index and position, to make + // sure our database range scan get edges from the + // entire range. edgeInfo2, _ := createEdge( - v, height+1, math.MaxUint32&0x00ffffff, math.MaxUint16, - 1, node1, node2, + height+1, math.MaxUint32&0x00ffffff, math.MaxUint16, 1, + node1, node2, ) // Create a third edge, this with a block height of 155. - edgeInfo3, _ := createEdge(v, height-1, 0, 0, 2, node1, node2) + edgeInfo3, _ := createEdge(height-1, 0, 0, 2, node1, node2) // Now add all these new edges to the database. - require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo)) - require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo2)) - require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo3)) - assertEdgeWithNoPoliciesInCache(t, graph, edgeInfo) - assertEdgeWithNoPoliciesInCache(t, graph, edgeInfo2) - assertEdgeWithNoPoliciesInCache(t, graph, edgeInfo3) + if err := graph.AddChannelEdge(ctx, &edgeInfo); err != nil { + t.Fatalf("unable to create channel edge: %v", err) + } + + if err := graph.AddChannelEdge(ctx, &edgeInfo2); err != nil { + t.Fatalf("unable to create channel edge: %v", err) + } + + if err := graph.AddChannelEdge(ctx, &edgeInfo3); err != nil { + t.Fatalf("unable to create channel edge: %v", err) + } + assertEdgeWithNoPoliciesInCache(t, graph, &edgeInfo) + assertEdgeWithNoPoliciesInCache(t, graph, &edgeInfo2) + assertEdgeWithNoPoliciesInCache(t, graph, &edgeInfo3) // Call DisconnectBlockAtHeight, which should prune every channel // that has a funding height of 'height' or greater. - removed, err := graph.DisconnectBlockAtHeight(ctx, height) - require.NoError(t, err) + removed, err := graph.DisconnectBlockAtHeight(uint32(height)) + if err != nil { + t.Fatalf("unable to prune %v", err) + } assertNoEdge(t, graph, edgeInfo.ChannelID) assertNoEdge(t, graph, edgeInfo2.ChannelID) - assertEdgeWithNoPoliciesInCache(t, graph, edgeInfo3) + assertEdgeWithNoPoliciesInCache(t, graph, &edgeInfo3) // The two edges should have been removed. - require.Len(t, removed, 2) - require.Equal(t, edgeInfo.ChannelID, removed[0].ChannelID) - require.Equal(t, edgeInfo2.ChannelID, removed[1].ChannelID) + if len(removed) != 2 { + t.Fatalf("expected two edges to be removed from graph, "+ + "only %d were", len(removed)) + } + if removed[0].ChannelID != edgeInfo.ChannelID { + t.Fatalf("expected edge to be removed from graph") + } + if removed[1].ChannelID != edgeInfo2.ChannelID { + t.Fatalf("expected edge to be removed from graph") + } // The two first edges should be removed from the db. - has, isZombie, err := graph.HasChannelEdge( - ctx, v, edgeInfo.ChannelID, - ) + _, _, has, isZombie, err := graph.HasChannelEdge(edgeInfo.ChannelID) require.NoError(t, err, "unable to query for edge") - require.False(t, has) - require.False(t, isZombie) - has, isZombie, err = graph.HasChannelEdge( - ctx, v, edgeInfo2.ChannelID, - ) + if has { + t.Fatalf("edge1 was not pruned from the graph") + } + if isZombie { + t.Fatal("reorged edge1 should not be marked as zombie") + } + _, _, has, isZombie, err = graph.HasChannelEdge(edgeInfo2.ChannelID) require.NoError(t, err, "unable to query for edge") - require.False(t, has) - require.False(t, isZombie) + if has { + t.Fatalf("edge2 was not pruned from the graph") + } + if isZombie { + t.Fatal("reorged edge2 should not be marked as zombie") + } // Edge 3 should not be removed. - has, isZombie, err = graph.HasChannelEdge( - ctx, v, edgeInfo3.ChannelID, - ) + _, _, has, isZombie, err = graph.HasChannelEdge(edgeInfo3.ChannelID) require.NoError(t, err, "unable to query for edge") - require.True(t, has) - require.False(t, isZombie) + if !has { + t.Fatalf("edge3 was pruned from the graph") + } + if isZombie { + t.Fatal("edge3 was marked as zombie") + } // PruneTip should be set to the blockHash we specified for the block // at height 155. - hash, h, err := graph.PruneTip(ctx) + hash, h, err := graph.PruneTip() require.NoError(t, err, "unable to get prune tip") require.True(t, blockHash.IsEqual(hash)) require.Equal(t, h, height-1) @@ -962,44 +716,88 @@ func testDisconnectBlockAtHeight(t *testing.T, v lnwire.GossipVersion) { func assertEdgeInfoEqual(t *testing.T, e1 *models.ChannelEdgeInfo, e2 *models.ChannelEdgeInfo) { - require.Equal(t, e2.ChannelID, e1.ChannelID) - require.Equal(t, e2.ChainHash, e1.ChainHash) - require.Equal(t, e2.NodeKey1Bytes[:], e1.NodeKey1Bytes[:]) - require.Equal(t, e2.NodeKey2Bytes[:], e1.NodeKey2Bytes[:]) - btcKey1E1 := e1.BitcoinKey1Bytes.UnwrapOr(route.Vertex{}) - btcKey1E2 := e2.BitcoinKey1Bytes.UnwrapOr(route.Vertex{}) - require.Equal(t, btcKey1E2[:], btcKey1E1[:]) - btcKey2E1 := e1.BitcoinKey2Bytes.UnwrapOr(route.Vertex{}) - btcKey2E2 := e2.BitcoinKey2Bytes.UnwrapOr(route.Vertex{}) - require.Equal(t, btcKey2E2[:], btcKey2E1[:]) - require.True(t, e1.Features.Equals(e2.Features.RawFeatureVector)) + + if e1.ChannelID != e2.ChannelID { + t.Fatalf("chan id's don't match: %v vs %v", e1.ChannelID, + e2.ChannelID) + } + + if e1.ChainHash != e2.ChainHash { + t.Fatalf("chain hashes don't match: %v vs %v", e1.ChainHash, + e2.ChainHash) + } + + if !bytes.Equal(e1.NodeKey1Bytes[:], e2.NodeKey1Bytes[:]) { + t.Fatalf("nodekey1 doesn't match") + } + if !bytes.Equal(e1.NodeKey2Bytes[:], e2.NodeKey2Bytes[:]) { + t.Fatalf("nodekey2 doesn't match") + } + if !bytes.Equal(e1.BitcoinKey1Bytes[:], e2.BitcoinKey1Bytes[:]) { + t.Fatalf("bitcoinkey1 doesn't match") + } + if !bytes.Equal(e1.BitcoinKey2Bytes[:], e2.BitcoinKey2Bytes[:]) { + t.Fatalf("bitcoinkey2 doesn't match") + } + + if !e1.Features.Equals(e2.Features.RawFeatureVector) { + t.Fatalf("features don't match: %v vs %v", e1.Features, + e2.Features) + } require.True(t, bytes.Equal( - e1.AuthProof.NodeSig1(), - e2.AuthProof.NodeSig1(), + e1.AuthProof.NodeSig1Bytes, e2.AuthProof.NodeSig1Bytes, )) require.True(t, bytes.Equal( - e1.AuthProof.NodeSig2(), - e2.AuthProof.NodeSig2(), + e1.AuthProof.NodeSig2Bytes, e2.AuthProof.NodeSig2Bytes, )) require.True(t, bytes.Equal( - e1.AuthProof.BitcoinSig1(), - e2.AuthProof.BitcoinSig1(), + e1.AuthProof.BitcoinSig1Bytes, + e2.AuthProof.BitcoinSig1Bytes, )) require.True(t, bytes.Equal( - e1.AuthProof.BitcoinSig2(), - e2.AuthProof.BitcoinSig2(), + e1.AuthProof.BitcoinSig2Bytes, e2.AuthProof.BitcoinSig2Bytes, )) - require.Equal(t, e2.ChannelPoint, e1.ChannelPoint) - require.Equal(t, e2.Capacity, e1.Capacity) - require.Equal(t, e2.ExtraOpaqueData, e1.ExtraOpaqueData) + if e1.ChannelPoint != e2.ChannelPoint { + t.Fatalf("channel point match: %v vs %v", e1.ChannelPoint, + e2.ChannelPoint) + } + + if e1.Capacity != e2.Capacity { + t.Fatalf("capacity doesn't match: %v vs %v", e1.Capacity, + e2.Capacity) + } + + if !bytes.Equal(e1.ExtraOpaqueData, e2.ExtraOpaqueData) { + t.Fatalf("extra data doesn't match: %v vs %v", + e2.ExtraOpaqueData, e2.ExtraOpaqueData) + } +} + +type createEdgeConfig struct { + skipProofs bool +} + +type createEdgeOpt func(*createEdgeConfig) + +// withSkipProofs will let createChannelEdge create an edge without auth +// proofs. In this case, createChannelEdge will then also not create policies. +func withSkipProofs() createEdgeOpt { + return func(cfg *createEdgeConfig) { + cfg.skipProofs = true + } } func createChannelEdge(node1, node2 *models.Node, - v lnwire.GossipVersion) (*models.ChannelEdgeInfo, + options ...createEdgeOpt) (*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) { + var opts createEdgeConfig + for _, o := range options { + o(&opts) + } + var ( firstNode [33]byte secondNode [33]byte @@ -1022,238 +820,173 @@ func createChannelEdge(node1, node2 *models.Node, // Add the new edge to the database, this should proceed without any // errors. - var node1Key, node2Key route.Vertex - copy(node1Key[:], firstNode[:]) - copy(node2Key[:], secondNode[:]) + edgeInfo := &models.ChannelEdgeInfo{ + ChannelID: chanID, + ChainHash: *chaincfg.MainNetParams.GenesisHash, + ChannelPoint: outpoint, + Capacity: 1000, + ExtraOpaqueData: []byte{ + 1, 1, 1, + 2, 2, 2, 2, + 3, 3, 3, 3, 3, + }, + Features: lnwire.EmptyFeatureVector(), + } + copy(edgeInfo.NodeKey1Bytes[:], firstNode[:]) + copy(edgeInfo.NodeKey2Bytes[:], secondNode[:]) + copy(edgeInfo.BitcoinKey1Bytes[:], firstNode[:]) + copy(edgeInfo.BitcoinKey2Bytes[:], secondNode[:]) - extraData := []byte{ - 1, 1, 1, - 2, 2, 2, 2, - 3, 3, 3, 3, 3, + if opts.skipProofs { + return edgeInfo, nil, nil } - var ( - edgeInfo *models.ChannelEdgeInfo - edge1 *models.ChannelEdgePolicy - edge2 *models.ChannelEdgePolicy - ) + edgeInfo.AuthProof = &models.ChannelAuthProof{ + NodeSig1Bytes: testSig.Serialize(), + NodeSig2Bytes: testSig.Serialize(), + BitcoinSig1Bytes: testSig.Serialize(), + BitcoinSig2Bytes: testSig.Serialize(), + } - switch v { - case gossipV1: - proof := models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ) - - edgeInfo, _ = models.NewV1Channel( - chanID, *chaincfg.MainNetParams.GenesisHash, - node1Key, node2Key, &models.ChannelV1Fields{ - BitcoinKey1Bytes: node1Key, - BitcoinKey2Bytes: node2Key, - ExtraOpaqueData: extraData, - }, - models.WithChanProof(proof), - models.WithChannelPoint(outpoint), - models.WithCapacity(1000), - ) - - edge1 = &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, - SigBytes: testSig.Serialize(), - ChannelID: chanID, - LastUpdate: nextUpdateTime(), - MessageFlags: 1, - ChannelFlags: 0, - TimeLockDelta: 99, - MinHTLC: 2342135, - MaxHTLC: 13928598, - FeeBaseMSat: 4352345, - FeeProportionalMillionths: 3452352, - ToNode: secondNode, - ExtraOpaqueData: []byte{1, 0}, - } - edge2 = &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, - SigBytes: testSig.Serialize(), - ChannelID: chanID, - LastUpdate: nextUpdateTime(), - MessageFlags: 1, - ChannelFlags: 1, - TimeLockDelta: 99, - MinHTLC: 2342135, - MaxHTLC: 13928598, - FeeBaseMSat: 4352345, - FeeProportionalMillionths: 90392423, - ToNode: firstNode, - ExtraOpaqueData: []byte{1, 0}, - } - - case gossipV2: - var merkleRoot chainhash.Hash - copy(merkleRoot[:], bytes.Repeat([]byte{0xaa}, 32)) - - fundingScript := []byte{0x00, 0x20} - fundingScript = append( - fundingScript, bytes.Repeat([]byte{0xbb}, 32)..., - ) - - proof := models.NewV2ChannelAuthProof(testSig.Serialize()) - - edgeInfo, _ = models.NewV2Channel( - chanID, *chaincfg.MainNetParams.GenesisHash, - node1Key, node2Key, &models.ChannelV2Fields{ - BitcoinKey1Bytes: fn.Some(node1Key), - BitcoinKey2Bytes: fn.Some(node2Key), - MerkleRootHash: fn.Some(merkleRoot), - FundingScript: fn.Some(fundingScript), - ExtraSignedFields: make(map[uint64][]byte), - }, - models.WithChanProof(proof), - models.WithChannelPoint(outpoint), - models.WithCapacity(1000), - ) - - edge1 = &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion2, - SigBytes: testSig.Serialize(), - ChannelID: chanID, - LastBlockHeight: nextBlockHeight(), - SecondPeer: false, - DisableFlags: 0, - TimeLockDelta: 99, - MinHTLC: 2342135, - MaxHTLC: 13928598, - FeeBaseMSat: 4352345, - FeeProportionalMillionths: 3452352, - ToNode: secondNode, - ExtraSignedFields: map[uint64][]byte{ - 100: {0x1, 0x2}, - }, - } - edge2 = &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion2, - SigBytes: testSig.Serialize(), - ChannelID: chanID, - LastBlockHeight: nextBlockHeight(), - SecondPeer: true, - DisableFlags: 0, - TimeLockDelta: 99, - MinHTLC: 2342135, - MaxHTLC: 13928598, - FeeBaseMSat: 4352345, - FeeProportionalMillionths: 90392423, - ToNode: firstNode, - ExtraSignedFields: map[uint64][]byte{ - 101: {0x3, 0x4}, - }, - } + edge1 := &models.ChannelEdgePolicy{ + SigBytes: testSig.Serialize(), + ChannelID: chanID, + LastUpdate: nextUpdateTime(), + MessageFlags: 1, + ChannelFlags: 0, + TimeLockDelta: 99, + MinHTLC: 2342135, + MaxHTLC: 13928598, + FeeBaseMSat: 4352345, + FeeProportionalMillionths: 3452352, + ToNode: secondNode, + ExtraOpaqueData: []byte{1, 0}, + } + edge2 := &models.ChannelEdgePolicy{ + SigBytes: testSig.Serialize(), + ChannelID: chanID, + LastUpdate: nextUpdateTime(), + MessageFlags: 1, + ChannelFlags: 1, + TimeLockDelta: 99, + MinHTLC: 2342135, + MaxHTLC: 13928598, + FeeBaseMSat: 4352345, + FeeProportionalMillionths: 90392423, + ToNode: firstNode, + ExtraOpaqueData: []byte{1, 0}, } return edgeInfo, edge1, edge2 } -func testEdgeInfoUpdates(t *testing.T, v lnwire.GossipVersion) { +func TestEdgeInfoUpdates(t *testing.T) { t.Parallel() ctx := t.Context() - graph := NewVersionedGraph( - MakeTestGraph(t, WithSyncGraphCachePopulation()), v, - ) + graph := MakeTestGraph(t) // We'd like to test the update of edges inserted into the database, so // we create two vertexes to connect. - node1 := createTestVertex(t, v) - require.NoError(t, graph.AddNode(ctx, node1)) - assertNodeInCache(t, graph.ChannelGraph, node1, testFeatures) - node2 := createTestVertex(t, v) - require.NoError(t, graph.AddNode(ctx, node2)) - assertNodeInCache(t, graph.ChannelGraph, node2, testFeatures) + node1 := createTestVertex(t) + if err := graph.AddNode(ctx, node1); err != nil { + t.Fatalf("unable to add node: %v", err) + } + assertNodeInCache(t, graph, node1, testFeatures) + node2 := createTestVertex(t) + if err := graph.AddNode(ctx, node2); err != nil { + t.Fatalf("unable to add node: %v", err) + } + assertNodeInCache(t, graph, node2, testFeatures) // Create an edge and add it to the db. - edgeInfo, edge1, edge2 := createChannelEdge(node1, node2, v) + edgeInfo, edge1, edge2 := createChannelEdge(node1, node2) // Make sure inserting the policy at this point, before the edge info // is added, will fail. err := graph.UpdateEdgePolicy(ctx, edge1) require.ErrorIs(t, err, ErrEdgeNotFound) - require.Len(t, graph.cache.graphCache.nodeChannels, 0) + require.Len(t, graph.graphCache.nodeChannels, 0) // Add the edge info. - require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo)) - assertEdgeWithNoPoliciesInCache(t, graph.ChannelGraph, edgeInfo) + if err := graph.AddChannelEdge(ctx, edgeInfo); err != nil { + t.Fatalf("unable to create channel edge: %v", err) + } + assertEdgeWithNoPoliciesInCache(t, graph, edgeInfo) chanID := edgeInfo.ChannelID outpoint := edgeInfo.ChannelPoint // Next, insert both edge policies into the database, they should both // be inserted without any issues. - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge1)) - assertEdgeWithPolicyInCache( - t, graph.ChannelGraph, edgeInfo, edge1, true, - ) - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge2)) - assertEdgeWithPolicyInCache( - t, graph.ChannelGraph, edgeInfo, edge2, false, - ) + if err := graph.UpdateEdgePolicy(ctx, edge1); err != nil { + t.Fatalf("unable to update edge: %v", err) + } + assertEdgeWithPolicyInCache(t, graph, edgeInfo, edge1, true) + if err := graph.UpdateEdgePolicy(ctx, edge2); err != nil { + t.Fatalf("unable to update edge: %v", err) + } + assertEdgeWithPolicyInCache(t, graph, edgeInfo, edge2, false) // Check for existence of the edge within the database, it should be // found. - found, isZombie, err := graph.HasChannelEdge(ctx, chanID) + _, _, found, isZombie, err := graph.HasChannelEdge(chanID) require.NoError(t, err, "unable to query for edge") - require.True(t, found) - require.False(t, isZombie) + if !found { + t.Fatalf("graph should have of inserted edge") + } + if isZombie { + t.Fatal("live edge should not be marked as zombie") + } // We should also be able to retrieve the channelID only knowing the // channel point of the channel. - dbChanID, err := graph.ChannelID(ctx, &outpoint) + dbChanID, err := graph.ChannelID(&outpoint) require.NoError(t, err, "unable to retrieve channel ID") - require.Equal(t, chanID, dbChanID) + if dbChanID != chanID { + t.Fatalf("chan ID's mismatch, expected %v got %v", dbChanID, + chanID) + } // With the edges inserted, perform some queries to ensure that they've // been inserted properly. - dbEdgeInfo, dbEdge1, dbEdge2, err := graph.FetchChannelEdgesByID( - ctx, chanID, - ) + dbEdgeInfo, dbEdge1, dbEdge2, err := graph.FetchChannelEdgesByID(chanID) require.NoError(t, err, "unable to fetch channel by ID") - compareEdgePolicies(t, dbEdge1, edge1) - compareEdgePolicies(t, dbEdge2, edge2) + if err := compareEdgePolicies(dbEdge1, edge1); err != nil { + t.Fatalf("edge doesn't match: %v", err) + } + if err := compareEdgePolicies(dbEdge2, edge2); err != nil { + t.Fatalf("edge doesn't match: %v", err) + } assertEdgeInfoEqual(t, dbEdgeInfo, edgeInfo) // Next, attempt to query the channel edges according to the outpoint // of the channel. dbEdgeInfo, dbEdge1, dbEdge2, err = graph.FetchChannelEdgesByOutpoint( - ctx, &outpoint, + &outpoint, ) require.NoError(t, err, "unable to fetch channel by ID") - compareEdgePolicies(t, dbEdge1, edge1) - compareEdgePolicies(t, dbEdge2, edge2) + if err := compareEdgePolicies(dbEdge1, edge1); err != nil { + t.Fatalf("edge doesn't match: %v", err) + } + if err := compareEdgePolicies(dbEdge2, edge2); err != nil { + t.Fatalf("edge doesn't match: %v", err) + } assertEdgeInfoEqual(t, dbEdgeInfo, edgeInfo) } -// testEdgePolicyCRUD tests basic CRUD operations for edge policies. -func testEdgePolicyCRUD(t *testing.T, v lnwire.GossipVersion) { +// TestEdgePolicyCRUD tests basic CRUD operations for edge policies. +func TestEdgePolicyCRUD(t *testing.T) { t.Parallel() ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), v) + graph := MakeTestGraph(t) - node1 := createTestVertex(t, v) - node2 := createTestVertex(t, v) + node1 := createTestVertex(t) + node2 := createTestVertex(t) // Create an edge. Don't add it to the DB yet. - edgeInfo, shortChanID := createEdge( - v, 100, 1, 0, 0, node1, node2, - ) - chanID := shortChanID.ToUint64() - - edge1 := newEdgePolicy(v, chanID, nextUpdateTime().Unix(), true) - edge2 := newEdgePolicy(v, chanID, nextUpdateTime().Unix(), false) - edge1.ToNode = edgeInfo.NodeKey2Bytes - edge2.ToNode = edgeInfo.NodeKey1Bytes - edge1.SigBytes = testSig.Serialize() - edge2.SigBytes = testSig.Serialize() + edgeInfo, edge1, edge2 := createChannelEdge(node1, node2) updateAndAssertPolicies := func() { // Make copies of the policies before calling UpdateEdgePolicy @@ -1262,14 +995,8 @@ func testEdgePolicyCRUD(t *testing.T, v lnwire.GossipVersion) { edge1 := copyEdgePolicy(edge1) edge2 := copyEdgePolicy(edge2) - switch v { - case lnwire.GossipVersion1: - edge1.LastUpdate = nextUpdateTime() - edge2.LastUpdate = nextUpdateTime() - case lnwire.GossipVersion2: - edge1.LastBlockHeight = nextBlockHeight() - edge2.LastBlockHeight = nextBlockHeight() - } + edge1.LastUpdate = nextUpdateTime() + edge2.LastUpdate = nextUpdateTime() require.NoError(t, graph.UpdateEdgePolicy(ctx, edge1)) require.NoError(t, graph.UpdateEdgePolicy(ctx, edge2)) @@ -1285,13 +1012,16 @@ func testEdgePolicyCRUD(t *testing.T, v lnwire.GossipVersion) { // assert that the deserialized policies match the original // ones. err := graph.ForEachChannel( - ctx, - func(info *models.ChannelEdgeInfo, + ctx, func(info *models.ChannelEdgeInfo, policy1 *models.ChannelEdgePolicy, policy2 *models.ChannelEdgePolicy) error { - compareEdgePolicies(t, edge1, policy1) - compareEdgePolicies(t, edge2, policy2) + require.NoError( + t, compareEdgePolicies(edge1, policy1), + ) + require.NoError( + t, compareEdgePolicies(edge2, policy2), + ) return nil }, func() {}, @@ -1313,26 +1043,13 @@ func testEdgePolicyCRUD(t *testing.T, v lnwire.GossipVersion) { updateAndAssertPolicies() - switch v { - case lnwire.GossipVersion1: - // Update one of the edges to have ChannelFlags include a bit - // unknown to us. - edge1.ChannelFlags |= 1 << 6 + // Update one of the edges to have ChannelFlags include a bit unknown + // to us. + edge1.ChannelFlags |= 1 << 6 - // Update the other edge to have MessageFlags include a bit - // unknown to us. - edge2.MessageFlags |= 1 << 4 - - case lnwire.GossipVersion2: - // Update one of the edges to have DisableFlags include a bit - // unknown to us. - edge1.DisableFlags |= 1 << 6 - - // Update the other edge to have a modified extra signed field. - edge2.ExtraSignedFields = map[uint64][]byte{ - 200: {0x4, 0x5}, - } - } + // Update the other edge to have MessageFlags include a bit unknown to + // us. + edge2.MessageFlags |= 1 << 4 updateAndAssertPolicies() } @@ -1341,9 +1058,8 @@ func assertNodeInCache(t *testing.T, g *ChannelGraph, n *models.Node, expectedFeatures *lnwire.FeatureVector) { // Let's check the internal view first. - nodeFeatures := g.cache.graphCache.nodeFeatures require.Equal( - t, expectedFeatures, nodeFeatures[n.PubKeyBytes], + t, expectedFeatures, g.graphCache.nodeFeatures[n.PubKeyBytes], ) // The external view should reflect this as well. Except when we expect @@ -1352,19 +1068,19 @@ func assertNodeInCache(t *testing.T, g *ChannelGraph, n *models.Node, if expectedFeatures == nil { expectedFeatures = lnwire.EmptyFeatureVector() } - features := g.cache.graphCache.GetFeatures(n.PubKeyBytes) + features := g.graphCache.GetFeatures(n.PubKeyBytes) require.Equal(t, expectedFeatures, features) } func assertNodeNotInCache(t *testing.T, g *ChannelGraph, n route.Vertex) { - _, ok := g.cache.graphCache.nodeFeatures[n] + _, ok := g.graphCache.nodeFeatures[n] require.False(t, ok) - _, ok = g.cache.graphCache.nodeChannels[n] + _, ok = g.graphCache.nodeChannels[n] require.False(t, ok) // We should get the default features for this node. - features := g.cache.graphCache.GetFeatures(n) + features := g.graphCache.GetFeatures(n) require.Equal(t, lnwire.EmptyFeatureVector(), features) } @@ -1372,8 +1088,8 @@ func assertEdgeWithNoPoliciesInCache(t *testing.T, g *ChannelGraph, e *models.ChannelEdgeInfo) { // Let's check the internal view first. - require.NotEmpty(t, g.cache.graphCache.nodeChannels[e.NodeKey1Bytes]) - require.NotEmpty(t, g.cache.graphCache.nodeChannels[e.NodeKey2Bytes]) + require.NotEmpty(t, g.graphCache.nodeChannels[e.NodeKey1Bytes]) + require.NotEmpty(t, g.graphCache.nodeChannels[e.NodeKey2Bytes]) expectedNode1Channel := &DirectedChannel{ ChannelID: e.ChannelID, @@ -1383,13 +1099,12 @@ func assertEdgeWithNoPoliciesInCache(t *testing.T, g *ChannelGraph, OutPolicySet: false, InPolicy: nil, } - nodeChannels := g.cache.graphCache.nodeChannels require.Contains( - t, nodeChannels[e.NodeKey1Bytes], e.ChannelID, + t, g.graphCache.nodeChannels[e.NodeKey1Bytes], e.ChannelID, ) require.Equal( t, expectedNode1Channel, - nodeChannels[e.NodeKey1Bytes][e.ChannelID], + g.graphCache.nodeChannels[e.NodeKey1Bytes][e.ChannelID], ) expectedNode2Channel := &DirectedChannel{ @@ -1401,16 +1116,16 @@ func assertEdgeWithNoPoliciesInCache(t *testing.T, g *ChannelGraph, InPolicy: nil, } require.Contains( - t, nodeChannels[e.NodeKey2Bytes], e.ChannelID, + t, g.graphCache.nodeChannels[e.NodeKey2Bytes], e.ChannelID, ) require.Equal( t, expectedNode2Channel, - nodeChannels[e.NodeKey2Bytes][e.ChannelID], + g.graphCache.nodeChannels[e.NodeKey2Bytes][e.ChannelID], ) // The external view should reflect this as well. var foundChannel *DirectedChannel - err := g.cache.graphCache.ForEachChannel( + err := g.graphCache.ForEachChannel( e.NodeKey1Bytes, func(c *DirectedChannel) error { if c.ChannelID == e.ChannelID { foundChannel = c @@ -1423,7 +1138,7 @@ func assertEdgeWithNoPoliciesInCache(t *testing.T, g *ChannelGraph, require.NotNil(t, foundChannel) require.Equal(t, expectedNode1Channel, foundChannel) - err = g.cache.graphCache.ForEachChannel( + err = g.graphCache.ForEachChannel( e.NodeKey2Bytes, func(c *DirectedChannel) error { if c.ChannelID == e.ChannelID { foundChannel = c @@ -1440,7 +1155,7 @@ func assertEdgeWithNoPoliciesInCache(t *testing.T, g *ChannelGraph, func assertNoEdge(t *testing.T, g *ChannelGraph, chanID uint64) { // Make sure no channel in the cache has the given channel ID. If there // are no channels at all, that is fine as well. - for _, channels := range g.cache.graphCache.nodeChannels { + for _, channels := range g.graphCache.nodeChannels { for _, channel := range channels { require.NotEqual(t, channel.ChannelID, chanID) } @@ -1451,7 +1166,7 @@ func assertEdgeWithPolicyInCache(t *testing.T, g *ChannelGraph, e *models.ChannelEdgeInfo, p *models.ChannelEdgePolicy, policy1 bool) { // Check the internal state first. - c1, ok := g.cache.graphCache.nodeChannels[e.NodeKey1Bytes][e.ChannelID] + c1, ok := g.graphCache.nodeChannels[e.NodeKey1Bytes][e.ChannelID] require.True(t, ok) if policy1 { @@ -1464,7 +1179,7 @@ func assertEdgeWithPolicyInCache(t *testing.T, g *ChannelGraph, ) } - c2, ok := g.cache.graphCache.nodeChannels[e.NodeKey2Bytes][e.ChannelID] + c2, ok := g.graphCache.nodeChannels[e.NodeKey2Bytes][e.ChannelID] require.True(t, ok) if policy1 { @@ -1482,14 +1197,14 @@ func assertEdgeWithPolicyInCache(t *testing.T, g *ChannelGraph, c1Ext *DirectedChannel c2Ext *DirectedChannel ) - require.NoError(t, g.cache.graphCache.ForEachChannel( + require.NoError(t, g.graphCache.ForEachChannel( e.NodeKey1Bytes, func(c *DirectedChannel) error { c1Ext = c return nil }, )) - require.NoError(t, g.cache.graphCache.ForEachChannel( + require.NoError(t, g.graphCache.ForEachChannel( e.NodeKey2Bytes, func(c *DirectedChannel) error { c2Ext = c @@ -1527,20 +1242,16 @@ func assertEdgeWithPolicyInCache(t *testing.T, g *ChannelGraph, func randEdgePolicy(chanID uint64) *models.ChannelEdgePolicy { update := prand.Int63() - return newEdgePolicy(lnwire.GossipVersion1, chanID, update, true) + return newEdgePolicy(chanID, update) } func copyEdgePolicy(p *models.ChannelEdgePolicy) *models.ChannelEdgePolicy { return &models.ChannelEdgePolicy{ - Version: p.Version, SigBytes: p.SigBytes, ChannelID: p.ChannelID, LastUpdate: p.LastUpdate, - LastBlockHeight: p.LastBlockHeight, - SecondPeer: p.SecondPeer, MessageFlags: p.MessageFlags, ChannelFlags: p.ChannelFlags, - DisableFlags: p.DisableFlags, TimeLockDelta: p.TimeLockDelta, MinHTLC: p.MinHTLC, MaxHTLC: p.MaxHTLC, @@ -1548,76 +1259,49 @@ func copyEdgePolicy(p *models.ChannelEdgePolicy) *models.ChannelEdgePolicy { FeeProportionalMillionths: p.FeeProportionalMillionths, ToNode: p.ToNode, ExtraOpaqueData: p.ExtraOpaqueData, - ExtraSignedFields: p.ExtraSignedFields, } } -func newEdgePolicy(v lnwire.GossipVersion, chanID uint64, - updateTime int64, isNode1 bool) *models.ChannelEdgePolicy { - - policy := &models.ChannelEdgePolicy{ - Version: v, - SecondPeer: !isNode1, +func newEdgePolicy(chanID uint64, updateTime int64) *models.ChannelEdgePolicy { + return &models.ChannelEdgePolicy{ ChannelID: chanID, + LastUpdate: time.Unix(updateTime, 0), + MessageFlags: 1, + ChannelFlags: 0, TimeLockDelta: uint16(prand.Int63()), MinHTLC: lnwire.MilliSatoshi(prand.Int63()), MaxHTLC: lnwire.MilliSatoshi(prand.Int63()), FeeBaseMSat: lnwire.MilliSatoshi(prand.Int63()), FeeProportionalMillionths: lnwire.MilliSatoshi(prand.Int63()), } - - if v == lnwire.GossipVersion1 { - policy.LastUpdate = time.Unix(updateTime, 0) - policy.MessageFlags = 1 - if !isNode1 { - policy.ChannelFlags = lnwire.ChanUpdateDirection - } - policy.ExtraOpaqueData = []byte{1, 0} - } else { - policy.LastBlockHeight = nextBlockHeight() - policy.DisableFlags = 0 - policy.ExtraSignedFields = map[uint64][]byte{ - 100: {0x1, 0x2, 0x3}, - } - } - - return policy } -// testAddEdgeProof tests the ability to add an edge proof to an existing edge. -func testAddEdgeProof(t *testing.T, v lnwire.GossipVersion) { +// TestAddEdgeProof tests the ability to add an edge proof to an existing edge. +func TestAddEdgeProof(t *testing.T) { t.Parallel() ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), v) + graph := MakeTestGraph(t) // Add an edge with no proof. - node1 := createTestVertex(t, v) - node2 := createTestVertex(t, v) - - // Create edge without proof (skipProof = true). - edge1, _ := createEdge(v, 100, 0, 0, 0, node1, node2, true) + node1 := createTestVertex(t) + node2 := createTestVertex(t) + edge1, _, _ := createChannelEdge(node1, node2, withSkipProofs()) require.NoError(t, graph.AddChannelEdge(ctx, edge1)) - // Fetch the edge and assert that the proof is nil. - dbEdge, _, _, err := graph.FetchChannelEdgesByID( - ctx, edge1.ChannelID, - ) + // Fetch the edge and assert that the proof is nil and that the rest + // of the edge info is correct. + dbEdge, _, _, err := graph.FetchChannelEdgesByID(edge1.ChannelID) require.NoError(t, err) require.Nil(t, dbEdge.AuthProof) + require.Equal(t, edge1, dbEdge) - // Create a proof appropriate for the version. - var proof *models.ChannelAuthProof - switch v { - case lnwire.GossipVersion1: - proof = models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ) - case lnwire.GossipVersion2: - proof = models.NewV2ChannelAuthProof(testSig.Serialize()) + // Now, add the edge proof. + proof := &models.ChannelAuthProof{ + NodeSig1Bytes: testSig.Serialize(), + NodeSig2Bytes: testSig.Serialize(), + BitcoinSig1Bytes: testSig.Serialize(), + BitcoinSig2Bytes: testSig.Serialize(), } // First, add the proof to the rest of the channel edge info and try @@ -1625,42 +1309,41 @@ func testAddEdgeProof(t *testing.T, v lnwire.GossipVersion) { // already existing. edge1.AuthProof = proof err = graph.AddChannelEdge(ctx, edge1) - require.ErrorIs(t, err, ErrEdgeAlreadyExist) + require.Error(t, err, ErrEdgeAlreadyExist) - // Now add just the proof via AddEdgeProof. + // Now add just the proof. scid1 := lnwire.NewShortChanIDFromInt(edge1.ChannelID) - require.NoError(t, graph.AddEdgeProof(ctx, scid1, proof)) + require.NoError(t, graph.AddEdgeProof(scid1, proof)) // Fetch the edge again and assert that the proof is now set. - dbEdge, _, _, err = graph.FetchChannelEdgesByID( - ctx, edge1.ChannelID, - ) + dbEdge, _, _, err = graph.FetchChannelEdgesByID(edge1.ChannelID) require.NoError(t, err) require.NotNil(t, dbEdge.AuthProof) + require.Equal(t, edge1, dbEdge) // For completeness, also test the case where we insert a new edge with - // an edge proof from the start. Show that the proof is present. - edge2, _ := createEdge(v, 200, 0, 0, 1, node1, node2) + // an edge proof. Show that the proof is present from the get go. + edge2, _, _ := createChannelEdge(node1, node2) require.NoError(t, graph.AddChannelEdge(ctx, edge2)) - // Fetch the edge and assert that the proof is set. - dbEdge2, _, _, err := graph.FetchChannelEdgesByID( - ctx, edge2.ChannelID, - ) + // Fetch the edge and assert that the proof is nil and that the rest + // of the edge info is correct. + dbEdge2, _, _, err := graph.FetchChannelEdgesByID(edge2.ChannelID) require.NoError(t, err) require.NotNil(t, dbEdge2.AuthProof) + require.Equal(t, edge2, dbEdge2) } -// testForEachSourceNodeChannel tests that the ForEachSourceNodeChannel +// TestForEachSourceNodeChannel tests that the ForEachSourceNodeChannel // correctly iterates through the channels of the set source node. -func testForEachSourceNodeChannel(t *testing.T, v lnwire.GossipVersion) { +func TestForEachSourceNodeChannel(t *testing.T) { t.Parallel() ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), v) + graph := MakeTestGraph(t) // Create a source node (A) and set it as such in the DB. - nodeA := createTestVertex(t, v) + nodeA := createTestVertex(t) require.NoError(t, graph.SetSourceNode(ctx, nodeA)) // Now, create a few more nodes (B, C, D) along with some channels @@ -1676,40 +1359,32 @@ func testForEachSourceNodeChannel(t *testing.T, v lnwire.GossipVersion) { // outgoing policy but for the A-C channel, we will set only an incoming // policy. - nodeB := createTestVertex(t, v) - nodeC := createTestVertex(t, v) - nodeD := createTestVertex(t, v) + nodeB := createTestVertex(t) + nodeC := createTestVertex(t) + nodeD := createTestVertex(t) - abEdge, _ := createEdge(v, 100, 0, 0, 0, nodeA, nodeB) + abEdge, abPolicy1, abPolicy2 := createChannelEdge(nodeA, nodeB) require.NoError(t, graph.AddChannelEdge(ctx, abEdge)) - acEdge, _ := createEdge(v, 200, 0, 0, 1, nodeA, nodeC) + acEdge, acPolicy1, acPolicy2 := createChannelEdge(nodeA, nodeC) require.NoError(t, graph.AddChannelEdge(ctx, acEdge)) - bdEdge, _ := createEdge(v, 300, 0, 0, 2, nodeB, nodeD) + bdEdge, _, _ := createChannelEdge(nodeB, nodeD) require.NoError(t, graph.AddChannelEdge(ctx, bdEdge)) - newPolicy := func(edge *models.ChannelEdgeInfo, fromNode, - toNode route.Vertex) *models.ChannelEdgePolicy { - - isNode1 := bytes.Equal(fromNode[:], edge.NodeKey1Bytes[:]) - policy := newEdgePolicy( - v, edge.ChannelID, nextUpdateTime().Unix(), isNode1, - ) - policy.ToNode = toNode - policy.SigBytes = testSig.Serialize() - - return policy - } - + // Figure out which of the policies returned above are node A's so that + // we know which to persist. + // // First, set the outgoing policy for the A-B channel. - abPolicyAOutgoing := newPolicy( - abEdge, nodeA.PubKeyBytes, nodeB.PubKeyBytes, - ) + abPolicyAOutgoing := abPolicy1 + if !bytes.Equal(abPolicy1.ToNode[:], nodeB.PubKeyBytes[:]) { + abPolicyAOutgoing = abPolicy2 + } require.NoError(t, graph.UpdateEdgePolicy(ctx, abPolicyAOutgoing)) // Now, set the incoming policy for the A-C channel. - acPolicyAIncoming := newPolicy( - acEdge, nodeC.PubKeyBytes, nodeA.PubKeyBytes, - ) + acPolicyAIncoming := acPolicy1 + if !bytes.Equal(acPolicy1.ToNode[:], nodeA.PubKeyBytes[:]) { + acPolicyAIncoming = acPolicy2 + } require.NoError(t, graph.UpdateEdgePolicy(ctx, acPolicyAIncoming)) type sourceNodeChan struct { @@ -1731,47 +1406,37 @@ func testForEachSourceNodeChannel(t *testing.T, v lnwire.GossipVersion) { // Now, we'll use the ForEachSourceNodeChannel and assert that it // returns the expected data in the call-back. - err := graph.ForEachSourceNodeChannel( - ctx, func(chanPoint wire.OutPoint, havePolicy bool, - otherNode *models.Node) error { + err := graph.ForEachSourceNodeChannel(ctx, func(chanPoint wire.OutPoint, + havePolicy bool, otherNode *models.Node) error { - require.Contains(t, expectedSrcChans, chanPoint) - expected := expectedSrcChans[chanPoint] + require.Contains(t, expectedSrcChans, chanPoint) + expected := expectedSrcChans[chanPoint] - require.Equal( - t, expected.otherNode[:], - otherNode.PubKeyBytes[:], - ) - require.Equal(t, expected.havePolicy, havePolicy) + require.Equal( + t, expected.otherNode[:], otherNode.PubKeyBytes[:], + ) + require.Equal(t, expected.havePolicy, havePolicy) - delete(expectedSrcChans, chanPoint) + delete(expectedSrcChans, chanPoint) - return nil - }, func() {}, - ) + return nil + }, func() {}) require.NoError(t, err) require.Empty(t, expectedSrcChans) } -// TestGraphTraversal tests that we can traverse the graph and find all -// nodes and channels that we expect to find. func TestGraphTraversal(t *testing.T) { t.Parallel() ctx := t.Context() - // If we turn the channel graph cache _off_, then iterate through the - // set of channels (to force the fall back), we should find all the - // channel as well as the nodes included. - graph := MakeTestGraph(t, WithUseGraphCache(false)) + graph := MakeTestGraph(t) // We'd like to test some of the graph traversal capabilities within // the DB, so we'll create a series of fake nodes to insert into the // graph. And we'll create 5 channels between each node pair. const numNodes = 20 const numChannels = 5 - chanIndex, nodeList := fillTestGraph( - t, graph, numNodes, numChannels, lnwire.GossipVersion1, - ) + chanIndex, nodeList := fillTestGraph(t, graph, numNodes, numChannels) // Make an index of the node list for easy look up below. nodeIndex := make(map[route.Vertex]struct{}) @@ -1779,39 +1444,39 @@ func TestGraphTraversal(t *testing.T) { nodeIndex[node.PubKeyBytes] = struct{}{} } - err := graph.ForEachNodeCached(ctx, lnwire.GossipVersion1, - func(_ context.Context, node route.Vertex, - chans map[uint64]*DirectedChannel) error { + // If we turn the channel graph cache _off_, then iterate through the + // set of channels (to force the fall back), we should find all the + // channel as well as the nodes included. + graph.graphCache = nil + err := graph.ForEachNodeCached(ctx, false, func(_ context.Context, + node route.Vertex, _ []net.Addr, + chans map[uint64]*DirectedChannel) error { - if _, ok := nodeIndex[node]; !ok { - return fmt.Errorf("node %x not found in graph", - node) + if _, ok := nodeIndex[node]; !ok { + return fmt.Errorf("node %x not found in graph", node) + } + + for chanID := range chans { + if _, ok := chanIndex[chanID]; !ok { + return fmt.Errorf("chan %v not found in "+ + "graph", chanID) } + } - for chanID := range chans { - if _, ok := chanIndex[chanID]; !ok { - return fmt.Errorf( - "chan %v not found in graph", - chanID, - ) - } - } - - return nil - }, func() {}) + return nil + }, func() {}) require.NoError(t, err) // Iterate through all the known channels within the graph DB, once // again if the map is empty that indicates that all edges have // properly been reached. - err = graph.ForEachChannel(ctx, lnwire.GossipVersion1, - func(ei *models.ChannelEdgeInfo, - _ *models.ChannelEdgePolicy, - _ *models.ChannelEdgePolicy) error { + err = graph.ForEachChannel(ctx, func(ei *models.ChannelEdgeInfo, + _ *models.ChannelEdgePolicy, + _ *models.ChannelEdgePolicy) error { - delete(chanIndex, ei.ChannelID) - return nil - }, func() {}) + delete(chanIndex, ei.ChannelID) + return nil + }, func() {}) require.NoError(t, err) require.Len(t, chanIndex, 0) @@ -1820,7 +1485,7 @@ func TestGraphTraversal(t *testing.T) { numNodeChans := 0 firstNode, secondNode := nodeList[0], nodeList[1] err = graph.ForEachNodeChannel( - ctx, lnwire.GossipVersion1, firstNode.PubKeyBytes, + ctx, firstNode.PubKeyBytes, func(_ *models.ChannelEdgeInfo, outEdge, inEdge *models.ChannelEdgePolicy) error { @@ -1857,43 +1522,46 @@ func TestGraphTraversal(t *testing.T) { require.Equal(t, numChannels, numNodeChans) } -// testGraphTraversalCacheable tests that the memory optimized node traversal is +// TestGraphTraversalCacheable tests that the memory optimized node traversal is // working correctly. -func testGraphTraversalCacheable(t *testing.T, v lnwire.GossipVersion) { +func TestGraphTraversalCacheable(t *testing.T) { t.Parallel() ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), v) + graph := MakeTestGraph(t) // We'd like to test some of the graph traversal capabilities within // the DB, so we'll create a series of fake nodes to insert into the // graph. And we'll create 5 channels between the first two nodes. const numNodes = 20 const numChannels = 5 - chanIndex, nodeList := fillTestGraph( - t, graph.ChannelGraph, numNodes, numChannels, v, - ) + chanIndex, _ := fillTestGraph(t, graph, numNodes, numChannels) - // Create a map of all nodes with the nodes we just inserted. + // Create a map of all nodes with the iteration we know works (because + // it is tested in another test). nodeMap := make(map[route.Vertex]struct{}) - for _, node := range nodeList { - nodeMap[node.PubKeyBytes] = struct{}{} - } + err := graph.ForEachNode(ctx, func(n *models.Node) error { + nodeMap[n.PubKeyBytes] = struct{}{} + + return nil + }, func() {}) + require.NoError(t, err) require.Len(t, nodeMap, numNodes) // Iterate through all the known channels within the graph DB by // iterating over each node, once again if the map is empty that // indicates that all edges have properly been reached. var nodes []route.Vertex - err := graph.ForEachNodeCacheable(ctx, - func(node route.Vertex, features *lnwire.FeatureVector) error { - delete(nodeMap, node) - nodes = append(nodes, node) + err = graph.ForEachNodeCacheable(ctx, func(node route.Vertex, + features *lnwire.FeatureVector) error { - return nil - }, func() { - nodes = nil - }) + delete(nodeMap, node) + nodes = append(nodes, node) + + return nil + }, func() { + nodes = nil + }) require.NoError(t, err) require.Len(t, nodeMap, 0) @@ -1906,10 +1574,10 @@ func testGraphTraversalCacheable(t *testing.T, v lnwire.GossipVersion) { } for _, node := range nodes { - // Query the VersionedGraph which uses the cache to iterate + // Query the ChannelGraph which uses the cache to iterate // through the channels for each node. err = graph.ForEachNodeDirectedChannel( - ctx, node, func(d *DirectedChannel) error { + node, func(d *DirectedChannel) error { delete(chanIndex, d.ChannelID) return nil }, func() {}, @@ -1917,8 +1585,8 @@ func testGraphTraversalCacheable(t *testing.T, v lnwire.GossipVersion) { require.NoError(t, err) // Now skip the cache and query the DB directly. - err = graph.db.ForEachNodeDirectedChannel( - ctx, v, node, func(d *DirectedChannel) error { + err = graph.V1Store.ForEachNodeDirectedChannel( + node, func(d *DirectedChannel) error { delete(chanIndex2, d.ChannelID) return nil }, func() {}, @@ -1929,32 +1597,27 @@ func testGraphTraversalCacheable(t *testing.T, v lnwire.GossipVersion) { require.Len(t, chanIndex2, 0) } -// TestGraphCacheTraversal tests traversal of the graph via the graph cache. func TestGraphCacheTraversal(t *testing.T) { t.Parallel() - ctx := t.Context() - // Explicitly enable the graph cache so that the - // ForEachNodeDirectedChannel call below will use the cache. - graph := MakeTestGraph(t, WithUseGraphCache(true)) + graph := MakeTestGraph(t) // We'd like to test some of the graph traversal capabilities within // the DB, so we'll create a series of fake nodes to insert into the // graph. And we'll create 5 channels between each node pair. const numNodes = 20 const numChannels = 5 - chanIndex, nodeList := fillTestGraph( - t, graph, numNodes, numChannels, lnwire.GossipVersion1, - ) + chanIndex, nodeList := fillTestGraph(t, graph, numNodes, numChannels) // Iterate through all the known channels within the graph DB, once // again if the map is empty that indicates that all edges have // properly been reached. numNodeChans := 0 for _, node := range nodeList { + node := node - err := graph.ForEachNodeDirectedChannel( - ctx, node.PubKeyBytes, func(d *DirectedChannel) error { + err := graph.graphCache.ForEachChannel( + node.PubKeyBytes, func(d *DirectedChannel) error { delete(chanIndex, d.ChannelID) if !d.OutPolicySet || d.InPolicy == nil { @@ -1975,8 +1638,6 @@ func TestGraphCacheTraversal(t *testing.T) { numNodeChans++ return nil - }, func() { - numNodeChans = 0 }, ) require.NoError(t, err) @@ -1989,21 +1650,20 @@ func TestGraphCacheTraversal(t *testing.T) { require.Equal(t, numChannels*2*(numNodes-1), numNodeChans) } -// fillTestGraph fills the graph with nodes and channels using the requested -// gossip version. +// fillTestGraph fills the graph with a given number of nodes and create a given +// number of channels between each node. func fillTestGraph(t testing.TB, graph *ChannelGraph, numNodes, - numChannels int, v lnwire.GossipVersion) (map[uint64]struct{}, - []*models.Node) { + numChannels int) (map[uint64]struct{}, []*models.Node) { ctx := t.Context() nodes := make([]*models.Node, numNodes) - nodeIndex := map[route.Vertex]struct{}{} + nodeIndex := map[string]struct{}{} for i := 0; i < numNodes; i++ { - node := createTestVertex(t, v) + node := createTestVertex(t) nodes[i] = node - nodeIndex[node.PubKeyBytes] = struct{}{} + nodeIndex[node.Alias] = struct{}{} } // Add each of the nodes into the graph, they should be inserted @@ -2014,86 +1674,16 @@ func fillTestGraph(t testing.TB, graph *ChannelGraph, numNodes, // Iterate over each node as returned by the graph, if all nodes are // reached, then the map created above should be empty. - err := graph.ForEachNodeCacheable(ctx, v, - func(node route.Vertex, _ *lnwire.FeatureVector) error { - delete(nodeIndex, node) - - return nil - }, func() {}) + err := graph.ForEachNode(ctx, func(n *models.Node) error { + delete(nodeIndex, n.Alias) + return nil + }, func() {}) require.NoError(t, err) require.Len(t, nodeIndex, 0) // Create a number of channels between each of the node pairs generated // above. This will result in numChannels*(numNodes-1) channels. chanIndex := map[uint64]struct{}{} - buildEdgeInfo := func(chanID uint64, node1Key, - node2Key route.Vertex, op wire.OutPoint, - version lnwire.GossipVersion) *models.ChannelEdgeInfo { - - switch version { - case gossipV1: - proof := models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ) - - edgeInfo, err := models.NewV1Channel( - chanID, *chaincfg.MainNetParams.GenesisHash, - node1Key, node2Key, &models.ChannelV1Fields{ - BitcoinKey1Bytes: node1Key, - BitcoinKey2Bytes: node2Key, - }, - models.WithChanProof(proof), - models.WithChannelPoint(op), - models.WithCapacity(1000), - ) - require.NoError(t, err) - - return edgeInfo - - case gossipV2: - var merkleRoot chainhash.Hash - copy(merkleRoot[:], bytes.Repeat([]byte{0xaa}, 32)) - - fundingScript := []byte{0x00, 0x20} - fundingScript = append( - fundingScript, - bytes.Repeat([]byte{0xbb}, 32)..., - ) - - proof := models.NewV2ChannelAuthProof( - testSig.Serialize(), - ) - - v2Fields := &models.ChannelV2Fields{ - BitcoinKey1Bytes: fn.Some(node1Key), - BitcoinKey2Bytes: fn.Some(node2Key), - MerkleRootHash: fn.Some(merkleRoot), - FundingScript: fn.Some(fundingScript), - ExtraSignedFields: make( - map[uint64][]byte, - ), - } - - edgeInfo, err := models.NewV2Channel( - chanID, *chaincfg.MainNetParams.GenesisHash, - node1Key, node2Key, v2Fields, - models.WithChanProof(proof), - models.WithChannelPoint(op), - models.WithCapacity(1000), - ) - require.NoError(t, err) - - return edgeInfo - } - - require.Failf(t, "unknown gossip version", "%v", version) - - return nil - } - for n := 0; n < numNodes-1; n++ { node1 := nodes[n] node2 := nodes[n+1] @@ -2111,30 +1701,38 @@ func fillTestGraph(t testing.TB, graph *ChannelGraph, numNodes, Index: 0, } - var node1Key, node2Key route.Vertex - copy(node1Key[:], node1.PubKeyBytes[:]) - copy(node2Key[:], node2.PubKeyBytes[:]) - - edgeInfo := buildEdgeInfo( - chanID, node1Key, node2Key, op, v, - ) - err = graph.AddChannelEdge(ctx, edgeInfo) + edgeInfo := models.ChannelEdgeInfo{ + ChannelID: chanID, + ChainHash: *chaincfg.MainNetParams.GenesisHash, + AuthProof: &models.ChannelAuthProof{ + NodeSig1Bytes: testSig.Serialize(), + NodeSig2Bytes: testSig.Serialize(), + BitcoinSig1Bytes: testSig.Serialize(), + BitcoinSig2Bytes: testSig.Serialize(), + }, + Features: lnwire.EmptyFeatureVector(), + ChannelPoint: op, + Capacity: 1000, + } + copy(edgeInfo.NodeKey1Bytes[:], node1.PubKeyBytes[:]) + copy(edgeInfo.NodeKey2Bytes[:], node2.PubKeyBytes[:]) + copy(edgeInfo.BitcoinKey1Bytes[:], node1.PubKeyBytes[:]) + copy(edgeInfo.BitcoinKey2Bytes[:], node2.PubKeyBytes[:]) + err := graph.AddChannelEdge(ctx, &edgeInfo) require.NoError(t, err) // Create and add an edge with random data that points // from node1 -> node2. - edge := newEdgePolicy( - v, chanID, prand.Int63(), true, - ) + edge := randEdgePolicy(chanID) + edge.ChannelFlags = 0 edge.ToNode = node2.PubKeyBytes edge.SigBytes = testSig.Serialize() require.NoError(t, graph.UpdateEdgePolicy(ctx, edge)) // Create another random edge that points from // node2 -> node1 this time. - edge = newEdgePolicy( - v, chanID, prand.Int63(), false, - ) + edge = randEdgePolicy(chanID) + edge.ChannelFlags = 1 edge.ToNode = node1.PubKeyBytes edge.SigBytes = testSig.Serialize() require.NoError(t, graph.UpdateEdgePolicy(ctx, edge)) @@ -2149,17 +1747,27 @@ func fillTestGraph(t testing.TB, graph *ChannelGraph, numNodes, func assertPruneTip(t *testing.T, graph *ChannelGraph, blockHash *chainhash.Hash, blockHeight uint32) { - pruneHash, pruneHeight, err := graph.PruneTip(t.Context()) - require.NoError(t, err) - require.Equal(t, blockHash[:], pruneHash[:]) - require.Equal(t, blockHeight, pruneHeight) + pruneHash, pruneHeight, err := graph.PruneTip() + if err != nil { + _, _, line, _ := runtime.Caller(1) + t.Fatalf("line %v: unable to fetch prune tip: %v", line, err) + } + if !bytes.Equal(blockHash[:], pruneHash[:]) { + _, _, line, _ := runtime.Caller(1) + t.Fatalf("line: %v, prune tips don't match, expected %x got %x", + line, blockHash, pruneHash) + } + if pruneHeight != blockHeight { + _, _, line, _ := runtime.Caller(1) + t.Fatalf("line %v: prune heights don't match, expected %v "+ + "got %v", line, blockHeight, pruneHeight) + } } func assertNumChans(t *testing.T, graph *ChannelGraph, n int) { numChans := 0 err := graph.ForEachChannel( - t.Context(), lnwire.GossipVersion1, - func(*models.ChannelEdgeInfo, + t.Context(), func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error { @@ -2175,18 +1783,29 @@ func assertNumChans(t *testing.T, graph *ChannelGraph, n int) { func assertNumNodes(t *testing.T, graph *ChannelGraph, n int) { numNodes := 0 - v1Graph := NewVersionedGraph(graph, lnwire.GossipVersion1) - err := v1Graph.ForEachNode(t.Context(), func(_ *models.Node) error { - numNodes++ + err := graph.ForEachNode(t.Context(), + func(_ *models.Node) error { + numNodes++ - return nil - }, func() {}) - require.NoError(t, err) - require.Equal(t, n, numNodes) + return nil + }, func() {}) + if err != nil { + _, _, line, _ := runtime.Caller(1) + t.Fatalf("line %v: unable to scan nodes: %v", line, err) + } + + if numNodes != n { + _, _, line, _ := runtime.Caller(1) + t.Fatalf("line %v: expected %v nodes, got %v", line, n, + numNodes) + } } func assertChanViewEqual(t *testing.T, a []EdgePoint, b []EdgePoint) { - require.Len(t, b, len(a)) + if len(a) != len(b) { + _, _, line, _ := runtime.Caller(1) + t.Fatalf("line %v: chan views don't match", line) + } chanViewSet := make(map[wire.OutPoint]struct{}) for _, op := range a { @@ -2194,15 +1813,21 @@ func assertChanViewEqual(t *testing.T, a []EdgePoint, b []EdgePoint) { } for _, op := range b { - _, ok := chanViewSet[op.OutPoint] - require.True(t, ok) + if _, ok := chanViewSet[op.OutPoint]; !ok { + _, _, line, _ := runtime.Caller(1) + t.Fatalf("line %v: chanPoint(%v) not found in first "+ + "view", line, op) + } } } func assertChanViewEqualChanPoints(t *testing.T, a []EdgePoint, b []*wire.OutPoint) { - require.Len(t, b, len(a)) + if len(a) != len(b) { + _, _, line, _ := runtime.Caller(1) + t.Fatalf("line %v: chan views don't match", line) + } chanViewSet := make(map[wire.OutPoint]struct{}) for _, op := range a { @@ -2210,8 +1835,11 @@ func assertChanViewEqualChanPoints(t *testing.T, a []EdgePoint, } for _, op := range b { - _, ok := chanViewSet[*op] - require.True(t, ok) + if _, ok := chanViewSet[*op]; !ok { + _, _, line, _ := runtime.Caller(1) + t.Fatalf("line %v: chanPoint(%v) not found in first "+ + "view", line, op) + } } } @@ -2221,8 +1849,10 @@ func TestGraphPruning(t *testing.T) { graph := MakeTestGraph(t) - sourceNode := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, graph.SetSourceNode(ctx, sourceNode)) + sourceNode := createTestVertex(t) + if err := graph.SetSourceNode(ctx, sourceNode); err != nil { + t.Fatalf("unable to set source node: %v", err) + } // As initial set up for the test, we'll create a graph with 5 vertexes // and enough edges to create a fully connected graph. The graph will @@ -2230,8 +1860,11 @@ func TestGraphPruning(t *testing.T) { const numNodes = 5 graphNodes := make([]*models.Node, numNodes) for i := 0; i < numNodes; i++ { - node := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, graph.AddNode(ctx, node)) + node := createTestVertex(t) + + if err := graph.AddNode(ctx, node); err != nil { + t.Fatalf("unable to add node: %v", err) + } graphNodes[i] = node } @@ -2250,33 +1883,37 @@ func TestGraphPruning(t *testing.T) { channelPoints = append(channelPoints, &op) - var node1Key, node2Key route.Vertex - copy(node1Key[:], graphNodes[i].PubKeyBytes[:]) - copy(node2Key[:], graphNodes[i+1].PubKeyBytes[:]) - - proof := models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ) - - edgeInfo, err := models.NewV1Channel( - chanID, *chaincfg.MainNetParams.GenesisHash, - node1Key, node2Key, &models.ChannelV1Fields{ - BitcoinKey1Bytes: node1Key, - BitcoinKey2Bytes: node2Key, + edgeInfo := models.ChannelEdgeInfo{ + ChannelID: chanID, + ChainHash: *chaincfg.MainNetParams.GenesisHash, + AuthProof: &models.ChannelAuthProof{ + NodeSig1Bytes: testSig.Serialize(), + NodeSig2Bytes: testSig.Serialize(), + BitcoinSig1Bytes: testSig.Serialize(), + BitcoinSig2Bytes: testSig.Serialize(), }, - models.WithChanProof(proof), - models.WithChannelPoint(op), - models.WithCapacity(1000), + Features: lnwire.EmptyFeatureVector(), + ChannelPoint: op, + Capacity: 1000, + } + copy(edgeInfo.NodeKey1Bytes[:], graphNodes[i].PubKeyBytes[:]) + copy(edgeInfo.NodeKey2Bytes[:], graphNodes[i+1].PubKeyBytes[:]) + copy(edgeInfo.BitcoinKey1Bytes[:], graphNodes[i].PubKeyBytes[:]) + copy( + edgeInfo.BitcoinKey2Bytes[:], + graphNodes[i+1].PubKeyBytes[:], ) - require.NoError(t, err) - require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo)) - - pkScript, err := edgeInfo.FundingPKScript() - require.NoError(t, err) + if err := graph.AddChannelEdge(ctx, &edgeInfo); err != nil { + t.Fatalf("unable to add node: %v", err) + } + pkScript, err := genMultiSigP2WSH( + edgeInfo.BitcoinKey1Bytes[:], + edgeInfo.BitcoinKey2Bytes[:], + ) + if err != nil { + t.Fatalf("unable to gen multi-sig p2wsh: %v", err) + } edgePoints = append(edgePoints, EdgePoint{ FundingPkScript: pkScript, OutPoint: op, @@ -2288,7 +1925,9 @@ func TestGraphPruning(t *testing.T) { edge.ChannelFlags = 0 edge.ToNode = graphNodes[i].PubKeyBytes edge.SigBytes = testSig.Serialize() - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge)) + if err := graph.UpdateEdgePolicy(ctx, edge); err != nil { + t.Fatalf("unable to update edge: %v", err) + } // Create another random edge that points from node_i+1 -> // node_i this time. @@ -2296,14 +1935,14 @@ func TestGraphPruning(t *testing.T) { edge.ChannelFlags = 1 edge.ToNode = graphNodes[i].PubKeyBytes edge.SigBytes = testSig.Serialize() - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge)) + if err := graph.UpdateEdgePolicy(ctx, edge); err != nil { + t.Fatalf("unable to update edge: %v", err) + } } - v1Graph := NewVersionedGraph(graph, lnwire.GossipVersion1) - // With all the channel points added, we'll consult the graph to ensure // it has the same channel view as the one we just constructed. - channelView, err := v1Graph.ChannelView(ctx) + channelView, err := graph.ChannelView() require.NoError(t, err, "unable to get graph channel view") assertChanViewEqual(t, channelView, edgePoints) @@ -2316,11 +1955,12 @@ func TestGraphPruning(t *testing.T) { copy(blockHash[:], bytes.Repeat([]byte{1}, 32)) blockHeight := uint32(1) block := channelPoints[:2] - prunedChans, err := graph.PruneGraph( - ctx, block, &blockHash, blockHeight, - ) + prunedChans, err := graph.PruneGraph(block, &blockHash, blockHeight) require.NoError(t, err, "unable to prune graph") - require.Len(t, prunedChans, 2) + if len(prunedChans) != 2 { + t.Fatalf("incorrect number of channels pruned: "+ + "expected %v, got %v", 2, prunedChans) + } // Now ensure that the prune tip has been updated. assertPruneTip(t, graph, &blockHash, blockHeight) @@ -2330,7 +1970,7 @@ func TestGraphPruning(t *testing.T) { assertNumChans(t, graph, 2) // Those channels should also be missing from the channel view. - channelView, err = v1Graph.ChannelView(ctx) + channelView, err = graph.ChannelView() require.NoError(t, err, "unable to get graph channel view") assertChanViewEqualChanPoints(t, channelView, channelPoints[2:]) @@ -2344,12 +1984,14 @@ func TestGraphPruning(t *testing.T) { blockHash = sha256.Sum256(blockHash[:]) blockHeight = 2 prunedChans, err = graph.PruneGraph( - ctx, []*wire.OutPoint{nonChannel}, &blockHash, blockHeight, + []*wire.OutPoint{nonChannel}, &blockHash, blockHeight, ) require.NoError(t, err, "unable to prune graph") // No channels should have been detected as pruned. - require.Empty(t, prunedChans) + if len(prunedChans) != 0 { + t.Fatalf("channels were pruned but shouldn't have been") + } // Once again, the prune tip should have been updated. We should still // see both channels and their participants, along with the source node. @@ -2362,13 +2004,16 @@ func TestGraphPruning(t *testing.T) { blockHash = sha256.Sum256(blockHash[:]) blockHeight = 3 prunedChans, err = graph.PruneGraph( - ctx, channelPoints[2:], &blockHash, blockHeight, + channelPoints[2:], &blockHash, blockHeight, ) require.NoError(t, err, "unable to prune graph") // The remainder of the channels should have been pruned from the // graph. - require.Len(t, prunedChans, 2) + if len(prunedChans) != 2 { + t.Fatalf("incorrect number of channels pruned: "+ + "expected %v, got %v", 2, len(prunedChans)) + } // The prune tip should be updated, no channels should be found, and // only the source node should remain within the current graph. @@ -2379,52 +2024,71 @@ func TestGraphPruning(t *testing.T) { // Finally, the channel view at this point in the graph should now be // completely empty. Those channels should also be missing from the // channel view. - channelView, err = v1Graph.ChannelView(ctx) + channelView, err = graph.ChannelView() require.NoError(t, err, "unable to get graph channel view") - require.Empty(t, channelView) + if len(channelView) != 0 { + t.Fatalf("channel view should be empty, instead have: %v", + channelView) + } } // TestHighestChanID tests that we're able to properly retrieve the highest // known channel ID in the database. -func testHighestChanID(t *testing.T, v lnwire.GossipVersion) { +func TestHighestChanID(t *testing.T) { t.Parallel() ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), v) + graph := MakeTestGraph(t) // If we don't yet have any channels in the database, then we should // get a channel ID of zero if we ask for the highest channel ID. bestID, err := graph.HighestChanID(ctx) require.NoError(t, err, "unable to get highest ID") - require.Zero(t, bestID) + if bestID != 0 { + t.Fatalf("best ID w/ no chan should be zero, is instead: %v", + bestID) + } // Next, we'll insert two channels into the database, with each channel // connecting the same two nodes. - node1 := createTestVertex(t, v) - node2 := createTestVertex(t, v) + node1 := createTestVertex(t) + node2 := createTestVertex(t) // The first channel with be at height 10, while the other will be at // height 100. - edge1, _ := createEdge(v, 10, 0, 0, 0, node1, node2) - edge2, chanID2 := createEdge(v, 100, 0, 0, 0, node1, node2) + edge1, _ := createEdge(10, 0, 0, 0, node1, node2) + edge2, chanID2 := createEdge(100, 0, 0, 0, node1, node2) - require.NoError(t, graph.AddChannelEdge(ctx, edge1)) - require.NoError(t, graph.AddChannelEdge(ctx, edge2)) + if err := graph.AddChannelEdge(ctx, &edge1); err != nil { + t.Fatalf("unable to create channel edge: %v", err) + } + if err := graph.AddChannelEdge(ctx, &edge2); err != nil { + t.Fatalf("unable to create channel edge: %v", err) + } // Now that the edges has been inserted, we'll query for the highest // known channel ID in the database. bestID, err = graph.HighestChanID(ctx) require.NoError(t, err, "unable to get highest ID") - require.Equal(t, chanID2.ToUint64(), bestID) + + if bestID != chanID2.ToUint64() { + t.Fatalf("expected %v got %v for best chan ID: ", + chanID2.ToUint64(), bestID) + } // If we add another edge, then the current best chan ID should be // updated as well. - edge3, chanID3 := createEdge(v, 1000, 0, 0, 0, node1, node2) - require.NoError(t, graph.AddChannelEdge(ctx, edge3)) + edge3, chanID3 := createEdge(1000, 0, 0, 0, node1, node2) + if err := graph.AddChannelEdge(ctx, &edge3); err != nil { + t.Fatalf("unable to create channel edge: %v", err) + } bestID, err = graph.HighestChanID(ctx) require.NoError(t, err, "unable to get highest ID") - require.Equal(t, chanID3.ToUint64(), bestID) + if bestID != chanID3.ToUint64() { + t.Fatalf("expected %v got %v for best chan ID: ", + chanID3.ToUint64(), bestID) + } } // TestChanUpdatesInHorizon tests the we're able to properly retrieve all known @@ -2434,26 +2098,31 @@ func TestChanUpdatesInHorizon(t *testing.T) { t.Parallel() ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), lnwire.GossipVersion1) + graph := MakeTestGraph(t) // If we issue an arbitrary query before any channel updates are // inserted in the database, we should get zero results. chanIter := graph.ChanUpdatesInHorizon( - ctx, ChanUpdateRange{ - StartTime: fn.Some(time.Unix(999, 0)), - EndTime: fn.Some(time.Unix(9999, 0)), - }, + time.Unix(999, 0), time.Unix(9999, 0), ) chanUpdates, err := fn.CollectErr(chanIter) require.NoError(t, err, "unable to updates for updates") - require.Empty(t, chanUpdates) + + if len(chanUpdates) != 0 { + t.Fatalf("expected 0 chan updates, instead got %v", + len(chanUpdates)) + } // We'll start by creating two nodes which will seed our test graph. - node1 := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, graph.AddNode(ctx, node1)) - node2 := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, graph.AddNode(ctx, node2)) + node1 := createTestVertex(t) + if err := graph.AddNode(ctx, node1); err != nil { + t.Fatalf("unable to add node: %v", err) + } + node2 := createTestVertex(t) + if err := graph.AddNode(ctx, node2); err != nil { + t.Fatalf("unable to add node: %v", err) + } // We'll now create 10 channels between the two nodes, with update // times 10 seconds after each other. @@ -2463,35 +2132,39 @@ func TestChanUpdatesInHorizon(t *testing.T) { edges := make([]ChannelEdge, 0, numChans) for i := 0; i < numChans; i++ { channel, chanID := createEdge( - lnwire.GossipVersion1, uint32(i*10), 0, 0, 0, - node1, node2, + uint32(i*10), 0, 0, 0, node1, node2, ) - require.NoError(t, graph.AddChannelEdge(ctx, channel)) + + if err := graph.AddChannelEdge(ctx, &channel); err != nil { + t.Fatalf("unable to create channel edge: %v", err) + } edge1UpdateTime := endTime edge2UpdateTime := edge1UpdateTime.Add(time.Second) endTime = endTime.Add(time.Second * 10) edge1 := newEdgePolicy( - lnwire.GossipVersion1, chanID.ToUint64(), - edge1UpdateTime.Unix(), true, + chanID.ToUint64(), edge1UpdateTime.Unix(), ) edge1.ChannelFlags = 0 edge1.ToNode = node2.PubKeyBytes edge1.SigBytes = testSig.Serialize() - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge1)) + if err := graph.UpdateEdgePolicy(ctx, edge1); err != nil { + t.Fatalf("unable to update edge: %v", err) + } edge2 := newEdgePolicy( - lnwire.GossipVersion1, chanID.ToUint64(), - edge2UpdateTime.Unix(), false, + chanID.ToUint64(), edge2UpdateTime.Unix(), ) edge2.ChannelFlags = 1 edge2.ToNode = node1.PubKeyBytes edge2.SigBytes = testSig.Serialize() - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge2)) + if err := graph.UpdateEdgePolicy(ctx, edge2); err != nil { + t.Fatalf("unable to update edge: %v", err) + } edges = append(edges, ChannelEdge{ - Info: channel, + Info: &channel, Policy1: edge1, Policy2: edge2, }) @@ -2549,15 +2222,19 @@ func TestChanUpdatesInHorizon(t *testing.T) { } for _, queryCase := range queryCases { respIter := graph.ChanUpdatesInHorizon( - ctx, ChanUpdateRange{ - StartTime: fn.Some(queryCase.start), - EndTime: fn.Some(queryCase.end), - }, + queryCase.start, queryCase.end, ) resp, err := fn.CollectErr(respIter) - require.NoError(t, err) - require.Len(t, resp, len(queryCase.resp)) + if err != nil { + t.Fatalf("unable to query for updates: %v", err) + } + + if len(resp) != len(queryCase.resp) { + t.Fatalf("expected %v chans, got %v chans", + len(queryCase.resp), len(resp)) + + } for i := 0; i < len(resp); i++ { chanExp := queryCase.resp[i] @@ -2565,12 +2242,15 @@ func TestChanUpdatesInHorizon(t *testing.T) { assertEdgeInfoEqual(t, chanExp.Info, chanRet.Info) - compareEdgePolicies( - t, chanExp.Policy1, chanRet.Policy1, + err = compareEdgePolicies( + chanExp.Policy1, chanRet.Policy1, ) - compareEdgePolicies( - t, chanExp.Policy2, chanRet.Policy2, + require.NoError(t, err) + + err = compareEdgePolicies( + chanExp.Policy2, chanRet.Policy2, ) + require.NoError(t, err) } } } @@ -2581,7 +2261,7 @@ func TestNodeUpdatesInHorizon(t *testing.T) { t.Parallel() ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), lnwire.GossipVersion1) + graph := MakeTestGraph(t) startTime := time.Unix(1234, 0) endTime := startTime @@ -2589,10 +2269,7 @@ func TestNodeUpdatesInHorizon(t *testing.T) { // If we issue an arbitrary query before we insert any nodes into the // database, then we shouldn't get any results back. nodeUpdatesIter := graph.NodeUpdatesInHorizon( - ctx, NodeUpdateRange{ - StartTime: fn.Some(time.Unix(999, 0)), - EndTime: fn.Some(time.Unix(9999, 0)), - }, + time.Unix(999, 0), time.Unix(9999, 0), ) nodeUpdates, err := fn.CollectErr(nodeUpdatesIter) require.NoError(t, err, "unable to query for node updates") @@ -2603,7 +2280,7 @@ func TestNodeUpdatesInHorizon(t *testing.T) { const numNodes = 10 nodeAnns := make([]models.Node, 0, numNodes) for i := 0; i < numNodes; i++ { - nodeAnn := createTestVertex(t, lnwire.GossipVersion1) + nodeAnn := createTestVertex(t) // The node ann will use the current end time as its last // update them, then we'll add 10 seconds in order to create @@ -2667,10 +2344,7 @@ func TestNodeUpdatesInHorizon(t *testing.T) { } for _, queryCase := range queryCases { iter := graph.NodeUpdatesInHorizon( - ctx, NodeUpdateRange{ - StartTime: fn.Some(queryCase.start), - EndTime: fn.Some(queryCase.end), - }, + queryCase.start, queryCase.end, ) resp, err := fn.CollectErr(iter) @@ -2678,96 +2352,11 @@ func TestNodeUpdatesInHorizon(t *testing.T) { require.Len(t, resp, len(queryCase.resp)) for i := 0; i < len(resp); i++ { - compareNodes(t, &queryCase.resp[i], resp[i]) + compareNodes(t, &queryCase.resp[i], &resp[i]) } } } -// TestNodeUpdatesInHorizonPublicOnly tests that NodeUpdatesInHorizon with -// WithIterPublicNodesOnly returns only nodes that have at least one public -// channel (one with a channel announcement proof). -func TestNodeUpdatesInHorizonPublicOnly(t *testing.T) { - t.Parallel() - ctx := t.Context() - - chanGraph := MakeTestGraph(t) - graph := NewVersionedGraph(chanGraph, lnwire.GossipVersion1) - - startTime := time.Unix(1000, 0) - - // Create 4 nodes: we'll make node pairs where one pair has a public - // channel (with proof) and the other has a private channel (no proof). - publicNode1 := createTestVertex(t, lnwire.GossipVersion1) - publicNode1.LastUpdate = startTime.Add(10 * time.Second) - - // Set publicNode1 as the source node (required before adding - // channel edges). - require.NoError(t, chanGraph.SetSourceNode(ctx, publicNode1)) - - publicNode2 := createTestVertex(t, lnwire.GossipVersion1) - publicNode2.LastUpdate = startTime.Add(20 * time.Second) - require.NoError(t, chanGraph.AddNode(ctx, publicNode2)) - - // privateNode has a channel to the source node (publicNode1) but - // without a proof, so it remains private in both KV and SQL backends. - privateNode := createTestVertex(t, lnwire.GossipVersion1) - privateNode.LastUpdate = startTime.Add(30 * time.Second) - require.NoError(t, chanGraph.AddNode(ctx, privateNode)) - - // Create a standalone node with no channels at all. - lonelyNode := createTestVertex(t, lnwire.GossipVersion1) - lonelyNode.LastUpdate = startTime.Add(40 * time.Second) - require.NoError(t, chanGraph.AddNode(ctx, lonelyNode)) - - // Add a public channel between publicNode1 and publicNode2 - // (with proof, making both nodes public). - publicEdge, _ := createEdge( - lnwire.GossipVersion1, 100, 0, 0, 0, - publicNode1, publicNode2, - ) - require.NoError(t, chanGraph.AddChannelEdge(ctx, publicEdge)) - - // Add a private channel between publicNode1 (source) and - // privateNode (no proof, so privateNode remains private). - privateEdge, _ := createEdge( - lnwire.GossipVersion1, 200, 0, 0, 1, - publicNode1, privateNode, true, // skipProof - ) - require.NoError(t, chanGraph.AddChannelEdge(ctx, privateEdge)) - - // Query without the public-only filter — should return all 4 nodes. - endTime := startTime.Add(60 * time.Second) - r := NodeUpdateRange{ - StartTime: fn.Some(startTime), - EndTime: fn.Some(endTime), - } - allIter := graph.NodeUpdatesInHorizon(ctx, r) - allNodes, err := fn.CollectErr(allIter) - require.NoError(t, err) - require.Len(t, allNodes, 4) - - // Query with the public-only filter — should return only the 2 - // public nodes. - publicIter := graph.NodeUpdatesInHorizon( - ctx, r, WithIterPublicNodesOnly(), - ) - publicNodes, err := fn.CollectErr(publicIter) - require.NoError(t, err) - require.Len(t, publicNodes, 2) - - // Verify the returned nodes are exactly the public ones. - pub1Key := publicNode1.PubKeyBytes - pub2Key := publicNode2.PubKeyBytes - for _, node := range publicNodes { - require.True( - t, node.PubKeyBytes == pub1Key || - node.PubKeyBytes == pub2Key, - "unexpected node in public-only results: %x", - node.PubKeyBytes, - ) - } -} - // testNodeUpdatesWithBatchSize is a helper function that tests node updates // with a specific batch size to ensure the iterator works correctly across // batch boundaries. @@ -2775,14 +2364,14 @@ func testNodeUpdatesWithBatchSize(t *testing.T, ctx context.Context, batchSize int) { // Create a fresh graph for each test. - testGraph := NewVersionedGraph(MakeTestGraph(t), lnwire.GossipVersion1) + testGraph := MakeTestGraph(t) // Add 25 nodes with increasing timestamps. startTime := time.Unix(1234567890, 0) var nodeAnns []models.Node for i := 0; i < 25; i++ { - nodeAnn := createTestVertex(t, lnwire.GossipVersion1) + nodeAnn := createTestVertex(t) nodeAnn.LastUpdate = startTime.Add( time.Duration(i) * time.Hour, ) @@ -2804,15 +2393,12 @@ func testNodeUpdatesWithBatchSize(t *testing.T, ctx context.Context, end: startTime.Add(26 * time.Hour), want: 25, }, - // The end time is exclusive per BOLT 07, so we - // add one extra hour to include the last node in - // the desired range. { name: "first batch only", start: startTime, end: startTime.Add( time.Duration( - min(batchSize, 25), + min(batchSize, 25)-1, ) * time.Hour, ), want: min(batchSize, 25), @@ -2822,7 +2408,7 @@ func testNodeUpdatesWithBatchSize(t *testing.T, ctx context.Context, start: startTime, end: startTime.Add( time.Duration( - min(batchSize+1, 25), + min(batchSize, 24), ) * time.Hour, ), want: min(batchSize+1, 25), @@ -2847,19 +2433,16 @@ func testNodeUpdatesWithBatchSize(t *testing.T, ctx context.Context, ) }(), end: func() time.Time { - // End is exclusive, so we add - // one hour to include the node - // at exactly the start time. if batchSize <= 25 { return startTime.Add( time.Duration( - batchSize, + batchSize-1, ) * time.Hour, ) } return startTime.Add( - time.Duration(26) * time.Hour, + time.Duration(25) * time.Hour, ) }(), want: func() int { @@ -2889,10 +2472,7 @@ func testNodeUpdatesWithBatchSize(t *testing.T, ctx context.Context, for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { iter := testGraph.NodeUpdatesInHorizon( - ctx, NodeUpdateRange{ - StartTime: fn.Some(tc.start), - EndTime: fn.Some(tc.end), - }, + tc.start, tc.end, WithNodeUpdateIterBatchSize( batchSize, ), @@ -2948,13 +2528,13 @@ func TestNodeUpdatesInHorizonEarlyTermination(t *testing.T) { t.Parallel() ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), lnwire.GossipVersion1) + graph := MakeTestGraph(t) // We'll start by creating 100 nodes, each with an update time spaced // one hour apart. startTime := time.Unix(1234567890, 0) for i := 0; i < 100; i++ { - nodeAnn := createTestVertex(t, lnwire.GossipVersion1) + nodeAnn := createTestVertex(t) nodeAnn.LastUpdate = startTime.Add(time.Duration(i) * time.Hour) require.NoError(t, graph.AddNode(ctx, nodeAnn)) } @@ -2965,17 +2545,12 @@ func TestNodeUpdatesInHorizonEarlyTermination(t *testing.T) { for _, stopAt := range terminationPoints { t.Run(fmt.Sprintf("StopAt%d", stopAt), func(t *testing.T) { iter := graph.NodeUpdatesInHorizon( - ctx, NodeUpdateRange{ - StartTime: fn.Some(startTime), - EndTime: fn.Some( - startTime.Add(200 * time.Hour), - ), - }, + startTime, startTime.Add(200*time.Hour), WithNodeUpdateIterBatchSize(10), ) // Collect only up to stopAt nodes, breaking afterwards. - var collected []*models.Node + var collected []models.Node count := 0 for node := range iter { if count >= stopAt { @@ -3007,11 +2582,9 @@ func TestChanUpdatesInHorizonBoundaryConditions(t *testing.T) { t.Run(testName, func(t *testing.T) { // Create a fresh graph for each test, then add two new // nodes to the graph. - graph := NewVersionedGraph( - MakeTestGraph(t), lnwire.GossipVersion1, - ) - node1 := createTestVertex(t, lnwire.GossipVersion1) - node2 := createTestVertex(t, lnwire.GossipVersion1) + graph := MakeTestGraph(t) + node1 := createTestVertex(t) + node2 := createTestVertex(t) require.NoError(t, graph.AddNode(ctx, node1)) require.NoError(t, graph.AddNode(ctx, node2)) @@ -3026,17 +2599,14 @@ func TestChanUpdatesInHorizonBoundaryConditions(t *testing.T) { ) channel, chanID := createEdge( - lnwire.GossipVersion1, uint32(i*10), 0, - 0, 0, node1, node2, + uint32(i*10), 0, 0, 0, node1, node2, ) require.NoError( - t, graph.AddChannelEdge(ctx, channel), + t, graph.AddChannelEdge(ctx, &channel), ) edge1 := newEdgePolicy( - lnwire.GossipVersion1, chanID.ToUint64(), updateTime.Unix(), - true, ) edge1.ChannelFlags = 0 edge1.ToNode = node2.PubKeyBytes @@ -3046,9 +2616,7 @@ func TestChanUpdatesInHorizonBoundaryConditions(t *testing.T) { ) edge2 := newEdgePolicy( - lnwire.GossipVersion1, chanID.ToUint64(), updateTime.Unix(), - false, ) edge2.ChannelFlags = 1 edge2.ToNode = node1.PubKeyBytes @@ -3061,12 +2629,7 @@ func TestChanUpdatesInHorizonBoundaryConditions(t *testing.T) { // Now we'll run the main query, and verify that we get // back the expected number of channels. iter := graph.ChanUpdatesInHorizon( - ctx, ChanUpdateRange{ - StartTime: fn.Some(startTime), - EndTime: fn.Some( - startTime.Add(26 * time.Hour), - ), - }, + startTime, startTime.Add(26*time.Hour), WithChanUpdateIterBatchSize(batchSize), ) @@ -3081,541 +2644,15 @@ func TestChanUpdatesInHorizonBoundaryConditions(t *testing.T) { } } -// TestNodeUpdatesInHorizonExclusiveEnd verifies that NodeUpdatesInHorizon uses -// an exclusive end time per BOLT 07: "timestamp is greater or equal to -// first_timestamp, and less than first_timestamp plus timestamp_range". -func TestNodeUpdatesInHorizonExclusiveEnd(t *testing.T) { - t.Parallel() - ctx := t.Context() - - graph := NewVersionedGraph(MakeTestGraph(t), lnwire.GossipVersion1) - - // Create three nodes at timestamps 100, 200, and 300. - timestamps := []int64{100, 200, 300} - for _, ts := range timestamps { - node := createTestVertex(t, lnwire.GossipVersion1) - node.LastUpdate = time.Unix(ts, 0) - require.NoError(t, graph.AddNode(ctx, node)) - } - - tests := []struct { - name string - start time.Time - end time.Time - want int - }{ - { - // Start is inclusive: node at exactly startTime - // should be included. - name: "start time is inclusive", - start: time.Unix(100, 0), - end: time.Unix(101, 0), - want: 1, - }, - { - // End is exclusive: node at exactly endTime should - // NOT be included. - name: "end time is exclusive", - start: time.Unix(100, 0), - end: time.Unix(200, 0), - want: 1, - }, - { - // One second past the boundary includes the node. - name: "one past end includes boundary node", - start: time.Unix(100, 0), - end: time.Unix(201, 0), - want: 2, - }, - { - // Range [200, 300) should include node at 200 but - // not node at 300. - name: "mid range excludes end", - start: time.Unix(200, 0), - end: time.Unix(300, 0), - want: 1, - }, - { - // Range [200, 301) should include nodes at 200 - // and 300. - name: "mid range includes end plus one", - start: time.Unix(200, 0), - end: time.Unix(301, 0), - want: 2, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - iter := graph.NodeUpdatesInHorizon( - ctx, NodeUpdateRange{ - StartTime: fn.Some(tc.start), - EndTime: fn.Some(tc.end), - }, - ) - - nodes, err := fn.CollectErr(iter) - require.NoError(t, err) - require.Len(t, nodes, tc.want) - }) - } -} - -// TestNodeUpdatesInHorizonV2 tests that NodeUpdatesInHorizon works correctly -// for v2 gossip using block-height-based ranges with [start, end) semantics. -func TestNodeUpdatesInHorizonV2(t *testing.T) { - t.Parallel() - - if !isSQLDB { - t.Skip("v2 gossip only supported with SQL backend") - } - - ctx := t.Context() - - graph := NewVersionedGraph( - MakeTestGraph(t), lnwire.GossipVersion2, - ) - - // Query before any nodes exist — should return empty. - iter := graph.NodeUpdatesInHorizon( - ctx, NodeUpdateRange{ - StartHeight: fn.Some(uint32(0)), - EndHeight: fn.Some(uint32(9999)), - }, - ) - nodes, err := fn.CollectErr(iter) - require.NoError(t, err) - require.Empty(t, nodes) - - // Create 10 v2 nodes at block heights 100, 110, 120, ..., 190. - const numNodes = 10 - const startHeight uint32 = 100 - const heightStep uint32 = 10 - - nodeAnns := make([]models.Node, 0, numNodes) - for i := 0; i < numNodes; i++ { - node := createTestVertex(t, lnwire.GossipVersion2) - node.LastBlockHeight = startHeight + uint32(i)*heightStep - nodeAnns = append(nodeAnns, *node) - require.NoError(t, graph.AddNode(ctx, node)) - } - - // endHeight is one past the last node's height (exclusive). - endHeight := startHeight + uint32(numNodes)*heightStep - - tests := []struct { - name string - start uint32 - end uint32 - want int - }{ - { - // Range strictly below all nodes. - name: "below range", - start: 0, - end: 50, - want: 0, - }, - { - // Range strictly above all nodes. - name: "above range", - start: 500, - end: 600, - want: 0, - }, - { - // Start is inclusive: node at exactly startHeight - // should be included. - name: "start height is inclusive", - start: startHeight, - end: startHeight + 1, - want: 1, - }, - { - // End is exclusive: node at exactly endHeight-10 - // (=190) should NOT be included when end=190. - name: "end height is exclusive", - start: startHeight, - end: endHeight - heightStep, - want: numNodes - 1, - }, - { - // One past the last node includes it. - name: "one past end includes last", - start: startHeight, - end: endHeight - heightStep + 1, - want: numNodes, - }, - { - // Full range returns all nodes. - name: "full range", - start: startHeight, - end: endHeight, - want: numNodes, - }, - { - // Skip the first node. - name: "skip first", - start: startHeight + heightStep, - end: endHeight, - want: numNodes - 1, - }, - { - // Middle slice: heights [120, 170) = nodes at - // 120, 130, 140, 150, 160 = 5 nodes. - name: "middle slice", - start: 120, - end: 170, - want: 5, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - iter := graph.NodeUpdatesInHorizon( - ctx, NodeUpdateRange{ - StartHeight: fn.Some(tc.start), - EndHeight: fn.Some(tc.end), - }, - ) - - results, err := fn.CollectErr(iter) - require.NoError(t, err) - require.Len(t, results, tc.want) - - // Verify nodes are in ascending block height - // order. - for i := 1; i < len(results); i++ { - require.LessOrEqual( - t, - results[i-1].LastBlockHeight, - results[i].LastBlockHeight, - "nodes should be in ascending "+ - "block height order", - ) - } - }) - } -} - -// TestChanUpdatesInHorizonExclusiveEnd verifies that ChanUpdatesInHorizon uses -// an exclusive end time per BOLT 07: "timestamp is greater or equal to -// first_timestamp, and less than first_timestamp plus timestamp_range". -func TestChanUpdatesInHorizonExclusiveEnd(t *testing.T) { - t.Parallel() - ctx := t.Context() - - graph := NewVersionedGraph(MakeTestGraph(t), lnwire.GossipVersion1) - - node1 := createTestVertex(t, lnwire.GossipVersion1) - node2 := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, graph.AddNode(ctx, node1)) - require.NoError(t, graph.AddNode(ctx, node2)) - - // Create three channels with policy updates at timestamps 100, 200, - // and 300. - timestamps := []int64{100, 200, 300} - for i, ts := range timestamps { - channel, chanID := createEdge( - lnwire.GossipVersion1, uint32(i*10), 0, 0, 0, - node1, node2, - ) - require.NoError(t, graph.AddChannelEdge(ctx, channel)) - - edge := newEdgePolicy( - lnwire.GossipVersion1, chanID.ToUint64(), ts, true, - ) - edge.ChannelFlags = 0 - edge.ToNode = node2.PubKeyBytes - edge.SigBytes = testSig.Serialize() - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge)) - } - - tests := []struct { - name string - start time.Time - end time.Time - want int - }{ - { - // Start is inclusive: channel at exactly startTime - // should be included. - name: "start time is inclusive", - start: time.Unix(100, 0), - end: time.Unix(101, 0), - want: 1, - }, - { - // End is exclusive: channel at exactly endTime - // should NOT be included. - name: "end time is exclusive", - start: time.Unix(100, 0), - end: time.Unix(200, 0), - want: 1, - }, - { - // One second past the boundary includes the - // channel. - name: "one past end includes boundary channel", - start: time.Unix(100, 0), - end: time.Unix(201, 0), - want: 2, - }, - { - // Range [200, 300) should include channel at 200 - // but not channel at 300. - name: "mid range excludes end", - start: time.Unix(200, 0), - end: time.Unix(300, 0), - want: 1, - }, - { - // Range [200, 301) should include channels at 200 - // and 300. - name: "mid range includes end plus one", - start: time.Unix(200, 0), - end: time.Unix(301, 0), - want: 2, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - iter := graph.ChanUpdatesInHorizon( - ctx, ChanUpdateRange{ - StartTime: fn.Some(tc.start), - EndTime: fn.Some(tc.end), - }, - ) - - channels, err := fn.CollectErr(iter) - require.NoError(t, err) - require.Len(t, channels, tc.want) - }) - } -} - -// TestChanUpdatesInHorizonV2 tests that ChanUpdatesInHorizon works correctly -// for v2 gossip using block-height-based ranges with [start, end) semantics. -func TestChanUpdatesInHorizonV2(t *testing.T) { - t.Parallel() - - if !isSQLDB { - t.Skip("v2 gossip only supported with SQL backend") - } - - ctx := t.Context() - - graph := NewVersionedGraph( - MakeTestGraph(t), lnwire.GossipVersion2, - ) - - node1 := createTestVertex(t, lnwire.GossipVersion2) - node2 := createTestVertex(t, lnwire.GossipVersion2) - require.NoError(t, graph.AddNode(ctx, node1)) - require.NoError(t, graph.AddNode(ctx, node2)) - - // Query before any channels exist — should return empty. - iter := graph.ChanUpdatesInHorizon( - ctx, ChanUpdateRange{ - StartHeight: fn.Some(uint32(0)), - EndHeight: fn.Some(uint32(9999)), - }, - ) - channels, err := fn.CollectErr(iter) - require.NoError(t, err) - require.Empty(t, channels) - - // Create 10 v2 channels with policy block heights at - // 100, 110, 120, ..., 190. - const numChans = 10 - const startHeight uint32 = 100 - const heightStep uint32 = 10 - - for i := 0; i < numChans; i++ { - height := startHeight + uint32(i)*heightStep - - channel, chanID := createEdge( - lnwire.GossipVersion2, uint32(i*10), 0, 0, 0, - node1, node2, - ) - require.NoError(t, graph.AddChannelEdge(ctx, channel)) - - edge1 := newEdgePolicy( - lnwire.GossipVersion2, chanID.ToUint64(), 0, true, - ) - edge1.LastBlockHeight = height - edge1.ToNode = node2.PubKeyBytes - edge1.SigBytes = testSig.Serialize() - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge1)) - - edge2 := newEdgePolicy( - lnwire.GossipVersion2, chanID.ToUint64(), 0, false, - ) - edge2.LastBlockHeight = height - edge2.ToNode = node1.PubKeyBytes - edge2.SigBytes = testSig.Serialize() - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge2)) - } - - endHeight := startHeight + uint32(numChans)*heightStep - - tests := []struct { - name string - start uint32 - end uint32 - want int - }{ - { - name: "below range", - start: 0, - end: 50, - want: 0, - }, - { - name: "above range", - start: 500, - end: 600, - want: 0, - }, - { - name: "start height is inclusive", - start: startHeight, - end: startHeight + 1, - want: 1, - }, - { - // End is exclusive: channel at exactly - // endHeight-10 (=190) should NOT be included - // when end=190. - name: "end height is exclusive", - start: startHeight, - end: endHeight - heightStep, - want: numChans - 1, - }, - { - name: "one past end includes last", - start: startHeight, - end: endHeight - heightStep + 1, - want: numChans, - }, - { - name: "full range", - start: startHeight, - end: endHeight, - want: numChans, - }, - { - name: "skip first", - start: startHeight + heightStep, - end: endHeight, - want: numChans - 1, - }, - { - // Heights [120, 170) = channels at - // 120, 130, 140, 150, 160 = 5 channels. - name: "middle slice", - start: 120, - end: 170, - want: 5, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - iter := graph.ChanUpdatesInHorizon( - ctx, ChanUpdateRange{ - StartHeight: fn.Some(tc.start), - EndHeight: fn.Some(tc.end), - }, - ) - - results, err := fn.CollectErr(iter) - require.NoError(t, err) - require.Len(t, results, tc.want) - }) - } - - // Test with asymmetric policy block heights: one policy inside - // the range, the other outside. The SQL query uses OR across the - // two policies, so the channel should still be returned if - // either policy is in range. - t.Run("asymmetric policy heights", func(t *testing.T) { - channel, chanID := createEdge( - lnwire.GossipVersion2, 500, 0, 0, 0, - node1, node2, - ) - require.NoError(t, graph.AddChannelEdge(ctx, channel)) - - // Policy 1 at height 300 (inside range). - edge1 := newEdgePolicy( - lnwire.GossipVersion2, - chanID.ToUint64(), 0, true, - ) - edge1.LastBlockHeight = 300 - edge1.ToNode = node2.PubKeyBytes - edge1.SigBytes = testSig.Serialize() - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge1)) - - // Policy 2 at height 900 (outside range). - edge2 := newEdgePolicy( - lnwire.GossipVersion2, - chanID.ToUint64(), 0, false, - ) - edge2.LastBlockHeight = 900 - edge2.ToNode = node1.PubKeyBytes - edge2.SigBytes = testSig.Serialize() - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge2)) - - // Query [250, 350) — only policy 1 is in range, but the - // channel should still be returned. - iter := graph.ChanUpdatesInHorizon( - ctx, ChanUpdateRange{ - StartHeight: fn.Some(uint32(250)), - EndHeight: fn.Some(uint32(350)), - }, - ) - results, err := fn.CollectErr(iter) - require.NoError(t, err) - require.Len(t, results, 1) - - // Query [850, 950) — only policy 2 is in range, channel - // should still be returned. - iter = graph.ChanUpdatesInHorizon( - ctx, ChanUpdateRange{ - StartHeight: fn.Some(uint32(850)), - EndHeight: fn.Some(uint32(950)), - }, - ) - results, err = fn.CollectErr(iter) - require.NoError(t, err) - require.Len(t, results, 1) - - // Query [400, 500) — neither policy is in range. - iter = graph.ChanUpdatesInHorizon( - ctx, ChanUpdateRange{ - StartHeight: fn.Some(uint32(400)), - EndHeight: fn.Some(uint32(500)), - }, - ) - results, err = fn.CollectErr(iter) - require.NoError(t, err) - require.Empty(t, results) - }) -} - -// testFilterKnownChanIDsZombieRevival tests that if a ChannelUpdateInfo is +// TestFilterKnownChanIDsZombieRevival tests that if a ChannelUpdateInfo is // passed to FilterKnownChanIDs that contains a channel that we have marked as // a zombie, then we will mark it as live again if the new ChannelUpdate has // timestamps that would make the channel be considered live again. // -// NOTE: this test focuses on zombie revival. The main logic of -// FilterKnownChanIDs is tested in testFilterKnownChanIDs. -func testFilterKnownChanIDsZombieRevival(t *testing.T, - v lnwire.GossipVersion) { - +// NOTE: this tests focuses on zombie revival. The main logic of +// FilterKnownChanIDs is tested in TestFilterKnownChanIDs. +func TestFilterKnownChanIDsZombieRevival(t *testing.T) { t.Parallel() - ctx := t.Context() graph := MakeTestGraph(t) @@ -3625,47 +2662,30 @@ func testFilterKnownChanIDsZombieRevival(t *testing.T, scid3 = lnwire.ShortChannelID{BlockHeight: 3} ) - vGraph := NewVersionedGraph(graph, v) isZombie := func(scid lnwire.ShortChannelID) bool { - zombie, _, _, err := vGraph.IsZombieEdge( - ctx, scid.ToUint64(), - ) + zombie, _, _, err := graph.IsZombieEdge(scid.ToUint64()) require.NoError(t, err) return zombie } // Mark channel 1 and 2 as zombies. - err := graph.MarkEdgeZombie( - ctx, v, scid1.ToUint64(), [33]byte{}, [33]byte{}, - ) + err := graph.MarkEdgeZombie(scid1.ToUint64(), [33]byte{}, [33]byte{}) require.NoError(t, err) - err = graph.MarkEdgeZombie( - ctx, v, scid2.ToUint64(), [33]byte{}, [33]byte{}, - ) + err = graph.MarkEdgeZombie(scid2.ToUint64(), [33]byte{}, [33]byte{}) require.NoError(t, err) require.True(t, isZombie(scid1)) require.True(t, isZombie(scid2)) require.False(t, isZombie(scid3)) - // Build a freshness marker appropriate for the gossip version. V1 - // uses unix timestamps, v2 uses block heights. - var revivalFreshness lnwire.Timestamp - switch v { - case lnwire.GossipVersion1: - revivalFreshness = lnwire.UnixTimestamp(1000) - case lnwire.GossipVersion2: - revivalFreshness = lnwire.BlockHeightTimestamp(1000) - } - // Call FilterKnownChanIDs with an isStillZombie call-back that would // result in the current zombies still be considered as zombies. - _, err = vGraph.FilterKnownChanIDs(ctx, []ChannelUpdateInfo{ - {ShortChannelID: scid1, Version: v}, - {ShortChannelID: scid2, Version: v}, - {ShortChannelID: scid3, Version: v}, - }, func(_ ChannelUpdateInfo) bool { + _, err = graph.FilterKnownChanIDs([]ChannelUpdateInfo{ + {ShortChannelID: scid1}, + {ShortChannelID: scid2}, + {ShortChannelID: scid3}, + }, func(_ time.Time, _ time.Time) bool { return true }) require.NoError(t, err) @@ -3674,19 +2694,18 @@ func testFilterKnownChanIDsZombieRevival(t *testing.T, require.True(t, isZombie(scid2)) require.False(t, isZombie(scid3)) - // Now call it again but this time with an isStillZombie call-back - // that would result in channel with SCID 2 no longer being - // considered a zombie. - _, err = vGraph.FilterKnownChanIDs(ctx, []ChannelUpdateInfo{ - {ShortChannelID: scid1, Version: v}, + // Now call it again but this time with a isStillZombie call-back that + // would result in channel with SCID 2 no longer being considered a + // zombie. + _, err = graph.FilterKnownChanIDs([]ChannelUpdateInfo{ + {ShortChannelID: scid1}, { - ShortChannelID: scid2, - Version: v, - Node1Freshness: revivalFreshness, + ShortChannelID: scid2, + Node1UpdateTimestamp: time.Unix(1000, 0), }, - {ShortChannelID: scid3, Version: v}, - }, func(info ChannelUpdateInfo) bool { - return info.Node1Freshness != revivalFreshness + {ShortChannelID: scid3}, + }, func(t1 time.Time, _ time.Time) bool { + return !t1.Equal(time.Unix(1000, 0)) }) require.NoError(t, err) @@ -3696,32 +2715,21 @@ func testFilterKnownChanIDsZombieRevival(t *testing.T, require.False(t, isZombie(scid3)) } -// testFilterKnownChanIDs tests that we're able to properly perform the set +// TestFilterKnownChanIDs tests that we're able to properly perform the set // differences of an incoming set of channel ID's, and those that we already // know of on disk. -func testFilterKnownChanIDs(t *testing.T, v lnwire.GossipVersion) { +func TestFilterKnownChanIDs(t *testing.T) { t.Parallel() ctx := t.Context() graph := MakeTestGraph(t) - vGraph := NewVersionedGraph(graph, v) - isZombieUpdate := func(_ ChannelUpdateInfo) bool { + isZombieUpdate := func(updateTime1 time.Time, + updateTime2 time.Time) bool { + return true } - // newChanUpdateInfo builds a ChannelUpdateInfo for the given SCID with - // the test's gossip version and zero freshness. - newChanUpdateInfo := func( - scid lnwire.ShortChannelID, - ) ChannelUpdateInfo { - - return ChannelUpdateInfo{ - ShortChannelID: scid, - Version: v, - } - } - var ( scid1 = lnwire.ShortChannelID{BlockHeight: 1} scid2 = lnwire.ShortChannelID{BlockHeight: 2} @@ -3731,13 +2739,11 @@ func testFilterKnownChanIDs(t *testing.T, v lnwire.GossipVersion) { // If we try to filter out a set of channel ID's before we even know of // any channels, then we should get the entire set back. preChanIDs := []ChannelUpdateInfo{ - newChanUpdateInfo(scid1), - newChanUpdateInfo(scid2), - newChanUpdateInfo(scid3), + {ShortChannelID: scid1}, + {ShortChannelID: scid2}, + {ShortChannelID: scid3}, } - filteredIDs, err := vGraph.FilterKnownChanIDs( - ctx, preChanIDs, isZombieUpdate, - ) + filteredIDs, err := graph.FilterKnownChanIDs(preChanIDs, isZombieUpdate) require.NoError(t, err, "unable to filter chan IDs") require.EqualValues(t, []uint64{ scid1.ToUint64(), @@ -3746,10 +2752,14 @@ func testFilterKnownChanIDs(t *testing.T, v lnwire.GossipVersion) { }, filteredIDs) // We'll start by creating two nodes which will seed our test graph. - node1 := createTestVertex(t, v) - require.NoError(t, graph.AddNode(ctx, node1)) - node2 := createTestVertex(t, v) - require.NoError(t, graph.AddNode(ctx, node2)) + node1 := createTestVertex(t) + if err := graph.AddNode(ctx, node1); err != nil { + t.Fatalf("unable to add node: %v", err) + } + node2 := createTestVertex(t) + if err := graph.AddNode(ctx, node2); err != nil { + t.Fatalf("unable to add node: %v", err) + } // Next, we'll add 5 channel ID's to the graph, each of them having a // block height 10 blocks after the previous. @@ -3757,87 +2767,115 @@ func testFilterKnownChanIDs(t *testing.T, v lnwire.GossipVersion) { chanIDs := make([]ChannelUpdateInfo, 0, numChans) for i := 0; i < numChans; i++ { channel, chanID := createEdge( - v, uint32(i*10), 0, 0, 0, node1, node2, + uint32(i*10), 0, 0, 0, node1, node2, ) - require.NoError(t, graph.AddChannelEdge(ctx, channel)) - chanIDs = append(chanIDs, newChanUpdateInfo(chanID)) + if err := graph.AddChannelEdge(ctx, &channel); err != nil { + t.Fatalf("unable to create channel edge: %v", err) + } + + chanIDs = append(chanIDs, NewChannelUpdateInfo( + chanID, time.Time{}, time.Time{}, + )) } const numZombies = 5 zombieIDs := make([]ChannelUpdateInfo, 0, numZombies) for i := 0; i < numZombies; i++ { channel, chanID := createEdge( - v, uint32(i*10+1), 0, 0, 0, node1, node2, + uint32(i*10+1), 0, 0, 0, node1, node2, ) - require.NoError(t, graph.AddChannelEdge(ctx, channel)) - err := graph.DeleteChannelEdges( - ctx, v, false, true, channel.ChannelID, - ) - require.NoError(t, err) + if err := graph.AddChannelEdge(ctx, &channel); err != nil { + t.Fatalf("unable to create channel edge: %v", err) + } + err := graph.DeleteChannelEdges(false, true, channel.ChannelID) + if err != nil { + t.Fatalf("unable to mark edge zombie: %v", err) + } - zombieIDs = append(zombieIDs, newChanUpdateInfo(chanID)) + zombieIDs = append( + zombieIDs, ChannelUpdateInfo{ShortChannelID: chanID}, + ) } queryCases := []struct { queryIDs []ChannelUpdateInfo - resp []ChannelUpdateInfo + + resp []ChannelUpdateInfo }{ // If we attempt to filter out all chanIDs we know of, the // response should be the empty set. { queryIDs: chanIDs, }, - // If we attempt to filter out all zombies that we know of, - // the response should be the empty set. + // If we attempt to filter out all zombies that we know of, the + // response should be the empty set. { queryIDs: zombieIDs, }, + // If we query for a set of ID's that we didn't insert, we // should get the same set back. { queryIDs: []ChannelUpdateInfo{ - newChanUpdateInfo(lnwire.ShortChannelID{ - BlockHeight: 99, - }), - newChanUpdateInfo(lnwire.ShortChannelID{ - BlockHeight: 100, - }), + { + ShortChannelID: lnwire.ShortChannelID{ + BlockHeight: 99, + }, + }, + { + ShortChannelID: lnwire.ShortChannelID{ + BlockHeight: 100, + }, + }, }, resp: []ChannelUpdateInfo{ - newChanUpdateInfo(lnwire.ShortChannelID{ - BlockHeight: 99, - }), - newChanUpdateInfo(lnwire.ShortChannelID{ - BlockHeight: 100, - }), + { + ShortChannelID: lnwire.ShortChannelID{ + BlockHeight: 99, + }, + }, + { + ShortChannelID: lnwire.ShortChannelID{ + BlockHeight: 100, + }, + }, }, }, + // If we query for a super-set of our the chan ID's inserted, // we should only get those new chanIDs back. { queryIDs: append(chanIDs, []ChannelUpdateInfo{ - newChanUpdateInfo(lnwire.ShortChannelID{ - BlockHeight: 99, - }), - newChanUpdateInfo(lnwire.ShortChannelID{ - BlockHeight: 101, - }), + { + ShortChannelID: lnwire.ShortChannelID{ + BlockHeight: 99, + }, + }, + { + ShortChannelID: lnwire.ShortChannelID{ + BlockHeight: 101, + }, + }, }...), resp: []ChannelUpdateInfo{ - newChanUpdateInfo(lnwire.ShortChannelID{ - BlockHeight: 99, - }), - newChanUpdateInfo(lnwire.ShortChannelID{ - BlockHeight: 101, - }), + { + ShortChannelID: lnwire.ShortChannelID{ + BlockHeight: 99, + }, + }, + { + ShortChannelID: lnwire.ShortChannelID{ + BlockHeight: 101, + }, + }, }, }, } for _, queryCase := range queryCases { - resp, err := vGraph.FilterKnownChanIDs( - ctx, queryCase.queryIDs, isZombieUpdate, + resp, err := graph.FilterKnownChanIDs( + queryCase.queryIDs, isZombieUpdate, ) require.NoError(t, err) @@ -3867,12 +2905,12 @@ func TestStressTestChannelGraphAPI(t *testing.T) { ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), lnwire.GossipVersion1) + graph := MakeTestGraph(t) - node1 := createTestVertex(t, lnwire.GossipVersion1) + node1 := createTestVertex(t) require.NoError(t, graph.AddNode(ctx, node1)) - node2 := createTestVertex(t, lnwire.GossipVersion1) + node2 := createTestVertex(t) require.NoError(t, graph.AddNode(ctx, node2)) // We need to update the node's timestamp since this call to @@ -3903,13 +2941,12 @@ func TestStressTestChannelGraphAPI(t *testing.T) { defer mu.Unlock() channel, chanID := createEdge( - lnwire.GossipVersion1, newBlockHeight(), - rand.Uint32(), uint16(rand.Int()), rand.Uint32(), - node1, node2, + newBlockHeight(), rand.Uint32(), uint16(rand.Int()), + rand.Uint32(), node1, node2, ) newChan := &chanInfo{ - info: *channel, + info: channel, id: chanID, } chans = append(chans, newChan) @@ -3994,7 +3031,6 @@ func TestStressTestChannelGraphAPI(t *testing.T) { } return graph.MarkEdgeZombie( - ctx, lnwire.GossipVersion1, channel.id.ToUint64(), node1.PubKeyBytes, node2.PubKeyBytes, @@ -4007,18 +3043,18 @@ func TestStressTestChannelGraphAPI(t *testing.T) { chanSet := getRandChanSet() var chanIDs []ChannelUpdateInfo - ver := lnwire.GossipVersion1 for _, c := range chanSet { - info := ChannelUpdateInfo{ - ShortChannelID: c.id, - Version: ver, - } - chanIDs = append(chanIDs, info) + chanIDs = append( + chanIDs, + ChannelUpdateInfo{ + ShortChannelID: c.id, + }, + ) } _, err := graph.FilterKnownChanIDs( - ctx, chanIDs, - func(_ ChannelUpdateInfo) bool { + chanIDs, + func(t time.Time, t2 time.Time) bool { return rand.Intn(2) == 0 }, ) @@ -4034,8 +3070,8 @@ func TestStressTestChannelGraphAPI(t *testing.T) { return nil } - _, _, err := graph.HasChannelEdge( - ctx, channel.id.ToUint64(), + _, _, _, _, err := graph.HasChannelEdge( + channel.id.ToUint64(), ) return err @@ -4055,7 +3091,7 @@ func TestStressTestChannelGraphAPI(t *testing.T) { } _, err := graph.PruneGraph( - ctx, spentOutpoints, &blockHash, 100, + spentOutpoints, &blockHash, 100, ) return err @@ -4064,14 +3100,8 @@ func TestStressTestChannelGraphAPI(t *testing.T) { { name: "ChanUpdateInHorizon", fn: func() error { - now := time.Now() iter := graph.ChanUpdatesInHorizon( - ctx, ChanUpdateRange{ - StartTime: fn.Some( - now.Add(-time.Hour), - ), - EndTime: fn.Some(now), - }, + time.Now().Add(-time.Hour), time.Now(), ) _, err := fn.CollectErr(iter) @@ -4096,8 +3126,7 @@ func TestStressTestChannelGraphAPI(t *testing.T) { } err := graph.DeleteChannelEdges( - ctx, strictPruning, markZombie, - chanIDs..., + strictPruning, markZombie, chanIDs..., ) if err != nil && !errors.Is(err, ErrEdgeNotFound) { @@ -4112,7 +3141,7 @@ func TestStressTestChannelGraphAPI(t *testing.T) { name: "DisconnectBlockAtHeight", fn: func() error { _, err := graph.DisconnectBlockAtHeight( - ctx, newBlockHeight(), + newBlockHeight(), ) return err @@ -4139,6 +3168,7 @@ func TestStressTestChannelGraphAPI(t *testing.T) { ) for i := 0; i < concurrencyLevel; i++ { + i := i t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { t.Parallel() @@ -4169,17 +3199,15 @@ func TestFilterChannelRange(t *testing.T) { // We'll first populate our graph with two nodes. All channels created // below will be made between these two nodes. - node1 := createTestVertex(t, lnwire.GossipVersion1) + node1 := createTestVertex(t) require.NoError(t, graph.AddNode(ctx, node1)) - node2 := createTestVertex(t, lnwire.GossipVersion1) + node2 := createTestVertex(t) require.NoError(t, graph.AddNode(ctx, node2)) // If we try to filter a channel range before we have any channels // inserted, we should get an empty slice of results. - resp, err := graph.FilterChannelRange( - ctx, lnwire.GossipVersion1, 10, 100, false, - ) + resp, err := graph.FilterChannelRange(10, 100, false) require.NoError(t, err) require.Empty(t, resp) @@ -4212,7 +3240,6 @@ func TestFilterChannelRange(t *testing.T) { updateTime = time.Unix(updateTimeSeed, 0) err = graph.UpdateEdgePolicy( ctx, &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, ToNode: node.PubKeyBytes, ChannelFlags: chanFlags, ChannelID: chanID, @@ -4229,21 +3256,19 @@ func TestFilterChannelRange(t *testing.T) { for i := 0; i < numChans/2; i++ { chanHeight := endHeight channel1, chanID1 := createEdge( - lnwire.GossipVersion1, chanHeight, uint32(i+1), 0, - 0, node1, node2, + chanHeight, uint32(i+1), 0, 0, node1, node2, ) - require.NoError(t, graph.AddChannelEdge(ctx, channel1)) + require.NoError(t, graph.AddChannelEdge(ctx, &channel1)) channel2, chanID2 := createEdge( - lnwire.GossipVersion1, chanHeight, uint32(i+2), 0, - 0, node1, node2, + chanHeight, uint32(i+2), 0, 0, node1, node2, ) - require.NoError(t, graph.AddChannelEdge(ctx, channel2)) + require.NoError(t, graph.AddChannelEdge(ctx, &channel2)) - chanInfo1 := NewV1ChannelUpdateInfo( + chanInfo1 := NewChannelUpdateInfo( chanID1, time.Time{}, time.Time{}, ) - chanInfo2 := NewV1ChannelUpdateInfo( + chanInfo2 := NewChannelUpdateInfo( chanID2, time.Time{}, time.Time{}, ) channelRanges = append(channelRanges, BlockChannelRange{ @@ -4260,8 +3285,12 @@ func TestFilterChannelRange(t *testing.T) { time4 = maybeAddPolicy(channel2.ChannelID, node2, true) ) - chanInfo1 = NewV1ChannelUpdateInfo(chanID1, time1, time2) - chanInfo2 = NewV1ChannelUpdateInfo(chanID2, time3, time4) + chanInfo1 = NewChannelUpdateInfo( + chanID1, time1, time2, + ) + chanInfo2 = NewChannelUpdateInfo( + chanID2, time3, time4, + ) channelRangesWithTimestamps = append( channelRangesWithTimestamps, BlockChannelRange{ Height: chanHeight, @@ -4343,14 +3372,14 @@ func TestFilterChannelRange(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() // First, do the query without requesting timestamps. resp, err := graph.FilterChannelRange( - ctx, lnwire.GossipVersion1, test.startHeight, - test.endHeight, false, + test.startHeight, test.endHeight, false, ) require.NoError(t, err) @@ -4364,8 +3393,7 @@ func TestFilterChannelRange(t *testing.T) { // Now, query the timestamps as well. resp, err = graph.FilterChannelRange( - ctx, lnwire.GossipVersion1, test.startHeight, - test.endHeight, true, + test.startHeight, test.endHeight, true, ) require.NoError(t, err) @@ -4380,47 +3408,24 @@ func TestFilterChannelRange(t *testing.T) { } } -// TestFilterChannelRangeVersionGuard checks that FilterChannelRange correctly -// handles version-specific requests. For gossip v1, the KV store returns -// results as normal; for v2, the KV store returns -// ErrVersionNotSupportedForKVDB while the SQL store returns empty results -// (a v2-aware query is a follow-up). -func TestFilterChannelRangeVersionGuard(t *testing.T) { - t.Parallel() - ctx := t.Context() - - store := NewTestDB(t) - - resp, err := store.FilterChannelRange( - ctx, lnwire.GossipVersion2, 0, 1000, false, - ) - - if isSQLDB { - // The SQL store accepts any known version and returns empty - // results since no v2 channels have been added. - require.NoError(t, err) - require.Empty(t, resp) - } else { - // The KV store does not support v2 and must return the - // sentinel error. - require.ErrorIs(t, err, ErrVersionNotSupportedForKVDB) - } -} - // TestFetchChanInfos tests that we're able to properly retrieve the full set // of ChannelEdge structs for a given set of short channel ID's. -func testFetchChanInfos(t *testing.T, v lnwire.GossipVersion) { +func TestFetchChanInfos(t *testing.T) { t.Parallel() ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), v) + graph := MakeTestGraph(t) // We'll first populate our graph with two nodes. All channels created // below will be made between these two nodes. - node1 := createTestVertex(t, v) - require.NoError(t, graph.AddNode(ctx, node1)) - node2 := createTestVertex(t, v) - require.NoError(t, graph.AddNode(ctx, node2)) + node1 := createTestVertex(t) + if err := graph.AddNode(ctx, node1); err != nil { + t.Fatalf("unable to add node: %v", err) + } + node2 := createTestVertex(t) + if err := graph.AddNode(ctx, node2); err != nil { + t.Fatalf("unable to add node: %v", err) + } // We'll make 5 test channels, ensuring we keep track of which channel // ID corresponds to a particular ChannelEdge. @@ -4431,38 +3436,34 @@ func testFetchChanInfos(t *testing.T, v lnwire.GossipVersion) { edgeQuery := make([]uint64, 0, numChans) for i := 0; i < numChans; i++ { channel, chanID := createEdge( - v, uint32(i*10), 0, 0, 0, node1, node2, + uint32(i*10), 0, 0, 0, node1, node2, ) - require.NoError(t, graph.AddChannelEdge(ctx, channel)) + if err := graph.AddChannelEdge(ctx, &channel); err != nil { + t.Fatalf("unable to create channel edge: %v", err) + } updateTime := endTime endTime = updateTime.Add(time.Second * 10) - edge1 := newEdgePolicy( - v, chanID.ToUint64(), - updateTime.Unix(), true, - ) - if v == lnwire.GossipVersion1 { - edge1.ChannelFlags = 0 - } + edge1 := newEdgePolicy(chanID.ToUint64(), updateTime.Unix()) + edge1.ChannelFlags = 0 edge1.ToNode = node2.PubKeyBytes edge1.SigBytes = testSig.Serialize() - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge1)) - - edge2 := newEdgePolicy( - v, chanID.ToUint64(), - updateTime.Unix(), false, - ) - if v == lnwire.GossipVersion1 { - edge2.ChannelFlags = 1 + if err := graph.UpdateEdgePolicy(ctx, edge1); err != nil { + t.Fatalf("unable to update edge: %v", err) } + + edge2 := newEdgePolicy(chanID.ToUint64(), updateTime.Unix()) + edge2.ChannelFlags = 1 edge2.ToNode = node1.PubKeyBytes edge2.SigBytes = testSig.Serialize() - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge2)) + if err := graph.UpdateEdgePolicy(ctx, edge2); err != nil { + t.Fatalf("unable to update edge: %v", err) + } edges = append(edges, ChannelEdge{ - Info: channel, + Info: &channel, Policy1: edge1, Policy2: edge2, }) @@ -4477,159 +3478,64 @@ func testFetchChanInfos(t *testing.T, v lnwire.GossipVersion) { // Add an another edge to the query that has been marked as a zombie // edge. The query should also skip this channel. zombieChan, zombieChanID := createEdge( - v, 666, 0, 0, 0, node1, node2, - ) - require.NoError(t, graph.AddChannelEdge(ctx, zombieChan)) - err := graph.DeleteChannelEdges( - ctx, false, true, zombieChan.ChannelID, + 666, 0, 0, 0, node1, node2, ) + if err := graph.AddChannelEdge(ctx, &zombieChan); err != nil { + t.Fatalf("unable to create channel edge: %v", err) + } + err := graph.DeleteChannelEdges(false, true, zombieChan.ChannelID) require.NoError(t, err, "unable to delete and mark edge zombie") edgeQuery = append(edgeQuery, zombieChanID.ToUint64()) // We'll now attempt to query for the range of channel ID's we just // inserted into the database. We should get the exact same set of // edges back. - resp, err := graph.FetchChanInfos(ctx, edgeQuery) + resp, err := graph.FetchChanInfos(edgeQuery) require.NoError(t, err, "unable to fetch chan edges") - require.Len(t, resp, len(edges)) + if len(resp) != len(edges) { + t.Fatalf("expected %v edges, instead got %v", len(edges), + len(resp)) + } for i := 0; i < len(resp); i++ { - compareEdgePolicies(t, resp[i].Policy1, edges[i].Policy1) - compareEdgePolicies(t, resp[i].Policy2, edges[i].Policy2) + err := compareEdgePolicies(resp[i].Policy1, edges[i].Policy1) + if err != nil { + t.Fatalf("edge doesn't match: %v", err) + } + err = compareEdgePolicies(resp[i].Policy2, edges[i].Policy2) + if err != nil { + t.Fatalf("edge doesn't match: %v", err) + } assertEdgeInfoEqual(t, resp[i].Info, edges[i].Info) } } -// testChannelView tests that ChannelView returns the correct edge points for -// each active channel in the graph. -func testChannelView(t *testing.T, v lnwire.GossipVersion) { - t.Parallel() - ctx := t.Context() - - graph := NewVersionedGraph(MakeTestGraph(t), v) - - // Initially the channel view should be empty. - channelView, err := graph.ChannelView(ctx) - require.NoError(t, err) - require.Empty(t, channelView) - - // Add some nodes and a set of channels between them. - node1 := createTestVertex(t, v) - require.NoError(t, graph.AddNode(ctx, node1)) - node2 := createTestVertex(t, v) - require.NoError(t, graph.AddNode(ctx, node2)) - - const numChans = 3 - edgePoints := make([]EdgePoint, 0, numChans) - for i := 0; i < numChans; i++ { - edge, _ := createEdge( - v, uint32(i+1), 0, 0, uint32(i), node1, node2, - ) - require.NoError(t, graph.AddChannelEdge(ctx, edge)) - - pkScript, err := edge.FundingPKScript() - require.NoError(t, err) - - edgePoints = append(edgePoints, EdgePoint{ - FundingPkScript: pkScript, - OutPoint: wire.OutPoint{ - Hash: rev, - Index: uint32(i), - }, - }) - } - - // Fetch the channel view and ensure it matches the expected edge - // points. - channelView, err = graph.ChannelView(ctx) - require.NoError(t, err) - assertChanViewEqual(t, channelView, edgePoints) -} - -// testChannelViewTaprootV1RoundTrip tests that a taproot channel persisted as a -// v1 edge can be read back from ChannelView() with the correct taproot funding -// script. -func testChannelViewTaprootV1RoundTrip(t *testing.T, v lnwire.GossipVersion) { - t.Parallel() - - if v != lnwire.GossipVersion1 { - t.Skip("only relevant for v1 taproot workaround channels") - } - - ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), v) - - node1 := createTestVertex(t, v) - require.NoError(t, graph.AddNode(ctx, node1)) - node2 := createTestVertex(t, v) - require.NoError(t, graph.AddNode(ctx, node2)) - - node1Pub, err := node1.PubKey() - require.NoError(t, err) - node2Pub, err := node2.PubKey() - require.NoError(t, err) - - node1Vertex := route.NewVertex(node1Pub) - node2Vertex := route.NewVertex(node2Pub) - outpoint := wire.OutPoint{ - Hash: rev, - Index: 1, - } - - // Persist a synthetic v1 channel that advertises the taproot staging - // bit. This reproduces the serialization path exercised by older graph - // entries. - edgeInfo, err := models.NewV1Channel( - 1, *chaincfg.MainNetParams.GenesisHash, - node1Vertex, node2Vertex, - &models.ChannelV1Fields{ - BitcoinKey1Bytes: node1Vertex, - BitcoinKey2Bytes: node2Vertex, - ExtraOpaqueData: make([]byte, 0), - }, - models.WithChannelPoint(outpoint), - models.WithCapacity(9000), - models.WithFeatures(lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsRequiredStaging, - )), - ) - require.NoError(t, err) - require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo)) - - // The fix should make ChannelView reconstruct the taproot funding - // script for v1 channels that advertise the taproot staging bit. - expectedScript, _, err := input.GenTaprootFundingScript( - node1Pub, node2Pub, 0, fn.None[chainhash.Hash](), - ) - require.NoError(t, err) - - channelView, err := graph.ChannelView(ctx) - require.NoError(t, err) - require.Len(t, channelView, 1) - require.Equal(t, expectedScript, channelView[0].FundingPkScript) - require.Equal(t, outpoint, channelView[0].OutPoint) -} - -// testIncompleteChannelPolicies tests that a channel that only has a policy +// TestIncompleteChannelPolicies tests that a channel that only has a policy // specified on one end is properly returned in ForEachChannel calls from // both sides. -func testIncompleteChannelPolicies(t *testing.T, v lnwire.GossipVersion) { +func TestIncompleteChannelPolicies(t *testing.T) { t.Parallel() ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), v) + graph := MakeTestGraph(t) // Create two nodes. - node1 := createTestVertex(t, v) - require.NoError(t, graph.AddNode(ctx, node1)) - node2 := createTestVertex(t, v) - require.NoError(t, graph.AddNode(ctx, node2)) + node1 := createTestVertex(t) + if err := graph.AddNode(ctx, node1); err != nil { + t.Fatalf("unable to add node: %v", err) + } + node2 := createTestVertex(t) + if err := graph.AddNode(ctx, node2); err != nil { + t.Fatalf("unable to add node: %v", err) + } channel, chanID := createEdge( - v, uint32(0), 0, 0, 0, node1, node2, + uint32(0), 0, 0, 0, node1, node2, ) - require.NoError(t, graph.AddChannelEdge(ctx, channel)) + if err := graph.AddChannelEdge(ctx, &channel); err != nil { + t.Fatalf("unable to create channel edge: %v", err) + } // Ensure that channel is reported with unknown policies. checkPolicies := func(node *models.Node, expectedIn, @@ -4641,8 +3547,21 @@ func testIncompleteChannelPolicies(t *testing.T, v lnwire.GossipVersion) { func(_ *models.ChannelEdgeInfo, outEdge, inEdge *models.ChannelEdgePolicy) error { - require.Equal(t, expectedOut, outEdge != nil) - require.Equal(t, expectedIn, inEdge != nil) + if !expectedOut && outEdge != nil { + t.Fatalf("Expected no outgoing policy") + } + + if expectedOut && outEdge == nil { + t.Fatalf("Expected an outgoing policy") + } + + if !expectedIn && inEdge != nil { + t.Fatalf("Expected no incoming policy") + } + + if expectedIn && inEdge == nil { + t.Fatalf("Expected an incoming policy") + } calls++ @@ -4655,30 +3574,30 @@ func testIncompleteChannelPolicies(t *testing.T, v lnwire.GossipVersion) { checkPolicies(node2, false, false) - newTestEdgePolicy := func(isNode1 bool, - toNode route.Vertex) *models.ChannelEdgePolicy { - - policy := newEdgePolicy( - v, chanID.ToUint64(), nextUpdateTime().Unix(), isNode1, - ) - policy.ToNode = toNode - policy.SigBytes = testSig.Serialize() - - return policy - } - // Only create an edge policy for node1 and leave the policy for node2 // unknown. - edgePolicy := newTestEdgePolicy(true, node2.PubKeyBytes) - require.NoError(t, graph.UpdateEdgePolicy(ctx, edgePolicy)) + updateTime := time.Unix(1234, 0) + + edgePolicy := newEdgePolicy(chanID.ToUint64(), updateTime.Unix()) + edgePolicy.ChannelFlags = 0 + edgePolicy.ToNode = node2.PubKeyBytes + edgePolicy.SigBytes = testSig.Serialize() + if err := graph.UpdateEdgePolicy(ctx, edgePolicy); err != nil { + t.Fatalf("unable to update edge: %v", err) + } checkPolicies(node1, false, true) checkPolicies(node2, true, false) // Create second policy and assert that both policies are reported // as present. - edgePolicy = newTestEdgePolicy(false, node1.PubKeyBytes) - require.NoError(t, graph.UpdateEdgePolicy(ctx, edgePolicy)) + edgePolicy = newEdgePolicy(chanID.ToUint64(), updateTime.Unix()) + edgePolicy.ChannelFlags = 1 + edgePolicy.ToNode = node1.PubKeyBytes + edgePolicy.SigBytes = testSig.Serialize() + if err := graph.UpdateEdgePolicy(ctx, edgePolicy); err != nil { + t.Fatalf("unable to update edge: %v", err) + } checkPolicies(node1, true, true) checkPolicies(node2, true, true) @@ -4694,40 +3613,50 @@ func TestChannelEdgePruningUpdateIndexDeletion(t *testing.T) { graph := MakeTestGraph(t) // The update index only applies to the bbolt graph. - boltStore, ok := graph.db.(*KVStore) + boltStore, ok := graph.V1Store.(*KVStore) if !ok { t.Skipf("skipping test that is aimed at a bbolt graph DB") } - sourceNode := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, graph.SetSourceNode(ctx, sourceNode)) + sourceNode := createTestVertex(t) + if err := graph.SetSourceNode(ctx, sourceNode); err != nil { + t.Fatalf("unable to set source node: %v", err) + } // We'll first populate our graph with two nodes. All channels created // below will be made between these two nodes. - node1 := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, graph.AddNode(ctx, node1)) - node2 := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, graph.AddNode(ctx, node2)) + node1 := createTestVertex(t) + if err := graph.AddNode(ctx, node1); err != nil { + t.Fatalf("unable to add node: %v", err) + } + node2 := createTestVertex(t) + if err := graph.AddNode(ctx, node2); err != nil { + t.Fatalf("unable to add node: %v", err) + } // With the two nodes created, we'll now create a random channel, as // well as two edges in the database with distinct update times. - edgeInfo, chanID := createEdge( - lnwire.GossipVersion1, 100, 0, 0, 0, node1, node2, - ) - require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo)) + edgeInfo, chanID := createEdge(100, 0, 0, 0, node1, node2) + if err := graph.AddChannelEdge(ctx, &edgeInfo); err != nil { + t.Fatalf("unable to add edge: %v", err) + } edge1 := randEdgePolicy(chanID.ToUint64()) edge1.ChannelFlags = 0 edge1.ToNode = node1.PubKeyBytes edge1.SigBytes = testSig.Serialize() - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge1)) + if err := graph.UpdateEdgePolicy(ctx, edge1); err != nil { + t.Fatalf("unable to update edge: %v", err) + } edge1 = copyEdgePolicy(edge1) // Avoid read/write race conditions. edge2 := randEdgePolicy(chanID.ToUint64()) edge2.ChannelFlags = 1 edge2.ToNode = node2.PubKeyBytes edge2.SigBytes = testSig.Serialize() - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge2)) + if err := graph.UpdateEdgePolicy(ctx, edge2); err != nil { + t.Fatalf("unable to update edge: %v", err) + } edge2 = copyEdgePolicy(edge2) // Avoid read/write race conditions. // checkIndexTimestamps is a helper function that checks the edge update @@ -4776,7 +3705,9 @@ func TestChannelEdgePruningUpdateIndexDeletion(t *testing.T) { return nil }) }, func() {}) - require.NoError(t, err) + if err != nil { + t.Fatal(err) + } } // With both edges policies added, we'll make sure to check they exist @@ -4790,10 +3721,14 @@ func TestChannelEdgePruningUpdateIndexDeletion(t *testing.T) { // removed from the update index. edge1.ChannelFlags = 2 edge1.LastUpdate = time.Now() - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge1)) + if err := graph.UpdateEdgePolicy(ctx, edge1); err != nil { + t.Fatalf("unable to update edge: %v", err) + } edge2.ChannelFlags = 3 edge2.LastUpdate = edge1.LastUpdate.Add(time.Hour) - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge2)) + if err := graph.UpdateEdgePolicy(ctx, edge2); err != nil { + t.Fatalf("unable to update edge: %v", err) + } // With the policies updated, we should now be able to find their // updated entries within the update index. @@ -4807,8 +3742,7 @@ func TestChannelEdgePruningUpdateIndexDeletion(t *testing.T) { var blockHash chainhash.Hash copy(blockHash[:], bytes.Repeat([]byte{2}, 32)) _, err := graph.PruneGraph( - ctx, []*wire.OutPoint{&edgeInfo.ChannelPoint}, &blockHash, - 101, + []*wire.OutPoint{&edgeInfo.ChannelPoint}, &blockHash, 101, ) require.NoError(t, err, "unable to prune graph") @@ -4824,29 +3758,37 @@ func TestPruneGraphNodes(t *testing.T) { t.Parallel() ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), lnwire.GossipVersion1) + graph := MakeTestGraph(t) // We'll start off by inserting our source node, to ensure that it's // the only node left after we prune the graph. - sourceNode := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, graph.SetSourceNode(ctx, sourceNode)) + sourceNode := createTestVertex(t) + if err := graph.SetSourceNode(ctx, sourceNode); err != nil { + t.Fatalf("unable to set source node: %v", err) + } // With the source node inserted, we'll now add three nodes to the // channel graph, at the end of the scenario, only two of these nodes // should still be in the graph. - node1 := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, graph.AddNode(ctx, node1)) - node2 := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, graph.AddNode(ctx, node2)) - node3 := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, graph.AddNode(ctx, node3)) + node1 := createTestVertex(t) + if err := graph.AddNode(ctx, node1); err != nil { + t.Fatalf("unable to add node: %v", err) + } + node2 := createTestVertex(t) + if err := graph.AddNode(ctx, node2); err != nil { + t.Fatalf("unable to add node: %v", err) + } + node3 := createTestVertex(t) + if err := graph.AddNode(ctx, node3); err != nil { + t.Fatalf("unable to add node: %v", err) + } // We'll now add a new edge to the graph, but only actually advertise // the edge of *one* of the nodes. - edgeInfo, chanID := createEdge( - lnwire.GossipVersion1, 100, 0, 0, 0, node1, node2, - ) - require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo)) + edgeInfo, chanID := createEdge(100, 0, 0, 0, node1, node2) + if err := graph.AddChannelEdge(ctx, &edgeInfo); err != nil { + t.Fatalf("unable to add edge: %v", err) + } // We'll now insert an advertised edge, but it'll only be the edge that // points from the first to the second node. @@ -4854,16 +3796,20 @@ func TestPruneGraphNodes(t *testing.T) { edge1.ChannelFlags = 0 edge1.ToNode = node1.PubKeyBytes edge1.SigBytes = testSig.Serialize() - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge1)) + if err := graph.UpdateEdgePolicy(ctx, edge1); err != nil { + t.Fatalf("unable to update edge: %v", err) + } // We'll now initiate a around of graph pruning. - require.NoError(t, graph.PruneGraphNodes(ctx)) + if err := graph.PruneGraphNodes(); err != nil { + t.Fatalf("unable to prune graph nodes: %v", err) + } // At this point, there should be 3 nodes left in the graph still: the // source node (which can't be pruned), and node 1+2. Nodes 1 and two // should still be left in the graph as there's half of an advertised // edge between them. - assertNumNodes(t, graph.ChannelGraph, 3) + assertNumNodes(t, graph, 3) // Finally, we'll ensure that node3, the only fully unconnected node as // properly deleted from the graph and not another node in its place. @@ -4871,41 +3817,39 @@ func TestPruneGraphNodes(t *testing.T) { require.NotNil(t, err) } -// testAddChannelEdgeShellNodes tests that when we attempt to add a ChannelEdge +// TestAddChannelEdgeShellNodes tests that when we attempt to add a ChannelEdge // to the graph, one or both of the nodes the edge involves aren't found in the // database, then shell edges are created for each node if needed. -func testAddChannelEdgeShellNodes(t *testing.T, v lnwire.GossipVersion) { +func TestAddChannelEdgeShellNodes(t *testing.T) { t.Parallel() ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), v) + graph := MakeTestGraph(t) // To start, we'll create two nodes, and only add one of them to the // channel graph. - node1 := createTestVertex(t, v) + node1 := createTestVertex(t) require.NoError(t, graph.SetSourceNode(ctx, node1)) - node2 := createTestVertex(t, v) + node2 := createTestVertex(t) // We'll now create an edge between the two nodes, as a result, node2 // should be inserted into the database as a shell node. - edgeInfo, _ := createEdge( - v, 100, 0, 0, 0, node1, node2, - ) - require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo)) + edgeInfo, _ := createEdge(100, 0, 0, 0, node1, node2) + require.NoError(t, graph.AddChannelEdge(ctx, &edgeInfo)) // Ensure that node1 was inserted as a full node, while node2 only has // a shell node present. node1, err := graph.FetchNode(ctx, node1.PubKeyBytes) require.NoError(t, err, "unable to fetch node1") - require.True(t, node1.HaveAnnouncement()) + require.True(t, node1.HaveNodeAnnouncement) node2, err = graph.FetchNode(ctx, node2.PubKeyBytes) require.NoError(t, err, "unable to fetch node2") - require.False(t, node2.HaveAnnouncement()) + require.False(t, node2.HaveNodeAnnouncement) // Show that attempting to add the channel again will result in an // error. - err = graph.AddChannelEdge(ctx, edgeInfo) + err = graph.AddChannelEdge(ctx, &edgeInfo) require.ErrorIs(t, err, ErrEdgeAlreadyExist) // Show that updating the shell node to a full node record works. @@ -4915,53 +3859,35 @@ func testAddChannelEdgeShellNodes(t *testing.T, v lnwire.GossipVersion) { // TestNodePruningUpdateIndexDeletion tests that once a node has been removed // from the channel graph, we also remove the entry from the update index as // well. -// testNodePruningUpdateIndexDeletion verifies that deleting a node also removes -// it from the update index used by NodeUpdatesInHorizon. -func testNodePruningUpdateIndexDeletion(t *testing.T, - v lnwire.GossipVersion) { - +func TestNodePruningUpdateIndexDeletion(t *testing.T) { t.Parallel() ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), v) + graph := MakeTestGraph(t) // We'll first populate our graph with a single node that will be // removed shortly. - node1 := createTestVertex(t, v) - require.NoError(t, graph.AddNode(ctx, node1)) - - // Build a NodeUpdateRange that covers the node we just inserted. V1 - // uses time-based ranges, v2 uses block-height-based ranges. - var updateRange NodeUpdateRange - switch v { - case lnwire.GossipVersion1: - updateRange = NodeUpdateRange{ - StartTime: fn.Some(time.Unix(9, 0)), - EndTime: fn.Some( - node1.LastUpdate.Add(time.Minute), - ), - } - case lnwire.GossipVersion2: - updateRange = NodeUpdateRange{ - StartHeight: fn.Some(uint32(0)), - EndHeight: fn.Some( - node1.LastBlockHeight + 1, - ), - } + node1 := createTestVertex(t) + if err := graph.AddNode(ctx, node1); err != nil { + t.Fatalf("unable to add node: %v", err) } // We'll confirm that we can retrieve the node using - // NodeUpdatesInHorizon. - nodesInHorizonIter := graph.NodeUpdatesInHorizon( - ctx, updateRange, - ) + // NodeUpdatesInHorizon, using a time that's slightly beyond the last + // update time of our test node. + startTime := time.Unix(9, 0) + endTime := node1.LastUpdate.Add(time.Minute) + nodesInHorizonIter := graph.NodeUpdatesInHorizon(startTime, endTime) // We should only have a single node, and that node should exactly // match the node we just inserted. nodesInHorizon, err := fn.CollectErr(nodesInHorizonIter) require.NoError(t, err, "unable to fetch nodes in horizon") - require.Len(t, nodesInHorizon, 1) - compareNodes(t, node1, nodesInHorizon[0]) + if len(nodesInHorizon) != 1 { + t.Fatalf("should have 1 nodes instead have: %v", + len(nodesInHorizon)) + } + compareNodes(t, node1, &nodesInHorizon[0]) // We'll now delete the node from the graph, this should result in it // being removed from the update index as well. @@ -4970,18 +3896,19 @@ func testNodePruningUpdateIndexDeletion(t *testing.T, // Now that the node has been deleted, we'll again query the nodes in // the horizon. This time we should have no nodes at all. - nodesInHorizonIter = graph.NodeUpdatesInHorizon( - ctx, updateRange, - ) + nodesInHorizonIter = graph.NodeUpdatesInHorizon(startTime, endTime) nodesInHorizon, err = fn.CollectErr(nodesInHorizonIter) require.NoError(t, err, "unable to fetch nodes in horizon") - require.Empty(t, nodesInHorizon) + + if len(nodesInHorizon) != 0 { + t.Fatalf("should have zero nodes instead have: %v", + len(nodesInHorizon)) + } } var ( updateTime = prand.Int63() updateTimeMu sync.Mutex - updateBlock = prand.Uint32() ) func nextUpdateTime() time.Time { @@ -4993,18 +3920,9 @@ func nextUpdateTime() time.Time { return time.Unix(updateTime, 0) } -func nextBlockHeight() uint32 { - updateTimeMu.Lock() - defer updateTimeMu.Unlock() - - updateBlock++ - - return updateBlock -} - -// testNodeIsPublic ensures that we properly detect nodes that are seen as +// TestNodeIsPublic ensures that we properly detect nodes that are seen as // public within the network graph. -func testNodeIsPublic(t *testing.T, v lnwire.GossipVersion) { +func TestNodeIsPublic(t *testing.T) { t.Parallel() ctx := t.Context() @@ -5016,29 +3934,32 @@ func testNodeIsPublic(t *testing.T, v lnwire.GossipVersion) { // We'll need to create a separate database and channel graph for each // participant to replicate real-world scenarios (private edges being in // some graphs but not others, etc.). - aliceGraph := NewVersionedGraph(MakeTestGraph(t), v) - aliceNode := createTestVertex(t, v) - err := aliceGraph.SetSourceNode(ctx, aliceNode) - require.NoError(t, err, "unable to set source node") + aliceGraph := MakeTestGraph(t) + aliceNode := createTestVertex(t) + if err := aliceGraph.SetSourceNode(ctx, aliceNode); err != nil { + t.Fatalf("unable to set source node: %v", err) + } - bobGraph := NewVersionedGraph(MakeTestGraph(t), v) - bobNode := createTestVertex(t, v) - err = bobGraph.SetSourceNode(ctx, bobNode) - require.NoError(t, err, "unable to set source node") + bobGraph := MakeTestGraph(t) + bobNode := createTestVertex(t) + if err := bobGraph.SetSourceNode(ctx, bobNode); err != nil { + t.Fatalf("unable to set source node: %v", err) + } - carolGraph := NewVersionedGraph(MakeTestGraph(t), v) - carolNode := createTestVertex(t, v) - err = carolGraph.SetSourceNode(ctx, carolNode) - require.NoError(t, err, "unable to set source node") + carolGraph := MakeTestGraph(t) + carolNode := createTestVertex(t) + if err := carolGraph.SetSourceNode(ctx, carolNode); err != nil { + t.Fatalf("unable to set source node: %v", err) + } - aliceBobEdge, _ := createEdge(v, 10, 0, 0, 0, aliceNode, bobNode) - bobCarolEdge, _ := createEdge(v, 10, 1, 0, 1, bobNode, carolNode) + aliceBobEdge, _ := createEdge(10, 0, 0, 0, aliceNode, bobNode) + bobCarolEdge, _ := createEdge(10, 1, 0, 1, bobNode, carolNode) // After creating all of our nodes and edges, we'll add them to each // participant's graph. nodes := []*models.Node{aliceNode, bobNode, carolNode} - edges := []*models.ChannelEdgeInfo{aliceBobEdge, bobCarolEdge} - graphs := []*VersionedGraph{aliceGraph, bobGraph, carolGraph} + edges := []*models.ChannelEdgeInfo{&aliceBobEdge, &bobCarolEdge} + graphs := []*ChannelGraph{aliceGraph, bobGraph, carolGraph} for _, graph := range graphs { for _, node := range nodes { node.LastUpdate = nextUpdateTime() @@ -5054,18 +3975,28 @@ func testNodeIsPublic(t *testing.T, v lnwire.GossipVersion) { // checkNodes is a helper closure that will be used to assert that the // given nodes are seen as public/private within the given graphs. checkNodes := func(nodes []*models.Node, - graphs []*VersionedGraph, public bool) { + graphs []*ChannelGraph, public bool) { t.Helper() for _, node := range nodes { for _, graph := range graphs { isPublic, err := graph.IsPublicNode( - ctx, node.PubKeyBytes, + node.PubKeyBytes, ) - require.NoError(t, err) + if err != nil { + t.Fatalf("unable to determine if "+ + "pivot is public: %v", err) + } - require.Equal(t, public, isPublic) + switch { + case isPublic && !public: + t.Fatalf("expected %x to be private", + node.PubKeyBytes) + case !isPublic && public: + t.Fatalf("expected %x to be public", + node.PubKeyBytes) + } } } } @@ -5079,13 +4010,15 @@ func testNodeIsPublic(t *testing.T, v lnwire.GossipVersion) { // has any advertised edges. for _, graph := range graphs { err := graph.DeleteChannelEdges( - ctx, false, true, aliceBobEdge.ChannelID, + false, true, aliceBobEdge.ChannelID, ) - require.NoError(t, err, "unable to remove edge") + if err != nil { + t.Fatalf("unable to remove edge: %v", err) + } } checkNodes( []*models.Node{aliceNode}, - []*VersionedGraph{bobGraph, carolGraph}, + []*ChannelGraph{bobGraph, carolGraph}, false, ) @@ -5096,81 +4029,31 @@ func testNodeIsPublic(t *testing.T, v lnwire.GossipVersion) { // it without it being advertised. for _, graph := range graphs { err := graph.DeleteChannelEdges( - ctx, false, true, bobCarolEdge.ChannelID, + false, true, bobCarolEdge.ChannelID, ) - require.NoError(t, err, "unable to remove edge") + if err != nil { + t.Fatalf("unable to remove edge: %v", err) + } if graph == aliceGraph { continue } bobCarolEdge.AuthProof = nil - err = graph.AddChannelEdge(ctx, bobCarolEdge) - require.NoError(t, err, "unable to add edge") + if err := graph.AddChannelEdge(ctx, &bobCarolEdge); err != nil { + t.Fatalf("unable to add edge: %v", err) + } } // With the modifications above, Bob should now be seen as a private // node from both Alice's and Carol's perspective. checkNodes( []*models.Node{bobNode}, - []*VersionedGraph{aliceGraph, carolGraph}, + []*ChannelGraph{aliceGraph, carolGraph}, false, ) } -// testIsPublicNodeEmptyChannelSignature ensures empty channel signatures don't -// mark nodes as public. -func testIsPublicNodeEmptyChannelSignature(t *testing.T, - v lnwire.GossipVersion) { - - t.Parallel() - ctx := t.Context() - - testGraph := MakeTestGraph(t) - graph := NewVersionedGraph(testGraph, v) - - // Set a source node as it's required for IsPublicNode. - sourceNode := createTestVertex(t, v) - err := graph.SetSourceNode(ctx, sourceNode) - require.NoError(t, err) - - node1 := createTestVertex(t, v) - - node1.LastUpdate = nextUpdateTime() - - err = graph.AddNode(ctx, node1) - require.NoError(t, err) - - // Create an edge between source node and node1, with - // empty signatures. This tests that empty signatures - // don't mark nodes as public. - edgeInfo, _ := createEdge( - v, 10, 0, 0, 0, sourceNode, node1, - true, - ) - - switch v { - case lnwire.GossipVersion1: - edgeInfo.AuthProof = - models.NewV1ChannelAuthProof( - []byte{}, []byte{}, - []byte{}, []byte{}, - ) - case lnwire.GossipVersion2: - edgeInfo.AuthProof = - models.NewV2ChannelAuthProof([]byte{}) - } - - err = graph.AddChannelEdge(ctx, edgeInfo) - require.NoError(t, err) - - // node1 should NOT be considered public because the - // channel announcement has empty signatures. - isPublic, err := graph.IsPublicNode(ctx, node1.PubKeyBytes) - require.NoError(t, err) - require.False(t, isPublic) -} - // BenchmarkIsPublicNode measures the performance of IsPublicNode when checking // a large number of nodes. func BenchmarkIsPublicNode(b *testing.B) { @@ -5179,19 +4062,16 @@ func BenchmarkIsPublicNode(b *testing.B) { // Create a graph with a reasonable number of nodes and channels. numNodes := 100 numChans := 4 - _, nodes := fillTestGraph( - b, graph, numNodes, numChans, lnwire.GossipVersion1, - ) + _, nodes := fillTestGraph(b, graph, numNodes, numChans) // Use deterministic random number generator for reproducible results. rng := prand.New(prand.NewSource(42)) - v1Graph := NewVersionedGraph(graph, lnwire.GossipVersion1) for b.Loop() { // Query random nodes to avoid query caching and better // represent real-world query patterns. nodePub := nodes[rng.Intn(len(nodes))].PubKeyBytes - _, err := v1Graph.IsPublicNode(b.Context(), nodePub) + _, err := graph.IsPublicNode(nodePub) require.NoError(b, err) } } @@ -5199,70 +4079,84 @@ func BenchmarkIsPublicNode(b *testing.B) { // TestDisabledChannelIDs ensures that the disabled channels within the // disabledEdgePolicyBucket are managed properly and the list returned from // DisabledChannelIDs is correct. -func testDisabledChannelIDs(t *testing.T, v lnwire.GossipVersion) { +func TestDisabledChannelIDs(t *testing.T) { t.Parallel() ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), v) + graph := MakeTestGraph(t) // Create first node and add it to the graph. - node1 := createTestVertex(t, v) - require.NoError(t, graph.AddNode(ctx, node1)) + node1 := createTestVertex(t) + if err := graph.AddNode(ctx, node1); err != nil { + t.Fatalf("unable to add node: %v", err) + } // Create second node and add it to the graph. - node2 := createTestVertex(t, v) - require.NoError(t, graph.AddNode(ctx, node2)) + node2 := createTestVertex(t) + if err := graph.AddNode(ctx, node2); err != nil { + t.Fatalf("unable to add node: %v", err) + } // Adding a new channel edge to the graph. - edgeInfo, edge1, edge2 := createChannelEdge(node1, node2, v) - switch v { - case lnwire.GossipVersion1: - node2.LastUpdate = nextUpdateTime() - case lnwire.GossipVersion2: - node2.LastBlockHeight = nextBlockHeight() + edgeInfo, edge1, edge2 := createChannelEdge(node1, node2) + node2.LastUpdate = nextUpdateTime() + if err := graph.AddNode(ctx, node2); err != nil { + t.Fatalf("unable to add node: %v", err) + } + + if err := graph.AddChannelEdge(ctx, edgeInfo); err != nil { + t.Fatalf("unable to create channel edge: %v", err) } - require.NoError(t, graph.AddNode(ctx, node2)) - require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo)) // Ensure no disabled channels exist in the bucket on start. - disabledChanIds, err := graph.DisabledChannelIDs(ctx) + disabledChanIds, err := graph.DisabledChannelIDs() require.NoError(t, err, "unable to get disabled channel ids") - require.Empty(t, disabledChanIds) + if len(disabledChanIds) > 0 { + t.Fatalf("expected empty disabled channels, got %v disabled "+ + "channels", len(disabledChanIds)) + } // Add one disabled policy and ensure the channel is still not in the // disabled list. - switch v { - case lnwire.GossipVersion1: - edge1.ChannelFlags |= lnwire.ChanUpdateDisabled - case lnwire.GossipVersion2: - edge1.DisableFlags |= lnwire.ChanUpdateDisableIncoming + edge1.ChannelFlags |= lnwire.ChanUpdateDisabled + if err := graph.UpdateEdgePolicy(ctx, edge1); err != nil { + t.Fatalf("unable to update edge: %v", err) } - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge1)) - disabledChanIds, err = graph.DisabledChannelIDs(ctx) + disabledChanIds, err = graph.DisabledChannelIDs() require.NoError(t, err, "unable to get disabled channel ids") - require.Empty(t, disabledChanIds) + if len(disabledChanIds) > 0 { + t.Fatalf("expected empty disabled channels, got %v disabled "+ + "channels", len(disabledChanIds)) + } // Add second disabled policy and ensure the channel is now in the // disabled list. - switch v { - case lnwire.GossipVersion1: - edge2.ChannelFlags |= lnwire.ChanUpdateDisabled - case lnwire.GossipVersion2: - edge2.DisableFlags |= lnwire.ChanUpdateDisableIncoming + edge2.ChannelFlags |= lnwire.ChanUpdateDisabled + if err := graph.UpdateEdgePolicy(ctx, edge2); err != nil { + t.Fatalf("unable to update edge: %v", err) } - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge2)) - disabledChanIds, err = graph.DisabledChannelIDs(ctx) + disabledChanIds, err = graph.DisabledChannelIDs() require.NoError(t, err, "unable to get disabled channel ids") - require.Equal(t, []uint64{edgeInfo.ChannelID}, disabledChanIds) + if len(disabledChanIds) != 1 || + disabledChanIds[0] != edgeInfo.ChannelID { + + t.Fatalf("expected disabled channel with id %v, "+ + "got %v", edgeInfo.ChannelID, disabledChanIds) + } // Delete the channel edge and ensure it is removed from the disabled // list. - require.NoError(t, graph.DeleteChannelEdges( - ctx, false, true, edgeInfo.ChannelID, - )) - disabledChanIds, err = graph.DisabledChannelIDs(ctx) + if err = graph.DeleteChannelEdges( + false, true, edgeInfo.ChannelID, + ); err != nil { + t.Fatalf("unable to delete channel edge: %v", err) + } + disabledChanIds, err = graph.DisabledChannelIDs() require.NoError(t, err, "unable to get disabled channel ids") - require.Empty(t, disabledChanIds) + if len(disabledChanIds) > 0 { + t.Fatalf("expected empty disabled channels, got %v disabled "+ + "channels", len(disabledChanIds)) + } } // TestEdgePolicyMissingMaxHTLC tests that if we find a ChannelEdgePolicy in @@ -5277,22 +4171,26 @@ func TestEdgePolicyMissingMaxHTLC(t *testing.T) { graph := MakeTestGraph(t) // This test currently directly edits the bytes stored in the bbolt DB. - boltStore, ok := graph.db.(*KVStore) + boltStore, ok := graph.V1Store.(*KVStore) if !ok { t.Skipf("skipping test that is aimed at a bbolt graph DB") } // We'd like to test the update of edges inserted into the database, so // we create two vertexes to connect. - node1 := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, graph.AddNode(ctx, node1)) - node2 := createTestVertex(t, lnwire.GossipVersion1) + node1 := createTestVertex(t) + if err := graph.AddNode(ctx, node1); err != nil { + t.Fatalf("unable to add node: %v", err) + } + node2 := createTestVertex(t) - edgeInfo, edge1, edge2 := createChannelEdge( - node1, node2, lnwire.GossipVersion1, - ) - require.NoError(t, graph.AddNode(ctx, node2)) - require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo)) + edgeInfo, edge1, edge2 := createChannelEdge(node1, node2) + if err := graph.AddNode(ctx, node2); err != nil { + t.Fatalf("unable to add node: %v", err) + } + if err := graph.AddChannelEdge(ctx, edgeInfo); err != nil { + t.Fatalf("unable to create channel edge: %v", err) + } chanID := edgeInfo.ChannelID from := edge2.ToNode[:] @@ -5333,26 +4231,32 @@ func TestEdgePolicyMissingMaxHTLC(t *testing.T) { // we added is invalid according to the new format, it should be as we // are not aware of the policy (indicated by the policy returned being // nil) - dbEdgeInfo, dbEdge1, dbEdge2, err := graph.FetchChannelEdgesByID( - ctx, chanID, - ) + dbEdgeInfo, dbEdge1, dbEdge2, err := graph.FetchChannelEdgesByID(chanID) require.NoError(t, err, "unable to fetch channel by ID") // The first edge should have a nil-policy returned - require.Nil(t, dbEdge1) - compareEdgePolicies(t, dbEdge2, edge2) + if dbEdge1 != nil { + t.Fatalf("expected db edge to be nil") + } + if err := compareEdgePolicies(dbEdge2, edge2); err != nil { + t.Fatalf("edge doesn't match: %v", err) + } assertEdgeInfoEqual(t, dbEdgeInfo, edgeInfo) // Now add the original, unmodified edge policy, and make sure the edge // policies then become fully populated. - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge1)) + if err := graph.UpdateEdgePolicy(ctx, edge1); err != nil { + t.Fatalf("unable to update edge: %v", err) + } - dbEdgeInfo, dbEdge1, dbEdge2, err = graph.FetchChannelEdgesByID( - ctx, chanID, - ) + dbEdgeInfo, dbEdge1, dbEdge2, err = graph.FetchChannelEdgesByID(chanID) require.NoError(t, err, "unable to fetch channel by ID") - compareEdgePolicies(t, dbEdge1, edge1) - compareEdgePolicies(t, dbEdge2, edge2) + if err := compareEdgePolicies(dbEdge1, edge1); err != nil { + t.Fatalf("edge doesn't match: %v", err) + } + if err := compareEdgePolicies(dbEdge2, edge2); err != nil { + t.Fatalf("edge doesn't match: %v", err) + } assertEdgeInfoEqual(t, dbEdgeInfo, edgeInfo) } @@ -5388,29 +4292,30 @@ func putSerializedPolicy(t *testing.T, db kvdb.Backend, from []byte, require.NoError(t, err, "error writing db") } -// assertNumZombies queries the provided ChannelGraph for NumZombies for the -// given gossip version and asserts that the result equals the expected count. -func assertNumZombies(t *testing.T, graph *ChannelGraph, - v lnwire.GossipVersion, expZombies uint64) { - +// assertNumZombies queries the provided ChannelGraph for NumZombies, and +// asserts that the returned number is equal to expZombies. +func assertNumZombies(t *testing.T, graph *ChannelGraph, expZombies uint64) { t.Helper() - vGraph := NewVersionedGraph(graph, v) - numZombies, err := vGraph.NumZombies(t.Context()) + numZombies, err := graph.NumZombies() require.NoError(t, err, "unable to query number of zombies") - require.Equal(t, expZombies, numZombies) + + if numZombies != expZombies { + t.Fatalf("expected %d zombies, found %d", + expZombies, numZombies) + } } -// testGraphZombieIndex ensures that we can mark edges correctly as zombie/live. -func testGraphZombieIndex(t *testing.T, v lnwire.GossipVersion) { +// TestGraphZombieIndex ensures that we can mark edges correctly as zombie/live. +func TestGraphZombieIndex(t *testing.T) { t.Parallel() ctx := t.Context() // We'll start by creating our test graph along with a test edge. graph := MakeTestGraph(t) - node1 := createTestVertex(t, v) - node2 := createTestVertex(t, v) + node1 := createTestVertex(t) + node2 := createTestVertex(t) // Swap the nodes if the second's pubkey is smaller than the first. // Without this, the comparisons at the end will fail probabilistically. @@ -5418,95 +4323,54 @@ func testGraphZombieIndex(t *testing.T, v lnwire.GossipVersion) { node1, node2 = node2, node1 } - edge, _, _ := createChannelEdge(node1, node2, v) + edge, _, _ := createChannelEdge(node1, node2) require.NoError(t, graph.AddChannelEdge(ctx, edge)) - vGraph := NewVersionedGraph(graph, v) - // Since the edge is known the graph and it isn't a zombie, IsZombieEdge // should not report the channel as a zombie. - isZombie, _, _, err := vGraph.IsZombieEdge(ctx, edge.ChannelID) + isZombie, _, _, err := graph.IsZombieEdge(edge.ChannelID) require.NoError(t, err) require.False(t, isZombie) - assertNumZombies(t, graph, v, 0) + assertNumZombies(t, graph, 0) // If we delete the edge and mark it as a zombie, then we should expect // to see it within the index. - err = graph.DeleteChannelEdges(ctx, v, false, true, edge.ChannelID) + err = graph.DeleteChannelEdges(false, true, edge.ChannelID) require.NoError(t, err, "unable to mark edge as zombie") - isZombie, pubKey1, pubKey2, err := vGraph.IsZombieEdge( - ctx, edge.ChannelID, - ) + isZombie, pubKey1, pubKey2, err := graph.IsZombieEdge(edge.ChannelID) require.NoError(t, err) require.True(t, isZombie) require.Equal(t, node1.PubKeyBytes, pubKey1) require.Equal(t, node2.PubKeyBytes, pubKey2) - assertNumZombies(t, graph, v, 1) + assertNumZombies(t, graph, 1) // Similarly, if we mark the same edge as live, we should no longer see // it within the index. - err = graph.MarkEdgeLive(ctx, v, edge.ChannelID) - require.NoError(t, err) + require.NoError(t, graph.MarkEdgeLive(edge.ChannelID)) // Attempting to mark the edge as live again now that it is no longer // in the zombie index should fail. require.ErrorIs( - t, graph.MarkEdgeLive(ctx, v, edge.ChannelID), - ErrZombieEdgeNotFound, + t, graph.MarkEdgeLive(edge.ChannelID), ErrZombieEdgeNotFound, ) - isZombie, _, _, err = vGraph.IsZombieEdge(ctx, edge.ChannelID) + isZombie, _, _, err = graph.IsZombieEdge(edge.ChannelID) require.NoError(t, err) require.False(t, isZombie) - assertNumZombies(t, graph, v, 0) + assertNumZombies(t, graph, 0) // If we mark the edge as a zombie manually, then it should show up as // being a zombie once again. err = graph.MarkEdgeZombie( - ctx, v, edge.ChannelID, - node1.PubKeyBytes, node2.PubKeyBytes, + edge.ChannelID, node1.PubKeyBytes, node2.PubKeyBytes, ) require.NoError(t, err, "unable to mark edge as zombie") - isZombie, _, _, err = vGraph.IsZombieEdge(ctx, edge.ChannelID) + isZombie, _, _, err = graph.IsZombieEdge(edge.ChannelID) require.NoError(t, err) require.True(t, isZombie) - assertNumZombies(t, graph, v, 1) -} - -// testFetchZombieEdgeVersioning verifies that when a zombie edge is fetched via -// FetchChannelEdgesByID, the returned ChannelEdgeInfo carries the correct -// gossip version. -func testFetchZombieEdgeVersioning(t *testing.T, v lnwire.GossipVersion) { - t.Parallel() - ctx := t.Context() - - graph := NewVersionedGraph(MakeTestGraph(t), v) - - node1 := createTestVertex(t, v) - node2 := createTestVertex(t, v) - - if bytes.Compare(node2.PubKeyBytes[:], node1.PubKeyBytes[:]) < 0 { - node1, node2 = node2, node1 - } - - edge, _, _ := createChannelEdge(node1, node2, v) - require.NoError(t, graph.AddChannelEdge(ctx, edge)) - - // Delete the edge and mark it as a zombie. - err := graph.DeleteChannelEdges(ctx, false, true, edge.ChannelID) - require.NoError(t, err) - - // Fetch the zombie edge by ID. The returned edge info should carry - // the correct gossip version even though the channel data has been - // removed. - info, _, _, err := graph.FetchChannelEdgesByID(ctx, edge.ChannelID) - require.ErrorIs(t, err, ErrZombieEdge) - require.NotNil(t, info) - require.Equal(t, v, info.Version) - require.Equal(t, edge.NodeKey1Bytes, info.NodeKey1Bytes) - require.Equal(t, edge.NodeKey2Bytes, info.NodeKey2Bytes) + assertNumZombies(t, graph, 1) } // compareNodes is used to compare two Nodes. @@ -5524,100 +4388,97 @@ func compareNodes(t *testing.T, a, b *models.Node) { require.Equal(t, a, b) } -// compareEdgePolicies compares two ChannelEdgePolicy values for semantic -// equality after normalizing version-specific/backend-specific differences. -func compareEdgePolicies(t testing.TB, a, b *models.ChannelEdgePolicy) { - t.Helper() - - //nolint:ll - normalize := func(p *models.ChannelEdgePolicy) *models.ChannelEdgePolicy { - if p == nil { - return nil - } - - policy := copyEdgePolicy(p) - if len(policy.ExtraOpaqueData) == 0 { - policy.ExtraOpaqueData = nil - } - if len(policy.ExtraSignedFields) == 0 { - policy.ExtraSignedFields = nil - } - - switch policy.Version { - case lnwire.GossipVersion1: - // SecondPeer is v2-specific; derive canonical direction - // for v1. - policy.SecondPeer = !policy.IsNode1() - policy.LastBlockHeight = 0 - policy.DisableFlags = 0 - policy.ExtraSignedFields = nil - - case lnwire.GossipVersion2: - policy.LastUpdate = time.Time{} - policy.MessageFlags = 0 - policy.ChannelFlags = 0 - policy.ExtraOpaqueData = nil - } - - return policy +// compareEdgePolicies is used to compare two ChannelEdgePolices using +// compareNodes, so as to exclude comparisons of the Nodes' Features struct. +func compareEdgePolicies(a, b *models.ChannelEdgePolicy) error { + if a.ChannelID != b.ChannelID { + return fmt.Errorf("ChannelID doesn't match: expected %v, "+ + "got %v", a.ChannelID, b.ChannelID) + } + if !reflect.DeepEqual(a.LastUpdate, b.LastUpdate) { + return fmt.Errorf("edge LastUpdate doesn't match: "+ + "expected %#v, got %#v", a.LastUpdate, b.LastUpdate) + } + if a.MessageFlags != b.MessageFlags { + return fmt.Errorf("MessageFlags doesn't match: expected %v, "+ + "got %v", a.MessageFlags, b.MessageFlags) + } + if a.ChannelFlags != b.ChannelFlags { + return fmt.Errorf("ChannelFlags doesn't match: expected %v, "+ + "got %v", a.ChannelFlags, b.ChannelFlags) + } + if a.TimeLockDelta != b.TimeLockDelta { + return fmt.Errorf("TimeLockDelta doesn't match: expected %v, "+ + "got %v", a.TimeLockDelta, b.TimeLockDelta) + } + if a.MinHTLC != b.MinHTLC { + return fmt.Errorf("MinHTLC doesn't match: expected %v, "+ + "got %v", a.MinHTLC, b.MinHTLC) + } + if a.MaxHTLC != b.MaxHTLC { + return fmt.Errorf("MaxHTLC doesn't match: expected %v, "+ + "got %v", a.MaxHTLC, b.MaxHTLC) + } + if a.FeeBaseMSat != b.FeeBaseMSat { + return fmt.Errorf("FeeBaseMSat doesn't match: expected %v, "+ + "got %v", a.FeeBaseMSat, b.FeeBaseMSat) + } + if a.FeeProportionalMillionths != b.FeeProportionalMillionths { + return fmt.Errorf("FeeProportionalMillionths doesn't match: "+ + "expected %v, got %v", a.FeeProportionalMillionths, + b.FeeProportionalMillionths) + } + if !bytes.Equal(a.ExtraOpaqueData, b.ExtraOpaqueData) { + return fmt.Errorf("extra data doesn't match: %v vs %v", + a.ExtraOpaqueData, b.ExtraOpaqueData) + } + if !bytes.Equal(a.ToNode[:], b.ToNode[:]) { + return fmt.Errorf("ToNode doesn't match: expected %x, got %x", + a.ToNode, b.ToNode) } - normalizedA := normalize(a) - normalizedB := normalize(b) - require.Equal(t, normalizedA, normalizedB) + return nil } -// testLightningNodeSigVerification checks that we can use the Node's pubkey to -// verify signatures. For v1 this exercises ECDSA, for v2 Schnorr. -func testLightningNodeSigVerification(t *testing.T, - v lnwire.GossipVersion) { - +// TestLightningNodeSigVerification checks that we can use the Node's +// pubkey to verify signatures. +func TestLightningNodeSigVerification(t *testing.T) { t.Parallel() // Create some dummy data to sign. var data [32]byte - _, err := prand.Read(data[:]) - require.NoError(t, err) + if _, err := prand.Read(data[:]); err != nil { + t.Fatalf("unable to read prand: %v", err) + } - // Create private key. + // Create private key and sign the data with it. priv, err := btcec.NewPrivateKey() - require.NoError(t, err, "unable to create priv key") + require.NoError(t, err, "unable to crete priv key") + + sign := ecdsa.Sign(priv, data[:]) + + // Sanity check that the signature checks out. + if !sign.Verify(data[:], priv.PubKey()) { + t.Fatalf("signature doesn't check out") + } // Create a Node from the same private key. - node := createNode(t, v, priv) + node := createNode(priv) - // Retrieve the public key from the node and verify a signature - // produced by the same private key. + // And finally check that we can verify the same signature from the + // pubkey returned from the lightning node. nodePub, err := node.PubKey() require.NoError(t, err, "unable to get pubkey") - // Sign the data using the appropriate scheme for the gossip version. - // V1 uses ECDSA, v2 uses Schnorr. - type verifiable interface { - Verify(hash []byte, pubKey *btcec.PublicKey) bool + if !sign.Verify(data[:], nodePub) { + t.Fatalf("unable to verify sig") } - - var sig verifiable - switch v { - case lnwire.GossipVersion1: - sig = ecdsa.Sign(priv, data[:]) - case lnwire.GossipVersion2: - schnorrSig, sErr := schnorr.Sign(priv, data[:]) - require.NoError(t, sErr) - sig = schnorrSig - } - - // Verify against the raw private key's pubkey, then against the - // pubkey extracted from the Node. - require.True(t, sig.Verify(data[:], priv.PubKey())) - require.True(t, sig.Verify(data[:], nodePub)) } // TestComputeFee tests fee calculation based on the outgoing amt. func TestComputeFee(t *testing.T) { var ( policy = models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, FeeBaseMSat: 10000, FeeProportionalMillionths: 30000, } @@ -5626,24 +4487,26 @@ func TestComputeFee(t *testing.T) { ) fee := policy.ComputeFee(outgoingAmt) - require.Equal(t, expectedFee, fee) + if fee != expectedFee { + t.Fatalf("expected fee %v, got %v", expectedFee, fee) + } } // TestBatchedAddChannelEdge asserts that BatchedAddChannelEdge properly // executes multiple AddChannelEdge requests in a single txn. -func testBatchedAddChannelEdge(t *testing.T, v lnwire.GossipVersion) { +func TestBatchedAddChannelEdge(t *testing.T) { t.Parallel() ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), v) + graph := MakeTestGraph(t) - sourceNode := createTestVertex(t, v) + sourceNode := createTestVertex(t) require.Nil(t, graph.SetSourceNode(ctx, sourceNode)) // We'd like to test the insertion/deletion of edges, so we create two // vertexes to connect. - node1 := createTestVertex(t, v) - node2 := createTestVertex(t, v) + node1 := createTestVertex(t) + node2 := createTestVertex(t) // In addition to the fake vertexes we create some fake channel // identifiers. @@ -5653,12 +4516,12 @@ func testBatchedAddChannelEdge(t *testing.T, v lnwire.GossipVersion) { // Prune the graph a few times to make sure we have entries in the // prune log. - _, err := graph.PruneGraph(ctx, spendOutputs, &blockHash, 155) + _, err := graph.PruneGraph(spendOutputs, &blockHash, 155) require.Nil(t, err) var blockHash2 chainhash.Hash copy(blockHash2[:], bytes.Repeat([]byte{2}, 32)) - _, err = graph.PruneGraph(ctx, spendOutputs, &blockHash2, 156) + _, err = graph.PruneGraph(spendOutputs, &blockHash2, 156) require.Nil(t, err) // We'll create 3 almost identical edges, so first create a helper @@ -5666,23 +4529,21 @@ func testBatchedAddChannelEdge(t *testing.T, v lnwire.GossipVersion) { // Create an edge which has its block height at 156. height := uint32(156) - edgeInfo, _ := createEdge(v, height, 0, 0, 0, node1, node2) + edgeInfo, _ := createEdge(height, 0, 0, 0, node1, node2) // Create an edge with block height 157. We give it // maximum values for tx index and position, to make // sure our database range scan get edges from the // entire range. edgeInfo2, _ := createEdge( - v, height+1, math.MaxUint32&0x00ffffff, math.MaxUint16, 1, + height+1, math.MaxUint32&0x00ffffff, math.MaxUint16, 1, node1, node2, ) // Create a third edge, this with a block height of 155. - edgeInfo3, _ := createEdge( - v, height-1, 0, 0, 2, node1, node2, - ) + edgeInfo3, _ := createEdge(height-1, 0, 0, 2, node1, node2) - edges := []models.ChannelEdgeInfo{*edgeInfo, *edgeInfo2, *edgeInfo3} + edges := []models.ChannelEdgeInfo{edgeInfo, edgeInfo2, edgeInfo3} errChan := make(chan error, len(edges)) errTimeout := errors.New("timeout adding batched channel") @@ -5710,21 +4571,21 @@ func testBatchedAddChannelEdge(t *testing.T, v lnwire.GossipVersion) { // TestBatchedUpdateEdgePolicy asserts that BatchedUpdateEdgePolicy properly // executes multiple UpdateEdgePolicy requests in a single txn. -func testBatchedUpdateEdgePolicy(t *testing.T, v lnwire.GossipVersion) { +func TestBatchedUpdateEdgePolicy(t *testing.T) { t.Parallel() ctx := t.Context() - graph := NewVersionedGraph(MakeTestGraph(t), v) + graph := MakeTestGraph(t) // We'd like to test the update of edges inserted into the database, so // we create two vertexes to connect. - node1 := createTestVertex(t, v) + node1 := createTestVertex(t) require.NoError(t, graph.AddNode(ctx, node1)) - node2 := createTestVertex(t, v) + node2 := createTestVertex(t) require.NoError(t, graph.AddNode(ctx, node2)) // Create an edge and add it to the db. - edgeInfo, edge1, edge2 := createChannelEdge(node1, node2, v) + edgeInfo, edge1, edge2 := createChannelEdge(node1, node2) // Make sure inserting the policy at this point, before the edge info // is added, will fail. @@ -5769,9 +4630,7 @@ func BenchmarkForEachChannel(b *testing.B) { const numNodes = 100 const numChannels = 4 - _, _ = fillTestGraph( - b, graph, numNodes, numChannels, lnwire.GossipVersion1, - ) + _, _ = fillTestGraph(b, graph, numNodes, numChannels) b.ReportAllocs() b.ResetTimer() @@ -5782,22 +4641,21 @@ func BenchmarkForEachChannel(b *testing.B) { ) var nodes []route.Vertex - err := graph.ForEachNodeCacheable( - ctx, lnwire.GossipVersion1, func(node route.Vertex, - vector *lnwire.FeatureVector) error { + err := graph.ForEachNodeCacheable(ctx, func(node route.Vertex, + vector *lnwire.FeatureVector) error { - nodes = append(nodes, node) + nodes = append(nodes, node) - return nil - }, func() { - nodes = nil - }) + return nil + }, func() { + nodes = nil + }) require.NoError(b, err) for _, n := range nodes { cb := func(info *models.ChannelEdgeInfo, policy *models.ChannelEdgePolicy, - policy2 *models.ChannelEdgePolicy) error { + policy2 *models.ChannelEdgePolicy) error { //nolint:ll // We need to do something with // the data here, otherwise the @@ -5811,36 +4669,31 @@ func BenchmarkForEachChannel(b *testing.B) { return nil } - err := graph.ForEachNodeChannel( - ctx, lnwire.GossipVersion1, n, cb, func() {}, - ) + err := graph.ForEachNodeChannel(ctx, n, cb, func() {}) require.NoError(b, err) } } } -// TestForEachNodeDirectedChannel tests that the ForEachNodeDirectedChannel +// TestGraphCacheForEachNodeChannel tests that the forEachNodeDirectedChannel // method works as expected, and is able to handle nil self edges. -func testGraphCacheForEachNodeChannel(t *testing.T, - v lnwire.GossipVersion) { - +func TestGraphCacheForEachNodeChannel(t *testing.T) { t.Parallel() ctx := t.Context() - // Unset the channel graph cache to simulate the user running with the - // option turned off. This forces the V1Store ForEachNodeDirectedChannel - // to be queried instead of the graph cache's ForEachChannel method. - graph := NewVersionedGraph( - MakeTestGraph(t, WithUseGraphCache(false)), v, - ) + graph := MakeTestGraph(t) - node1 := createTestVertex(t, v) + // Unset the channel graph cache to simulate the user running with the + // option turned off. + graph.graphCache = nil + + node1 := createTestVertex(t) require.NoError(t, graph.AddNode(ctx, node1)) - node2 := createTestVertex(t, v) + node2 := createTestVertex(t) require.NoError(t, graph.AddNode(ctx, node2)) // Create an edge and add it to the db. - edgeInfo, e1, e2 := createChannelEdge(node1, node2, v) + edgeInfo, e1, e2 := createChannelEdge(node1, node2) // Because of lexigraphical sorting and the usage of random node keys in // this test, we need to determine which edge belongs to node 1 at @@ -5857,8 +4710,7 @@ func testGraphCacheForEachNodeChannel(t *testing.T, getSingleChannel := func() *DirectedChannel { var ch *DirectedChannel - err := graph.db.ForEachNodeDirectedChannel( - ctx, v, node1.PubKeyBytes, + err := graph.ForEachNodeDirectedChannel(node1.PubKeyBytes, func(c *DirectedChannel) error { require.Nil(t, ch) ch = c @@ -5884,12 +4736,6 @@ func testGraphCacheForEachNodeChannel(t *testing.T, FeeRate: 20, } edge1.InboundFee = fn.Some(inboundFee) - switch v { - case lnwire.GossipVersion1: - edge1.LastUpdate = edge1.LastUpdate.Add(time.Second) - case lnwire.GossipVersion2: - edge1.LastBlockHeight = nextBlockHeight() - } require.NoError(t, graph.UpdateEdgePolicy(ctx, edge1)) edge1 = copyEdgePolicy(edge1) // Avoid read/write race conditions. @@ -5897,29 +4743,22 @@ func testGraphCacheForEachNodeChannel(t *testing.T, require.NotNil(t, directedChan) require.Equal(t, inboundFee, directedChan.InboundFee) - // The below test only applies to v1 since in v2, we would fail TLV - // parsing at the lnwire level when parsing bytes from the wire. - if v == lnwire.GossipVersion1 { - // Set an invalid inbound fee and check that persistence fails. - edge1.ExtraOpaqueData = []byte{ - 253, 217, 3, 8, 0, - } - // We need to update the timestamp so that we don't hit - // the DB conflict error when we try to update the edge - // policy. - edge1.LastUpdate = edge1.LastUpdate.Add(time.Second) - require.ErrorIs( - t, graph.UpdateEdgePolicy(ctx, edge1), - ErrParsingExtraTLVBytes, - ) - - // Since persistence of the last update failed, we should - // still bet the previous result when we query the channel - // again. - directedChan = getSingleChannel() - require.NotNil(t, directedChan) - require.Equal(t, inboundFee, directedChan.InboundFee) + // Set an invalid inbound fee and check that persistence fails. + edge1.ExtraOpaqueData = []byte{ + 253, 217, 3, 8, 0, } + // We need to update the timestamp so that we don't hit the DB conflict + // error when we try to update the edge policy. + edge1.LastUpdate = edge1.LastUpdate.Add(time.Second) + require.ErrorIs( + t, graph.UpdateEdgePolicy(ctx, edge1), ErrParsingExtraTLVBytes, + ) + + // Since persistence of the last update failed, we should still bet + // the previous result when we query the channel again. + directedChan = getSingleChannel() + require.NotNil(t, directedChan) + require.Equal(t, inboundFee, directedChan.InboundFee) } // TestGraphLoading asserts that the cache is properly reconstructed after a @@ -5930,9 +4769,7 @@ func TestGraphLoading(t *testing.T) { // Next, create the graph for the first time. graphStore := NewTestDB(t) - graph, err := NewChannelGraph( - graphStore, WithSyncGraphCachePopulation(), - ) + graph, err := NewChannelGraph(graphStore) require.NoError(t, err) require.NoError(t, graph.Start()) t.Cleanup(func() { @@ -5942,15 +4779,11 @@ func TestGraphLoading(t *testing.T) { // Populate the graph with test data. const numNodes = 100 const numChannels = 4 - _, _ = fillTestGraph( - t, graph, numNodes, numChannels, lnwire.GossipVersion1, - ) + _, _ = fillTestGraph(t, graph, numNodes, numChannels) // Recreate the graph. This should cause the graph cache to be // populated. - graphReloaded, err := NewChannelGraph( - graphStore, WithSyncGraphCachePopulation(), - ) + graphReloaded, err := NewChannelGraph(graphStore) require.NoError(t, err) require.NoError(t, graphReloaded.Start()) t.Cleanup(func() { @@ -5959,537 +4792,16 @@ func TestGraphLoading(t *testing.T) { // Assert that the cache content is identical. require.Equal( - t, graph.cache.graphCache.nodeChannels, - graphReloaded.cache.graphCache.nodeChannels, + t, graph.graphCache.nodeChannels, + graphReloaded.graphCache.nodeChannels, ) require.Equal( - t, graph.cache.graphCache.nodeFeatures, - graphReloaded.cache.graphCache.nodeFeatures, + t, graph.graphCache.nodeFeatures, + graphReloaded.graphCache.nodeFeatures, ) } -// TestAsyncGraphCache tests the behaviour of the ChannelGraph when the graph -// cache is populated asynchronously. -func TestAsyncGraphCache(t *testing.T) { - t.Parallel() - ctx := t.Context() - - const ( - numNodes = 100 - numChannels = 3 - ) - - // Next, create the graph for the first time. - graphStore := NewTestDB(t) - - // The first time we spin up the graph, we Start is as normal and fill - // it with test data. This will ensure that the graph cache has - // something to load on the next Start. - graph, err := NewChannelGraph(graphStore) - require.NoError(t, err) - require.NoError(t, graph.Start()) - channels, nodes := fillTestGraph( - t, graph, numNodes, numChannels, lnwire.GossipVersion1, - ) - - assertGraphState := func() { - var ( - numNodes int - chanIndex = make(map[uint64]struct{}, numChannels) - ) - - // We query the graph for all nodes and channels, and - // assert that we get the expected number of nodes and - // channels. - err := graph.ForEachNodeCached( - ctx, lnwire.GossipVersion1, - func(_ context.Context, node route.Vertex, - chans map[uint64]*DirectedChannel) error { - - numNodes++ - for chanID := range chans { - chanIndex[chanID] = struct{}{} - } - - return nil - }, func() { - numNodes = 0 - chanIndex = make( - map[uint64]struct{}, numChannels, - ) - }, - ) - require.NoError(t, err) - - require.Equal(t, len(nodes), numNodes) - require.Equal(t, len(channels), len(chanIndex)) - } - - assertGraphState() - - // Now we stop the graph. - require.NoError(t, graph.Stop()) - - // Recreate it but don't start it yet. - graph, err = NewChannelGraph(graphStore) - require.NoError(t, err) - - // Spin off a goroutine that starts to make queries to the ChannelGraph. - // We start this before we start the graph, so that we can ensure that - // the queries are made while the graph cache is being populated. - var ( - wg sync.WaitGroup - numRuns = 10 - ) - for i := 0; i < numRuns; i++ { - wg.Add(1) - go func() { - defer wg.Done() - - assertGraphState() - }() - } - - require.NoError(t, graph.Start()) - t.Cleanup(func() { - require.NoError(t, graph.Stop()) - }) - - wg.Wait() - - // Wait for the cache to be fully populated. - err = wait.Predicate(func() bool { - return graph.cache.isLoaded() - }, wait.DefaultTimeout) - require.NoError(t, err) - - // And then assert that all the expected nodes and channels are - // present in the graph cache. - for _, node := range nodes { - _, ok := graph.cache.graphCache.nodeChannels[node.PubKeyBytes] - require.True(t, ok) - } -} - -type blockingCacheLoadStore struct { - Store - - cacheLoadStarted chan struct{} - allowCacheLoad chan struct{} - blockOnce sync.Once -} - -// ForEachChannelCacheable pauses the first cacheable channel iteration until -// the test allows it to continue. -func (s *blockingCacheLoadStore) ForEachChannelCacheable(ctx context.Context, - v lnwire.GossipVersion, cb func(*models.CachedEdgeInfo, - *models.CachedEdgePolicy, *models.CachedEdgePolicy) error, - reset func()) error { - - return s.Store.ForEachChannelCacheable( - ctx, v, func(info *models.CachedEdgeInfo, - policy1, - policy2 *models.CachedEdgePolicy) error { - - s.blockOnce.Do(func() { - close(s.cacheLoadStarted) - <-s.allowCacheLoad - }) - - return cb(info, policy1, policy2) - }, reset, - ) -} - -type shutdownBlockingCacheLoadStore struct { - Store - - cacheLoadStarted chan struct{} - blockOnce sync.Once -} - -// ForEachChannelCacheable blocks until the context is canceled so tests can -// assert that Stop interrupts async cache population. -func (s *shutdownBlockingCacheLoadStore) ForEachChannelCacheable( - ctx context.Context, v lnwire.GossipVersion, - cb func(*models.CachedEdgeInfo, *models.CachedEdgePolicy, - *models.CachedEdgePolicy) error, reset func()) error { - - return s.Store.ForEachChannelCacheable( - ctx, v, func(info *models.CachedEdgeInfo, - policy1, - policy2 *models.CachedEdgePolicy) error { - - s.blockOnce.Do(func() { - close(s.cacheLoadStarted) - <-ctx.Done() - }) - - return ctx.Err() - }, reset, - ) -} - -type failingCacheLoadStore struct { - Store - - cacheLoadAttempted chan struct{} - populateErr error -} - -// ForEachChannelCacheable fails the initial cache population after signaling -// that the async load reached channel iteration. -func (s *failingCacheLoadStore) ForEachChannelCacheable(ctx context.Context, - v lnwire.GossipVersion, cb func(*models.CachedEdgeInfo, - *models.CachedEdgePolicy, *models.CachedEdgePolicy) error, - reset func()) error { - - close(s.cacheLoadAttempted) - - return s.populateErr -} - -// TestAsyncGraphCacheReplaysConcurrentWrites asserts that graph mutations that -// happen while the async cache population is running are replayed onto the -// cache before it becomes readable. -func TestAsyncGraphCacheReplaysConcurrentWrites(t *testing.T) { - t.Parallel() - ctx := t.Context() - - store := NewTestDB(t) - - setupGraph, err := NewChannelGraph( - store, WithSyncGraphCachePopulation(), - ) - require.NoError(t, err) - require.NoError(t, setupGraph.Start()) - - node1 := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, setupGraph.AddNode(ctx, node1)) - node2 := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, setupGraph.AddNode(ctx, node2)) - - edgeInfo, edge1, edge2 := createChannelEdge( - node1, node2, lnwire.GossipVersion1, - ) - require.NoError(t, setupGraph.AddChannelEdge(ctx, edgeInfo)) - require.NoError(t, setupGraph.UpdateEdgePolicy(ctx, edge1)) - require.NoError(t, setupGraph.UpdateEdgePolicy(ctx, edge2)) - require.NoError(t, setupGraph.Stop()) - - blockingStore := &blockingCacheLoadStore{ - Store: store, - cacheLoadStarted: make(chan struct{}), - allowCacheLoad: make(chan struct{}), - } - - graph, err := NewChannelGraph(blockingStore) - require.NoError(t, err) - require.NoError(t, graph.Start()) - t.Cleanup(func() { - require.NoError(t, graph.Stop()) - }) - - <-blockingStore.cacheLoadStarted - - updatedEdge := *edge1 - updatedEdge.LastUpdate = nextUpdateTime() - updatedEdge.FeeBaseMSat++ - require.NoError(t, graph.UpdateEdgePolicy(ctx, &updatedEdge)) - - close(blockingStore.allowCacheLoad) - - err = wait.Predicate(func() bool { - return graph.cache.isLoaded() - }, wait.DefaultTimeout) - require.NoError(t, err) - - var cachedFee lnwire.MilliSatoshi - err = graph.ForEachNodeDirectedChannel( - ctx, updatedEdge.ToNode, - func(channel *DirectedChannel) error { - if channel.ChannelID != updatedEdge.ChannelID { - return nil - } - - require.NotNil(t, channel.InPolicy) - cachedFee = channel.InPolicy.FeeBaseMSat - - return nil - }, func() {}, - ) - require.NoError(t, err) - require.Equal(t, updatedEdge.FeeBaseMSat, cachedFee) -} - -// TestAsyncGraphCacheStopCancelsLoad asserts that Stop interrupts async cache -// population instead of waiting for the full load to finish. -func TestAsyncGraphCacheStopCancelsLoad(t *testing.T) { - t.Parallel() - ctx := t.Context() - - store := NewTestDB(t) - - setupGraph, err := NewChannelGraph( - store, WithSyncGraphCachePopulation(), - ) - require.NoError(t, err) - require.NoError(t, setupGraph.Start()) - - node1 := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, setupGraph.AddNode(ctx, node1)) - node2 := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, setupGraph.AddNode(ctx, node2)) - - edgeInfo, edge1, edge2 := createChannelEdge( - node1, node2, lnwire.GossipVersion1, - ) - require.NoError(t, setupGraph.AddChannelEdge(ctx, edgeInfo)) - require.NoError(t, setupGraph.UpdateEdgePolicy(ctx, edge1)) - require.NoError(t, setupGraph.UpdateEdgePolicy(ctx, edge2)) - require.NoError(t, setupGraph.Stop()) - - blockingStore := &shutdownBlockingCacheLoadStore{ - Store: store, - cacheLoadStarted: make(chan struct{}), - } - - graph, err := NewChannelGraph(blockingStore) - require.NoError(t, err) - require.NoError(t, graph.Start()) - - <-blockingStore.cacheLoadStarted - - stopErr := make(chan error, 1) - go func() { - stopErr <- graph.Stop() - }() - - select { - case err := <-stopErr: - require.NoError(t, err) - - case <-time.After(wait.DefaultTimeout): - t.Fatal("Stop did not cancel graph cache loading") - } -} - -// TestAsyncGraphCachePopulationFailureFallsBackToDB asserts that cache -// population errors leave the cache unreadable while reads continue to succeed -// through the DB-backed path. -func TestAsyncGraphCachePopulationFailureFallsBackToDB(t *testing.T) { - t.Parallel() - ctx := t.Context() - - store := NewTestDB(t) - - setupGraph, err := NewChannelGraph( - store, WithSyncGraphCachePopulation(), - ) - require.NoError(t, err) - require.NoError(t, setupGraph.Start()) - - node1 := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, setupGraph.AddNode(ctx, node1)) - node2 := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, setupGraph.AddNode(ctx, node2)) - - edgeInfo, edge1, edge2 := createChannelEdge( - node1, node2, lnwire.GossipVersion1, - ) - require.NoError(t, setupGraph.AddChannelEdge(ctx, edgeInfo)) - require.NoError(t, setupGraph.UpdateEdgePolicy(ctx, edge1)) - require.NoError(t, setupGraph.UpdateEdgePolicy(ctx, edge2)) - require.NoError(t, setupGraph.Stop()) - - populateErr := errors.New("cache population failed") - failingStore := &failingCacheLoadStore{ - Store: store, - cacheLoadAttempted: make(chan struct{}), - populateErr: populateErr, - } - - graph, err := NewChannelGraph(failingStore) - require.NoError(t, err) - require.NoError(t, graph.Start()) - t.Cleanup(func() { - require.NoError(t, graph.Stop()) - }) - - <-failingStore.cacheLoadAttempted - - err = wait.Predicate(func() bool { - return graph.GraphCacheStatus() == GraphCacheStatusFailed - }, wait.DefaultTimeout) - require.NoError(t, err) - require.False(t, graph.cache.isLoaded()) - - var numChannels int - err = graph.ForEachNodeDirectedChannel( - ctx, edge1.ToNode, - func(channel *DirectedChannel) error { - if channel.ChannelID != edge1.ChannelID { - return nil - } - - numChannels++ - require.NotNil(t, channel.InPolicy) - require.Equal(t, edge1.FeeBaseMSat, - channel.InPolicy.FeeBaseMSat) - - return nil - }, func() {}, - ) - require.NoError(t, err) - require.Equal(t, 1, numChannels) -} - -// TestGraphCacheStatus asserts that the graph cache reports disabled, loading, -// loaded and failed states as expected. -func TestGraphCacheStatus(t *testing.T) { - t.Parallel() - ctx := t.Context() - - store := NewTestDB(t) - - disabledGraph, err := NewChannelGraph( - store, WithUseGraphCache(false), - ) - require.NoError(t, err) - require.Equal( - t, GraphCacheStatusDisabled, disabledGraph.GraphCacheStatus(), - ) - require.NoError(t, disabledGraph.Start()) - require.Equal( - t, GraphCacheStatusDisabled, disabledGraph.GraphCacheStatus(), - ) - require.NoError(t, disabledGraph.Stop()) - - setupGraph, err := NewChannelGraph( - store, WithSyncGraphCachePopulation(), - ) - require.NoError(t, err) - require.NoError(t, setupGraph.Start()) - - node1 := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, setupGraph.AddNode(ctx, node1)) - node2 := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, setupGraph.AddNode(ctx, node2)) - - edgeInfo, edge1, edge2 := createChannelEdge( - node1, node2, lnwire.GossipVersion1, - ) - require.NoError(t, setupGraph.AddChannelEdge(ctx, edgeInfo)) - require.NoError(t, setupGraph.UpdateEdgePolicy(ctx, edge1)) - require.NoError(t, setupGraph.UpdateEdgePolicy(ctx, edge2)) - require.NoError(t, setupGraph.Stop()) - - blockingStore := &blockingCacheLoadStore{ - Store: store, - cacheLoadStarted: make(chan struct{}), - allowCacheLoad: make(chan struct{}), - } - - graph, err := NewChannelGraph(blockingStore) - require.NoError(t, err) - require.Equal(t, GraphCacheStatusLoading, graph.GraphCacheStatus()) - require.NoError(t, graph.Start()) - t.Cleanup(func() { - require.NoError(t, graph.Stop()) - }) - - <-blockingStore.cacheLoadStarted - require.Equal(t, GraphCacheStatusLoading, graph.GraphCacheStatus()) - - close(blockingStore.allowCacheLoad) - err = wait.Predicate(func() bool { - return graph.GraphCacheStatus() == GraphCacheStatusLoaded - }, wait.DefaultTimeout) - require.NoError(t, err) - require.NoError(t, graph.Stop()) - - // Assert the failed state by using a store that errors during cache - // population. - populateErr := errors.New("cache population failed") - failingStore := &failingCacheLoadStore{ - Store: store, - cacheLoadAttempted: make(chan struct{}), - populateErr: populateErr, - } - - failedGraph, err := NewChannelGraph(failingStore) - require.NoError(t, err) - require.NoError(t, failedGraph.Start()) - t.Cleanup(func() { - require.NoError(t, failedGraph.Stop()) - }) - - <-failingStore.cacheLoadAttempted - err = wait.Predicate(func() bool { - return failedGraph.GraphCacheStatus() == GraphCacheStatusFailed - }, wait.DefaultTimeout) - require.NoError(t, err) -} - -// TestKVCacheableIteratorsRespectCancellation asserts that KV-backed cache -// iterators return when their context is canceled. -func TestKVCacheableIteratorsRespectCancellation(t *testing.T) { - t.Parallel() - - if isSQLDB { - t.Skip("KV iterator cancellation is specific to KVStore") - } - - ctx := t.Context() - store := NewTestDB(t) - - kvStore, ok := store.(*KVStore) - require.True(t, ok) - - graph, err := NewChannelGraph( - kvStore, WithSyncGraphCachePopulation(), - ) - require.NoError(t, err) - require.NoError(t, graph.Start()) - t.Cleanup(func() { - require.NoError(t, graph.Stop()) - }) - - node1 := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, graph.AddNode(ctx, node1)) - node2 := createTestVertex(t, lnwire.GossipVersion1) - require.NoError(t, graph.AddNode(ctx, node2)) - - edgeInfo, edge1, edge2 := createChannelEdge( - node1, node2, lnwire.GossipVersion1, - ) - require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo)) - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge1)) - require.NoError(t, graph.UpdateEdgePolicy(ctx, edge2)) - - canceledCtx, cancel := context.WithCancel(ctx) - cancel() - - err = kvStore.ForEachNodeCacheable( - canceledCtx, lnwire.GossipVersion1, - func(route.Vertex, *lnwire.FeatureVector) error { - return nil - }, func() {}, - ) - require.ErrorIs(t, err, context.Canceled) - - err = kvStore.ForEachChannelCacheable( - canceledCtx, lnwire.GossipVersion1, - func(*models.CachedEdgeInfo, *models.CachedEdgePolicy, - *models.CachedEdgePolicy) error { - - return nil - }, func() {}, - ) - require.ErrorIs(t, err, context.Canceled) -} - // TestClosedScid tests that we can correctly insert a SCID into the index of // closed short channel ids. func TestClosedScid(t *testing.T) { @@ -6500,16 +4812,16 @@ func TestClosedScid(t *testing.T) { scid := lnwire.ShortChannelID{} // The scid should not exist in the closedScidBucket. - exists, err := graph.IsClosedScid(t.Context(), scid) + exists, err := graph.IsClosedScid(scid) require.Nil(t, err) require.False(t, exists) // After we call PutClosedScid, the call to IsClosedScid should return // true. - err = graph.PutClosedScid(t.Context(), scid) + err = graph.PutClosedScid(scid) require.Nil(t, err) - exists, err = graph.IsClosedScid(t.Context(), scid) + exists, err = graph.IsClosedScid(scid) require.Nil(t, err) require.True(t, exists) } @@ -6532,7 +4844,7 @@ func TestLightningNodePersistence(t *testing.T) { ctx := t.Context() // Create a new test graph instance. - graph := NewVersionedGraph(MakeTestGraph(t), lnwire.GossipVersion1) + graph := MakeTestGraph(t) nodeAnnBytes, err := hex.DecodeString(testNodeAnn) require.NoError(t, err) @@ -6566,207 +4878,3 @@ func TestLightningNodePersistence(t *testing.T) { require.Equal(t, nodeAnnBytes, b.Bytes()) } - -// TestUpdateRangeValidateForVersion verifies that ChanUpdateRange and -// NodeUpdateRange reject invalid field combinations for each gossip version. -func TestUpdateRangeValidateForVersion(t *testing.T) { - t.Parallel() - - now := time.Now() - - tests := []struct { - name string - fn func() error - wantErr string - }{ - { - name: "v1 chan range with time - ok", - fn: func() error { - r := ChanUpdateRange{ - StartTime: fn.Some(now), - EndTime: fn.Some(now), - } - - return r.validateForVersion( - lnwire.GossipVersion1, - ) - }, - }, - { - name: "v1 chan range with height - rejected", - fn: func() error { - r := ChanUpdateRange{ - StartHeight: fn.Some(uint32(1)), - EndHeight: fn.Some(uint32(100)), - } - - return r.validateForVersion( - lnwire.GossipVersion1, - ) - }, - wantErr: "v1 chan update range must use time", - }, - { - name: "v2 chan range with height - ok", - fn: func() error { - r := ChanUpdateRange{ - StartHeight: fn.Some(uint32(1)), - EndHeight: fn.Some(uint32(100)), - } - - return r.validateForVersion( - lnwire.GossipVersion2, - ) - }, - }, - { - name: "v2 chan range with time - rejected", - fn: func() error { - r := ChanUpdateRange{ - StartTime: fn.Some(now), - EndTime: fn.Some(now), - } - - return r.validateForVersion( - lnwire.GossipVersion2, - ) - }, - wantErr: "v2 chan update range must use blocks", - }, - { - name: "mixed chan range - rejected", - fn: func() error { - r := ChanUpdateRange{ - StartTime: fn.Some(now), - StartHeight: fn.Some(uint32(1)), - } - - return r.validateForVersion( - lnwire.GossipVersion1, - ) - }, - wantErr: "both time and block", - }, - { - name: "v1 node range with time - ok", - fn: func() error { - r := NodeUpdateRange{ - StartTime: fn.Some(now), - EndTime: fn.Some(now), - } - - return r.validateForVersion( - lnwire.GossipVersion1, - ) - }, - }, - { - name: "v2 node range with height - ok", - fn: func() error { - r := NodeUpdateRange{ - StartHeight: fn.Some(uint32(1)), - EndHeight: fn.Some(uint32(100)), - } - - return r.validateForVersion( - lnwire.GossipVersion2, - ) - }, - }, - { - name: "v2 node range with time - rejected", - fn: func() error { - r := NodeUpdateRange{ - StartTime: fn.Some(now), - EndTime: fn.Some(now), - } - - return r.validateForVersion( - lnwire.GossipVersion2, - ) - }, - wantErr: "v2 node update range must use height", - }, - { - name: "v1 chan range missing bounds - rejected", - fn: func() error { - r := ChanUpdateRange{ - StartTime: fn.Some(now), - } - - return r.validateForVersion( - lnwire.GossipVersion1, - ) - }, - wantErr: "missing time bounds", - }, - { - name: "v1 chan range inverted - rejected", - fn: func() error { - r := ChanUpdateRange{ - StartTime: fn.Some(now.Add(time.Hour)), - EndTime: fn.Some(now), - } - - return r.validateForVersion( - lnwire.GossipVersion1, - ) - }, - wantErr: "start time after end time", - }, - { - name: "v2 chan range inverted - rejected", - fn: func() error { - r := ChanUpdateRange{ - StartHeight: fn.Some(uint32(100)), - EndHeight: fn.Some(uint32(50)), - } - - return r.validateForVersion( - lnwire.GossipVersion2, - ) - }, - wantErr: "start height after end height", - }, - { - name: "v1 node range inverted - rejected", - fn: func() error { - r := NodeUpdateRange{ - StartTime: fn.Some(now.Add(time.Hour)), - EndTime: fn.Some(now), - } - - return r.validateForVersion( - lnwire.GossipVersion1, - ) - }, - wantErr: "start time after end time", - }, - { - name: "v2 node range inverted - rejected", - fn: func() error { - r := NodeUpdateRange{ - StartHeight: fn.Some(uint32(100)), - EndHeight: fn.Some(uint32(50)), - } - - return r.validateForVersion( - lnwire.GossipVersion2, - ) - }, - wantErr: "start height after end height", - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - err := tc.fn() - if tc.wantErr == "" { - require.NoError(t, err) - } else { - require.ErrorContains(t, err, - tc.wantErr) - } - }) - } -} diff --git a/graph/db/interfaces.go b/graph/db/interfaces.go index df112064d..25eb6f5b1 100644 --- a/graph/db/interfaces.go +++ b/graph/db/interfaces.go @@ -7,8 +7,8 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/batch" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lnwire" @@ -21,26 +21,17 @@ import ( type NodeTraverser interface { // ForEachNodeDirectedChannel calls the callback for every channel of // the given node. - ForEachNodeDirectedChannel(ctx context.Context, nodePub route.Vertex, + ForEachNodeDirectedChannel(nodePub route.Vertex, cb func(channel *DirectedChannel) error, reset func()) error // FetchNodeFeatures returns the features of the given node. - FetchNodeFeatures(ctx context.Context, - nodePub route.Vertex) (*lnwire.FeatureVector, error) + FetchNodeFeatures(nodePub route.Vertex) (*lnwire.FeatureVector, error) } -// Store represents the main interface for the channel graph database for all +// V1Store represents the main interface for the channel graph database for all // channels and nodes gossiped via the V1 gossip protocol as defined in BOLT 7. -type Store interface { //nolint:interfacebloat - // ForEachNodeDirectedChannel calls the callback for every channel of - // the given node. - ForEachNodeDirectedChannel(ctx context.Context, v lnwire.GossipVersion, - nodePub route.Vertex, cb func(channel *DirectedChannel) error, - reset func()) error - - // FetchNodeFeatures returns the features of the given node. - FetchNodeFeatures(ctx context.Context, v lnwire.GossipVersion, - nodePub route.Vertex) (*lnwire.FeatureVector, error) +type V1Store interface { //nolint:interfacebloat + NodeTraverser // AddNode adds a vertex/node to the graph database. If the // node is not in the database from before, this will add a new, @@ -54,14 +45,14 @@ type Store interface { //nolint:interfacebloat // AddrsForNode returns all known addresses for the target node public // key that the graph DB is aware of. The returned boolean indicates if // the given node is unknown to the graph DB or not. - AddrsForNode(ctx context.Context, v lnwire.GossipVersion, + AddrsForNode(ctx context.Context, nodePub *btcec.PublicKey) (bool, []net.Addr, error) // ForEachSourceNodeChannel iterates through all channels of the source // node, executing the passed callback on each. The call-back is // provided with the channel's outpoint, whether we have a policy for // the channel and the channel peer's node information. - ForEachSourceNodeChannel(ctx context.Context, v lnwire.GossipVersion, + ForEachSourceNodeChannel(ctx context.Context, cb func(chanPoint wire.OutPoint, havePolicy bool, otherNode *models.Node) error, reset func()) error @@ -75,17 +66,21 @@ type Store interface { //nolint:interfacebloat // to the caller. // // Unknown policies are passed into the callback as nil values. - ForEachNodeChannel(ctx context.Context, v lnwire.GossipVersion, - nodePub route.Vertex, cb func(*models.ChannelEdgeInfo, - *models.ChannelEdgePolicy, + ForEachNodeChannel(ctx context.Context, nodePub route.Vertex, + cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error, reset func()) error // ForEachNodeCached is similar to forEachNode, but it returns - // DirectedChannel data to the call-back. + // DirectedChannel data to the call-back. If withAddrs is true, then + // the call-back will also be provided with the addresses associated + // with the node. The address retrieval will likely result in an + // additional round-trip to the database, so it should only be used if + // the addresses are actually needed. // // NOTE: The callback contents MUST not be modified. - ForEachNodeCached(ctx context.Context, v lnwire.GossipVersion, + ForEachNodeCached(ctx context.Context, withAddrs bool, cb func(ctx context.Context, node route.Vertex, + addrs []net.Addr, chans map[uint64]*DirectedChannel) error, reset func()) error @@ -93,68 +88,53 @@ type Store interface { //nolint:interfacebloat // graph, executing the passed callback with each node encountered. If // the callback returns an error, then the transaction is aborted and // the iteration stops early. - ForEachNode(ctx context.Context, v lnwire.GossipVersion, - cb func(*models.Node) error, reset func()) error + ForEachNode(ctx context.Context, cb func(*models.Node) error, + reset func()) error // ForEachNodeCacheable iterates through all the stored vertices/nodes // in the graph, executing the passed callback with each node // encountered. If the callback returns an error, then the transaction // is aborted and the iteration stops early. - ForEachNodeCacheable(ctx context.Context, v lnwire.GossipVersion, - cb func(route.Vertex, *lnwire.FeatureVector) error, - reset func()) error + ForEachNodeCacheable(ctx context.Context, cb func(route.Vertex, + *lnwire.FeatureVector) error, reset func()) error // LookupAlias attempts to return the alias as advertised by the target // node. - LookupAlias(ctx context.Context, v lnwire.GossipVersion, - pub *btcec.PublicKey) (string, error) + LookupAlias(ctx context.Context, pub *btcec.PublicKey) (string, error) // DeleteNode starts a new database transaction to remove a // vertex/node from the database according to the node's public key. - DeleteNode(ctx context.Context, v lnwire.GossipVersion, - nodePub route.Vertex) error + DeleteNode(ctx context.Context, nodePub route.Vertex) error - // NodeUpdatesInHorizon returns all the known lightning nodes which have - // updates within the passed range for the given gossip version. For v1 - // gossip, the range is time-based with [start, end) per BOLT 07. This - // method can be used by two nodes to quickly determine if they have - // the same set of up to date node announcements. - NodeUpdatesInHorizon(ctx context.Context, v lnwire.GossipVersion, - r NodeUpdateRange, - opts ...IteratorOption) iter.Seq2[*models.Node, error] + // NodeUpdatesInHorizon returns all the known lightning node which have + // an update timestamp within the passed range. This method can be used + // by two nodes to quickly determine if they have the same set of up to + // date node announcements. + NodeUpdatesInHorizon(startTime, endTime time.Time, + opts ...IteratorOption) iter.Seq2[models.Node, error] // FetchNode attempts to look up a target node by its identity // public key. If the node isn't found in the database, then // ErrGraphNodeNotFound is returned. - FetchNode(ctx context.Context, v lnwire.GossipVersion, - nodePub route.Vertex) (*models.Node, error) - - // HasV1Node determines if the graph has a vertex identified by - // the target node identity public key in the V1 graph. If the node - // exists in the database, a timestamp of when the data for the node - // was lasted updated is returned along with a true boolean. Otherwise, - // an empty time.Time is returned with a false boolean. - // This is specific to the V1 graph since only V1 node announcements - // use timestamps for their latest update timestamp. - HasV1Node(ctx context.Context, nodePub [33]byte) (time.Time, bool, + FetchNode(ctx context.Context, nodePub route.Vertex) (*models.Node, error) // HasNode determines if the graph has a vertex identified by - // the target node identity public key. - HasNode(ctx context.Context, v lnwire.GossipVersion, - nodePub [33]byte) (bool, error) + // the target node identity public key. If the node exists in the + // database, a timestamp of when the data for the node was lasted + // updated is returned along with a true boolean. Otherwise, an empty + // time.Time is returned with a false boolean. + HasNode(ctx context.Context, nodePub [33]byte) (time.Time, bool, error) // IsPublicNode is a helper method that determines whether the node with // the given public key is seen as a public node in the graph from the // graph's source node's point of view. - IsPublicNode(ctx context.Context, v lnwire.GossipVersion, - pubKey [33]byte) (bool, error) + IsPublicNode(pubKey [33]byte) (bool, error) // GraphSession will provide the call-back with access to a // NodeTraverser instance which can be used to perform queries against // the channel graph. - GraphSession(ctx context.Context, - cb func(graph NodeTraverser) error, reset func()) error + GraphSession(cb func(graph NodeTraverser) error, reset func()) error // ForEachChannel iterates through all the channel edges stored within // the graph and invokes the passed callback for each edge. The callback @@ -165,12 +145,9 @@ type Store interface { //nolint:interfacebloat // NOTE: If an edge can't be found, or wasn't advertised, then a nil // pointer for that particular channel edge routing policy will be // passed into the callback. - // - // TODO(elle): add a cross-version iteration API and make this iterate - // over all versions. - ForEachChannel(ctx context.Context, v lnwire.GossipVersion, - cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, - *models.ChannelEdgePolicy) error, reset func()) error + ForEachChannel(ctx context.Context, cb func(*models.ChannelEdgeInfo, + *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error, + reset func()) error // ForEachChannelCacheable iterates through all the channel edges stored // within the graph and invokes the passed callback for each edge. The @@ -184,15 +161,14 @@ type Store interface { //nolint:interfacebloat // // NOTE: this method is like ForEachChannel but fetches only the data // required for the graph cache. - ForEachChannelCacheable(ctx context.Context, v lnwire.GossipVersion, - cb func(*models.CachedEdgeInfo, *models.CachedEdgePolicy, - *models.CachedEdgePolicy) error, reset func()) error + ForEachChannelCacheable(cb func(*models.CachedEdgeInfo, + *models.CachedEdgePolicy, *models.CachedEdgePolicy) error, + reset func()) error // DisabledChannelIDs returns the channel ids of disabled channels. // A channel is disabled when two of the associated ChanelEdgePolicies // have their disabled bit on. - DisabledChannelIDs(ctx context.Context, - v lnwire.GossipVersion) ([]uint64, error) + DisabledChannelIDs() ([]uint64, error) // AddChannelEdge adds a new (undirected, blank) edge to the graph // database. An undirected edge from the two target nodes are created. @@ -204,21 +180,14 @@ type Store interface { //nolint:interfacebloat AddChannelEdge(ctx context.Context, edge *models.ChannelEdgeInfo, op ...batch.SchedulerOption) error - // HasV1ChannelEdge returns true if the database knows of a channel edge + // HasChannelEdge returns true if the database knows of a channel edge // with the passed channel ID, and false otherwise. If an edge with that // ID is found within the graph, then two time stamps representing the // last time the edge was updated for both directed edges are returned // along with the boolean. If it is not found, then the zombie index is // checked and its result is returned as the second boolean. - HasV1ChannelEdge(ctx context.Context, chanID uint64) ( - time.Time, time.Time, bool, bool, error) - - // HasChannelEdge returns true if the database knows of a channel edge - // with the passed channel ID and gossip version, and false otherwise. - // If it is not found, then the zombie index is checked and its result - // is returned as the second boolean. - HasChannelEdge(ctx context.Context, v lnwire.GossipVersion, - chanID uint64) (bool, bool, error) + HasChannelEdge(chanID uint64) (time.Time, time.Time, bool, bool, + error) // DeleteChannelEdges removes edges with the given channel IDs from the // database and marks them as zombies. This ensures that we're unable to @@ -229,58 +198,51 @@ type Store interface { //nolint:interfacebloat // failed to send the fresh update to be the one that resurrects the // channel from its zombie state. The markZombie bool denotes whether // to mark the channel as a zombie. - DeleteChannelEdges(ctx context.Context, v lnwire.GossipVersion, - strictZombiePruning, markZombie bool, chanIDs ...uint64) ( - []*models.ChannelEdgeInfo, error) + DeleteChannelEdges(strictZombiePruning, markZombie bool, + chanIDs ...uint64) ([]*models.ChannelEdgeInfo, error) // AddEdgeProof sets the proof of an existing edge in the graph // database. - AddEdgeProof(ctx context.Context, chanID lnwire.ShortChannelID, + AddEdgeProof(chanID lnwire.ShortChannelID, proof *models.ChannelAuthProof) error // ChannelID attempt to lookup the 8-byte compact channel ID which maps // to the passed channel point (outpoint). If the passed channel doesn't // exist within the database, then ErrEdgeNotFound is returned. - ChannelID(ctx context.Context, v lnwire.GossipVersion, - chanPoint *wire.OutPoint) (uint64, error) + ChannelID(chanPoint *wire.OutPoint) (uint64, error) // HighestChanID returns the "highest" known channel ID in the channel // graph. This represents the "newest" channel from the PoV of the // chain. This method can be used by peers to quickly determine if // they're graphs are in sync. - HighestChanID(ctx context.Context, v lnwire.GossipVersion) ( - uint64, error) + HighestChanID(ctx context.Context) (uint64, error) // ChanUpdatesInHorizon returns all the known channel edges which have - // at least one edge update within the specified range for the given - // gossip version. For v1 gossip, the range is time-based with - // [start, end) per BOLT 07. - ChanUpdatesInHorizon(ctx context.Context, v lnwire.GossipVersion, - r ChanUpdateRange, + // at least one edge that has an update timestamp within the specified + // horizon. + ChanUpdatesInHorizon(startTime, endTime time.Time, opts ...IteratorOption) iter.Seq2[ChannelEdge, error] - // FilterKnownChanIDs takes a set of channel IDs for a given gossip - // version and returns the subset of chan ID's that we don't know and - // are not known zombies of the passed set. In other words, we perform - // a set difference of our set of chan ID's and the ones passed in. - // This method can be used by callers to determine the set of channels - // another peer knows of that we don't. The ChannelUpdateInfos for the - // known zombies is also returned. - FilterKnownChanIDs(ctx context.Context, v lnwire.GossipVersion, - chansInfo []ChannelUpdateInfo) ([]uint64, []ChannelUpdateInfo, - error) + // FilterKnownChanIDs takes a set of channel IDs and return the subset + // of chan ID's that we don't know and are not known zombies of the + // passed set. In other words, we perform a set difference of our set + // of chan ID's and the ones passed in. This method can be used by + // callers to determine the set of channels another peer knows of that + // we don't. The ChannelUpdateInfos for the known zombies is also + // returned. + FilterKnownChanIDs(chansInfo []ChannelUpdateInfo) ([]uint64, + []ChannelUpdateInfo, error) // FilterChannelRange returns the channel ID's of all known channels - // which were mined in a block height within the passed range for the - // given gossip version. The channel IDs are grouped by their common - // block height. This method can be used to quickly share with a peer - // the set of channels we know of within a particular range to catch - // them up after a period of time offline. If withTimestamps is true - // then the timestamp info of the latest received channel update - // messages of the channel will be included in the response. - FilterChannelRange(ctx context.Context, v lnwire.GossipVersion, - startHeight, endHeight uint32, - withTimestamps bool) ([]BlockChannelRange, error) + // which were mined in a block height within the passed range. The + // channel IDs are grouped by their common block height. This method can + // be used to quickly share with a peer the set of channels we know of + // within a particular range to catch them up after a period of time + // offline. If withTimestamps is true then the timestamp info of the + // latest received channel update messages of the channel will be + // included in the response. + FilterChannelRange(startHeight, endHeight uint32, withTimestamps bool) ( + []BlockChannelRange, error) // FetchChanInfos returns the set of channel edges that correspond to // the passed channel ID's. If an edge is the query is unknown to the @@ -288,8 +250,7 @@ type Store interface { //nolint:interfacebloat // edges that exist at the time of the query. This can be used to // respond to peer queries that are seeking to fill in gaps in their // view of the channel graph. - FetchChanInfos(ctx context.Context, v lnwire.GossipVersion, - chanIDs []uint64) ([]ChannelEdge, error) + FetchChanInfos(chanIDs []uint64) ([]ChannelEdge, error) // FetchChannelEdgesByOutpoint attempts to lookup the two directed edges // for the channel identified by the funding outpoint. If the channel @@ -297,8 +258,7 @@ type Store interface { //nolint:interfacebloat // houses the general information for the channel itself is returned as // well as two structs that contain the routing policies for the channel // in either direction. - FetchChannelEdgesByOutpoint(ctx context.Context, - v lnwire.GossipVersion, op *wire.OutPoint) ( + FetchChannelEdgesByOutpoint(op *wire.OutPoint) ( *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy, error) @@ -313,51 +273,44 @@ type Store interface { //nolint:interfacebloat // zombie within the database. In this case, the ChannelEdgePolicy's // will be nil, and the ChannelEdgeInfo will only include the public // keys of each node. - FetchChannelEdgesByID(ctx context.Context, v lnwire.GossipVersion, - chanID uint64) ( + FetchChannelEdgesByID(chanID uint64) ( *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy, error) // ChannelView returns the verifiable edge information for each active - // channel within the known channel graph for the given gossip version. - // The set of UTXO's (along with their scripts) returned are the ones - // that need to be watched on chain to detect channel closes on the - // resident blockchain. - ChannelView(ctx context.Context, v lnwire.GossipVersion) ([]EdgePoint, - error) + // channel within the known channel graph. The set of UTXO's (along with + // their scripts) returned are the ones that need to be watched on chain + // to detect channel closes on the resident blockchain. + ChannelView() ([]EdgePoint, error) // MarkEdgeZombie attempts to mark a channel identified by its channel - // ID as a zombie for the given gossip version. This method is used on - // an ad-hoc basis, when channels need to be marked as zombies outside - // the normal pruning cycle. - MarkEdgeZombie(ctx context.Context, v lnwire.GossipVersion, - chanID uint64, pubKey1, pubKey2 [33]byte) error + // ID as a zombie. This method is used on an ad-hoc basis, when channels + // need to be marked as zombies outside the normal pruning cycle. + MarkEdgeZombie(chanID uint64, + pubKey1, pubKey2 [33]byte) error - // MarkEdgeLive clears an edge from our zombie index for the given - // gossip version, deeming it as live. - MarkEdgeLive(ctx context.Context, v lnwire.GossipVersion, - chanID uint64) error + // MarkEdgeLive clears an edge from our zombie index, deeming it as + // live. + MarkEdgeLive(chanID uint64) error // IsZombieEdge returns whether the edge is considered zombie. If it is // a zombie, then the two node public keys corresponding to this edge // are also returned. - IsZombieEdge(ctx context.Context, v lnwire.GossipVersion, - chanID uint64) (bool, [33]byte, [33]byte, error) + IsZombieEdge(chanID uint64) (bool, [33]byte, [33]byte, error) // NumZombies returns the current number of zombie channels in the // graph. - NumZombies(ctx context.Context, v lnwire.GossipVersion) (uint64, error) + NumZombies() (uint64, error) // PutClosedScid stores a SCID for a closed channel in the database. // This is so that we can ignore channel announcements that we know to // be closed without having to validate them and fetch a block. - PutClosedScid(ctx context.Context, scid lnwire.ShortChannelID) error + PutClosedScid(scid lnwire.ShortChannelID) error // IsClosedScid checks whether a channel identified by the passed in // scid is closed. This helps avoid having to perform expensive // validation checks. - IsClosedScid(ctx context.Context, - scid lnwire.ShortChannelID) (bool, error) + IsClosedScid(scid lnwire.ShortChannelID) (bool, error) // UpdateEdgePolicy updates the edge routing policy for a single // directed edge within the database for the referenced channel. The @@ -374,8 +327,7 @@ type Store interface { //nolint:interfacebloat // treated as the center node within a star-graph. This method may be // used to kick off a path finding algorithm in order to explore the // reachability of another node based off the source node. - SourceNode(ctx context.Context, v lnwire.GossipVersion) (*models.Node, - error) + SourceNode(ctx context.Context) (*models.Node, error) // SetSourceNode sets the source node within the graph database. The // source node is to be used as the center of a star-graph within path @@ -387,14 +339,14 @@ type Store interface { //nolint:interfacebloat // has been used to prune channels in the graph. Knowing the "prune tip" // allows callers to tell if the graph is currently in sync with the // current best known UTXO state. - PruneTip(ctx context.Context) (*chainhash.Hash, uint32, error) + PruneTip() (*chainhash.Hash, uint32, error) // PruneGraphNodes is a garbage collection method which attempts to // prune out any nodes from the channel graph that are currently // unconnected. This ensures that we only maintain a graph of reachable // nodes. In the event that a pruned node gains more channels, it will // be re-added back to the graph. - PruneGraphNodes(ctx context.Context) ([]route.Vertex, error) + PruneGraphNodes() ([]route.Vertex, error) // PruneGraph prunes newly closed channels from the channel graph in // response to a new block being solved on the network. Any transactions @@ -405,7 +357,7 @@ type Store interface { //nolint:interfacebloat // slice of channels that have been closed by the target block along // with any pruned nodes are returned if the function succeeds without // error. - PruneGraph(ctx context.Context, spentOutputs []*wire.OutPoint, + PruneGraph(spentOutputs []*wire.OutPoint, blockHash *chainhash.Hash, blockHeight uint32) ( []*models.ChannelEdgeInfo, []route.Vertex, error) @@ -416,6 +368,6 @@ type Store interface { //nolint:interfacebloat // set to the last prune height valid for the remaining chain. // Channels that were removed from the graph resulting from the // disconnected block are returned. - DisconnectBlockAtHeight(ctx context.Context, - height uint32) ([]*models.ChannelEdgeInfo, error) + DisconnectBlockAtHeight(height uint32) ([]*models.ChannelEdgeInfo, + error) } diff --git a/graph/db/kv_store.go b/graph/db/kv_store.go index 7ca125d23..7a572dbd9 100644 --- a/graph/db/kv_store.go +++ b/graph/db/kv_store.go @@ -3,10 +3,10 @@ package graphdb import ( "bytes" "context" + "crypto/sha256" "encoding/binary" "errors" "fmt" - "image/color" "io" "iter" "math" @@ -16,13 +16,15 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/walletdb" "github.com/lightningnetwork/lnd/aliasmgr" "github.com/lightningnetwork/lnd/batch" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" + "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" @@ -161,11 +163,6 @@ var ( // // maps: scid -> []byte{} closedScidBucket = []byte("closed-scid") - - // ErrVersionNotSupportedForKVDB is returned with KVStore queries are - // made using a gossip version other than V1. - ErrVersionNotSupportedForKVDB = errors.New("only gossip v1 is " + - "supported for kvdb graph store") ) const ( @@ -201,8 +198,8 @@ type KVStore struct { } // A compile-time assertion to ensure that the KVStore struct implements the -// Store interface. -var _ Store = (*KVStore)(nil) +// V1Store interface. +var _ V1Store = (*KVStore)(nil) // NewKVStore allocates a new KVStore backed by a DB instance. The // returned instance has its own unique reject cache and channel cache. @@ -250,7 +247,7 @@ func (c channelMapKey) String() string { // getChannelMap loads all channel edge policies from the database and stores // them in a map. -func getChannelMap(ctx context.Context, edges kvdb.RBucket) ( +func getChannelMap(edges kvdb.RBucket) ( map[channelMapKey]*models.ChannelEdgePolicy, error) { // Create a map to store all channel edge policies. @@ -378,7 +375,7 @@ func initKVStore(db kvdb.Backend) error { // unknown to the graph DB or not. // // NOTE: this is part of the channeldb.AddrSource interface. -func (c *KVStore) AddrsForNode(ctx context.Context, v lnwire.GossipVersion, +func (c *KVStore) AddrsForNode(ctx context.Context, nodePub *btcec.PublicKey) (bool, []net.Addr, error) { pubKey, err := route.NewVertexFromBytes(nodePub.SerializeCompressed()) @@ -386,7 +383,7 @@ func (c *KVStore) AddrsForNode(ctx context.Context, v lnwire.GossipVersion, return false, nil, err } - node, err := c.FetchNode(ctx, v, pubKey) + node, err := c.FetchNode(ctx, pubKey) // We don't consider it an error if the graph is unaware of the node. switch { case err != nil && !errors.Is(err, ErrGraphNodeNotFound): @@ -408,14 +405,10 @@ func (c *KVStore) AddrsForNode(ctx context.Context, v lnwire.GossipVersion, // NOTE: If an edge can't be found, or wasn't advertised, then a nil pointer // for that particular channel edge routing policy will be passed into the // callback. -func (c *KVStore) ForEachChannel(_ context.Context, v lnwire.GossipVersion, +func (c *KVStore) ForEachChannel(_ context.Context, cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error, reset func()) error { - if v != lnwire.GossipVersion1 { - return ErrVersionNotSupportedForKVDB - } - return forEachChannel(c.db, cb, reset) } @@ -440,9 +433,7 @@ func forEachChannel(db kvdb.Backend, cb func(*models.ChannelEdgeInfo, // First, load all edges in memory indexed by node and channel // id. - channelMap, err := getChannelMap( - context.Background(), edges, - ) + channelMap, err := getChannelMap(edges) if err != nil { return err } @@ -477,7 +468,7 @@ func forEachChannel(db kvdb.Backend, cb func(*models.ChannelEdgeInfo, chanID: chanID, }] - return cb(info, policy1, policy2) + return cb(&info, policy1, policy2) }, ) }, reset) @@ -495,15 +486,10 @@ func forEachChannel(db kvdb.Backend, cb func(*models.ChannelEdgeInfo, // // NOTE: this method is like ForEachChannel but fetches only the data required // for the graph cache. -func (c *KVStore) ForEachChannelCacheable(ctx context.Context, - v lnwire.GossipVersion, cb func(*models.CachedEdgeInfo, - *models.CachedEdgePolicy, *models.CachedEdgePolicy) error, +func (c *KVStore) ForEachChannelCacheable(cb func(*models.CachedEdgeInfo, + *models.CachedEdgePolicy, *models.CachedEdgePolicy) error, reset func()) error { - if v != lnwire.GossipVersion1 { - return ErrVersionNotSupportedForKVDB - } - return c.db.View(func(tx kvdb.RTx) error { edges := tx.ReadBucket(edgeBucket) if edges == nil { @@ -512,7 +498,7 @@ func (c *KVStore) ForEachChannelCacheable(ctx context.Context, // First, load all edges in memory indexed by node and channel // id. - channelMap, err := getChannelMap(ctx, edges) + channelMap, err := getChannelMap(edges) if err != nil { return err } @@ -526,10 +512,6 @@ func (c *KVStore) ForEachChannelCacheable(ctx context.Context, // loaded above and invoke the callback. return kvdb.ForAll( edgeIndex, func(k, edgeInfoBytes []byte) error { - if err := ctx.Err(); err != nil { - return err - } - var chanID [8]byte copy(chanID[:], k) @@ -574,7 +556,7 @@ func (c *KVStore) ForEachChannelCacheable(ctx context.Context, } return cb( - models.NewCachedEdge(info), + models.NewCachedEdge(&info), cachedPolicy1, cachedPolicy2, ) }, @@ -673,14 +655,9 @@ func (c *KVStore) fetchNodeFeatures(tx kvdb.RTx, // Unknown policies are passed into the callback as nil values. // // NOTE: this is part of the graphdb.NodeTraverser interface. -func (c *KVStore) ForEachNodeDirectedChannel(_ context.Context, - v lnwire.GossipVersion, nodePub route.Vertex, +func (c *KVStore) ForEachNodeDirectedChannel(nodePub route.Vertex, cb func(channel *DirectedChannel) error, reset func()) error { - if v != lnwire.GossipVersion1 { - return ErrVersionNotSupportedForKVDB - } - return c.forEachNodeDirectedChannel(nil, nodePub, cb, reset) } @@ -688,12 +665,8 @@ func (c *KVStore) ForEachNodeDirectedChannel(_ context.Context, // known for the node, an empty feature vector is returned. // // NOTE: this is part of the graphdb.NodeTraverser interface. -func (c *KVStore) FetchNodeFeatures(_ context.Context, v lnwire.GossipVersion, - nodePub route.Vertex) (*lnwire.FeatureVector, error) { - - if v != lnwire.GossipVersion1 { - return nil, ErrVersionNotSupportedForKVDB - } +func (c *KVStore) FetchNodeFeatures(nodePub route.Vertex) ( + *lnwire.FeatureVector, error) { return c.fetchNodeFeatures(nil, nodePub) } @@ -702,15 +675,10 @@ func (c *KVStore) FetchNodeFeatures(_ context.Context, v lnwire.GossipVersion, // data to the call-back. // // NOTE: The callback contents MUST not be modified. -func (c *KVStore) ForEachNodeCached(ctx context.Context, - v lnwire.GossipVersion, - cb func(ctx context.Context, node route.Vertex, +func (c *KVStore) ForEachNodeCached(ctx context.Context, withAddrs bool, + cb func(ctx context.Context, node route.Vertex, addrs []net.Addr, chans map[uint64]*DirectedChannel) error, reset func()) error { - if v != lnwire.GossipVersion1 { - return ErrVersionNotSupportedForKVDB - } - // Otherwise call back to a version that uses the database directly. // We'll iterate over each node, then the set of channels for each // node, and construct a similar callback functiopn signature as the @@ -769,20 +737,19 @@ func (c *KVStore) ForEachNodeCached(ctx context.Context, return err } - return cb(ctx, node.PubKeyBytes, channels) + var addrs []net.Addr + if withAddrs { + addrs = node.Addresses + } + + return cb(ctx, node.PubKeyBytes, addrs, channels) }, reset) } // DisabledChannelIDs returns the channel ids of disabled channels. // A channel is disabled when two of the associated ChanelEdgePolicies // have their disabled bit on. -func (c *KVStore) DisabledChannelIDs( - _ context.Context, v lnwire.GossipVersion) ([]uint64, error) { - - if v != lnwire.GossipVersion1 { - return nil, ErrVersionNotSupportedForKVDB - } - +func (c *KVStore) DisabledChannelIDs() ([]uint64, error) { var disabledChanIDs []uint64 var chanEdgeFound map[uint64]struct{} @@ -836,14 +803,10 @@ func (c *KVStore) DisabledChannelIDs( // returns an error, then the transaction is aborted and the iteration stops // early. // -// NOTE: this is part of the Store interface. -func (c *KVStore) ForEachNode(_ context.Context, v lnwire.GossipVersion, +// NOTE: this is part of the V1Store interface. +func (c *KVStore) ForEachNode(_ context.Context, cb func(*models.Node) error, reset func()) error { - if v != lnwire.GossipVersion1 { - return ErrVersionNotSupportedForKVDB - } - return forEachNode(c.db, func(tx kvdb.RTx, node *models.Node) error { @@ -885,7 +848,7 @@ func forEachNode(db kvdb.Backend, // Execute the callback, the transaction will abort if // this returns an error. - return cb(tx, node) + return cb(tx, &node) }) } @@ -896,13 +859,9 @@ func forEachNode(db kvdb.Backend, // graph, executing the passed callback with each node encountered. If the // callback returns an error, then the transaction is aborted and the iteration // stops early. -func (c *KVStore) ForEachNodeCacheable(ctx context.Context, - v lnwire.GossipVersion, cb func(route.Vertex, - *lnwire.FeatureVector) error, reset func()) error { - - if v != lnwire.GossipVersion1 { - return ErrVersionNotSupportedForKVDB - } +func (c *KVStore) ForEachNodeCacheable(_ context.Context, + cb func(route.Vertex, *lnwire.FeatureVector) error, + reset func()) error { traversal := func(tx kvdb.RTx) error { // First grab the nodes bucket which stores the mapping from @@ -913,10 +872,6 @@ func (c *KVStore) ForEachNodeCacheable(ctx context.Context, } return nodes.ForEach(func(pubKey, nodeBytes []byte) error { - if err := ctx.Err(); err != nil { - return err - } - // If this is the source key, then we skip this // iteration as the value for this key is a pubKey // rather than raw node information. @@ -945,13 +900,7 @@ func (c *KVStore) ForEachNodeCacheable(ctx context.Context, // as the center node within a star-graph. This method may be used to kick off // a path finding algorithm in order to explore the reachability of another // node based off the source node. -func (c *KVStore) SourceNode(_ context.Context, - v lnwire.GossipVersion) (*models.Node, error) { - - if v != lnwire.GossipVersion1 { - return nil, ErrVersionNotSupportedForKVDB - } - +func (c *KVStore) SourceNode(_ context.Context) (*models.Node, error) { return sourceNode(c.db) } @@ -996,7 +945,12 @@ func sourceNodeWithTx(nodes kvdb.RBucket) (*models.Node, error) { // With the pubKey of the source node retrieved, we're able to // fetch the full node information. - return fetchLightningNode(nodes, selfPub) + node, err := fetchLightningNode(nodes, selfPub) + if err != nil { + return nil, err + } + + return &node, nil } // SetSourceNode sets the source node within the graph database. The source @@ -1005,10 +959,6 @@ func sourceNodeWithTx(nodes kvdb.RBucket) (*models.Node, error) { func (c *KVStore) SetSourceNode(_ context.Context, node *models.Node) error { - if node.Version != lnwire.GossipVersion1 { - return ErrVersionNotSupportedForKVDB - } - nodePubBytes := node.PubKeyBytes[:] return kvdb.Update(c.db, func(tx kvdb.RwTx) error { @@ -1075,13 +1025,9 @@ func addLightningNode(tx kvdb.RwTx, node *models.Node) error { // LookupAlias attempts to return the alias as advertised by the target node. // TODO(roasbeef): currently assumes that aliases are unique... -func (c *KVStore) LookupAlias(_ context.Context, v lnwire.GossipVersion, +func (c *KVStore) LookupAlias(_ context.Context, pub *btcec.PublicKey) (string, error) { - if v != lnwire.GossipVersion1 { - return "", ErrVersionNotSupportedForKVDB - } - var alias string err := kvdb.View(c.db, func(tx kvdb.RTx) error { @@ -1118,13 +1064,9 @@ func (c *KVStore) LookupAlias(_ context.Context, v lnwire.GossipVersion, // DeleteNode starts a new database transaction to remove a vertex/node // from the database according to the node's public key. -func (c *KVStore) DeleteNode(_ context.Context, v lnwire.GossipVersion, +func (c *KVStore) DeleteNode(_ context.Context, nodePub route.Vertex) error { - if v != lnwire.GossipVersion1 { - return ErrVersionNotSupportedForKVDB - } - // TODO(roasbeef): ensure dangling edges are removed... return kvdb.Update(c.db, func(tx kvdb.RwTx) error { nodes := tx.ReadWriteBucket(nodeBucket) @@ -1214,13 +1156,8 @@ func (c *KVStore) AddChannelEdge(ctx context.Context, case alreadyExists: return ErrEdgeAlreadyExist default: - c.rejectCache.remove( - lnwire.GossipVersion1, edge.ChannelID, - ) - c.chanCache.remove( - lnwire.GossipVersion1, edge.ChannelID, - ) - + c.rejectCache.remove(edge.ChannelID) + c.chanCache.remove(edge.ChannelID) return nil } }, @@ -1268,9 +1205,11 @@ func (c *KVStore) addChannelEdge(tx kvdb.RwTx, _, node1Err := fetchLightningNode(nodes, edge.NodeKey1Bytes[:]) switch { case errors.Is(node1Err, ErrGraphNodeNotFound): - err := addLightningNode( - tx, models.NewV1ShellNode(edge.NodeKey1Bytes), - ) + node1Shell := models.Node{ + PubKeyBytes: edge.NodeKey1Bytes, + HaveNodeAnnouncement: false, + } + err := addLightningNode(tx, &node1Shell) if err != nil { return fmt.Errorf("unable to create shell node "+ "for: %x: %w", edge.NodeKey1Bytes, err) @@ -1282,9 +1221,11 @@ func (c *KVStore) addChannelEdge(tx kvdb.RwTx, _, node2Err := fetchLightningNode(nodes, edge.NodeKey2Bytes[:]) switch { case errors.Is(node2Err, ErrGraphNodeNotFound): - err := addLightningNode( - tx, models.NewV1ShellNode(edge.NodeKey2Bytes), - ) + node2Shell := models.Node{ + PubKeyBytes: edge.NodeKey2Bytes, + HaveNodeAnnouncement: false, + } + err := addLightningNode(tx, &node2Shell) if err != nil { return fmt.Errorf("unable to create shell node "+ "for: %x: %w", edge.NodeKey2Bytes, err) @@ -1302,9 +1243,9 @@ func (c *KVStore) addChannelEdge(tx kvdb.RwTx, // Mark edge policies for both sides as unknown. This is to enable // efficient incoming channel lookup for a node. - keys := []route.Vertex{ - edge.NodeKey1Bytes, - edge.NodeKey2Bytes, + keys := []*[33]byte{ + &edge.NodeKey1Bytes, + &edge.NodeKey2Bytes, } for _, key := range keys { err := putChanEdgePolicyUnknown(edges, edge.ChannelID, key[:]) @@ -1323,13 +1264,13 @@ func (c *KVStore) addChannelEdge(tx kvdb.RwTx, return chanIndex.Put(b.Bytes(), chanKey[:]) } -// HasV1ChannelEdge returns true if the database knows of a channel edge -// with the passed channel ID, and false otherwise. If an edge with that ID -// is found within the graph, then two time stamps representing the last time -// the edge was updated for both directed edges are returned along with the -// boolean. If it is not found, then the zombie index is checked and its -// result is returned as the second boolean. -func (c *KVStore) HasV1ChannelEdge(_ context.Context, +// HasChannelEdge returns true if the database knows of a channel edge with the +// passed channel ID, and false otherwise. If an edge with that ID is found +// within the graph, then two time stamps representing the last time the edge +// was updated for both directed edges are returned along with the boolean. If +// it is not found, then the zombie index is checked and its result is returned +// as the second boolean. +func (c *KVStore) HasChannelEdge( chanID uint64) (time.Time, time.Time, bool, bool, error) { var ( @@ -1342,7 +1283,7 @@ func (c *KVStore) HasV1ChannelEdge(_ context.Context, // We'll query the cache with the shared lock held to allow multiple // readers to access values in the cache concurrently if they exist. c.cacheMu.RLock() - if entry, ok := c.rejectCache.get(lnwire.GossipVersion1, chanID); ok { + if entry, ok := c.rejectCache.get(chanID); ok { c.cacheMu.RUnlock() upd1Time = time.Unix(entry.upd1Time, 0) upd2Time = time.Unix(entry.upd2Time, 0) @@ -1358,7 +1299,7 @@ func (c *KVStore) HasV1ChannelEdge(_ context.Context, // The item was not found with the shared lock, so we'll acquire the // exclusive lock and check the cache again in case another method added // the entry to the cache while no lock was held. - if entry, ok := c.rejectCache.get(lnwire.GossipVersion1, chanID); ok { + if entry, ok := c.rejectCache.get(chanID); ok { upd1Time = time.Unix(entry.upd1Time, 0) upd2Time = time.Unix(entry.upd2Time, 0) exists, isZombie = entry.flags.unpack() @@ -1425,7 +1366,7 @@ func (c *KVStore) HasV1ChannelEdge(_ context.Context, return time.Time{}, time.Time{}, exists, isZombie, err } - c.rejectCache.insert(lnwire.GossipVersion1, chanID, rejectCacheEntry{ + c.rejectCache.insert(chanID, rejectCacheEntry{ upd1Time: upd1Time.Unix(), upd2Time: upd2Time.Unix(), flags: packRejectFlags(exists, isZombie), @@ -1434,32 +1375,10 @@ func (c *KVStore) HasV1ChannelEdge(_ context.Context, return upd1Time, upd2Time, exists, isZombie, nil } -// HasChannelEdge returns true if the database knows of a channel edge with the -// passed channel ID and gossip version, and false otherwise. If it is not -// found, then the zombie index is checked and its result is returned as the -// second boolean. -func (c *KVStore) HasChannelEdge(ctx context.Context, v lnwire.GossipVersion, - chanID uint64) (bool, bool, error) { - - if v != lnwire.GossipVersion1 { - return false, false, ErrVersionNotSupportedForKVDB - } - - _, _, exists, isZombie, err := c.HasV1ChannelEdge(ctx, chanID) - - return exists, isZombie, err -} - // AddEdgeProof sets the proof of an existing edge in the graph database. -func (c *KVStore) AddEdgeProof(_ context.Context, chanID lnwire.ShortChannelID, +func (c *KVStore) AddEdgeProof(chanID lnwire.ShortChannelID, proof *models.ChannelAuthProof) error { - // We only support v1 channel proofs in the KVStore. - if proof.Version != lnwire.GossipVersion1 { - return fmt.Errorf("only v1 channel proofs supported, got v%d", - proof.Version) - } - // Construct the channel's primary key which is the 8-byte channel ID. var chanKey [8]byte binary.BigEndian.PutUint64(chanKey[:], chanID.ToUint64()) @@ -1482,7 +1401,7 @@ func (c *KVStore) AddEdgeProof(_ context.Context, chanID lnwire.ShortChannelID, edge.AuthProof = proof - return putChanEdgeInfo(edgeIndex, edge, chanKey) + return putChanEdgeInfo(edgeIndex, &edge, chanKey) }, func() {}) } @@ -1503,7 +1422,7 @@ const ( // with the current UTXO state. A slice of channels that have been closed by // the target block along with any pruned nodes are returned if the function // succeeds without error. -func (c *KVStore) PruneGraph(_ context.Context, spentOutputs []*wire.OutPoint, +func (c *KVStore) PruneGraph(spentOutputs []*wire.OutPoint, blockHash *chainhash.Hash, blockHeight uint32) ( []*models.ChannelEdgeInfo, []route.Vertex, error) { @@ -1620,8 +1539,8 @@ func (c *KVStore) PruneGraph(_ context.Context, spentOutputs []*wire.OutPoint, } for _, channel := range chansClosed { - c.rejectCache.remove(lnwire.GossipVersion1, channel.ChannelID) - c.chanCache.remove(lnwire.GossipVersion1, channel.ChannelID) + c.rejectCache.remove(channel.ChannelID) + c.chanCache.remove(channel.ChannelID) } return chansClosed, prunedNodes, nil @@ -1631,7 +1550,7 @@ func (c *KVStore) PruneGraph(_ context.Context, spentOutputs []*wire.OutPoint, // any nodes from the channel graph that are currently unconnected. This ensure // that we only maintain a graph of reachable nodes. In the event that a pruned // node gains more channels, it will be re-added back to the graph. -func (c *KVStore) PruneGraphNodes(_ context.Context) ([]route.Vertex, error) { +func (c *KVStore) PruneGraphNodes() ([]route.Vertex, error) { var prunedNodes []route.Vertex err := kvdb.Update(c.db, func(tx kvdb.RwTx) error { nodes := tx.ReadWriteBucket(nodeBucket) @@ -1771,8 +1690,8 @@ func (c *KVStore) pruneGraphNodes(nodes kvdb.RwBucket, // set to the last prune height valid for the remaining chain. // Channels that were removed from the graph resulting from the // disconnected block are returned. -func (c *KVStore) DisconnectBlockAtHeight(_ context.Context, - height uint32) ([]*models.ChannelEdgeInfo, error) { +func (c *KVStore) DisconnectBlockAtHeight(height uint32) ( + []*models.ChannelEdgeInfo, error) { // Every channel having a ShortChannelID starting at 'height' // will no longer be confirmed. @@ -1887,8 +1806,8 @@ func (c *KVStore) DisconnectBlockAtHeight(_ context.Context, } for _, channel := range removedChans { - c.rejectCache.remove(lnwire.GossipVersion1, channel.ChannelID) - c.chanCache.remove(lnwire.GossipVersion1, channel.ChannelID) + c.rejectCache.remove(channel.ChannelID) + c.chanCache.remove(channel.ChannelID) } return removedChans, nil @@ -1898,7 +1817,7 @@ func (c *KVStore) DisconnectBlockAtHeight(_ context.Context, // used to prune channels in the graph. Knowing the "prune tip" allows callers // to tell if the graph is currently in sync with the current best known UTXO // state. -func (c *KVStore) PruneTip(_ context.Context) (*chainhash.Hash, uint32, error) { +func (c *KVStore) PruneTip() (*chainhash.Hash, uint32, error) { var ( tipHash chainhash.Hash tipHeight uint32 @@ -1945,14 +1864,8 @@ func (c *KVStore) PruneTip(_ context.Context) (*chainhash.Hash, uint32, error) { // that we require the node that failed to send the fresh update to be the one // that resurrects the channel from its zombie state. The markZombie bool // denotes whether or not to mark the channel as a zombie. -func (c *KVStore) DeleteChannelEdges(_ context.Context, - v lnwire.GossipVersion, strictZombiePruning, markZombie bool, - chanIDs ...uint64) ( - []*models.ChannelEdgeInfo, error) { - - if v != lnwire.GossipVersion1 { - return nil, ErrVersionNotSupportedForKVDB - } +func (c *KVStore) DeleteChannelEdges(strictZombiePruning, markZombie bool, + chanIDs ...uint64) ([]*models.ChannelEdgeInfo, error) { // TODO(roasbeef): possibly delete from node bucket if node has no more // channels @@ -2007,8 +1920,8 @@ func (c *KVStore) DeleteChannelEdges(_ context.Context, } for _, chanID := range chanIDs { - c.rejectCache.remove(lnwire.GossipVersion1, chanID) - c.chanCache.remove(lnwire.GossipVersion1, chanID) + c.rejectCache.remove(chanID) + c.chanCache.remove(chanID) } return infos, nil @@ -2017,13 +1930,7 @@ func (c *KVStore) DeleteChannelEdges(_ context.Context, // ChannelID attempt to lookup the 8-byte compact channel ID which maps to the // passed channel point (outpoint). If the passed channel doesn't exist within // the database, then ErrEdgeNotFound is returned. -func (c *KVStore) ChannelID(_ context.Context, v lnwire.GossipVersion, - chanPoint *wire.OutPoint) (uint64, error) { - - if v != lnwire.GossipVersion1 { - return 0, ErrVersionNotSupportedForKVDB - } - +func (c *KVStore) ChannelID(chanPoint *wire.OutPoint) (uint64, error) { var chanID uint64 if err := kvdb.View(c.db, func(tx kvdb.RTx) error { var err error @@ -2069,13 +1976,7 @@ func getChanID(tx kvdb.RTx, chanPoint *wire.OutPoint) (uint64, error) { // HighestChanID returns the "highest" known channel ID in the channel graph. // This represents the "newest" channel from the PoV of the chain. This method // can be used by peers to quickly determine if they're graphs are in sync. -func (c *KVStore) HighestChanID(_ context.Context, - v lnwire.GossipVersion) (uint64, error) { - - if v != lnwire.GossipVersion1 { - return 0, ErrVersionNotSupportedForKVDB - } - +func (c *KVStore) HighestChanID(_ context.Context) (uint64, error) { var cid uint64 err := kvdb.View(c.db, func(tx kvdb.RTx) error { @@ -2150,7 +2051,7 @@ func (c *KVStore) updateChanCacheBatch(edgesToCache map[uint64]ChannelEdge) { defer c.cacheMu.Unlock() for cid, edge := range edgesToCache { - c.chanCache.insert(lnwire.GossipVersion1, cid, edge) + c.chanCache.insert(cid, edge) } } @@ -2281,9 +2182,8 @@ func (c *KVStore) fetchNextChanUpdateBatch( // Now we'll read items up to the batch size, exiting early if // we exceed the ending time. for len(batch) < state.batchSize && indexKey != nil { - // If we've reached or passed the end time, break - // out. Per BOLT 07, the end time is exclusive. - if bytes.Compare(indexKey, endTimeBytes[:]) >= 0 { + // If we're at the end, then we'll break out now. + if bytes.Compare(indexKey, endTimeBytes[:]) > 0 { break } @@ -2303,10 +2203,7 @@ func (c *KVStore) fetchNextChanUpdateBatch( } // Check cache (we already hold shared read lock). - channel, ok := c.chanCache.get( - lnwire.GossipVersion1, chanIDInt, - ) - if ok { + if channel, ok := c.chanCache.get(chanIDInt); ok { state.edgesSeen[chanIDInt] = struct{}{} batch = append(batch, channel) @@ -2350,12 +2247,12 @@ func (c *KVStore) fetchNextChanUpdateBatch( // Now we have all the information we need to build the // channel edge. - channel = ChannelEdge{ - Info: edgeInfo, + channel := ChannelEdge{ + Info: &edgeInfo, Policy1: edge1, Policy2: edge2, - Node1: node1, - Node2: node2, + Node1: &node1, + Node2: &node2, } state.edgesSeen[chanIDInt] = struct{}{} @@ -2369,11 +2266,10 @@ func (c *KVStore) fetchNextChanUpdateBatch( indexKey, _ = updateCursor.Next() } - // If we haven't yet reached the endTimeBytes, then we still - // have more entries to deliver. The end time is exclusive - // per BOLT 07. + // If we haven't yet crossed the endTimeBytes, then we still + // have more entries to deliver. if indexKey != nil && - bytes.Compare(indexKey, endTimeBytes[:]) < 0 { + bytes.Compare(indexKey, endTimeBytes[:]) <= 0 { hasMore = true } @@ -2391,10 +2287,8 @@ func (c *KVStore) fetchNextChanUpdateBatch( } // ChanUpdatesInHorizon returns all the known channel edges which have at least -// one edge update within the specified range for the given gossip version. For -// v1, the range is time-based with [start, end) per BOLT 07. -func (c *KVStore) ChanUpdatesInHorizon(_ context.Context, - v lnwire.GossipVersion, r ChanUpdateRange, +// one edge that has an update timestamp within the specified horizon. +func (c *KVStore) ChanUpdatesInHorizon(startTime, endTime time.Time, opts ...IteratorOption) iter.Seq2[ChannelEdge, error] { cfg := defaultIteratorConfig() @@ -2403,19 +2297,8 @@ func (c *KVStore) ChanUpdatesInHorizon(_ context.Context, } return func(yield func(ChannelEdge, error) bool) { - if v != lnwire.GossipVersion1 { - yield(ChannelEdge{}, ErrVersionNotSupportedForKVDB) - return - } - if err := r.validateForVersion(v); err != nil { - yield(ChannelEdge{}, err) - return - } - iterState := newChanUpdatesIterator( - cfg.chanUpdateIterBatchSize, - r.StartTime.UnwrapOr(time.Time{}), - r.EndTime.UnwrapOr(time.Time{}), + cfg.chanUpdateIterBatchSize, startTime, endTime, ) for { @@ -2464,8 +2347,8 @@ func (c *KVStore) ChanUpdatesInHorizon(_ context.Context, float64(iterState.total), iterState.hits, iterState.total) } else { - log.Tracef("ChanUpdatesInHorizon(v%d) returned "+ - "no edges in horizon", v) + log.Tracef("ChanUpdatesInHorizon returned no edges "+ + "in horizon (%s, %s)", startTime, endTime) } } } @@ -2515,10 +2398,10 @@ func newNodeUpdatesIterator(batchSize int, startTime, endTime time.Time, // fetchNextNodeBatch fetches the next batch of node announcements using the // iterator state. func (c *KVStore) fetchNextNodeBatch( - state *nodeUpdatesIterator) ([]*models.Node, bool, error) { + state *nodeUpdatesIterator) ([]models.Node, bool, error) { var ( - nodeBatch []*models.Node + nodeBatch []models.Node hasMore bool ) @@ -2577,10 +2460,9 @@ func (c *KVStore) fetchNextNodeBatch( // Extract the timestamp from the index key (first 8 // bytes). Only compare timestamps, not the full key // with pubkey. - // The end time is exclusive per BOLT 07. keyTimestamp := byteOrder.Uint64(indexKey[:8]) endTimestamp := uint64(state.endTime.Unix()) - if keyTimestamp >= endTimestamp { + if keyTimestamp > endTimestamp { break } @@ -2617,13 +2499,12 @@ func (c *KVStore) fetchNextNodeBatch( indexKey, _ = updateCursor.Next() } - // If we haven't yet reached the endTime, then we still - // have more entries to deliver. The end time is exclusive - // per BOLT 07. + // If we haven't yet crossed the endTime, then we still + // have more entries to deliver. if indexKey != nil { keyTimestamp := byteOrder.Uint64(indexKey[:8]) endTimestamp := uint64(state.endTime.Unix()) - if keyTimestamp < endTimestamp { + if keyTimestamp <= endTimestamp { hasMore = true } } @@ -2655,33 +2536,22 @@ func (c *KVStore) fetchNextNodeBatch( return nodeBatch, hasMore, nil } -// NodeUpdatesInHorizon returns all the known lightning nodes which have -// updates within the passed range for the given gossip version. For v1, the -// range is time-based with [start, end) per BOLT 07. -func (c *KVStore) NodeUpdatesInHorizon(_ context.Context, - v lnwire.GossipVersion, r NodeUpdateRange, - opts ...IteratorOption) iter.Seq2[*models.Node, error] { +// NodeUpdatesInHorizon returns all the known lightning node which have an +// update timestamp within the passed range. +func (c *KVStore) NodeUpdatesInHorizon(startTime, + endTime time.Time, + opts ...IteratorOption) iter.Seq2[models.Node, error] { cfg := defaultIteratorConfig() for _, opt := range opts { opt(cfg) } - return func(yield func(*models.Node, error) bool) { - if v != lnwire.GossipVersion1 { - yield(nil, ErrVersionNotSupportedForKVDB) - return - } - if err := r.validateForVersion(v); err != nil { - yield(nil, err) - return - } - + return func(yield func(models.Node, error) bool) { // Initialize iterator state. state := newNodeUpdatesIterator( cfg.nodeUpdateIterBatchSize, - r.StartTime.UnwrapOr(time.Time{}), - r.EndTime.UnwrapOr(time.Time{}), + startTime, endTime, cfg.iterPublicNodes, ) @@ -2691,7 +2561,7 @@ func (c *KVStore) NodeUpdatesInHorizon(_ context.Context, log.Errorf("unable to read node updates in "+ "horizon: %v", err) - yield(&models.Node{}, err) + yield(models.Node{}, err) return } @@ -2717,13 +2587,8 @@ func (c *KVStore) NodeUpdatesInHorizon(_ context.Context, // passed in. This method can be used by callers to determine the set of // channels another peer knows of that we don't. The ChannelUpdateInfos for the // known zombies is also returned. -func (c *KVStore) FilterKnownChanIDs(_ context.Context, - v lnwire.GossipVersion, - chansInfo []ChannelUpdateInfo) ([]uint64, []ChannelUpdateInfo, error) { - - if v != lnwire.GossipVersion1 { - return nil, nil, ErrVersionNotSupportedForKVDB - } +func (c *KVStore) FilterKnownChanIDs(chansInfo []ChannelUpdateInfo) ([]uint64, + []ChannelUpdateInfo, error) { var ( newChanIDs []uint64 @@ -2807,79 +2672,38 @@ type ChannelUpdateInfo struct { // ShortChannelID is the SCID identifier of the channel. ShortChannelID lnwire.ShortChannelID - // Version is the gossip version of the channel. - Version lnwire.GossipVersion + // Node1UpdateTimestamp is the timestamp of the latest received update + // from the node 1 channel peer. This will be set to zero time if no + // update has yet been received from this node. + Node1UpdateTimestamp time.Time - // Node1Freshness is the update-ordering value of the latest received - // update from the node 1 channel peer. For v1 channels this is a - // lnwire.UnixTimestamp; for v2 channels it is a - // lnwire.BlockHeightTimestamp. A zero value means no update has been - // received from this node. - Node1Freshness lnwire.Timestamp - - // Node2Freshness is the update-ordering value of the latest received - // update from the node 2 channel peer. For v1 channels this is a - // lnwire.UnixTimestamp; for v2 channels it is a - // lnwire.BlockHeightTimestamp. A zero value means no update has been - // received from this node. - Node2Freshness lnwire.Timestamp + // Node2UpdateTimestamp is the timestamp of the latest received update + // from the node 2 channel peer. This will be set to zero time if no + // update has yet been received from this node. + Node2UpdateTimestamp time.Time } -// NewV1ChannelUpdateInfo constructs a ChannelUpdateInfo for a v1 gossip -// channel. The node timestamps are normalised to the unix epoch if zero. -func NewV1ChannelUpdateInfo(scid lnwire.ShortChannelID, - node1Timestamp, node2Timestamp time.Time) ChannelUpdateInfo { +// NewChannelUpdateInfo is a constructor which makes sure we initialize the +// timestamps with zero seconds unix timestamp which equals +// `January 1, 1970, 00:00:00 UTC` in case the value is `time.Time{}`. +func NewChannelUpdateInfo(scid lnwire.ShortChannelID, node1Timestamp, + node2Timestamp time.Time) ChannelUpdateInfo { + + chanInfo := ChannelUpdateInfo{ + ShortChannelID: scid, + Node1UpdateTimestamp: node1Timestamp, + Node2UpdateTimestamp: node2Timestamp, + } - node1Unix := lnwire.UnixTimestamp(node1Timestamp.Unix()) if node1Timestamp.IsZero() { - node1Unix = 0 + chanInfo.Node1UpdateTimestamp = time.Unix(0, 0) } - node2Unix := lnwire.UnixTimestamp(node2Timestamp.Unix()) if node2Timestamp.IsZero() { - node2Unix = 0 + chanInfo.Node2UpdateTimestamp = time.Unix(0, 0) } - return ChannelUpdateInfo{ - ShortChannelID: scid, - Version: lnwire.GossipVersion1, - Node1Freshness: node1Unix, - Node2Freshness: node2Unix, - } -} - -// NewV2ChannelUpdateInfo constructs a ChannelUpdateInfo for a v2 gossip -// channel. A block height of zero means no update has been received from -// the corresponding node. -func NewV2ChannelUpdateInfo(scid lnwire.ShortChannelID, - node1BlockHeight, node2BlockHeight uint32) ChannelUpdateInfo { - - return ChannelUpdateInfo{ - ShortChannelID: scid, - Version: lnwire.GossipVersion2, - Node1Freshness: lnwire.BlockHeightTimestamp(node1BlockHeight), - Node2Freshness: lnwire.BlockHeightTimestamp(node2BlockHeight), - } -} - -// Node1FreshnessTime returns the v1 unix-time freshness for node 1's latest -// update. It returns the zero time if the freshness is not a unix timestamp. -func (c ChannelUpdateInfo) Node1FreshnessTime() time.Time { - if u, ok := c.Node1Freshness.(lnwire.UnixTimestamp); ok { - return time.Unix(int64(u), 0) - } - - return time.Time{} -} - -// Node2FreshnessTime returns the v1 unix-time freshness for node 2's latest -// update. It returns the zero time if the freshness is not a unix timestamp. -func (c ChannelUpdateInfo) Node2FreshnessTime() time.Time { - if u, ok := c.Node2Freshness.(lnwire.UnixTimestamp); ok { - return time.Unix(int64(u), 0) - } - - return time.Time{} + return chanInfo } // BlockChannelRange represents a range of channels for a given block height. @@ -2902,13 +2726,8 @@ type BlockChannelRange struct { // up after a period of time offline. If withTimestamps is true then the // timestamp info of the latest received channel update messages of the channel // will be included in the response. -func (c *KVStore) FilterChannelRange(_ context.Context, - v lnwire.GossipVersion, startHeight, endHeight uint32, - withTimestamps bool) ([]BlockChannelRange, error) { - - if v != lnwire.GossipVersion1 { - return nil, ErrVersionNotSupportedForKVDB - } +func (c *KVStore) FilterChannelRange(startHeight, + endHeight uint32, withTimestamps bool) ([]BlockChannelRange, error) { startChanID := &lnwire.ShortChannelID{ BlockHeight: startHeight, @@ -2962,7 +2781,7 @@ func (c *KVStore) FilterChannelRange(_ context.Context, rawCid := byteOrder.Uint64(k) cid := lnwire.NewShortChanIDFromInt(rawCid) - chanInfo := NewV1ChannelUpdateInfo( + chanInfo := NewChannelUpdateInfo( cid, time.Time{}, time.Time{}, ) @@ -2975,7 +2794,7 @@ func (c *KVStore) FilterChannelRange(_ context.Context, continue } - node1Key, node2Key := computeEdgePolicyKeys(edgeInfo) + node1Key, node2Key := computeEdgePolicyKeys(&edgeInfo) rawPolicy := edges.Get(node1Key) if len(rawPolicy) != 0 { @@ -2989,9 +2808,7 @@ func (c *KVStore) FilterChannelRange(_ context.Context, return err } - chanInfo.Node1Freshness = lnwire.UnixTimestamp( - edge.LastUpdate.Unix(), - ) + chanInfo.Node1UpdateTimestamp = edge.LastUpdate } rawPolicy = edges.Get(node2Key) @@ -3006,9 +2823,7 @@ func (c *KVStore) FilterChannelRange(_ context.Context, return err } - chanInfo.Node2Freshness = lnwire.UnixTimestamp( - edge.LastUpdate.Unix(), - ) + chanInfo.Node2UpdateTimestamp = edge.LastUpdate } channelsPerBlock[cid.BlockHeight] = append( @@ -3056,13 +2871,7 @@ func (c *KVStore) FilterChannelRange(_ context.Context, // skipped and the result will contain only those edges that exist at the time // of the query. This can be used to respond to peer queries that are seeking to // fill in gaps in their view of the channel graph. -func (c *KVStore) FetchChanInfos(_ context.Context, v lnwire.GossipVersion, - chanIDs []uint64) ([]ChannelEdge, error) { - - if v != lnwire.GossipVersion1 { - return nil, ErrVersionNotSupportedForKVDB - } - +func (c *KVStore) FetchChanInfos(chanIDs []uint64) ([]ChannelEdge, error) { return c.fetchChanInfos(nil, chanIDs) } @@ -3137,11 +2946,11 @@ func (c *KVStore) fetchChanInfos(tx kvdb.RTx, chanIDs []uint64) ( } chanEdges = append(chanEdges, ChannelEdge{ - Info: edgeInfo, + Info: &edgeInfo, Policy1: edge1, Policy2: edge2, - Node1: node1, - Node2: node2, + Node1: &node1, + Node2: &node2, }) } @@ -3288,7 +3097,7 @@ func (c *KVStore) delChannelEdgeUnsafe(edges, edgeIndex, chanIndex, // being removed due to the channel becoming a zombie. We do this to // ensure we don't store unnecessary data for spent channels. if !isZombie { - return edgeInfo, nil + return &edgeInfo, nil } nodeKey1, nodeKey2 := edgeInfo.NodeKey1Bytes, edgeInfo.NodeKey2Bytes @@ -3307,7 +3116,7 @@ func (c *KVStore) delChannelEdgeUnsafe(edges, edgeIndex, chanIndex, ) } - return edgeInfo, markEdgeZombie( + return &edgeInfo, markEdgeZombie( zombieIndex, byteOrder.Uint64(chanID), nodeKey1, nodeKey2, ) } @@ -3426,30 +3235,26 @@ func (c *KVStore) updateEdgeCache(e *models.ChannelEdgePolicy, // the entry with the updated timestamp for the direction that was just // written. If the edge doesn't exist, we'll load the cache entry lazily // during the next query for this edge. - entry, ok := c.rejectCache.get(lnwire.GossipVersion1, e.ChannelID) - if ok { + if entry, ok := c.rejectCache.get(e.ChannelID); ok { if isUpdate1 { entry.upd1Time = e.LastUpdate.Unix() } else { entry.upd2Time = e.LastUpdate.Unix() } - c.rejectCache.insert(lnwire.GossipVersion1, e.ChannelID, entry) + c.rejectCache.insert(e.ChannelID, entry) } // If an entry for this channel is found in channel cache, we'll modify // the entry with the updated policy for the direction that was just // written. If the edge doesn't exist, we'll defer loading the info and // policies and lazily read from disk during the next query. - channel, ok := c.chanCache.get( - lnwire.GossipVersion1, e.ChannelID, - ) - if ok { + if channel, ok := c.chanCache.get(e.ChannelID); ok { if isUpdate1 { channel.Policy1 = e } else { channel.Policy2 = e } - c.chanCache.insert(lnwire.GossipVersion1, e.ChannelID, channel) + c.chanCache.insert(e.ChannelID, channel) } } @@ -3461,9 +3266,6 @@ func updateEdgePolicy(tx kvdb.RwTx, edge *models.ChannelEdgePolicy) ( route.Vertex, route.Vertex, bool, error) { var noVertex route.Vertex - if edge.Version != lnwire.GossipVersion1 { - return noVertex, noVertex, false, ErrVersionNotSupportedForKVDB - } edges := tx.ReadWriteBucket(edgeBucket) if edges == nil { @@ -3545,11 +3347,8 @@ func (c *KVStore) isPublic(tx kvdb.RTx, nodePub route.Vertex, } // Since the edge _does_ extend to the source node, we'll also - // need to ensure that this is a public edge with valid - // signatures (not empty). - if info.AuthProof != nil && !info.AuthProof.IsEmpty() && - len(info.AuthProof.BitcoinSig1()) > 0 { - + // need to ensure that this is a public edge. + if info.AuthProof != nil { nodeIsPublic = true return errDone } @@ -3579,13 +3378,9 @@ func (c *KVStore) fetchNodeTx(tx kvdb.RTx, nodePub route.Vertex) (*models.Node, // FetchNode attempts to look up a target node by its identity public // key. If the node isn't found in the database, then ErrGraphNodeNotFound is // returned. -func (c *KVStore) FetchNode(_ context.Context, v lnwire.GossipVersion, +func (c *KVStore) FetchNode(_ context.Context, nodePub route.Vertex) (*models.Node, error) { - if v != lnwire.GossipVersion1 { - return nil, ErrVersionNotSupportedForKVDB - } - return c.fetchLightningNode(nil, nodePub) } @@ -3620,7 +3415,7 @@ func (c *KVStore) fetchLightningNode(tx kvdb.RTx, return err } - node = n + node = &n return nil } @@ -3646,12 +3441,11 @@ func (c *KVStore) fetchLightningNode(tx kvdb.RTx, return node, nil } -// HasV1Node determines if the graph has a vertex identified by the -// target node identity public key. If the node exists in the database, a -// timestamp of when the data for the node was lasted updated is returned along -// with a true boolean. Otherwise, an empty time.Time is returned with a false -// boolean. -func (c *KVStore) HasV1Node(_ context.Context, +// HasNode determines if the graph has a vertex identified by the target node +// identity public key. If the node exists in the database, a timestamp of when +// the data for the node was lasted updated is returned along with a true +// boolean. Otherwise, an empty time.Time is returned with a false boolean. +func (c *KVStore) HasNode(_ context.Context, nodePub [33]byte) (time.Time, bool, error) { var ( @@ -3671,6 +3465,7 @@ func (c *KVStore) HasV1Node(_ context.Context, // exit early. nodeBytes := nodes.Get(nodePub[:]) if nodeBytes == nil { + exists = false return nil } @@ -3698,44 +3493,6 @@ func (c *KVStore) HasV1Node(_ context.Context, return updateTime, exists, nil } -// HasNode determines if the graph has a vertex identified by the target node -// identity public key. -func (c *KVStore) HasNode(_ context.Context, v lnwire.GossipVersion, - nodePub [33]byte) (bool, error) { - - if v != lnwire.GossipVersion1 { - return false, ErrVersionNotSupportedForKVDB - } - - var exists bool - err := kvdb.View(c.db, func(tx kvdb.RTx) error { - // First grab the nodes bucket which stores the mapping from - // pubKey to node information. - nodes := tx.ReadBucket(nodeBucket) - if nodes == nil { - return ErrGraphNotFound - } - - // If a key for this serialized public key isn't found, we can - // exit early. - nodeBytes := nodes.Get(nodePub[:]) - if nodeBytes == nil { - return nil - } - - exists = true - - return nil - }, func() { - exists = false - }) - if err != nil { - return exists, err - } - - return exists, nil -} - // nodeTraversal is used to traverse all channels of a node given by its // public key and passes channel information into the specified callback. // @@ -3803,7 +3560,7 @@ func nodeTraversal(tx kvdb.RTx, nodePub []byte, db kvdb.Backend, } // Finally, we execute the callback. - err = cb(tx, edgeInfo, outgoingPolicy, incomingPolicy) + err = cb(tx, &edgeInfo, outgoingPolicy, incomingPolicy) if err != nil { return err } @@ -3831,15 +3588,10 @@ func nodeTraversal(tx kvdb.RTx, nodePub []byte, db kvdb.Backend, // halted with the error propagated back up to the caller. // // Unknown policies are passed into the callback as nil values. -func (c *KVStore) ForEachNodeChannel(_ context.Context, - v lnwire.GossipVersion, nodePub route.Vertex, +func (c *KVStore) ForEachNodeChannel(_ context.Context, nodePub route.Vertex, cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error, reset func()) error { - if v != lnwire.GossipVersion1 { - return ErrVersionNotSupportedForKVDB - } - return nodeTraversal( nil, nodePub[:], c.db, func(_ kvdb.RTx, info *models.ChannelEdgeInfo, policy, @@ -3855,13 +3607,8 @@ func (c *KVStore) ForEachNodeChannel(_ context.Context, // channel's outpoint, whether we have a policy for the channel and the channel // peer's node information. func (c *KVStore) ForEachSourceNodeChannel(_ context.Context, - v lnwire.GossipVersion, cb func(chanPoint wire.OutPoint, - havePolicy bool, otherNode *models.Node) error, - reset func()) error { - - if v != lnwire.GossipVersion1 { - return ErrVersionNotSupportedForKVDB - } + cb func(chanPoint wire.OutPoint, havePolicy bool, + otherNode *models.Node) error, reset func()) error { return kvdb.View(c.db, func(tx kvdb.RTx) error { nodes := tx.ReadBucket(nodeBucket) @@ -3950,7 +3697,7 @@ func (c *KVStore) fetchOtherNode(tx kvdb.RTx, return err } - targetNode = node + targetNode = &node return nil } @@ -3992,8 +3739,7 @@ func computeEdgePolicyKeys(info *models.ChannelEdgeInfo) ([]byte, []byte) { // found, then ErrEdgeNotFound is returned. A struct which houses the general // information for the channel itself is returned as well as two structs that // contain the routing policies for the channel in either direction. -func (c *KVStore) FetchChannelEdgesByOutpoint(_ context.Context, - v lnwire.GossipVersion, op *wire.OutPoint) ( +func (c *KVStore) FetchChannelEdgesByOutpoint(op *wire.OutPoint) ( *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy, error) { @@ -4003,10 +3749,6 @@ func (c *KVStore) FetchChannelEdgesByOutpoint(_ context.Context, policy2 *models.ChannelEdgePolicy ) - if v != lnwire.GossipVersion1 { - return nil, nil, nil, ErrVersionNotSupportedForKVDB - } - err := kvdb.View(c.db, func(tx kvdb.RTx) error { // First, grab the node bucket. This will be used to populate // the Node pointers in each edge read from disk. @@ -4048,7 +3790,7 @@ func (c *KVStore) FetchChannelEdgesByOutpoint(_ context.Context, if err != nil { return fmt.Errorf("%w: chanID=%x", err, chanID) } - edgeInfo = edge + edgeInfo = &edge // Once we have the information about the channels' parameters, // we'll fetch the routing policies for each for the directed @@ -4083,15 +3825,10 @@ func (c *KVStore) FetchChannelEdgesByOutpoint(_ context.Context, // ErrZombieEdge an be returned if the edge is currently marked as a zombie // within the database. In this case, the ChannelEdgePolicy's will be nil, and // the ChannelEdgeInfo will only include the public keys of each node. -func (c *KVStore) FetchChannelEdgesByID(_ context.Context, - v lnwire.GossipVersion, chanID uint64) ( +func (c *KVStore) FetchChannelEdgesByID(chanID uint64) ( *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy, error) { - if v != lnwire.GossipVersion1 { - return nil, nil, nil, ErrVersionNotSupportedForKVDB - } - var ( edgeInfo *models.ChannelEdgeInfo policy1 *models.ChannelEdgePolicy @@ -4146,14 +3883,10 @@ func (c *KVStore) FetchChannelEdgesByID(_ context.Context, // populate the edge info with the public keys of each // party as this is the only information we have about // it and return an error signaling so. - zombieEdge, err := models.NewV1Channel( - 0, chainhash.Hash{}, pubKey1, pubKey2, - &models.ChannelV1Fields{}, - ) - if err != nil { - return err + edgeInfo = &models.ChannelEdgeInfo{ + NodeKey1Bytes: pubKey1, + NodeKey2Bytes: pubKey2, } - edgeInfo = zombieEdge return ErrZombieEdge } @@ -4163,7 +3896,7 @@ func (c *KVStore) FetchChannelEdgesByID(_ context.Context, return err } - edgeInfo = edge + edgeInfo = &edge // Then we'll attempt to fetch the accompanying policies of this // edge. @@ -4196,13 +3929,7 @@ func (c *KVStore) FetchChannelEdgesByID(_ context.Context, // IsPublicNode is a helper method that determines whether the node with the // given public key is seen as a public node in the graph from the graph's // source node's point of view. -func (c *KVStore) IsPublicNode(_ context.Context, v lnwire.GossipVersion, - pubKey [33]byte) (bool, error) { - - if v != lnwire.GossipVersion1 { - return false, ErrVersionNotSupportedForKVDB - } - +func (c *KVStore) IsPublicNode(pubKey [33]byte) (bool, error) { var nodeIsPublic bool err := kvdb.View(c.db, func(tx kvdb.RTx) error { nodes := tx.ReadBucket(nodeBucket) @@ -4231,6 +3958,26 @@ func (c *KVStore) IsPublicNode(_ context.Context, v lnwire.GossipVersion, return nodeIsPublic, nil } +// genMultiSigP2WSH generates the p2wsh'd multisig script for 2 of 2 pubkeys. +func genMultiSigP2WSH(aPub, bPub []byte) ([]byte, error) { + witnessScript, err := input.GenMultiSigScript(aPub, bPub) + if err != nil { + return nil, err + } + + // With the witness script generated, we'll now turn it into a p2wsh + // script: + // * OP_0 + bldr := txscript.NewScriptBuilder( + txscript.WithScriptAllocSize(input.P2WSHSize), + ) + bldr.AddOp(txscript.OP_0) + scriptHash := sha256.Sum256(witnessScript) + bldr.AddData(scriptHash[:]) + + return bldr.Script() +} + // EdgePoint couples the outpoint of a channel with the funding script that it // creates. The FilteredChainView will use this to watch for spends of this // edge point on chain. We require both of these values as depending on the @@ -4253,12 +4000,7 @@ func (e *EdgePoint) String() string { // within the known channel graph. The set of UTXO's (along with their scripts) // returned are the ones that need to be watched on chain to detect channel // closes on the resident blockchain. -func (c *KVStore) ChannelView(_ context.Context, - v lnwire.GossipVersion) ([]EdgePoint, error) { - - if v != lnwire.GossipVersion1 { - return nil, ErrVersionNotSupportedForKVDB - } +func (c *KVStore) ChannelView() ([]EdgePoint, error) { var edgePoints []EdgePoint if err := kvdb.View(c.db, func(tx kvdb.RTx) error { // We're going to iterate over the entire channel index, so @@ -4299,7 +4041,10 @@ func (c *KVStore) ChannelView(_ context.Context, return err } - pkScript, err := edgeInfo.FundingPKScript() + pkScript, err := genMultiSigP2WSH( + edgeInfo.BitcoinKey1Bytes[:], + edgeInfo.BitcoinKey2Bytes[:], + ) if err != nil { return err } @@ -4322,14 +4067,10 @@ func (c *KVStore) ChannelView(_ context.Context, } // MarkEdgeZombie attempts to mark a channel identified by its channel ID as a -// zombie for the given gossip version. This method is used on an ad-hoc basis, -// when channels need to be marked as zombies outside the normal pruning cycle. -func (c *KVStore) MarkEdgeZombie(_ context.Context, v lnwire.GossipVersion, - chanID uint64, pubKey1, pubKey2 [33]byte) error { - - if v != lnwire.GossipVersion1 { - return ErrVersionNotSupportedForKVDB - } +// zombie. This method is used on an ad-hoc basis, when channels need to be +// marked as zombies outside the normal pruning cycle. +func (c *KVStore) MarkEdgeZombie(chanID uint64, + pubKey1, pubKey2 [33]byte) error { c.cacheMu.Lock() defer c.cacheMu.Unlock() @@ -4351,8 +4092,8 @@ func (c *KVStore) MarkEdgeZombie(_ context.Context, v lnwire.GossipVersion, return err } - c.rejectCache.remove(lnwire.GossipVersion1, chanID) - c.chanCache.remove(lnwire.GossipVersion1, chanID) + c.rejectCache.remove(chanID) + c.chanCache.remove(chanID) return nil } @@ -4373,15 +4114,8 @@ func markEdgeZombie(zombieIndex kvdb.RwBucket, chanID uint64, pubKey1, return zombieIndex.Put(k[:], v[:]) } -// MarkEdgeLive clears an edge from our zombie index for the given gossip -// version, deeming it as live. -func (c *KVStore) MarkEdgeLive(_ context.Context, v lnwire.GossipVersion, - chanID uint64) error { - - if v != lnwire.GossipVersion1 { - return ErrVersionNotSupportedForKVDB - } - +// MarkEdgeLive clears an edge from our zombie index, deeming it as live. +func (c *KVStore) MarkEdgeLive(chanID uint64) error { c.cacheMu.Lock() defer c.cacheMu.Unlock() @@ -4427,8 +4161,8 @@ func (c *KVStore) markEdgeLiveUnsafe(tx kvdb.RwTx, chanID uint64) error { return err } - c.rejectCache.remove(lnwire.GossipVersion1, chanID) - c.chanCache.remove(lnwire.GossipVersion1, chanID) + c.rejectCache.remove(chanID) + c.chanCache.remove(chanID) return nil } @@ -4436,19 +4170,14 @@ func (c *KVStore) markEdgeLiveUnsafe(tx kvdb.RwTx, chanID uint64) error { // IsZombieEdge returns whether the edge is considered zombie. If it is a // zombie, then the two node public keys corresponding to this edge are also // returned. -func (c *KVStore) IsZombieEdge(_ context.Context, v lnwire.GossipVersion, - chanID uint64) (bool, [33]byte, [33]byte, error) { +func (c *KVStore) IsZombieEdge(chanID uint64) (bool, [33]byte, [33]byte, + error) { var ( isZombie bool pubKey1, pubKey2 [33]byte ) - if v != lnwire.GossipVersion1 { - return false, [33]byte{}, [33]byte{}, - ErrVersionNotSupportedForKVDB - } - err := kvdb.View(c.db, func(tx kvdb.RTx) error { edges := tx.ReadBucket(edgeBucket) if edges == nil { @@ -4497,13 +4226,7 @@ func isZombieEdge(zombieIndex kvdb.RBucket, } // NumZombies returns the current number of zombie channels in the graph. -func (c *KVStore) NumZombies( - _ context.Context, v lnwire.GossipVersion, -) (uint64, error) { - - if v != lnwire.GossipVersion1 { - return 0, ErrVersionNotSupportedForKVDB - } +func (c *KVStore) NumZombies() (uint64, error) { var numZombies uint64 err := kvdb.View(c.db, func(tx kvdb.RTx) error { edges := tx.ReadBucket(edgeBucket) @@ -4532,9 +4255,7 @@ func (c *KVStore) NumZombies( // PutClosedScid stores a SCID for a closed channel in the database. This is so // that we can ignore channel announcements that we know to be closed without // having to validate them and fetch a block. -func (c *KVStore) PutClosedScid(_ context.Context, - scid lnwire.ShortChannelID) error { - +func (c *KVStore) PutClosedScid(scid lnwire.ShortChannelID) error { return kvdb.Update(c.db, func(tx kvdb.RwTx) error { closedScids, err := tx.CreateTopLevelBucket(closedScidBucket) if err != nil { @@ -4551,9 +4272,7 @@ func (c *KVStore) PutClosedScid(_ context.Context, // IsClosedScid checks whether a channel identified by the passed in scid is // closed. This helps avoid having to perform expensive validation checks. // TODO: Add an LRU cache to cut down on disc reads. -func (c *KVStore) IsClosedScid(_ context.Context, - scid lnwire.ShortChannelID) (bool, error) { - +func (c *KVStore) IsClosedScid(scid lnwire.ShortChannelID) (bool, error) { var isClosed bool err := kvdb.View(c.db, func(tx kvdb.RTx) error { closedScids := tx.ReadBucket(closedScidBucket) @@ -4582,8 +4301,8 @@ func (c *KVStore) IsClosedScid(_ context.Context, // GraphSession will provide the call-back with access to a NodeTraverser // instance which can be used to perform queries against the channel graph. -func (c *KVStore) GraphSession(_ context.Context, - cb func(graph NodeTraverser) error, reset func()) error { +func (c *KVStore) GraphSession(cb func(graph NodeTraverser) error, + reset func()) error { return c.db.View(func(tx walletdb.ReadTx) error { return cb(&nodeTraverserSession{ @@ -4604,8 +4323,7 @@ type nodeTraverserSession struct { // node. // // NOTE: Part of the NodeTraverser interface. -func (c *nodeTraverserSession) ForEachNodeDirectedChannel( - _ context.Context, nodePub route.Vertex, +func (c *nodeTraverserSession) ForEachNodeDirectedChannel(nodePub route.Vertex, cb func(channel *DirectedChannel) error, _ func()) error { return c.db.forEachNodeDirectedChannel(c.tx, nodePub, cb, func() {}) @@ -4615,8 +4333,7 @@ func (c *nodeTraverserSession) ForEachNodeDirectedChannel( // unknown, assume no additional features are supported. // // NOTE: Part of the NodeTraverser interface. -func (c *nodeTraverserSession) FetchNodeFeatures(_ context.Context, - nodePub route.Vertex) ( +func (c *nodeTraverserSession) FetchNodeFeatures(nodePub route.Vertex) ( *lnwire.FeatureVector, error) { return c.db.fetchNodeFeatures(c.tx, nodePub) @@ -4625,10 +4342,6 @@ func (c *nodeTraverserSession) FetchNodeFeatures(_ context.Context, func putLightningNode(nodeBucket, aliasBucket, updateIndex kvdb.RwBucket, node *models.Node) error { - if node.Version != lnwire.GossipVersion1 { - return ErrVersionNotSupportedForKVDB - } - var ( scratch [16]byte b bytes.Buffer @@ -4657,7 +4370,7 @@ func putLightningNode(nodeBucket, aliasBucket, updateIndex kvdb.RwBucket, // If we got a node announcement for this node, we will have the rest // of the data available. If not we don't have more data to write. - if !node.HaveAnnouncement() { + if !node.HaveNodeAnnouncement { // Write HaveNodeAnnouncement=0. byteOrder.PutUint16(scratch[:2], 0) if _, err := b.Write(scratch[:2]); err != nil { @@ -4673,20 +4386,17 @@ func putLightningNode(nodeBucket, aliasBucket, updateIndex kvdb.RwBucket, return err } - nodeColor := node.Color.UnwrapOr(color.RGBA{}) - - if err := binary.Write(&b, byteOrder, nodeColor.R); err != nil { + if err := binary.Write(&b, byteOrder, node.Color.R); err != nil { return err } - if err := binary.Write(&b, byteOrder, nodeColor.G); err != nil { + if err := binary.Write(&b, byteOrder, node.Color.G); err != nil { return err } - if err := binary.Write(&b, byteOrder, nodeColor.B); err != nil { + if err := binary.Write(&b, byteOrder, node.Color.B); err != nil { return err } - err = wire.WriteVarString(&b, 0, node.Alias.UnwrapOr("")) - if err != nil { + if err := wire.WriteVarString(&b, 0, node.Alias); err != nil { return err } @@ -4725,8 +4435,7 @@ func putLightningNode(nodeBucket, aliasBucket, updateIndex kvdb.RwBucket, return err } - err = aliasBucket.Put(nodePub, []byte(node.Alias.UnwrapOr(""))) - if err != nil { + if err := aliasBucket.Put(nodePub, []byte(node.Alias)); err != nil { return err } @@ -4760,11 +4469,11 @@ func putLightningNode(nodeBucket, aliasBucket, updateIndex kvdb.RwBucket, } func fetchLightningNode(nodeBucket kvdb.RBucket, - nodePub []byte) (*models.Node, error) { + nodePub []byte) (models.Node, error) { nodeBytes := nodeBucket.Get(nodePub) if nodeBytes == nil { - return nil, ErrGraphNodeNotFound + return models.Node{}, ErrGraphNodeNotFound } nodeReader := bytes.NewReader(nodeBytes) @@ -4827,65 +4536,69 @@ func deserializeLightningNodeCacheable(r io.Reader) (route.Vertex, return pubKey, features, nil } -func deserializeLightningNode(r io.Reader) (*models.Node, error) { +func deserializeLightningNode(r io.Reader) (models.Node, error) { var ( + node models.Node scratch [8]byte err error - pubKey [33]byte ) + // Always populate a feature vector, even if we don't have a node + // announcement and short circuit below. + node.Features = lnwire.EmptyFeatureVector() + if _, err := r.Read(scratch[:]); err != nil { - return nil, err + return models.Node{}, err } unix := int64(byteOrder.Uint64(scratch[:])) - lastUpdate := time.Unix(unix, 0) + node.LastUpdate = time.Unix(unix, 0) - if _, err := io.ReadFull(r, pubKey[:]); err != nil { - return nil, err + if _, err := io.ReadFull(r, node.PubKeyBytes[:]); err != nil { + return models.Node{}, err } - node := models.NewV1ShellNode(pubKey) - node.LastUpdate = lastUpdate - if _, err := r.Read(scratch[:2]); err != nil { - return nil, err + return models.Node{}, err } hasNodeAnn := byteOrder.Uint16(scratch[:2]) + if hasNodeAnn == 1 { + node.HaveNodeAnnouncement = true + } else { + node.HaveNodeAnnouncement = false + } + // The rest of the data is optional, and will only be there if we got a // node announcement for this node. - if hasNodeAnn == 0 { + if !node.HaveNodeAnnouncement { return node, nil } // We did get a node announcement for this node, so we'll have the rest // of the data available. - var nodeColor color.RGBA - if err := binary.Read(r, byteOrder, &nodeColor.R); err != nil { - return nil, err + if err := binary.Read(r, byteOrder, &node.Color.R); err != nil { + return models.Node{}, err } - if err := binary.Read(r, byteOrder, &nodeColor.G); err != nil { - return nil, err + if err := binary.Read(r, byteOrder, &node.Color.G); err != nil { + return models.Node{}, err } - if err := binary.Read(r, byteOrder, &nodeColor.B); err != nil { - return nil, err + if err := binary.Read(r, byteOrder, &node.Color.B); err != nil { + return models.Node{}, err } - node.Color = fn.Some(nodeColor) - alias, err := wire.ReadVarString(r, 0) + node.Alias, err = wire.ReadVarString(r, 0) if err != nil { - return nil, err + return models.Node{}, err } - node.Alias = fn.Some(alias) err = node.Features.Decode(r) if err != nil { - return nil, err + return models.Node{}, err } if _, err := r.Read(scratch[:2]); err != nil { - return nil, err + return models.Node{}, err } numAddresses := int(byteOrder.Uint16(scratch[:2])) @@ -4893,7 +4606,7 @@ func deserializeLightningNode(r io.Reader) (*models.Node, error) { for i := 0; i < numAddresses; i++ { address, err := DeserializeAddr(r) if err != nil { - return nil, err + return models.Node{}, err } addresses = append(addresses, address) } @@ -4901,7 +4614,7 @@ func deserializeLightningNode(r io.Reader) (*models.Node, error) { node.AuthSigBytes, err = wire.ReadVarBytes(r, 0, 80, "sig") if err != nil { - return nil, err + return models.Node{}, err } // We'll try and see if there are any opaque bytes left, if not, then @@ -4913,7 +4626,7 @@ func deserializeLightningNode(r io.Reader) (*models.Node, error) { case errors.Is(err, io.ErrUnexpectedEOF): case errors.Is(err, io.EOF): case err != nil: - return nil, err + return models.Node{}, err } if len(extraBytes) > 0 { @@ -4926,12 +4639,6 @@ func deserializeLightningNode(r io.Reader) (*models.Node, error) { func putChanEdgeInfo(edgeIndex kvdb.RwBucket, edgeInfo *models.ChannelEdgeInfo, chanID [8]byte) error { - // We only support V1 channel edges in the KV store. - if edgeInfo.Version != lnwire.GossipVersion1 { - return fmt.Errorf("only V1 channel edges supported, got V%d", - edgeInfo.Version) - } - var b bytes.Buffer if _, err := b.Write(edgeInfo.NodeKey1Bytes[:]); err != nil { @@ -4940,24 +4647,10 @@ func putChanEdgeInfo(edgeIndex kvdb.RwBucket, if _, err := b.Write(edgeInfo.NodeKey2Bytes[:]); err != nil { return err } - - btc1Key, err := edgeInfo.BitcoinKey1Bytes.UnwrapOrErr( - fmt.Errorf("edge missing bitcoin key 1"), - ) - if err != nil { + if _, err := b.Write(edgeInfo.BitcoinKey1Bytes[:]); err != nil { return err } - btc2Key, err := edgeInfo.BitcoinKey2Bytes.UnwrapOrErr( - fmt.Errorf("edge missing bitcoin key 2"), - ) - if err != nil { - return err - } - - if _, err := b.Write(btc1Key[:]); err != nil { - return err - } - if _, err := b.Write(btc2Key[:]); err != nil { + if _, err := b.Write(edgeInfo.BitcoinKey2Bytes[:]); err != nil { return err } @@ -4973,10 +4666,10 @@ func putChanEdgeInfo(edgeIndex kvdb.RwBucket, authProof := edgeInfo.AuthProof var nodeSig1, nodeSig2, bitcoinSig1, bitcoinSig2 []byte if authProof != nil { - nodeSig1 = authProof.NodeSig1() - nodeSig2 = authProof.NodeSig2() - bitcoinSig1 = authProof.BitcoinSig1() - bitcoinSig2 = authProof.BitcoinSig2() + nodeSig1 = authProof.NodeSig1Bytes + nodeSig2 = authProof.NodeSig2Bytes + bitcoinSig1 = authProof.BitcoinSig1Bytes + bitcoinSig2 = authProof.BitcoinSig2Bytes } if err := wire.WriteVarBytes(&b, 0, nodeSig1); err != nil { @@ -4995,7 +4688,7 @@ func putChanEdgeInfo(edgeIndex kvdb.RwBucket, if err := WriteOutpoint(&b, &edgeInfo.ChannelPoint); err != nil { return err } - err = binary.Write(&b, byteOrder, uint64(edgeInfo.Capacity)) + err := binary.Write(&b, byteOrder, uint64(edgeInfo.Capacity)) if err != nil { return err } @@ -5018,11 +4711,11 @@ func putChanEdgeInfo(edgeIndex kvdb.RwBucket, } func fetchChanEdgeInfo(edgeIndex kvdb.RBucket, - chanID []byte) (*models.ChannelEdgeInfo, error) { + chanID []byte) (models.ChannelEdgeInfo, error) { edgeInfoBytes := edgeIndex.Get(chanID) if edgeInfoBytes == nil { - return nil, ErrEdgeNotFound + return models.ChannelEdgeInfo{}, ErrEdgeNotFound } edgeInfoReader := bytes.NewReader(edgeInfoBytes) @@ -5102,78 +4795,52 @@ func deserializeChanEdgeFeatures(featureBytes []byte) (*lnwire.FeatureVector, return lnwire.NewFeatureVector(features, lnwire.Features), nil } -func deserializeChanEdgeInfo(r io.Reader) (*models.ChannelEdgeInfo, error) { +func deserializeChanEdgeInfo(r io.Reader) (models.ChannelEdgeInfo, error) { var ( err error edgeInfo models.ChannelEdgeInfo ) - // All channel edges in the KV store are V1. - edgeInfo.Version = lnwire.GossipVersion1 - if _, err := io.ReadFull(r, edgeInfo.NodeKey1Bytes[:]); err != nil { - return nil, err + return models.ChannelEdgeInfo{}, err } if _, err := io.ReadFull(r, edgeInfo.NodeKey2Bytes[:]); err != nil { - return nil, err + return models.ChannelEdgeInfo{}, err } - - var btcKey1, btcKey2 route.Vertex - if _, err := io.ReadFull(r, btcKey1[:]); err != nil { - return nil, err + if _, err := io.ReadFull(r, edgeInfo.BitcoinKey1Bytes[:]); err != nil { + return models.ChannelEdgeInfo{}, err } - edgeInfo.BitcoinKey1Bytes = fn.Some(btcKey1) - - if _, err := io.ReadFull(r, btcKey2[:]); err != nil { - return nil, err + if _, err := io.ReadFull(r, edgeInfo.BitcoinKey2Bytes[:]); err != nil { + return models.ChannelEdgeInfo{}, err } - edgeInfo.BitcoinKey2Bytes = fn.Some(btcKey2) featureBytes, err := wire.ReadVarBytes(r, 0, 900, "features") if err != nil { - return nil, err + return models.ChannelEdgeInfo{}, err } edgeInfo.Features, err = deserializeChanEdgeFeatures(featureBytes) if err != nil { - return nil, err + return models.ChannelEdgeInfo{}, err } - proof := &models.ChannelAuthProof{ - // KV store always uses v1. - Version: lnwire.GossipVersion1, - } + proof := &models.ChannelAuthProof{} - nodeSig1, err := wire.ReadVarBytes(r, 0, 80, "sigs") + proof.NodeSig1Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs") if err != nil { - return nil, err + return models.ChannelEdgeInfo{}, err } - if len(nodeSig1) > 0 { - proof.NodeSig1Bytes = fn.Some(nodeSig1) - } - - nodeSig2, err := wire.ReadVarBytes(r, 0, 80, "sigs") + proof.NodeSig2Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs") if err != nil { - return nil, err + return models.ChannelEdgeInfo{}, err } - if len(nodeSig2) > 0 { - proof.NodeSig2Bytes = fn.Some(nodeSig2) - } - - bitcoinSig1, err := wire.ReadVarBytes(r, 0, 80, "sigs") + proof.BitcoinSig1Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs") if err != nil { - return nil, err + return models.ChannelEdgeInfo{}, err } - if len(bitcoinSig1) > 0 { - proof.BitcoinSig1Bytes = fn.Some(bitcoinSig1) - } - - bitcoinSig2, err := wire.ReadVarBytes(r, 0, 80, "sigs") + proof.BitcoinSig2Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs") if err != nil { - return nil, err - } - if len(bitcoinSig2) > 0 { - proof.BitcoinSig2Bytes = fn.Some(bitcoinSig2) + return models.ChannelEdgeInfo{}, err } if !proof.IsEmpty() { @@ -5182,17 +4849,17 @@ func deserializeChanEdgeInfo(r io.Reader) (*models.ChannelEdgeInfo, error) { edgeInfo.ChannelPoint = wire.OutPoint{} if err := ReadOutpoint(r, &edgeInfo.ChannelPoint); err != nil { - return nil, err + return models.ChannelEdgeInfo{}, err } if err := binary.Read(r, byteOrder, &edgeInfo.Capacity); err != nil { - return nil, err + return models.ChannelEdgeInfo{}, err } if err := binary.Read(r, byteOrder, &edgeInfo.ChannelID); err != nil { - return nil, err + return models.ChannelEdgeInfo{}, err } if _, err := io.ReadFull(r, edgeInfo.ChainHash[:]); err != nil { - return nil, err + return models.ChannelEdgeInfo{}, err } // We'll try and see if there are any opaque bytes left, if not, then @@ -5204,10 +4871,10 @@ func deserializeChanEdgeInfo(r io.Reader) (*models.ChannelEdgeInfo, error) { case errors.Is(err, io.ErrUnexpectedEOF): case errors.Is(err, io.EOF): case err != nil: - return nil, err + return models.ChannelEdgeInfo{}, err } - return &edgeInfo, nil + return edgeInfo, nil } func putChanEdgePolicy(edges kvdb.RwBucket, edge *models.ChannelEdgePolicy, @@ -5414,10 +5081,6 @@ func fetchChanEdgePolicies(edgeIndex kvdb.RBucket, edges kvdb.RBucket, func serializeChanEdgePolicy(w io.Writer, edge *models.ChannelEdgePolicy, to []byte) error { - if edge.Version != lnwire.GossipVersion1 { - return ErrVersionNotSupportedForKVDB - } - err := wire.WriteVarBytes(w, 0, edge.SigBytes) if err != nil { return err @@ -5506,9 +5169,7 @@ func deserializeChanEdgePolicy(r io.Reader) (*models.ChannelEdgePolicy, error) { func deserializeChanEdgePolicyRaw(r io.Reader) (*models.ChannelEdgePolicy, error) { - edge := &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, - } + edge := &models.ChannelEdgePolicy{} var err error edge.SigBytes, err = wire.ReadVarBytes(r, 0, 80, "sig") diff --git a/graph/db/migration1/addr.go b/graph/db/migration1/addr.go deleted file mode 100644 index 4a2ed6e46..000000000 --- a/graph/db/migration1/addr.go +++ /dev/null @@ -1,324 +0,0 @@ -package migration1 - -import ( - "encoding/binary" - "errors" - "fmt" - "io" - "net" - - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/tor" -) - -// addressType specifies the network protocol and version that should be used -// when connecting to a node at a particular address. -type addressType uint8 - -const ( - // tcp4Addr denotes an IPv4 TCP address. - tcp4Addr addressType = 0 - - // tcp6Addr denotes an IPv6 TCP address. - tcp6Addr addressType = 1 - - // v2OnionAddr denotes a version 2 Tor onion service address. - v2OnionAddr addressType = 2 - - // v3OnionAddr denotes a version 3 Tor (prop224) onion service address. - v3OnionAddr addressType = 3 - - // opaqueAddrs denotes an address (or a set of addresses) that LND was - // not able to parse since LND is not yet aware of the address type. - opaqueAddrs addressType = 4 - - // dnsAddr denotes a DNS address type. - dnsAddr addressType = 5 -) - -// encodeDNSAddr encodes a DNS address. -func encodeDNSAddr(w io.Writer, addr *lnwire.DNSAddress) error { - if _, err := w.Write([]byte{byte(dnsAddr)}); err != nil { - return err - } - - // Write the length of the hostname. - hostLen := len(addr.Hostname) - if _, err := w.Write([]byte{byte(hostLen)}); err != nil { - return err - } - - if _, err := w.Write([]byte(addr.Hostname)); err != nil { - return err - } - - var port [2]byte - byteOrder.PutUint16(port[:], addr.Port) - if _, err := w.Write(port[:]); err != nil { - return err - } - - return nil -} - -// encodeTCPAddr serializes a TCP address into its compact raw bytes -// representation. -func encodeTCPAddr(w io.Writer, addr *net.TCPAddr) error { - var ( - addrType byte - ip []byte - ) - - if addr.IP.To4() != nil { - addrType = byte(tcp4Addr) - ip = addr.IP.To4() - } else { - addrType = byte(tcp6Addr) - ip = addr.IP.To16() - } - - if ip == nil { - return fmt.Errorf("unable to encode IP %v", addr.IP) - } - - if _, err := w.Write([]byte{addrType}); err != nil { - return err - } - - if _, err := w.Write(ip); err != nil { - return err - } - - var port [2]byte - byteOrder.PutUint16(port[:], uint16(addr.Port)) - if _, err := w.Write(port[:]); err != nil { - return err - } - - return nil -} - -// encodeOnionAddr serializes an onion address into its compact raw bytes -// representation. -func encodeOnionAddr(w io.Writer, addr *tor.OnionAddr) error { - var suffixIndex int - hostLen := len(addr.OnionService) - switch hostLen { - case tor.V2Len: - if _, err := w.Write([]byte{byte(v2OnionAddr)}); err != nil { - return err - } - suffixIndex = tor.V2Len - tor.OnionSuffixLen - case tor.V3Len: - if _, err := w.Write([]byte{byte(v3OnionAddr)}); err != nil { - return err - } - suffixIndex = tor.V3Len - tor.OnionSuffixLen - default: - return errors.New("unknown onion service length") - } - - suffix := addr.OnionService[suffixIndex:] - if suffix != tor.OnionSuffix { - return fmt.Errorf("invalid suffix \"%v\"", suffix) - } - - host, err := tor.Base32Encoding.DecodeString( - addr.OnionService[:suffixIndex], - ) - if err != nil { - return err - } - - // Sanity check the decoded length. - switch { - case hostLen == tor.V2Len && len(host) != tor.V2DecodedLen: - return fmt.Errorf("onion service %v decoded to invalid host %x", - addr.OnionService, host) - - case hostLen == tor.V3Len && len(host) != tor.V3DecodedLen: - return fmt.Errorf("onion service %v decoded to invalid host %x", - addr.OnionService, host) - } - - if _, err := w.Write(host); err != nil { - return err - } - - var port [2]byte - byteOrder.PutUint16(port[:], uint16(addr.Port)) - if _, err := w.Write(port[:]); err != nil { - return err - } - - return nil -} - -// encodeOpaqueAddrs serializes the lnwire.OpaqueAddrs type to a raw set of -// bytes that we will persist. -func encodeOpaqueAddrs(w io.Writer, addr *lnwire.OpaqueAddrs) error { - // Write the type byte. - if _, err := w.Write([]byte{byte(opaqueAddrs)}); err != nil { - return err - } - - // Write the length of the payload. - var l [2]byte - binary.BigEndian.PutUint16(l[:], uint16(len(addr.Payload))) - if _, err := w.Write(l[:]); err != nil { - return err - } - - // Write the payload. - _, err := w.Write(addr.Payload) - - return err -} - -// DeserializeAddr reads the serialized raw representation of an address and -// deserializes it into the actual address. This allows us to avoid address -// resolution within the channeldb package. -func DeserializeAddr(r io.Reader) (net.Addr, error) { - var addrType [1]byte - if _, err := r.Read(addrType[:]); err != nil { - return nil, err - } - - var address net.Addr - switch addressType(addrType[0]) { - case tcp4Addr: - var ip [4]byte - if _, err := r.Read(ip[:]); err != nil { - return nil, err - } - - var port [2]byte - if _, err := r.Read(port[:]); err != nil { - return nil, err - } - - address = &net.TCPAddr{ - IP: net.IP(ip[:]), - Port: int(binary.BigEndian.Uint16(port[:])), - } - - case tcp6Addr: - var ip [16]byte - if _, err := r.Read(ip[:]); err != nil { - return nil, err - } - - var port [2]byte - if _, err := r.Read(port[:]); err != nil { - return nil, err - } - - address = &net.TCPAddr{ - IP: net.IP(ip[:]), - Port: int(binary.BigEndian.Uint16(port[:])), - } - - case v2OnionAddr: - var h [tor.V2DecodedLen]byte - if _, err := r.Read(h[:]); err != nil { - return nil, err - } - - var p [2]byte - if _, err := r.Read(p[:]); err != nil { - return nil, err - } - - onionService := tor.Base32Encoding.EncodeToString(h[:]) - onionService += tor.OnionSuffix - port := int(binary.BigEndian.Uint16(p[:])) - - address = &tor.OnionAddr{ - OnionService: onionService, - Port: port, - } - - case v3OnionAddr: - var h [tor.V3DecodedLen]byte - if _, err := r.Read(h[:]); err != nil { - return nil, err - } - - var p [2]byte - if _, err := r.Read(p[:]); err != nil { - return nil, err - } - - onionService := tor.Base32Encoding.EncodeToString(h[:]) - onionService += tor.OnionSuffix - port := int(binary.BigEndian.Uint16(p[:])) - - address = &tor.OnionAddr{ - OnionService: onionService, - Port: port, - } - - case dnsAddr: - // Read the length of the hostname. - var hostLen [1]byte - if _, err := r.Read(hostLen[:]); err != nil { - return nil, err - } - - // Read the hostname. - hostname := make([]byte, hostLen[0]) - if _, err := r.Read(hostname); err != nil { - return nil, err - } - - // Read the port. - var port [2]byte - if _, err := r.Read(port[:]); err != nil { - return nil, err - } - - address = &lnwire.DNSAddress{ - Hostname: string(hostname), - Port: binary.BigEndian.Uint16(port[:]), - } - - case opaqueAddrs: - // Read the length of the payload. - var l [2]byte - if _, err := r.Read(l[:]); err != nil { - return nil, err - } - - // Read the payload. - payload := make([]byte, binary.BigEndian.Uint16(l[:])) - if _, err := r.Read(payload); err != nil { - return nil, err - } - - address = &lnwire.OpaqueAddrs{ - Payload: payload, - } - - default: - return nil, ErrUnknownAddressType - } - - return address, nil -} - -// SerializeAddr serializes an address into its raw bytes representation so that -// it can be deserialized without requiring address resolution. -func SerializeAddr(w io.Writer, address net.Addr) error { - switch addr := address.(type) { - case *net.TCPAddr: - return encodeTCPAddr(w, addr) - case *tor.OnionAddr: - return encodeOnionAddr(w, addr) - case *lnwire.OpaqueAddrs: - return encodeOpaqueAddrs(w, addr) - case *lnwire.DNSAddress: - return encodeDNSAddr(w, addr) - default: - return ErrUnknownAddressType - } -} diff --git a/graph/db/migration1/codec.go b/graph/db/migration1/codec.go deleted file mode 100644 index 3f839e7bc..000000000 --- a/graph/db/migration1/codec.go +++ /dev/null @@ -1,80 +0,0 @@ -package migration1 - -import ( - "encoding/binary" - "fmt" - "image/color" - "io" - "strconv" - - "github.com/btcsuite/btcd/wire/v2" -) - -var ( - // byteOrder defines the preferred byte order, which is Big Endian. - byteOrder = binary.BigEndian -) - -// WriteOutpoint writes an outpoint to the passed writer using the minimal -// amount of bytes possible. -func WriteOutpoint(w io.Writer, o *wire.OutPoint) error { - if _, err := w.Write(o.Hash[:]); err != nil { - return err - } - if err := binary.Write(w, byteOrder, o.Index); err != nil { - return err - } - - return nil -} - -// ReadOutpoint reads an outpoint from the passed reader that was previously -// written using the WriteOutpoint struct. -func ReadOutpoint(r io.Reader, o *wire.OutPoint) error { - if _, err := io.ReadFull(r, o.Hash[:]); err != nil { - return err - } - if err := binary.Read(r, byteOrder, &o.Index); err != nil { - return err - } - - return nil -} - -// EncodeHexColor takes a color and returns it in hex code format. -func EncodeHexColor(color color.RGBA) string { - return fmt.Sprintf("#%02x%02x%02x", color.R, color.G, color.B) -} - -// DecodeHexColor takes a hex color string like "#rrggbb" and returns a -// color.RGBA. -func DecodeHexColor(hex string) (color.RGBA, error) { - if len(hex) != 7 || hex[0] != '#' { - return color.RGBA{}, fmt.Errorf("invalid hex color string: %s", - hex) - } - - r, err := strconv.ParseUint(hex[1:3], 16, 8) - if err != nil { - return color.RGBA{}, fmt.Errorf("invalid red component: %w", - err) - } - - g, err := strconv.ParseUint(hex[3:5], 16, 8) - if err != nil { - return color.RGBA{}, fmt.Errorf("invalid green component: %w", - err) - } - - b, err := strconv.ParseUint(hex[5:7], 16, 8) - if err != nil { - return color.RGBA{}, fmt.Errorf("invalid blue component: %w", - err) - } - - return color.RGBA{ - R: uint8(r), - G: uint8(g), - B: uint8(b), - }, nil -} diff --git a/graph/db/migration1/errors.go b/graph/db/migration1/errors.go deleted file mode 100644 index 745119775..000000000 --- a/graph/db/migration1/errors.go +++ /dev/null @@ -1,84 +0,0 @@ -package migration1 - -import ( - "errors" - "fmt" -) - -var ( - // ErrEdgePolicyOptionalFieldNotFound is an error returned if a channel - // policy field is not found in the db even though its message flags - // indicate it should be. - ErrEdgePolicyOptionalFieldNotFound = fmt.Errorf("optional field not " + - "present") - - // ErrParsingExtraTLVBytes is returned when we attempt to parse - // extra opaque bytes as a TLV stream, but the parsing fails. - ErrParsingExtraTLVBytes = fmt.Errorf("error parsing extra TLV bytes") - - // ErrGraphNotFound is returned when at least one of the components of - // graph doesn't exist. - ErrGraphNotFound = fmt.Errorf("graph bucket not initialized") - - // ErrGraphNeverPruned is returned when graph was never pruned. - ErrGraphNeverPruned = fmt.Errorf("graph never pruned") - - // ErrSourceNodeNotSet is returned if the source node of the graph - // hasn't been added The source node is the center node within a - // star-graph. - ErrSourceNodeNotSet = fmt.Errorf("source node does not exist") - - // ErrGraphNodesNotFound is returned in case none of the nodes has - // been added in graph node bucket. - ErrGraphNodesNotFound = fmt.Errorf("no graph nodes exist") - - // ErrGraphNoEdgesFound is returned in case of none of the channel/edges - // has been added in graph edge bucket. - ErrGraphNoEdgesFound = fmt.Errorf("no graph edges exist") - - // ErrGraphNodeNotFound is returned when we're unable to find the target - // node. - ErrGraphNodeNotFound = fmt.Errorf("unable to find node") - - // ErrZombieEdge is an error returned when we attempt to look up an edge - // but it is marked as a zombie within the zombie index. - ErrZombieEdge = errors.New("edge marked as zombie") - - // ErrEdgeNotFound is returned when an edge for the target chanID - // can't be found. - ErrEdgeNotFound = fmt.Errorf("edge not found") - - // ErrEdgeAlreadyExist is returned when edge with specific - // channel id can't be added because it already exist. - ErrEdgeAlreadyExist = fmt.Errorf("edge already exist") - - // ErrNodeAliasNotFound is returned when alias for node can't be found. - ErrNodeAliasNotFound = fmt.Errorf("alias for node not found") - - // ErrClosedScidsNotFound is returned when the closed scid bucket - // hasn't been created. - ErrClosedScidsNotFound = fmt.Errorf("closed scid bucket doesn't exist") - - // ErrZombieEdgeNotFound is an error returned when we attempt to find an - // edge in the zombie index which is not there. - ErrZombieEdgeNotFound = errors.New("edge not found in zombie index") - - // ErrUnknownAddressType is returned when a node's addressType is not - // an expected value. - ErrUnknownAddressType = fmt.Errorf("address type cannot be resolved") - - // ErrCantCheckIfZombieEdgeStr is an error returned when we - // attempt to check if an edge is a zombie but encounter an error. - ErrCantCheckIfZombieEdgeStr = fmt.Errorf("unable to check if edge " + - "is a zombie") -) - -// ErrTooManyExtraOpaqueBytes creates an error which should be returned if the -// caller attempts to write an announcement message which bares too many extra -// opaque bytes. We limit this value in order to ensure that we don't waste -// disk space due to nodes unnecessarily padding out their announcements with -// garbage data. -func ErrTooManyExtraOpaqueBytes(numBytes int) error { - return fmt.Errorf("max allowed number of opaque bytes is %v, received "+ - "%v bytes", MaxAllowedExtraOpaqueBytes, numBytes) -} diff --git a/graph/db/migration1/interfaces.go b/graph/db/migration1/interfaces.go deleted file mode 100644 index da3f2f258..000000000 --- a/graph/db/migration1/interfaces.go +++ /dev/null @@ -1,37 +0,0 @@ -package migration1 - -import ( - "context" - - "github.com/lightningnetwork/lnd/graph/db/migration1/models" -) - -// V1Store represents the main interface for the channel graph database for all -// channels and nodes gossiped via the V1 gossip protocol as defined in BOLT 7. -type V1Store interface { - // ForEachNode iterates through all the stored vertices/nodes in the - // graph, executing the passed callback with each node encountered. If - // the callback returns an error, then the transaction is aborted and - // the iteration stops early. - ForEachNode(ctx context.Context, cb func(*models.Node) error, - reset func()) error - - // ForEachChannel iterates through all the channel edges stored within - // the graph and invokes the passed callback for each edge. The callback - // takes two edges as since this is a directed graph, both the in/out - // edges are visited. If the callback returns an error, then the - // transaction is aborted and the iteration stops early. - // - // NOTE: If an edge can't be found, or wasn't advertised, then a nil - // pointer for that particular channel edge routing policy will be - // passed into the callback. - ForEachChannel(ctx context.Context, cb func(*models.ChannelEdgeInfo, - *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error, - reset func()) error - - // SourceNode returns the source node of the graph. The source node is - // treated as the center node within a star-graph. This method may be - // used to kick off a path finding algorithm in order to explore the - // reachability of another node based off the source node. - SourceNode(ctx context.Context) (*models.Node, error) -} diff --git a/graph/db/migration1/kv_store.go b/graph/db/migration1/kv_store.go deleted file mode 100644 index c9552402a..000000000 --- a/graph/db/migration1/kv_store.go +++ /dev/null @@ -1,2154 +0,0 @@ -package migration1 - -import ( - "bytes" - "context" - "encoding/binary" - "errors" - "fmt" - "image/color" - "io" - "net" - "time" - - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/batch" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/graph/db/migration1/models" - "github.com/lightningnetwork/lnd/kvdb" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/routing/route" -) - -var ( - // nodeBucket is a bucket which houses all the vertices or nodes within - // the channel graph. This bucket has a single-sub bucket which adds an - // additional index from pubkey -> alias. Within the top-level of this - // bucket, the key space maps a node's compressed public key to the - // serialized information for that node. Additionally, there's a - // special key "source" which stores the pubkey of the source node. The - // source node is used as the starting point for all graph/queries and - // traversals. The graph is formed as a star-graph with the source node - // at the center. - // - // maps: pubKey -> nodeInfo - // maps: source -> selfPubKey - nodeBucket = []byte("graph-node") - - // nodeUpdateIndexBucket is a sub-bucket of the nodeBucket. This bucket - // will be used to quickly look up the "freshness" of a node's last - // update to the network. The bucket only contains keys, and no values, - // it's mapping: - // - // maps: updateTime || nodeID -> nil - nodeUpdateIndexBucket = []byte("graph-node-update-index") - - // sourceKey is a special key that resides within the nodeBucket. The - // sourceKey maps a key to the public key of the "self node". - sourceKey = []byte("source") - - // aliasIndexBucket is a sub-bucket that's nested within the main - // nodeBucket. This bucket maps the public key of a node to its - // current alias. This bucket is provided as it can be used within a - // future UI layer to add an additional degree of confirmation. - aliasIndexBucket = []byte("alias") - - // edgeBucket is a bucket which houses all of the edge or channel - // information within the channel graph. This bucket essentially acts - // as an adjacency list, which in conjunction with a range scan, can be - // used to iterate over all the incoming and outgoing edges for a - // particular node. Key in the bucket use a prefix scheme which leads - // with the node's public key and sends with the compact edge ID. - // For each chanID, there will be two entries within the bucket, as the - // graph is directed: nodes may have different policies w.r.t to fees - // for their respective directions. - // - // maps: pubKey || chanID -> channel edge policy for node - edgeBucket = []byte("graph-edge") - - // unknownPolicy is represented as an empty slice. It is - // used as the value in edgeBucket for unknown channel edge policies. - // Unknown policies are still stored in the database to enable efficient - // lookup of incoming channel edges. - unknownPolicy = []byte{} - - // edgeIndexBucket is an index which can be used to iterate all edges - // in the bucket, grouping them according to their in/out nodes. - // Additionally, the items in this bucket also contain the complete - // edge information for a channel. The edge information includes the - // capacity of the channel, the nodes that made the channel, etc. This - // bucket resides within the edgeBucket above. Creation of an edge - // proceeds in two phases: first the edge is added to the edge index, - // afterwards the edgeBucket can be updated with the latest details of - // the edge as they are announced on the network. - // - // maps: chanID -> pubKey1 || pubKey2 || restofEdgeInfo - edgeIndexBucket = []byte("edge-index") - - // edgeUpdateIndexBucket is a sub-bucket of the main edgeBucket. This - // bucket contains an index which allows us to gauge the "freshness" of - // a channel's last updates. - // - // maps: updateTime || chanID -> nil - edgeUpdateIndexBucket = []byte("edge-update-index") - - // channelPointBucket maps a channel's full outpoint (txid:index) to - // its short 8-byte channel ID. This bucket resides within the - // edgeBucket above, and can be used to quickly remove an edge due to - // the outpoint being spent, or to query for existence of a channel. - // - // maps: outPoint -> chanID - channelPointBucket = []byte("chan-index") - - // zombieBucket is a sub-bucket of the main edgeBucket bucket - // responsible for maintaining an index of zombie channels. Each entry - // exists within the bucket as follows: - // - // maps: chanID -> pubKey1 || pubKey2 - // - // The chanID represents the channel ID of the edge that is marked as a - // zombie and is used as the key, which maps to the public keys of the - // edge's participants. - zombieBucket = []byte("zombie-index") - - // disabledEdgePolicyBucket is a sub-bucket of the main edgeBucket - // bucket responsible for maintaining an index of disabled edge - // policies. Each entry exists within the bucket as follows: - // - // maps: -> []byte{} - // - // The chanID represents the channel ID of the edge and the direction is - // one byte representing the direction of the edge. The main purpose of - // this index is to allow pruning disabled channels in a fast way - // without the need to iterate all over the graph. - disabledEdgePolicyBucket = []byte("disabled-edge-policy-index") - - // graphMetaBucket is a top-level bucket which stores various meta-deta - // related to the on-disk channel graph. Data stored in this bucket - // includes the block to which the graph has been synced to, the total - // number of channels, etc. - graphMetaBucket = []byte("graph-meta") - - // pruneLogBucket is a bucket within the graphMetaBucket that stores - // a mapping from the block height to the hash for the blocks used to - // prune the graph. - // Once a new block is discovered, any channels that have been closed - // (by spending the outpoint) can safely be removed from the graph, and - // the block is added to the prune log. We need to keep such a log for - // the case where a reorg happens, and we must "rewind" the state of the - // graph by removing channels that were previously confirmed. In such a - // case we'll remove all entries from the prune log with a block height - // that no longer exists. - pruneLogBucket = []byte("prune-log") - - // closedScidBucket is a top-level bucket that stores scids for - // channels that we know to be closed. This is used so that we don't - // need to perform expensive validation checks if we receive a channel - // announcement for the channel again. - // - // maps: scid -> []byte{} - closedScidBucket = []byte("closed-scid") -) - -const ( - // MaxAllowedExtraOpaqueBytes is the largest amount of opaque bytes that - // we'll permit to be written to disk. We limit this as otherwise, it - // would be possible for a node to create a ton of updates and slowly - // fill our disk, and also waste bandwidth due to relaying. - MaxAllowedExtraOpaqueBytes = 10000 -) - -// KVStore is a persistent, on-disk graph representation of the Lightning -// Network. This struct can be used to implement path finding algorithms on top -// of, and also to update a node's view based on information received from the -// p2p network. Internally, the graph is stored using a modified adjacency list -// representation with some added object interaction possible with each -// serialized edge/node. The graph is stored is directed, meaning that are two -// edges stored for each channel: an inbound/outbound edge for each node pair. -// Nodes, edges, and edge information can all be added to the graph -// independently. Edge removal results in the deletion of all edge information -// for that edge. -type KVStore struct { - db kvdb.Backend -} - -// A compile-time assertion to ensure that the KVStore struct implements the -// V1Store interface. -var _ V1Store = (*KVStore)(nil) - -// NewKVStore allocates a new KVStore backed by a DB instance. The -// returned instance has its own unique reject cache and channel cache. -func NewKVStore(db kvdb.Backend) (*KVStore, error) { - if err := initKVStore(db); err != nil { - return nil, err - } - - g := &KVStore{ - db: db, - } - - return g, nil -} - -// channelMapKey is the key structure used for storing channel edge policies. -type channelMapKey struct { - nodeKey route.Vertex - chanID [8]byte -} - -// String returns a human-readable representation of the key. -func (c channelMapKey) String() string { - return fmt.Sprintf("node=%v, chanID=%x", c.nodeKey, c.chanID) -} - -// getChannelMap loads all channel edge policies from the database and stores -// them in a map. -func getChannelMap(edges kvdb.RBucket) ( - map[channelMapKey]*models.ChannelEdgePolicy, error) { - - // Create a map to store all channel edge policies. - channelMap := make(map[channelMapKey]*models.ChannelEdgePolicy) - - err := kvdb.ForAll(edges, func(k, edgeBytes []byte) error { - // Skip embedded buckets. - if bytes.Equal(k, edgeIndexBucket) || - bytes.Equal(k, edgeUpdateIndexBucket) || - bytes.Equal(k, zombieBucket) || - bytes.Equal(k, disabledEdgePolicyBucket) || - bytes.Equal(k, channelPointBucket) { - - return nil - } - - // Validate key length. - if len(k) != 33+8 { - return fmt.Errorf("invalid edge key %x encountered", k) - } - - var key channelMapKey - copy(key.nodeKey[:], k[:33]) - copy(key.chanID[:], k[33:]) - - // No need to deserialize unknown policy. - if bytes.Equal(edgeBytes, unknownPolicy) { - return nil - } - - edgeReader := bytes.NewReader(edgeBytes) - edge, err := deserializeChanEdgePolicyRaw( - edgeReader, - ) - - switch { - // If the db policy was missing an expected optional field, we - // return nil as if the policy was unknown. - case errors.Is(err, ErrEdgePolicyOptionalFieldNotFound): - return nil - - // We don't want a single policy with bad TLV data to stop us - // from loading the rest of the data, so we just skip this - // policy. This is for backwards compatibility since we did not - // use to validate TLV data in the past before persisting it. - case errors.Is(err, ErrParsingExtraTLVBytes): - return nil - - case err != nil: - return err - } - - channelMap[key] = edge - - return nil - }) - if err != nil { - return nil, err - } - - return channelMap, nil -} - -var graphTopLevelBuckets = [][]byte{ - nodeBucket, - edgeBucket, - graphMetaBucket, - closedScidBucket, -} - -// createChannelDB creates and initializes a fresh version of In -// the case that the target path has not yet been created or doesn't yet exist, -// then the path is created. Additionally, all required top-level buckets used -// within the database are created. -func initKVStore(db kvdb.Backend) error { - err := kvdb.Update(db, func(tx kvdb.RwTx) error { - for _, tlb := range graphTopLevelBuckets { - if _, err := tx.CreateTopLevelBucket(tlb); err != nil { - return err - } - } - - nodes := tx.ReadWriteBucket(nodeBucket) - _, err := nodes.CreateBucketIfNotExists(aliasIndexBucket) - if err != nil { - return err - } - _, err = nodes.CreateBucketIfNotExists(nodeUpdateIndexBucket) - if err != nil { - return err - } - - edges := tx.ReadWriteBucket(edgeBucket) - _, err = edges.CreateBucketIfNotExists(edgeIndexBucket) - if err != nil { - return err - } - _, err = edges.CreateBucketIfNotExists(edgeUpdateIndexBucket) - if err != nil { - return err - } - _, err = edges.CreateBucketIfNotExists(channelPointBucket) - if err != nil { - return err - } - _, err = edges.CreateBucketIfNotExists(zombieBucket) - if err != nil { - return err - } - - graphMeta := tx.ReadWriteBucket(graphMetaBucket) - _, err = graphMeta.CreateBucketIfNotExists(pruneLogBucket) - - return err - }, func() {}) - if err != nil { - return fmt.Errorf("unable to create new channel graph: %w", err) - } - - return nil -} - -// SourceNode returns the source node of the graph. The source node is treated -// as the center node within a star-graph. This method may be used to kick off -// a path finding algorithm in order to explore the reachability of another -// node based off the source node. -func (c *KVStore) SourceNode(_ context.Context) (*models.Node, error) { - return sourceNode(c.db) -} - -// ForEachChannel iterates through all the channel edges stored within the -// graph and invokes the passed callback for each edge. The callback takes two -// edges as since this is a directed graph, both the in/out edges are visited. -// If the callback returns an error, then the transaction is aborted and the -// iteration stops early. -// -// NOTE: If an edge can't be found, or wasn't advertised, then a nil pointer -// for that particular channel edge routing policy will be passed into the -// callback. -func (c *KVStore) ForEachChannel(_ context.Context, - cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, - *models.ChannelEdgePolicy) error, reset func()) error { - - return forEachChannel(c.db, cb, reset) -} - -// forEachChannel iterates through all the channel edges stored within the -// graph and invokes the passed callback for each edge. The callback takes two -// edges as since this is a directed graph, both the in/out edges are visited. -// If the callback returns an error, then the transaction is aborted and the -// iteration stops early. -// -// NOTE: If an edge can't be found, or wasn't advertised, then a nil pointer -// for that particular channel edge routing policy will be passed into the -// callback. -func forEachChannel(db kvdb.Backend, cb func(*models.ChannelEdgeInfo, - *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error, - reset func()) error { - - return db.View(func(tx kvdb.RTx) error { - edges := tx.ReadBucket(edgeBucket) - if edges == nil { - return ErrGraphNoEdgesFound - } - - // First, load all edges in memory indexed by node and channel - // id. - channelMap, err := getChannelMap(edges) - if err != nil { - return err - } - - edgeIndex := edges.NestedReadBucket(edgeIndexBucket) - if edgeIndex == nil { - return ErrGraphNoEdgesFound - } - - // Load edge index, recombine each channel with the policies - // loaded above and invoke the callback. - return kvdb.ForAll( - edgeIndex, func(k, edgeInfoBytes []byte) error { - var chanID [8]byte - copy(chanID[:], k) - - edgeInfoReader := bytes.NewReader(edgeInfoBytes) - info, err := deserializeChanEdgeInfo( - edgeInfoReader, - ) - if err != nil { - return err - } - - policy1 := channelMap[channelMapKey{ - nodeKey: info.NodeKey1Bytes, - chanID: chanID, - }] - - policy2 := channelMap[channelMapKey{ - nodeKey: info.NodeKey2Bytes, - chanID: chanID, - }] - - return cb(info, policy1, policy2) - }, - ) - }, reset) -} - -// ForEachNode iterates through all the stored vertices/nodes in the graph, -// executing the passed callback with each node encountered. If the callback -// returns an error, then the transaction is aborted and the iteration stops -// early. -// -// NOTE: this is part of the V1Store interface. -func (c *KVStore) ForEachNode(_ context.Context, - cb func(*models.Node) error, reset func()) error { - - return forEachNode(c.db, func(tx kvdb.RTx, - node *models.Node) error { - - return cb(node) - }, reset) -} - -// forEachNode iterates through all the stored vertices/nodes in the graph, -// executing the passed callback with each node encountered. If the callback -// returns an error, then the transaction is aborted and the iteration stops -// early. -// -// TODO(roasbeef): add iterator interface to allow for memory efficient graph -// traversal when graph gets mega. -func forEachNode(db kvdb.Backend, - cb func(kvdb.RTx, *models.Node) error, reset func()) error { - - traversal := func(tx kvdb.RTx) error { - // First grab the nodes bucket which stores the mapping from - // pubKey to node information. - nodes := tx.ReadBucket(nodeBucket) - if nodes == nil { - return ErrGraphNotFound - } - - return nodes.ForEach(func(pubKey, nodeBytes []byte) error { - // If this is the source key, then we skip this - // iteration as the value for this key is a pubKey - // rather than raw node information. - if bytes.Equal(pubKey, sourceKey) || len(pubKey) != 33 { - return nil - } - - nodeReader := bytes.NewReader(nodeBytes) - node, err := deserializeLightningNode(nodeReader) - if err != nil { - return err - } - - // Execute the callback, the transaction will abort if - // this returns an error. - return cb(tx, node) - }) - } - - return kvdb.View(db, traversal, reset) -} - -// sourceNode fetches the source node of the graph. The source node is treated -// as the center node within a star-graph. -func sourceNode(db kvdb.Backend) (*models.Node, error) { - var source *models.Node - err := kvdb.View(db, func(tx kvdb.RTx) error { - // First grab the nodes bucket which stores the mapping from - // pubKey to node information. - nodes := tx.ReadBucket(nodeBucket) - if nodes == nil { - return ErrGraphNotFound - } - - node, err := sourceNodeWithTx(nodes) - if err != nil { - return err - } - source = node - - return nil - }, func() { - source = nil - }) - if err != nil { - return nil, err - } - - return source, nil -} - -// sourceNodeWithTx uses an existing database transaction and returns the source -// node of the graph. The source node is treated as the center node within a -// star-graph. This method may be used to kick off a path finding algorithm in -// order to explore the reachability of another node based off the source node. -func sourceNodeWithTx(nodes kvdb.RBucket) (*models.Node, error) { - selfPub := nodes.Get(sourceKey) - if selfPub == nil { - return nil, ErrSourceNodeNotSet - } - - // With the pubKey of the source node retrieved, we're able to - // fetch the full node information. - return fetchLightningNode(nodes, selfPub) -} - -// SetSourceNode sets the source node within the graph database. The source -// node is to be used as the center of a star-graph within path finding -// algorithms. -func (c *KVStore) SetSourceNode(_ context.Context, - node *models.Node) error { - - nodePubBytes := node.PubKeyBytes[:] - - return kvdb.Update(c.db, func(tx kvdb.RwTx) error { - // First grab the nodes bucket which stores the mapping from - // pubKey to node information. - nodes, err := tx.CreateTopLevelBucket(nodeBucket) - if err != nil { - return err - } - - // Next we create the mapping from source to the targeted - // public key. - if err := nodes.Put(sourceKey, nodePubBytes); err != nil { - return err - } - - // Finally, we commit the information of the lightning node - // itself. - return addLightningNode(tx, node) - }, func() {}) -} - -// AddNode adds a vertex/node to the graph database. If the node is not -// in the database from before, this will add a new, unconnected one to the -// graph. If it is present from before, this will update that node's -// information. Note that this method is expected to only be called to update an -// already present node from a node announcement, or to insert a node found in a -// channel update. -// -// TODO(roasbeef): also need sig of announcement. -func (c *KVStore) AddNode(_ context.Context, - node *models.Node, _ ...batch.SchedulerOption) error { - - return kvdb.Update(c.db, func(tx kvdb.RwTx) error { - return addLightningNode(tx, node) - }, func() {}) -} - -func addLightningNode(tx kvdb.RwTx, node *models.Node) error { - nodes, err := tx.CreateTopLevelBucket(nodeBucket) - if err != nil { - return err - } - - aliases, err := nodes.CreateBucketIfNotExists(aliasIndexBucket) - if err != nil { - return err - } - - updateIndex, err := nodes.CreateBucketIfNotExists( - nodeUpdateIndexBucket, - ) - if err != nil { - return err - } - - return putLightningNode(nodes, aliases, updateIndex, node) -} - -// deleteLightningNode uses an existing database transaction to remove a -// vertex/node from the database according to the node's public key. -func (c *KVStore) deleteLightningNode(nodes kvdb.RwBucket, - compressedPubKey []byte) error { - - aliases := nodes.NestedReadWriteBucket(aliasIndexBucket) - if aliases == nil { - return ErrGraphNodesNotFound - } - - if err := aliases.Delete(compressedPubKey); err != nil { - return err - } - - // Before we delete the node, we'll fetch its current state so we can - // determine when its last update was to clear out the node update - // index. - node, err := fetchLightningNode(nodes, compressedPubKey) - if err != nil { - return err - } - - if err := nodes.Delete(compressedPubKey); err != nil { - return err - } - - // Finally, we'll delete the index entry for the node within the - // nodeUpdateIndexBucket as this node is no longer active, so we don't - // need to track its last update. - nodeUpdateIndex := nodes.NestedReadWriteBucket(nodeUpdateIndexBucket) - if nodeUpdateIndex == nil { - return ErrGraphNodesNotFound - } - - // In order to delete the entry, we'll need to reconstruct the key for - // its last update. - updateUnix := uint64(node.LastUpdate.Unix()) - var indexKey [8 + 33]byte - byteOrder.PutUint64(indexKey[:8], updateUnix) - copy(indexKey[8:], compressedPubKey) - - return nodeUpdateIndex.Delete(indexKey[:]) -} - -// AddChannelEdge adds a new (undirected, blank) edge to the graph database. An -// undirected edge from the two target nodes are created. The information stored -// denotes the static attributes of the channel, such as the channelID, the keys -// involved in creation of the channel, and the set of features that the channel -// supports. The chanPoint and chanID are used to uniquely identify the edge -// globally within the database. -func (c *KVStore) AddChannelEdge(_ context.Context, - edge *models.ChannelEdgeInfo, _ ...batch.SchedulerOption) error { - - return kvdb.Update(c.db, func(tx kvdb.RwTx) error { - return c.addChannelEdge(tx, edge) - }, func() {}) -} - -// addChannelEdge is the private form of AddChannelEdge that allows callers to -// utilize an existing db transaction. -func (c *KVStore) addChannelEdge(tx kvdb.RwTx, - edge *models.ChannelEdgeInfo) error { - - // Construct the channel's primary key which is the 8-byte channel ID. - var chanKey [8]byte - binary.BigEndian.PutUint64(chanKey[:], edge.ChannelID) - - nodes, err := tx.CreateTopLevelBucket(nodeBucket) - if err != nil { - return err - } - edges, err := tx.CreateTopLevelBucket(edgeBucket) - if err != nil { - return err - } - edgeIndex, err := edges.CreateBucketIfNotExists(edgeIndexBucket) - if err != nil { - return err - } - chanIndex, err := edges.CreateBucketIfNotExists(channelPointBucket) - if err != nil { - return err - } - - // First, attempt to check if this edge has already been created. If - // so, then we can exit early as this method is meant to be idempotent. - if edgeInfo := edgeIndex.Get(chanKey[:]); edgeInfo != nil { - return ErrEdgeAlreadyExist - } - - // Before we insert the channel into the database, we'll ensure that - // both nodes already exist in the channel graph. If either node - // doesn't, then we'll insert a "shell" node that just includes its - // public key, so subsequent validation and queries can work properly. - _, node1Err := fetchLightningNode(nodes, edge.NodeKey1Bytes[:]) - switch { - case errors.Is(node1Err, ErrGraphNodeNotFound): - err := addLightningNode( - tx, models.NewV1ShellNode(edge.NodeKey1Bytes), - ) - if err != nil { - return fmt.Errorf("unable to create shell node "+ - "for: %x: %w", edge.NodeKey1Bytes, err) - } - case node1Err != nil: - return node1Err - } - - _, node2Err := fetchLightningNode(nodes, edge.NodeKey2Bytes[:]) - switch { - case errors.Is(node2Err, ErrGraphNodeNotFound): - err := addLightningNode( - tx, models.NewV1ShellNode(edge.NodeKey2Bytes), - ) - if err != nil { - return fmt.Errorf("unable to create shell node "+ - "for: %x: %w", edge.NodeKey2Bytes, err) - } - case node2Err != nil: - return node2Err - } - - // If the edge hasn't been created yet, then we'll first add it to the - // edge index in order to associate the edge between two nodes and also - // store the static components of the channel. - if err := putChanEdgeInfo(edgeIndex, edge, chanKey); err != nil { - return err - } - - // Mark edge policies for both sides as unknown. This is to enable - // efficient incoming channel lookup for a node. - keys := []*[33]byte{ - &edge.NodeKey1Bytes, - &edge.NodeKey2Bytes, - } - for _, key := range keys { - err := putChanEdgePolicyUnknown(edges, edge.ChannelID, key[:]) - if err != nil { - return err - } - } - - // Finally we add it to the channel index which maps channel points - // (outpoints) to the shorter channel ID's. - var b bytes.Buffer - if err := WriteOutpoint(&b, &edge.ChannelPoint); err != nil { - return err - } - - return chanIndex.Put(b.Bytes(), chanKey[:]) -} - -const ( - // pruneTipBytes is the total size of the value which stores a prune - // entry of the graph in the prune log. The "prune tip" is the last - // entry in the prune log, and indicates if the channel graph is in - // sync with the current UTXO state. The structure of the value - // is: blockHash, taking 32 bytes total. - pruneTipBytes = 32 -) - -// PruneGraph prunes newly closed channels from the channel graph in response -// to a new block being solved on the network. Any transactions which spend the -// funding output of any known channels within he graph will be deleted. -// Additionally, the "prune tip", or the last block which has been used to -// prune the graph is stored so callers can ensure the graph is fully in sync -// with the current UTXO state. A slice of channels that have been closed by -// the target block along with any pruned nodes are returned if the function -// succeeds without error. -func (c *KVStore) PruneGraph(spentOutputs []*wire.OutPoint, - blockHash *chainhash.Hash, blockHeight uint32) ( - []*models.ChannelEdgeInfo, []route.Vertex, error) { - - var ( - chansClosed []*models.ChannelEdgeInfo - prunedNodes []route.Vertex - ) - - err := kvdb.Update(c.db, func(tx kvdb.RwTx) error { - // First grab the edges bucket which houses the information - // we'd like to delete - edges, err := tx.CreateTopLevelBucket(edgeBucket) - if err != nil { - return err - } - - // Next grab the two edge indexes which will also need to be - // updated. - edgeIndex, err := edges.CreateBucketIfNotExists(edgeIndexBucket) - if err != nil { - return err - } - chanIndex, err := edges.CreateBucketIfNotExists( - channelPointBucket, - ) - if err != nil { - return err - } - nodes := tx.ReadWriteBucket(nodeBucket) - if nodes == nil { - return ErrSourceNodeNotSet - } - zombieIndex, err := edges.CreateBucketIfNotExists(zombieBucket) - if err != nil { - return err - } - - // For each of the outpoints that have been spent within the - // block, we attempt to delete them from the graph as if that - // outpoint was a channel, then it has now been closed. - for _, chanPoint := range spentOutputs { - // TODO(roasbeef): load channel bloom filter, continue - // if NOT if filter - - var opBytes bytes.Buffer - err := WriteOutpoint(&opBytes, chanPoint) - if err != nil { - return err - } - - // First attempt to see if the channel exists within - // the database, if not, then we can exit early. - chanID := chanIndex.Get(opBytes.Bytes()) - if chanID == nil { - continue - } - - // Attempt to delete the channel, an ErrEdgeNotFound - // will be returned if that outpoint isn't known to be - // a channel. If no error is returned, then a channel - // was successfully pruned. - edgeInfo, err := c.delChannelEdgeUnsafe( - edges, edgeIndex, chanIndex, zombieIndex, - chanID, false, false, - ) - if err != nil && !errors.Is(err, ErrEdgeNotFound) { - return err - } - - chansClosed = append(chansClosed, edgeInfo) - } - - metaBucket, err := tx.CreateTopLevelBucket(graphMetaBucket) - if err != nil { - return err - } - - pruneBucket, err := metaBucket.CreateBucketIfNotExists( - pruneLogBucket, - ) - if err != nil { - return err - } - - // With the graph pruned, add a new entry to the prune log, - // which can be used to check if the graph is fully synced with - // the current UTXO state. - var blockHeightBytes [4]byte - byteOrder.PutUint32(blockHeightBytes[:], blockHeight) - - var newTip [pruneTipBytes]byte - copy(newTip[:], blockHash[:]) - - err = pruneBucket.Put(blockHeightBytes[:], newTip[:]) - if err != nil { - return err - } - - // Now that the graph has been pruned, we'll also attempt to - // prune any nodes that have had a channel closed within the - // latest block. - prunedNodes, err = c.pruneGraphNodes(nodes, edgeIndex) - - return err - }, func() { - chansClosed = nil - prunedNodes = nil - }) - if err != nil { - return nil, nil, err - } - - return chansClosed, prunedNodes, nil -} - -// pruneGraphNodes attempts to remove any nodes from the graph who have had a -// channel closed within the current block. If the node still has existing -// channels in the graph, this will act as a no-op. -func (c *KVStore) pruneGraphNodes(nodes kvdb.RwBucket, - edgeIndex kvdb.RwBucket) ([]route.Vertex, error) { - - log.Trace("Pruning nodes from graph with no open channels") - - // We'll retrieve the graph's source node to ensure we don't remove it - // even if it no longer has any open channels. - sourceNode, err := sourceNodeWithTx(nodes) - if err != nil { - return nil, err - } - - // We'll use this map to keep count the number of references to a node - // in the graph. A node should only be removed once it has no more - // references in the graph. - nodeRefCounts := make(map[[33]byte]int) - err = nodes.ForEach(func(pubKey, nodeBytes []byte) error { - // If this is the source key, then we skip this - // iteration as the value for this key is a pubKey - // rather than raw node information. - if bytes.Equal(pubKey, sourceKey) || len(pubKey) != 33 { - return nil - } - - var nodePub [33]byte - copy(nodePub[:], pubKey) - nodeRefCounts[nodePub] = 0 - - return nil - }) - if err != nil { - return nil, err - } - - // To ensure we never delete the source node, we'll start off by - // bumping its ref count to 1. - nodeRefCounts[sourceNode.PubKeyBytes] = 1 - - // Next, we'll run through the edgeIndex which maps a channel ID to the - // edge info. We'll use this scan to populate our reference count map - // above. - err = edgeIndex.ForEach(func(chanID, edgeInfoBytes []byte) error { - // The first 66 bytes of the edge info contain the pubkeys of - // the nodes that this edge attaches. We'll extract them, and - // add them to the ref count map. - var node1, node2 [33]byte - copy(node1[:], edgeInfoBytes[:33]) - copy(node2[:], edgeInfoBytes[33:]) - - // With the nodes extracted, we'll increase the ref count of - // each of the nodes. - nodeRefCounts[node1]++ - nodeRefCounts[node2]++ - - return nil - }) - if err != nil { - return nil, err - } - - // Finally, we'll make a second pass over the set of nodes, and delete - // any nodes that have a ref count of zero. - var pruned []route.Vertex - for nodePubKey, refCount := range nodeRefCounts { - // If the ref count of the node isn't zero, then we can safely - // skip it as it still has edges to or from it within the - // graph. - if refCount != 0 { - continue - } - - // If we reach this point, then there are no longer any edges - // that connect this node, so we can delete it. - err := c.deleteLightningNode(nodes, nodePubKey[:]) - if err != nil { - if errors.Is(err, ErrGraphNodeNotFound) || - errors.Is(err, ErrGraphNodesNotFound) { - - log.Warnf("Unable to prune node %x from the "+ - "graph: %v", nodePubKey, err) - continue - } - - return nil, err - } - - log.Infof("Pruned unconnected node %x from channel graph", - nodePubKey[:]) - - pruned = append(pruned, nodePubKey) - } - - if len(pruned) > 0 { - log.Infof("Pruned %v unconnected nodes from the channel graph", - len(pruned)) - } - - return pruned, err -} - -// PruneTip returns the block height and hash of the latest block that has been -// used to prune channels in the graph. Knowing the "prune tip" allows callers -// to tell if the graph is currently in sync with the current best known UTXO -// state. -func (c *KVStore) PruneTip() (*chainhash.Hash, uint32, error) { - var ( - tipHash chainhash.Hash - tipHeight uint32 - ) - - err := kvdb.View(c.db, func(tx kvdb.RTx) error { - graphMeta := tx.ReadBucket(graphMetaBucket) - if graphMeta == nil { - return ErrGraphNotFound - } - pruneBucket := graphMeta.NestedReadBucket(pruneLogBucket) - if pruneBucket == nil { - return ErrGraphNeverPruned - } - - pruneCursor := pruneBucket.ReadCursor() - - // The prune key with the largest block height will be our - // prune tip. - k, v := pruneCursor.Last() - if k == nil { - return ErrGraphNeverPruned - } - - // Once we have the prune tip, the value will be the block hash, - // and the key the block height. - copy(tipHash[:], v) - tipHeight = byteOrder.Uint32(k) - - return nil - }, func() {}) - if err != nil { - return nil, 0, err - } - - return &tipHash, tipHeight, nil -} - -func delEdgeUpdateIndexEntry(edgesBucket kvdb.RwBucket, chanID uint64, - edge1, edge2 *models.ChannelEdgePolicy) error { - - // First, we'll fetch the edge update index bucket which currently - // stores an entry for the channel we're about to delete. - updateIndex := edgesBucket.NestedReadWriteBucket(edgeUpdateIndexBucket) - if updateIndex == nil { - // No edges in bucket, return early. - return nil - } - - // Now that we have the bucket, we'll attempt to construct a template - // for the index key: updateTime || chanid. - var indexKey [8 + 8]byte - byteOrder.PutUint64(indexKey[8:], chanID) - - // With the template constructed, we'll attempt to delete an entry that - // would have been created by both edges: we'll alternate the update - // times, as one may had overridden the other. - if edge1 != nil { - byteOrder.PutUint64( - indexKey[:8], uint64(edge1.LastUpdate.Unix()), - ) - if err := updateIndex.Delete(indexKey[:]); err != nil { - return err - } - } - - // We'll also attempt to delete the entry that may have been created by - // the second edge. - if edge2 != nil { - byteOrder.PutUint64( - indexKey[:8], uint64(edge2.LastUpdate.Unix()), - ) - if err := updateIndex.Delete(indexKey[:]); err != nil { - return err - } - } - - return nil -} - -// delChannelEdgeUnsafe deletes the edge with the given chanID from the graph -// cache. It then goes on to delete any policy info and edge info for this -// channel from the DB and finally, if isZombie is true, it will add an entry -// for this channel in the zombie index. -// -// NOTE: this method MUST only be called if the cacheMu has already been -// acquired. -func (c *KVStore) delChannelEdgeUnsafe(edges, edgeIndex, chanIndex, - zombieIndex kvdb.RwBucket, chanID []byte, isZombie, - strictZombie bool) (*models.ChannelEdgeInfo, error) { - - edgeInfo, err := fetchChanEdgeInfo(edgeIndex, chanID) - if err != nil { - return nil, err - } - - // We'll also remove the entry in the edge update index bucket before - // we delete the edges themselves so we can access their last update - // times. - cid := byteOrder.Uint64(chanID) - edge1, edge2, err := fetchChanEdgePolicies(edgeIndex, edges, chanID) - if err != nil { - return nil, err - } - err = delEdgeUpdateIndexEntry(edges, cid, edge1, edge2) - if err != nil { - return nil, err - } - - // The edge key is of the format pubKey || chanID. First we construct - // the latter half, populating the channel ID. - var edgeKey [33 + 8]byte - copy(edgeKey[33:], chanID) - - // With the latter half constructed, copy over the first public key to - // delete the edge in this direction, then the second to delete the - // edge in the opposite direction. - copy(edgeKey[:33], edgeInfo.NodeKey1Bytes[:]) - if edges.Get(edgeKey[:]) != nil { - if err := edges.Delete(edgeKey[:]); err != nil { - return nil, err - } - } - copy(edgeKey[:33], edgeInfo.NodeKey2Bytes[:]) - if edges.Get(edgeKey[:]) != nil { - if err := edges.Delete(edgeKey[:]); err != nil { - return nil, err - } - } - - // As part of deleting the edge we also remove all disabled entries - // from the edgePolicyDisabledIndex bucket. We do that for both - // directions. - err = updateEdgePolicyDisabledIndex(edges, cid, false, false) - if err != nil { - return nil, err - } - err = updateEdgePolicyDisabledIndex(edges, cid, true, false) - if err != nil { - return nil, err - } - - // With the edge data deleted, we can purge the information from the two - // edge indexes. - if err := edgeIndex.Delete(chanID); err != nil { - return nil, err - } - var b bytes.Buffer - if err := WriteOutpoint(&b, &edgeInfo.ChannelPoint); err != nil { - return nil, err - } - if err := chanIndex.Delete(b.Bytes()); err != nil { - return nil, err - } - - // Finally, we'll mark the edge as a zombie within our index if it's - // being removed due to the channel becoming a zombie. We do this to - // ensure we don't store unnecessary data for spent channels. - if !isZombie { - return edgeInfo, nil - } - - nodeKey1, nodeKey2 := edgeInfo.NodeKey1Bytes, edgeInfo.NodeKey2Bytes - if strictZombie { - var e1UpdateTime, e2UpdateTime *time.Time - if edge1 != nil { - e1UpdateTime = &edge1.LastUpdate - } - if edge2 != nil { - e2UpdateTime = &edge2.LastUpdate - } - - nodeKey1, nodeKey2 = makeZombiePubkeys( - edgeInfo.NodeKey1Bytes, edgeInfo.NodeKey2Bytes, - e1UpdateTime, e2UpdateTime, - ) - } - - return edgeInfo, markEdgeZombie( - zombieIndex, byteOrder.Uint64(chanID), nodeKey1, nodeKey2, - ) -} - -// makeZombiePubkeys derives the node pubkeys to store in the zombie index for a -// particular pair of channel policies. The return values are one of: -// 1. (pubkey1, pubkey2) -// 2. (pubkey1, blank) -// 3. (blank, pubkey2) -// -// A blank pubkey means that corresponding node will be unable to resurrect a -// channel on its own. For example, node1 may continue to publish recent -// updates, but node2 has fallen way behind. After marking an edge as a zombie, -// we don't want another fresh update from node1 to resurrect, as the edge can -// only become live once node2 finally sends something recent. -// -// In the case where we have neither update, we allow either party to resurrect -// the channel. If the channel were to be marked zombie again, it would be -// marked with the correct lagging channel since we received an update from only -// one side. -func makeZombiePubkeys(node1, node2 [33]byte, e1, e2 *time.Time) ([33]byte, - [33]byte) { - - switch { - // If we don't have either edge policy, we'll return both pubkeys so - // that the channel can be resurrected by either party. - case e1 == nil && e2 == nil: - return node1, node2 - - // If we're missing edge1, or if both edges are present but edge1 is - // older, we'll return edge1's pubkey and a blank pubkey for edge2. This - // means that only an update from edge1 will be able to resurrect the - // channel. - case e1 == nil || (e2 != nil && e1.Before(*e2)): - return node1, [33]byte{} - - // Otherwise, we're missing edge2 or edge2 is the older side, so we - // return a blank pubkey for edge1. In this case, only an update from - // edge2 can resurect the channel. - default: - return [33]byte{}, node1 - } -} - -// UpdateEdgePolicy updates the edge routing policy for a single directed edge -// within the database for the referenced channel. The `flags` attribute within -// the ChannelEdgePolicy determines which of the directed edges are being -// updated. If the flag is 1, then the first node's information is being -// updated, otherwise it's the second node's information. The node ordering is -// determined by the lexicographical ordering of the identity public keys of the -// nodes on either side of the channel. -func (c *KVStore) UpdateEdgePolicy(_ context.Context, - edge *models.ChannelEdgePolicy, - _ ...batch.SchedulerOption) (route.Vertex, route.Vertex, error) { - - var from, to route.Vertex - err := kvdb.Update(c.db, func(tx kvdb.RwTx) error { - // Validate that the ExtraOpaqueData is in fact a valid - // TLV stream. This is done here instead of within - // updateEdgePolicy so that updateEdgePolicy can be used - // by unit tests to recreate the case where we already - // have nodes persisted with invalid TLV data. - err := edge.ExtraOpaqueData.ValidateTLV() - if err != nil { - return fmt.Errorf("%w: %w", - ErrParsingExtraTLVBytes, err) - } - - from, to, _, err = updateEdgePolicy(tx, edge) - - return err - }, func() {}) - - return from, to, err -} - -// updateEdgePolicy attempts to update an edge's policy within the relevant -// buckets using an existing database transaction. The returned boolean will be -// true if the updated policy belongs to node1, and false if the policy belonged -// to node2. -func updateEdgePolicy(tx kvdb.RwTx, edge *models.ChannelEdgePolicy) ( - route.Vertex, route.Vertex, bool, error) { - - var noVertex route.Vertex - - edges := tx.ReadWriteBucket(edgeBucket) - if edges == nil { - return noVertex, noVertex, false, ErrEdgeNotFound - } - edgeIndex := edges.NestedReadWriteBucket(edgeIndexBucket) - if edgeIndex == nil { - return noVertex, noVertex, false, ErrEdgeNotFound - } - - // Create the channelID key be converting the channel ID - // integer into a byte slice. - var chanID [8]byte - byteOrder.PutUint64(chanID[:], edge.ChannelID) - - // With the channel ID, we then fetch the value storing the two - // nodes which connect this channel edge. - nodeInfo := edgeIndex.Get(chanID[:]) - if nodeInfo == nil { - return noVertex, noVertex, false, ErrEdgeNotFound - } - - // Depending on the flags value passed above, either the first - // or second edge policy is being updated. - var fromNode, toNode []byte - var isUpdate1 bool - if edge.ChannelFlags&lnwire.ChanUpdateDirection == 0 { - fromNode = nodeInfo[:33] - toNode = nodeInfo[33:66] - isUpdate1 = true - } else { - fromNode = nodeInfo[33:66] - toNode = nodeInfo[:33] - isUpdate1 = false - } - - // Finally, with the direction of the edge being updated - // identified, we update the on-disk edge representation. - err := putChanEdgePolicy(edges, edge, fromNode, toNode) - if err != nil { - return noVertex, noVertex, false, err - } - - var ( - fromNodePubKey route.Vertex - toNodePubKey route.Vertex - ) - copy(fromNodePubKey[:], fromNode) - copy(toNodePubKey[:], toNode) - - return fromNodePubKey, toNodePubKey, isUpdate1, nil -} - -// MarkEdgeZombie attempts to mark a channel identified by its channel ID as a -// zombie. This method is used on an ad-hoc basis, when channels need to be -// marked as zombies outside the normal pruning cycle. -func (c *KVStore) MarkEdgeZombie(chanID uint64, - pubKey1, pubKey2 [33]byte) error { - - err := kvdb.Batch(c.db, func(tx kvdb.RwTx) error { - edges := tx.ReadWriteBucket(edgeBucket) - if edges == nil { - return ErrGraphNoEdgesFound - } - zombieIndex, err := edges.CreateBucketIfNotExists(zombieBucket) - if err != nil { - return fmt.Errorf("unable to create zombie "+ - "bucket: %w", err) - } - - return markEdgeZombie(zombieIndex, chanID, pubKey1, pubKey2) - }) - if err != nil { - return err - } - - return nil -} - -// markEdgeZombie marks an edge as a zombie within our zombie index. The public -// keys should represent the node public keys of the two parties involved in the -// edge. -func markEdgeZombie(zombieIndex kvdb.RwBucket, chanID uint64, pubKey1, - pubKey2 [33]byte) error { - - var k [8]byte - byteOrder.PutUint64(k[:], chanID) - - var v [66]byte - copy(v[:33], pubKey1[:]) - copy(v[33:], pubKey2[:]) - - return zombieIndex.Put(k[:], v[:]) -} - -// PutClosedScid stores a SCID for a closed channel in the database. This is so -// that we can ignore channel announcements that we know to be closed without -// having to validate them and fetch a block. -func (c *KVStore) PutClosedScid(scid lnwire.ShortChannelID) error { - return kvdb.Update(c.db, func(tx kvdb.RwTx) error { - closedScids, err := tx.CreateTopLevelBucket(closedScidBucket) - if err != nil { - return err - } - - var k [8]byte - byteOrder.PutUint64(k[:], scid.ToUint64()) - - return closedScids.Put(k[:], []byte{}) - }, func() {}) -} - -func putLightningNode(nodeBucket, aliasBucket, updateIndex kvdb.RwBucket, - node *models.Node) error { - - var ( - scratch [16]byte - b bytes.Buffer - ) - - pub, err := node.PubKey() - if err != nil { - return err - } - nodePub := pub.SerializeCompressed() - - // If the node has the update time set, write it, else write 0. - updateUnix := uint64(0) - if node.LastUpdate.Unix() > 0 { - updateUnix = uint64(node.LastUpdate.Unix()) - } - - byteOrder.PutUint64(scratch[:8], updateUnix) - if _, err := b.Write(scratch[:8]); err != nil { - return err - } - - if _, err := b.Write(nodePub); err != nil { - return err - } - - // If we got a node announcement for this node, we will have the rest - // of the data available. If not we don't have more data to write. - if !node.HaveAnnouncement() { - // Write HaveNodeAnnouncement=0. - byteOrder.PutUint16(scratch[:2], 0) - if _, err := b.Write(scratch[:2]); err != nil { - return err - } - - return nodeBucket.Put(nodePub, b.Bytes()) - } - - // Write HaveNodeAnnouncement=1. - byteOrder.PutUint16(scratch[:2], 1) - if _, err := b.Write(scratch[:2]); err != nil { - return err - } - - nodeColor := node.Color.UnwrapOr(color.RGBA{}) - - if err := binary.Write(&b, byteOrder, nodeColor.R); err != nil { - return err - } - if err := binary.Write(&b, byteOrder, nodeColor.G); err != nil { - return err - } - if err := binary.Write(&b, byteOrder, nodeColor.B); err != nil { - return err - } - - err = wire.WriteVarString(&b, 0, node.Alias.UnwrapOr("")) - if err != nil { - return err - } - - if err := node.Features.Encode(&b); err != nil { - return err - } - - numAddresses := uint16(len(node.Addresses)) - byteOrder.PutUint16(scratch[:2], numAddresses) - if _, err := b.Write(scratch[:2]); err != nil { - return err - } - - for _, address := range node.Addresses { - if err := SerializeAddr(&b, address); err != nil { - return err - } - } - - sigLen := len(node.AuthSigBytes) - if sigLen > 80 { - return fmt.Errorf("max sig len allowed is 80, had %v", - sigLen) - } - - err = wire.WriteVarBytes(&b, 0, node.AuthSigBytes) - if err != nil { - return err - } - - if len(node.ExtraOpaqueData) > MaxAllowedExtraOpaqueBytes { - return ErrTooManyExtraOpaqueBytes(len(node.ExtraOpaqueData)) - } - err = wire.WriteVarBytes(&b, 0, node.ExtraOpaqueData) - if err != nil { - return err - } - - err = aliasBucket.Put(nodePub, []byte(node.Alias.UnwrapOr(""))) - if err != nil { - return err - } - - // With the alias bucket updated, we'll now update the index that - // tracks the time series of node updates. - var indexKey [8 + 33]byte - byteOrder.PutUint64(indexKey[:8], updateUnix) - copy(indexKey[8:], nodePub) - - // If there was already an old index entry for this node, then we'll - // delete the old one before we write the new entry. - if nodeBytes := nodeBucket.Get(nodePub); nodeBytes != nil { - // Extract out the old update time to we can reconstruct the - // prior index key to delete it from the index. - oldUpdateTime := nodeBytes[:8] - - var oldIndexKey [8 + 33]byte - copy(oldIndexKey[:8], oldUpdateTime) - copy(oldIndexKey[8:], nodePub) - - if err := updateIndex.Delete(oldIndexKey[:]); err != nil { - return err - } - } - - if err := updateIndex.Put(indexKey[:], nil); err != nil { - return err - } - - return nodeBucket.Put(nodePub, b.Bytes()) -} - -func fetchLightningNode(nodeBucket kvdb.RBucket, - nodePub []byte) (*models.Node, error) { - - nodeBytes := nodeBucket.Get(nodePub) - if nodeBytes == nil { - return nil, ErrGraphNodeNotFound - } - - nodeReader := bytes.NewReader(nodeBytes) - - return deserializeLightningNode(nodeReader) -} - -func deserializeLightningNode(r io.Reader) (*models.Node, error) { - var ( - scratch [8]byte - err error - pubKey [33]byte - ) - - if _, err := r.Read(scratch[:]); err != nil { - return nil, err - } - - unix := int64(byteOrder.Uint64(scratch[:])) - lastUpdate := time.Unix(unix, 0) - - if _, err := io.ReadFull(r, pubKey[:]); err != nil { - return nil, err - } - - node := models.NewV1ShellNode(pubKey) - node.LastUpdate = lastUpdate - - if _, err := r.Read(scratch[:2]); err != nil { - return nil, err - } - - hasNodeAnn := byteOrder.Uint16(scratch[:2]) - // The rest of the data is optional, and will only be there if we got a - // node announcement for this node. - if hasNodeAnn == 0 { - return node, nil - } - - // We did get a node announcement for this node, so we'll have the rest - // of the data available. - var nodeColor color.RGBA - if err := binary.Read(r, byteOrder, &nodeColor.R); err != nil { - return nil, err - } - if err := binary.Read(r, byteOrder, &nodeColor.G); err != nil { - return nil, err - } - if err := binary.Read(r, byteOrder, &nodeColor.B); err != nil { - return nil, err - } - node.Color = fn.Some(nodeColor) - - alias, err := wire.ReadVarString(r, 0) - if err != nil { - return nil, err - } - node.Alias = fn.Some(alias) - - err = node.Features.Decode(r) - if err != nil { - return nil, err - } - - if _, err := r.Read(scratch[:2]); err != nil { - return nil, err - } - numAddresses := int(byteOrder.Uint16(scratch[:2])) - - var addresses []net.Addr - for i := 0; i < numAddresses; i++ { - address, err := DeserializeAddr(r) - if err != nil { - return nil, err - } - addresses = append(addresses, address) - } - node.Addresses = addresses - - node.AuthSigBytes, err = wire.ReadVarBytes(r, 0, 80, "sig") - if err != nil { - return nil, err - } - - // We'll try and see if there are any opaque bytes left, if not, then - // we'll ignore the EOF error and return the node as is. - extraBytes, err := wire.ReadVarBytes( - r, 0, MaxAllowedExtraOpaqueBytes, "blob", - ) - switch { - case errors.Is(err, io.ErrUnexpectedEOF): - case errors.Is(err, io.EOF): - case err != nil: - return nil, err - } - - if len(extraBytes) > 0 { - node.ExtraOpaqueData = extraBytes - } - - return node, nil -} - -func putChanEdgeInfo(edgeIndex kvdb.RwBucket, - edgeInfo *models.ChannelEdgeInfo, chanID [8]byte) error { - - var b bytes.Buffer - - if _, err := b.Write(edgeInfo.NodeKey1Bytes[:]); err != nil { - return err - } - if _, err := b.Write(edgeInfo.NodeKey2Bytes[:]); err != nil { - return err - } - if _, err := b.Write(edgeInfo.BitcoinKey1Bytes[:]); err != nil { - return err - } - if _, err := b.Write(edgeInfo.BitcoinKey2Bytes[:]); err != nil { - return err - } - - var featureBuf bytes.Buffer - if err := edgeInfo.Features.Encode(&featureBuf); err != nil { - return fmt.Errorf("unable to encode features: %w", err) - } - - if err := wire.WriteVarBytes(&b, 0, featureBuf.Bytes()); err != nil { - return err - } - - authProof := edgeInfo.AuthProof - var nodeSig1, nodeSig2, bitcoinSig1, bitcoinSig2 []byte - if authProof != nil { - nodeSig1 = authProof.NodeSig1Bytes - nodeSig2 = authProof.NodeSig2Bytes - bitcoinSig1 = authProof.BitcoinSig1Bytes - bitcoinSig2 = authProof.BitcoinSig2Bytes - } - - if err := wire.WriteVarBytes(&b, 0, nodeSig1); err != nil { - return err - } - if err := wire.WriteVarBytes(&b, 0, nodeSig2); err != nil { - return err - } - if err := wire.WriteVarBytes(&b, 0, bitcoinSig1); err != nil { - return err - } - if err := wire.WriteVarBytes(&b, 0, bitcoinSig2); err != nil { - return err - } - - if err := WriteOutpoint(&b, &edgeInfo.ChannelPoint); err != nil { - return err - } - err := binary.Write(&b, byteOrder, uint64(edgeInfo.Capacity)) - if err != nil { - return err - } - if _, err := b.Write(chanID[:]); err != nil { - return err - } - if _, err := b.Write(edgeInfo.ChainHash[:]); err != nil { - return err - } - - if len(edgeInfo.ExtraOpaqueData) > MaxAllowedExtraOpaqueBytes { - return ErrTooManyExtraOpaqueBytes(len(edgeInfo.ExtraOpaqueData)) - } - err = wire.WriteVarBytes(&b, 0, edgeInfo.ExtraOpaqueData) - if err != nil { - return err - } - - return edgeIndex.Put(chanID[:], b.Bytes()) -} - -func fetchChanEdgeInfo(edgeIndex kvdb.RBucket, - chanID []byte) (*models.ChannelEdgeInfo, error) { - - edgeInfoBytes := edgeIndex.Get(chanID) - if edgeInfoBytes == nil { - return nil, ErrEdgeNotFound - } - - edgeInfoReader := bytes.NewReader(edgeInfoBytes) - - return deserializeChanEdgeInfo(edgeInfoReader) -} - -func deserializeChanEdgeInfo(r io.Reader) (*models.ChannelEdgeInfo, error) { - var ( - err error - edgeInfo models.ChannelEdgeInfo - ) - - if _, err := io.ReadFull(r, edgeInfo.NodeKey1Bytes[:]); err != nil { - return nil, err - } - if _, err := io.ReadFull(r, edgeInfo.NodeKey2Bytes[:]); err != nil { - return nil, err - } - if _, err := io.ReadFull(r, edgeInfo.BitcoinKey1Bytes[:]); err != nil { - return nil, err - } - if _, err := io.ReadFull(r, edgeInfo.BitcoinKey2Bytes[:]); err != nil { - return nil, err - } - - featureBytes, err := wire.ReadVarBytes(r, 0, 900, "features") - if err != nil { - return nil, err - } - - features := lnwire.NewRawFeatureVector() - err = features.Decode(bytes.NewReader(featureBytes)) - if err != nil { - return nil, fmt.Errorf("unable to decode "+ - "features: %w", err) - } - edgeInfo.Features = lnwire.NewFeatureVector(features, lnwire.Features) - - proof := &models.ChannelAuthProof{} - - proof.NodeSig1Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs") - if err != nil { - return nil, err - } - proof.NodeSig2Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs") - if err != nil { - return nil, err - } - proof.BitcoinSig1Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs") - if err != nil { - return nil, err - } - proof.BitcoinSig2Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs") - if err != nil { - return nil, err - } - - if !proof.IsEmpty() { - edgeInfo.AuthProof = proof - } - - edgeInfo.ChannelPoint = wire.OutPoint{} - if err := ReadOutpoint(r, &edgeInfo.ChannelPoint); err != nil { - return nil, err - } - if err := binary.Read(r, byteOrder, &edgeInfo.Capacity); err != nil { - return nil, err - } - if err := binary.Read(r, byteOrder, &edgeInfo.ChannelID); err != nil { - return nil, err - } - - if _, err := io.ReadFull(r, edgeInfo.ChainHash[:]); err != nil { - return nil, err - } - - // We'll try and see if there are any opaque bytes left, if not, then - // we'll ignore the EOF error and return the edge as is. - edgeInfo.ExtraOpaqueData, err = wire.ReadVarBytes( - r, 0, MaxAllowedExtraOpaqueBytes, "blob", - ) - switch { - case errors.Is(err, io.ErrUnexpectedEOF): - case errors.Is(err, io.EOF): - case err != nil: - return nil, err - } - - return &edgeInfo, nil -} - -func putChanEdgePolicy(edges kvdb.RwBucket, edge *models.ChannelEdgePolicy, - from, to []byte) error { - - var edgeKey [33 + 8]byte - copy(edgeKey[:], from) - byteOrder.PutUint64(edgeKey[33:], edge.ChannelID) - - var b bytes.Buffer - if err := serializeChanEdgePolicy(&b, edge, to); err != nil { - return err - } - - // Before we write out the new edge, we'll create a new entry in the - // update index in order to keep it fresh. - updateUnix := uint64(edge.LastUpdate.Unix()) - var indexKey [8 + 8]byte - byteOrder.PutUint64(indexKey[:8], updateUnix) - byteOrder.PutUint64(indexKey[8:], edge.ChannelID) - - updateIndex, err := edges.CreateBucketIfNotExists(edgeUpdateIndexBucket) - if err != nil { - return err - } - - // If there was already an entry for this edge, then we'll need to - // delete the old one to ensure we don't leave around any after-images. - // An unknown policy value does not have a update time recorded, so - // it also does not need to be removed. - if edgeBytes := edges.Get(edgeKey[:]); edgeBytes != nil && - !bytes.Equal(edgeBytes, unknownPolicy) { - - // In order to delete the old entry, we'll need to obtain the - // *prior* update time in order to delete it. To do this, we'll - // need to deserialize the existing policy within the database - // (now outdated by the new one), and delete its corresponding - // entry within the update index. We'll ignore any - // ErrEdgePolicyOptionalFieldNotFound or ErrParsingExtraTLVBytes - // errors, as we only need the channel ID and update time to - // delete the entry. - // - // TODO(halseth): get rid of these invalid policies in a - // migration. - // - // NOTE: the above TODO was completed in the SQL migration and - // so such edge cases no longer need to be handled there. - oldEdgePolicy, err := deserializeChanEdgePolicy( - bytes.NewReader(edgeBytes), - ) - if err != nil && - !errors.Is(err, ErrEdgePolicyOptionalFieldNotFound) && - !errors.Is(err, ErrParsingExtraTLVBytes) { - - return err - } - - oldUpdateTime := uint64(oldEdgePolicy.LastUpdate.Unix()) - - var oldIndexKey [8 + 8]byte - byteOrder.PutUint64(oldIndexKey[:8], oldUpdateTime) - byteOrder.PutUint64(oldIndexKey[8:], edge.ChannelID) - - if err := updateIndex.Delete(oldIndexKey[:]); err != nil { - return err - } - } - - if err := updateIndex.Put(indexKey[:], nil); err != nil { - return err - } - - err = updateEdgePolicyDisabledIndex( - edges, edge.ChannelID, - edge.ChannelFlags&lnwire.ChanUpdateDirection > 0, - edge.IsDisabled(), - ) - if err != nil { - return err - } - - return edges.Put(edgeKey[:], b.Bytes()) -} - -// updateEdgePolicyDisabledIndex is used to update the disabledEdgePolicyIndex -// bucket by either add a new disabled ChannelEdgePolicy or remove an existing -// one. -// The direction represents the direction of the edge and disabled is used for -// deciding whether to remove or add an entry to the bucket. -// In general a channel is disabled if two entries for the same chanID exist -// in this bucket. -// Maintaining the bucket this way allows a fast retrieval of disabled -// channels, for example when prune is needed. -func updateEdgePolicyDisabledIndex(edges kvdb.RwBucket, chanID uint64, - direction bool, disabled bool) error { - - var disabledEdgeKey [8 + 1]byte - byteOrder.PutUint64(disabledEdgeKey[0:], chanID) - if direction { - disabledEdgeKey[8] = 1 - } - - disabledEdgePolicyIndex, err := edges.CreateBucketIfNotExists( - disabledEdgePolicyBucket, - ) - if err != nil { - return err - } - - if disabled { - return disabledEdgePolicyIndex.Put(disabledEdgeKey[:], []byte{}) - } - - return disabledEdgePolicyIndex.Delete(disabledEdgeKey[:]) -} - -// putChanEdgePolicyUnknown marks the edge policy as unknown -// in the edges bucket. -func putChanEdgePolicyUnknown(edges kvdb.RwBucket, channelID uint64, - from []byte) error { - - var edgeKey [33 + 8]byte - copy(edgeKey[:], from) - byteOrder.PutUint64(edgeKey[33:], channelID) - - if edges.Get(edgeKey[:]) != nil { - return fmt.Errorf("cannot write unknown policy for channel %v "+ - " when there is already a policy present", channelID) - } - - return edges.Put(edgeKey[:], unknownPolicy) -} - -func fetchChanEdgePolicy(edges kvdb.RBucket, chanID []byte, - nodePub []byte) (*models.ChannelEdgePolicy, error) { - - var edgeKey [33 + 8]byte - copy(edgeKey[:], nodePub) - copy(edgeKey[33:], chanID) - - edgeBytes := edges.Get(edgeKey[:]) - if edgeBytes == nil { - return nil, ErrEdgeNotFound - } - - // No need to deserialize unknown policy. - if bytes.Equal(edgeBytes, unknownPolicy) { - return nil, nil - } - - edgeReader := bytes.NewReader(edgeBytes) - - ep, err := deserializeChanEdgePolicy(edgeReader) - switch { - // If the db policy was missing an expected optional field, we return - // nil as if the policy was unknown. - case errors.Is(err, ErrEdgePolicyOptionalFieldNotFound): - return nil, nil - - // If the policy contains invalid TLV bytes, we return nil as if - // the policy was unknown. - case errors.Is(err, ErrParsingExtraTLVBytes): - return nil, nil - - case err != nil: - return nil, err - } - - return ep, nil -} - -func fetchChanEdgePolicies(edgeIndex kvdb.RBucket, edges kvdb.RBucket, - chanID []byte) (*models.ChannelEdgePolicy, *models.ChannelEdgePolicy, - error) { - - edgeInfo := edgeIndex.Get(chanID) - if edgeInfo == nil { - return nil, nil, fmt.Errorf("%w: chanID=%x", ErrEdgeNotFound, - chanID) - } - - // The first node is contained within the first half of the edge - // information. We only propagate the error here and below if it's - // something other than edge non-existence. - node1Pub := edgeInfo[:33] - edge1, err := fetchChanEdgePolicy(edges, chanID, node1Pub) - if err != nil { - return nil, nil, fmt.Errorf("%w: node1Pub=%x", ErrEdgeNotFound, - node1Pub) - } - - // Similarly, the second node is contained within the latter - // half of the edge information. - node2Pub := edgeInfo[33:66] - edge2, err := fetchChanEdgePolicy(edges, chanID, node2Pub) - if err != nil { - return nil, nil, fmt.Errorf("%w: node2Pub=%x", ErrEdgeNotFound, - node2Pub) - } - - return edge1, edge2, nil -} - -func serializeChanEdgePolicy(w io.Writer, edge *models.ChannelEdgePolicy, - to []byte) error { - - err := wire.WriteVarBytes(w, 0, edge.SigBytes) - if err != nil { - return err - } - - if err := binary.Write(w, byteOrder, edge.ChannelID); err != nil { - return err - } - - var scratch [8]byte - updateUnix := uint64(edge.LastUpdate.Unix()) - byteOrder.PutUint64(scratch[:], updateUnix) - if _, err := w.Write(scratch[:]); err != nil { - return err - } - - if err := binary.Write(w, byteOrder, edge.MessageFlags); err != nil { - return err - } - if err := binary.Write(w, byteOrder, edge.ChannelFlags); err != nil { - return err - } - if err := binary.Write(w, byteOrder, edge.TimeLockDelta); err != nil { - return err - } - if err := binary.Write(w, byteOrder, uint64(edge.MinHTLC)); err != nil { - return err - } - err = binary.Write(w, byteOrder, uint64(edge.FeeBaseMSat)) - if err != nil { - return err - } - err = binary.Write( - w, byteOrder, uint64(edge.FeeProportionalMillionths), - ) - if err != nil { - return err - } - - if _, err := w.Write(to); err != nil { - return err - } - - // If the max_htlc field is present, we write it. To be compatible with - // older versions that wasn't aware of this field, we write it as part - // of the opaque data. - // TODO(halseth): clean up when moving to TLV. - var opaqueBuf bytes.Buffer - if edge.MessageFlags.HasMaxHtlc() { - err := binary.Write(&opaqueBuf, byteOrder, uint64(edge.MaxHTLC)) - if err != nil { - return err - } - } - - if len(edge.ExtraOpaqueData) > MaxAllowedExtraOpaqueBytes { - return ErrTooManyExtraOpaqueBytes(len(edge.ExtraOpaqueData)) - } - if _, err := opaqueBuf.Write(edge.ExtraOpaqueData); err != nil { - return err - } - - if err := wire.WriteVarBytes(w, 0, opaqueBuf.Bytes()); err != nil { - return err - } - - return nil -} - -func deserializeChanEdgePolicy(r io.Reader) (*models.ChannelEdgePolicy, error) { - // Deserialize the policy. Note that in case an optional field is not - // found or if the edge has invalid TLV data, then both an error and a - // populated policy object are returned so that the caller can decide - // if it still wants to use the edge or not. - edge, err := deserializeChanEdgePolicyRaw(r) - if err != nil && - !errors.Is(err, ErrEdgePolicyOptionalFieldNotFound) && - !errors.Is(err, ErrParsingExtraTLVBytes) { - - return nil, err - } - - return edge, err -} - -func deserializeChanEdgePolicyRaw(r io.Reader) (*models.ChannelEdgePolicy, - error) { - - edge := &models.ChannelEdgePolicy{} - - var err error - edge.SigBytes, err = wire.ReadVarBytes(r, 0, 80, "sig") - if err != nil { - return nil, err - } - - if err := binary.Read(r, byteOrder, &edge.ChannelID); err != nil { - return nil, err - } - - var scratch [8]byte - if _, err := r.Read(scratch[:]); err != nil { - return nil, err - } - unix := int64(byteOrder.Uint64(scratch[:])) - edge.LastUpdate = time.Unix(unix, 0) - - if err := binary.Read(r, byteOrder, &edge.MessageFlags); err != nil { - return nil, err - } - if err := binary.Read(r, byteOrder, &edge.ChannelFlags); err != nil { - return nil, err - } - if err := binary.Read(r, byteOrder, &edge.TimeLockDelta); err != nil { - return nil, err - } - - var n uint64 - if err := binary.Read(r, byteOrder, &n); err != nil { - return nil, err - } - edge.MinHTLC = lnwire.MilliSatoshi(n) - - if err := binary.Read(r, byteOrder, &n); err != nil { - return nil, err - } - edge.FeeBaseMSat = lnwire.MilliSatoshi(n) - - if err := binary.Read(r, byteOrder, &n); err != nil { - return nil, err - } - edge.FeeProportionalMillionths = lnwire.MilliSatoshi(n) - - if _, err := r.Read(edge.ToNode[:]); err != nil { - return nil, err - } - - // We'll try and see if there are any opaque bytes left, if not, then - // we'll ignore the EOF error and return the edge as is. - edge.ExtraOpaqueData, err = wire.ReadVarBytes( - r, 0, MaxAllowedExtraOpaqueBytes, "blob", - ) - switch { - case errors.Is(err, io.ErrUnexpectedEOF): - case errors.Is(err, io.EOF): - case err != nil: - return nil, err - } - - // See if optional fields are present. - if edge.MessageFlags.HasMaxHtlc() { - // The max_htlc field should be at the beginning of the opaque - // bytes. - opq := edge.ExtraOpaqueData - - // If the max_htlc field is not present, it might be old data - // stored before this field was validated. We'll return the - // edge along with an error. - if len(opq) < 8 { - return edge, ErrEdgePolicyOptionalFieldNotFound - } - - maxHtlc := byteOrder.Uint64(opq[:8]) - edge.MaxHTLC = lnwire.MilliSatoshi(maxHtlc) - - // Exclude the parsed field from the rest of the opaque data. - edge.ExtraOpaqueData = opq[8:] - } - - // Attempt to extract the inbound fee from the opaque data. If we fail - // to parse the TLV here, we return an error we also return the edge - // so that the caller can still use it. This is for backwards - // compatibility in case we have already persisted some policies that - // have invalid TLV data. - var inboundFee lnwire.Fee - typeMap, err := edge.ExtraOpaqueData.ExtractRecords(&inboundFee) - if err != nil { - return edge, fmt.Errorf("%w: %w", ErrParsingExtraTLVBytes, err) - } - - val, ok := typeMap[lnwire.FeeRecordType] - if ok && val == nil { - edge.InboundFee = fn.Some(inboundFee) - } - - return edge, nil -} diff --git a/graph/db/migration1/log.go b/graph/db/migration1/log.go deleted file mode 100644 index d0814e154..000000000 --- a/graph/db/migration1/log.go +++ /dev/null @@ -1,31 +0,0 @@ -package migration1 - -import ( - "github.com/btcsuite/btclog/v2" - "github.com/lightningnetwork/lnd/build" -) - -// Subsystem defines the logging code for this subsystem. -const Subsystem = "GRDB" - -// log is a logger that is initialized with no output filters. This -// means the package will not perform any logging by default until the caller -// requests it. -var log btclog.Logger - -func init() { - UseLogger(build.NewSubLogger(Subsystem, nil)) -} - -// DisableLog disables all library log output. Logging output is disabled -// by default until UseLogger is called. -func DisableLog() { - UseLogger(btclog.Disabled) -} - -// UseLogger uses a specified Logger to output package logging info. -// This should be used in preference to SetLogWriter if the caller is also -// using btclog. -func UseLogger(logger btclog.Logger) { - log = logger -} diff --git a/graph/db/migration1/models/channel_auth_proof.go b/graph/db/migration1/models/channel_auth_proof.go deleted file mode 100644 index daf120b10..000000000 --- a/graph/db/migration1/models/channel_auth_proof.go +++ /dev/null @@ -1,35 +0,0 @@ -package models - -// ChannelAuthProof is the authentication proof (the signature portion) for a -// channel. Using the four signatures contained in the struct, and some -// auxiliary knowledge (the funding script, node identities, and outpoint) nodes -// on the network are able to validate the authenticity and existence of a -// channel. Each of these signatures signs the following digest: chanID || -// nodeID1 || nodeID2 || bitcoinKey1|| bitcoinKey2 || 2-byte-feature-len || -// features. -type ChannelAuthProof struct { - // NodeSig1Bytes are the raw bytes of the first node signature encoded - // in DER format. - NodeSig1Bytes []byte - - // NodeSig2Bytes are the raw bytes of the second node signature - // encoded in DER format. - NodeSig2Bytes []byte - - // BitcoinSig1Bytes are the raw bytes of the first bitcoin signature - // encoded in DER format. - BitcoinSig1Bytes []byte - - // BitcoinSig2Bytes are the raw bytes of the second bitcoin signature - // encoded in DER format. - BitcoinSig2Bytes []byte -} - -// IsEmpty check is the authentication proof is empty Proof is empty if at -// least one of the signatures are equal to nil. -func (c *ChannelAuthProof) IsEmpty() bool { - return len(c.NodeSig1Bytes) == 0 || - len(c.NodeSig2Bytes) == 0 || - len(c.BitcoinSig1Bytes) == 0 || - len(c.BitcoinSig2Bytes) == 0 -} diff --git a/graph/db/migration1/models/channel_edge_info.go b/graph/db/migration1/models/channel_edge_info.go deleted file mode 100644 index 443d089a6..000000000 --- a/graph/db/migration1/models/channel_edge_info.go +++ /dev/null @@ -1,69 +0,0 @@ -package models - -import ( - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/lnwire" -) - -// ChannelEdgeInfo represents a fully authenticated channel along with all its -// unique attributes. Once an authenticated channel announcement has been -// processed on the network, then an instance of ChannelEdgeInfo encapsulating -// the channels attributes is stored. The other portions relevant to routing -// policy of a channel are stored within a ChannelEdgePolicy for each direction -// of the channel. -type ChannelEdgeInfo struct { - // ChannelID is the unique channel ID for the channel. The first 3 - // bytes are the block height, the next 3 the index within the block, - // and the last 2 bytes are the output index for the channel. - ChannelID uint64 - - // ChainHash is the hash that uniquely identifies the chain that this - // channel was opened within. - ChainHash chainhash.Hash - - // NodeKey1Bytes is the raw public key of the first node. - NodeKey1Bytes [33]byte - - // NodeKey2Bytes is the raw public key of the first node. - NodeKey2Bytes [33]byte - - // BitcoinKey1Bytes is the raw public key of the first node. - BitcoinKey1Bytes [33]byte - - // BitcoinKey2Bytes is the raw public key of the first node. - BitcoinKey2Bytes [33]byte - - // Features is the list of protocol features supported by this channel - // edge. - Features *lnwire.FeatureVector - - // AuthProof is the authentication proof for this channel. This proof - // contains a set of signatures binding four identities, which attests - // to the legitimacy of the advertised channel. - AuthProof *ChannelAuthProof - - // ChannelPoint is the funding outpoint of the channel. This can be - // used to uniquely identify the channel within the channel graph. - ChannelPoint wire.OutPoint - - // Capacity is the total capacity of the channel, this is determined by - // the value output in the outpoint that created this channel. - Capacity btcutil.Amount - - // FundingScript holds the script of the channel's funding transaction. - // - // NOTE: this is not currently persisted and so will not be present if - // the edge object is loaded from the database. - FundingScript fn.Option[[]byte] - - // ExtraOpaqueData is the set of data that was appended to this - // message, some of which we may not actually know how to iterate or - // parse. By holding onto this data, we ensure that we're able to - // properly validate the set of signatures that cover these new fields, - // and ensure we're able to make upgrades to the network in a forwards - // compatible manner. - ExtraOpaqueData []byte -} diff --git a/graph/db/migration1/models/channel_edge_policy.go b/graph/db/migration1/models/channel_edge_policy.go deleted file mode 100644 index 1469602aa..000000000 --- a/graph/db/migration1/models/channel_edge_policy.go +++ /dev/null @@ -1,85 +0,0 @@ -package models - -import ( - "time" - - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/lnwire" -) - -// ChannelEdgePolicy represents a *directed* edge within the channel graph. For -// each channel in the database, there are two distinct edges: one for each -// possible direction of travel along the channel. The edges themselves hold -// information concerning fees, and minimum time-lock information which is -// utilized during path finding. -type ChannelEdgePolicy struct { - // SigBytes is the raw bytes of the signature of the channel edge - // policy. We'll only parse these if the caller needs to access the - // signature for validation purposes. Do not set SigBytes directly, but - // use SetSigBytes instead to make sure that the cache is invalidated. - SigBytes []byte - - // ChannelID is the unique channel ID for the channel. The first 3 - // bytes are the block height, the next 3 the index within the block, - // and the last 2 bytes are the output index for the channel. - ChannelID uint64 - - // LastUpdate is the last time an authenticated edge for this channel - // was received. - LastUpdate time.Time - - // MessageFlags is a bitfield which indicates the presence of optional - // fields (like max_htlc) in the policy. - MessageFlags lnwire.ChanUpdateMsgFlags - - // ChannelFlags is a bitfield which signals the capabilities of the - // channel as well as the directed edge this update applies to. - ChannelFlags lnwire.ChanUpdateChanFlags - - // TimeLockDelta is the number of blocks this node will subtract from - // the expiry of an incoming HTLC. This value expresses the time buffer - // the node would like to HTLC exchanges. - TimeLockDelta uint16 - - // MinHTLC is the smallest value HTLC this node will forward, expressed - // in millisatoshi. - MinHTLC lnwire.MilliSatoshi - - // MaxHTLC is the largest value HTLC this node will forward, expressed - // in millisatoshi. - MaxHTLC lnwire.MilliSatoshi - - // FeeBaseMSat is the base HTLC fee that will be charged for forwarding - // ANY HTLC, expressed in mSAT's. - FeeBaseMSat lnwire.MilliSatoshi - - // FeeProportionalMillionths is the rate that the node will charge for - // HTLCs for each millionth of a satoshi forwarded. - FeeProportionalMillionths lnwire.MilliSatoshi - - // ToNode is the public key of the node that this directed edge leads - // to. Using this pub key, the channel graph can further be traversed. - ToNode [33]byte - - // InboundFee is the fee that must be paid for incoming HTLCs. - // - // NOTE: for our kvdb implementation of the graph store, inbound fees - // are still only persisted as part of extra opaque data and so this - // field is not explicitly stored but is rather populated from the - // ExtraOpaqueData field on deserialization. For our SQL implementation, - // this field will be explicitly persisted in the database. - InboundFee fn.Option[lnwire.Fee] - - // ExtraOpaqueData is the set of data that was appended to this - // message, some of which we may not actually know how to iterate or - // parse. By holding onto this data, we ensure that we're able to - // properly validate the set of signatures that cover these new fields, - // and ensure we're able to make upgrades to the network in a forwards - // compatible manner. - ExtraOpaqueData lnwire.ExtraOpaqueData -} - -// IsDisabled determines whether the edge has the disabled bit set. -func (c *ChannelEdgePolicy) IsDisabled() bool { - return c.ChannelFlags.IsDisabled() -} diff --git a/graph/db/migration1/models/node.go b/graph/db/migration1/models/node.go deleted file mode 100644 index 0b0223833..000000000 --- a/graph/db/migration1/models/node.go +++ /dev/null @@ -1,146 +0,0 @@ -package models - -import ( - "image/color" - "net" - "time" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/routing/route" -) - -// Node represents an individual vertex/node within the channel graph. -// A node is connected to other nodes by one or more channel edges emanating -// from it. As the graph is directed, a node will also have an incoming edge -// attached to it for each outgoing edge. -type Node struct { - // Version is the gossip version that this node was advertised on. - Version lnwire.GossipVersion - - // PubKeyBytes is the raw bytes of the public key of the target node. - PubKeyBytes [33]byte - pubKey *btcec.PublicKey - - // LastUpdate is the last time the vertex information for this node has - // been updated. - LastUpdate time.Time - - // Address is the TCP address this node is reachable over. - Addresses []net.Addr - - // Color is the selected color for the node. - Color fn.Option[color.RGBA] - - // Alias is a nick-name for the node. The alias can be used to confirm - // a node's identity or to serve as a short ID for an address book. - Alias fn.Option[string] - - // AuthSigBytes is the raw signature under the advertised public key - // which serves to authenticate the attributes announced by this node. - AuthSigBytes []byte - - // Features is the list of protocol features supported by this node. - Features *lnwire.FeatureVector - - // ExtraOpaqueData is the set of data that was appended to this - // message, some of which we may not actually know how to iterate or - // parse. By holding onto this data, we ensure that we're able to - // properly validate the set of signatures that cover these new fields, - // and ensure we're able to make upgrades to the network in a forwards - // compatible manner. - ExtraOpaqueData []byte -} - -// NodeV1Fields houses the fields that are specific to a version 1 node -// announcement. -type NodeV1Fields struct { - // Address is the TCP address this node is reachable over. - Addresses []net.Addr - - // AuthSigBytes is the raw signature under the advertised public key - // which serves to authenticate the attributes announced by this node. - AuthSigBytes []byte - - // Features is the list of protocol features supported by this node. - Features *lnwire.RawFeatureVector - - // Color is the selected color for the node. - Color color.RGBA - - // Alias is a nick-name for the node. The alias can be used to confirm - // a node's identity or to serve as a short ID for an address book. - Alias string - - // LastUpdate is the last time the vertex information for this node has - // been updated. - LastUpdate time.Time - - // ExtraOpaqueData is the set of data that was appended to this - // message, some of which we may not actually know how to iterate or - // parse. By holding onto this data, we ensure that we're able to - // properly validate the set of signatures that cover these new fields, - // and ensure we're able to make upgrades to the network in a forwards - // compatible manner. - ExtraOpaqueData []byte -} - -// NewV1Node creates a new version 1 node from the passed fields. -func NewV1Node(pub route.Vertex, n *NodeV1Fields) *Node { - return &Node{ - Version: lnwire.GossipVersion1, - PubKeyBytes: pub, - Addresses: n.Addresses, - AuthSigBytes: n.AuthSigBytes, - Features: lnwire.NewFeatureVector( - n.Features, lnwire.Features, - ), - Color: fn.Some(n.Color), - Alias: fn.Some(n.Alias), - LastUpdate: n.LastUpdate, - ExtraOpaqueData: n.ExtraOpaqueData, - } -} - -// NewV1ShellNode creates a new shell version 1 node. -func NewV1ShellNode(pubKey route.Vertex) *Node { - return NewShellNode(lnwire.GossipVersion1, pubKey) -} - -// NewShellNode creates a new shell node with the given gossip version and -// public key. -func NewShellNode(v lnwire.GossipVersion, pubKey route.Vertex) *Node { - return &Node{ - Version: v, - PubKeyBytes: pubKey, - Features: lnwire.EmptyFeatureVector(), - LastUpdate: time.Unix(0, 0), - } -} - -// HaveAnnouncement returns true if we have received a node announcement for -// this node. We determine this by checking if we have a signature for the -// announcement. -func (n *Node) HaveAnnouncement() bool { - return len(n.AuthSigBytes) > 0 -} - -// PubKey is the node's long-term identity public key. This key will be used to -// authenticated any advertisements/updates sent by the node. -// -// NOTE: By having this method to access an attribute, we ensure we only need -// to fully deserialize the pubkey if absolutely necessary. -func (n *Node) PubKey() (*btcec.PublicKey, error) { - if n.pubKey != nil { - return n.pubKey, nil - } - - key, err := btcec.ParsePubKey(n.PubKeyBytes[:]) - if err != nil { - return nil, err - } - n.pubKey = key - - return key, nil -} diff --git a/graph/db/migration1/sql_store.go b/graph/db/migration1/sql_store.go deleted file mode 100644 index d095e0492..000000000 --- a/graph/db/migration1/sql_store.go +++ /dev/null @@ -1,1808 +0,0 @@ -package migration1 - -import ( - "bytes" - "context" - "database/sql" - "encoding/hex" - "errors" - "fmt" - "math" - "net" - "strconv" - "time" - - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/graph/db/migration1/models" - "github.com/lightningnetwork/lnd/graph/db/migration1/sqlc" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/routing/route" - "github.com/lightningnetwork/lnd/sqldb" - "github.com/lightningnetwork/lnd/tlv" - "github.com/lightningnetwork/lnd/tor" -) - -// SQLQueries is a subset of the sqlc.Querier interface that can be used to -// execute queries against the SQL graph tables. -// -//nolint:ll,interfacebloat -type SQLQueries interface { - /* - Node queries. - */ - UpsertNode(ctx context.Context, arg sqlc.UpsertNodeParams) (int64, error) - GetNodeByPubKey(ctx context.Context, arg sqlc.GetNodeByPubKeyParams) (sqlc.GraphNode, error) - GetNodesByIDs(ctx context.Context, ids []int64) ([]sqlc.GraphNode, error) - GetNodeIDByPubKey(ctx context.Context, arg sqlc.GetNodeIDByPubKeyParams) (int64, error) - GetNodesByLastUpdateRange(ctx context.Context, arg sqlc.GetNodesByLastUpdateRangeParams) ([]sqlc.GraphNode, error) - ListNodesPaginated(ctx context.Context, arg sqlc.ListNodesPaginatedParams) ([]sqlc.GraphNode, error) - ListNodeIDsAndPubKeys(ctx context.Context, arg sqlc.ListNodeIDsAndPubKeysParams) ([]sqlc.ListNodeIDsAndPubKeysRow, error) - DeleteUnconnectedNodes(ctx context.Context) ([][]byte, error) - DeleteNodeByPubKey(ctx context.Context, arg sqlc.DeleteNodeByPubKeyParams) (sql.Result, error) - DeleteNode(ctx context.Context, id int64) error - - GetExtraNodeTypes(ctx context.Context, nodeID int64) ([]sqlc.GraphNodeExtraType, error) - GetNodeExtraTypesBatch(ctx context.Context, ids []int64) ([]sqlc.GraphNodeExtraType, error) - UpsertNodeExtraType(ctx context.Context, arg sqlc.UpsertNodeExtraTypeParams) error - DeleteExtraNodeType(ctx context.Context, arg sqlc.DeleteExtraNodeTypeParams) error - - UpsertNodeAddress(ctx context.Context, arg sqlc.UpsertNodeAddressParams) error - GetNodeAddresses(ctx context.Context, nodeID int64) ([]sqlc.GetNodeAddressesRow, error) - GetNodeAddressesBatch(ctx context.Context, ids []int64) ([]sqlc.GraphNodeAddress, error) - DeleteNodeAddresses(ctx context.Context, nodeID int64) error - - InsertNodeFeature(ctx context.Context, arg sqlc.InsertNodeFeatureParams) error - GetNodeFeaturesBatch(ctx context.Context, ids []int64) ([]sqlc.GraphNodeFeature, error) - GetNodeFeaturesByPubKey(ctx context.Context, arg sqlc.GetNodeFeaturesByPubKeyParams) ([]int32, error) - DeleteNodeFeature(ctx context.Context, arg sqlc.DeleteNodeFeatureParams) error - - /* - Source node queries. - */ - AddSourceNode(ctx context.Context, nodeID int64) error - GetSourceNodesByVersion(ctx context.Context, version int16) ([]sqlc.GetSourceNodesByVersionRow, error) - - /* - Channel queries. - */ - CreateChannel(ctx context.Context, arg sqlc.CreateChannelParams) (int64, error) - AddV1ChannelProof(ctx context.Context, arg sqlc.AddV1ChannelProofParams) (sql.Result, error) - GetChannelBySCID(ctx context.Context, arg sqlc.GetChannelBySCIDParams) (sqlc.GraphChannel, error) - GetChannelsBySCIDs(ctx context.Context, arg sqlc.GetChannelsBySCIDsParams) ([]sqlc.GraphChannel, error) - GetChannelsByOutpoints(ctx context.Context, outpoints []string) ([]sqlc.GetChannelsByOutpointsRow, error) - GetChannelsBySCIDRange(ctx context.Context, arg sqlc.GetChannelsBySCIDRangeParams) ([]sqlc.GetChannelsBySCIDRangeRow, error) - GetChannelBySCIDWithPolicies(ctx context.Context, arg sqlc.GetChannelBySCIDWithPoliciesParams) (sqlc.GetChannelBySCIDWithPoliciesRow, error) - GetChannelsBySCIDWithPolicies(ctx context.Context, arg sqlc.GetChannelsBySCIDWithPoliciesParams) ([]sqlc.GetChannelsBySCIDWithPoliciesRow, error) - GetChannelsByIDs(ctx context.Context, ids []int64) ([]sqlc.GetChannelsByIDsRow, error) - GetChannelAndNodesBySCID(ctx context.Context, arg sqlc.GetChannelAndNodesBySCIDParams) (sqlc.GetChannelAndNodesBySCIDRow, error) - HighestSCID(ctx context.Context, version int16) ([]byte, error) - ListChannelsByNodeID(ctx context.Context, arg sqlc.ListChannelsByNodeIDParams) ([]sqlc.ListChannelsByNodeIDRow, error) - ListChannelsForNodeIDs(ctx context.Context, arg sqlc.ListChannelsForNodeIDsParams) ([]sqlc.ListChannelsForNodeIDsRow, error) - ListChannelsWithPoliciesPaginated(ctx context.Context, arg sqlc.ListChannelsWithPoliciesPaginatedParams) ([]sqlc.ListChannelsWithPoliciesPaginatedRow, error) - ListChannelsPaginated(ctx context.Context, arg sqlc.ListChannelsPaginatedParams) ([]sqlc.ListChannelsPaginatedRow, error) - GetChannelsByPolicyLastUpdateRange(ctx context.Context, arg sqlc.GetChannelsByPolicyLastUpdateRangeParams) ([]sqlc.GetChannelsByPolicyLastUpdateRangeRow, error) - GetChannelByOutpointWithPolicies(ctx context.Context, arg sqlc.GetChannelByOutpointWithPoliciesParams) (sqlc.GetChannelByOutpointWithPoliciesRow, error) - GetPublicV1ChannelsBySCID(ctx context.Context, arg sqlc.GetPublicV1ChannelsBySCIDParams) ([]sqlc.GraphChannel, error) - GetSCIDByOutpoint(ctx context.Context, arg sqlc.GetSCIDByOutpointParams) ([]byte, error) - DeleteChannels(ctx context.Context, ids []int64) error - - UpsertChannelExtraType(ctx context.Context, arg sqlc.UpsertChannelExtraTypeParams) error - GetChannelExtrasBatch(ctx context.Context, chanIds []int64) ([]sqlc.GraphChannelExtraType, error) - InsertChannelFeature(ctx context.Context, arg sqlc.InsertChannelFeatureParams) error - GetChannelFeaturesBatch(ctx context.Context, chanIds []int64) ([]sqlc.GraphChannelFeature, error) - - /* - Channel Policy table queries. - */ - UpsertEdgePolicy(ctx context.Context, arg sqlc.UpsertEdgePolicyParams) (int64, error) - GetChannelPolicyByChannelAndNode(ctx context.Context, arg sqlc.GetChannelPolicyByChannelAndNodeParams) (sqlc.GraphChannelPolicy, error) - GetV1DisabledSCIDs(ctx context.Context) ([][]byte, error) - - UpsertChanPolicyExtraType(ctx context.Context, arg sqlc.UpsertChanPolicyExtraTypeParams) error - GetChannelPolicyExtraTypesBatch(ctx context.Context, policyIds []int64) ([]sqlc.GetChannelPolicyExtraTypesBatchRow, error) - DeleteChannelPolicyExtraTypes(ctx context.Context, channelPolicyID int64) error - - /* - Zombie index queries. - */ - UpsertZombieChannel(ctx context.Context, arg sqlc.UpsertZombieChannelParams) error - GetZombieChannel(ctx context.Context, arg sqlc.GetZombieChannelParams) (sqlc.GraphZombieChannel, error) - GetZombieChannelsSCIDs(ctx context.Context, arg sqlc.GetZombieChannelsSCIDsParams) ([]sqlc.GraphZombieChannel, error) - CountZombieChannels(ctx context.Context, version int16) (int64, error) - DeleteZombieChannel(ctx context.Context, arg sqlc.DeleteZombieChannelParams) (sql.Result, error) - IsZombieChannel(ctx context.Context, arg sqlc.IsZombieChannelParams) (bool, error) - - /* - Prune log table queries. - */ - GetPruneTip(ctx context.Context) (sqlc.GraphPruneLog, error) - GetPruneHashByHeight(ctx context.Context, blockHeight int64) ([]byte, error) - GetPruneEntriesForHeights(ctx context.Context, heights []int64) ([]sqlc.GraphPruneLog, error) - UpsertPruneLogEntry(ctx context.Context, arg sqlc.UpsertPruneLogEntryParams) error - DeletePruneLogEntriesInRange(ctx context.Context, arg sqlc.DeletePruneLogEntriesInRangeParams) error - - /* - Closed SCID table queries. - */ - InsertClosedChannel(ctx context.Context, scid []byte) error - IsClosedChannel(ctx context.Context, scid []byte) (bool, error) - GetClosedChannelsSCIDs(ctx context.Context, scids [][]byte) ([][]byte, error) - - /* - Migration specific queries. - - NOTE: these should not be used in code other than migrations. - Once sqldbv2 is in place, these can be removed from this struct - as then migrations will have their own dedicated queries - structs. - */ - InsertNodeMig(ctx context.Context, arg sqlc.InsertNodeMigParams) (int64, error) - InsertChannelMig(ctx context.Context, arg sqlc.InsertChannelMigParams) (int64, error) - InsertEdgePolicyMig(ctx context.Context, arg sqlc.InsertEdgePolicyMigParams) (int64, error) -} - -// BatchedSQLQueries is a version of SQLQueries that's capable of batched -// database operations. -type BatchedSQLQueries interface { - SQLQueries - sqldb.BatchedTx[SQLQueries] -} - -// SQLStore is an implementation of the V1Store interface that uses a SQL -// database as the backend. -type SQLStore struct { - cfg *SQLStoreConfig - db BatchedSQLQueries -} - -// A compile-time assertion to ensure that SQLStore implements the V1Store -// interface. -var _ V1Store = (*SQLStore)(nil) - -// SQLStoreConfig holds the configuration for the SQLStore. -type SQLStoreConfig struct { - // ChainHash is the genesis hash for the chain that all the gossip - // messages in this store are aimed at. - ChainHash chainhash.Hash - - // QueryConfig holds configuration values for SQL queries. - QueryCfg *sqldb.QueryConfig -} - -// NewSQLStore creates a new SQLStore instance given an open BatchedSQLQueries -// storage backend. -func NewSQLStore(cfg *SQLStoreConfig, db BatchedSQLQueries) (*SQLStore, error) { - s := &SQLStore{ - cfg: cfg, - db: db, - } - - return s, nil -} - -// SourceNode returns the source node of the graph. The source node is treated -// as the center node within a star-graph. This method may be used to kick off -// a path finding algorithm in order to explore the reachability of another -// node based off the source node. -// -// NOTE: part of the V1Store interface. -func (s *SQLStore) SourceNode(ctx context.Context) (*models.Node, - error) { - - var node *models.Node - err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - _, nodePub, err := s.getSourceNode( - ctx, db, lnwire.GossipVersion1, - ) - if err != nil { - return fmt.Errorf("unable to fetch V1 source node: %w", - err) - } - - _, node, err = getNodeByPubKey(ctx, s.cfg.QueryCfg, db, nodePub) - - return err - }, sqldb.NoOpReset) - if err != nil { - return nil, fmt.Errorf("unable to fetch source node: %w", err) - } - - return node, nil -} - -// ForEachNode iterates through all the stored vertices/nodes in the graph, -// executing the passed callback with each node encountered. If the callback -// returns an error, then the transaction is aborted and the iteration stops -// early. -// -// NOTE: part of the V1Store interface. -func (s *SQLStore) ForEachNode(ctx context.Context, - cb func(node *models.Node) error, reset func()) error { - - return s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - return forEachNodePaginated( - ctx, s.cfg.QueryCfg, db, - lnwire.GossipVersion1, func(_ context.Context, _ int64, - node *models.Node) error { - - return cb(node) - }, - ) - }, reset) -} - -// ForEachChannel iterates through all the channel edges stored within the -// graph and invokes the passed callback for each edge. The callback takes two -// edges as since this is a directed graph, both the in/out edges are visited. -// If the callback returns an error, then the transaction is aborted and the -// iteration stops early. -// -// NOTE: If an edge can't be found, or wasn't advertised, then a nil pointer -// for that particular channel edge routing policy will be passed into the -// callback. -// -// NOTE: part of the V1Store interface. -func (s *SQLStore) ForEachChannel(ctx context.Context, - cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, - *models.ChannelEdgePolicy) error, reset func()) error { - - return s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - return forEachChannelWithPolicies(ctx, db, s.cfg, cb) - }, reset) -} - -// IsZombieEdge returns whether the edge is considered zombie. If it is a -// zombie, then the two node public keys corresponding to this edge are also -// returned. -// -// NOTE: part of the V1Store interface. -func (s *SQLStore) IsZombieEdge(chanID uint64) (bool, [33]byte, [33]byte, - error) { - - var ( - ctx = context.TODO() - isZombie bool - pubKey1, pubKey2 route.Vertex - chanIDB = channelIDToBytes(chanID) - ) - - err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - zombie, err := db.GetZombieChannel( - ctx, sqlc.GetZombieChannelParams{ - Scid: chanIDB, - Version: int16(lnwire.GossipVersion1), - }, - ) - if errors.Is(err, sql.ErrNoRows) { - return nil - } - if err != nil { - return fmt.Errorf("unable to fetch zombie channel: %w", - err) - } - - copy(pubKey1[:], zombie.NodeKey1) - copy(pubKey2[:], zombie.NodeKey2) - isZombie = true - - return nil - }, sqldb.NoOpReset) - if err != nil { - return false, route.Vertex{}, route.Vertex{}, - fmt.Errorf("%w: %w (chanID=%d)", - ErrCantCheckIfZombieEdgeStr, err, chanID) - } - - return isZombie, pubKey1, pubKey2, nil -} - -// PruneTip returns the block height and hash of the latest block that has been -// used to prune channels in the graph. Knowing the "prune tip" allows callers -// to tell if the graph is currently in sync with the current best known UTXO -// state. -// -// NOTE: part of the V1Store interface. -func (s *SQLStore) PruneTip() (*chainhash.Hash, uint32, error) { - var ( - ctx = context.TODO() - tipHash chainhash.Hash - tipHeight uint32 - ) - err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { - pruneTip, err := db.GetPruneTip(ctx) - if errors.Is(err, sql.ErrNoRows) { - return ErrGraphNeverPruned - } else if err != nil { - return fmt.Errorf("unable to fetch prune tip: %w", err) - } - - tipHash = chainhash.Hash(pruneTip.BlockHash) - tipHeight = uint32(pruneTip.BlockHeight) - - return nil - }, sqldb.NoOpReset) - if err != nil { - return nil, 0, err - } - - return &tipHash, tipHeight, nil -} - -// IsClosedScid checks whether a channel identified by the passed in scid is -// closed. This helps avoid having to perform expensive validation checks. -// -// NOTE: part of the V1Store interface. -func (s *SQLStore) IsClosedScid(scid lnwire.ShortChannelID) (bool, error) { - var ( - ctx = context.TODO() - isClosed bool - chanIDB = channelIDToBytes(scid.ToUint64()) - ) - err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - var err error - isClosed, err = db.IsClosedChannel(ctx, chanIDB) - if err != nil { - return fmt.Errorf("unable to fetch closed channel: %w", - err) - } - - return nil - }, sqldb.NoOpReset) - if err != nil { - return false, fmt.Errorf("unable to fetch closed channel: %w", - err) - } - - return isClosed, nil -} - -// getNodeByPubKey attempts to look up a target node by its public key. -func getNodeByPubKey(ctx context.Context, cfg *sqldb.QueryConfig, db SQLQueries, - pubKey route.Vertex) (int64, *models.Node, error) { - - dbNode, err := db.GetNodeByPubKey( - ctx, sqlc.GetNodeByPubKeyParams{ - Version: int16(lnwire.GossipVersion1), - PubKey: pubKey[:], - }, - ) - if errors.Is(err, sql.ErrNoRows) { - return 0, nil, ErrGraphNodeNotFound - } else if err != nil { - return 0, nil, fmt.Errorf("unable to fetch node: %w", err) - } - - node, err := buildNode(ctx, cfg, db, dbNode) - if err != nil { - return 0, nil, fmt.Errorf("unable to build node: %w", err) - } - - return dbNode.ID, node, nil -} - -// buildNode constructs a Node instance from the given database node -// record. The node's features, addresses and extra signed fields are also -// fetched from the database and set on the node. -func buildNode(ctx context.Context, cfg *sqldb.QueryConfig, db SQLQueries, - dbNode sqlc.GraphNode) (*models.Node, error) { - - data, err := batchLoadNodeData(ctx, cfg, db, []int64{dbNode.ID}) - if err != nil { - return nil, fmt.Errorf("unable to batch load node data: %w", - err) - } - - return buildNodeWithBatchData(dbNode, data) -} - -// buildNodeWithBatchData builds a models.Node instance -// from the provided sqlc.GraphNode and batchNodeData. If the node does have -// features/addresses/extra fields, then the corresponding fields are expected -// to be present in the batchNodeData. -func buildNodeWithBatchData(dbNode sqlc.GraphNode, - batchData *batchNodeData) (*models.Node, error) { - - if dbNode.Version != int16(lnwire.GossipVersion1) { - return nil, fmt.Errorf("unsupported node version: %d", - dbNode.Version) - } - - var pub [33]byte - copy(pub[:], dbNode.PubKey) - - node := models.NewV1ShellNode(pub) - - if len(dbNode.Signature) == 0 { - return node, nil - } - - node.AuthSigBytes = dbNode.Signature - - if dbNode.Alias.Valid { - node.Alias = fn.Some(dbNode.Alias.String) - } - if dbNode.LastUpdate.Valid { - node.LastUpdate = time.Unix(dbNode.LastUpdate.Int64, 0) - } - - var err error - if dbNode.Color.Valid { - nodeColor, err := DecodeHexColor(dbNode.Color.String) - if err != nil { - return nil, fmt.Errorf("unable to decode color: %w", - err) - } - - node.Color = fn.Some(nodeColor) - } - - // Use preloaded features. - if features, exists := batchData.features[dbNode.ID]; exists { - fv := lnwire.EmptyFeatureVector() - for _, bit := range features { - fv.Set(lnwire.FeatureBit(bit)) - } - node.Features = fv - } - - // Use preloaded addresses. - addresses, exists := batchData.addresses[dbNode.ID] - if exists && len(addresses) > 0 { - node.Addresses, err = buildNodeAddresses(addresses) - if err != nil { - return nil, fmt.Errorf("unable to build addresses "+ - "for node(%d): %w", dbNode.ID, err) - } - } - - // Use preloaded extra fields. - if extraFields, exists := batchData.extraFields[dbNode.ID]; exists { - recs, err := lnwire.CustomRecords(extraFields).Serialize() - if err != nil { - return nil, fmt.Errorf("unable to serialize extra "+ - "signed fields: %w", err) - } - if len(recs) != 0 { - node.ExtraOpaqueData = recs - } - } - - return node, nil -} - -// dbAddressType is an enum type that represents the different address types -// that we store in the node_addresses table. The address type determines how -// the address is to be serialised/deserialize. -type dbAddressType uint8 - -const ( - addressTypeIPv4 dbAddressType = 1 - addressTypeIPv6 dbAddressType = 2 - addressTypeTorV2 dbAddressType = 3 - addressTypeTorV3 dbAddressType = 4 - addressTypeDNS dbAddressType = 5 - addressTypeOpaque dbAddressType = math.MaxInt8 -) - -// collectAddressRecords collects the addresses from the provided -// net.Addr slice and returns a map of dbAddressType to a slice of address -// strings. -func collectAddressRecords(addresses []net.Addr) (map[dbAddressType][]string, - error) { - - // Copy the nodes latest set of addresses. - newAddresses := map[dbAddressType][]string{ - addressTypeIPv4: {}, - addressTypeIPv6: {}, - addressTypeTorV2: {}, - addressTypeTorV3: {}, - addressTypeDNS: {}, - addressTypeOpaque: {}, - } - addAddr := func(t dbAddressType, addr net.Addr) { - newAddresses[t] = append(newAddresses[t], addr.String()) - } - - for _, address := range addresses { - switch addr := address.(type) { - case *net.TCPAddr: - if ip4 := addr.IP.To4(); ip4 != nil { - addAddr(addressTypeIPv4, addr) - } else if ip6 := addr.IP.To16(); ip6 != nil { - addAddr(addressTypeIPv6, addr) - } else { - return nil, fmt.Errorf("unhandled IP "+ - "address: %v", addr) - } - - case *tor.OnionAddr: - switch len(addr.OnionService) { - case tor.V2Len: - addAddr(addressTypeTorV2, addr) - case tor.V3Len: - addAddr(addressTypeTorV3, addr) - default: - return nil, fmt.Errorf("invalid length for " + - "a tor address") - } - - case *lnwire.DNSAddress: - addAddr(addressTypeDNS, addr) - - case *lnwire.OpaqueAddrs: - addAddr(addressTypeOpaque, addr) - - default: - return nil, fmt.Errorf("unhandled address type: %T", - addr) - } - } - - return newAddresses, nil -} - -// sourceNode returns the DB node ID and pub key of the source node for the -// specified protocol version. -func (s *SQLStore) getSourceNode(ctx context.Context, db SQLQueries, - version lnwire.GossipVersion) (int64, route.Vertex, error) { - - var pubKey route.Vertex - - nodes, err := db.GetSourceNodesByVersion(ctx, int16(version)) - if err != nil { - return 0, pubKey, fmt.Errorf("unable to fetch source node: %w", - err) - } - - if len(nodes) == 0 { - return 0, pubKey, ErrSourceNodeNotSet - } else if len(nodes) > 1 { - return 0, pubKey, fmt.Errorf("multiple source nodes for "+ - "protocol %s found", version) - } - - copy(pubKey[:], nodes[0].PubKey) - - return nodes[0].NodeID, pubKey, nil -} - -// marshalExtraOpaqueData takes a flat byte slice parses it as a TLV stream. -// This then produces a map from TLV type to value. If the input is not a -// valid TLV stream, then an error is returned. -func marshalExtraOpaqueData(data []byte) (map[uint64][]byte, error) { - r := bytes.NewReader(data) - - tlvStream, err := tlv.NewStream() - if err != nil { - return nil, err - } - - // Since ExtraOpaqueData is provided by a potentially malicious peer, - // pass it into the P2P decoding variant. - parsedTypes, err := tlvStream.DecodeWithParsedTypesP2P(r) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrParsingExtraTLVBytes, err) - } - if len(parsedTypes) == 0 { - return nil, nil - } - - records := make(map[uint64][]byte) - for k, v := range parsedTypes { - records[uint64(k)] = v - } - - return records, nil -} - -// maybeCreateShellNode checks if a shell node entry exists for the -// given public key. If it does not exist, then a new shell node entry is -// created. The ID of the node is returned. A shell node only has a protocol -// version and public key persisted. -func maybeCreateShellNode(ctx context.Context, db SQLQueries, - pubKey route.Vertex) (int64, error) { - - dbNode, err := db.GetNodeByPubKey( - ctx, sqlc.GetNodeByPubKeyParams{ - PubKey: pubKey[:], - Version: int16(lnwire.GossipVersion1), - }, - ) - // The node exists. Return the ID. - if err == nil { - return dbNode.ID, nil - } else if !errors.Is(err, sql.ErrNoRows) { - return 0, err - } - - // Otherwise, the node does not exist, so we create a shell entry for - // it. - id, err := db.UpsertNode(ctx, sqlc.UpsertNodeParams{ - Version: int16(lnwire.GossipVersion1), - PubKey: pubKey[:], - }) - if err != nil { - return 0, fmt.Errorf("unable to create shell node: %w", err) - } - - return id, nil -} - -// buildEdgeInfoWithBatchData builds edge info using pre-loaded batch data. -func buildEdgeInfoWithBatchData(chain chainhash.Hash, - dbChan sqlc.GraphChannel, node1, node2 route.Vertex, - batchData *batchChannelData) (*models.ChannelEdgeInfo, error) { - - if dbChan.Version != int16(lnwire.GossipVersion1) { - return nil, fmt.Errorf("unsupported channel version: %d", - dbChan.Version) - } - - // Use pre-loaded features and extras types. - fv := lnwire.EmptyFeatureVector() - if features, exists := batchData.chanfeatures[dbChan.ID]; exists { - for _, bit := range features { - fv.Set(lnwire.FeatureBit(bit)) - } - } - - var extras map[uint64][]byte - channelExtras, exists := batchData.chanExtraTypes[dbChan.ID] - if exists { - extras = channelExtras - } else { - extras = make(map[uint64][]byte) - } - - op, err := wire.NewOutPointFromString(dbChan.Outpoint) - if err != nil { - return nil, err - } - - recs, err := lnwire.CustomRecords(extras).Serialize() - if err != nil { - return nil, fmt.Errorf("unable to serialize extra signed "+ - "fields: %w", err) - } - if recs == nil { - recs = make([]byte, 0) - } - - var btcKey1, btcKey2 route.Vertex - copy(btcKey1[:], dbChan.BitcoinKey1) - copy(btcKey2[:], dbChan.BitcoinKey2) - - channel := &models.ChannelEdgeInfo{ - ChainHash: chain, - ChannelID: byteOrder.Uint64(dbChan.Scid), - NodeKey1Bytes: node1, - NodeKey2Bytes: node2, - BitcoinKey1Bytes: btcKey1, - BitcoinKey2Bytes: btcKey2, - ChannelPoint: *op, - Capacity: btcutil.Amount(dbChan.Capacity.Int64), - Features: fv, - ExtraOpaqueData: recs, - } - - // We always set all the signatures at the same time, so we can - // safely check if one signature is present to determine if we have the - // rest of the signatures for the auth proof. - if len(dbChan.Bitcoin1Signature) > 0 { - channel.AuthProof = &models.ChannelAuthProof{ - NodeSig1Bytes: dbChan.Node1Signature, - NodeSig2Bytes: dbChan.Node2Signature, - BitcoinSig1Bytes: dbChan.Bitcoin1Signature, - BitcoinSig2Bytes: dbChan.Bitcoin2Signature, - } - } - - return channel, nil -} - -// buildNodeVertices is a helper that converts raw node public keys -// into route.Vertex instances. -func buildNodeVertices(node1Pub, node2Pub []byte) (route.Vertex, - route.Vertex, error) { - - node1Vertex, err := route.NewVertexFromBytes(node1Pub) - if err != nil { - return route.Vertex{}, route.Vertex{}, fmt.Errorf("unable to "+ - "create vertex from node1 pubkey: %w", err) - } - - node2Vertex, err := route.NewVertexFromBytes(node2Pub) - if err != nil { - return route.Vertex{}, route.Vertex{}, fmt.Errorf("unable to "+ - "create vertex from node2 pubkey: %w", err) - } - - return node1Vertex, node2Vertex, nil -} - -// buildChanPolicy builds a models.ChannelEdgePolicy instance from the -// provided sqlc.GraphChannelPolicy and other required information. -func buildChanPolicy(dbPolicy sqlc.GraphChannelPolicy, channelID uint64, - extras map[uint64][]byte, - toNode route.Vertex) (*models.ChannelEdgePolicy, error) { - - recs, err := lnwire.CustomRecords(extras).Serialize() - if err != nil { - return nil, fmt.Errorf("unable to serialize extra signed "+ - "fields: %w", err) - } - - var inboundFee fn.Option[lnwire.Fee] - if dbPolicy.InboundFeeRateMilliMsat.Valid || - dbPolicy.InboundBaseFeeMsat.Valid { - - inboundFee = fn.Some(lnwire.Fee{ - BaseFee: int32(dbPolicy.InboundBaseFeeMsat.Int64), - FeeRate: int32(dbPolicy.InboundFeeRateMilliMsat.Int64), - }) - } - - return &models.ChannelEdgePolicy{ - SigBytes: dbPolicy.Signature, - ChannelID: channelID, - LastUpdate: time.Unix( - dbPolicy.LastUpdate.Int64, 0, - ), - MessageFlags: sqldb.ExtractSqlInt16[lnwire.ChanUpdateMsgFlags]( - dbPolicy.MessageFlags, - ), - ChannelFlags: sqldb.ExtractSqlInt16[lnwire.ChanUpdateChanFlags]( - dbPolicy.ChannelFlags, - ), - TimeLockDelta: uint16(dbPolicy.Timelock), - MinHTLC: lnwire.MilliSatoshi( - dbPolicy.MinHtlcMsat, - ), - MaxHTLC: lnwire.MilliSatoshi( - dbPolicy.MaxHtlcMsat.Int64, - ), - FeeBaseMSat: lnwire.MilliSatoshi( - dbPolicy.BaseFeeMsat, - ), - FeeProportionalMillionths: lnwire.MilliSatoshi(dbPolicy.FeePpm), - ToNode: toNode, - InboundFee: inboundFee, - ExtraOpaqueData: recs, - }, nil -} - -// extractChannelPolicies extracts the sqlc.GraphChannelPolicy records from the give -// row which is expected to be a sqlc type that contains channel policy -// information. It returns two policies, which may be nil if the policy -// information is not present in the row. -// -//nolint:ll,dupl,funlen -func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, - *sqlc.GraphChannelPolicy, error) { - - var policy1, policy2 *sqlc.GraphChannelPolicy - switch r := row.(type) { - case sqlc.ListChannelsWithPoliciesForCachePaginatedRow: - if r.Policy1Timelock.Valid { - policy1 = &sqlc.GraphChannelPolicy{ - Timelock: r.Policy1Timelock.Int32, - FeePpm: r.Policy1FeePpm.Int64, - BaseFeeMsat: r.Policy1BaseFeeMsat.Int64, - MinHtlcMsat: r.Policy1MinHtlcMsat.Int64, - MaxHtlcMsat: r.Policy1MaxHtlcMsat, - InboundBaseFeeMsat: r.Policy1InboundBaseFeeMsat, - InboundFeeRateMilliMsat: r.Policy1InboundFeeRateMilliMsat, - Disabled: r.Policy1Disabled, - MessageFlags: r.Policy1MessageFlags, - ChannelFlags: r.Policy1ChannelFlags, - } - } - if r.Policy2Timelock.Valid { - policy2 = &sqlc.GraphChannelPolicy{ - Timelock: r.Policy2Timelock.Int32, - FeePpm: r.Policy2FeePpm.Int64, - BaseFeeMsat: r.Policy2BaseFeeMsat.Int64, - MinHtlcMsat: r.Policy2MinHtlcMsat.Int64, - MaxHtlcMsat: r.Policy2MaxHtlcMsat, - InboundBaseFeeMsat: r.Policy2InboundBaseFeeMsat, - InboundFeeRateMilliMsat: r.Policy2InboundFeeRateMilliMsat, - Disabled: r.Policy2Disabled, - MessageFlags: r.Policy2MessageFlags, - ChannelFlags: r.Policy2ChannelFlags, - } - } - - return policy1, policy2, nil - - case sqlc.GetChannelsBySCIDWithPoliciesRow: - if r.Policy1ID.Valid { - policy1 = &sqlc.GraphChannelPolicy{ - ID: r.Policy1ID.Int64, - Version: r.Policy1Version.Int16, - ChannelID: r.GraphChannel.ID, - NodeID: r.Policy1NodeID.Int64, - Timelock: r.Policy1Timelock.Int32, - FeePpm: r.Policy1FeePpm.Int64, - BaseFeeMsat: r.Policy1BaseFeeMsat.Int64, - MinHtlcMsat: r.Policy1MinHtlcMsat.Int64, - MaxHtlcMsat: r.Policy1MaxHtlcMsat, - LastUpdate: r.Policy1LastUpdate, - InboundBaseFeeMsat: r.Policy1InboundBaseFeeMsat, - InboundFeeRateMilliMsat: r.Policy1InboundFeeRateMilliMsat, - Disabled: r.Policy1Disabled, - MessageFlags: r.Policy1MessageFlags, - ChannelFlags: r.Policy1ChannelFlags, - Signature: r.Policy1Signature, - } - } - if r.Policy2ID.Valid { - policy2 = &sqlc.GraphChannelPolicy{ - ID: r.Policy2ID.Int64, - Version: r.Policy2Version.Int16, - ChannelID: r.GraphChannel.ID, - NodeID: r.Policy2NodeID.Int64, - Timelock: r.Policy2Timelock.Int32, - FeePpm: r.Policy2FeePpm.Int64, - BaseFeeMsat: r.Policy2BaseFeeMsat.Int64, - MinHtlcMsat: r.Policy2MinHtlcMsat.Int64, - MaxHtlcMsat: r.Policy2MaxHtlcMsat, - LastUpdate: r.Policy2LastUpdate, - InboundBaseFeeMsat: r.Policy2InboundBaseFeeMsat, - InboundFeeRateMilliMsat: r.Policy2InboundFeeRateMilliMsat, - Disabled: r.Policy2Disabled, - MessageFlags: r.Policy2MessageFlags, - ChannelFlags: r.Policy2ChannelFlags, - Signature: r.Policy2Signature, - } - } - - return policy1, policy2, nil - - case sqlc.GetChannelByOutpointWithPoliciesRow: - if r.Policy1ID.Valid { - policy1 = &sqlc.GraphChannelPolicy{ - ID: r.Policy1ID.Int64, - Version: r.Policy1Version.Int16, - ChannelID: r.GraphChannel.ID, - NodeID: r.Policy1NodeID.Int64, - Timelock: r.Policy1Timelock.Int32, - FeePpm: r.Policy1FeePpm.Int64, - BaseFeeMsat: r.Policy1BaseFeeMsat.Int64, - MinHtlcMsat: r.Policy1MinHtlcMsat.Int64, - MaxHtlcMsat: r.Policy1MaxHtlcMsat, - LastUpdate: r.Policy1LastUpdate, - InboundBaseFeeMsat: r.Policy1InboundBaseFeeMsat, - InboundFeeRateMilliMsat: r.Policy1InboundFeeRateMilliMsat, - Disabled: r.Policy1Disabled, - MessageFlags: r.Policy1MessageFlags, - ChannelFlags: r.Policy1ChannelFlags, - Signature: r.Policy1Signature, - } - } - if r.Policy2ID.Valid { - policy2 = &sqlc.GraphChannelPolicy{ - ID: r.Policy2ID.Int64, - Version: r.Policy2Version.Int16, - ChannelID: r.GraphChannel.ID, - NodeID: r.Policy2NodeID.Int64, - Timelock: r.Policy2Timelock.Int32, - FeePpm: r.Policy2FeePpm.Int64, - BaseFeeMsat: r.Policy2BaseFeeMsat.Int64, - MinHtlcMsat: r.Policy2MinHtlcMsat.Int64, - MaxHtlcMsat: r.Policy2MaxHtlcMsat, - LastUpdate: r.Policy2LastUpdate, - InboundBaseFeeMsat: r.Policy2InboundBaseFeeMsat, - InboundFeeRateMilliMsat: r.Policy2InboundFeeRateMilliMsat, - Disabled: r.Policy2Disabled, - MessageFlags: r.Policy2MessageFlags, - ChannelFlags: r.Policy2ChannelFlags, - Signature: r.Policy2Signature, - } - } - - return policy1, policy2, nil - - case sqlc.GetChannelBySCIDWithPoliciesRow: - if r.Policy1ID.Valid { - policy1 = &sqlc.GraphChannelPolicy{ - ID: r.Policy1ID.Int64, - Version: r.Policy1Version.Int16, - ChannelID: r.GraphChannel.ID, - NodeID: r.Policy1NodeID.Int64, - Timelock: r.Policy1Timelock.Int32, - FeePpm: r.Policy1FeePpm.Int64, - BaseFeeMsat: r.Policy1BaseFeeMsat.Int64, - MinHtlcMsat: r.Policy1MinHtlcMsat.Int64, - MaxHtlcMsat: r.Policy1MaxHtlcMsat, - LastUpdate: r.Policy1LastUpdate, - InboundBaseFeeMsat: r.Policy1InboundBaseFeeMsat, - InboundFeeRateMilliMsat: r.Policy1InboundFeeRateMilliMsat, - Disabled: r.Policy1Disabled, - MessageFlags: r.Policy1MessageFlags, - ChannelFlags: r.Policy1ChannelFlags, - Signature: r.Policy1Signature, - } - } - if r.Policy2ID.Valid { - policy2 = &sqlc.GraphChannelPolicy{ - ID: r.Policy2ID.Int64, - Version: r.Policy2Version.Int16, - ChannelID: r.GraphChannel.ID, - NodeID: r.Policy2NodeID.Int64, - Timelock: r.Policy2Timelock.Int32, - FeePpm: r.Policy2FeePpm.Int64, - BaseFeeMsat: r.Policy2BaseFeeMsat.Int64, - MinHtlcMsat: r.Policy2MinHtlcMsat.Int64, - MaxHtlcMsat: r.Policy2MaxHtlcMsat, - LastUpdate: r.Policy2LastUpdate, - InboundBaseFeeMsat: r.Policy2InboundBaseFeeMsat, - InboundFeeRateMilliMsat: r.Policy2InboundFeeRateMilliMsat, - Disabled: r.Policy2Disabled, - MessageFlags: r.Policy2MessageFlags, - ChannelFlags: r.Policy2ChannelFlags, - Signature: r.Policy2Signature, - } - } - - return policy1, policy2, nil - - case sqlc.GetChannelsByPolicyLastUpdateRangeRow: - if r.Policy1ID.Valid { - policy1 = &sqlc.GraphChannelPolicy{ - ID: r.Policy1ID.Int64, - Version: r.Policy1Version.Int16, - ChannelID: r.GraphChannel.ID, - NodeID: r.Policy1NodeID.Int64, - Timelock: r.Policy1Timelock.Int32, - FeePpm: r.Policy1FeePpm.Int64, - BaseFeeMsat: r.Policy1BaseFeeMsat.Int64, - MinHtlcMsat: r.Policy1MinHtlcMsat.Int64, - MaxHtlcMsat: r.Policy1MaxHtlcMsat, - LastUpdate: r.Policy1LastUpdate, - InboundBaseFeeMsat: r.Policy1InboundBaseFeeMsat, - InboundFeeRateMilliMsat: r.Policy1InboundFeeRateMilliMsat, - Disabled: r.Policy1Disabled, - MessageFlags: r.Policy1MessageFlags, - ChannelFlags: r.Policy1ChannelFlags, - Signature: r.Policy1Signature, - } - } - if r.Policy2ID.Valid { - policy2 = &sqlc.GraphChannelPolicy{ - ID: r.Policy2ID.Int64, - Version: r.Policy2Version.Int16, - ChannelID: r.GraphChannel.ID, - NodeID: r.Policy2NodeID.Int64, - Timelock: r.Policy2Timelock.Int32, - FeePpm: r.Policy2FeePpm.Int64, - BaseFeeMsat: r.Policy2BaseFeeMsat.Int64, - MinHtlcMsat: r.Policy2MinHtlcMsat.Int64, - MaxHtlcMsat: r.Policy2MaxHtlcMsat, - LastUpdate: r.Policy2LastUpdate, - InboundBaseFeeMsat: r.Policy2InboundBaseFeeMsat, - InboundFeeRateMilliMsat: r.Policy2InboundFeeRateMilliMsat, - Disabled: r.Policy2Disabled, - MessageFlags: r.Policy2MessageFlags, - ChannelFlags: r.Policy2ChannelFlags, - Signature: r.Policy2Signature, - } - } - - return policy1, policy2, nil - - case sqlc.ListChannelsForNodeIDsRow: - if r.Policy1ID.Valid { - policy1 = &sqlc.GraphChannelPolicy{ - ID: r.Policy1ID.Int64, - Version: r.Policy1Version.Int16, - ChannelID: r.GraphChannel.ID, - NodeID: r.Policy1NodeID.Int64, - Timelock: r.Policy1Timelock.Int32, - FeePpm: r.Policy1FeePpm.Int64, - BaseFeeMsat: r.Policy1BaseFeeMsat.Int64, - MinHtlcMsat: r.Policy1MinHtlcMsat.Int64, - MaxHtlcMsat: r.Policy1MaxHtlcMsat, - LastUpdate: r.Policy1LastUpdate, - InboundBaseFeeMsat: r.Policy1InboundBaseFeeMsat, - InboundFeeRateMilliMsat: r.Policy1InboundFeeRateMilliMsat, - Disabled: r.Policy1Disabled, - MessageFlags: r.Policy1MessageFlags, - ChannelFlags: r.Policy1ChannelFlags, - Signature: r.Policy1Signature, - } - } - if r.Policy2ID.Valid { - policy2 = &sqlc.GraphChannelPolicy{ - ID: r.Policy2ID.Int64, - Version: r.Policy2Version.Int16, - ChannelID: r.GraphChannel.ID, - NodeID: r.Policy2NodeID.Int64, - Timelock: r.Policy2Timelock.Int32, - FeePpm: r.Policy2FeePpm.Int64, - BaseFeeMsat: r.Policy2BaseFeeMsat.Int64, - MinHtlcMsat: r.Policy2MinHtlcMsat.Int64, - MaxHtlcMsat: r.Policy2MaxHtlcMsat, - LastUpdate: r.Policy2LastUpdate, - InboundBaseFeeMsat: r.Policy2InboundBaseFeeMsat, - InboundFeeRateMilliMsat: r.Policy2InboundFeeRateMilliMsat, - Disabled: r.Policy2Disabled, - MessageFlags: r.Policy2MessageFlags, - ChannelFlags: r.Policy2ChannelFlags, - Signature: r.Policy2Signature, - } - } - - return policy1, policy2, nil - - case sqlc.ListChannelsByNodeIDRow: - if r.Policy1ID.Valid { - policy1 = &sqlc.GraphChannelPolicy{ - ID: r.Policy1ID.Int64, - Version: r.Policy1Version.Int16, - ChannelID: r.GraphChannel.ID, - NodeID: r.Policy1NodeID.Int64, - Timelock: r.Policy1Timelock.Int32, - FeePpm: r.Policy1FeePpm.Int64, - BaseFeeMsat: r.Policy1BaseFeeMsat.Int64, - MinHtlcMsat: r.Policy1MinHtlcMsat.Int64, - MaxHtlcMsat: r.Policy1MaxHtlcMsat, - LastUpdate: r.Policy1LastUpdate, - InboundBaseFeeMsat: r.Policy1InboundBaseFeeMsat, - InboundFeeRateMilliMsat: r.Policy1InboundFeeRateMilliMsat, - Disabled: r.Policy1Disabled, - MessageFlags: r.Policy1MessageFlags, - ChannelFlags: r.Policy1ChannelFlags, - Signature: r.Policy1Signature, - } - } - if r.Policy2ID.Valid { - policy2 = &sqlc.GraphChannelPolicy{ - ID: r.Policy2ID.Int64, - Version: r.Policy2Version.Int16, - ChannelID: r.GraphChannel.ID, - NodeID: r.Policy2NodeID.Int64, - Timelock: r.Policy2Timelock.Int32, - FeePpm: r.Policy2FeePpm.Int64, - BaseFeeMsat: r.Policy2BaseFeeMsat.Int64, - MinHtlcMsat: r.Policy2MinHtlcMsat.Int64, - MaxHtlcMsat: r.Policy2MaxHtlcMsat, - LastUpdate: r.Policy2LastUpdate, - InboundBaseFeeMsat: r.Policy2InboundBaseFeeMsat, - InboundFeeRateMilliMsat: r.Policy2InboundFeeRateMilliMsat, - Disabled: r.Policy2Disabled, - MessageFlags: r.Policy2MessageFlags, - ChannelFlags: r.Policy2ChannelFlags, - Signature: r.Policy2Signature, - } - } - - return policy1, policy2, nil - - case sqlc.ListChannelsWithPoliciesPaginatedRow: - if r.Policy1ID.Valid { - policy1 = &sqlc.GraphChannelPolicy{ - ID: r.Policy1ID.Int64, - Version: r.Policy1Version.Int16, - ChannelID: r.GraphChannel.ID, - NodeID: r.Policy1NodeID.Int64, - Timelock: r.Policy1Timelock.Int32, - FeePpm: r.Policy1FeePpm.Int64, - BaseFeeMsat: r.Policy1BaseFeeMsat.Int64, - MinHtlcMsat: r.Policy1MinHtlcMsat.Int64, - MaxHtlcMsat: r.Policy1MaxHtlcMsat, - LastUpdate: r.Policy1LastUpdate, - InboundBaseFeeMsat: r.Policy1InboundBaseFeeMsat, - InboundFeeRateMilliMsat: r.Policy1InboundFeeRateMilliMsat, - Disabled: r.Policy1Disabled, - MessageFlags: r.Policy1MessageFlags, - ChannelFlags: r.Policy1ChannelFlags, - Signature: r.Policy1Signature, - } - } - if r.Policy2ID.Valid { - policy2 = &sqlc.GraphChannelPolicy{ - ID: r.Policy2ID.Int64, - Version: r.Policy2Version.Int16, - ChannelID: r.GraphChannel.ID, - NodeID: r.Policy2NodeID.Int64, - Timelock: r.Policy2Timelock.Int32, - FeePpm: r.Policy2FeePpm.Int64, - BaseFeeMsat: r.Policy2BaseFeeMsat.Int64, - MinHtlcMsat: r.Policy2MinHtlcMsat.Int64, - MaxHtlcMsat: r.Policy2MaxHtlcMsat, - LastUpdate: r.Policy2LastUpdate, - InboundBaseFeeMsat: r.Policy2InboundBaseFeeMsat, - InboundFeeRateMilliMsat: r.Policy2InboundFeeRateMilliMsat, - Disabled: r.Policy2Disabled, - MessageFlags: r.Policy2MessageFlags, - ChannelFlags: r.Policy2ChannelFlags, - Signature: r.Policy2Signature, - } - } - - return policy1, policy2, nil - - case sqlc.GetChannelsByIDsRow: - if r.Policy1ID.Valid { - policy1 = &sqlc.GraphChannelPolicy{ - ID: r.Policy1ID.Int64, - Version: r.Policy1Version.Int16, - ChannelID: r.GraphChannel.ID, - NodeID: r.Policy1NodeID.Int64, - Timelock: r.Policy1Timelock.Int32, - FeePpm: r.Policy1FeePpm.Int64, - BaseFeeMsat: r.Policy1BaseFeeMsat.Int64, - MinHtlcMsat: r.Policy1MinHtlcMsat.Int64, - MaxHtlcMsat: r.Policy1MaxHtlcMsat, - LastUpdate: r.Policy1LastUpdate, - InboundBaseFeeMsat: r.Policy1InboundBaseFeeMsat, - InboundFeeRateMilliMsat: r.Policy1InboundFeeRateMilliMsat, - Disabled: r.Policy1Disabled, - MessageFlags: r.Policy1MessageFlags, - ChannelFlags: r.Policy1ChannelFlags, - Signature: r.Policy1Signature, - } - } - if r.Policy2ID.Valid { - policy2 = &sqlc.GraphChannelPolicy{ - ID: r.Policy2ID.Int64, - Version: r.Policy2Version.Int16, - ChannelID: r.GraphChannel.ID, - NodeID: r.Policy2NodeID.Int64, - Timelock: r.Policy2Timelock.Int32, - FeePpm: r.Policy2FeePpm.Int64, - BaseFeeMsat: r.Policy2BaseFeeMsat.Int64, - MinHtlcMsat: r.Policy2MinHtlcMsat.Int64, - MaxHtlcMsat: r.Policy2MaxHtlcMsat, - LastUpdate: r.Policy2LastUpdate, - InboundBaseFeeMsat: r.Policy2InboundBaseFeeMsat, - InboundFeeRateMilliMsat: r.Policy2InboundFeeRateMilliMsat, - Disabled: r.Policy2Disabled, - MessageFlags: r.Policy2MessageFlags, - ChannelFlags: r.Policy2ChannelFlags, - Signature: r.Policy2Signature, - } - } - - return policy1, policy2, nil - - default: - return nil, nil, fmt.Errorf("unexpected row type in "+ - "extractChannelPolicies: %T", r) - } -} - -// channelIDToBytes converts a channel ID (SCID) to a byte array -// representation. -func channelIDToBytes(channelID uint64) []byte { - var chanIDB [8]byte - byteOrder.PutUint64(chanIDB[:], channelID) - - return chanIDB[:] -} - -// buildNodeAddresses converts a slice of nodeAddress into a slice of net.Addr. -func buildNodeAddresses(addresses []nodeAddress) ([]net.Addr, error) { - if len(addresses) == 0 { - return nil, nil - } - - result := make([]net.Addr, 0, len(addresses)) - for _, addr := range addresses { - netAddr, err := parseAddress(addr.addrType, addr.address) - if err != nil { - return nil, fmt.Errorf("unable to parse address %s "+ - "of type %d: %w", addr.address, addr.addrType, - err) - } - if netAddr != nil { - result = append(result, netAddr) - } - } - - // If we have no valid addresses, return nil instead of empty slice. - if len(result) == 0 { - return nil, nil - } - - return result, nil -} - -// parseAddress parses the given address string based on the address type -// and returns a net.Addr instance. It supports IPv4, IPv6, Tor v2, Tor v3, -// and opaque addresses. -func parseAddress(addrType dbAddressType, address string) (net.Addr, error) { - switch addrType { - case addressTypeIPv4: - tcp, err := net.ResolveTCPAddr("tcp4", address) - if err != nil { - return nil, err - } - - tcp.IP = tcp.IP.To4() - - return tcp, nil - - case addressTypeIPv6: - tcp, err := net.ResolveTCPAddr("tcp6", address) - if err != nil { - return nil, err - } - - return tcp, nil - - case addressTypeTorV3, addressTypeTorV2: - service, portStr, err := net.SplitHostPort(address) - if err != nil { - return nil, fmt.Errorf("unable to split tor "+ - "address: %v", address) - } - - port, err := strconv.Atoi(portStr) - if err != nil { - return nil, err - } - - return &tor.OnionAddr{ - OnionService: service, - Port: port, - }, nil - - case addressTypeDNS: - hostname, portStr, err := net.SplitHostPort(address) - if err != nil { - return nil, fmt.Errorf("unable to split DNS "+ - "address: %v", address) - } - - port, err := strconv.Atoi(portStr) - if err != nil { - return nil, err - } - - return &lnwire.DNSAddress{ - Hostname: hostname, - Port: uint16(port), - }, nil - - case addressTypeOpaque: - opaque, err := hex.DecodeString(address) - if err != nil { - return nil, fmt.Errorf("unable to decode opaque "+ - "address: %v", address) - } - - return &lnwire.OpaqueAddrs{ - Payload: opaque, - }, nil - - default: - return nil, fmt.Errorf("unknown address type: %v", addrType) - } -} - -// batchNodeData holds all the related data for a batch of nodes. -type batchNodeData struct { - // features is a map from a DB node ID to the feature bits for that - // node. - features map[int64][]int - - // addresses is a map from a DB node ID to the node's addresses. - addresses map[int64][]nodeAddress - - // extraFields is a map from a DB node ID to the extra signed fields - // for that node. - extraFields map[int64]map[uint64][]byte -} - -// nodeAddress holds the address type, position and address string for a -// node. This is used to batch the fetching of node addresses. -type nodeAddress struct { - addrType dbAddressType - position int32 - address string -} - -// batchLoadNodeData loads all related data for a batch of node IDs using the -// provided SQLQueries interface. It returns a batchNodeData instance containing -// the node features, addresses and extra signed fields. -func batchLoadNodeData(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, nodeIDs []int64) (*batchNodeData, error) { - - // Batch load the node features. - features, err := batchLoadNodeFeaturesHelper(ctx, cfg, db, nodeIDs) - if err != nil { - return nil, fmt.Errorf("unable to batch load node "+ - "features: %w", err) - } - - // Batch load the node addresses. - addrs, err := batchLoadNodeAddressesHelper(ctx, cfg, db, nodeIDs) - if err != nil { - return nil, fmt.Errorf("unable to batch load node "+ - "addresses: %w", err) - } - - // Batch load the node extra signed fields. - extraTypes, err := batchLoadNodeExtraTypesHelper(ctx, cfg, db, nodeIDs) - if err != nil { - return nil, fmt.Errorf("unable to batch load node extra "+ - "signed fields: %w", err) - } - - return &batchNodeData{ - features: features, - addresses: addrs, - extraFields: extraTypes, - }, nil -} - -// batchLoadNodeFeaturesHelper loads node features for a batch of node IDs -// using ExecuteBatchQuery wrapper around the GetNodeFeaturesBatch query. -func batchLoadNodeFeaturesHelper(ctx context.Context, - cfg *sqldb.QueryConfig, db SQLQueries, - nodeIDs []int64) (map[int64][]int, error) { - - features := make(map[int64][]int) - - return features, sqldb.ExecuteBatchQuery( - ctx, cfg, nodeIDs, - func(id int64) int64 { - return id - }, - func(ctx context.Context, ids []int64) ([]sqlc.GraphNodeFeature, - error) { - - return db.GetNodeFeaturesBatch(ctx, ids) - }, - func(ctx context.Context, feature sqlc.GraphNodeFeature) error { - features[feature.NodeID] = append( - features[feature.NodeID], - int(feature.FeatureBit), - ) - - return nil - }, - ) -} - -// batchLoadNodeAddressesHelper loads node addresses using ExecuteBatchQuery -// wrapper around the GetNodeAddressesBatch query. It returns a map from -// node ID to a slice of nodeAddress structs. -func batchLoadNodeAddressesHelper(ctx context.Context, - cfg *sqldb.QueryConfig, db SQLQueries, - nodeIDs []int64) (map[int64][]nodeAddress, error) { - - addrs := make(map[int64][]nodeAddress) - - return addrs, sqldb.ExecuteBatchQuery( - ctx, cfg, nodeIDs, - func(id int64) int64 { - return id - }, - func(ctx context.Context, ids []int64) ([]sqlc.GraphNodeAddress, - error) { - - return db.GetNodeAddressesBatch(ctx, ids) - }, - func(ctx context.Context, addr sqlc.GraphNodeAddress) error { - addrs[addr.NodeID] = append( - addrs[addr.NodeID], nodeAddress{ - addrType: dbAddressType(addr.Type), - position: addr.Position, - address: addr.Address, - }, - ) - - return nil - }, - ) -} - -// batchLoadNodeExtraTypesHelper loads node extra type bytes for a batch of -// node IDs using ExecuteBatchQuery wrapper around the GetNodeExtraTypesBatch -// query. -func batchLoadNodeExtraTypesHelper(ctx context.Context, - cfg *sqldb.QueryConfig, db SQLQueries, - nodeIDs []int64) (map[int64]map[uint64][]byte, error) { - - extraFields := make(map[int64]map[uint64][]byte) - - callback := func(ctx context.Context, - field sqlc.GraphNodeExtraType) error { - - if extraFields[field.NodeID] == nil { - extraFields[field.NodeID] = make(map[uint64][]byte) - } - extraFields[field.NodeID][uint64(field.Type)] = field.Value - - return nil - } - - return extraFields, sqldb.ExecuteBatchQuery( - ctx, cfg, nodeIDs, - func(id int64) int64 { - return id - }, - func(ctx context.Context, ids []int64) ( - []sqlc.GraphNodeExtraType, error) { - - return db.GetNodeExtraTypesBatch(ctx, ids) - }, - callback, - ) -} - -// buildChanPoliciesWithBatchData builds two models.ChannelEdgePolicy instances -// from the provided sqlc.GraphChannelPolicy records and the -// provided batchChannelData. -func buildChanPoliciesWithBatchData(dbPol1, dbPol2 *sqlc.GraphChannelPolicy, - channelID uint64, node1, node2 route.Vertex, - batchData *batchChannelData) (*models.ChannelEdgePolicy, - *models.ChannelEdgePolicy, error) { - - pol1, err := buildChanPolicyWithBatchData( - dbPol1, channelID, node2, batchData, - ) - if err != nil { - return nil, nil, fmt.Errorf("unable to build policy1: %w", err) - } - - pol2, err := buildChanPolicyWithBatchData( - dbPol2, channelID, node1, batchData, - ) - if err != nil { - return nil, nil, fmt.Errorf("unable to build policy2: %w", err) - } - - return pol1, pol2, nil -} - -// buildChanPolicyWithBatchData builds a models.ChannelEdgePolicy instance from -// the provided sqlc.GraphChannelPolicy and the provided batchChannelData. -func buildChanPolicyWithBatchData(dbPol *sqlc.GraphChannelPolicy, - channelID uint64, toNode route.Vertex, - batchData *batchChannelData) (*models.ChannelEdgePolicy, error) { - - if dbPol == nil { - return nil, nil - } - - var dbPol1Extras map[uint64][]byte - if extras, exists := batchData.policyExtras[dbPol.ID]; exists { - dbPol1Extras = extras - } else { - dbPol1Extras = make(map[uint64][]byte) - } - - return buildChanPolicy(*dbPol, channelID, dbPol1Extras, toNode) -} - -// batchChannelData holds all the related data for a batch of channels. -type batchChannelData struct { - // chanFeatures is a map from DB channel ID to a slice of feature bits. - chanfeatures map[int64][]int - - // chanExtras is a map from DB channel ID to a map of TLV type to - // extra signed field bytes. - chanExtraTypes map[int64]map[uint64][]byte - - // policyExtras is a map from DB channel policy ID to a map of TLV type - // to extra signed field bytes. - policyExtras map[int64]map[uint64][]byte -} - -// batchLoadChannelData loads all related data for batches of channels and -// policies. -func batchLoadChannelData(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, channelIDs []int64, - policyIDs []int64) (*batchChannelData, error) { - - batchData := &batchChannelData{ - chanfeatures: make(map[int64][]int), - chanExtraTypes: make(map[int64]map[uint64][]byte), - policyExtras: make(map[int64]map[uint64][]byte), - } - - // Batch load channel features and extras - var err error - if len(channelIDs) > 0 { - batchData.chanfeatures, err = batchLoadChannelFeaturesHelper( - ctx, cfg, db, channelIDs, - ) - if err != nil { - return nil, fmt.Errorf("unable to batch load "+ - "channel features: %w", err) - } - - batchData.chanExtraTypes, err = batchLoadChannelExtrasHelper( - ctx, cfg, db, channelIDs, - ) - if err != nil { - return nil, fmt.Errorf("unable to batch load "+ - "channel extras: %w", err) - } - } - - if len(policyIDs) > 0 { - policyExtras, err := batchLoadChannelPolicyExtrasHelper( - ctx, cfg, db, policyIDs, - ) - if err != nil { - return nil, fmt.Errorf("unable to batch load "+ - "policy extras: %w", err) - } - batchData.policyExtras = policyExtras - } - - return batchData, nil -} - -// batchLoadChannelFeaturesHelper loads channel features for a batch of -// channel IDs using ExecuteBatchQuery wrapper around the -// GetChannelFeaturesBatch query. It returns a map from DB channel ID to a -// slice of feature bits. -func batchLoadChannelFeaturesHelper(ctx context.Context, - cfg *sqldb.QueryConfig, db SQLQueries, - channelIDs []int64) (map[int64][]int, error) { - - features := make(map[int64][]int) - - return features, sqldb.ExecuteBatchQuery( - ctx, cfg, channelIDs, - func(id int64) int64 { - return id - }, - func(ctx context.Context, - ids []int64) ([]sqlc.GraphChannelFeature, error) { - - return db.GetChannelFeaturesBatch(ctx, ids) - }, - func(ctx context.Context, - feature sqlc.GraphChannelFeature) error { - - features[feature.ChannelID] = append( - features[feature.ChannelID], - int(feature.FeatureBit), - ) - - return nil - }, - ) -} - -// batchLoadChannelExtrasHelper loads channel extra types for a batch of -// channel IDs using ExecuteBatchQuery wrapper around the GetChannelExtrasBatch -// query. It returns a map from DB channel ID to a map of TLV type to extra -// signed field bytes. -func batchLoadChannelExtrasHelper(ctx context.Context, - cfg *sqldb.QueryConfig, db SQLQueries, - channelIDs []int64) (map[int64]map[uint64][]byte, error) { - - extras := make(map[int64]map[uint64][]byte) - - cb := func(ctx context.Context, - extra sqlc.GraphChannelExtraType) error { - - if extras[extra.ChannelID] == nil { - extras[extra.ChannelID] = make(map[uint64][]byte) - } - extras[extra.ChannelID][uint64(extra.Type)] = extra.Value - - return nil - } - - return extras, sqldb.ExecuteBatchQuery( - ctx, cfg, channelIDs, - func(id int64) int64 { - return id - }, - func(ctx context.Context, - ids []int64) ([]sqlc.GraphChannelExtraType, error) { - - return db.GetChannelExtrasBatch(ctx, ids) - }, cb, - ) -} - -// batchLoadChannelPolicyExtrasHelper loads channel policy extra types for a -// batch of policy IDs using ExecuteBatchQuery wrapper around the -// GetChannelPolicyExtraTypesBatch query. It returns a map from DB policy ID to -// a map of TLV type to extra signed field bytes. -func batchLoadChannelPolicyExtrasHelper(ctx context.Context, - cfg *sqldb.QueryConfig, db SQLQueries, - policyIDs []int64) (map[int64]map[uint64][]byte, error) { - - extras := make(map[int64]map[uint64][]byte) - - return extras, sqldb.ExecuteBatchQuery( - ctx, cfg, policyIDs, - func(id int64) int64 { - return id - }, - func(ctx context.Context, ids []int64) ( - []sqlc.GetChannelPolicyExtraTypesBatchRow, error) { - - return db.GetChannelPolicyExtraTypesBatch(ctx, ids) - }, - func(ctx context.Context, - row sqlc.GetChannelPolicyExtraTypesBatchRow) error { - - if extras[row.PolicyID] == nil { - extras[row.PolicyID] = make(map[uint64][]byte) - } - extras[row.PolicyID][uint64(row.Type)] = row.Value - - return nil - }, - ) -} - -// forEachNodePaginated executes a paginated query to process each node in the -// graph. It uses the provided SQLQueries interface to fetch nodes in batches -// and applies the provided processNode function to each node. -func forEachNodePaginated(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, protocol lnwire.GossipVersion, - processNode func(context.Context, int64, - *models.Node) error) error { - - pageQueryFunc := func(ctx context.Context, lastID int64, - limit int32) ([]sqlc.GraphNode, error) { - - return db.ListNodesPaginated( - ctx, sqlc.ListNodesPaginatedParams{ - Version: int16(protocol), - ID: lastID, - Limit: limit, - }, - ) - } - - extractPageCursor := func(node sqlc.GraphNode) int64 { - return node.ID - } - - collectFunc := func(node sqlc.GraphNode) (int64, error) { - return node.ID, nil - } - - batchQueryFunc := func(ctx context.Context, - nodeIDs []int64) (*batchNodeData, error) { - - return batchLoadNodeData(ctx, cfg, db, nodeIDs) - } - - processItem := func(ctx context.Context, dbNode sqlc.GraphNode, - batchData *batchNodeData) error { - - node, err := buildNodeWithBatchData(dbNode, batchData) - if err != nil { - return fmt.Errorf("unable to build "+ - "node(id=%d): %w", dbNode.ID, err) - } - - return processNode(ctx, dbNode.ID, node) - } - - return sqldb.ExecuteCollectAndBatchWithSharedDataQuery( - ctx, cfg, int64(-1), pageQueryFunc, extractPageCursor, - collectFunc, batchQueryFunc, processItem, - ) -} - -// forEachChannelWithPolicies executes a paginated query to process each channel -// with policies in the graph. -func forEachChannelWithPolicies(ctx context.Context, db SQLQueries, - cfg *SQLStoreConfig, processChannel func(*models.ChannelEdgeInfo, - *models.ChannelEdgePolicy, - *models.ChannelEdgePolicy) error) error { - - type channelBatchIDs struct { - channelID int64 - policyIDs []int64 - } - - pageQueryFunc := func(ctx context.Context, lastID int64, - limit int32) ([]sqlc.ListChannelsWithPoliciesPaginatedRow, - error) { - - return db.ListChannelsWithPoliciesPaginated( - ctx, sqlc.ListChannelsWithPoliciesPaginatedParams{ - Version: int16(lnwire.GossipVersion1), - ID: lastID, - Limit: limit, - }, - ) - } - - extractPageCursor := func( - row sqlc.ListChannelsWithPoliciesPaginatedRow) int64 { - - return row.GraphChannel.ID - } - - collectFunc := func(row sqlc.ListChannelsWithPoliciesPaginatedRow) ( - channelBatchIDs, error) { - - ids := channelBatchIDs{ - channelID: row.GraphChannel.ID, - } - - // Extract policy IDs from the row. - dbPol1, dbPol2, err := extractChannelPolicies(row) - if err != nil { - return ids, err - } - - if dbPol1 != nil { - ids.policyIDs = append(ids.policyIDs, dbPol1.ID) - } - if dbPol2 != nil { - ids.policyIDs = append(ids.policyIDs, dbPol2.ID) - } - - return ids, nil - } - - batchDataFunc := func(ctx context.Context, - allIDs []channelBatchIDs) (*batchChannelData, error) { - - // Separate channel IDs from policy IDs. - var ( - channelIDs = make([]int64, len(allIDs)) - policyIDs = make([]int64, 0, len(allIDs)*2) - ) - - for i, ids := range allIDs { - channelIDs[i] = ids.channelID - policyIDs = append(policyIDs, ids.policyIDs...) - } - - return batchLoadChannelData( - ctx, cfg.QueryCfg, db, channelIDs, policyIDs, - ) - } - - processItem := func(ctx context.Context, - row sqlc.ListChannelsWithPoliciesPaginatedRow, - batchData *batchChannelData) error { - - node1, node2, err := buildNodeVertices( - row.Node1Pubkey, row.Node2Pubkey, - ) - if err != nil { - return err - } - - edge, err := buildEdgeInfoWithBatchData( - cfg.ChainHash, row.GraphChannel, node1, node2, - batchData, - ) - if err != nil { - return fmt.Errorf("unable to build channel info: %w", - err) - } - - dbPol1, dbPol2, err := extractChannelPolicies(row) - if err != nil { - return err - } - - p1, p2, err := buildChanPoliciesWithBatchData( - dbPol1, dbPol2, edge.ChannelID, node1, node2, batchData, - ) - if err != nil { - return err - } - - return processChannel(edge, p1, p2) - } - - return sqldb.ExecuteCollectAndBatchWithSharedDataQuery( - ctx, cfg.QueryCfg, int64(-1), pageQueryFunc, extractPageCursor, - collectFunc, batchDataFunc, processItem, - ) -} diff --git a/graph/db/migration1/sqlc/db.go b/graph/db/migration1/sqlc/db.go deleted file mode 100644 index e4d78283b..000000000 --- a/graph/db/migration1/sqlc/db.go +++ /dev/null @@ -1,31 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.29.0 - -package sqlc - -import ( - "context" - "database/sql" -) - -type DBTX interface { - ExecContext(context.Context, string, ...interface{}) (sql.Result, error) - PrepareContext(context.Context, string) (*sql.Stmt, error) - QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) - QueryRowContext(context.Context, string, ...interface{}) *sql.Row -} - -func New(db DBTX) *Queries { - return &Queries{db: db} -} - -type Queries struct { - db DBTX -} - -func (q *Queries) WithTx(tx *sql.Tx) *Queries { - return &Queries{ - db: tx, - } -} diff --git a/graph/db/migration1/sqlc/db_custom.go b/graph/db/migration1/sqlc/db_custom.go deleted file mode 100644 index f7bc49918..000000000 --- a/graph/db/migration1/sqlc/db_custom.go +++ /dev/null @@ -1,163 +0,0 @@ -package sqlc - -import ( - "fmt" - "strings" -) - -// makeQueryParams generates a string of query parameters for a SQL query. It is -// meant to replace the `?` placeholders in a SQL query with numbered parameters -// like `$1`, `$2`, etc. This is required for the sqlc /*SLICE:*/ -// workaround. See scripts/gen_sqlc_docker.sh for more details. -func makeQueryParams(numTotalArgs, numListArgs int) string { - if numListArgs == 0 { - return "" - } - - var b strings.Builder - - // Pre-allocate a rough estimation of the buffer size to avoid - // re-allocations. A parameter like $1000, takes 6 bytes. - b.Grow(numListArgs * 6) - - diff := numTotalArgs - numListArgs - for i := 0; i < numListArgs; i++ { - if i > 0 { - // We don't need to check the error here because the - // WriteString method of strings.Builder always returns - // nil. - _, _ = b.WriteString(",") - } - - // We don't need to check the error here because the - // Write method (called by fmt.Fprintf) of strings.Builder - // always returns nil. - _, _ = fmt.Fprintf(&b, "$%d", i+diff+1) - } - - return b.String() -} - -// ChannelAndNodes is an interface that provides access to a channel and its -// two nodes. -type ChannelAndNodes interface { - // Channel returns the GraphChannel associated with this interface. - Channel() GraphChannel - - // Node1 returns the first GraphNode associated with this channel. - Node1() GraphNode - - // Node2 returns the second GraphNode associated with this channel. - Node2() GraphNode -} - -// Channel returns the GraphChannel associated with this interface. -// -// NOTE: This method is part of the ChannelAndNodes interface. -func (r GetChannelsByPolicyLastUpdateRangeRow) Channel() GraphChannel { - return r.GraphChannel -} - -// Node1 returns the first GraphNode associated with this channel. -// -// NOTE: This method is part of the ChannelAndNodes interface. -func (r GetChannelsByPolicyLastUpdateRangeRow) Node1() GraphNode { - return r.GraphNode -} - -// Node2 returns the second GraphNode associated with this channel. -// -// NOTE: This method is part of the ChannelAndNodes interface. -func (r GetChannelsByPolicyLastUpdateRangeRow) Node2() GraphNode { - return r.GraphNode_2 -} - -// ChannelAndNodeIDs is an interface that provides access to a channel and its -// two node public keys. -type ChannelAndNodeIDs interface { - // Channel returns the GraphChannel associated with this interface. - Channel() GraphChannel - - // Node1Pub returns the public key of the first node as a byte slice. - Node1Pub() []byte - - // Node2Pub returns the public key of the second node as a byte slice. - Node2Pub() []byte -} - -// Channel returns the GraphChannel associated with this interface. -// -// NOTE: This method is part of the ChannelAndNodeIDs interface. -func (r GetChannelsBySCIDWithPoliciesRow) Channel() GraphChannel { - return r.GraphChannel -} - -// Node1Pub returns the public key of the first node as a byte slice. -// -// NOTE: This method is part of the ChannelAndNodeIDs interface. -func (r GetChannelsBySCIDWithPoliciesRow) Node1Pub() []byte { - return r.GraphNode.PubKey -} - -// Node2Pub returns the public key of the second node as a byte slice. -// -// NOTE: This method is part of the ChannelAndNodeIDs interface. -func (r GetChannelsBySCIDWithPoliciesRow) Node2Pub() []byte { - return r.GraphNode_2.PubKey -} - -// Node1 returns the first GraphNode associated with this channel. -// -// NOTE: This method is part of the ChannelAndNodes interface. -func (r GetChannelsBySCIDWithPoliciesRow) Node1() GraphNode { - return r.GraphNode -} - -// Node2 returns the second GraphNode associated with this channel. -// -// NOTE: This method is part of the ChannelAndNodes interface. -func (r GetChannelsBySCIDWithPoliciesRow) Node2() GraphNode { - return r.GraphNode_2 -} - -// Channel returns the GraphChannel associated with this interface. -// -// NOTE: This method is part of the ChannelAndNodeIDs interface. -func (r GetChannelsByOutpointsRow) Channel() GraphChannel { - return r.GraphChannel -} - -// Node1Pub returns the public key of the first node as a byte slice. -// -// NOTE: This method is part of the ChannelAndNodeIDs interface. -func (r GetChannelsByOutpointsRow) Node1Pub() []byte { - return r.Node1Pubkey -} - -// Node2Pub returns the public key of the second node as a byte slice. -// -// NOTE: This method is part of the ChannelAndNodeIDs interface. -func (r GetChannelsByOutpointsRow) Node2Pub() []byte { - return r.Node2Pubkey -} - -// Channel returns the GraphChannel associated with this interface. -// -// NOTE: This method is part of the ChannelAndNodeIDs interface. -func (r GetChannelsBySCIDRangeRow) Channel() GraphChannel { - return r.GraphChannel -} - -// Node1Pub returns the public key of the first node as a byte slice. -// -// NOTE: This method is part of the ChannelAndNodeIDs interface. -func (r GetChannelsBySCIDRangeRow) Node1Pub() []byte { - return r.Node1PubKey -} - -// Node2Pub returns the public key of the second node as a byte slice. -// -// NOTE: This method is part of the ChannelAndNodeIDs interface. -func (r GetChannelsBySCIDRangeRow) Node2Pub() []byte { - return r.Node2PubKey -} diff --git a/graph/db/migration1/sqlc/graph.sql.go b/graph/db/migration1/sqlc/graph.sql.go deleted file mode 100644 index 9c2702737..000000000 --- a/graph/db/migration1/sqlc/graph.sql.go +++ /dev/null @@ -1,3769 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.29.0 -// source: graph.sql - -package sqlc - -import ( - "context" - "database/sql" - "strings" -) - -const addSourceNode = `-- name: AddSourceNode :exec -/* ───────────────────────────────────────────── - graph_source_nodes table queries - ───────────────────────────────────────────── -*/ - -INSERT INTO graph_source_nodes (node_id) -VALUES ($1) -ON CONFLICT (node_id) DO NOTHING -` - -func (q *Queries) AddSourceNode(ctx context.Context, nodeID int64) error { - _, err := q.db.ExecContext(ctx, addSourceNode, nodeID) - return err -} - -const addV1ChannelProof = `-- name: AddV1ChannelProof :execresult -UPDATE graph_channels -SET node_1_signature = $2, - node_2_signature = $3, - bitcoin_1_signature = $4, - bitcoin_2_signature = $5 -WHERE scid = $1 - AND version = 1 -` - -type AddV1ChannelProofParams struct { - Scid []byte - Node1Signature []byte - Node2Signature []byte - Bitcoin1Signature []byte - Bitcoin2Signature []byte -} - -func (q *Queries) AddV1ChannelProof(ctx context.Context, arg AddV1ChannelProofParams) (sql.Result, error) { - return q.db.ExecContext(ctx, addV1ChannelProof, - arg.Scid, - arg.Node1Signature, - arg.Node2Signature, - arg.Bitcoin1Signature, - arg.Bitcoin2Signature, - ) -} - -const countZombieChannels = `-- name: CountZombieChannels :one -SELECT COUNT(*) -FROM graph_zombie_channels -WHERE version = $1 -` - -func (q *Queries) CountZombieChannels(ctx context.Context, version int16) (int64, error) { - row := q.db.QueryRowContext(ctx, countZombieChannels, version) - var count int64 - err := row.Scan(&count) - return count, err -} - -const createChannel = `-- name: CreateChannel :one -/* ───────────────────────────────────────────── - graph_channels table queries - ───────────────────────────────────────────── -*/ - -INSERT INTO graph_channels ( - version, scid, node_id_1, node_id_2, - outpoint, capacity, bitcoin_key_1, bitcoin_key_2, - node_1_signature, node_2_signature, bitcoin_1_signature, - bitcoin_2_signature -) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 -) -RETURNING id -` - -type CreateChannelParams struct { - Version int16 - Scid []byte - NodeID1 int64 - NodeID2 int64 - Outpoint string - Capacity sql.NullInt64 - BitcoinKey1 []byte - BitcoinKey2 []byte - Node1Signature []byte - Node2Signature []byte - Bitcoin1Signature []byte - Bitcoin2Signature []byte -} - -func (q *Queries) CreateChannel(ctx context.Context, arg CreateChannelParams) (int64, error) { - row := q.db.QueryRowContext(ctx, createChannel, - arg.Version, - arg.Scid, - arg.NodeID1, - arg.NodeID2, - arg.Outpoint, - arg.Capacity, - arg.BitcoinKey1, - arg.BitcoinKey2, - arg.Node1Signature, - arg.Node2Signature, - arg.Bitcoin1Signature, - arg.Bitcoin2Signature, - ) - var id int64 - err := row.Scan(&id) - return id, err -} - -const deleteChannelPolicyExtraTypes = `-- name: DeleteChannelPolicyExtraTypes :exec -DELETE FROM graph_channel_policy_extra_types -WHERE channel_policy_id = $1 -` - -func (q *Queries) DeleteChannelPolicyExtraTypes(ctx context.Context, channelPolicyID int64) error { - _, err := q.db.ExecContext(ctx, deleteChannelPolicyExtraTypes, channelPolicyID) - return err -} - -const deleteChannels = `-- name: DeleteChannels :exec -DELETE FROM graph_channels -WHERE id IN (/*SLICE:ids*/?) -` - -func (q *Queries) DeleteChannels(ctx context.Context, ids []int64) error { - query := deleteChannels - var queryParams []interface{} - if len(ids) > 0 { - for _, v := range ids { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:ids*/?", makeQueryParams(len(queryParams), len(ids)), 1) - } else { - query = strings.Replace(query, "/*SLICE:ids*/?", "NULL", 1) - } - _, err := q.db.ExecContext(ctx, query, queryParams...) - return err -} - -const deleteExtraNodeType = `-- name: DeleteExtraNodeType :exec -DELETE FROM graph_node_extra_types -WHERE node_id = $1 - AND type = $2 -` - -type DeleteExtraNodeTypeParams struct { - NodeID int64 - Type int64 -} - -func (q *Queries) DeleteExtraNodeType(ctx context.Context, arg DeleteExtraNodeTypeParams) error { - _, err := q.db.ExecContext(ctx, deleteExtraNodeType, arg.NodeID, arg.Type) - return err -} - -const deleteNode = `-- name: DeleteNode :exec -DELETE FROM graph_nodes -WHERE id = $1 -` - -func (q *Queries) DeleteNode(ctx context.Context, id int64) error { - _, err := q.db.ExecContext(ctx, deleteNode, id) - return err -} - -const deleteNodeAddresses = `-- name: DeleteNodeAddresses :exec -DELETE FROM graph_node_addresses -WHERE node_id = $1 -` - -func (q *Queries) DeleteNodeAddresses(ctx context.Context, nodeID int64) error { - _, err := q.db.ExecContext(ctx, deleteNodeAddresses, nodeID) - return err -} - -const deleteNodeByPubKey = `-- name: DeleteNodeByPubKey :execresult -DELETE FROM graph_nodes -WHERE pub_key = $1 - AND version = $2 -` - -type DeleteNodeByPubKeyParams struct { - PubKey []byte - Version int16 -} - -func (q *Queries) DeleteNodeByPubKey(ctx context.Context, arg DeleteNodeByPubKeyParams) (sql.Result, error) { - return q.db.ExecContext(ctx, deleteNodeByPubKey, arg.PubKey, arg.Version) -} - -const deleteNodeFeature = `-- name: DeleteNodeFeature :exec -DELETE FROM graph_node_features -WHERE node_id = $1 - AND feature_bit = $2 -` - -type DeleteNodeFeatureParams struct { - NodeID int64 - FeatureBit int32 -} - -func (q *Queries) DeleteNodeFeature(ctx context.Context, arg DeleteNodeFeatureParams) error { - _, err := q.db.ExecContext(ctx, deleteNodeFeature, arg.NodeID, arg.FeatureBit) - return err -} - -const deletePruneLogEntriesInRange = `-- name: DeletePruneLogEntriesInRange :exec -DELETE FROM graph_prune_log -WHERE block_height >= $1 - AND block_height <= $2 -` - -type DeletePruneLogEntriesInRangeParams struct { - StartHeight int64 - EndHeight int64 -} - -func (q *Queries) DeletePruneLogEntriesInRange(ctx context.Context, arg DeletePruneLogEntriesInRangeParams) error { - _, err := q.db.ExecContext(ctx, deletePruneLogEntriesInRange, arg.StartHeight, arg.EndHeight) - return err -} - -const deleteUnconnectedNodes = `-- name: DeleteUnconnectedNodes :many -DELETE FROM graph_nodes -WHERE - -- Ignore any of our source nodes. - NOT EXISTS ( - SELECT 1 - FROM graph_source_nodes sn - WHERE sn.node_id = graph_nodes.id - ) - -- Select all nodes that do not have any channels. - AND NOT EXISTS ( - SELECT 1 - FROM graph_channels c - WHERE c.node_id_1 = graph_nodes.id OR c.node_id_2 = graph_nodes.id -) RETURNING pub_key -` - -func (q *Queries) DeleteUnconnectedNodes(ctx context.Context) ([][]byte, error) { - rows, err := q.db.QueryContext(ctx, deleteUnconnectedNodes) - if err != nil { - return nil, err - } - defer rows.Close() - var items [][]byte - for rows.Next() { - var pub_key []byte - if err := rows.Scan(&pub_key); err != nil { - return nil, err - } - items = append(items, pub_key) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const deleteZombieChannel = `-- name: DeleteZombieChannel :execresult -DELETE FROM graph_zombie_channels -WHERE scid = $1 -AND version = $2 -` - -type DeleteZombieChannelParams struct { - Scid []byte - Version int16 -} - -func (q *Queries) DeleteZombieChannel(ctx context.Context, arg DeleteZombieChannelParams) (sql.Result, error) { - return q.db.ExecContext(ctx, deleteZombieChannel, arg.Scid, arg.Version) -} - -const getChannelAndNodesBySCID = `-- name: GetChannelAndNodesBySCID :one -SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, - n1.pub_key AS node1_pub_key, - n2.pub_key AS node2_pub_key -FROM graph_channels c - JOIN graph_nodes n1 ON c.node_id_1 = n1.id - JOIN graph_nodes n2 ON c.node_id_2 = n2.id -WHERE c.scid = $1 - AND c.version = $2 -` - -type GetChannelAndNodesBySCIDParams struct { - Scid []byte - Version int16 -} - -type GetChannelAndNodesBySCIDRow struct { - ID int64 - Version int16 - Scid []byte - NodeID1 int64 - NodeID2 int64 - Outpoint string - Capacity sql.NullInt64 - BitcoinKey1 []byte - BitcoinKey2 []byte - Node1Signature []byte - Node2Signature []byte - Bitcoin1Signature []byte - Bitcoin2Signature []byte - Node1PubKey []byte - Node2PubKey []byte -} - -func (q *Queries) GetChannelAndNodesBySCID(ctx context.Context, arg GetChannelAndNodesBySCIDParams) (GetChannelAndNodesBySCIDRow, error) { - row := q.db.QueryRowContext(ctx, getChannelAndNodesBySCID, arg.Scid, arg.Version) - var i GetChannelAndNodesBySCIDRow - err := row.Scan( - &i.ID, - &i.Version, - &i.Scid, - &i.NodeID1, - &i.NodeID2, - &i.Outpoint, - &i.Capacity, - &i.BitcoinKey1, - &i.BitcoinKey2, - &i.Node1Signature, - &i.Node2Signature, - &i.Bitcoin1Signature, - &i.Bitcoin2Signature, - &i.Node1PubKey, - &i.Node2PubKey, - ) - return i, err -} - -const getChannelByOutpointWithPolicies = `-- name: GetChannelByOutpointWithPolicies :one -SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, - - n1.pub_key AS node1_pubkey, - n2.pub_key AS node2_pubkey, - - -- Node 1 policy - cp1.id AS policy_1_id, - cp1.node_id AS policy_1_node_id, - cp1.version AS policy_1_version, - cp1.timelock AS policy_1_timelock, - cp1.fee_ppm AS policy_1_fee_ppm, - cp1.base_fee_msat AS policy_1_base_fee_msat, - cp1.min_htlc_msat AS policy_1_min_htlc_msat, - cp1.max_htlc_msat AS policy_1_max_htlc_msat, - cp1.last_update AS policy_1_last_update, - cp1.disabled AS policy_1_disabled, - cp1.inbound_base_fee_msat AS policy1_inbound_base_fee_msat, - cp1.inbound_fee_rate_milli_msat AS policy1_inbound_fee_rate_milli_msat, - cp1.message_flags AS policy_1_message_flags, - cp1.channel_flags AS policy_1_channel_flags, - cp1.signature AS policy_1_signature, - - -- Node 2 policy - cp2.id AS policy_2_id, - cp2.node_id AS policy_2_node_id, - cp2.version AS policy_2_version, - cp2.timelock AS policy_2_timelock, - cp2.fee_ppm AS policy_2_fee_ppm, - cp2.base_fee_msat AS policy_2_base_fee_msat, - cp2.min_htlc_msat AS policy_2_min_htlc_msat, - cp2.max_htlc_msat AS policy_2_max_htlc_msat, - cp2.last_update AS policy_2_last_update, - cp2.disabled AS policy_2_disabled, - cp2.inbound_base_fee_msat AS policy2_inbound_base_fee_msat, - cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, - cp2.message_flags AS policy_2_message_flags, - cp2.channel_flags AS policy_2_channel_flags, - cp2.signature AS policy_2_signature -FROM graph_channels c - JOIN graph_nodes n1 ON c.node_id_1 = n1.id - JOIN graph_nodes n2 ON c.node_id_2 = n2.id - LEFT JOIN graph_channel_policies cp1 - ON cp1.channel_id = c.id AND cp1.node_id = c.node_id_1 AND cp1.version = c.version - LEFT JOIN graph_channel_policies cp2 - ON cp2.channel_id = c.id AND cp2.node_id = c.node_id_2 AND cp2.version = c.version -WHERE c.outpoint = $1 AND c.version = $2 -` - -type GetChannelByOutpointWithPoliciesParams struct { - Outpoint string - Version int16 -} - -type GetChannelByOutpointWithPoliciesRow struct { - GraphChannel GraphChannel - Node1Pubkey []byte - Node2Pubkey []byte - Policy1ID sql.NullInt64 - Policy1NodeID sql.NullInt64 - Policy1Version sql.NullInt16 - Policy1Timelock sql.NullInt32 - Policy1FeePpm sql.NullInt64 - Policy1BaseFeeMsat sql.NullInt64 - Policy1MinHtlcMsat sql.NullInt64 - Policy1MaxHtlcMsat sql.NullInt64 - Policy1LastUpdate sql.NullInt64 - Policy1Disabled sql.NullBool - Policy1InboundBaseFeeMsat sql.NullInt64 - Policy1InboundFeeRateMilliMsat sql.NullInt64 - Policy1MessageFlags sql.NullInt16 - Policy1ChannelFlags sql.NullInt16 - Policy1Signature []byte - Policy2ID sql.NullInt64 - Policy2NodeID sql.NullInt64 - Policy2Version sql.NullInt16 - Policy2Timelock sql.NullInt32 - Policy2FeePpm sql.NullInt64 - Policy2BaseFeeMsat sql.NullInt64 - Policy2MinHtlcMsat sql.NullInt64 - Policy2MaxHtlcMsat sql.NullInt64 - Policy2LastUpdate sql.NullInt64 - Policy2Disabled sql.NullBool - Policy2InboundBaseFeeMsat sql.NullInt64 - Policy2InboundFeeRateMilliMsat sql.NullInt64 - Policy2MessageFlags sql.NullInt16 - Policy2ChannelFlags sql.NullInt16 - Policy2Signature []byte -} - -func (q *Queries) GetChannelByOutpointWithPolicies(ctx context.Context, arg GetChannelByOutpointWithPoliciesParams) (GetChannelByOutpointWithPoliciesRow, error) { - row := q.db.QueryRowContext(ctx, getChannelByOutpointWithPolicies, arg.Outpoint, arg.Version) - var i GetChannelByOutpointWithPoliciesRow - err := row.Scan( - &i.GraphChannel.ID, - &i.GraphChannel.Version, - &i.GraphChannel.Scid, - &i.GraphChannel.NodeID1, - &i.GraphChannel.NodeID2, - &i.GraphChannel.Outpoint, - &i.GraphChannel.Capacity, - &i.GraphChannel.BitcoinKey1, - &i.GraphChannel.BitcoinKey2, - &i.GraphChannel.Node1Signature, - &i.GraphChannel.Node2Signature, - &i.GraphChannel.Bitcoin1Signature, - &i.GraphChannel.Bitcoin2Signature, - &i.Node1Pubkey, - &i.Node2Pubkey, - &i.Policy1ID, - &i.Policy1NodeID, - &i.Policy1Version, - &i.Policy1Timelock, - &i.Policy1FeePpm, - &i.Policy1BaseFeeMsat, - &i.Policy1MinHtlcMsat, - &i.Policy1MaxHtlcMsat, - &i.Policy1LastUpdate, - &i.Policy1Disabled, - &i.Policy1InboundBaseFeeMsat, - &i.Policy1InboundFeeRateMilliMsat, - &i.Policy1MessageFlags, - &i.Policy1ChannelFlags, - &i.Policy1Signature, - &i.Policy2ID, - &i.Policy2NodeID, - &i.Policy2Version, - &i.Policy2Timelock, - &i.Policy2FeePpm, - &i.Policy2BaseFeeMsat, - &i.Policy2MinHtlcMsat, - &i.Policy2MaxHtlcMsat, - &i.Policy2LastUpdate, - &i.Policy2Disabled, - &i.Policy2InboundBaseFeeMsat, - &i.Policy2InboundFeeRateMilliMsat, - &i.Policy2MessageFlags, - &i.Policy2ChannelFlags, - &i.Policy2Signature, - ) - return i, err -} - -const getChannelBySCID = `-- name: GetChannelBySCID :one -SELECT id, version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, bitcoin_2_signature FROM graph_channels -WHERE scid = $1 AND version = $2 -` - -type GetChannelBySCIDParams struct { - Scid []byte - Version int16 -} - -func (q *Queries) GetChannelBySCID(ctx context.Context, arg GetChannelBySCIDParams) (GraphChannel, error) { - row := q.db.QueryRowContext(ctx, getChannelBySCID, arg.Scid, arg.Version) - var i GraphChannel - err := row.Scan( - &i.ID, - &i.Version, - &i.Scid, - &i.NodeID1, - &i.NodeID2, - &i.Outpoint, - &i.Capacity, - &i.BitcoinKey1, - &i.BitcoinKey2, - &i.Node1Signature, - &i.Node2Signature, - &i.Bitcoin1Signature, - &i.Bitcoin2Signature, - ) - return i, err -} - -const getChannelBySCIDWithPolicies = `-- name: GetChannelBySCIDWithPolicies :one -SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, - n1.id, n1.version, n1.pub_key, n1.alias, n1.last_update, n1.color, n1.signature, - n2.id, n2.version, n2.pub_key, n2.alias, n2.last_update, n2.color, n2.signature, - - -- Policy 1 - cp1.id AS policy1_id, - cp1.node_id AS policy1_node_id, - cp1.version AS policy1_version, - cp1.timelock AS policy1_timelock, - cp1.fee_ppm AS policy1_fee_ppm, - cp1.base_fee_msat AS policy1_base_fee_msat, - cp1.min_htlc_msat AS policy1_min_htlc_msat, - cp1.max_htlc_msat AS policy1_max_htlc_msat, - cp1.last_update AS policy1_last_update, - cp1.disabled AS policy1_disabled, - cp1.inbound_base_fee_msat AS policy1_inbound_base_fee_msat, - cp1.inbound_fee_rate_milli_msat AS policy1_inbound_fee_rate_milli_msat, - cp1.message_flags AS policy1_message_flags, - cp1.channel_flags AS policy1_channel_flags, - cp1.signature AS policy1_signature, - - -- Policy 2 - cp2.id AS policy2_id, - cp2.node_id AS policy2_node_id, - cp2.version AS policy2_version, - cp2.timelock AS policy2_timelock, - cp2.fee_ppm AS policy2_fee_ppm, - cp2.base_fee_msat AS policy2_base_fee_msat, - cp2.min_htlc_msat AS policy2_min_htlc_msat, - cp2.max_htlc_msat AS policy2_max_htlc_msat, - cp2.last_update AS policy2_last_update, - cp2.disabled AS policy2_disabled, - cp2.inbound_base_fee_msat AS policy2_inbound_base_fee_msat, - cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, - cp2.message_flags AS policy_2_message_flags, - cp2.channel_flags AS policy_2_channel_flags, - cp2.signature AS policy2_signature - -FROM graph_channels c - JOIN graph_nodes n1 ON c.node_id_1 = n1.id - JOIN graph_nodes n2 ON c.node_id_2 = n2.id - LEFT JOIN graph_channel_policies cp1 - ON cp1.channel_id = c.id AND cp1.node_id = c.node_id_1 AND cp1.version = c.version - LEFT JOIN graph_channel_policies cp2 - ON cp2.channel_id = c.id AND cp2.node_id = c.node_id_2 AND cp2.version = c.version -WHERE c.scid = $1 - AND c.version = $2 -` - -type GetChannelBySCIDWithPoliciesParams struct { - Scid []byte - Version int16 -} - -type GetChannelBySCIDWithPoliciesRow struct { - GraphChannel GraphChannel - GraphNode GraphNode - GraphNode_2 GraphNode - Policy1ID sql.NullInt64 - Policy1NodeID sql.NullInt64 - Policy1Version sql.NullInt16 - Policy1Timelock sql.NullInt32 - Policy1FeePpm sql.NullInt64 - Policy1BaseFeeMsat sql.NullInt64 - Policy1MinHtlcMsat sql.NullInt64 - Policy1MaxHtlcMsat sql.NullInt64 - Policy1LastUpdate sql.NullInt64 - Policy1Disabled sql.NullBool - Policy1InboundBaseFeeMsat sql.NullInt64 - Policy1InboundFeeRateMilliMsat sql.NullInt64 - Policy1MessageFlags sql.NullInt16 - Policy1ChannelFlags sql.NullInt16 - Policy1Signature []byte - Policy2ID sql.NullInt64 - Policy2NodeID sql.NullInt64 - Policy2Version sql.NullInt16 - Policy2Timelock sql.NullInt32 - Policy2FeePpm sql.NullInt64 - Policy2BaseFeeMsat sql.NullInt64 - Policy2MinHtlcMsat sql.NullInt64 - Policy2MaxHtlcMsat sql.NullInt64 - Policy2LastUpdate sql.NullInt64 - Policy2Disabled sql.NullBool - Policy2InboundBaseFeeMsat sql.NullInt64 - Policy2InboundFeeRateMilliMsat sql.NullInt64 - Policy2MessageFlags sql.NullInt16 - Policy2ChannelFlags sql.NullInt16 - Policy2Signature []byte -} - -func (q *Queries) GetChannelBySCIDWithPolicies(ctx context.Context, arg GetChannelBySCIDWithPoliciesParams) (GetChannelBySCIDWithPoliciesRow, error) { - row := q.db.QueryRowContext(ctx, getChannelBySCIDWithPolicies, arg.Scid, arg.Version) - var i GetChannelBySCIDWithPoliciesRow - err := row.Scan( - &i.GraphChannel.ID, - &i.GraphChannel.Version, - &i.GraphChannel.Scid, - &i.GraphChannel.NodeID1, - &i.GraphChannel.NodeID2, - &i.GraphChannel.Outpoint, - &i.GraphChannel.Capacity, - &i.GraphChannel.BitcoinKey1, - &i.GraphChannel.BitcoinKey2, - &i.GraphChannel.Node1Signature, - &i.GraphChannel.Node2Signature, - &i.GraphChannel.Bitcoin1Signature, - &i.GraphChannel.Bitcoin2Signature, - &i.GraphNode.ID, - &i.GraphNode.Version, - &i.GraphNode.PubKey, - &i.GraphNode.Alias, - &i.GraphNode.LastUpdate, - &i.GraphNode.Color, - &i.GraphNode.Signature, - &i.GraphNode_2.ID, - &i.GraphNode_2.Version, - &i.GraphNode_2.PubKey, - &i.GraphNode_2.Alias, - &i.GraphNode_2.LastUpdate, - &i.GraphNode_2.Color, - &i.GraphNode_2.Signature, - &i.Policy1ID, - &i.Policy1NodeID, - &i.Policy1Version, - &i.Policy1Timelock, - &i.Policy1FeePpm, - &i.Policy1BaseFeeMsat, - &i.Policy1MinHtlcMsat, - &i.Policy1MaxHtlcMsat, - &i.Policy1LastUpdate, - &i.Policy1Disabled, - &i.Policy1InboundBaseFeeMsat, - &i.Policy1InboundFeeRateMilliMsat, - &i.Policy1MessageFlags, - &i.Policy1ChannelFlags, - &i.Policy1Signature, - &i.Policy2ID, - &i.Policy2NodeID, - &i.Policy2Version, - &i.Policy2Timelock, - &i.Policy2FeePpm, - &i.Policy2BaseFeeMsat, - &i.Policy2MinHtlcMsat, - &i.Policy2MaxHtlcMsat, - &i.Policy2LastUpdate, - &i.Policy2Disabled, - &i.Policy2InboundBaseFeeMsat, - &i.Policy2InboundFeeRateMilliMsat, - &i.Policy2MessageFlags, - &i.Policy2ChannelFlags, - &i.Policy2Signature, - ) - return i, err -} - -const getChannelExtrasBatch = `-- name: GetChannelExtrasBatch :many -SELECT - channel_id, - type, - value -FROM graph_channel_extra_types -WHERE channel_id IN (/*SLICE:chan_ids*/?) -ORDER BY channel_id, type -` - -func (q *Queries) GetChannelExtrasBatch(ctx context.Context, chanIds []int64) ([]GraphChannelExtraType, error) { - query := getChannelExtrasBatch - var queryParams []interface{} - if len(chanIds) > 0 { - for _, v := range chanIds { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:chan_ids*/?", makeQueryParams(len(queryParams), len(chanIds)), 1) - } else { - query = strings.Replace(query, "/*SLICE:chan_ids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GraphChannelExtraType - for rows.Next() { - var i GraphChannelExtraType - if err := rows.Scan(&i.ChannelID, &i.Type, &i.Value); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getChannelFeaturesBatch = `-- name: GetChannelFeaturesBatch :many -SELECT - channel_id, - feature_bit -FROM graph_channel_features -WHERE channel_id IN (/*SLICE:chan_ids*/?) -ORDER BY channel_id, feature_bit -` - -func (q *Queries) GetChannelFeaturesBatch(ctx context.Context, chanIds []int64) ([]GraphChannelFeature, error) { - query := getChannelFeaturesBatch - var queryParams []interface{} - if len(chanIds) > 0 { - for _, v := range chanIds { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:chan_ids*/?", makeQueryParams(len(queryParams), len(chanIds)), 1) - } else { - query = strings.Replace(query, "/*SLICE:chan_ids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GraphChannelFeature - for rows.Next() { - var i GraphChannelFeature - if err := rows.Scan(&i.ChannelID, &i.FeatureBit); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getChannelPolicyByChannelAndNode = `-- name: GetChannelPolicyByChannelAndNode :one -SELECT id, version, channel_id, node_id, timelock, fee_ppm, base_fee_msat, min_htlc_msat, max_htlc_msat, last_update, disabled, inbound_base_fee_msat, inbound_fee_rate_milli_msat, message_flags, channel_flags, signature -FROM graph_channel_policies -WHERE channel_id = $1 - AND node_id = $2 - AND version = $3 -` - -type GetChannelPolicyByChannelAndNodeParams struct { - ChannelID int64 - NodeID int64 - Version int16 -} - -func (q *Queries) GetChannelPolicyByChannelAndNode(ctx context.Context, arg GetChannelPolicyByChannelAndNodeParams) (GraphChannelPolicy, error) { - row := q.db.QueryRowContext(ctx, getChannelPolicyByChannelAndNode, arg.ChannelID, arg.NodeID, arg.Version) - var i GraphChannelPolicy - err := row.Scan( - &i.ID, - &i.Version, - &i.ChannelID, - &i.NodeID, - &i.Timelock, - &i.FeePpm, - &i.BaseFeeMsat, - &i.MinHtlcMsat, - &i.MaxHtlcMsat, - &i.LastUpdate, - &i.Disabled, - &i.InboundBaseFeeMsat, - &i.InboundFeeRateMilliMsat, - &i.MessageFlags, - &i.ChannelFlags, - &i.Signature, - ) - return i, err -} - -const getChannelPolicyExtraTypesBatch = `-- name: GetChannelPolicyExtraTypesBatch :many -SELECT - channel_policy_id as policy_id, - type, - value -FROM graph_channel_policy_extra_types -WHERE channel_policy_id IN (/*SLICE:policy_ids*/?) -ORDER BY channel_policy_id, type -` - -type GetChannelPolicyExtraTypesBatchRow struct { - PolicyID int64 - Type int64 - Value []byte -} - -func (q *Queries) GetChannelPolicyExtraTypesBatch(ctx context.Context, policyIds []int64) ([]GetChannelPolicyExtraTypesBatchRow, error) { - query := getChannelPolicyExtraTypesBatch - var queryParams []interface{} - if len(policyIds) > 0 { - for _, v := range policyIds { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:policy_ids*/?", makeQueryParams(len(queryParams), len(policyIds)), 1) - } else { - query = strings.Replace(query, "/*SLICE:policy_ids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GetChannelPolicyExtraTypesBatchRow - for rows.Next() { - var i GetChannelPolicyExtraTypesBatchRow - if err := rows.Scan(&i.PolicyID, &i.Type, &i.Value); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getChannelsByIDs = `-- name: GetChannelsByIDs :many -SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, - - -- Minimal node data. - n1.id AS node1_id, - n1.pub_key AS node1_pub_key, - n2.id AS node2_id, - n2.pub_key AS node2_pub_key, - - -- Policy 1 - cp1.id AS policy1_id, - cp1.node_id AS policy1_node_id, - cp1.version AS policy1_version, - cp1.timelock AS policy1_timelock, - cp1.fee_ppm AS policy1_fee_ppm, - cp1.base_fee_msat AS policy1_base_fee_msat, - cp1.min_htlc_msat AS policy1_min_htlc_msat, - cp1.max_htlc_msat AS policy1_max_htlc_msat, - cp1.last_update AS policy1_last_update, - cp1.disabled AS policy1_disabled, - cp1.inbound_base_fee_msat AS policy1_inbound_base_fee_msat, - cp1.inbound_fee_rate_milli_msat AS policy1_inbound_fee_rate_milli_msat, - cp1.message_flags AS policy1_message_flags, - cp1.channel_flags AS policy1_channel_flags, - cp1.signature AS policy1_signature, - - -- Policy 2 - cp2.id AS policy2_id, - cp2.node_id AS policy2_node_id, - cp2.version AS policy2_version, - cp2.timelock AS policy2_timelock, - cp2.fee_ppm AS policy2_fee_ppm, - cp2.base_fee_msat AS policy2_base_fee_msat, - cp2.min_htlc_msat AS policy2_min_htlc_msat, - cp2.max_htlc_msat AS policy2_max_htlc_msat, - cp2.last_update AS policy2_last_update, - cp2.disabled AS policy2_disabled, - cp2.inbound_base_fee_msat AS policy2_inbound_base_fee_msat, - cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, - cp2.message_flags AS policy2_message_flags, - cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature - -FROM graph_channels c - JOIN graph_nodes n1 ON c.node_id_1 = n1.id - JOIN graph_nodes n2 ON c.node_id_2 = n2.id - LEFT JOIN graph_channel_policies cp1 - ON cp1.channel_id = c.id AND cp1.node_id = c.node_id_1 AND cp1.version = c.version - LEFT JOIN graph_channel_policies cp2 - ON cp2.channel_id = c.id AND cp2.node_id = c.node_id_2 AND cp2.version = c.version -WHERE c.id IN (/*SLICE:ids*/?) -` - -type GetChannelsByIDsRow struct { - GraphChannel GraphChannel - Node1ID int64 - Node1PubKey []byte - Node2ID int64 - Node2PubKey []byte - Policy1ID sql.NullInt64 - Policy1NodeID sql.NullInt64 - Policy1Version sql.NullInt16 - Policy1Timelock sql.NullInt32 - Policy1FeePpm sql.NullInt64 - Policy1BaseFeeMsat sql.NullInt64 - Policy1MinHtlcMsat sql.NullInt64 - Policy1MaxHtlcMsat sql.NullInt64 - Policy1LastUpdate sql.NullInt64 - Policy1Disabled sql.NullBool - Policy1InboundBaseFeeMsat sql.NullInt64 - Policy1InboundFeeRateMilliMsat sql.NullInt64 - Policy1MessageFlags sql.NullInt16 - Policy1ChannelFlags sql.NullInt16 - Policy1Signature []byte - Policy2ID sql.NullInt64 - Policy2NodeID sql.NullInt64 - Policy2Version sql.NullInt16 - Policy2Timelock sql.NullInt32 - Policy2FeePpm sql.NullInt64 - Policy2BaseFeeMsat sql.NullInt64 - Policy2MinHtlcMsat sql.NullInt64 - Policy2MaxHtlcMsat sql.NullInt64 - Policy2LastUpdate sql.NullInt64 - Policy2Disabled sql.NullBool - Policy2InboundBaseFeeMsat sql.NullInt64 - Policy2InboundFeeRateMilliMsat sql.NullInt64 - Policy2MessageFlags sql.NullInt16 - Policy2ChannelFlags sql.NullInt16 - Policy2Signature []byte -} - -func (q *Queries) GetChannelsByIDs(ctx context.Context, ids []int64) ([]GetChannelsByIDsRow, error) { - query := getChannelsByIDs - var queryParams []interface{} - if len(ids) > 0 { - for _, v := range ids { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:ids*/?", makeQueryParams(len(queryParams), len(ids)), 1) - } else { - query = strings.Replace(query, "/*SLICE:ids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GetChannelsByIDsRow - for rows.Next() { - var i GetChannelsByIDsRow - if err := rows.Scan( - &i.GraphChannel.ID, - &i.GraphChannel.Version, - &i.GraphChannel.Scid, - &i.GraphChannel.NodeID1, - &i.GraphChannel.NodeID2, - &i.GraphChannel.Outpoint, - &i.GraphChannel.Capacity, - &i.GraphChannel.BitcoinKey1, - &i.GraphChannel.BitcoinKey2, - &i.GraphChannel.Node1Signature, - &i.GraphChannel.Node2Signature, - &i.GraphChannel.Bitcoin1Signature, - &i.GraphChannel.Bitcoin2Signature, - &i.Node1ID, - &i.Node1PubKey, - &i.Node2ID, - &i.Node2PubKey, - &i.Policy1ID, - &i.Policy1NodeID, - &i.Policy1Version, - &i.Policy1Timelock, - &i.Policy1FeePpm, - &i.Policy1BaseFeeMsat, - &i.Policy1MinHtlcMsat, - &i.Policy1MaxHtlcMsat, - &i.Policy1LastUpdate, - &i.Policy1Disabled, - &i.Policy1InboundBaseFeeMsat, - &i.Policy1InboundFeeRateMilliMsat, - &i.Policy1MessageFlags, - &i.Policy1ChannelFlags, - &i.Policy1Signature, - &i.Policy2ID, - &i.Policy2NodeID, - &i.Policy2Version, - &i.Policy2Timelock, - &i.Policy2FeePpm, - &i.Policy2BaseFeeMsat, - &i.Policy2MinHtlcMsat, - &i.Policy2MaxHtlcMsat, - &i.Policy2LastUpdate, - &i.Policy2Disabled, - &i.Policy2InboundBaseFeeMsat, - &i.Policy2InboundFeeRateMilliMsat, - &i.Policy2MessageFlags, - &i.Policy2ChannelFlags, - &i.Policy2Signature, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getChannelsByOutpoints = `-- name: GetChannelsByOutpoints :many -SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, - n1.pub_key AS node1_pubkey, - n2.pub_key AS node2_pubkey -FROM graph_channels c - JOIN graph_nodes n1 ON c.node_id_1 = n1.id - JOIN graph_nodes n2 ON c.node_id_2 = n2.id -WHERE c.outpoint IN - (/*SLICE:outpoints*/?) -` - -type GetChannelsByOutpointsRow struct { - GraphChannel GraphChannel - Node1Pubkey []byte - Node2Pubkey []byte -} - -func (q *Queries) GetChannelsByOutpoints(ctx context.Context, outpoints []string) ([]GetChannelsByOutpointsRow, error) { - query := getChannelsByOutpoints - var queryParams []interface{} - if len(outpoints) > 0 { - for _, v := range outpoints { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:outpoints*/?", makeQueryParams(len(queryParams), len(outpoints)), 1) - } else { - query = strings.Replace(query, "/*SLICE:outpoints*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GetChannelsByOutpointsRow - for rows.Next() { - var i GetChannelsByOutpointsRow - if err := rows.Scan( - &i.GraphChannel.ID, - &i.GraphChannel.Version, - &i.GraphChannel.Scid, - &i.GraphChannel.NodeID1, - &i.GraphChannel.NodeID2, - &i.GraphChannel.Outpoint, - &i.GraphChannel.Capacity, - &i.GraphChannel.BitcoinKey1, - &i.GraphChannel.BitcoinKey2, - &i.GraphChannel.Node1Signature, - &i.GraphChannel.Node2Signature, - &i.GraphChannel.Bitcoin1Signature, - &i.GraphChannel.Bitcoin2Signature, - &i.Node1Pubkey, - &i.Node2Pubkey, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getChannelsByPolicyLastUpdateRange = `-- name: GetChannelsByPolicyLastUpdateRange :many -SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, - n1.id, n1.version, n1.pub_key, n1.alias, n1.last_update, n1.color, n1.signature, - n2.id, n2.version, n2.pub_key, n2.alias, n2.last_update, n2.color, n2.signature, - - -- Policy 1 (node_id_1) - cp1.id AS policy1_id, - cp1.node_id AS policy1_node_id, - cp1.version AS policy1_version, - cp1.timelock AS policy1_timelock, - cp1.fee_ppm AS policy1_fee_ppm, - cp1.base_fee_msat AS policy1_base_fee_msat, - cp1.min_htlc_msat AS policy1_min_htlc_msat, - cp1.max_htlc_msat AS policy1_max_htlc_msat, - cp1.last_update AS policy1_last_update, - cp1.disabled AS policy1_disabled, - cp1.inbound_base_fee_msat AS policy1_inbound_base_fee_msat, - cp1.inbound_fee_rate_milli_msat AS policy1_inbound_fee_rate_milli_msat, - cp1.message_flags AS policy1_message_flags, - cp1.channel_flags AS policy1_channel_flags, - cp1.signature AS policy1_signature, - - -- Policy 2 (node_id_2) - cp2.id AS policy2_id, - cp2.node_id AS policy2_node_id, - cp2.version AS policy2_version, - cp2.timelock AS policy2_timelock, - cp2.fee_ppm AS policy2_fee_ppm, - cp2.base_fee_msat AS policy2_base_fee_msat, - cp2.min_htlc_msat AS policy2_min_htlc_msat, - cp2.max_htlc_msat AS policy2_max_htlc_msat, - cp2.last_update AS policy2_last_update, - cp2.disabled AS policy2_disabled, - cp2.inbound_base_fee_msat AS policy2_inbound_base_fee_msat, - cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, - cp2.message_flags AS policy2_message_flags, - cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature - -FROM graph_channels c - JOIN graph_nodes n1 ON c.node_id_1 = n1.id - JOIN graph_nodes n2 ON c.node_id_2 = n2.id - LEFT JOIN graph_channel_policies cp1 - ON cp1.channel_id = c.id AND cp1.node_id = c.node_id_1 AND cp1.version = c.version - LEFT JOIN graph_channel_policies cp2 - ON cp2.channel_id = c.id AND cp2.node_id = c.node_id_2 AND cp2.version = c.version -WHERE c.version = $1 - AND ( - (cp1.last_update >= $2 AND cp1.last_update < $3) - OR - (cp2.last_update >= $2 AND cp2.last_update < $3) - ) - -- Pagination using compound cursor (max_update_time, id). - -- We use COALESCE with -1 as sentinel since timestamps are always positive. - AND ( - (CASE - WHEN COALESCE(cp1.last_update, 0) >= COALESCE(cp2.last_update, 0) - THEN COALESCE(cp1.last_update, 0) - ELSE COALESCE(cp2.last_update, 0) - END > COALESCE($4, -1)) - OR - (CASE - WHEN COALESCE(cp1.last_update, 0) >= COALESCE(cp2.last_update, 0) - THEN COALESCE(cp1.last_update, 0) - ELSE COALESCE(cp2.last_update, 0) - END = COALESCE($4, -1) - AND c.id > COALESCE($5, -1)) - ) -ORDER BY - CASE - WHEN COALESCE(cp1.last_update, 0) >= COALESCE(cp2.last_update, 0) - THEN COALESCE(cp1.last_update, 0) - ELSE COALESCE(cp2.last_update, 0) - END ASC, - c.id ASC -LIMIT COALESCE($6, 999999999) -` - -type GetChannelsByPolicyLastUpdateRangeParams struct { - Version int16 - StartTime sql.NullInt64 - EndTime sql.NullInt64 - LastUpdateTime sql.NullInt64 - LastID sql.NullInt64 - MaxResults interface{} -} - -type GetChannelsByPolicyLastUpdateRangeRow struct { - GraphChannel GraphChannel - GraphNode GraphNode - GraphNode_2 GraphNode - Policy1ID sql.NullInt64 - Policy1NodeID sql.NullInt64 - Policy1Version sql.NullInt16 - Policy1Timelock sql.NullInt32 - Policy1FeePpm sql.NullInt64 - Policy1BaseFeeMsat sql.NullInt64 - Policy1MinHtlcMsat sql.NullInt64 - Policy1MaxHtlcMsat sql.NullInt64 - Policy1LastUpdate sql.NullInt64 - Policy1Disabled sql.NullBool - Policy1InboundBaseFeeMsat sql.NullInt64 - Policy1InboundFeeRateMilliMsat sql.NullInt64 - Policy1MessageFlags sql.NullInt16 - Policy1ChannelFlags sql.NullInt16 - Policy1Signature []byte - Policy2ID sql.NullInt64 - Policy2NodeID sql.NullInt64 - Policy2Version sql.NullInt16 - Policy2Timelock sql.NullInt32 - Policy2FeePpm sql.NullInt64 - Policy2BaseFeeMsat sql.NullInt64 - Policy2MinHtlcMsat sql.NullInt64 - Policy2MaxHtlcMsat sql.NullInt64 - Policy2LastUpdate sql.NullInt64 - Policy2Disabled sql.NullBool - Policy2InboundBaseFeeMsat sql.NullInt64 - Policy2InboundFeeRateMilliMsat sql.NullInt64 - Policy2MessageFlags sql.NullInt16 - Policy2ChannelFlags sql.NullInt16 - Policy2Signature []byte -} - -func (q *Queries) GetChannelsByPolicyLastUpdateRange(ctx context.Context, arg GetChannelsByPolicyLastUpdateRangeParams) ([]GetChannelsByPolicyLastUpdateRangeRow, error) { - rows, err := q.db.QueryContext(ctx, getChannelsByPolicyLastUpdateRange, - arg.Version, - arg.StartTime, - arg.EndTime, - arg.LastUpdateTime, - arg.LastID, - arg.MaxResults, - ) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GetChannelsByPolicyLastUpdateRangeRow - for rows.Next() { - var i GetChannelsByPolicyLastUpdateRangeRow - if err := rows.Scan( - &i.GraphChannel.ID, - &i.GraphChannel.Version, - &i.GraphChannel.Scid, - &i.GraphChannel.NodeID1, - &i.GraphChannel.NodeID2, - &i.GraphChannel.Outpoint, - &i.GraphChannel.Capacity, - &i.GraphChannel.BitcoinKey1, - &i.GraphChannel.BitcoinKey2, - &i.GraphChannel.Node1Signature, - &i.GraphChannel.Node2Signature, - &i.GraphChannel.Bitcoin1Signature, - &i.GraphChannel.Bitcoin2Signature, - &i.GraphNode.ID, - &i.GraphNode.Version, - &i.GraphNode.PubKey, - &i.GraphNode.Alias, - &i.GraphNode.LastUpdate, - &i.GraphNode.Color, - &i.GraphNode.Signature, - &i.GraphNode_2.ID, - &i.GraphNode_2.Version, - &i.GraphNode_2.PubKey, - &i.GraphNode_2.Alias, - &i.GraphNode_2.LastUpdate, - &i.GraphNode_2.Color, - &i.GraphNode_2.Signature, - &i.Policy1ID, - &i.Policy1NodeID, - &i.Policy1Version, - &i.Policy1Timelock, - &i.Policy1FeePpm, - &i.Policy1BaseFeeMsat, - &i.Policy1MinHtlcMsat, - &i.Policy1MaxHtlcMsat, - &i.Policy1LastUpdate, - &i.Policy1Disabled, - &i.Policy1InboundBaseFeeMsat, - &i.Policy1InboundFeeRateMilliMsat, - &i.Policy1MessageFlags, - &i.Policy1ChannelFlags, - &i.Policy1Signature, - &i.Policy2ID, - &i.Policy2NodeID, - &i.Policy2Version, - &i.Policy2Timelock, - &i.Policy2FeePpm, - &i.Policy2BaseFeeMsat, - &i.Policy2MinHtlcMsat, - &i.Policy2MaxHtlcMsat, - &i.Policy2LastUpdate, - &i.Policy2Disabled, - &i.Policy2InboundBaseFeeMsat, - &i.Policy2InboundFeeRateMilliMsat, - &i.Policy2MessageFlags, - &i.Policy2ChannelFlags, - &i.Policy2Signature, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getChannelsBySCIDRange = `-- name: GetChannelsBySCIDRange :many -SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, - n1.pub_key AS node1_pub_key, - n2.pub_key AS node2_pub_key -FROM graph_channels c - JOIN graph_nodes n1 ON c.node_id_1 = n1.id - JOIN graph_nodes n2 ON c.node_id_2 = n2.id -WHERE scid >= $1 - AND scid < $2 -` - -type GetChannelsBySCIDRangeParams struct { - StartScid []byte - EndScid []byte -} - -type GetChannelsBySCIDRangeRow struct { - GraphChannel GraphChannel - Node1PubKey []byte - Node2PubKey []byte -} - -func (q *Queries) GetChannelsBySCIDRange(ctx context.Context, arg GetChannelsBySCIDRangeParams) ([]GetChannelsBySCIDRangeRow, error) { - rows, err := q.db.QueryContext(ctx, getChannelsBySCIDRange, arg.StartScid, arg.EndScid) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GetChannelsBySCIDRangeRow - for rows.Next() { - var i GetChannelsBySCIDRangeRow - if err := rows.Scan( - &i.GraphChannel.ID, - &i.GraphChannel.Version, - &i.GraphChannel.Scid, - &i.GraphChannel.NodeID1, - &i.GraphChannel.NodeID2, - &i.GraphChannel.Outpoint, - &i.GraphChannel.Capacity, - &i.GraphChannel.BitcoinKey1, - &i.GraphChannel.BitcoinKey2, - &i.GraphChannel.Node1Signature, - &i.GraphChannel.Node2Signature, - &i.GraphChannel.Bitcoin1Signature, - &i.GraphChannel.Bitcoin2Signature, - &i.Node1PubKey, - &i.Node2PubKey, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getChannelsBySCIDWithPolicies = `-- name: GetChannelsBySCIDWithPolicies :many -SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, - n1.id, n1.version, n1.pub_key, n1.alias, n1.last_update, n1.color, n1.signature, - n2.id, n2.version, n2.pub_key, n2.alias, n2.last_update, n2.color, n2.signature, - - -- Policy 1 - cp1.id AS policy1_id, - cp1.node_id AS policy1_node_id, - cp1.version AS policy1_version, - cp1.timelock AS policy1_timelock, - cp1.fee_ppm AS policy1_fee_ppm, - cp1.base_fee_msat AS policy1_base_fee_msat, - cp1.min_htlc_msat AS policy1_min_htlc_msat, - cp1.max_htlc_msat AS policy1_max_htlc_msat, - cp1.last_update AS policy1_last_update, - cp1.disabled AS policy1_disabled, - cp1.inbound_base_fee_msat AS policy1_inbound_base_fee_msat, - cp1.inbound_fee_rate_milli_msat AS policy1_inbound_fee_rate_milli_msat, - cp1.message_flags AS policy1_message_flags, - cp1.channel_flags AS policy1_channel_flags, - cp1.signature AS policy1_signature, - - -- Policy 2 - cp2.id AS policy2_id, - cp2.node_id AS policy2_node_id, - cp2.version AS policy2_version, - cp2.timelock AS policy2_timelock, - cp2.fee_ppm AS policy2_fee_ppm, - cp2.base_fee_msat AS policy2_base_fee_msat, - cp2.min_htlc_msat AS policy2_min_htlc_msat, - cp2.max_htlc_msat AS policy2_max_htlc_msat, - cp2.last_update AS policy2_last_update, - cp2.disabled AS policy2_disabled, - cp2.inbound_base_fee_msat AS policy2_inbound_base_fee_msat, - cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, - cp2.message_flags AS policy_2_message_flags, - cp2.channel_flags AS policy_2_channel_flags, - cp2.signature AS policy2_signature - -FROM graph_channels c - JOIN graph_nodes n1 ON c.node_id_1 = n1.id - JOIN graph_nodes n2 ON c.node_id_2 = n2.id - LEFT JOIN graph_channel_policies cp1 - ON cp1.channel_id = c.id AND cp1.node_id = c.node_id_1 AND cp1.version = c.version - LEFT JOIN graph_channel_policies cp2 - ON cp2.channel_id = c.id AND cp2.node_id = c.node_id_2 AND cp2.version = c.version -WHERE - c.version = $1 - AND c.scid IN (/*SLICE:scids*/?) -` - -type GetChannelsBySCIDWithPoliciesParams struct { - Version int16 - Scids [][]byte -} - -type GetChannelsBySCIDWithPoliciesRow struct { - GraphChannel GraphChannel - GraphNode GraphNode - GraphNode_2 GraphNode - Policy1ID sql.NullInt64 - Policy1NodeID sql.NullInt64 - Policy1Version sql.NullInt16 - Policy1Timelock sql.NullInt32 - Policy1FeePpm sql.NullInt64 - Policy1BaseFeeMsat sql.NullInt64 - Policy1MinHtlcMsat sql.NullInt64 - Policy1MaxHtlcMsat sql.NullInt64 - Policy1LastUpdate sql.NullInt64 - Policy1Disabled sql.NullBool - Policy1InboundBaseFeeMsat sql.NullInt64 - Policy1InboundFeeRateMilliMsat sql.NullInt64 - Policy1MessageFlags sql.NullInt16 - Policy1ChannelFlags sql.NullInt16 - Policy1Signature []byte - Policy2ID sql.NullInt64 - Policy2NodeID sql.NullInt64 - Policy2Version sql.NullInt16 - Policy2Timelock sql.NullInt32 - Policy2FeePpm sql.NullInt64 - Policy2BaseFeeMsat sql.NullInt64 - Policy2MinHtlcMsat sql.NullInt64 - Policy2MaxHtlcMsat sql.NullInt64 - Policy2LastUpdate sql.NullInt64 - Policy2Disabled sql.NullBool - Policy2InboundBaseFeeMsat sql.NullInt64 - Policy2InboundFeeRateMilliMsat sql.NullInt64 - Policy2MessageFlags sql.NullInt16 - Policy2ChannelFlags sql.NullInt16 - Policy2Signature []byte -} - -func (q *Queries) GetChannelsBySCIDWithPolicies(ctx context.Context, arg GetChannelsBySCIDWithPoliciesParams) ([]GetChannelsBySCIDWithPoliciesRow, error) { - query := getChannelsBySCIDWithPolicies - var queryParams []interface{} - queryParams = append(queryParams, arg.Version) - if len(arg.Scids) > 0 { - for _, v := range arg.Scids { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:scids*/?", makeQueryParams(len(queryParams), len(arg.Scids)), 1) - } else { - query = strings.Replace(query, "/*SLICE:scids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GetChannelsBySCIDWithPoliciesRow - for rows.Next() { - var i GetChannelsBySCIDWithPoliciesRow - if err := rows.Scan( - &i.GraphChannel.ID, - &i.GraphChannel.Version, - &i.GraphChannel.Scid, - &i.GraphChannel.NodeID1, - &i.GraphChannel.NodeID2, - &i.GraphChannel.Outpoint, - &i.GraphChannel.Capacity, - &i.GraphChannel.BitcoinKey1, - &i.GraphChannel.BitcoinKey2, - &i.GraphChannel.Node1Signature, - &i.GraphChannel.Node2Signature, - &i.GraphChannel.Bitcoin1Signature, - &i.GraphChannel.Bitcoin2Signature, - &i.GraphNode.ID, - &i.GraphNode.Version, - &i.GraphNode.PubKey, - &i.GraphNode.Alias, - &i.GraphNode.LastUpdate, - &i.GraphNode.Color, - &i.GraphNode.Signature, - &i.GraphNode_2.ID, - &i.GraphNode_2.Version, - &i.GraphNode_2.PubKey, - &i.GraphNode_2.Alias, - &i.GraphNode_2.LastUpdate, - &i.GraphNode_2.Color, - &i.GraphNode_2.Signature, - &i.Policy1ID, - &i.Policy1NodeID, - &i.Policy1Version, - &i.Policy1Timelock, - &i.Policy1FeePpm, - &i.Policy1BaseFeeMsat, - &i.Policy1MinHtlcMsat, - &i.Policy1MaxHtlcMsat, - &i.Policy1LastUpdate, - &i.Policy1Disabled, - &i.Policy1InboundBaseFeeMsat, - &i.Policy1InboundFeeRateMilliMsat, - &i.Policy1MessageFlags, - &i.Policy1ChannelFlags, - &i.Policy1Signature, - &i.Policy2ID, - &i.Policy2NodeID, - &i.Policy2Version, - &i.Policy2Timelock, - &i.Policy2FeePpm, - &i.Policy2BaseFeeMsat, - &i.Policy2MinHtlcMsat, - &i.Policy2MaxHtlcMsat, - &i.Policy2LastUpdate, - &i.Policy2Disabled, - &i.Policy2InboundBaseFeeMsat, - &i.Policy2InboundFeeRateMilliMsat, - &i.Policy2MessageFlags, - &i.Policy2ChannelFlags, - &i.Policy2Signature, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getChannelsBySCIDs = `-- name: GetChannelsBySCIDs :many -SELECT id, version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, bitcoin_2_signature FROM graph_channels -WHERE version = $1 - AND scid IN (/*SLICE:scids*/?) -` - -type GetChannelsBySCIDsParams struct { - Version int16 - Scids [][]byte -} - -func (q *Queries) GetChannelsBySCIDs(ctx context.Context, arg GetChannelsBySCIDsParams) ([]GraphChannel, error) { - query := getChannelsBySCIDs - var queryParams []interface{} - queryParams = append(queryParams, arg.Version) - if len(arg.Scids) > 0 { - for _, v := range arg.Scids { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:scids*/?", makeQueryParams(len(queryParams), len(arg.Scids)), 1) - } else { - query = strings.Replace(query, "/*SLICE:scids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GraphChannel - for rows.Next() { - var i GraphChannel - if err := rows.Scan( - &i.ID, - &i.Version, - &i.Scid, - &i.NodeID1, - &i.NodeID2, - &i.Outpoint, - &i.Capacity, - &i.BitcoinKey1, - &i.BitcoinKey2, - &i.Node1Signature, - &i.Node2Signature, - &i.Bitcoin1Signature, - &i.Bitcoin2Signature, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getClosedChannelsSCIDs = `-- name: GetClosedChannelsSCIDs :many -SELECT scid -FROM graph_closed_scids -WHERE scid IN (/*SLICE:scids*/?) -` - -func (q *Queries) GetClosedChannelsSCIDs(ctx context.Context, scids [][]byte) ([][]byte, error) { - query := getClosedChannelsSCIDs - var queryParams []interface{} - if len(scids) > 0 { - for _, v := range scids { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:scids*/?", makeQueryParams(len(queryParams), len(scids)), 1) - } else { - query = strings.Replace(query, "/*SLICE:scids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items [][]byte - for rows.Next() { - var scid []byte - if err := rows.Scan(&scid); err != nil { - return nil, err - } - items = append(items, scid) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getExtraNodeTypes = `-- name: GetExtraNodeTypes :many -SELECT node_id, type, value -FROM graph_node_extra_types -WHERE node_id = $1 -` - -func (q *Queries) GetExtraNodeTypes(ctx context.Context, nodeID int64) ([]GraphNodeExtraType, error) { - rows, err := q.db.QueryContext(ctx, getExtraNodeTypes, nodeID) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GraphNodeExtraType - for rows.Next() { - var i GraphNodeExtraType - if err := rows.Scan(&i.NodeID, &i.Type, &i.Value); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getNodeAddresses = `-- name: GetNodeAddresses :many -SELECT type, address -FROM graph_node_addresses -WHERE node_id = $1 -ORDER BY type ASC, position ASC -` - -type GetNodeAddressesRow struct { - Type int16 - Address string -} - -func (q *Queries) GetNodeAddresses(ctx context.Context, nodeID int64) ([]GetNodeAddressesRow, error) { - rows, err := q.db.QueryContext(ctx, getNodeAddresses, nodeID) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GetNodeAddressesRow - for rows.Next() { - var i GetNodeAddressesRow - if err := rows.Scan(&i.Type, &i.Address); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getNodeAddressesBatch = `-- name: GetNodeAddressesBatch :many -SELECT node_id, type, position, address -FROM graph_node_addresses -WHERE node_id IN (/*SLICE:ids*/?) -ORDER BY node_id, type, position -` - -func (q *Queries) GetNodeAddressesBatch(ctx context.Context, ids []int64) ([]GraphNodeAddress, error) { - query := getNodeAddressesBatch - var queryParams []interface{} - if len(ids) > 0 { - for _, v := range ids { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:ids*/?", makeQueryParams(len(queryParams), len(ids)), 1) - } else { - query = strings.Replace(query, "/*SLICE:ids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GraphNodeAddress - for rows.Next() { - var i GraphNodeAddress - if err := rows.Scan( - &i.NodeID, - &i.Type, - &i.Position, - &i.Address, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getNodeByPubKey = `-- name: GetNodeByPubKey :one -SELECT id, version, pub_key, alias, last_update, color, signature -FROM graph_nodes -WHERE pub_key = $1 - AND version = $2 -` - -type GetNodeByPubKeyParams struct { - PubKey []byte - Version int16 -} - -func (q *Queries) GetNodeByPubKey(ctx context.Context, arg GetNodeByPubKeyParams) (GraphNode, error) { - row := q.db.QueryRowContext(ctx, getNodeByPubKey, arg.PubKey, arg.Version) - var i GraphNode - err := row.Scan( - &i.ID, - &i.Version, - &i.PubKey, - &i.Alias, - &i.LastUpdate, - &i.Color, - &i.Signature, - ) - return i, err -} - -const getNodeExtraTypesBatch = `-- name: GetNodeExtraTypesBatch :many -SELECT node_id, type, value -FROM graph_node_extra_types -WHERE node_id IN (/*SLICE:ids*/?) -ORDER BY node_id, type -` - -func (q *Queries) GetNodeExtraTypesBatch(ctx context.Context, ids []int64) ([]GraphNodeExtraType, error) { - query := getNodeExtraTypesBatch - var queryParams []interface{} - if len(ids) > 0 { - for _, v := range ids { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:ids*/?", makeQueryParams(len(queryParams), len(ids)), 1) - } else { - query = strings.Replace(query, "/*SLICE:ids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GraphNodeExtraType - for rows.Next() { - var i GraphNodeExtraType - if err := rows.Scan(&i.NodeID, &i.Type, &i.Value); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getNodeFeatures = `-- name: GetNodeFeatures :many -SELECT node_id, feature_bit -FROM graph_node_features -WHERE node_id = $1 -` - -func (q *Queries) GetNodeFeatures(ctx context.Context, nodeID int64) ([]GraphNodeFeature, error) { - rows, err := q.db.QueryContext(ctx, getNodeFeatures, nodeID) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GraphNodeFeature - for rows.Next() { - var i GraphNodeFeature - if err := rows.Scan(&i.NodeID, &i.FeatureBit); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getNodeFeaturesBatch = `-- name: GetNodeFeaturesBatch :many -SELECT node_id, feature_bit -FROM graph_node_features -WHERE node_id IN (/*SLICE:ids*/?) -ORDER BY node_id, feature_bit -` - -func (q *Queries) GetNodeFeaturesBatch(ctx context.Context, ids []int64) ([]GraphNodeFeature, error) { - query := getNodeFeaturesBatch - var queryParams []interface{} - if len(ids) > 0 { - for _, v := range ids { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:ids*/?", makeQueryParams(len(queryParams), len(ids)), 1) - } else { - query = strings.Replace(query, "/*SLICE:ids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GraphNodeFeature - for rows.Next() { - var i GraphNodeFeature - if err := rows.Scan(&i.NodeID, &i.FeatureBit); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getNodeFeaturesByPubKey = `-- name: GetNodeFeaturesByPubKey :many -SELECT f.feature_bit -FROM graph_nodes n - JOIN graph_node_features f ON f.node_id = n.id -WHERE n.pub_key = $1 - AND n.version = $2 -` - -type GetNodeFeaturesByPubKeyParams struct { - PubKey []byte - Version int16 -} - -func (q *Queries) GetNodeFeaturesByPubKey(ctx context.Context, arg GetNodeFeaturesByPubKeyParams) ([]int32, error) { - rows, err := q.db.QueryContext(ctx, getNodeFeaturesByPubKey, arg.PubKey, arg.Version) - if err != nil { - return nil, err - } - defer rows.Close() - var items []int32 - for rows.Next() { - var feature_bit int32 - if err := rows.Scan(&feature_bit); err != nil { - return nil, err - } - items = append(items, feature_bit) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getNodeIDByPubKey = `-- name: GetNodeIDByPubKey :one -SELECT id -FROM graph_nodes -WHERE pub_key = $1 - AND version = $2 -` - -type GetNodeIDByPubKeyParams struct { - PubKey []byte - Version int16 -} - -func (q *Queries) GetNodeIDByPubKey(ctx context.Context, arg GetNodeIDByPubKeyParams) (int64, error) { - row := q.db.QueryRowContext(ctx, getNodeIDByPubKey, arg.PubKey, arg.Version) - var id int64 - err := row.Scan(&id) - return id, err -} - -const getNodesByIDs = `-- name: GetNodesByIDs :many -SELECT id, version, pub_key, alias, last_update, color, signature -FROM graph_nodes -WHERE id IN (/*SLICE:ids*/?) -` - -func (q *Queries) GetNodesByIDs(ctx context.Context, ids []int64) ([]GraphNode, error) { - query := getNodesByIDs - var queryParams []interface{} - if len(ids) > 0 { - for _, v := range ids { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:ids*/?", makeQueryParams(len(queryParams), len(ids)), 1) - } else { - query = strings.Replace(query, "/*SLICE:ids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GraphNode - for rows.Next() { - var i GraphNode - if err := rows.Scan( - &i.ID, - &i.Version, - &i.PubKey, - &i.Alias, - &i.LastUpdate, - &i.Color, - &i.Signature, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getNodesByLastUpdateRange = `-- name: GetNodesByLastUpdateRange :many -SELECT id, version, pub_key, alias, last_update, color, signature -FROM graph_nodes -WHERE last_update >= $1 - AND last_update <= $2 - -- Pagination: We use (last_update, pub_key) as a compound cursor. - -- This ensures stable ordering and allows us to resume from where we left off. - -- We use COALESCE with -1 as sentinel since timestamps are always positive. - AND ( - -- Include rows with last_update greater than cursor (or all rows if cursor is -1) - last_update > COALESCE($3, -1) - OR - -- For rows with same last_update, use pub_key as tiebreaker - (last_update = COALESCE($3, -1) - AND pub_key > $4) - ) - -- Optional filter for public nodes only - AND ( - -- If only_public is false or not provided, include all nodes - COALESCE($5, FALSE) IS FALSE - OR - -- For V1 protocol, a node is public if it has at least one public channel. - -- A public channel has bitcoin_1_signature set (channel announcement received). - EXISTS ( - SELECT 1 - FROM graph_channels c - WHERE c.version = 1 - AND c.bitcoin_1_signature IS NOT NULL - AND (c.node_id_1 = graph_nodes.id OR c.node_id_2 = graph_nodes.id) - ) - ) -ORDER BY last_update ASC, pub_key ASC -LIMIT COALESCE($6, 999999999) -` - -type GetNodesByLastUpdateRangeParams struct { - StartTime sql.NullInt64 - EndTime sql.NullInt64 - LastUpdate sql.NullInt64 - LastPubKey []byte - OnlyPublic interface{} - MaxResults interface{} -} - -func (q *Queries) GetNodesByLastUpdateRange(ctx context.Context, arg GetNodesByLastUpdateRangeParams) ([]GraphNode, error) { - rows, err := q.db.QueryContext(ctx, getNodesByLastUpdateRange, - arg.StartTime, - arg.EndTime, - arg.LastUpdate, - arg.LastPubKey, - arg.OnlyPublic, - arg.MaxResults, - ) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GraphNode - for rows.Next() { - var i GraphNode - if err := rows.Scan( - &i.ID, - &i.Version, - &i.PubKey, - &i.Alias, - &i.LastUpdate, - &i.Color, - &i.Signature, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getPruneEntriesForHeights = `-- name: GetPruneEntriesForHeights :many -SELECT block_height, block_hash -FROM graph_prune_log -WHERE block_height - IN (/*SLICE:heights*/?) -` - -func (q *Queries) GetPruneEntriesForHeights(ctx context.Context, heights []int64) ([]GraphPruneLog, error) { - query := getPruneEntriesForHeights - var queryParams []interface{} - if len(heights) > 0 { - for _, v := range heights { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:heights*/?", makeQueryParams(len(queryParams), len(heights)), 1) - } else { - query = strings.Replace(query, "/*SLICE:heights*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GraphPruneLog - for rows.Next() { - var i GraphPruneLog - if err := rows.Scan(&i.BlockHeight, &i.BlockHash); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getPruneHashByHeight = `-- name: GetPruneHashByHeight :one -SELECT block_hash -FROM graph_prune_log -WHERE block_height = $1 -` - -func (q *Queries) GetPruneHashByHeight(ctx context.Context, blockHeight int64) ([]byte, error) { - row := q.db.QueryRowContext(ctx, getPruneHashByHeight, blockHeight) - var block_hash []byte - err := row.Scan(&block_hash) - return block_hash, err -} - -const getPruneTip = `-- name: GetPruneTip :one -SELECT block_height, block_hash -FROM graph_prune_log -ORDER BY block_height DESC -LIMIT 1 -` - -func (q *Queries) GetPruneTip(ctx context.Context) (GraphPruneLog, error) { - row := q.db.QueryRowContext(ctx, getPruneTip) - var i GraphPruneLog - err := row.Scan(&i.BlockHeight, &i.BlockHash) - return i, err -} - -const getPublicV1ChannelsBySCID = `-- name: GetPublicV1ChannelsBySCID :many -SELECT id, version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, bitcoin_2_signature -FROM graph_channels -WHERE node_1_signature IS NOT NULL - AND scid >= $1 - AND scid < $2 -` - -type GetPublicV1ChannelsBySCIDParams struct { - StartScid []byte - EndScid []byte -} - -func (q *Queries) GetPublicV1ChannelsBySCID(ctx context.Context, arg GetPublicV1ChannelsBySCIDParams) ([]GraphChannel, error) { - rows, err := q.db.QueryContext(ctx, getPublicV1ChannelsBySCID, arg.StartScid, arg.EndScid) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GraphChannel - for rows.Next() { - var i GraphChannel - if err := rows.Scan( - &i.ID, - &i.Version, - &i.Scid, - &i.NodeID1, - &i.NodeID2, - &i.Outpoint, - &i.Capacity, - &i.BitcoinKey1, - &i.BitcoinKey2, - &i.Node1Signature, - &i.Node2Signature, - &i.Bitcoin1Signature, - &i.Bitcoin2Signature, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getSCIDByOutpoint = `-- name: GetSCIDByOutpoint :one -SELECT scid from graph_channels -WHERE outpoint = $1 AND version = $2 -` - -type GetSCIDByOutpointParams struct { - Outpoint string - Version int16 -} - -func (q *Queries) GetSCIDByOutpoint(ctx context.Context, arg GetSCIDByOutpointParams) ([]byte, error) { - row := q.db.QueryRowContext(ctx, getSCIDByOutpoint, arg.Outpoint, arg.Version) - var scid []byte - err := row.Scan(&scid) - return scid, err -} - -const getSourceNodesByVersion = `-- name: GetSourceNodesByVersion :many -SELECT sn.node_id, n.pub_key -FROM graph_source_nodes sn - JOIN graph_nodes n ON sn.node_id = n.id -WHERE n.version = $1 -` - -type GetSourceNodesByVersionRow struct { - NodeID int64 - PubKey []byte -} - -func (q *Queries) GetSourceNodesByVersion(ctx context.Context, version int16) ([]GetSourceNodesByVersionRow, error) { - rows, err := q.db.QueryContext(ctx, getSourceNodesByVersion, version) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GetSourceNodesByVersionRow - for rows.Next() { - var i GetSourceNodesByVersionRow - if err := rows.Scan(&i.NodeID, &i.PubKey); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getV1DisabledSCIDs = `-- name: GetV1DisabledSCIDs :many -SELECT c.scid -FROM graph_channels c - JOIN graph_channel_policies cp ON cp.channel_id = c.id -WHERE cp.disabled = true -AND c.version = 1 -GROUP BY c.scid -HAVING COUNT(*) > 1 -` - -// NOTE: this is V1 specific since for V1, disabled is a -// simple, single boolean. The proposed V2 policy -// structure will have a more complex disabled bit vector -// and so the query for V2 may differ. -func (q *Queries) GetV1DisabledSCIDs(ctx context.Context) ([][]byte, error) { - rows, err := q.db.QueryContext(ctx, getV1DisabledSCIDs) - if err != nil { - return nil, err - } - defer rows.Close() - var items [][]byte - for rows.Next() { - var scid []byte - if err := rows.Scan(&scid); err != nil { - return nil, err - } - items = append(items, scid) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getZombieChannel = `-- name: GetZombieChannel :one -SELECT scid, version, node_key_1, node_key_2 -FROM graph_zombie_channels -WHERE scid = $1 -AND version = $2 -` - -type GetZombieChannelParams struct { - Scid []byte - Version int16 -} - -func (q *Queries) GetZombieChannel(ctx context.Context, arg GetZombieChannelParams) (GraphZombieChannel, error) { - row := q.db.QueryRowContext(ctx, getZombieChannel, arg.Scid, arg.Version) - var i GraphZombieChannel - err := row.Scan( - &i.Scid, - &i.Version, - &i.NodeKey1, - &i.NodeKey2, - ) - return i, err -} - -const getZombieChannelsSCIDs = `-- name: GetZombieChannelsSCIDs :many -SELECT scid, version, node_key_1, node_key_2 -FROM graph_zombie_channels -WHERE version = $1 - AND scid IN (/*SLICE:scids*/?) -` - -type GetZombieChannelsSCIDsParams struct { - Version int16 - Scids [][]byte -} - -func (q *Queries) GetZombieChannelsSCIDs(ctx context.Context, arg GetZombieChannelsSCIDsParams) ([]GraphZombieChannel, error) { - query := getZombieChannelsSCIDs - var queryParams []interface{} - queryParams = append(queryParams, arg.Version) - if len(arg.Scids) > 0 { - for _, v := range arg.Scids { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:scids*/?", makeQueryParams(len(queryParams), len(arg.Scids)), 1) - } else { - query = strings.Replace(query, "/*SLICE:scids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GraphZombieChannel - for rows.Next() { - var i GraphZombieChannel - if err := rows.Scan( - &i.Scid, - &i.Version, - &i.NodeKey1, - &i.NodeKey2, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const highestSCID = `-- name: HighestSCID :one -SELECT scid -FROM graph_channels -WHERE version = $1 -ORDER BY scid DESC -LIMIT 1 -` - -func (q *Queries) HighestSCID(ctx context.Context, version int16) ([]byte, error) { - row := q.db.QueryRowContext(ctx, highestSCID, version) - var scid []byte - err := row.Scan(&scid) - return scid, err -} - -const insertChannelFeature = `-- name: InsertChannelFeature :exec -/* ───────────────────────────────────────────── - graph_channel_features table queries - ───────────────────────────────────────────── -*/ - -INSERT INTO graph_channel_features ( - channel_id, feature_bit -) VALUES ( - $1, $2 -) ON CONFLICT (channel_id, feature_bit) - -- Do nothing if the channel_id and feature_bit already exist. - DO NOTHING -` - -type InsertChannelFeatureParams struct { - ChannelID int64 - FeatureBit int32 -} - -func (q *Queries) InsertChannelFeature(ctx context.Context, arg InsertChannelFeatureParams) error { - _, err := q.db.ExecContext(ctx, insertChannelFeature, arg.ChannelID, arg.FeatureBit) - return err -} - -const insertChannelMig = `-- name: InsertChannelMig :one -INSERT INTO graph_channels ( - version, scid, node_id_1, node_id_2, - outpoint, capacity, bitcoin_key_1, bitcoin_key_2, - node_1_signature, node_2_signature, bitcoin_1_signature, - bitcoin_2_signature -) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 -) ON CONFLICT (scid, version) - -- If a conflict occurs, we have already migrated this channel. However, we - -- still need to do an "UPDATE SET" here instead of "DO NOTHING" because - -- otherwise, the "RETURNING id" part does not work. - DO UPDATE SET - node_id_1 = EXCLUDED.node_id_1, - node_id_2 = EXCLUDED.node_id_2, - outpoint = EXCLUDED.outpoint, - capacity = EXCLUDED.capacity, - bitcoin_key_1 = EXCLUDED.bitcoin_key_1, - bitcoin_key_2 = EXCLUDED.bitcoin_key_2, - node_1_signature = EXCLUDED.node_1_signature, - node_2_signature = EXCLUDED.node_2_signature, - bitcoin_1_signature = EXCLUDED.bitcoin_1_signature, - bitcoin_2_signature = EXCLUDED.bitcoin_2_signature -RETURNING id -` - -type InsertChannelMigParams struct { - Version int16 - Scid []byte - NodeID1 int64 - NodeID2 int64 - Outpoint string - Capacity sql.NullInt64 - BitcoinKey1 []byte - BitcoinKey2 []byte - Node1Signature []byte - Node2Signature []byte - Bitcoin1Signature []byte - Bitcoin2Signature []byte -} - -// NOTE: This query is only meant to be used by the graph SQL migration since -// for that migration, in order to be retry-safe, we don't want to error out if -// we re-insert the same channel again (which would error if the normal -// CreateChannel query is used because of the uniqueness constraint on the scid -// and version columns). -func (q *Queries) InsertChannelMig(ctx context.Context, arg InsertChannelMigParams) (int64, error) { - row := q.db.QueryRowContext(ctx, insertChannelMig, - arg.Version, - arg.Scid, - arg.NodeID1, - arg.NodeID2, - arg.Outpoint, - arg.Capacity, - arg.BitcoinKey1, - arg.BitcoinKey2, - arg.Node1Signature, - arg.Node2Signature, - arg.Bitcoin1Signature, - arg.Bitcoin2Signature, - ) - var id int64 - err := row.Scan(&id) - return id, err -} - -const insertClosedChannel = `-- name: InsertClosedChannel :exec -/* ───────────────────────────────────────────── - graph_closed_scid table queries - ────────────────────────────────────────────- -*/ - -INSERT INTO graph_closed_scids (scid) -VALUES ($1) -ON CONFLICT (scid) DO NOTHING -` - -func (q *Queries) InsertClosedChannel(ctx context.Context, scid []byte) error { - _, err := q.db.ExecContext(ctx, insertClosedChannel, scid) - return err -} - -const insertEdgePolicyMig = `-- name: InsertEdgePolicyMig :one -INSERT INTO graph_channel_policies ( - version, channel_id, node_id, timelock, fee_ppm, - base_fee_msat, min_htlc_msat, last_update, disabled, - max_htlc_msat, inbound_base_fee_msat, - inbound_fee_rate_milli_msat, message_flags, channel_flags, - signature -) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15 -) -ON CONFLICT (channel_id, node_id, version) - -- If a conflict occurs, we have already migrated this policy. However, we - -- still need to do an "UPDATE SET" here instead of "DO NOTHING" because - -- otherwise, the "RETURNING id" part does not work. - DO UPDATE SET - timelock = EXCLUDED.timelock, - fee_ppm = EXCLUDED.fee_ppm, - base_fee_msat = EXCLUDED.base_fee_msat, - min_htlc_msat = EXCLUDED.min_htlc_msat, - last_update = EXCLUDED.last_update, - disabled = EXCLUDED.disabled, - max_htlc_msat = EXCLUDED.max_htlc_msat, - inbound_base_fee_msat = EXCLUDED.inbound_base_fee_msat, - inbound_fee_rate_milli_msat = EXCLUDED.inbound_fee_rate_milli_msat, - message_flags = EXCLUDED.message_flags, - channel_flags = EXCLUDED.channel_flags, - signature = EXCLUDED.signature -RETURNING id -` - -type InsertEdgePolicyMigParams struct { - Version int16 - ChannelID int64 - NodeID int64 - Timelock int32 - FeePpm int64 - BaseFeeMsat int64 - MinHtlcMsat int64 - LastUpdate sql.NullInt64 - Disabled sql.NullBool - MaxHtlcMsat sql.NullInt64 - InboundBaseFeeMsat sql.NullInt64 - InboundFeeRateMilliMsat sql.NullInt64 - MessageFlags sql.NullInt16 - ChannelFlags sql.NullInt16 - Signature []byte -} - -// NOTE: This query is only meant to be used by the graph SQL migration since -// for that migration, in order to be retry-safe, we don't want to error out if -// we re-insert the same policy (which would error if the normal -// UpsertEdgePolicy query is used because of the constraint in that query that -// requires a policy update to have a newer last_update than the existing one). -func (q *Queries) InsertEdgePolicyMig(ctx context.Context, arg InsertEdgePolicyMigParams) (int64, error) { - row := q.db.QueryRowContext(ctx, insertEdgePolicyMig, - arg.Version, - arg.ChannelID, - arg.NodeID, - arg.Timelock, - arg.FeePpm, - arg.BaseFeeMsat, - arg.MinHtlcMsat, - arg.LastUpdate, - arg.Disabled, - arg.MaxHtlcMsat, - arg.InboundBaseFeeMsat, - arg.InboundFeeRateMilliMsat, - arg.MessageFlags, - arg.ChannelFlags, - arg.Signature, - ) - var id int64 - err := row.Scan(&id) - return id, err -} - -const insertNodeFeature = `-- name: InsertNodeFeature :exec -/* ───────────────────────────────────────────── - graph_node_features table queries - ───────────────────────────────────────────── -*/ - -INSERT INTO graph_node_features ( - node_id, feature_bit -) VALUES ( - $1, $2 -) ON CONFLICT (node_id, feature_bit) - -- Do nothing if the feature already exists for the node. - DO NOTHING -` - -type InsertNodeFeatureParams struct { - NodeID int64 - FeatureBit int32 -} - -func (q *Queries) InsertNodeFeature(ctx context.Context, arg InsertNodeFeatureParams) error { - _, err := q.db.ExecContext(ctx, insertNodeFeature, arg.NodeID, arg.FeatureBit) - return err -} - -const insertNodeMig = `-- name: InsertNodeMig :one -/* ───────────────────────────────────────────── - Migration specific queries - - NOTE: once sqldbv2 is in place, these queries can be contained to a package - dedicated to the migration that requires it, and so we can then remove - it from the main set of "live" queries that the code-base has access to. - ────────────────────────────────────────────- -*/ - -INSERT INTO graph_nodes ( - version, pub_key, alias, last_update, color, signature -) VALUES ( - $1, $2, $3, $4, $5, $6 -) -ON CONFLICT (pub_key, version) - -- If a conflict occurs, we have already migrated this node. However, we - -- still need to do an "UPDATE SET" here instead of "DO NOTHING" because - -- otherwise, the "RETURNING id" part does not work. - DO UPDATE SET - alias = EXCLUDED.alias, - last_update = EXCLUDED.last_update, - color = EXCLUDED.color, - signature = EXCLUDED.signature -RETURNING id -` - -type InsertNodeMigParams struct { - Version int16 - PubKey []byte - Alias sql.NullString - LastUpdate sql.NullInt64 - Color sql.NullString - Signature []byte -} - -// NOTE: This query is only meant to be used by the graph SQL migration since -// for that migration, in order to be retry-safe, we don't want to error out if -// we re-insert the same node (which would error if the normal UpsertNode query -// is used because of the constraint in that query that requires a node update -// to have a newer last_update than the existing node). -func (q *Queries) InsertNodeMig(ctx context.Context, arg InsertNodeMigParams) (int64, error) { - row := q.db.QueryRowContext(ctx, insertNodeMig, - arg.Version, - arg.PubKey, - arg.Alias, - arg.LastUpdate, - arg.Color, - arg.Signature, - ) - var id int64 - err := row.Scan(&id) - return id, err -} - -const isClosedChannel = `-- name: IsClosedChannel :one -SELECT EXISTS ( - SELECT 1 - FROM graph_closed_scids - WHERE scid = $1 -) -` - -func (q *Queries) IsClosedChannel(ctx context.Context, scid []byte) (bool, error) { - row := q.db.QueryRowContext(ctx, isClosedChannel, scid) - var exists bool - err := row.Scan(&exists) - return exists, err -} - -const isPublicV1Node = `-- name: IsPublicV1Node :one -SELECT EXISTS ( - SELECT 1 - FROM graph_channels c - JOIN graph_nodes n ON n.id = c.node_id_1 OR n.id = c.node_id_2 - -- NOTE: we hard-code the version here since the clauses - -- here that determine if a node is public is specific - -- to the V1 gossip protocol. In V1, a node is public - -- if it has a public channel and a public channel is one - -- where we have the set of signatures of the channel - -- announcement. It is enough to just check that we have - -- one of the signatures since we only ever set them - -- together. - WHERE c.version = 1 - AND c.bitcoin_1_signature IS NOT NULL - AND n.pub_key = $1 -) -` - -func (q *Queries) IsPublicV1Node(ctx context.Context, pubKey []byte) (bool, error) { - row := q.db.QueryRowContext(ctx, isPublicV1Node, pubKey) - var exists bool - err := row.Scan(&exists) - return exists, err -} - -const isZombieChannel = `-- name: IsZombieChannel :one -SELECT EXISTS ( - SELECT 1 - FROM graph_zombie_channels - WHERE scid = $1 - AND version = $2 -) AS is_zombie -` - -type IsZombieChannelParams struct { - Scid []byte - Version int16 -} - -func (q *Queries) IsZombieChannel(ctx context.Context, arg IsZombieChannelParams) (bool, error) { - row := q.db.QueryRowContext(ctx, isZombieChannel, arg.Scid, arg.Version) - var is_zombie bool - err := row.Scan(&is_zombie) - return is_zombie, err -} - -const listChannelsByNodeID = `-- name: ListChannelsByNodeID :many -SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, - n1.pub_key AS node1_pubkey, - n2.pub_key AS node2_pubkey, - - -- Policy 1 - -- TODO(elle): use sqlc.embed to embed policy structs - -- once this issue is resolved: - -- https://github.com/sqlc-dev/sqlc/issues/2997 - cp1.id AS policy1_id, - cp1.node_id AS policy1_node_id, - cp1.version AS policy1_version, - cp1.timelock AS policy1_timelock, - cp1.fee_ppm AS policy1_fee_ppm, - cp1.base_fee_msat AS policy1_base_fee_msat, - cp1.min_htlc_msat AS policy1_min_htlc_msat, - cp1.max_htlc_msat AS policy1_max_htlc_msat, - cp1.last_update AS policy1_last_update, - cp1.disabled AS policy1_disabled, - cp1.inbound_base_fee_msat AS policy1_inbound_base_fee_msat, - cp1.inbound_fee_rate_milli_msat AS policy1_inbound_fee_rate_milli_msat, - cp1.message_flags AS policy1_message_flags, - cp1.channel_flags AS policy1_channel_flags, - cp1.signature AS policy1_signature, - - -- Policy 2 - cp2.id AS policy2_id, - cp2.node_id AS policy2_node_id, - cp2.version AS policy2_version, - cp2.timelock AS policy2_timelock, - cp2.fee_ppm AS policy2_fee_ppm, - cp2.base_fee_msat AS policy2_base_fee_msat, - cp2.min_htlc_msat AS policy2_min_htlc_msat, - cp2.max_htlc_msat AS policy2_max_htlc_msat, - cp2.last_update AS policy2_last_update, - cp2.disabled AS policy2_disabled, - cp2.inbound_base_fee_msat AS policy2_inbound_base_fee_msat, - cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, - cp2.message_flags AS policy2_message_flags, - cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature - -FROM graph_channels c - JOIN graph_nodes n1 ON c.node_id_1 = n1.id - JOIN graph_nodes n2 ON c.node_id_2 = n2.id - LEFT JOIN graph_channel_policies cp1 - ON cp1.channel_id = c.id AND cp1.node_id = c.node_id_1 AND cp1.version = c.version - LEFT JOIN graph_channel_policies cp2 - ON cp2.channel_id = c.id AND cp2.node_id = c.node_id_2 AND cp2.version = c.version -WHERE c.version = $1 - AND (c.node_id_1 = $2 OR c.node_id_2 = $2) -` - -type ListChannelsByNodeIDParams struct { - Version int16 - NodeID1 int64 -} - -type ListChannelsByNodeIDRow struct { - GraphChannel GraphChannel - Node1Pubkey []byte - Node2Pubkey []byte - Policy1ID sql.NullInt64 - Policy1NodeID sql.NullInt64 - Policy1Version sql.NullInt16 - Policy1Timelock sql.NullInt32 - Policy1FeePpm sql.NullInt64 - Policy1BaseFeeMsat sql.NullInt64 - Policy1MinHtlcMsat sql.NullInt64 - Policy1MaxHtlcMsat sql.NullInt64 - Policy1LastUpdate sql.NullInt64 - Policy1Disabled sql.NullBool - Policy1InboundBaseFeeMsat sql.NullInt64 - Policy1InboundFeeRateMilliMsat sql.NullInt64 - Policy1MessageFlags sql.NullInt16 - Policy1ChannelFlags sql.NullInt16 - Policy1Signature []byte - Policy2ID sql.NullInt64 - Policy2NodeID sql.NullInt64 - Policy2Version sql.NullInt16 - Policy2Timelock sql.NullInt32 - Policy2FeePpm sql.NullInt64 - Policy2BaseFeeMsat sql.NullInt64 - Policy2MinHtlcMsat sql.NullInt64 - Policy2MaxHtlcMsat sql.NullInt64 - Policy2LastUpdate sql.NullInt64 - Policy2Disabled sql.NullBool - Policy2InboundBaseFeeMsat sql.NullInt64 - Policy2InboundFeeRateMilliMsat sql.NullInt64 - Policy2MessageFlags sql.NullInt16 - Policy2ChannelFlags sql.NullInt16 - Policy2Signature []byte -} - -func (q *Queries) ListChannelsByNodeID(ctx context.Context, arg ListChannelsByNodeIDParams) ([]ListChannelsByNodeIDRow, error) { - rows, err := q.db.QueryContext(ctx, listChannelsByNodeID, arg.Version, arg.NodeID1) - if err != nil { - return nil, err - } - defer rows.Close() - var items []ListChannelsByNodeIDRow - for rows.Next() { - var i ListChannelsByNodeIDRow - if err := rows.Scan( - &i.GraphChannel.ID, - &i.GraphChannel.Version, - &i.GraphChannel.Scid, - &i.GraphChannel.NodeID1, - &i.GraphChannel.NodeID2, - &i.GraphChannel.Outpoint, - &i.GraphChannel.Capacity, - &i.GraphChannel.BitcoinKey1, - &i.GraphChannel.BitcoinKey2, - &i.GraphChannel.Node1Signature, - &i.GraphChannel.Node2Signature, - &i.GraphChannel.Bitcoin1Signature, - &i.GraphChannel.Bitcoin2Signature, - &i.Node1Pubkey, - &i.Node2Pubkey, - &i.Policy1ID, - &i.Policy1NodeID, - &i.Policy1Version, - &i.Policy1Timelock, - &i.Policy1FeePpm, - &i.Policy1BaseFeeMsat, - &i.Policy1MinHtlcMsat, - &i.Policy1MaxHtlcMsat, - &i.Policy1LastUpdate, - &i.Policy1Disabled, - &i.Policy1InboundBaseFeeMsat, - &i.Policy1InboundFeeRateMilliMsat, - &i.Policy1MessageFlags, - &i.Policy1ChannelFlags, - &i.Policy1Signature, - &i.Policy2ID, - &i.Policy2NodeID, - &i.Policy2Version, - &i.Policy2Timelock, - &i.Policy2FeePpm, - &i.Policy2BaseFeeMsat, - &i.Policy2MinHtlcMsat, - &i.Policy2MaxHtlcMsat, - &i.Policy2LastUpdate, - &i.Policy2Disabled, - &i.Policy2InboundBaseFeeMsat, - &i.Policy2InboundFeeRateMilliMsat, - &i.Policy2MessageFlags, - &i.Policy2ChannelFlags, - &i.Policy2Signature, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const listChannelsForNodeIDs = `-- name: ListChannelsForNodeIDs :many -SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, - n1.pub_key AS node1_pubkey, - n2.pub_key AS node2_pubkey, - - -- Policy 1 - -- TODO(elle): use sqlc.embed to embed policy structs - -- once this issue is resolved: - -- https://github.com/sqlc-dev/sqlc/issues/2997 - cp1.id AS policy1_id, - cp1.node_id AS policy1_node_id, - cp1.version AS policy1_version, - cp1.timelock AS policy1_timelock, - cp1.fee_ppm AS policy1_fee_ppm, - cp1.base_fee_msat AS policy1_base_fee_msat, - cp1.min_htlc_msat AS policy1_min_htlc_msat, - cp1.max_htlc_msat AS policy1_max_htlc_msat, - cp1.last_update AS policy1_last_update, - cp1.disabled AS policy1_disabled, - cp1.inbound_base_fee_msat AS policy1_inbound_base_fee_msat, - cp1.inbound_fee_rate_milli_msat AS policy1_inbound_fee_rate_milli_msat, - cp1.message_flags AS policy1_message_flags, - cp1.channel_flags AS policy1_channel_flags, - cp1.signature AS policy1_signature, - - -- Policy 2 - cp2.id AS policy2_id, - cp2.node_id AS policy2_node_id, - cp2.version AS policy2_version, - cp2.timelock AS policy2_timelock, - cp2.fee_ppm AS policy2_fee_ppm, - cp2.base_fee_msat AS policy2_base_fee_msat, - cp2.min_htlc_msat AS policy2_min_htlc_msat, - cp2.max_htlc_msat AS policy2_max_htlc_msat, - cp2.last_update AS policy2_last_update, - cp2.disabled AS policy2_disabled, - cp2.inbound_base_fee_msat AS policy2_inbound_base_fee_msat, - cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, - cp2.message_flags AS policy2_message_flags, - cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature - -FROM graph_channels c - JOIN graph_nodes n1 ON c.node_id_1 = n1.id - JOIN graph_nodes n2 ON c.node_id_2 = n2.id - LEFT JOIN graph_channel_policies cp1 - ON cp1.channel_id = c.id AND cp1.node_id = c.node_id_1 AND cp1.version = c.version - LEFT JOIN graph_channel_policies cp2 - ON cp2.channel_id = c.id AND cp2.node_id = c.node_id_2 AND cp2.version = c.version -WHERE c.version = $1 - AND (c.node_id_1 IN (/*SLICE:node1_ids*/?) - OR c.node_id_2 IN (/*SLICE:node2_ids*/?)) -` - -type ListChannelsForNodeIDsParams struct { - Version int16 - Node1Ids []int64 - Node2Ids []int64 -} - -type ListChannelsForNodeIDsRow struct { - GraphChannel GraphChannel - Node1Pubkey []byte - Node2Pubkey []byte - Policy1ID sql.NullInt64 - Policy1NodeID sql.NullInt64 - Policy1Version sql.NullInt16 - Policy1Timelock sql.NullInt32 - Policy1FeePpm sql.NullInt64 - Policy1BaseFeeMsat sql.NullInt64 - Policy1MinHtlcMsat sql.NullInt64 - Policy1MaxHtlcMsat sql.NullInt64 - Policy1LastUpdate sql.NullInt64 - Policy1Disabled sql.NullBool - Policy1InboundBaseFeeMsat sql.NullInt64 - Policy1InboundFeeRateMilliMsat sql.NullInt64 - Policy1MessageFlags sql.NullInt16 - Policy1ChannelFlags sql.NullInt16 - Policy1Signature []byte - Policy2ID sql.NullInt64 - Policy2NodeID sql.NullInt64 - Policy2Version sql.NullInt16 - Policy2Timelock sql.NullInt32 - Policy2FeePpm sql.NullInt64 - Policy2BaseFeeMsat sql.NullInt64 - Policy2MinHtlcMsat sql.NullInt64 - Policy2MaxHtlcMsat sql.NullInt64 - Policy2LastUpdate sql.NullInt64 - Policy2Disabled sql.NullBool - Policy2InboundBaseFeeMsat sql.NullInt64 - Policy2InboundFeeRateMilliMsat sql.NullInt64 - Policy2MessageFlags sql.NullInt16 - Policy2ChannelFlags sql.NullInt16 - Policy2Signature []byte -} - -func (q *Queries) ListChannelsForNodeIDs(ctx context.Context, arg ListChannelsForNodeIDsParams) ([]ListChannelsForNodeIDsRow, error) { - query := listChannelsForNodeIDs - var queryParams []interface{} - queryParams = append(queryParams, arg.Version) - if len(arg.Node1Ids) > 0 { - for _, v := range arg.Node1Ids { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:node1_ids*/?", makeQueryParams(len(queryParams), len(arg.Node1Ids)), 1) - } else { - query = strings.Replace(query, "/*SLICE:node1_ids*/?", "NULL", 1) - } - if len(arg.Node2Ids) > 0 { - for _, v := range arg.Node2Ids { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:node2_ids*/?", makeQueryParams(len(queryParams), len(arg.Node2Ids)), 1) - } else { - query = strings.Replace(query, "/*SLICE:node2_ids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []ListChannelsForNodeIDsRow - for rows.Next() { - var i ListChannelsForNodeIDsRow - if err := rows.Scan( - &i.GraphChannel.ID, - &i.GraphChannel.Version, - &i.GraphChannel.Scid, - &i.GraphChannel.NodeID1, - &i.GraphChannel.NodeID2, - &i.GraphChannel.Outpoint, - &i.GraphChannel.Capacity, - &i.GraphChannel.BitcoinKey1, - &i.GraphChannel.BitcoinKey2, - &i.GraphChannel.Node1Signature, - &i.GraphChannel.Node2Signature, - &i.GraphChannel.Bitcoin1Signature, - &i.GraphChannel.Bitcoin2Signature, - &i.Node1Pubkey, - &i.Node2Pubkey, - &i.Policy1ID, - &i.Policy1NodeID, - &i.Policy1Version, - &i.Policy1Timelock, - &i.Policy1FeePpm, - &i.Policy1BaseFeeMsat, - &i.Policy1MinHtlcMsat, - &i.Policy1MaxHtlcMsat, - &i.Policy1LastUpdate, - &i.Policy1Disabled, - &i.Policy1InboundBaseFeeMsat, - &i.Policy1InboundFeeRateMilliMsat, - &i.Policy1MessageFlags, - &i.Policy1ChannelFlags, - &i.Policy1Signature, - &i.Policy2ID, - &i.Policy2NodeID, - &i.Policy2Version, - &i.Policy2Timelock, - &i.Policy2FeePpm, - &i.Policy2BaseFeeMsat, - &i.Policy2MinHtlcMsat, - &i.Policy2MaxHtlcMsat, - &i.Policy2LastUpdate, - &i.Policy2Disabled, - &i.Policy2InboundBaseFeeMsat, - &i.Policy2InboundFeeRateMilliMsat, - &i.Policy2MessageFlags, - &i.Policy2ChannelFlags, - &i.Policy2Signature, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const listChannelsPaginated = `-- name: ListChannelsPaginated :many -SELECT id, bitcoin_key_1, bitcoin_key_2, outpoint -FROM graph_channels c -WHERE c.version = $1 AND c.id > $2 -ORDER BY c.id -LIMIT $3 -` - -type ListChannelsPaginatedParams struct { - Version int16 - ID int64 - Limit int32 -} - -type ListChannelsPaginatedRow struct { - ID int64 - BitcoinKey1 []byte - BitcoinKey2 []byte - Outpoint string -} - -func (q *Queries) ListChannelsPaginated(ctx context.Context, arg ListChannelsPaginatedParams) ([]ListChannelsPaginatedRow, error) { - rows, err := q.db.QueryContext(ctx, listChannelsPaginated, arg.Version, arg.ID, arg.Limit) - if err != nil { - return nil, err - } - defer rows.Close() - var items []ListChannelsPaginatedRow - for rows.Next() { - var i ListChannelsPaginatedRow - if err := rows.Scan( - &i.ID, - &i.BitcoinKey1, - &i.BitcoinKey2, - &i.Outpoint, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const listChannelsWithPoliciesForCachePaginated = `-- name: ListChannelsWithPoliciesForCachePaginated :many -SELECT - c.id as id, - c.scid as scid, - c.capacity AS capacity, - - -- Join node pubkeys - n1.pub_key AS node1_pubkey, - n2.pub_key AS node2_pubkey, - - -- Node 1 policy - cp1.timelock AS policy_1_timelock, - cp1.fee_ppm AS policy_1_fee_ppm, - cp1.base_fee_msat AS policy_1_base_fee_msat, - cp1.min_htlc_msat AS policy_1_min_htlc_msat, - cp1.max_htlc_msat AS policy_1_max_htlc_msat, - cp1.disabled AS policy_1_disabled, - cp1.inbound_base_fee_msat AS policy1_inbound_base_fee_msat, - cp1.inbound_fee_rate_milli_msat AS policy1_inbound_fee_rate_milli_msat, - cp1.message_flags AS policy1_message_flags, - cp1.channel_flags AS policy1_channel_flags, - - -- Node 2 policy - cp2.timelock AS policy_2_timelock, - cp2.fee_ppm AS policy_2_fee_ppm, - cp2.base_fee_msat AS policy_2_base_fee_msat, - cp2.min_htlc_msat AS policy_2_min_htlc_msat, - cp2.max_htlc_msat AS policy_2_max_htlc_msat, - cp2.disabled AS policy_2_disabled, - cp2.inbound_base_fee_msat AS policy2_inbound_base_fee_msat, - cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, - cp2.message_flags AS policy2_message_flags, - cp2.channel_flags AS policy2_channel_flags - -FROM graph_channels c -JOIN graph_nodes n1 ON c.node_id_1 = n1.id -JOIN graph_nodes n2 ON c.node_id_2 = n2.id -LEFT JOIN graph_channel_policies cp1 - ON cp1.channel_id = c.id AND cp1.node_id = c.node_id_1 AND cp1.version = c.version -LEFT JOIN graph_channel_policies cp2 - ON cp2.channel_id = c.id AND cp2.node_id = c.node_id_2 AND cp2.version = c.version -WHERE c.version = $1 AND c.id > $2 -ORDER BY c.id -LIMIT $3 -` - -type ListChannelsWithPoliciesForCachePaginatedParams struct { - Version int16 - ID int64 - Limit int32 -} - -type ListChannelsWithPoliciesForCachePaginatedRow struct { - ID int64 - Scid []byte - Capacity sql.NullInt64 - Node1Pubkey []byte - Node2Pubkey []byte - Policy1Timelock sql.NullInt32 - Policy1FeePpm sql.NullInt64 - Policy1BaseFeeMsat sql.NullInt64 - Policy1MinHtlcMsat sql.NullInt64 - Policy1MaxHtlcMsat sql.NullInt64 - Policy1Disabled sql.NullBool - Policy1InboundBaseFeeMsat sql.NullInt64 - Policy1InboundFeeRateMilliMsat sql.NullInt64 - Policy1MessageFlags sql.NullInt16 - Policy1ChannelFlags sql.NullInt16 - Policy2Timelock sql.NullInt32 - Policy2FeePpm sql.NullInt64 - Policy2BaseFeeMsat sql.NullInt64 - Policy2MinHtlcMsat sql.NullInt64 - Policy2MaxHtlcMsat sql.NullInt64 - Policy2Disabled sql.NullBool - Policy2InboundBaseFeeMsat sql.NullInt64 - Policy2InboundFeeRateMilliMsat sql.NullInt64 - Policy2MessageFlags sql.NullInt16 - Policy2ChannelFlags sql.NullInt16 -} - -func (q *Queries) ListChannelsWithPoliciesForCachePaginated(ctx context.Context, arg ListChannelsWithPoliciesForCachePaginatedParams) ([]ListChannelsWithPoliciesForCachePaginatedRow, error) { - rows, err := q.db.QueryContext(ctx, listChannelsWithPoliciesForCachePaginated, arg.Version, arg.ID, arg.Limit) - if err != nil { - return nil, err - } - defer rows.Close() - var items []ListChannelsWithPoliciesForCachePaginatedRow - for rows.Next() { - var i ListChannelsWithPoliciesForCachePaginatedRow - if err := rows.Scan( - &i.ID, - &i.Scid, - &i.Capacity, - &i.Node1Pubkey, - &i.Node2Pubkey, - &i.Policy1Timelock, - &i.Policy1FeePpm, - &i.Policy1BaseFeeMsat, - &i.Policy1MinHtlcMsat, - &i.Policy1MaxHtlcMsat, - &i.Policy1Disabled, - &i.Policy1InboundBaseFeeMsat, - &i.Policy1InboundFeeRateMilliMsat, - &i.Policy1MessageFlags, - &i.Policy1ChannelFlags, - &i.Policy2Timelock, - &i.Policy2FeePpm, - &i.Policy2BaseFeeMsat, - &i.Policy2MinHtlcMsat, - &i.Policy2MaxHtlcMsat, - &i.Policy2Disabled, - &i.Policy2InboundBaseFeeMsat, - &i.Policy2InboundFeeRateMilliMsat, - &i.Policy2MessageFlags, - &i.Policy2ChannelFlags, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const listChannelsWithPoliciesPaginated = `-- name: ListChannelsWithPoliciesPaginated :many -SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, - - -- Join node pubkeys - n1.pub_key AS node1_pubkey, - n2.pub_key AS node2_pubkey, - - -- Node 1 policy - cp1.id AS policy_1_id, - cp1.node_id AS policy_1_node_id, - cp1.version AS policy_1_version, - cp1.timelock AS policy_1_timelock, - cp1.fee_ppm AS policy_1_fee_ppm, - cp1.base_fee_msat AS policy_1_base_fee_msat, - cp1.min_htlc_msat AS policy_1_min_htlc_msat, - cp1.max_htlc_msat AS policy_1_max_htlc_msat, - cp1.last_update AS policy_1_last_update, - cp1.disabled AS policy_1_disabled, - cp1.inbound_base_fee_msat AS policy1_inbound_base_fee_msat, - cp1.inbound_fee_rate_milli_msat AS policy1_inbound_fee_rate_milli_msat, - cp1.message_flags AS policy1_message_flags, - cp1.channel_flags AS policy1_channel_flags, - cp1.signature AS policy_1_signature, - - -- Node 2 policy - cp2.id AS policy_2_id, - cp2.node_id AS policy_2_node_id, - cp2.version AS policy_2_version, - cp2.timelock AS policy_2_timelock, - cp2.fee_ppm AS policy_2_fee_ppm, - cp2.base_fee_msat AS policy_2_base_fee_msat, - cp2.min_htlc_msat AS policy_2_min_htlc_msat, - cp2.max_htlc_msat AS policy_2_max_htlc_msat, - cp2.last_update AS policy_2_last_update, - cp2.disabled AS policy_2_disabled, - cp2.inbound_base_fee_msat AS policy2_inbound_base_fee_msat, - cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, - cp2.message_flags AS policy2_message_flags, - cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy_2_signature - -FROM graph_channels c -JOIN graph_nodes n1 ON c.node_id_1 = n1.id -JOIN graph_nodes n2 ON c.node_id_2 = n2.id -LEFT JOIN graph_channel_policies cp1 - ON cp1.channel_id = c.id AND cp1.node_id = c.node_id_1 AND cp1.version = c.version -LEFT JOIN graph_channel_policies cp2 - ON cp2.channel_id = c.id AND cp2.node_id = c.node_id_2 AND cp2.version = c.version -WHERE c.version = $1 AND c.id > $2 -ORDER BY c.id -LIMIT $3 -` - -type ListChannelsWithPoliciesPaginatedParams struct { - Version int16 - ID int64 - Limit int32 -} - -type ListChannelsWithPoliciesPaginatedRow struct { - GraphChannel GraphChannel - Node1Pubkey []byte - Node2Pubkey []byte - Policy1ID sql.NullInt64 - Policy1NodeID sql.NullInt64 - Policy1Version sql.NullInt16 - Policy1Timelock sql.NullInt32 - Policy1FeePpm sql.NullInt64 - Policy1BaseFeeMsat sql.NullInt64 - Policy1MinHtlcMsat sql.NullInt64 - Policy1MaxHtlcMsat sql.NullInt64 - Policy1LastUpdate sql.NullInt64 - Policy1Disabled sql.NullBool - Policy1InboundBaseFeeMsat sql.NullInt64 - Policy1InboundFeeRateMilliMsat sql.NullInt64 - Policy1MessageFlags sql.NullInt16 - Policy1ChannelFlags sql.NullInt16 - Policy1Signature []byte - Policy2ID sql.NullInt64 - Policy2NodeID sql.NullInt64 - Policy2Version sql.NullInt16 - Policy2Timelock sql.NullInt32 - Policy2FeePpm sql.NullInt64 - Policy2BaseFeeMsat sql.NullInt64 - Policy2MinHtlcMsat sql.NullInt64 - Policy2MaxHtlcMsat sql.NullInt64 - Policy2LastUpdate sql.NullInt64 - Policy2Disabled sql.NullBool - Policy2InboundBaseFeeMsat sql.NullInt64 - Policy2InboundFeeRateMilliMsat sql.NullInt64 - Policy2MessageFlags sql.NullInt16 - Policy2ChannelFlags sql.NullInt16 - Policy2Signature []byte -} - -func (q *Queries) ListChannelsWithPoliciesPaginated(ctx context.Context, arg ListChannelsWithPoliciesPaginatedParams) ([]ListChannelsWithPoliciesPaginatedRow, error) { - rows, err := q.db.QueryContext(ctx, listChannelsWithPoliciesPaginated, arg.Version, arg.ID, arg.Limit) - if err != nil { - return nil, err - } - defer rows.Close() - var items []ListChannelsWithPoliciesPaginatedRow - for rows.Next() { - var i ListChannelsWithPoliciesPaginatedRow - if err := rows.Scan( - &i.GraphChannel.ID, - &i.GraphChannel.Version, - &i.GraphChannel.Scid, - &i.GraphChannel.NodeID1, - &i.GraphChannel.NodeID2, - &i.GraphChannel.Outpoint, - &i.GraphChannel.Capacity, - &i.GraphChannel.BitcoinKey1, - &i.GraphChannel.BitcoinKey2, - &i.GraphChannel.Node1Signature, - &i.GraphChannel.Node2Signature, - &i.GraphChannel.Bitcoin1Signature, - &i.GraphChannel.Bitcoin2Signature, - &i.Node1Pubkey, - &i.Node2Pubkey, - &i.Policy1ID, - &i.Policy1NodeID, - &i.Policy1Version, - &i.Policy1Timelock, - &i.Policy1FeePpm, - &i.Policy1BaseFeeMsat, - &i.Policy1MinHtlcMsat, - &i.Policy1MaxHtlcMsat, - &i.Policy1LastUpdate, - &i.Policy1Disabled, - &i.Policy1InboundBaseFeeMsat, - &i.Policy1InboundFeeRateMilliMsat, - &i.Policy1MessageFlags, - &i.Policy1ChannelFlags, - &i.Policy1Signature, - &i.Policy2ID, - &i.Policy2NodeID, - &i.Policy2Version, - &i.Policy2Timelock, - &i.Policy2FeePpm, - &i.Policy2BaseFeeMsat, - &i.Policy2MinHtlcMsat, - &i.Policy2MaxHtlcMsat, - &i.Policy2LastUpdate, - &i.Policy2Disabled, - &i.Policy2InboundBaseFeeMsat, - &i.Policy2InboundFeeRateMilliMsat, - &i.Policy2MessageFlags, - &i.Policy2ChannelFlags, - &i.Policy2Signature, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const listNodeIDsAndPubKeys = `-- name: ListNodeIDsAndPubKeys :many -SELECT id, pub_key -FROM graph_nodes -WHERE version = $1 AND id > $2 -ORDER BY id -LIMIT $3 -` - -type ListNodeIDsAndPubKeysParams struct { - Version int16 - ID int64 - Limit int32 -} - -type ListNodeIDsAndPubKeysRow struct { - ID int64 - PubKey []byte -} - -func (q *Queries) ListNodeIDsAndPubKeys(ctx context.Context, arg ListNodeIDsAndPubKeysParams) ([]ListNodeIDsAndPubKeysRow, error) { - rows, err := q.db.QueryContext(ctx, listNodeIDsAndPubKeys, arg.Version, arg.ID, arg.Limit) - if err != nil { - return nil, err - } - defer rows.Close() - var items []ListNodeIDsAndPubKeysRow - for rows.Next() { - var i ListNodeIDsAndPubKeysRow - if err := rows.Scan(&i.ID, &i.PubKey); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const listNodesPaginated = `-- name: ListNodesPaginated :many -SELECT id, version, pub_key, alias, last_update, color, signature -FROM graph_nodes -WHERE version = $1 AND id > $2 -ORDER BY id -LIMIT $3 -` - -type ListNodesPaginatedParams struct { - Version int16 - ID int64 - Limit int32 -} - -func (q *Queries) ListNodesPaginated(ctx context.Context, arg ListNodesPaginatedParams) ([]GraphNode, error) { - rows, err := q.db.QueryContext(ctx, listNodesPaginated, arg.Version, arg.ID, arg.Limit) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GraphNode - for rows.Next() { - var i GraphNode - if err := rows.Scan( - &i.ID, - &i.Version, - &i.PubKey, - &i.Alias, - &i.LastUpdate, - &i.Color, - &i.Signature, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const upsertChanPolicyExtraType = `-- name: UpsertChanPolicyExtraType :exec -/* ───────────────────────────────────────────── - graph_channel_policy_extra_types table queries - ───────────────────────────────────────────── -*/ - -INSERT INTO graph_channel_policy_extra_types ( - channel_policy_id, type, value -) -VALUES ($1, $2, $3) -ON CONFLICT (channel_policy_id, type) - -- If a conflict occurs on channel_policy_id and type, then we update the - -- value. - DO UPDATE SET value = EXCLUDED.value -` - -type UpsertChanPolicyExtraTypeParams struct { - ChannelPolicyID int64 - Type int64 - Value []byte -} - -func (q *Queries) UpsertChanPolicyExtraType(ctx context.Context, arg UpsertChanPolicyExtraTypeParams) error { - _, err := q.db.ExecContext(ctx, upsertChanPolicyExtraType, arg.ChannelPolicyID, arg.Type, arg.Value) - return err -} - -const upsertChannelExtraType = `-- name: UpsertChannelExtraType :exec -/* ───────────────────────────────────────────── - graph_channel_extra_types table queries - ───────────────────────────────────────────── -*/ - -INSERT INTO graph_channel_extra_types ( - channel_id, type, value -) -VALUES ($1, $2, $3) - ON CONFLICT (channel_id, type) - -- Update the value if a conflict occurs on channel_id and type. - DO UPDATE SET value = EXCLUDED.value -` - -type UpsertChannelExtraTypeParams struct { - ChannelID int64 - Type int64 - Value []byte -} - -func (q *Queries) UpsertChannelExtraType(ctx context.Context, arg UpsertChannelExtraTypeParams) error { - _, err := q.db.ExecContext(ctx, upsertChannelExtraType, arg.ChannelID, arg.Type, arg.Value) - return err -} - -const upsertEdgePolicy = `-- name: UpsertEdgePolicy :one -/* ───────────────────────────────────────────── - graph_channel_policies table queries - ───────────────────────────────────────────── -*/ - -INSERT INTO graph_channel_policies ( - version, channel_id, node_id, timelock, fee_ppm, - base_fee_msat, min_htlc_msat, last_update, disabled, - max_htlc_msat, inbound_base_fee_msat, - inbound_fee_rate_milli_msat, message_flags, channel_flags, - signature -) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15 -) -ON CONFLICT (channel_id, node_id, version) - -- Update the following fields if a conflict occurs on channel_id, - -- node_id, and version. - DO UPDATE SET - timelock = EXCLUDED.timelock, - fee_ppm = EXCLUDED.fee_ppm, - base_fee_msat = EXCLUDED.base_fee_msat, - min_htlc_msat = EXCLUDED.min_htlc_msat, - last_update = EXCLUDED.last_update, - disabled = EXCLUDED.disabled, - max_htlc_msat = EXCLUDED.max_htlc_msat, - inbound_base_fee_msat = EXCLUDED.inbound_base_fee_msat, - inbound_fee_rate_milli_msat = EXCLUDED.inbound_fee_rate_milli_msat, - message_flags = EXCLUDED.message_flags, - channel_flags = EXCLUDED.channel_flags, - signature = EXCLUDED.signature -WHERE EXCLUDED.last_update > graph_channel_policies.last_update -RETURNING id -` - -type UpsertEdgePolicyParams struct { - Version int16 - ChannelID int64 - NodeID int64 - Timelock int32 - FeePpm int64 - BaseFeeMsat int64 - MinHtlcMsat int64 - LastUpdate sql.NullInt64 - Disabled sql.NullBool - MaxHtlcMsat sql.NullInt64 - InboundBaseFeeMsat sql.NullInt64 - InboundFeeRateMilliMsat sql.NullInt64 - MessageFlags sql.NullInt16 - ChannelFlags sql.NullInt16 - Signature []byte -} - -func (q *Queries) UpsertEdgePolicy(ctx context.Context, arg UpsertEdgePolicyParams) (int64, error) { - row := q.db.QueryRowContext(ctx, upsertEdgePolicy, - arg.Version, - arg.ChannelID, - arg.NodeID, - arg.Timelock, - arg.FeePpm, - arg.BaseFeeMsat, - arg.MinHtlcMsat, - arg.LastUpdate, - arg.Disabled, - arg.MaxHtlcMsat, - arg.InboundBaseFeeMsat, - arg.InboundFeeRateMilliMsat, - arg.MessageFlags, - arg.ChannelFlags, - arg.Signature, - ) - var id int64 - err := row.Scan(&id) - return id, err -} - -const upsertNode = `-- name: UpsertNode :one -/* ───────────────────────────────────────────── - graph_nodes table queries - ───────────────────────────��───────────────── -*/ - -INSERT INTO graph_nodes ( - version, pub_key, alias, last_update, color, signature -) VALUES ( - $1, $2, $3, $4, $5, $6 -) -ON CONFLICT (pub_key, version) - -- Update the following fields if a conflict occurs on pub_key - -- and version. - DO UPDATE SET - alias = EXCLUDED.alias, - last_update = EXCLUDED.last_update, - color = EXCLUDED.color, - signature = EXCLUDED.signature -WHERE graph_nodes.last_update IS NULL - OR EXCLUDED.last_update > graph_nodes.last_update -RETURNING id -` - -type UpsertNodeParams struct { - Version int16 - PubKey []byte - Alias sql.NullString - LastUpdate sql.NullInt64 - Color sql.NullString - Signature []byte -} - -func (q *Queries) UpsertNode(ctx context.Context, arg UpsertNodeParams) (int64, error) { - row := q.db.QueryRowContext(ctx, upsertNode, - arg.Version, - arg.PubKey, - arg.Alias, - arg.LastUpdate, - arg.Color, - arg.Signature, - ) - var id int64 - err := row.Scan(&id) - return id, err -} - -const upsertNodeAddress = `-- name: UpsertNodeAddress :exec -/* ───────────────────────────────────────────── - graph_node_addresses table queries - ───────────────────────────────────��───────── -*/ - -INSERT INTO graph_node_addresses ( - node_id, - type, - address, - position -) VALUES ( - $1, $2, $3, $4 -) ON CONFLICT (node_id, type, position) - DO UPDATE SET address = EXCLUDED.address -` - -type UpsertNodeAddressParams struct { - NodeID int64 - Type int16 - Address string - Position int32 -} - -func (q *Queries) UpsertNodeAddress(ctx context.Context, arg UpsertNodeAddressParams) error { - _, err := q.db.ExecContext(ctx, upsertNodeAddress, - arg.NodeID, - arg.Type, - arg.Address, - arg.Position, - ) - return err -} - -const upsertNodeExtraType = `-- name: UpsertNodeExtraType :exec -/* ───────────────────────────────────────────── - graph_node_extra_types table queries - ───────────────────────────────────────────── -*/ - -INSERT INTO graph_node_extra_types ( - node_id, type, value -) -VALUES ($1, $2, $3) -ON CONFLICT (type, node_id) - -- Update the value if a conflict occurs on type - -- and node_id. - DO UPDATE SET value = EXCLUDED.value -` - -type UpsertNodeExtraTypeParams struct { - NodeID int64 - Type int64 - Value []byte -} - -func (q *Queries) UpsertNodeExtraType(ctx context.Context, arg UpsertNodeExtraTypeParams) error { - _, err := q.db.ExecContext(ctx, upsertNodeExtraType, arg.NodeID, arg.Type, arg.Value) - return err -} - -const upsertPruneLogEntry = `-- name: UpsertPruneLogEntry :exec -/* ───────────────────────────���───────────────── - graph_prune_log table queries - ───────────────────────────────────────────── -*/ - -INSERT INTO graph_prune_log ( - block_height, block_hash -) VALUES ( - $1, $2 -) -ON CONFLICT(block_height) DO UPDATE SET - block_hash = EXCLUDED.block_hash -` - -type UpsertPruneLogEntryParams struct { - BlockHeight int64 - BlockHash []byte -} - -func (q *Queries) UpsertPruneLogEntry(ctx context.Context, arg UpsertPruneLogEntryParams) error { - _, err := q.db.ExecContext(ctx, upsertPruneLogEntry, arg.BlockHeight, arg.BlockHash) - return err -} - -const upsertZombieChannel = `-- name: UpsertZombieChannel :exec -/* ───────────────────────────────────────────── - graph_zombie_channels table queries - ───────────────────────────────────────────── -*/ - -INSERT INTO graph_zombie_channels (scid, version, node_key_1, node_key_2) -VALUES ($1, $2, $3, $4) -ON CONFLICT (scid, version) -DO UPDATE SET - -- If a conflict exists for the SCID and version pair, then we - -- update the node keys. - node_key_1 = COALESCE(EXCLUDED.node_key_1, graph_zombie_channels.node_key_1), - node_key_2 = COALESCE(EXCLUDED.node_key_2, graph_zombie_channels.node_key_2) -` - -type UpsertZombieChannelParams struct { - Scid []byte - Version int16 - NodeKey1 []byte - NodeKey2 []byte -} - -func (q *Queries) UpsertZombieChannel(ctx context.Context, arg UpsertZombieChannelParams) error { - _, err := q.db.ExecContext(ctx, upsertZombieChannel, - arg.Scid, - arg.Version, - arg.NodeKey1, - arg.NodeKey2, - ) - return err -} diff --git a/graph/db/migration1/sqlc/models.go b/graph/db/migration1/sqlc/models.go deleted file mode 100644 index 5e5d12c57..000000000 --- a/graph/db/migration1/sqlc/models.go +++ /dev/null @@ -1,109 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.29.0 - -package sqlc - -import ( - "database/sql" -) - -type GraphChannel struct { - ID int64 - Version int16 - Scid []byte - NodeID1 int64 - NodeID2 int64 - Outpoint string - Capacity sql.NullInt64 - BitcoinKey1 []byte - BitcoinKey2 []byte - Node1Signature []byte - Node2Signature []byte - Bitcoin1Signature []byte - Bitcoin2Signature []byte -} - -type GraphChannelExtraType struct { - ChannelID int64 - Type int64 - Value []byte -} - -type GraphChannelFeature struct { - ChannelID int64 - FeatureBit int32 -} - -type GraphChannelPolicy struct { - ID int64 - Version int16 - ChannelID int64 - NodeID int64 - Timelock int32 - FeePpm int64 - BaseFeeMsat int64 - MinHtlcMsat int64 - MaxHtlcMsat sql.NullInt64 - LastUpdate sql.NullInt64 - Disabled sql.NullBool - InboundBaseFeeMsat sql.NullInt64 - InboundFeeRateMilliMsat sql.NullInt64 - MessageFlags sql.NullInt16 - ChannelFlags sql.NullInt16 - Signature []byte -} - -type GraphChannelPolicyExtraType struct { - ChannelPolicyID int64 - Type int64 - Value []byte -} - -type GraphClosedScid struct { - Scid []byte -} - -type GraphNode struct { - ID int64 - Version int16 - PubKey []byte - Alias sql.NullString - LastUpdate sql.NullInt64 - Color sql.NullString - Signature []byte -} - -type GraphNodeAddress struct { - NodeID int64 - Type int16 - Position int32 - Address string -} - -type GraphNodeExtraType struct { - NodeID int64 - Type int64 - Value []byte -} - -type GraphNodeFeature struct { - NodeID int64 - FeatureBit int32 -} - -type GraphPruneLog struct { - BlockHeight int64 - BlockHash []byte -} - -type GraphSourceNode struct { - NodeID int64 -} - -type GraphZombieChannel struct { - Scid []byte - Version int16 - NodeKey1 []byte - NodeKey2 []byte -} diff --git a/graph/db/migration1/test_kvdb.go b/graph/db/migration1/test_kvdb.go deleted file mode 100644 index 4d2f0dbb7..000000000 --- a/graph/db/migration1/test_kvdb.go +++ /dev/null @@ -1,23 +0,0 @@ -//go:build !test_db_sqlite && !test_db_postgres - -package migration1 - -import ( - "testing" - - "github.com/lightningnetwork/lnd/kvdb" - "github.com/stretchr/testify/require" -) - -// NewTestDB is a helper function that creates an BBolt database for testing. -func NewTestDB(t testing.TB) V1Store { - backend, backendCleanup, err := kvdb.GetTestBackend(t.TempDir(), "cgr") - require.NoError(t, err) - - t.Cleanup(backendCleanup) - - graphStore, err := NewKVStore(backend) - require.NoError(t, err) - - return graphStore -} diff --git a/graph/db/migration1/test_postgres.go b/graph/db/migration1/test_postgres.go deleted file mode 100644 index a428af064..000000000 --- a/graph/db/migration1/test_postgres.go +++ /dev/null @@ -1,78 +0,0 @@ -//go:build test_db_postgres && !test_db_sqlite - -package migration1 - -import ( - "testing" - - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/lightningnetwork/lnd/graph/db/migration1/sqlc" - "github.com/lightningnetwork/lnd/sqldb" - "github.com/stretchr/testify/require" -) - -// NewTestDB is a helper function that creates a SQLStore backed by a SQL -// database for testing. -func NewTestDB(t testing.TB) V1Store { - return NewTestDBWithFixture(t, nil) -} - -// NewTestDBFixture creates a new sqldb.TestPgFixture for testing purposes. -func NewTestDBFixture(t *testing.T) *sqldb.TestPgFixture { - pgFixture := sqldb.NewTestPgFixture( - t, sqldb.DefaultPostgresFixtureLifetime, - ) - t.Cleanup(func() { - pgFixture.TearDown(t) - }) - return pgFixture -} - -// NewTestDBWithFixture is a helper function that creates a SQLStore backed by a -// SQL database for testing. -func NewTestDBWithFixture(t testing.TB, - pgFixture *sqldb.TestPgFixture) V1Store { - - var querier BatchedSQLQueries - if pgFixture == nil { - querier = newBatchQuerier(t) - } else { - querier = newBatchQuerierWithFixture(t, pgFixture) - } - - store, err := NewSQLStore( - &SQLStoreConfig{ - ChainHash: *chaincfg.MainNetParams.GenesisHash, - QueryCfg: sqldb.DefaultPostgresConfig(), - }, querier, - ) - require.NoError(t, err) - - return store -} - -// newBatchQuerier creates a new BatchedSQLQueries instance for testing -// using a PostgreSQL database fixture. -func newBatchQuerier(t testing.TB) BatchedSQLQueries { - pgFixture := sqldb.NewTestPgFixture( - t, sqldb.DefaultPostgresFixtureLifetime, - ) - t.Cleanup(func() { - pgFixture.TearDown(t) - }) - - return newBatchQuerierWithFixture(t, pgFixture) -} - -// newBatchQuerierWithFixture creates a new BatchedSQLQueries instance for -// testing using a PostgreSQL database fixture. -func newBatchQuerierWithFixture(t testing.TB, - pgFixture *sqldb.TestPgFixture) BatchedSQLQueries { - - rawDB := sqldb.NewTestPostgresDB(t, pgFixture).BaseDB.DB - - return &testBatchedSQLQueries{ - db: rawDB, - Queries: sqlc.New(rawDB), - } -} diff --git a/graph/db/migration1/test_sql.go b/graph/db/migration1/test_sql.go deleted file mode 100644 index 3d157dc9f..000000000 --- a/graph/db/migration1/test_sql.go +++ /dev/null @@ -1,45 +0,0 @@ -//go:build test_db_postgres || test_db_sqlite - -package migration1 - -import ( - "context" - "database/sql" - - "github.com/lightningnetwork/lnd/graph/db/migration1/sqlc" - "github.com/lightningnetwork/lnd/sqldb" -) - -// testBatchedSQLQueries is a simple implementation of BatchedSQLQueries for -// testing. -type testBatchedSQLQueries struct { - db *sql.DB - *sqlc.Queries -} - -// ExecTx implements the transaction execution logic. -func (t *testBatchedSQLQueries) ExecTx(ctx context.Context, - txOpts sqldb.TxOptions, txBody func(SQLQueries) error, - reset func()) error { - - sqlOptions := sql.TxOptions{ - Isolation: sql.LevelSerializable, - ReadOnly: txOpts.ReadOnly(), - } - - tx, err := t.db.BeginTx(ctx, &sqlOptions) - if err != nil { - return err - } - - reset() - queries := sqlc.New(tx) - - if err := txBody(queries); err != nil { - _ = tx.Rollback() - - return err - } - - return tx.Commit() -} diff --git a/graph/db/migration1/test_sqlite.go b/graph/db/migration1/test_sqlite.go deleted file mode 100644 index ed6402ffd..000000000 --- a/graph/db/migration1/test_sqlite.go +++ /dev/null @@ -1,55 +0,0 @@ -//go:build !test_db_postgres && test_db_sqlite - -package migration1 - -import ( - "testing" - - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/lightningnetwork/lnd/graph/db/migration1/sqlc" - "github.com/lightningnetwork/lnd/sqldb" - "github.com/stretchr/testify/require" -) - -// NewTestDB is a helper function that creates a SQLStore backed by a SQL -// database for testing. -func NewTestDB(t testing.TB) V1Store { - return NewTestDBWithFixture(t, nil) -} - -// NewTestDBFixture is a no-op for the sqlite build. -func NewTestDBFixture(_ *testing.T) *sqldb.TestPgFixture { - return nil -} - -// NewTestDBWithFixture is a helper function that creates a SQLStore backed by a -// SQL database for testing. -func NewTestDBWithFixture(t testing.TB, _ *sqldb.TestPgFixture) V1Store { - store, err := NewSQLStore( - &SQLStoreConfig{ - ChainHash: *chaincfg.MainNetParams.GenesisHash, - QueryCfg: sqldb.DefaultSQLiteConfig(), - }, newBatchQuerier(t), - ) - require.NoError(t, err) - return store -} - -// newBatchQuerier creates a new BatchedSQLQueries instance for testing -// using a SQLite database. -func newBatchQuerier(t testing.TB) BatchedSQLQueries { - return newBatchQuerierWithFixture(t, nil) -} - -// newBatchQuerierWithFixture creates a new BatchedSQLQueries instance for -// testing using a SQLite database. -func newBatchQuerierWithFixture(t testing.TB, - _ *sqldb.TestPgFixture) BatchedSQLQueries { - - rawDB := sqldb.NewTestSqliteDB(t).BaseDB.DB - - return &testBatchedSQLQueries{ - db: rawDB, - Queries: sqlc.New(rawDB), - } -} diff --git a/graph/db/models/cached_edge_info.go b/graph/db/models/cached_edge_info.go index 3e9cfea07..eab12fbdd 100644 --- a/graph/db/models/cached_edge_info.go +++ b/graph/db/models/cached_edge_info.go @@ -1,6 +1,6 @@ package models -import "github.com/btcsuite/btcd/btcutil/v2" +import "github.com/btcsuite/btcd/btcutil" // CachedEdgeInfo is a struct that only caches the information of a // ChannelEdgeInfo that we actually use for pathfinding and therefore need to diff --git a/graph/db/models/cached_edge_policy.go b/graph/db/models/cached_edge_policy.go index 90c8d56c3..40b0d9212 100644 --- a/graph/db/models/cached_edge_policy.go +++ b/graph/db/models/cached_edge_policy.go @@ -20,15 +20,13 @@ type CachedEdgePolicy struct { // and the last 2 bytes are the output index for the channel. ChannelID uint64 - // HasMaxHTLC indicates whether the policy has a max HTLC value. - HasMaxHTLC bool + // MessageFlags is a bitfield which indicates the presence of optional + // fields (like max_htlc) in the policy. + MessageFlags lnwire.ChanUpdateMsgFlags - // IsNode1 indicates whether this policy was announced by the channel's - // node_1. - IsNode1 bool - - // IsDisabled indicates whether the policy disables forwarding. - IsDisabled bool + // ChannelFlags is a bitfield which signals the capabilities of the + // channel as well as the directed edge this update applies to. + ChannelFlags lnwire.ChanUpdateChanFlags // TimeLockDelta is the number of blocks this node will subtract from // the expiry of an incoming HTLC. This value expresses the time buffer @@ -77,31 +75,24 @@ func (c *CachedEdgePolicy) ComputeFee( return c.FeeBaseMSat + (amt*c.FeeProportionalMillionths)/feeRateParts } +// IsDisabled returns true if the channel is disabled in the direction from the +// advertising node. +func (c *CachedEdgePolicy) IsDisabled() bool { + return c.ChannelFlags&lnwire.ChanUpdateDisabled != 0 +} + +// IsNode1 returns true if this policy was announced by the channel's node_1 +// node. +func (c *CachedEdgePolicy) IsNode1() bool { + return c.ChannelFlags&lnwire.ChanUpdateDirection == 0 +} + // NewCachedPolicy turns a full policy into a minimal one that can be cached. func NewCachedPolicy(policy *ChannelEdgePolicy) *CachedEdgePolicy { - if policy.Version != lnwire.GossipVersion2 { - return &CachedEdgePolicy{ - ChannelID: policy.ChannelID, - HasMaxHTLC: policy.MessageFlags.HasMaxHtlc(), - IsDisabled: policy.ChannelFlags& - lnwire.ChanUpdateDisabled != 0, - IsNode1: policy.ChannelFlags& - lnwire.ChanUpdateDirection == 0, - TimeLockDelta: policy.TimeLockDelta, - MinHTLC: policy.MinHTLC, - MaxHTLC: policy.MaxHTLC, - FeeBaseMSat: policy.FeeBaseMSat, - FeeProportionalMillionths: policy. - FeeProportionalMillionths, - InboundFee: policy.InboundFee, - } - } - return &CachedEdgePolicy{ ChannelID: policy.ChannelID, - HasMaxHTLC: true, - IsNode1: !policy.SecondPeer, - IsDisabled: !policy.DisableFlags.IsEnabled(), + MessageFlags: policy.MessageFlags, + ChannelFlags: policy.ChannelFlags, TimeLockDelta: policy.TimeLockDelta, MinHTLC: policy.MinHTLC, MaxHTLC: policy.MaxHTLC, diff --git a/graph/db/models/channel_auth_proof.go b/graph/db/models/channel_auth_proof.go index e4acc2f8b..134139467 100644 --- a/graph/db/models/channel_auth_proof.go +++ b/graph/db/models/channel_auth_proof.go @@ -1,117 +1,131 @@ package models -import ( - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/lnwire" -) +import "github.com/btcsuite/btcd/btcec/v2/ecdsa" // ChannelAuthProof is the authentication proof (the signature portion) for a -// channel. -// -// For v1 channels: -// Using the four node and bitcoin signatures contained in the struct, and some +// channel. Using the four signatures contained in the struct, and some // auxiliary knowledge (the funding script, node identities, and outpoint) nodes // on the network are able to validate the authenticity and existence of a // channel. Each of these signatures signs the following digest: chanID || // nodeID1 || nodeID2 || bitcoinKey1|| bitcoinKey2 || 2-byte-feature-len || // features. -// -// For v2 channels: -// The single schnorr signature signs the tlv fields of the v2 channel -// announcement message which are in the signed range. type ChannelAuthProof struct { - // Version is the version of the channel announcement. - Version lnwire.GossipVersion + // nodeSig1 is a cached instance of the first node signature. + nodeSig1 *ecdsa.Signature // NodeSig1Bytes are the raw bytes of the first node signature encoded // in DER format. - // - // NOTE: v1 channel announcements only. - NodeSig1Bytes fn.Option[[]byte] + NodeSig1Bytes []byte + + // nodeSig2 is a cached instance of the second node signature. + nodeSig2 *ecdsa.Signature // NodeSig2Bytes are the raw bytes of the second node signature // encoded in DER format. - // - // NOTE: v1 channel announcements only. - NodeSig2Bytes fn.Option[[]byte] + NodeSig2Bytes []byte + + // bitcoinSig1 is a cached instance of the first bitcoin signature. + bitcoinSig1 *ecdsa.Signature // BitcoinSig1Bytes are the raw bytes of the first bitcoin signature // encoded in DER format. - // - // NOTE: v1 channel announcements only. - BitcoinSig1Bytes fn.Option[[]byte] + BitcoinSig1Bytes []byte + + // bitcoinSig2 is a cached instance of the second bitcoin signature. + bitcoinSig2 *ecdsa.Signature // BitcoinSig2Bytes are the raw bytes of the second bitcoin signature // encoded in DER format. - // - // NOTE: v1 channel announcements only. - BitcoinSig2Bytes fn.Option[[]byte] - - // Signature is the raw bytes of the single schnorr signature for v2 - // channel announcements. - // - // NOTE: v2 channel announcements only. - Signature fn.Option[[]byte] + BitcoinSig2Bytes []byte } -// IsEmpty check is the authentication proof is empty Proof is empty. +// Node1Sig is the signature using the identity key of the node that is first +// in a lexicographical ordering of the serialized public keys of the two nodes +// that created the channel. +// +// NOTE: By having this method to access an attribute, we ensure we only need +// to fully deserialize the signature if absolutely necessary. +func (c *ChannelAuthProof) Node1Sig() (*ecdsa.Signature, error) { + if c.nodeSig1 != nil { + return c.nodeSig1, nil + } + + sig, err := ecdsa.ParseSignature(c.NodeSig1Bytes) + if err != nil { + return nil, err + } + + c.nodeSig1 = sig + + return sig, nil +} + +// Node2Sig is the signature using the identity key of the node that is second +// in a lexicographical ordering of the serialized public keys of the two nodes +// that created the channel. +// +// NOTE: By having this method to access an attribute, we ensure we only need +// to fully deserialize the signature if absolutely necessary. +func (c *ChannelAuthProof) Node2Sig() (*ecdsa.Signature, error) { + if c.nodeSig2 != nil { + return c.nodeSig2, nil + } + + sig, err := ecdsa.ParseSignature(c.NodeSig2Bytes) + if err != nil { + return nil, err + } + + c.nodeSig2 = sig + + return sig, nil +} + +// BitcoinSig1 is the signature using the public key of the first node that was +// used in the channel's multi-sig output. +// +// NOTE: By having this method to access an attribute, we ensure we only need +// to fully deserialize the signature if absolutely necessary. +func (c *ChannelAuthProof) BitcoinSig1() (*ecdsa.Signature, error) { + if c.bitcoinSig1 != nil { + return c.bitcoinSig1, nil + } + + sig, err := ecdsa.ParseSignature(c.BitcoinSig1Bytes) + if err != nil { + return nil, err + } + + c.bitcoinSig1 = sig + + return sig, nil +} + +// BitcoinSig2 is the signature using the public key of the second node that +// was used in the channel's multi-sig output. +// +// NOTE: By having this method to access an attribute, we ensure we only need +// to fully deserialize the signature if absolutely necessary. +func (c *ChannelAuthProof) BitcoinSig2() (*ecdsa.Signature, error) { + if c.bitcoinSig2 != nil { + return c.bitcoinSig2, nil + } + + sig, err := ecdsa.ParseSignature(c.BitcoinSig2Bytes) + if err != nil { + return nil, err + } + + c.bitcoinSig2 = sig + + return sig, nil +} + +// IsEmpty check is the authentication proof is empty Proof is empty if at +// least one of the signatures are equal to nil. func (c *ChannelAuthProof) IsEmpty() bool { - // For v1 channel announcements, we either have all four signatures or - // none. - if c.Version == lnwire.GossipVersion1 { - return c.NodeSig1Bytes.IsNone() - } - - // For v2 channel announcements, we only have a single signature. - return c.Signature.IsNone() -} - -// NewV1ChannelAuthProof creates a new ChannelAuthProof for a v1 channel -// announcement. -func NewV1ChannelAuthProof(nodeSig1, nodeSig2, bitcoinSig1, - bitcoinSig2 []byte) *ChannelAuthProof { - - return &ChannelAuthProof{ - Version: lnwire.GossipVersion1, - NodeSig1Bytes: fn.Some(nodeSig1), - NodeSig2Bytes: fn.Some(nodeSig2), - BitcoinSig1Bytes: fn.Some(bitcoinSig1), - BitcoinSig2Bytes: fn.Some(bitcoinSig2), - } -} - -// NewV2ChannelAuthProof creates a new ChannelAuthProof for a v2 channel -// announcement. -func NewV2ChannelAuthProof(signature []byte) *ChannelAuthProof { - return &ChannelAuthProof{ - Version: lnwire.GossipVersion2, - Signature: fn.Some(signature), - } -} - -// NodeSig1 returns the first node signature bytes, or nil if not present. -func (c *ChannelAuthProof) NodeSig1() []byte { - return c.NodeSig1Bytes.UnwrapOr(nil) -} - -// NodeSig2 returns the second node signature bytes, or nil if not present. -func (c *ChannelAuthProof) NodeSig2() []byte { - return c.NodeSig2Bytes.UnwrapOr(nil) -} - -// BitcoinSig1 returns the first bitcoin signature bytes, or nil if not -// present. -func (c *ChannelAuthProof) BitcoinSig1() []byte { - return c.BitcoinSig1Bytes.UnwrapOr(nil) -} - -// BitcoinSig2 returns the second bitcoin signature bytes, or nil if not -// present. -func (c *ChannelAuthProof) BitcoinSig2() []byte { - return c.BitcoinSig2Bytes.UnwrapOr(nil) -} - -// Sig returns the v2 signature bytes, or nil if not present. -func (c *ChannelAuthProof) Sig() []byte { - return c.Signature.UnwrapOr(nil) + return len(c.NodeSig1Bytes) == 0 || + len(c.NodeSig2Bytes) == 0 || + len(c.BitcoinSig1Bytes) == 0 || + len(c.BitcoinSig2Bytes) == 0 } diff --git a/graph/db/models/channel_edge_info.go b/graph/db/models/channel_edge_info.go index d26658426..efceed419 100644 --- a/graph/db/models/channel_edge_info.go +++ b/graph/db/models/channel_edge_info.go @@ -5,14 +5,11 @@ import ( "fmt" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/routing/route" ) // ChannelEdgeInfo represents a fully authenticated channel along with all its @@ -22,9 +19,6 @@ import ( // policy of a channel are stored within a ChannelEdgePolicy for each direction // of the channel. type ChannelEdgeInfo struct { - // Version is the gossip version that this channel was advertised on. - Version lnwire.GossipVersion - // ChannelID is the unique channel ID for the channel. The first 3 // bytes are the block height, the next 3 the index within the block, // and the last 2 bytes are the output index for the channel. @@ -32,25 +26,24 @@ type ChannelEdgeInfo struct { // ChainHash is the hash that uniquely identifies the chain that this // channel was opened within. + // + // TODO(roasbeef): need to modify db keying for multi-chain + // * must add chain hash to prefix as well ChainHash chainhash.Hash // NodeKey1Bytes is the raw public key of the first node. - NodeKey1Bytes route.Vertex + NodeKey1Bytes [33]byte - // NodeKey2Bytes is the raw public key of the second node. - NodeKey2Bytes route.Vertex + // NodeKey2Bytes is the raw public key of the first node. + NodeKey2Bytes [33]byte // BitcoinKey1Bytes is the raw public key of the first node. - // - // NOTE: this must be set for v1 channels but is optional for v2 and - // beyond. - BitcoinKey1Bytes fn.Option[route.Vertex] + BitcoinKey1Bytes [33]byte + bitcoinKey1 *btcec.PublicKey - // BitcoinKey2Bytes is the raw public key of the second node. - // - // NOTE: this must be set for v1 channels but is optional for v2 and - // beyond. - BitcoinKey2Bytes fn.Option[route.Vertex] + // BitcoinKey2Bytes is the raw public key of the first node. + BitcoinKey2Bytes [33]byte + bitcoinKey2 *btcec.PublicKey // Features is the list of protocol features supported by this channel // edge. @@ -75,84 +68,6 @@ type ChannelEdgeInfo struct { // the edge object is loaded from the database. FundingScript fn.Option[[]byte] - // MerkleRootHash is an optional root hash of a Merkle tree that the - // funding output is committed to. This is then used to compute the - // final funding output script. - // - // NOTE: only used for version 2 channels and beyond. - MerkleRootHash fn.Option[chainhash.Hash] - - // ExtraOpaqueData is the set of data that was appended to this - // message, some of which we may not actually know how to iterate or - // parse. By holding onto this data, we ensure that we're able to - // properly validate the set of signatures that cover these new fields, - // and ensure we're able to make upgrades to the network in a forwards - // compatible manner. - // - // NOTE: only used for version 1 channels. - ExtraOpaqueData []byte - - // ExtraSignedFields is a map of extra fields that are covered by the - // node announcement's signature that we have not explicitly parsed. - // - // NOTE: This is only used for version 2 node announcements and beyond. - ExtraSignedFields map[uint64][]byte -} - -// EdgeModifier is a functional option that modifies a ChannelEdgeInfo. -type EdgeModifier func(*ChannelEdgeInfo) - -// WithChannelPoint sets the channel point (funding outpoint) on the edge. -func WithChannelPoint(cp wire.OutPoint) EdgeModifier { - return func(e *ChannelEdgeInfo) { - e.ChannelPoint = cp - } -} - -// WithFeatures sets the feature vector on the edge. -func WithFeatures(f *lnwire.RawFeatureVector) EdgeModifier { - return func(e *ChannelEdgeInfo) { - e.Features = lnwire.NewFeatureVector(f, lnwire.Features) - } -} - -// WithCapacity sets the capacity on the edge. -func WithCapacity(c btcutil.Amount) EdgeModifier { - return func(e *ChannelEdgeInfo) { - e.Capacity = c - } -} - -// WithChanProof sets the authentication proof on the edge. -func WithChanProof(proof *ChannelAuthProof) EdgeModifier { - return func(e *ChannelEdgeInfo) { - e.AuthProof = proof - } -} - -// WithFundingScript sets the funding script on the edge. -func WithFundingScript(script []byte) EdgeModifier { - return func(e *ChannelEdgeInfo) { - e.FundingScript = fn.Some(script) - } -} - -// WithMerkleRootHash sets the merkle root hash on the edge. -func WithMerkleRootHash(hash chainhash.Hash) EdgeModifier { - return func(e *ChannelEdgeInfo) { - e.MerkleRootHash = fn.Some(hash) - } -} - -// ChannelV1Fields contains the fields that are specific to v1 channel -// announcements. -type ChannelV1Fields struct { - // BitcoinKey1Bytes is the raw public key of the first node. - BitcoinKey1Bytes route.Vertex - - // BitcoinKey2Bytes is the raw public key of the second node. - BitcoinKey2Bytes route.Vertex - // ExtraOpaqueData is the set of data that was appended to this // message, some of which we may not actually know how to iterate or // parse. By holding onto this data, we ensure that we're able to @@ -162,98 +77,20 @@ type ChannelV1Fields struct { ExtraOpaqueData []byte } -// NewV1Channel creates a new ChannelEdgeInfo for a v1 channel announcement. -// It takes the required fields for all channels (chanID, chainHash, node keys) -// and v1-specific fields, along with optional modifiers for setting additional -// fields like capacity, channel point, features, and auth proof. -// -// The constructor validates that if an AuthProof is provided via modifiers, its -// version matches the channel version (v1). -func NewV1Channel(chanID uint64, chainHash chainhash.Hash, node1, - node2 route.Vertex, v1Fields *ChannelV1Fields, - opts ...EdgeModifier) (*ChannelEdgeInfo, error) { +// AddNodeKeys is a setter-like method that can be used to replace the set of +// keys for the target ChannelEdgeInfo. +func (c *ChannelEdgeInfo) AddNodeKeys(nodeKey1, nodeKey2, bitcoinKey1, + bitcoinKey2 *btcec.PublicKey) { - edge := &ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - NodeKey1Bytes: node1, - NodeKey2Bytes: node2, - BitcoinKey1Bytes: fn.Some(v1Fields.BitcoinKey1Bytes), - BitcoinKey2Bytes: fn.Some(v1Fields.BitcoinKey2Bytes), - ChannelID: chanID, - ChainHash: chainHash, - Features: lnwire.EmptyFeatureVector(), - ExtraOpaqueData: v1Fields.ExtraOpaqueData, - } + copy(c.NodeKey1Bytes[:], nodeKey1.SerializeCompressed()) - for _, opt := range opts { - opt(edge) - } + copy(c.NodeKey2Bytes[:], nodeKey2.SerializeCompressed()) - // Validate some fields after the options have been applied. - if edge.AuthProof != nil && edge.AuthProof.Version != edge.Version { - return nil, fmt.Errorf("channel auth proof version %d does "+ - "not match channel version %d", edge.AuthProof.Version, - edge.Version) - } + c.bitcoinKey1 = bitcoinKey1 + copy(c.BitcoinKey1Bytes[:], c.bitcoinKey1.SerializeCompressed()) - return edge, nil -} - -// ChannelV2Fields contains the fields that are specific to v2 channel -// announcements. -type ChannelV2Fields struct { - // BitcoinKey1Bytes is the raw public key of the first node. - BitcoinKey1Bytes fn.Option[route.Vertex] - - // BitcoinKey2Bytes is the raw public key of the second node. - BitcoinKey2Bytes fn.Option[route.Vertex] - - // FundingScript is the funding output's pkScript. This is required for - // v2 channels when the bitcoin keys are not provided. - FundingScript fn.Option[[]byte] - - // MerkleRootHash is an optional root hash of a Merkle tree that the - // funding output is committed to. - MerkleRootHash fn.Option[chainhash.Hash] - - // ExtraSignedFields is a map of extra fields that are covered by the - // node announcement's signature that we have not explicitly parsed. - // - // NOTE: This is only used for version 2 node announcements and beyond. - ExtraSignedFields map[uint64][]byte -} - -// NewV2Channel creates a new ChannelEdgeInfo for a v2 channel announcement. -func NewV2Channel(chanID uint64, chainHash chainhash.Hash, node1, - node2 route.Vertex, v2Fields *ChannelV2Fields, - opts ...EdgeModifier) (*ChannelEdgeInfo, error) { - - edge := &ChannelEdgeInfo{ - Version: lnwire.GossipVersion2, - NodeKey1Bytes: node1, - NodeKey2Bytes: node2, - BitcoinKey1Bytes: v2Fields.BitcoinKey1Bytes, - BitcoinKey2Bytes: v2Fields.BitcoinKey2Bytes, - FundingScript: v2Fields.FundingScript, - MerkleRootHash: v2Fields.MerkleRootHash, - ChannelID: chanID, - ChainHash: chainHash, - Features: lnwire.EmptyFeatureVector(), - ExtraSignedFields: v2Fields.ExtraSignedFields, - } - - for _, opt := range opts { - opt(edge) - } - - // Validate some fields after the options have been applied. - if edge.AuthProof != nil && edge.AuthProof.Version != edge.Version { - return nil, fmt.Errorf("channel auth proof version %d does "+ - "not match channel version %d", edge.AuthProof.Version, - edge.Version) - } - - return edge, nil + c.bitcoinKey2 = bitcoinKey2 + copy(c.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) } // NodeKey1 is the identity public key of the "first" node that was involved in @@ -272,9 +109,49 @@ func (c *ChannelEdgeInfo) NodeKey2() (*btcec.PublicKey, error) { return btcec.ParsePubKey(c.NodeKey2Bytes[:]) } +// BitcoinKey1 is the Bitcoin multi-sig key belonging to the first node, that +// was involved in the funding transaction that originally created the channel +// that this struct represents. +// +// NOTE: By having this method to access an attribute, we ensure we only need +// to fully deserialize the pubkey if absolutely necessary. +func (c *ChannelEdgeInfo) BitcoinKey1() (*btcec.PublicKey, error) { + if c.bitcoinKey1 != nil { + return c.bitcoinKey1, nil + } + + key, err := btcec.ParsePubKey(c.BitcoinKey1Bytes[:]) + if err != nil { + return nil, err + } + c.bitcoinKey1 = key + + return key, nil +} + +// BitcoinKey2 is the Bitcoin multi-sig key belonging to the second node, that +// was involved in the funding transaction that originally created the channel +// that this struct represents. +// +// NOTE: By having this method to access an attribute, we ensure we only need +// to fully deserialize the pubkey if absolutely necessary. +func (c *ChannelEdgeInfo) BitcoinKey2() (*btcec.PublicKey, error) { + if c.bitcoinKey2 != nil { + return c.bitcoinKey2, nil + } + + key, err := btcec.ParsePubKey(c.BitcoinKey2Bytes[:]) + if err != nil { + return nil, err + } + c.bitcoinKey2 = key + + return key, nil +} + // OtherNodeKeyBytes returns the node key bytes of the other end of the channel. func (c *ChannelEdgeInfo) OtherNodeKeyBytes(thisNodeKey []byte) ( - route.Vertex, error) { + [33]byte, error) { switch { case bytes.Equal(c.NodeKey1Bytes[:], thisNodeKey): @@ -282,199 +159,7 @@ func (c *ChannelEdgeInfo) OtherNodeKeyBytes(thisNodeKey []byte) ( case bytes.Equal(c.NodeKey2Bytes[:], thisNodeKey): return c.NodeKey1Bytes, nil default: - return route.Vertex{}, fmt.Errorf("node not participating in " + + return [33]byte{}, fmt.Errorf("node not participating in " + "this channel") } } - -// FundingPKScript returns the funding output's pkScript for the channel. -func (c *ChannelEdgeInfo) FundingPKScript() ([]byte, error) { - switch c.Version { - case lnwire.GossipVersion1: - btc1Key, err := c.BitcoinKey1Bytes.UnwrapOrErr( - fmt.Errorf("missing bitcoin key 1"), - ) - if err != nil { - return nil, err - } - btc2Key, err := c.BitcoinKey2Bytes.UnwrapOrErr( - fmt.Errorf("missing bitcoin key 2"), - ) - if err != nil { - return nil, err - } - - if c.Features != nil && c.Features.HasFeature( - lnwire.SimpleTaprootChannelsOptionalStaging, - ) { - - pubKey1, err := btcec.ParsePubKey(btc1Key[:]) - if err != nil { - return nil, err - } - pubKey2, err := btcec.ParsePubKey(btc2Key[:]) - if err != nil { - return nil, err - } - - fundingScript, _, err := input.GenTaprootFundingScript( - pubKey1, pubKey2, 0, c.MerkleRootHash, - ) - if err != nil { - return nil, fmt.Errorf( - "unable to make taproot pkscript: %w", - err, - ) - } - - return fundingScript, nil - } - - witnessScript, err := input.GenMultiSigScript( - btc1Key[:], btc2Key[:], - ) - if err != nil { - return nil, err - } - - return input.WitnessScriptHash(witnessScript) - - case lnwire.GossipVersion2: - var ( - pubKey1 *btcec.PublicKey - pubKey2 *btcec.PublicKey - err error - ) - c.BitcoinKey1Bytes.WhenSome(func(key route.Vertex) { - pubKey1, err = btcec.ParsePubKey(key[:]) - }) - if err != nil { - return nil, err - } - - c.BitcoinKey2Bytes.WhenSome(func(key route.Vertex) { - pubKey2, err = btcec.ParsePubKey(key[:]) - }) - if err != nil { - return nil, err - } - - // If both bitcoin keys are not present in the announcement, - // then we should previously have stored the funding script - // found on-chain. - if pubKey1 == nil || pubKey2 == nil { - return c.FundingScript.UnwrapOrErr(fmt.Errorf( - "expected a funding pk script since no " + - "bitcoin keys were provided", - )) - } - - // By default, the tweak is empty which results in a BIP86 - // output. If we have a merkle root, we'll use that as the - // tweak. - muSig2Opt := musig2.WithBIP86KeyTweak() - c.MerkleRootHash.WhenSome(func(hash chainhash.Hash) { - muSig2Opt = musig2.WithTaprootKeyTweak(hash[:]) - }) - - // Compute the output key. - combinedKey, _, _, err := musig2.AggregateKeys( - []*btcec.PublicKey{pubKey1, pubKey2}, true, muSig2Opt, - ) - if err != nil { - return nil, err - } - - // Now that we have the combined key, we can create a taproot - // pkScript from this, and then make the txout given the amount. - fundingScript, err := input.PayToTaprootScript( - combinedKey.FinalKey, - ) - if err != nil { - return nil, fmt.Errorf("unable to make taproot "+ - "pkscript: %w", err) - } - - return fundingScript, nil - - default: - return nil, fmt.Errorf("unsupported channel version: %d", - c.Version) - } -} - -// ToChannelAnnouncement converts the ChannelEdgeInfo to a -// lnwire.ChannelAnnouncement1 message. Returns an error if AuthProof is nil -// or if the version is not v1. -func (c *ChannelEdgeInfo) ToChannelAnnouncement() ( - *lnwire.ChannelAnnouncement1, error) { - - // We currently only support v1 channel announcements. - if c.Version != lnwire.GossipVersion1 { - return nil, fmt.Errorf("unsupported channel version: %d", - c.Version) - } - - // If there's no auth proof, we can't create a full channel - // announcement. - if c.AuthProof == nil { - return nil, fmt.Errorf("cannot create channel announcement " + - "without auth proof") - } - - btc1, err := c.BitcoinKey1Bytes.UnwrapOrErr( - fmt.Errorf("bitcoin key 1 missing for v1 channel announcement"), - ) - if err != nil { - return nil, err - } - - btc2, err := c.BitcoinKey2Bytes.UnwrapOrErr( - fmt.Errorf("bitcoin key 2 missing for v1 channel announcement"), - ) - if err != nil { - return nil, err - } - - chanID := lnwire.NewShortChanIDFromInt(c.ChannelID) - chanAnn := &lnwire.ChannelAnnouncement1{ - ShortChannelID: chanID, - NodeID1: c.NodeKey1Bytes, - NodeID2: c.NodeKey2Bytes, - ChainHash: c.ChainHash, - BitcoinKey1: btc1, - BitcoinKey2: btc2, - Features: c.Features.RawFeatureVector, - ExtraOpaqueData: c.ExtraOpaqueData, - } - - chanAnn.NodeSig1, err = lnwire.NewSigFromECDSARawSignature( - c.AuthProof.NodeSig1(), - ) - if err != nil { - return nil, err - } - - chanAnn.NodeSig2, err = lnwire.NewSigFromECDSARawSignature( - c.AuthProof.NodeSig2(), - ) - if err != nil { - return nil, err - } - - chanAnn.BitcoinSig1, err = lnwire.NewSigFromECDSARawSignature( - c.AuthProof.BitcoinSig1(), - ) - if err != nil { - return nil, err - } - - chanAnn.BitcoinSig2, err = lnwire.NewSigFromECDSARawSignature( - c.AuthProof.BitcoinSig2(), - ) - if err != nil { - return nil, err - } - - return chanAnn, nil -} diff --git a/graph/db/models/channel_edge_info_test.go b/graph/db/models/channel_edge_info_test.go deleted file mode 100644 index 1ed191571..000000000 --- a/graph/db/models/channel_edge_info_test.go +++ /dev/null @@ -1,191 +0,0 @@ -package models - -import ( - "testing" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/input" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/routing/route" - "github.com/stretchr/testify/require" -) - -// TestFundingPKScriptV2 tests the FundingPKScript method for v2 channels -// which uses MuSig2 key aggregation. -func TestFundingPKScriptV2(t *testing.T) { - t.Parallel() - - // Generate two test keys for bitcoin keys. - privKey1, err := btcec.NewPrivateKey() - require.NoError(t, err) - pubKey1 := privKey1.PubKey() - - privKey2, err := btcec.NewPrivateKey() - require.NoError(t, err) - pubKey2 := privKey2.PubKey() - - // Convert to route.Vertex format. - var btcKey1, btcKey2 route.Vertex - copy(btcKey1[:], pubKey1.SerializeCompressed()) - copy(btcKey2[:], pubKey2.SerializeCompressed()) - - // Create a test merkle root. - var merkleRoot chainhash.Hash - copy(merkleRoot[:], []byte("test-merkle-root-hash-32-bytes!")) - - t.Run("v2 with btc keys, no merkle root (BIP86)", func(t *testing.T) { - t.Parallel() - - edge := &ChannelEdgeInfo{ - Version: lnwire.GossipVersion2, - BitcoinKey1Bytes: fn.Some(btcKey1), - BitcoinKey2Bytes: fn.Some(btcKey2), - } - - pkScript, err := edge.FundingPKScript() - require.NoError(t, err) - require.NotEmpty(t, pkScript) - - // Verify it's a valid taproot script (OP_1 <32-byte-key>). - require.Len(t, pkScript, 34) - require.Equal(t, byte(0x51), pkScript[0]) // OP_1 - - // Manually compute expected script using BIP86 tweak. - combinedKey, _, _, err := musig2.AggregateKeys( - []*btcec.PublicKey{pubKey1, pubKey2}, true, - musig2.WithBIP86KeyTweak(), - ) - require.NoError(t, err) - - expectedScript, err := input.PayToTaprootScript( - combinedKey.FinalKey, - ) - require.NoError(t, err) - require.Equal(t, expectedScript, pkScript) - }) - - t.Run("v2 with bitcoin keys and merkle root", func(t *testing.T) { - t.Parallel() - - edge := &ChannelEdgeInfo{ - Version: lnwire.GossipVersion2, - BitcoinKey1Bytes: fn.Some(btcKey1), - BitcoinKey2Bytes: fn.Some(btcKey2), - MerkleRootHash: fn.Some(merkleRoot), - } - - pkScript, err := edge.FundingPKScript() - require.NoError(t, err) - require.NotEmpty(t, pkScript) - - // Verify it's a valid taproot script. - require.Len(t, pkScript, 34) - require.Equal(t, byte(0x51), pkScript[0]) // OP_1 - - // Manually compute expected script with taproot tweak. - combinedKey, _, _, err := musig2.AggregateKeys( - []*btcec.PublicKey{pubKey1, pubKey2}, true, - musig2.WithTaprootKeyTweak(merkleRoot[:]), - ) - require.NoError(t, err) - - expectedScript, err := input.PayToTaprootScript( - combinedKey.FinalKey, - ) - require.NoError(t, err) - require.Equal(t, expectedScript, pkScript) - }) - - t.Run("v2 no btc keys returns stored script", func(t *testing.T) { - t.Parallel() - - storedScript := []byte{0x51, 0x20} // OP_1 + push 32 - storedScript = append(storedScript, make([]byte, 32)...) - - edge := &ChannelEdgeInfo{ - Version: lnwire.GossipVersion2, - FundingScript: fn.Some(storedScript), - } - - pkScript, err := edge.FundingPKScript() - require.NoError(t, err) - require.Equal(t, storedScript, pkScript) - }) - - t.Run("v2 no btc keys and no stored script errors", func(t *testing.T) { - t.Parallel() - - edge := &ChannelEdgeInfo{ - Version: lnwire.GossipVersion2, - } - - _, err := edge.FundingPKScript() - require.Error(t, err) - require.Contains(t, err.Error(), "expected a funding pk script") - }) - - t.Run("v2 one btc key returns stored script", func(t *testing.T) { - t.Parallel() - - storedScript := []byte{0x51, 0x20} - storedScript = append(storedScript, make([]byte, 32)...) - - // Only key1 set, key2 missing. - edge := &ChannelEdgeInfo{ - Version: lnwire.GossipVersion2, - BitcoinKey1Bytes: fn.Some(btcKey1), - FundingScript: fn.Some(storedScript), - } - - pkScript, err := edge.FundingPKScript() - require.NoError(t, err) - require.Equal(t, storedScript, pkScript) - }) -} - -// TestFundingPKScriptV1TaprootFeatureBit tests that a v1 channel edge carrying -// the taproot staging bit reconstructs a taproot funding script. -func TestFundingPKScriptV1TaprootFeatureBit(t *testing.T) { - t.Parallel() - - privKey1, err := btcec.NewPrivateKey() - require.NoError(t, err) - pubKey1 := privKey1.PubKey() - - privKey2, err := btcec.NewPrivateKey() - require.NoError(t, err) - pubKey2 := privKey2.PubKey() - - var btcKey1, btcKey2 route.Vertex - copy(btcKey1[:], pubKey1.SerializeCompressed()) - copy(btcKey2[:], pubKey2.SerializeCompressed()) - - // Build a v1 edge that only has the legacy bitcoin keys populated, but - // does advertise the taproot staging bit in its feature vector. - edge := &ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - BitcoinKey1Bytes: fn.Some(btcKey1), - BitcoinKey2Bytes: fn.Some(btcKey2), - Features: lnwire.NewFeatureVector( - lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsRequiredStaging, - ), - lnwire.Features, - ), - } - - pkScript, err := edge.FundingPKScript() - require.NoError(t, err) - - // The fix should make FundingPKScript honor the feature bit and derive - // the taproot funding script directly from the stored bitcoin keys. - expectedScript, _, err := input.GenTaprootFundingScript( - pubKey1, pubKey2, 0, fn.None[chainhash.Hash](), - ) - require.NoError(t, err) - - require.Equal(t, expectedScript, pkScript) -} diff --git a/graph/db/models/channel_edge_policy.go b/graph/db/models/channel_edge_policy.go index 067c7861a..48d748ee0 100644 --- a/graph/db/models/channel_edge_policy.go +++ b/graph/db/models/channel_edge_policy.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwire" ) @@ -14,15 +15,15 @@ import ( // information concerning fees, and minimum time-lock information which is // utilized during path finding. type ChannelEdgePolicy struct { - // Version is the gossip version of the channel update that produced - // this policy. - Version lnwire.GossipVersion - // SigBytes is the raw bytes of the signature of the channel edge // policy. We'll only parse these if the caller needs to access the - // signature for validation purposes. + // signature for validation purposes. Do not set SigBytes directly, but + // use SetSigBytes instead to make sure that the cache is invalidated. SigBytes []byte + // sig is a cached fully parsed signature. + sig *ecdsa.Signature + // ChannelID is the unique channel ID for the channel. The first 3 // bytes are the block height, the next 3 the index within the block, // and the last 2 bytes are the output index for the channel. @@ -32,14 +33,6 @@ type ChannelEdgePolicy struct { // was received. LastUpdate time.Time - // LastBlockHeight is the block height that timestamps the last update - // for v2 channel updates. - LastBlockHeight uint32 - - // SecondPeer indicates whether this policy was announced by the second - // peer in the channel for v2 channel updates. - SecondPeer bool - // MessageFlags is a bitfield which indicates the presence of optional // fields (like max_htlc) in the policy. MessageFlags lnwire.ChanUpdateMsgFlags @@ -48,10 +41,6 @@ type ChannelEdgePolicy struct { // channel as well as the directed edge this update applies to. ChannelFlags lnwire.ChanUpdateChanFlags - // DisableFlags is a v2-specific bitfield which signals whether the - // channel is disabled for incoming or outgoing traffic. - DisableFlags lnwire.ChanUpdateDisableFlags - // TimeLockDelta is the number of blocks this node will subtract from // the expiry of an incoming HTLC. This value expresses the time buffer // the node would like to HTLC exchanges. @@ -93,81 +82,38 @@ type ChannelEdgePolicy struct { // and ensure we're able to make upgrades to the network in a forwards // compatible manner. ExtraOpaqueData lnwire.ExtraOpaqueData - - // ExtraSignedFields are the extra signed fields found in v2 channel - // updates. - ExtraSignedFields map[uint64][]byte } -// ChanEdgePolicyFromWire constructs a ChannelEdgePolicy from a channel update -// message. -func ChanEdgePolicyFromWire(scid uint64, - update lnwire.ChannelUpdate) (*ChannelEdgePolicy, error) { - - switch upd := update.(type) { - case *lnwire.ChannelUpdate1: - //nolint:ll - return &ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, - SigBytes: upd.Signature.ToSignatureBytes(), - ChannelID: scid, - LastUpdate: time.Unix(int64(upd.Timestamp), 0), - MessageFlags: upd.MessageFlags, - ChannelFlags: upd.ChannelFlags, - TimeLockDelta: upd.TimeLockDelta, - MinHTLC: upd.HtlcMinimumMsat, - MaxHTLC: upd.HtlcMaximumMsat, - FeeBaseMSat: lnwire.MilliSatoshi(upd.BaseFee), - FeeProportionalMillionths: lnwire.MilliSatoshi(upd.FeeRate), - InboundFee: upd.InboundFee.ValOpt(), - ExtraOpaqueData: upd.ExtraOpaqueData, - }, nil - - case *lnwire.ChannelUpdate2: - return &ChannelEdgePolicy{ - Version: lnwire.GossipVersion2, - SigBytes: upd.Signature.Val.ToSignatureBytes(), - ChannelID: scid, - LastBlockHeight: upd.BlockHeight.Val, - SecondPeer: upd.SecondPeer.IsSome(), - DisableFlags: upd.DisabledFlags.Val, - TimeLockDelta: upd.CLTVExpiryDelta.Val, - MinHTLC: upd.HTLCMinimumMsat.Val, - MaxHTLC: upd.HTLCMaximumMsat.Val, - FeeBaseMSat: lnwire.MilliSatoshi( - upd.FeeBaseMsat.Val, - ), - FeeProportionalMillionths: lnwire.MilliSatoshi( - upd.FeeProportionalMillionths.Val, - ), - InboundFee: upd.InboundFee.ValOpt(), - ExtraSignedFields: upd.ExtraSignedFields, - }, nil +// Signature is a channel announcement signature, which is needed for proper +// edge policy announcement. +// +// NOTE: By having this method to access an attribute, we ensure we only need +// to fully deserialize the signature if absolutely necessary. +func (c *ChannelEdgePolicy) Signature() (*ecdsa.Signature, error) { + if c.sig != nil { + return c.sig, nil } - return nil, fmt.Errorf("unknown channel update version: %v", - update.MsgType()) + sig, err := ecdsa.ParseSignature(c.SigBytes) + if err != nil { + return nil, err + } + + c.sig = sig + + return sig, nil } -// IsNode1 returns true if this policy was announced by the channel's node_1. -func (c *ChannelEdgePolicy) IsNode1() bool { - if c.Version == lnwire.GossipVersion1 { - return c.ChannelFlags&lnwire.ChanUpdateDirection == 0 - } - - return !c.SecondPeer +// SetSigBytes updates the signature and invalidates the cached parsed +// signature. +func (c *ChannelEdgePolicy) SetSigBytes(sig []byte) { + c.SigBytes = sig + c.sig = nil } // IsDisabled determines whether the edge has the disabled bit set. -// -// NOTE: for v2 channel updates, we return true here only if both the incoming -// and outgoing disabled bits are set. func (c *ChannelEdgePolicy) IsDisabled() bool { - if c.Version == lnwire.GossipVersion1 { - return c.ChannelFlags.IsDisabled() - } - - return !c.DisableFlags.IsEnabled() + return c.ChannelFlags.IsDisabled() } // ComputeFee computes the fee to forward an HTLC of `amt` milli-satoshis over @@ -181,13 +127,7 @@ func (c *ChannelEdgePolicy) ComputeFee( // String returns a human-readable version of the channel edge policy. func (c *ChannelEdgePolicy) String() string { - if c.Version == lnwire.GossipVersion1 { - return fmt.Sprintf("ChannelID=%v, MessageFlags=%v, "+ - "ChannelFlags=%v, LastUpdate=%v", c.ChannelID, - c.MessageFlags, c.ChannelFlags, c.LastUpdate) - } - - return fmt.Sprintf("ChannelID=%v, Node1=%v, DisableFlags=%v, "+ - "BlockHeight=%v", c.ChannelID, !c.SecondPeer, - c.DisableFlags, c.LastBlockHeight) + return fmt.Sprintf("ChannelID=%v, MessageFlags=%v, ChannelFlags=%v, "+ + "LastUpdate=%v", c.ChannelID, c.MessageFlags, c.ChannelFlags, + c.LastUpdate) } diff --git a/graph/db/models/node.go b/graph/db/models/node.go index 582aeea4b..46e127044 100644 --- a/graph/db/models/node.go +++ b/graph/db/models/node.go @@ -7,9 +7,8 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/fn/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/routing/route" ) // Node represents an individual vertex/node within the channel graph. @@ -17,30 +16,27 @@ import ( // from it. As the graph is directed, a node will also have an incoming edge // attached to it for each outgoing edge. type Node struct { - // Version is the gossip version that this node was advertised on. - Version lnwire.GossipVersion - // PubKeyBytes is the raw bytes of the public key of the target node. PubKeyBytes [33]byte + // HaveNodeAnnouncement indicates whether we received a node + // announcement for this particular node. If true, the remaining fields + // will be set, if false only the PubKey is known for this node. + HaveNodeAnnouncement bool + // LastUpdate is the last time the vertex information for this node has // been updated. LastUpdate time.Time - // LastBlockHeight is the block height that timestamps the last update - // we received for this node. This is only used if this is a V2 node - // announcement. - LastBlockHeight uint32 - // Address is the TCP address this node is reachable over. Addresses []net.Addr // Color is the selected color for the node. - Color fn.Option[color.RGBA] + Color color.RGBA // Alias is a nick-name for the node. The alias can be used to confirm // a node's identity or to serve as a short ID for an address book. - Alias fn.Option[string] + Alias string // AuthSigBytes is the raw signature under the advertised public key // which serves to authenticate the attributes announced by this node. @@ -49,44 +45,6 @@ type Node struct { // Features is the list of protocol features supported by this node. Features *lnwire.FeatureVector - // ExtraOpaqueData is the set of data that was appended to this - // message, some of which we may not actually know how to iterate or - // parse. By holding onto this data, we ensure that we're able to - // properly validate the set of signatures that cover these new fields, - // and ensure we're able to make upgrades to the network in a forwards - // compatible manner. This is only used for V1 node announcements. - ExtraOpaqueData []byte - - // ExtraSignedFields is a map of extra fields that are covered by the - // node announcement's signature that we have not explicitly parsed. - // This is only used for version 2 node announcements and beyond. - ExtraSignedFields map[uint64][]byte -} - -// NodeV1Fields houses the fields that are specific to a version 1 node -// announcement. -type NodeV1Fields struct { - // Address is the TCP address this node is reachable over. - Addresses []net.Addr - - // AuthSigBytes is the raw signature under the advertised public key - // which serves to authenticate the attributes announced by this node. - AuthSigBytes []byte - - // Features is the list of protocol features supported by this node. - Features *lnwire.RawFeatureVector - - // Color is the selected color for the node. - Color color.RGBA - - // Alias is a nick-name for the node. The alias can be used to confirm - // a node's identity or to serve as a short ID for an address book. - Alias string - - // LastUpdate is the last time the vertex information for this node has - // been updated. - LastUpdate time.Time - // ExtraOpaqueData is the set of data that was appended to this // message, some of which we may not actually know how to iterate or // parse. By holding onto this data, we ensure that we're able to @@ -94,93 +52,11 @@ type NodeV1Fields struct { // and ensure we're able to make upgrades to the network in a forwards // compatible manner. ExtraOpaqueData []byte -} -// NewV1Node creates a new version 1 node from the passed fields. -func NewV1Node(pub route.Vertex, n *NodeV1Fields) *Node { - return &Node{ - Version: lnwire.GossipVersion1, - PubKeyBytes: pub, - Addresses: n.Addresses, - AuthSigBytes: n.AuthSigBytes, - Features: lnwire.NewFeatureVector( - n.Features, lnwire.Features, - ), - Color: fn.Some(n.Color), - Alias: fn.Some(n.Alias), - LastUpdate: n.LastUpdate, - ExtraOpaqueData: n.ExtraOpaqueData, - } -} + // TODO(roasbeef): discovery will need storage to keep it's last IP + // address and re-announce if interface changes? -// NodeV2Fields houses the fields that are specific to a version 2 node -// announcement. -type NodeV2Fields struct { - // LastBlockHeight is the block height that timestamps the last update - // we received for this node. - LastBlockHeight uint32 - - // Address is the TCP address this node is reachable over. - Addresses []net.Addr - - // Color is the selected color for the node. - Color fn.Option[color.RGBA] - - // Alias is a nick-name for the node. The alias can be used to confirm - // a node's identity or to serve as a short ID for an address book. - Alias fn.Option[string] - - // Signature is the schnorr signature under the advertised public key - // which serves to authenticate the attributes announced by this node. - Signature []byte - - // Features is the list of protocol features supported by this node. - Features *lnwire.RawFeatureVector - - // ExtraSignedFields is a map of extra fields that are covered by the - // node announcement's signature that we have not explicitly parsed. - ExtraSignedFields map[uint64][]byte -} - -// NewV2Node creates a new version 2 node from the passed fields. -func NewV2Node(pub route.Vertex, n *NodeV2Fields) *Node { - return &Node{ - Version: lnwire.GossipVersion2, - PubKeyBytes: pub, - Addresses: n.Addresses, - AuthSigBytes: n.Signature, - Features: lnwire.NewFeatureVector( - n.Features, lnwire.Features, - ), - LastBlockHeight: n.LastBlockHeight, - Color: n.Color, - Alias: n.Alias, - LastUpdate: time.Unix(0, 0), - ExtraSignedFields: n.ExtraSignedFields, - } -} - -// NewV1ShellNode creates a new shell version 1 node. -func NewV1ShellNode(pubKey route.Vertex) *Node { - return NewShellNode(lnwire.GossipVersion1, pubKey) -} - -// NewShellNode creates a new shell node with the given gossip version and -// public key. -func NewShellNode(v lnwire.GossipVersion, pubKey route.Vertex) *Node { - return &Node{ - Version: v, - PubKeyBytes: pubKey, - Features: lnwire.EmptyFeatureVector(), - LastUpdate: time.Unix(0, 0), - } -} - -// HaveAnnouncement returns true if we have received a node announcement for -// this node. We determine this by checking if we have a signature for the -// announcement. -func (n *Node) HaveAnnouncement() bool { - return len(n.AuthSigBytes) > 0 + // TODO(roasbeef): add update method and fetch? } // PubKey is the node's long-term identity public key. This key will be used to @@ -189,17 +65,30 @@ func (n *Node) PubKey() (*btcec.PublicKey, error) { return btcec.ParsePubKey(n.PubKeyBytes[:]) } +// AuthSig is a signature under the advertised public key which serves to +// authenticate the attributes announced by this node. +// +// NOTE: By having this method to access an attribute, we ensure we only need +// to fully deserialize the signature if absolutely necessary. +func (n *Node) AuthSig() (*ecdsa.Signature, error) { + return ecdsa.ParseSignature(n.AuthSigBytes) +} + +// AddPubKey is a setter-link method that can be used to swap out the public +// key for a node. +func (n *Node) AddPubKey(key *btcec.PublicKey) { + copy(n.PubKeyBytes[:], key.SerializeCompressed()) +} + // NodeAnnouncement retrieves the latest node announcement of the node. func (n *Node) NodeAnnouncement(signed bool) (*lnwire.NodeAnnouncement1, error) { - // Error out if we request the signed announcement, but we don't have - // a signature for this announcement. - if !n.HaveAnnouncement() && signed { + if !n.HaveNodeAnnouncement { return nil, fmt.Errorf("node does not have node announcement") } - alias, err := lnwire.NewNodeAlias(n.Alias.UnwrapOr("")) + alias, err := lnwire.NewNodeAlias(n.Alias) if err != nil { return nil, err } @@ -207,7 +96,7 @@ func (n *Node) NodeAnnouncement(signed bool) (*lnwire.NodeAnnouncement1, nodeAnn := &lnwire.NodeAnnouncement1{ Features: n.Features.RawFeatureVector, NodeID: n.PubKeyBytes, - RGBColor: n.Color.UnwrapOr(color.RGBA{}), + RGBColor: n.Color, Alias: alias, Addresses: n.Addresses, Timestamp: uint32(n.LastUpdate.Unix()), @@ -229,25 +118,20 @@ func (n *Node) NodeAnnouncement(signed bool) (*lnwire.NodeAnnouncement1, } // NodeFromWireAnnouncement creates a Node instance from an -// lnwire.NodeAnnouncement1 message. The address list from msg.Addresses -// is copied verbatim, including legacy entries such as tor v2 onion -// addresses that lnd no longer produces itself. This is required so -// that Node.NodeAnnouncement can later reconstruct the exact byte -// sequence the remote peer signed, allowing signature verification and -// re-broadcast to succeed across restarts. +// lnwire.NodeAnnouncement1 message. func NodeFromWireAnnouncement(msg *lnwire.NodeAnnouncement1) *Node { timestamp := time.Unix(int64(msg.Timestamp), 0) + features := lnwire.NewFeatureVector(msg.Features, lnwire.Features) - return NewV1Node( - msg.NodeID, - &NodeV1Fields{ - LastUpdate: timestamp, - Addresses: msg.Addresses, - Alias: msg.Alias.String(), - AuthSigBytes: msg.Signature.ToSignatureBytes(), - Features: msg.Features, - Color: msg.RGBColor, - ExtraOpaqueData: msg.ExtraOpaqueData, - }, - ) + return &Node{ + HaveNodeAnnouncement: true, + LastUpdate: timestamp, + Addresses: msg.Addresses, + PubKeyBytes: msg.NodeID, + Alias: msg.Alias.String(), + AuthSigBytes: msg.Signature.ToSignatureBytes(), + Features: features, + Color: msg.RGBColor, + ExtraOpaqueData: msg.ExtraOpaqueData, + } } diff --git a/graph/db/notifications.go b/graph/db/notifications.go index 405c26558..eecc38c27 100644 --- a/graph/db/notifications.go +++ b/graph/db/notifications.go @@ -1,7 +1,6 @@ package graphdb import ( - "context" "errors" "fmt" "image/color" @@ -11,8 +10,8 @@ import ( "sync/atomic" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lnutils" @@ -202,11 +201,11 @@ func (c *ChannelGraph) notifyTopologyChange(topologyDiff *TopologyChange) { // // NOTE: must be run inside goroutine and must only ever be called from within // handleTopologySubscriptions. -func (c *ChannelGraph) handleTopologyUpdate(ctx context.Context, update any) { +func (c *ChannelGraph) handleTopologyUpdate(update any) { defer c.wg.Done() topChange := &TopologyChange{} - err := c.addToTopologyChange(ctx, topChange, update) + err := c.addToTopologyChange(topChange, update) if err != nil { log.Errorf("unable to update topology change notification: %v", err) @@ -376,8 +375,8 @@ type ChannelEdgeUpdate struct { // constitutes. This function will also fetch any required auxiliary // information required to create the topology change update from the graph // database. -func (c *ChannelGraph) addToTopologyChange(ctx context.Context, - update *TopologyChange, msg any) error { +func (c *ChannelGraph) addToTopologyChange(update *TopologyChange, + msg any) error { switch m := msg.(type) { @@ -392,11 +391,9 @@ func (c *ChannelGraph) addToTopologyChange(ctx context.Context, nodeUpdate := &NetworkNodeUpdate{ Addresses: m.Addresses, IdentityKey: pubKey, - Alias: m.Alias.UnwrapOr(""), - Color: EncodeHexColor( - m.Color.UnwrapOr(color.RGBA{}), - ), - Features: m.Features.Clone(), + Alias: m.Alias, + Color: EncodeHexColor(m.Color), + Features: m.Features.Clone(), } update.NodeUpdates = append(update.NodeUpdates, nodeUpdate) @@ -413,9 +410,7 @@ func (c *ChannelGraph) addToTopologyChange(ctx context.Context, // We'll need to fetch the edge's information from the database // in order to get the information concerning which nodes are // being connected. - edgeInfo, _, _, err := c.FetchChannelEdgesByID( - ctx, m.ChannelID, - ) + edgeInfo, _, _, err := c.FetchChannelEdgesByID(m.ChannelID) if err != nil { return fmt.Errorf("unable fetch channel edge: %w", err) } diff --git a/graph/db/options.go b/graph/db/options.go index 5c876a1ec..15ea6f4ee 100644 --- a/graph/db/options.go +++ b/graph/db/options.go @@ -1,12 +1,6 @@ package graphdb -import ( - "fmt" - "time" - - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/lnwire" -) +import "time" const ( // DefaultRejectCacheSize is the default number of rejectCacheEntries to @@ -45,182 +39,6 @@ type iterConfig struct { iterPublicNodes bool } -// ChanUpdateRange describes a range for channel updates. Only one of the time -// or height ranges should be set depending on the gossip version. -type ChanUpdateRange struct { - // StartTime is the inclusive lower time bound (v1 gossip only). - StartTime fn.Option[time.Time] - - // EndTime is the exclusive upper time bound (v1 gossip only). - EndTime fn.Option[time.Time] - - // StartHeight is the inclusive lower block-height bound (v2 gossip - // only). - StartHeight fn.Option[uint32] - - // EndHeight is the exclusive upper block-height bound (v2 gossip - // only). - EndHeight fn.Option[uint32] -} - -// validateForVersion checks that the range fields are consistent with the -// given gossip version: v1 requires time bounds, v2 requires block-height -// bounds, and mixing the two is rejected. -func (r ChanUpdateRange) validateForVersion(v lnwire.GossipVersion) error { - var ( - hasStartTime = r.StartTime.IsSome() - hasEndTime = r.EndTime.IsSome() - hasTimeRange = hasStartTime || hasEndTime - - hasStartHeight = r.StartHeight.IsSome() - hasEndHeight = r.EndHeight.IsSome() - hasBlockRange = hasStartHeight || hasEndHeight - ) - - if hasTimeRange && hasBlockRange { - return fmt.Errorf("chan update range has both time and block " + - "ranges") - } - - switch v { - case gossipV1: - if hasBlockRange { - return fmt.Errorf("v1 chan update range must use time") - } - - if !hasTimeRange { - return fmt.Errorf("v1 chan update range missing time") - } - - if !hasStartTime || !hasEndTime { - return fmt.Errorf("v1 chan update range " + - "missing time bounds") - } - - start := r.StartTime.UnwrapOr(time.Time{}) - end := r.EndTime.UnwrapOr(time.Time{}) - - if start.After(end) { - return fmt.Errorf("v1 chan update range: " + - "start time after end time") - } - - case gossipV2: - if hasTimeRange { - return fmt.Errorf("v2 chan update range must use " + - "blocks") - } - - if !hasBlockRange { - return fmt.Errorf("v2 chan update range missing " + - "block range") - } - - if !hasStartHeight || !hasEndHeight { - return fmt.Errorf("v2 chan update range " + - "missing block bounds") - } - - start := r.StartHeight.UnwrapOr(0) - end := r.EndHeight.UnwrapOr(0) - if start > end { - return fmt.Errorf("v2 chan update range: " + - "start height after end height") - } - - default: - return fmt.Errorf("unknown gossip version: %v", v) - } - - return nil -} - -// NodeUpdateRange describes a range for node updates. Only one of the time or -// height ranges should be set depending on the gossip version. -type NodeUpdateRange struct { - // StartTime is the inclusive lower time bound (v1 gossip only). - StartTime fn.Option[time.Time] - - // EndTime is the exclusive upper time bound (v1 gossip only). - EndTime fn.Option[time.Time] - - // StartHeight is the inclusive lower block-height bound (v2 gossip - // only). - StartHeight fn.Option[uint32] - - // EndHeight is the exclusive upper block-height bound (v2 gossip - // only). - EndHeight fn.Option[uint32] -} - -// validateForVersion checks that the range fields are consistent with the -// given gossip version: v1 requires time bounds, v2 requires block-height -// bounds, and mixing the two is rejected. -func (r NodeUpdateRange) validateForVersion(v lnwire.GossipVersion) error { - var ( - hasStartTime = r.StartTime.IsSome() - hasEndTime = r.EndTime.IsSome() - - hasStartHeight = r.StartHeight.IsSome() - hasEndHeight = r.EndHeight.IsSome() - - hasTimeRange = hasStartTime || hasEndTime - hasBlockRange = hasStartHeight || hasEndHeight - ) - - if hasTimeRange && hasBlockRange { - return fmt.Errorf("node update range has both " + - "time and block ranges") - } - - switch v { - case gossipV1: - if hasBlockRange { - return fmt.Errorf("v1 node update range must use time") - } - - if !hasTimeRange { - return fmt.Errorf("v1 node update range missing time") - } - if !hasStartTime || !hasEndTime { - return fmt.Errorf("v1 node update range missing " + - "time bounds") - } - - start := r.StartTime.UnwrapOr(time.Time{}) - end := r.EndTime.UnwrapOr(time.Time{}) - if start.After(end) { - return fmt.Errorf("v1 node update range: start time " + - "after end time") - } - - case gossipV2: - if hasTimeRange { - return fmt.Errorf("v2 node update range must use " + - "height") - } - if !hasBlockRange { - return fmt.Errorf("v2 node update range missing height") - } - if !hasStartHeight || !hasEndHeight { - return fmt.Errorf("v2 node update range missing " + - "height bounds") - } - - start := r.StartHeight.UnwrapOr(0) - end := r.EndHeight.UnwrapOr(0) - if start > end { - return fmt.Errorf("v2 node update range: start " + - "height after end height") - } - - default: - return fmt.Errorf("unknown gossip version: %d", v) - } - - return nil -} - // defaultIteratorConfig returns the default configuration. func defaultIteratorConfig() *iterConfig { return &iterConfig{ @@ -267,21 +85,14 @@ type chanGraphOptions struct { // preAllocCacheNumNodes is the number of nodes we expect to be in the // graph cache, so we can pre-allocate the map accordingly. preAllocCacheNumNodes int - - // asyncGraphCachePopulation indicates whether the graph cache - // should be populated asynchronously or if the Start method should - // block until the cache is fully populated. This is true by - // default. - asyncGraphCachePopulation bool } // defaultChanGraphOptions returns a new chanGraphOptions instance populated // with default values. func defaultChanGraphOptions() *chanGraphOptions { return &chanGraphOptions{ - useGraphCache: true, - asyncGraphCachePopulation: true, - preAllocCacheNumNodes: DefaultPreAllocCacheNumNodes, + useGraphCache: true, + preAllocCacheNumNodes: DefaultPreAllocCacheNumNodes, } } @@ -304,25 +115,6 @@ func WithPreAllocCacheNumNodes(n int) ChanGraphOption { } } -// WithAsyncGraphCachePopulation sets whether the graph cache should be -// populated asynchronously or if the Start method should block until the -// cache is fully populated. -func WithAsyncGraphCachePopulation(async bool) ChanGraphOption { - return func(o *chanGraphOptions) { - o.asyncGraphCachePopulation = async - } -} - -// WithSyncGraphCachePopulation will cause the ChannelGraph to block -// until the graph cache is fully populated before returning from the Start -// method. This is useful for tests that need to ensure the graph cache is -// fully populated before proceeding with further operations. -func WithSyncGraphCachePopulation() ChanGraphOption { - return func(o *chanGraphOptions) { - o.asyncGraphCachePopulation = false - } -} - // StoreOptions holds parameters for tuning and customizing a graph DB. type StoreOptions struct { // RejectCacheSize is the maximum number of rejectCacheEntries to hold diff --git a/graph/db/reject_cache.go b/graph/db/reject_cache.go index f54463224..2a2721928 100644 --- a/graph/db/reject_cache.go +++ b/graph/db/reject_cache.go @@ -1,11 +1,5 @@ package graphdb -import ( - "time" - - "github.com/lightningnetwork/lnd/lnwire" -) - // rejectFlags is a compact representation of various metadata stored by the // reject cache about a particular channel. type rejectFlags uint8 @@ -47,63 +41,9 @@ func (f rejectFlags) unpack() (bool, bool) { // including the timestamps of its latest edge policies and whether or not the // channel exists in the graph. type rejectCacheEntry struct { - // upd{1,2}Time are Unix timestamps for v1 policies. upd1Time int64 upd2Time int64 - - // upd{1,2}BlockHeight are the last known block heights for v2 - // policies. - upd1BlockHeight uint32 - upd2BlockHeight uint32 - - flags rejectFlags -} - -func newRejectCacheEntryV1(upd1, upd2 time.Time, exists, - isZombie bool) rejectCacheEntry { - - return rejectCacheEntry{ - upd1Time: upd1.Unix(), - upd2Time: upd2.Unix(), - flags: packRejectFlags(exists, isZombie), - } -} - -func newRejectCacheEntryV2(upd1, upd2 uint32, exists, - isZombie bool) rejectCacheEntry { - - return rejectCacheEntry{ - upd1BlockHeight: upd1, - upd2BlockHeight: upd2, - flags: packRejectFlags(exists, isZombie), - } -} - -func updateRejectCacheEntryV1(entry *rejectCacheEntry, isUpdate1 bool, - lastUpdate time.Time) { - - if isUpdate1 { - entry.upd1Time = lastUpdate.Unix() - } else { - entry.upd2Time = lastUpdate.Unix() - } -} - -func updateRejectCacheEntryV2(entry *rejectCacheEntry, isUpdate1 bool, - blockHeight uint32) { - - if isUpdate1 { - entry.upd1BlockHeight = blockHeight - } else { - entry.upd2BlockHeight = blockHeight - } -} - -// rejectCacheKey uniquely identifies a channel entry in the reject cache by -// gossip version and channel ID. -type rejectCacheKey struct { - version lnwire.GossipVersion - chanID uint64 + flags rejectFlags } // rejectCache is an in-memory cache used to improve the performance of @@ -111,25 +51,20 @@ type rejectCacheKey struct { // well as the most recent timestamps for each policy (if they exists). type rejectCache struct { n int - edges map[rejectCacheKey]rejectCacheEntry + edges map[uint64]rejectCacheEntry } // newRejectCache creates a new rejectCache with maximum capacity of n entries. func newRejectCache(n int) *rejectCache { return &rejectCache{ n: n, - edges: make(map[rejectCacheKey]rejectCacheEntry, n), + edges: make(map[uint64]rejectCacheEntry, n), } } // get returns the entry from the cache for chanid, if it exists. -func (c *rejectCache) get(version lnwire.GossipVersion, chanid uint64) ( - rejectCacheEntry, bool) { - - entry, ok := c.edges[rejectCacheKey{ - version: version, - chanID: chanid, - }] +func (c *rejectCache) get(chanid uint64) (rejectCacheEntry, bool) { + entry, ok := c.edges[chanid] return entry, ok } @@ -137,17 +72,10 @@ func (c *rejectCache) get(version lnwire.GossipVersion, chanid uint64) ( // exists, it will be replaced with the new entry. If the entry doesn't exists, // it will be inserted to the cache, performing a random eviction if the cache // is at capacity. -func (c *rejectCache) insert(version lnwire.GossipVersion, chanid uint64, - entry rejectCacheEntry) { - - key := rejectCacheKey{ - version: version, - chanID: chanid, - } - +func (c *rejectCache) insert(chanid uint64, entry rejectCacheEntry) { // If entry exists, replace it. - if _, ok := c.edges[key]; ok { - c.edges[key] = entry + if _, ok := c.edges[chanid]; ok { + c.edges[chanid] = entry return } @@ -158,13 +86,10 @@ func (c *rejectCache) insert(version lnwire.GossipVersion, chanid uint64, break } } - c.edges[key] = entry + c.edges[chanid] = entry } // remove deletes an entry for chanid from the cache, if it exists. -func (c *rejectCache) remove(version lnwire.GossipVersion, chanid uint64) { - delete(c.edges, rejectCacheKey{ - version: version, - chanID: chanid, - }) +func (c *rejectCache) remove(chanid uint64) { + delete(c.edges, chanid) } diff --git a/graph/db/reject_cache_test.go b/graph/db/reject_cache_test.go index e97a37a61..f64c39c33 100644 --- a/graph/db/reject_cache_test.go +++ b/graph/db/reject_cache_test.go @@ -1,10 +1,8 @@ package graphdb import ( + "reflect" "testing" - - "github.com/lightningnetwork/lnd/lnwire" - "github.com/stretchr/testify/require" ) // TestRejectCache checks the behavior of the rejectCache with respect to insertion, @@ -17,12 +15,14 @@ func TestRejectCache(t *testing.T) { // As a sanity check, assert that querying the empty cache does not // return an entry. - _, ok := c.get(lnwire.GossipVersion1, 0) - require.False(t, ok) + _, ok := c.get(0) + if ok { + t.Fatalf("reject cache should be empty") + } // Now, fill up the cache entirely. for i := uint64(0); i < cacheSize; i++ { - c.insert(lnwire.GossipVersion1, i, entryForInt(i)) + c.insert(i, entryForInt(i)) } // Assert that the cache has all of the entries just inserted, since no @@ -30,10 +30,7 @@ func TestRejectCache(t *testing.T) { assertHasEntries(t, c, 0, cacheSize) // Now, insert a new element that causes the cache to evict an element. - c.insert( - lnwire.GossipVersion1, cacheSize, - entryForInt(cacheSize), - ) + c.insert(cacheSize, entryForInt(cacheSize)) // Assert that the cache has this last entry, as the cache should evict // some prior element and not the newly inserted one. @@ -43,7 +40,7 @@ func TestRejectCache(t *testing.T) { // elements. evicted := make(map[uint64]struct{}) for i := uint64(0); i < cacheSize+1; i++ { - _, ok := c.get(lnwire.GossipVersion1, i) + _, ok := c.get(i) if !ok { evicted[i] = struct{}{} } @@ -51,13 +48,15 @@ func TestRejectCache(t *testing.T) { // Assert that exactly one element has been evicted. numEvicted := len(evicted) - require.Equal(t, 1, numEvicted) + if numEvicted != 1 { + t.Fatalf("expected one evicted entry, got: %d", numEvicted) + } // Remove the highest item which initially caused the eviction and // reinsert the element that was evicted prior. - c.remove(lnwire.GossipVersion1, cacheSize) + c.remove(cacheSize) for i := range evicted { - c.insert(lnwire.GossipVersion1, i, entryForInt(i)) + c.insert(i, entryForInt(i)) } // Since the removal created an extra slot, the last insertion should @@ -70,7 +69,7 @@ func TestRejectCache(t *testing.T) { // happening on inserts for existing cache items, we expect this to fail // with high probability. for i := uint64(0); i < cacheSize; i++ { - c.insert(lnwire.GossipVersion1, i, entryForInt(i)) + c.insert(i, entryForInt(i)) } assertHasEntries(t, c, 0, cacheSize) @@ -83,11 +82,16 @@ func assertHasEntries(t *testing.T, c *rejectCache, start, end uint64) { t.Helper() for i := start; i < end; i++ { - entry, ok := c.get(lnwire.GossipVersion1, i) - require.True(t, ok) + entry, ok := c.get(i) + if !ok { + t.Fatalf("reject cache should contain chan %d", i) + } expEntry := entryForInt(i) - require.Equal(t, expEntry, entry) + if !reflect.DeepEqual(entry, expEntry) { + t.Fatalf("entry mismatch, want: %v, got: %v", + expEntry, entry) + } } } diff --git a/graph/db/migration1/sql_migration.go b/graph/db/sql_migration.go similarity index 98% rename from graph/db/migration1/sql_migration.go rename to graph/db/sql_migration.go index 7a68a2148..7cc4c414e 100644 --- a/graph/db/migration1/sql_migration.go +++ b/graph/db/sql_migration.go @@ -1,4 +1,4 @@ -package migration1 +package graphdb import ( "bytes" @@ -7,18 +7,17 @@ import ( "database/sql" "errors" "fmt" - "image/color" "net" "slices" "time" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/lightningnetwork/lnd/graph/db/migration1/models" - "github.com/lightningnetwork/lnd/graph/db/migration1/sqlc" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/sqldb" + "github.com/lightningnetwork/lnd/sqldb/sqlc" "golang.org/x/time/rate" ) @@ -424,7 +423,7 @@ func migrateSourceNode(ctx context.Context, kvdb kvdb.Backend, id, err := sqlDB.GetNodeIDByPubKey( ctx, sqlc.GetNodeIDByPubKeyParams{ PubKey: pub[:], - Version: int16(lnwire.GossipVersion1), + Version: int16(ProtocolV1), }, ) if err != nil { @@ -442,9 +441,7 @@ func migrateSourceNode(ctx context.Context, kvdb kvdb.Backend, // from the SQL database and checking that the expected DB ID and // pub key are returned. We don't need to do a whole node comparison // here, as this was already done in the previous migration step. - srcNodes, err := sqlDB.GetSourceNodesByVersion( - ctx, int16(lnwire.GossipVersion1), - ) + srcNodes, err := sqlDB.GetSourceNodesByVersion(ctx, int16(ProtocolV1)) if err != nil { return fmt.Errorf("could not get source nodes from SQL "+ "store: %w", err) @@ -1254,7 +1251,7 @@ func migrateZombieIndex(ctx context.Context, cfg *sqldb.QueryConfig, // Batch fetch all zombie channels from the database. rows, err := sqlDB.GetZombieChannelsSCIDs( ctx, sqlc.GetZombieChannelsSCIDsParams{ - Version: int16(lnwire.GossipVersion1), + Version: int16(ProtocolV1), Scids: scids, }, ) @@ -1330,7 +1327,7 @@ func migrateZombieIndex(ctx context.Context, cfg *sqldb.QueryConfig, err = sqlDB.UpsertZombieChannel( ctx, sqlc.UpsertZombieChannelParams{ - Version: int16(lnwire.GossipVersion1), + Version: int16(ProtocolV1), Scid: chanIDB, NodeKey1: pubKey1[:], NodeKey2: pubKey2[:], @@ -1446,16 +1443,14 @@ func insertNodeSQLMig(ctx context.Context, db SQLQueries, node *models.Node) (int64, error) { params := sqlc.InsertNodeMigParams{ - Version: int16(lnwire.GossipVersion1), + Version: int16(ProtocolV1), PubKey: node.PubKeyBytes[:], } - if node.HaveAnnouncement() { + if node.HaveNodeAnnouncement { params.LastUpdate = sqldb.SQLInt64(node.LastUpdate.Unix()) - params.Color = sqldb.SQLStrValid( - EncodeHexColor(node.Color.UnwrapOr(color.RGBA{})), - ) - params.Alias = sqldb.SQLStrValid(node.Alias.UnwrapOr("")) + params.Color = sqldb.SQLStrValid(EncodeHexColor(node.Color)) + params.Alias = sqldb.SQLStrValid(node.Alias) params.Signature = node.AuthSigBytes } @@ -1466,7 +1461,7 @@ func insertNodeSQLMig(ctx context.Context, db SQLQueries, } // We can exit here if we don't have the announcement yet. - if !node.HaveAnnouncement() { + if !node.HaveNodeAnnouncement { return nodeID, nil } @@ -1569,7 +1564,7 @@ func insertChannelMig(ctx context.Context, db SQLQueries, } createParams := sqlc.InsertChannelMigParams{ - Version: int16(lnwire.GossipVersion1), + Version: int16(ProtocolV1), Scid: channelIDToBytes(edge.ChannelID), NodeID1: node1DBID, NodeID2: node2DBID, @@ -1660,7 +1655,7 @@ func insertChanEdgePolicyMig(ctx context.Context, tx SQLQueries, }) id, err := tx.InsertEdgePolicyMig(ctx, sqlc.InsertEdgePolicyMigParams{ - Version: int16(lnwire.GossipVersion1), + Version: int16(ProtocolV1), ChannelID: dbChan.channelID, NodeID: nodeID, Timelock: int32(edge.TimeLockDelta), diff --git a/graph/db/migration1/sql_migration_test.go b/graph/db/sql_migration_test.go similarity index 92% rename from graph/db/migration1/sql_migration_test.go rename to graph/db/sql_migration_test.go index 4e86b0304..66a198438 100644 --- a/graph/db/migration1/sql_migration_test.go +++ b/graph/db/sql_migration_test.go @@ -1,12 +1,11 @@ //go:build test_db_postgres || test_db_sqlite -package migration1 +package graphdb import ( "bytes" "cmp" "crypto/rand" - "encoding/hex" "errors" "fmt" "image/color" @@ -17,46 +16,27 @@ import ( "path" "slices" "strings" - "sync" "testing" "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btclog/v2" - "github.com/lightningnetwork/lnd/graph/db/migration1/models" + "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/kvdb/sqlbase" "github.com/lightningnetwork/lnd/kvdb/sqlite" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/sqldb" - "github.com/lightningnetwork/lnd/tor" "github.com/stretchr/testify/require" "pgregory.net/rapid" ) var ( - testPub = route.Vertex{2, 202, 4} - - testRBytes, _ = hex.DecodeString( - "8ce2bc69281ce27da07e6683571319d18e949ddfa2965fb6caa1bf03" + - "14f882d7", - ) - testSBytes, _ = hex.DecodeString( - "299105481d63e0f4bc2a88121167221b6700d72a0ead154c03be696a2" + - "92d24ae", - ) - testRScalar = new(btcec.ModNScalar) - testSScalar = new(btcec.ModNScalar) - _ = testRScalar.SetByteSlice(testRBytes) - _ = testSScalar.SetByteSlice(testSBytes) - testSig = ecdsa.NewSignature(testRScalar, testSScalar) - testChain = *chaincfg.MainNetParams.GenesisHash testColor = color.RGBA{R: 1, G: 2, B: 3} testTime = time.Unix(11111, 0) @@ -141,58 +121,6 @@ var ( 0x1F, 0x90, }, } - - testIP4 = net.ParseIP("192.168.1.1").To4() - testIP6 = net.ParseIP("2001:0db8:0000:0000:0000:ff00:0042:8329") - - testIPV4Addr = &net.TCPAddr{ - IP: testIP4, - Port: 12345, - } - - testIPV6Addr = &net.TCPAddr{ - IP: testIP6, - Port: 65535, - } - - testOnionV2Addr = &tor.OnionAddr{ - OnionService: "3g2upl4pq6kufc4m.onion", - Port: 9735, - } - - testOnionV3Addr = &tor.OnionAddr{ - OnionService: "vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion", //nolint:ll - Port: 80, - } - - testOpaqueAddr = &lnwire.OpaqueAddrs{ - // NOTE: the first byte is a protocol level address type. So - // for we set it to 0xff to guarantee that we do not know this - // type yet. - Payload: []byte{0xff, 0x02, 0x03, 0x04, 0x05, 0x06}, - } - - testDNSAddr = &lnwire.DNSAddress{ - Hostname: "example.com", - Port: 8080, - } - - testAddr = &net.TCPAddr{IP: (net.IP)([]byte{0xA, 0x0, 0x0, 0x1}), - Port: 9000} - anotherAddr, _ = net.ResolveTCPAddr("tcp", - "[2001:db8:85a3:0:0:8a2e:370:7334]:80") - testAddrs = []net.Addr{testAddr, anotherAddr} - - testFeatures = lnwire.NewFeatureVector( - lnwire.NewRawFeatureVector(lnwire.GossipQueriesRequired), - lnwire.Features, - ) - - rev = [chainhash.HashSize]byte{ - 0x51, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda, - 0x48, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17, - 0x2d, 0xe7, 0x93, 0xe4, - } ) // TestMigrateGraphToSQL tests various deterministic cases that we want to test @@ -456,7 +384,10 @@ func TestMigrateGraphToSQL(t *testing.T) { // The PruneGraph call requires that the source // node be set. So that is the first object // we will write. - models.NewV1ShellNode(testPub), + &models.Node{ + HaveNodeAnnouncement: false, + PubKeyBytes: testPub, + }, // Now we add some block heights to prune // the graph at. uint32(1), uint32(2), uint32(20), uint32(3), @@ -816,15 +747,17 @@ type testNodeOpt func(*models.Node) // makeTestNode can be used to create a test models.Node. The // functional options can be used to modify the node's attributes. func makeTestNode(t *testing.T, opts ...testNodeOpt) *models.Node { - n := models.NewV1Node(genPubKey(t), &models.NodeV1Fields{ - AuthSigBytes: testSigBytes, - LastUpdate: testTime, - Color: testColor, - Alias: "kek", - Features: testFeatures.RawFeatureVector, - Addresses: testAddrs, - ExtraOpaqueData: testExtraData, - }) + n := &models.Node{ + HaveNodeAnnouncement: true, + AuthSigBytes: testSigBytes, + LastUpdate: testTime, + Color: testColor, + Alias: "kek", + Features: testFeatures, + Addresses: testAddrs, + ExtraOpaqueData: testExtraData, + PubKeyBytes: genPubKey(t), + } for _, opt := range opts { opt(n) @@ -843,7 +776,12 @@ func makeTestNode(t *testing.T, opts ...testNodeOpt) *models.Node { func makeTestShellNode(t *testing.T, opts ...testNodeOpt) *models.Node { - n := models.NewV1ShellNode(genPubKey(t)) + n := &models.Node{ + HaveNodeAnnouncement: false, + PubKeyBytes: genPubKey(t), + Features: testEmptyFeatures, + LastUpdate: time.Unix(0, 0), + } for _, opt := range opts { opt(n) @@ -893,20 +831,6 @@ func makeTestChannel(t *testing.T, // attributes of a models.ChannelEdgePolicy created by makeTestPolicy. type testPolicyOpt func(*models.ChannelEdgePolicy) -var ( - updateTime = prand.Int63() - updateTimeMu sync.Mutex -) - -func nextUpdateTime() time.Time { - updateTimeMu.Lock() - defer updateTimeMu.Unlock() - - updateTime++ - - return time.Unix(updateTime, 0) -} - // makeTestPolicy creates a test models.ChannelEdgePolicy. The functional // options can be used to modify the policy's attributes. func makeTestPolicy(chanID uint64, toNode route.Vertex, isNode1 bool, @@ -1892,15 +1816,19 @@ func genRandomNode(t *rapid.T) *models.Node { extraOpaqueData = nil } - node := models.NewV1Node(pubKeyBytes, &models.NodeV1Fields{ - AuthSigBytes: sigBytes, - LastUpdate: randTime, - Color: randColor, - Alias: alias.String(), - Features: features, + node := &models.Node{ + HaveNodeAnnouncement: true, + AuthSigBytes: sigBytes, + LastUpdate: randTime, + Color: randColor, + Alias: alias.String(), + Features: lnwire.NewFeatureVector( + features, lnwire.Features, + ), Addresses: addrs, ExtraOpaqueData: extraOpaqueData, - }) + PubKeyBytes: pubKeyBytes, + } // We call this method so that the internal pubkey field is populated // which then lets us to proper struct comparison later on. @@ -1909,35 +1837,3 @@ func genRandomNode(t *rapid.T) *models.Node { return node } - -// putSerializedPolicy is a helper function that writes a serialized -// ChannelEdgePolicy to the edge bucket in the database. -func putSerializedPolicy(t *testing.T, db kvdb.Backend, from []byte, - chanID uint64, b []byte) { - - err := kvdb.Update(db, func(tx kvdb.RwTx) error { - edges := tx.ReadWriteBucket(edgeBucket) - require.NotNil(t, edges) - - edgeIndex := edges.NestedReadWriteBucket(edgeIndexBucket) - require.NotNil(t, edgeIndex) - - var edgeKey [33 + 8]byte - copy(edgeKey[:], from) - byteOrder.PutUint64(edgeKey[33:], chanID) - - var scratch [8]byte - var indexKey [8 + 8]byte - copy(indexKey[:], scratch[:]) - byteOrder.PutUint64(indexKey[8:], chanID) - - updateIndex, err := edges.CreateBucketIfNotExists( - edgeUpdateIndexBucket, - ) - require.NoError(t, err) - require.NoError(t, updateIndex.Put(indexKey[:], nil)) - - return edges.Put(edgeKey[:], b) - }, func() {}) - require.NoError(t, err, "error writing db") -} diff --git a/graph/db/sql_store.go b/graph/db/sql_store.go index 147651517..ff68ea2bf 100644 --- a/graph/db/sql_store.go +++ b/graph/db/sql_store.go @@ -7,7 +7,6 @@ import ( "encoding/hex" "errors" "fmt" - color "image/color" "iter" "maps" "math" @@ -18,9 +17,9 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/aliasmgr" "github.com/lightningnetwork/lnd/batch" "github.com/lightningnetwork/lnd/fn/v2" @@ -33,11 +32,20 @@ import ( "github.com/lightningnetwork/lnd/tor" ) +// ProtocolVersion is an enum that defines the gossip protocol version of a +// message. +type ProtocolVersion uint8 + const ( - gossipV1 = lnwire.GossipVersion1 - gossipV2 = lnwire.GossipVersion2 + // ProtocolV1 is the gossip protocol version defined in BOLT #7. + ProtocolV1 ProtocolVersion = 1 ) +// String returns a string representation of the protocol version. +func (v ProtocolVersion) String() string { + return fmt.Sprintf("V%d", v) +} + // SQLQueries is a subset of the sqlc.Querier interface that can be used to // execute queries against the SQL graph tables. // @@ -52,16 +60,12 @@ type SQLQueries interface { GetNodesByIDs(ctx context.Context, ids []int64) ([]sqlc.GraphNode, error) GetNodeIDByPubKey(ctx context.Context, arg sqlc.GetNodeIDByPubKeyParams) (int64, error) GetNodesByLastUpdateRange(ctx context.Context, arg sqlc.GetNodesByLastUpdateRangeParams) ([]sqlc.GraphNode, error) - GetNodesByBlockHeightRange(ctx context.Context, arg sqlc.GetNodesByBlockHeightRangeParams) ([]sqlc.GraphNode, error) - GetPublicNodesByLastUpdateRange(ctx context.Context, arg sqlc.GetPublicNodesByLastUpdateRangeParams) ([]sqlc.GraphNode, error) ListNodesPaginated(ctx context.Context, arg sqlc.ListNodesPaginatedParams) ([]sqlc.GraphNode, error) ListNodeIDsAndPubKeys(ctx context.Context, arg sqlc.ListNodeIDsAndPubKeysParams) ([]sqlc.ListNodeIDsAndPubKeysRow, error) IsPublicV1Node(ctx context.Context, pubKey []byte) (bool, error) - IsPublicV2Node(ctx context.Context, pubKey []byte) (bool, error) DeleteUnconnectedNodes(ctx context.Context) ([][]byte, error) DeleteNodeByPubKey(ctx context.Context, arg sqlc.DeleteNodeByPubKeyParams) (sql.Result, error) DeleteNode(ctx context.Context, id int64) error - NodeExists(ctx context.Context, arg sqlc.NodeExistsParams) (bool, error) GetExtraNodeTypes(ctx context.Context, nodeID int64) ([]sqlc.GraphNodeExtraType, error) GetNodeExtraTypesBatch(ctx context.Context, ids []int64) ([]sqlc.GraphNodeExtraType, error) @@ -78,8 +82,6 @@ type SQLQueries interface { GetNodeFeaturesBatch(ctx context.Context, ids []int64) ([]sqlc.GraphNodeFeature, error) GetNodeFeaturesByPubKey(ctx context.Context, arg sqlc.GetNodeFeaturesByPubKeyParams) ([]int32, error) DeleteNodeFeature(ctx context.Context, arg sqlc.DeleteNodeFeatureParams) error - GetV1DisabledSCIDs(ctx context.Context) ([][]byte, error) - GetV2DisabledSCIDs(ctx context.Context) ([][]byte, error) /* Source node queries. @@ -92,7 +94,6 @@ type SQLQueries interface { */ CreateChannel(ctx context.Context, arg sqlc.CreateChannelParams) (int64, error) AddV1ChannelProof(ctx context.Context, arg sqlc.AddV1ChannelProofParams) (sql.Result, error) - AddV2ChannelProof(ctx context.Context, arg sqlc.AddV2ChannelProofParams) (sql.Result, error) GetChannelBySCID(ctx context.Context, arg sqlc.GetChannelBySCIDParams) (sqlc.GraphChannel, error) GetChannelsBySCIDs(ctx context.Context, arg sqlc.GetChannelsBySCIDsParams) ([]sqlc.GraphChannel, error) GetChannelsByOutpoints(ctx context.Context, outpoints []string) ([]sqlc.GetChannelsByOutpointsRow, error) @@ -107,12 +108,9 @@ type SQLQueries interface { ListChannelsWithPoliciesPaginated(ctx context.Context, arg sqlc.ListChannelsWithPoliciesPaginatedParams) ([]sqlc.ListChannelsWithPoliciesPaginatedRow, error) ListChannelsWithPoliciesForCachePaginated(ctx context.Context, arg sqlc.ListChannelsWithPoliciesForCachePaginatedParams) ([]sqlc.ListChannelsWithPoliciesForCachePaginatedRow, error) ListChannelsPaginated(ctx context.Context, arg sqlc.ListChannelsPaginatedParams) ([]sqlc.ListChannelsPaginatedRow, error) - ListChannelsPaginatedV2(ctx context.Context, arg sqlc.ListChannelsPaginatedV2Params) ([]sqlc.ListChannelsPaginatedV2Row, error) GetChannelsByPolicyLastUpdateRange(ctx context.Context, arg sqlc.GetChannelsByPolicyLastUpdateRangeParams) ([]sqlc.GetChannelsByPolicyLastUpdateRangeRow, error) - GetChannelsByPolicyBlockRange(ctx context.Context, arg sqlc.GetChannelsByPolicyBlockRangeParams) ([]sqlc.GetChannelsByPolicyBlockRangeRow, error) GetChannelByOutpointWithPolicies(ctx context.Context, arg sqlc.GetChannelByOutpointWithPoliciesParams) (sqlc.GetChannelByOutpointWithPoliciesRow, error) GetPublicV1ChannelsBySCID(ctx context.Context, arg sqlc.GetPublicV1ChannelsBySCIDParams) ([]sqlc.GraphChannel, error) - GetPublicV2ChannelsBySCID(ctx context.Context, arg sqlc.GetPublicV2ChannelsBySCIDParams) ([]sqlc.GraphChannel, error) GetSCIDByOutpoint(ctx context.Context, arg sqlc.GetSCIDByOutpointParams) ([]byte, error) DeleteChannels(ctx context.Context, ids []int64) error @@ -126,6 +124,7 @@ type SQLQueries interface { */ UpsertEdgePolicy(ctx context.Context, arg sqlc.UpsertEdgePolicyParams) (int64, error) GetChannelPolicyByChannelAndNode(ctx context.Context, arg sqlc.GetChannelPolicyByChannelAndNodeParams) (sqlc.GraphChannelPolicy, error) + GetV1DisabledSCIDs(ctx context.Context) ([][]byte, error) UpsertChanPolicyExtraType(ctx context.Context, arg sqlc.UpsertChanPolicyExtraTypeParams) error GetChannelPolicyExtraTypesBatch(ctx context.Context, policyIds []int64) ([]sqlc.GetChannelPolicyExtraTypesBatchRow, error) @@ -177,7 +176,7 @@ type BatchedSQLQueries interface { sqldb.BatchedTx[SQLQueries] } -// SQLStore is an implementation of the Store interface that uses a SQL +// SQLStore is an implementation of the V1Store interface that uses a SQL // database as the backend. type SQLStore struct { cfg *SQLStoreConfig @@ -193,13 +192,13 @@ type SQLStore struct { chanScheduler batch.Scheduler[SQLQueries] nodeScheduler batch.Scheduler[SQLQueries] - srcNodes map[lnwire.GossipVersion]*srcNodeInfo + srcNodes map[ProtocolVersion]*srcNodeInfo srcNodeMu sync.Mutex } -// A compile-time assertion to ensure that SQLStore implements the Store +// A compile-time assertion to ensure that SQLStore implements the V1Store // interface. -var _ Store = (*SQLStore)(nil) +var _ V1Store = (*SQLStore)(nil) // SQLStoreConfig holds the configuration for the SQLStore. type SQLStoreConfig struct { @@ -231,7 +230,7 @@ func NewSQLStore(cfg *SQLStoreConfig, db BatchedSQLQueries, db: db, rejectCache: newRejectCache(opts.RejectCacheSize), chanCache: newChannelCache(opts.ChannelCacheSize), - srcNodes: make(map[lnwire.GossipVersion]*srcNodeInfo), + srcNodes: make(map[ProtocolVersion]*srcNodeInfo), } s.chanScheduler = batch.NewTimeScheduler( @@ -249,7 +248,7 @@ func NewSQLStore(cfg *SQLStoreConfig, db BatchedSQLQueries, // graph. If it is present from before, this will update that node's // information. // -// NOTE: part of the Store interface. +// NOTE: part of the V1Store interface. func (s *SQLStore) AddNode(ctx context.Context, node *models.Node, opts ...batch.SchedulerOption) error { @@ -279,16 +278,14 @@ func (s *SQLStore) AddNode(ctx context.Context, // key. If the node isn't found in the database, then ErrGraphNodeNotFound is // returned. // -// NOTE: part of the Store interface. -func (s *SQLStore) FetchNode(ctx context.Context, v lnwire.GossipVersion, +// NOTE: part of the V1Store interface. +func (s *SQLStore) FetchNode(ctx context.Context, pubKey route.Vertex) (*models.Node, error) { var node *models.Node err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { var err error - _, node, err = getNodeByPubKey( - ctx, s.cfg.QueryCfg, db, v, pubKey, - ) + _, node, err = getNodeByPubKey(ctx, s.cfg.QueryCfg, db, pubKey) return err }, sqldb.NoOpReset) @@ -299,14 +296,14 @@ func (s *SQLStore) FetchNode(ctx context.Context, v lnwire.GossipVersion, return node, nil } -// HasV1Node determines if the graph has a vertex identified by the +// HasNode determines if the graph has a vertex identified by the // target node identity public key. If the node exists in the database, a // timestamp of when the data for the node was lasted updated is returned along // with a true boolean. Otherwise, an empty time.Time is returned with a false // boolean. // -// NOTE: part of the Store interface. -func (s *SQLStore) HasV1Node(ctx context.Context, +// NOTE: part of the V1Store interface. +func (s *SQLStore) HasNode(ctx context.Context, pubKey [33]byte) (time.Time, bool, error) { var ( @@ -316,7 +313,7 @@ func (s *SQLStore) HasV1Node(ctx context.Context, err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { dbNode, err := db.GetNodeByPubKey( ctx, sqlc.GetNodeByPubKeyParams{ - Version: int16(gossipV1), + Version: int16(ProtocolV1), PubKey: pubKey[:], }, ) @@ -342,37 +339,12 @@ func (s *SQLStore) HasV1Node(ctx context.Context, return lastUpdate, exists, nil } -// HasNode determines if the graph has a vertex identified by the -// target node identity public key. -// -// NOTE: part of the Store interface. -func (s *SQLStore) HasNode(ctx context.Context, v lnwire.GossipVersion, - pubKey [33]byte) (bool, error) { - - var exists bool - err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - var err error - exists, err = db.NodeExists(ctx, sqlc.NodeExistsParams{ - Version: int16(v), - PubKey: pubKey[:], - }) - - return err - }, sqldb.NoOpReset) - if err != nil { - return false, fmt.Errorf("unable to check if node (%x) "+ - "exists: %w", pubKey, err) - } - - return exists, nil -} - // AddrsForNode returns all known addresses for the target node public key // that the graph DB is aware of. The returned boolean indicates if the // given node is unknown to the graph DB or not. // -// NOTE: part of the Store interface. -func (s *SQLStore) AddrsForNode(ctx context.Context, v lnwire.GossipVersion, +// NOTE: part of the V1Store interface. +func (s *SQLStore) AddrsForNode(ctx context.Context, nodePub *btcec.PublicKey) (bool, []net.Addr, error) { var ( @@ -384,7 +356,7 @@ func (s *SQLStore) AddrsForNode(ctx context.Context, v lnwire.GossipVersion, // does. dbID, err := db.GetNodeIDByPubKey( ctx, sqlc.GetNodeIDByPubKeyParams{ - Version: int16(v), + Version: int16(ProtocolV1), PubKey: nodePub.SerializeCompressed(), }, ) @@ -413,14 +385,14 @@ func (s *SQLStore) AddrsForNode(ctx context.Context, v lnwire.GossipVersion, // DeleteNode starts a new database transaction to remove a vertex/node // from the database according to the node's public key. // -// NOTE: part of the Store interface. -func (s *SQLStore) DeleteNode(ctx context.Context, v lnwire.GossipVersion, +// NOTE: part of the V1Store interface. +func (s *SQLStore) DeleteNode(ctx context.Context, pubKey route.Vertex) error { err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { res, err := db.DeleteNodeByPubKey( ctx, sqlc.DeleteNodeByPubKeyParams{ - Version: int16(v), + Version: int16(ProtocolV1), PubKey: pubKey[:], }, ) @@ -452,37 +424,26 @@ func (s *SQLStore) DeleteNode(ctx context.Context, v lnwire.GossipVersion, // known for the node, an empty feature vector is returned. // // NOTE: this is part of the graphdb.NodeTraverser interface. -func (s *SQLStore) FetchNodeFeatures(ctx context.Context, - v lnwire.GossipVersion, nodePub route.Vertex) (*lnwire.FeatureVector, - error) { +func (s *SQLStore) FetchNodeFeatures(nodePub route.Vertex) ( + *lnwire.FeatureVector, error) { - return fetchNodeFeatures(ctx, s.db, v, nodePub) + ctx := context.TODO() + + return fetchNodeFeatures(ctx, s.db, nodePub) } // DisabledChannelIDs returns the channel ids of disabled channels. // A channel is disabled when two of the associated ChanelEdgePolicies // have their disabled bit on. // -// NOTE: part of the Store interface. -func (s *SQLStore) DisabledChannelIDs( - ctx context.Context, v lnwire.GossipVersion) ([]uint64, error) { - +// NOTE: part of the V1Store interface. +func (s *SQLStore) DisabledChannelIDs() ([]uint64, error) { var ( + ctx = context.TODO() chanIDs []uint64 ) err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - var ( - dbChanIDs [][]byte - err error - ) - switch v { - case gossipV1: - dbChanIDs, err = db.GetV1DisabledSCIDs(ctx) - case gossipV2: - dbChanIDs, err = db.GetV2DisabledSCIDs(ctx) - default: - return fmt.Errorf("unsupported gossip version: %d", v) - } + dbChanIDs, err := db.GetV1DisabledSCIDs(ctx) if err != nil { return fmt.Errorf("unable to fetch disabled "+ "channels: %w", err) @@ -502,15 +463,15 @@ func (s *SQLStore) DisabledChannelIDs( // LookupAlias attempts to return the alias as advertised by the target node. // -// NOTE: part of the Store interface. -func (s *SQLStore) LookupAlias(ctx context.Context, v lnwire.GossipVersion, +// NOTE: part of the V1Store interface. +func (s *SQLStore) LookupAlias(ctx context.Context, pub *btcec.PublicKey) (string, error) { var alias string err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { dbNode, err := db.GetNodeByPubKey( ctx, sqlc.GetNodeByPubKeyParams{ - Version: int16(v), + Version: int16(ProtocolV1), PubKey: pub.SerializeCompressed(), }, ) @@ -540,21 +501,19 @@ func (s *SQLStore) LookupAlias(ctx context.Context, v lnwire.GossipVersion, // a path finding algorithm in order to explore the reachability of another // node based off the source node. // -// NOTE: part of the Store interface. -func (s *SQLStore) SourceNode(ctx context.Context, - v lnwire.GossipVersion) (*models.Node, error) { +// NOTE: part of the V1Store interface. +func (s *SQLStore) SourceNode(ctx context.Context) (*models.Node, + error) { var node *models.Node err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - _, nodePub, err := s.getSourceNode(ctx, db, v) + _, nodePub, err := s.getSourceNode(ctx, db, ProtocolV1) if err != nil { - return fmt.Errorf("unable to fetch source node: %w", + return fmt.Errorf("unable to fetch V1 source node: %w", err) } - _, node, err = getNodeByPubKey( - ctx, s.cfg.QueryCfg, db, v, nodePub, - ) + _, node, err = getNodeByPubKey(ctx, s.cfg.QueryCfg, db, nodePub) return err }, sqldb.NoOpReset) @@ -569,7 +528,7 @@ func (s *SQLStore) SourceNode(ctx context.Context, // node is to be used as the center of a star-graph within path finding // algorithms. // -// NOTE: part of the Store interface. +// NOTE: part of the V1Store interface. func (s *SQLStore) SetSourceNode(ctx context.Context, node *models.Node) error { @@ -589,9 +548,7 @@ func (s *SQLStore) SetSourceNode(ctx context.Context, // Make sure that if a source node for this version is already // set, then the ID is the same as the one we are about to set. - dbSourceNodeID, _, err := s.getSourceNode( - ctx, db, node.Version, - ) + dbSourceNodeID, _, err := s.getSourceNode(ctx, db, ProtocolV1) if err != nil && !errors.Is(err, ErrSourceNodeNotSet) { return fmt.Errorf("unable to fetch source node: %w", err) @@ -609,52 +566,23 @@ func (s *SQLStore) SetSourceNode(ctx context.Context, }, sqldb.NoOpReset) } -// NodeUpdatesInHorizon returns all the known lightning nodes which have -// updates within the passed range for the given gossip version. This method -// can be used by two nodes to quickly determine if they have the same set of -// up-to-date node announcements. +// NodeUpdatesInHorizon returns all the known lightning node which have an +// update timestamp within the passed range. This method can be used by two +// nodes to quickly determine if they have the same set of up to date node +// announcements. // -// NOTE: This is part of the Store interface. -func (s *SQLStore) NodeUpdatesInHorizon(ctx context.Context, - v lnwire.GossipVersion, r NodeUpdateRange, - opts ...IteratorOption) iter.Seq2[*models.Node, error] { - - if err := r.validateForVersion(v); err != nil { - return func(yield func(*models.Node, error) bool) { - _ = yield(nil, err) - } - } +// NOTE: This is part of the V1Store interface. +func (s *SQLStore) NodeUpdatesInHorizon(startTime, endTime time.Time, + opts ...IteratorOption) iter.Seq2[models.Node, error] { cfg := defaultIteratorConfig() for _, opt := range opts { opt(cfg) } - switch v { - case gossipV1: - return s.nodeUpdatesInHorizonV1(ctx, r, cfg) - - case gossipV2: - return s.nodeUpdatesInHorizonV2(ctx, r, cfg) - - default: - err := fmt.Errorf("unknown gossip version: %v", v) - return func(yield func(*models.Node, error) bool) { - _ = yield(nil, err) - } - } -} - -// nodeUpdatesInHorizonV1 implements the v1 time-based node horizon query. -func (s *SQLStore) nodeUpdatesInHorizonV1(ctx context.Context, - r NodeUpdateRange, - cfg *iterConfig) iter.Seq2[*models.Node, error] { - - startTime := r.StartTime.UnwrapOr(time.Time{}) - endTime := r.EndTime.UnwrapOr(time.Time{}) - - return func(yield func(*models.Node, error) bool) { + return func(yield func(models.Node, error) bool) { var ( + ctx = context.TODO() lastUpdateTime sql.NullInt64 lastPubKey = make([]byte, 33) hasMore = true @@ -663,14 +591,30 @@ func (s *SQLStore) nodeUpdatesInHorizonV1(ctx context.Context, // Each iteration, we'll read a batch amount of nodes, yield // them, then decide is we have more or not. for hasMore { - var batch []*models.Node + var batch []models.Node //nolint:ll err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - rows, err := nodesByLastUpdateRange( - ctx, db, cfg, startTime, - endTime, lastUpdateTime, - lastPubKey, + //nolint:ll + params := sqlc.GetNodesByLastUpdateRangeParams{ + StartTime: sqldb.SQLInt64( + startTime.Unix(), + ), + EndTime: sqldb.SQLInt64( + endTime.Unix(), + ), + LastUpdate: lastUpdateTime, + LastPubKey: lastPubKey, + OnlyPublic: sql.NullBool{ + Bool: cfg.iterPublicNodes, + Valid: true, + }, + MaxResults: sqldb.SQLInt32( + cfg.nodeUpdateIterBatchSize, + ), + } + rows, err := db.GetNodesByLastUpdateRange( + ctx, params, ) if err != nil { return err @@ -681,7 +625,7 @@ func (s *SQLStore) nodeUpdatesInHorizonV1(ctx context.Context, err = forEachNodeInBatch( ctx, s.cfg.QueryCfg, db, rows, func(_ int64, node *models.Node) error { - batch = append(batch, node) + batch = append(batch, *node) // Update pagination cursors // based on the last processed @@ -703,14 +647,14 @@ func (s *SQLStore) nodeUpdatesInHorizonV1(ctx context.Context, return nil }, func() { - batch = []*models.Node{} + batch = []models.Node{} }) if err != nil { log.Errorf("NodeUpdatesInHorizon batch "+ "error: %v", err) - yield(&models.Node{}, err) + yield(models.Node{}, err) return } @@ -729,153 +673,6 @@ func (s *SQLStore) nodeUpdatesInHorizonV1(ctx context.Context, } } -// nodeUpdatesInHorizonV2 implements the v2 block-height-based node horizon -// query. -func (s *SQLStore) nodeUpdatesInHorizonV2(ctx context.Context, - r NodeUpdateRange, - cfg *iterConfig) iter.Seq2[*models.Node, error] { - - startHeight := int64(r.StartHeight.UnwrapOr(0)) - endHeight := int64(r.EndHeight.UnwrapOr(0)) - batchSize := cfg.nodeUpdateIterBatchSize - - return func(yield func(*models.Node, error) bool) { - var ( - lastBlock sql.NullInt64 - lastPubKey = make([]byte, 33) - hasMore = true - ) - - // queryNodes fetches the next page of v2 nodes in the - // block-height range. - queryNodes := func(db SQLQueries) ([]sqlc.GraphNode, error) { - return db.GetNodesByBlockHeightRange( - ctx, sqlc.GetNodesByBlockHeightRangeParams{ - Version: int16(gossipV2), - StartHeight: sqldb.SQLInt64( - startHeight, - ), - EndHeight: sqldb.SQLInt64( - endHeight, - ), - LastBlockHeight: lastBlock, - LastPubKey: lastPubKey, - OnlyPublic: sql.NullBool{ - Bool: cfg.iterPublicNodes, - Valid: true, - }, - MaxResults: sqldb.SQLInt32(batchSize), - }, - ) - } - - // processNode accumulates a node into the batch and - // advances the pagination cursors. - processNode := func(node *models.Node, - batch *[]*models.Node) error { - - *batch = append(*batch, node) - - lastBlock = sql.NullInt64{ - Int64: int64(node.LastBlockHeight), - Valid: true, - } - lastPubKey = node.PubKeyBytes[:] - - return nil - } - - for hasMore { - var batch []*models.Node - - err := s.db.ExecTx( - ctx, sqldb.ReadTxOpt(), - func(db SQLQueries) error { - rows, err := queryNodes(db) - if err != nil { - return err - } - - hasMore = len(rows) == batchSize - - return forEachNodeInBatch( - ctx, s.cfg.QueryCfg, db, - rows, func(_ int64, - n *models.Node) error { - - return processNode( - n, &batch, - ) - }, - ) - }, func() { - batch = nil - }, - ) - if err != nil { - log.Errorf("NodeUpdatesInHorizon(v2) "+ - "batch error: %v", err) - - yield(nil, err) - - return - } - - for _, node := range batch { - if !yield(node, nil) { - return - } - } - - if len(batch) == 0 { - break - } - } - } -} - -// nodesByLastUpdateRange dispatches to either the all-nodes or public-only -// variant of the v1 node horizon query based on the iterator config. -func nodesByLastUpdateRange(ctx context.Context, db SQLQueries, - cfg *iterConfig, startTime, endTime time.Time, - lastUpdateTime sql.NullInt64, - lastPubKey []byte) ([]sqlc.GraphNode, error) { - - if cfg.iterPublicNodes { - return db.GetPublicNodesByLastUpdateRange( - ctx, sqlc.GetPublicNodesByLastUpdateRangeParams{ - StartTime: sqldb.SQLInt64( - startTime.Unix(), - ), - EndTime: sqldb.SQLInt64( - endTime.Unix(), - ), - LastUpdate: lastUpdateTime, - LastPubKey: lastPubKey, - MaxResults: sqldb.SQLInt32( - cfg.nodeUpdateIterBatchSize, - ), - }, - ) - } - - return db.GetNodesByLastUpdateRange( - ctx, sqlc.GetNodesByLastUpdateRangeParams{ - StartTime: sqldb.SQLInt64( - startTime.Unix(), - ), - EndTime: sqldb.SQLInt64( - endTime.Unix(), - ), - LastUpdate: lastUpdateTime, - LastPubKey: lastPubKey, - MaxResults: sqldb.SQLInt32( - cfg.nodeUpdateIterBatchSize, - ), - }, - ) -} - // AddChannelEdge adds a new (undirected, blank) edge to the graph database. An // undirected edge from the two target nodes are created. The information stored // denotes the static attributes of the channel, such as the channelID, the keys @@ -883,15 +680,10 @@ func nodesByLastUpdateRange(ctx context.Context, db SQLQueries, // supports. The chanPoint and chanID are used to uniquely identify the edge // globally within the database. // -// NOTE: part of the Store interface. +// NOTE: part of the V1Store interface. func (s *SQLStore) AddChannelEdge(ctx context.Context, edge *models.ChannelEdgeInfo, opts ...batch.SchedulerOption) error { - if !isKnownGossipVersion(edge.Version) { - return fmt.Errorf("unsupported gossip version: %d", - edge.Version) - } - var alreadyExists bool r := &batch.Request[SQLQueries]{ Opts: batch.NewSchedulerOptions(opts...), @@ -909,7 +701,7 @@ func (s *SQLStore) AddChannelEdge(ctx context.Context, _, err := tx.GetChannelBySCID( ctx, sqlc.GetChannelBySCIDParams{ Scid: chanIDB, - Version: int16(edge.Version), + Version: int16(ProtocolV1), }, ) if err == nil { @@ -929,13 +721,8 @@ func (s *SQLStore) AddChannelEdge(ctx context.Context, case alreadyExists: return ErrEdgeAlreadyExist default: - s.rejectCache.remove( - edge.Version, edge.ChannelID, - ) - s.chanCache.remove( - edge.Version, edge.ChannelID, - ) - + s.rejectCache.remove(edge.ChannelID) + s.chanCache.remove(edge.ChannelID) return nil } }, @@ -948,17 +735,11 @@ func (s *SQLStore) AddChannelEdge(ctx context.Context, // This represents the "newest" channel from the PoV of the chain. This method // can be used by peers to quickly determine if their graphs are in sync. // -// NOTE: This is part of the Store interface. -func (s *SQLStore) HighestChanID(ctx context.Context, - v lnwire.GossipVersion) (uint64, error) { - +// NOTE: This is part of the V1Store interface. +func (s *SQLStore) HighestChanID(ctx context.Context) (uint64, error) { var highestChanID uint64 err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - if !isKnownGossipVersion(v) { - return fmt.Errorf("unsupported gossip version: %d", v) - } - - chanID, err := db.HighestSCID(ctx, int16(v)) + chanID, err := db.HighestSCID(ctx, int16(ProtocolV1)) if errors.Is(err, sql.ErrNoRows) { return nil } else if err != nil { @@ -985,7 +766,7 @@ func (s *SQLStore) HighestChanID(ctx context.Context, // determined by the lexicographical ordering of the identity public keys of the // nodes on either side of the channel. // -// NOTE: part of the Store interface. +// NOTE: part of the V1Store interface. func (s *SQLStore) UpdateEdgePolicy(ctx context.Context, edge *models.ChannelEdgePolicy, opts ...batch.SchedulerOption) (route.Vertex, route.Vertex, error) { @@ -1055,31 +836,26 @@ func (s *SQLStore) updateEdgeCache(e *models.ChannelEdgePolicy, // the entry with the updated timestamp for the direction that was just // written. If the edge doesn't exist, we'll load the cache entry lazily // during the next query for this edge. - if entry, ok := s.rejectCache.get(e.Version, e.ChannelID); ok { - switch e.Version { - case gossipV1: - updateRejectCacheEntryV1( - &entry, isUpdate1, e.LastUpdate, - ) - case gossipV2: - updateRejectCacheEntryV2( - &entry, isUpdate1, e.LastBlockHeight, - ) + if entry, ok := s.rejectCache.get(e.ChannelID); ok { + if isUpdate1 { + entry.upd1Time = e.LastUpdate.Unix() + } else { + entry.upd2Time = e.LastUpdate.Unix() } - s.rejectCache.insert(e.Version, e.ChannelID, entry) + s.rejectCache.insert(e.ChannelID, entry) } // If an entry for this channel is found in channel cache, we'll modify // the entry with the updated policy for the direction that was just // written. If the edge doesn't exist, we'll defer loading the info and // policies and lazily read from disk during the next query. - if channel, ok := s.chanCache.get(e.Version, e.ChannelID); ok { + if channel, ok := s.chanCache.get(e.ChannelID); ok { if isUpdate1 { channel.Policy1 = e } else { channel.Policy2 = e } - s.chanCache.insert(e.Version, e.ChannelID, channel) + s.chanCache.insert(e.ChannelID, channel) } } @@ -1088,21 +864,20 @@ func (s *SQLStore) updateEdgeCache(e *models.ChannelEdgePolicy, // channel's outpoint, whether we have a policy for the channel and the channel // peer's node information. // -// NOTE: part of the Store interface. +// NOTE: part of the V1Store interface. func (s *SQLStore) ForEachSourceNodeChannel(ctx context.Context, - v lnwire.GossipVersion, cb func(chanPoint wire.OutPoint, - havePolicy bool, otherNode *models.Node) error, - reset func()) error { + cb func(chanPoint wire.OutPoint, havePolicy bool, + otherNode *models.Node) error, reset func()) error { return s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - nodeID, nodePub, err := s.getSourceNode(ctx, db, v) + nodeID, nodePub, err := s.getSourceNode(ctx, db, ProtocolV1) if err != nil { return fmt.Errorf("unable to fetch source node: %w", err) } return forEachNodeChannel( - ctx, db, s.cfg, v, nodeID, + ctx, db, s.cfg, nodeID, func(info *models.ChannelEdgeInfo, outPolicy *models.ChannelEdgePolicy, _ *models.ChannelEdgePolicy) error { @@ -1124,8 +899,7 @@ func (s *SQLStore) ForEachSourceNodeChannel(ctx context.Context, } _, otherNode, err := getNodeByPubKey( - ctx, s.cfg.QueryCfg, db, v, - otherNodePub, + ctx, s.cfg.QueryCfg, db, otherNodePub, ) if err != nil { return fmt.Errorf("unable to fetch "+ @@ -1147,14 +921,14 @@ func (s *SQLStore) ForEachSourceNodeChannel(ctx context.Context, // returns an error, then the transaction is aborted and the iteration stops // early. // -// NOTE: part of the Store interface. -func (s *SQLStore) ForEachNode(ctx context.Context, v lnwire.GossipVersion, +// NOTE: part of the V1Store interface. +func (s *SQLStore) ForEachNode(ctx context.Context, cb func(node *models.Node) error, reset func()) error { return s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { return forEachNodePaginated( ctx, s.cfg.QueryCfg, db, - v, func(_ context.Context, _ int64, + ProtocolV1, func(_ context.Context, _ int64, node *models.Node) error { return cb(node) @@ -1171,12 +945,13 @@ func (s *SQLStore) ForEachNode(ctx context.Context, v lnwire.GossipVersion, // Unknown policies are passed into the callback as nil values. // // NOTE: this is part of the graphdb.NodeTraverser interface. -func (s *SQLStore) ForEachNodeDirectedChannel(ctx context.Context, - v lnwire.GossipVersion, nodePub route.Vertex, +func (s *SQLStore) ForEachNodeDirectedChannel(nodePub route.Vertex, cb func(channel *DirectedChannel) error, reset func()) error { + var ctx = context.TODO() + return s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - return forEachNodeDirectedChannel(ctx, db, v, nodePub, cb) + return forEachNodeDirectedChannel(ctx, db, nodePub, cb) }, reset) } @@ -1185,12 +960,12 @@ func (s *SQLStore) ForEachNodeDirectedChannel(ctx context.Context, // callback returns an error, then the transaction is aborted and the iteration // stops early. func (s *SQLStore) ForEachNodeCacheable(ctx context.Context, - v lnwire.GossipVersion, cb func(route.Vertex, - *lnwire.FeatureVector) error, reset func()) error { + cb func(route.Vertex, *lnwire.FeatureVector) error, + reset func()) error { err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { return forEachNodeCacheable( - ctx, s.cfg.QueryCfg, db, v, + ctx, s.cfg.QueryCfg, db, func(_ int64, nodePub route.Vertex, features *lnwire.FeatureVector) error { @@ -1214,16 +989,15 @@ func (s *SQLStore) ForEachNodeCacheable(ctx context.Context, // // Unknown policies are passed into the callback as nil values. // -// NOTE: part of the Store interface. -func (s *SQLStore) ForEachNodeChannel(ctx context.Context, - v lnwire.GossipVersion, nodePub route.Vertex, +// NOTE: part of the V1Store interface. +func (s *SQLStore) ForEachNodeChannel(ctx context.Context, nodePub route.Vertex, cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error, reset func()) error { return s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { dbNode, err := db.GetNodeByPubKey( ctx, sqlc.GetNodeByPubKeyParams{ - Version: int16(v), + Version: int16(ProtocolV1), PubKey: nodePub[:], }, ) @@ -1233,7 +1007,7 @@ func (s *SQLStore) ForEachNodeChannel(ctx context.Context, return fmt.Errorf("unable to fetch node: %w", err) } - return forEachNodeChannel(ctx, db, s.cfg, v, dbNode.ID, cb) + return forEachNodeChannel(ctx, db, s.cfg, dbNode.ID, cb) }, reset) } @@ -1255,28 +1029,26 @@ func extractMaxUpdateTime( } } -// buildChannelEdgeFromRow constructs a ChannelEdge from the common fields -// shared by both the v1 time-range and v2 block-height-range query rows. -// The policyRow parameter is passed to extractChannelPolicies which -// type-switches on the concrete sqlc row type. -func (s *SQLStore) buildChannelEdgeFromRow(ctx context.Context, - db SQLQueries, n1, n2 sqlc.GraphNode, ch sqlc.GraphChannel, - policyRow any) (ChannelEdge, error) { +// buildChannelFromRow constructs a ChannelEdge from a database row. +// This includes building the nodes, channel info, and policies. +func (s *SQLStore) buildChannelFromRow(ctx context.Context, db SQLQueries, + row sqlc.GetChannelsByPolicyLastUpdateRangeRow) (ChannelEdge, error) { - node1, err := buildNode(ctx, s.cfg.QueryCfg, db, n1) + node1, err := buildNode(ctx, s.cfg.QueryCfg, db, row.GraphNode) if err != nil { return ChannelEdge{}, fmt.Errorf("unable to build node1: %w", err) } - node2, err := buildNode(ctx, s.cfg.QueryCfg, db, n2) + node2, err := buildNode(ctx, s.cfg.QueryCfg, db, row.GraphNode_2) if err != nil { return ChannelEdge{}, fmt.Errorf("unable to build node2: %w", err) } channel, err := getAndBuildEdgeInfo( - ctx, s.cfg, db, ch, node1.PubKeyBytes, + ctx, s.cfg, db, + row.GraphChannel, node1.PubKeyBytes, node2.PubKeyBytes, ) if err != nil { @@ -1284,7 +1056,7 @@ func (s *SQLStore) buildChannelEdgeFromRow(ctx context.Context, "channel info: %w", err) } - dbPol1, dbPol2, err := extractChannelPolicies(policyRow) + dbPol1, dbPol2, err := extractChannelPolicies(row) if err != nil { return ChannelEdge{}, fmt.Errorf("unable to extract "+ "channel policies: %w", err) @@ -1308,31 +1080,9 @@ func (s *SQLStore) buildChannelEdgeFromRow(ctx context.Context, }, nil } -// extractMaxBlockHeight returns the maximum of the two policy block heights. -// This is used for pagination cursor tracking in v2 gossip queries. -func extractMaxBlockHeight( - row sqlc.GetChannelsByPolicyBlockRangeRow) int64 { - - switch { - case row.Policy1BlockHeight.Valid && - row.Policy2BlockHeight.Valid: - - return max(row.Policy1BlockHeight.Int64, - row.Policy2BlockHeight.Int64) - case row.Policy1BlockHeight.Valid: - return row.Policy1BlockHeight.Int64 - case row.Policy2BlockHeight.Valid: - return row.Policy2BlockHeight.Int64 - default: - return 0 - } -} - // updateChanCacheBatch updates the channel cache with multiple edges at once. // This method acquires the cache lock only once for the entire batch. -func (s *SQLStore) updateChanCacheBatch(v lnwire.GossipVersion, - edgesToCache map[uint64]ChannelEdge) { - +func (s *SQLStore) updateChanCacheBatch(edgesToCache map[uint64]ChannelEdge) { if len(edgesToCache) == 0 { return } @@ -1341,55 +1091,34 @@ func (s *SQLStore) updateChanCacheBatch(v lnwire.GossipVersion, defer s.cacheMu.Unlock() for chanID, edge := range edgesToCache { - s.chanCache.insert(v, chanID, edge) + s.chanCache.insert(chanID, edge) } } // ChanUpdatesInHorizon returns all the known channel edges which have at least -// one edge update within the specified range for the given gossip version. For -// v1, the range is time-based with [start, end) per BOLT 07. +// one edge that has an update timestamp within the specified horizon. // -// NOTE: This is part of the Store interface. -func (s *SQLStore) ChanUpdatesInHorizon(ctx context.Context, - v lnwire.GossipVersion, r ChanUpdateRange, +// Iterator Lifecycle: +// 1. Initialize state (edgesSeen map, cache tracking, pagination cursors) +// 2. Query batch of channels with policies in time range +// 3. For each channel: check if seen, check cache, or build from DB +// 4. Yield channels to caller +// 5. Update cache after successful batch +// 6. Repeat with updated pagination cursor until no more results +// +// NOTE: This is part of the V1Store interface. +func (s *SQLStore) ChanUpdatesInHorizon(startTime, endTime time.Time, opts ...IteratorOption) iter.Seq2[ChannelEdge, error] { - if err := r.validateForVersion(v); err != nil { - return func(yield func(ChannelEdge, error) bool) { - _ = yield(ChannelEdge{}, err) - } - } - + // Apply options. cfg := defaultIteratorConfig() for _, opt := range opts { opt(cfg) } - switch v { - case gossipV1: - return s.chanUpdatesInHorizonV1(ctx, r, cfg) - - case gossipV2: - return s.chanUpdatesInHorizonV2(ctx, r, cfg) - - default: - err := fmt.Errorf("unknown gossip version: %v", v) - return func(yield func(ChannelEdge, error) bool) { - _ = yield(ChannelEdge{}, err) - } - } -} - -// chanUpdatesInHorizonV1 implements the v1 time-based channel horizon query. -func (s *SQLStore) chanUpdatesInHorizonV1(ctx context.Context, - r ChanUpdateRange, - cfg *iterConfig) iter.Seq2[ChannelEdge, error] { - - startTime := r.StartTime.UnwrapOr(time.Time{}) - endTime := r.EndTime.UnwrapOr(time.Time{}) - return func(yield func(ChannelEdge, error) bool) { var ( + ctx = context.TODO() edgesSeen = make(map[uint64]struct{}) edgesToCache = make(map[uint64]ChannelEdge) hits int @@ -1414,7 +1143,7 @@ func (s *SQLStore) chanUpdatesInHorizonV1(ctx context.Context, func(db SQLQueries) error { //nolint:ll params := sqlc.GetChannelsByPolicyLastUpdateRangeParams{ - Version: int16(gossipV1), + Version: int16(ProtocolV1), StartTime: sqldb.SQLInt64( startTime.Unix(), ), @@ -1465,7 +1194,6 @@ func (s *SQLStore) chanUpdatesInHorizonV1(ctx context.Context, // Check cache (we already hold // shared read lock). channel, ok := s.chanCache.get( - gossipV1, chanIDInt, ) if ok { @@ -1477,12 +1205,8 @@ func (s *SQLStore) chanUpdatesInHorizonV1(ctx context.Context, continue } - chanEdge, err := s.buildChannelEdgeFromRow( - ctx, db, - row.GraphNode, - row.GraphNode_2, - row.GraphChannel, - row, + chanEdge, err := s.buildChannelFromRow( + ctx, db, row, ) if err != nil { return err @@ -1525,7 +1249,7 @@ func (s *SQLStore) chanUpdatesInHorizonV1(ctx context.Context, // Update cache after successful batch yield, setting // the cache lock only once for the entire batch. - s.updateChanCacheBatch(gossipV1, edgesToCache) + s.updateChanCacheBatch(edgesToCache) edgesToCache = make(map[uint64]ChannelEdge) // If the batch didn't yield anything, then we're done. @@ -1535,200 +1259,30 @@ func (s *SQLStore) chanUpdatesInHorizonV1(ctx context.Context, } if total > 0 { - log.Debugf("ChanUpdatesInHorizon(v1) hit "+ - "percentage: %.2f (%d/%d)", + log.Debugf("ChanUpdatesInHorizon hit percentage: "+ + "%.2f (%d/%d)", float64(hits)*100/float64(total), hits, total) } else { - log.Debugf("ChanUpdatesInHorizon(v1) returned no " + - "edges in horizon") - } - } -} - -// chanUpdatesInHorizonV2 implements the v2 block-height-based channel horizon -// query. -func (s *SQLStore) chanUpdatesInHorizonV2(ctx context.Context, - r ChanUpdateRange, - cfg *iterConfig) iter.Seq2[ChannelEdge, error] { - - startHeight := int64(r.StartHeight.UnwrapOr(0)) - endHeight := int64(r.EndHeight.UnwrapOr(0)) - batchSize := cfg.chanUpdateIterBatchSize - - return func(yield func(ChannelEdge, error) bool) { - var ( - edgesSeen = make(map[uint64]struct{}) - edgesToCache = make(map[uint64]ChannelEdge) - hits int - total int - lastBlockHeight sql.NullInt64 - lastID sql.NullInt64 - hasMore = true - ) - - // queryChannels fetches the next page of v2 channels in - // the block-height range. - queryChannels := func( - db SQLQueries, - ) ([]sqlc.GetChannelsByPolicyBlockRangeRow, error) { - - return db.GetChannelsByPolicyBlockRange( - ctx, - sqlc.GetChannelsByPolicyBlockRangeParams{ - Version: int16(gossipV2), - StartHeight: sqldb.SQLInt64( - startHeight, - ), - EndHeight: sqldb.SQLInt64( - endHeight, - ), - LastBlockHeight: lastBlockHeight, - LastID: lastID, - MaxResults: sql.NullInt32{ - Int32: int32(batchSize), - Valid: true, - }, - }, - ) - } - - // processRow handles a single channel row: updates - // pagination cursors, checks the seen set and cache, and - // builds the channel edge if needed. - processRow := func(ctx context.Context, db SQLQueries, - row sqlc.GetChannelsByPolicyBlockRangeRow, - batch *[]ChannelEdge) error { - - lastBlockHeight = sql.NullInt64{ - Int64: extractMaxBlockHeight(row), - Valid: true, - } - lastID = sql.NullInt64{ - Int64: row.GraphChannel.ID, - Valid: true, - } - - chanIDInt := byteOrder.Uint64( - row.GraphChannel.Scid, - ) - if _, ok := edgesSeen[chanIDInt]; ok { - return nil - } - - // Check cache (we already hold shared read - // lock). - channel, ok := s.chanCache.get( - gossipV2, chanIDInt, - ) - if ok { - hits++ - total++ - edgesSeen[chanIDInt] = struct{}{} - *batch = append(*batch, channel) - - return nil - } - - chanEdge, err := s.buildChannelEdgeFromRow( - ctx, db, row.GraphNode, - row.GraphNode_2, - row.GraphChannel, row, - ) - if err != nil { - return err - } - - edgesSeen[chanIDInt] = struct{}{} - edgesToCache[chanIDInt] = chanEdge - *batch = append(*batch, chanEdge) - total++ - - return nil - } - - for hasMore { - var batch []ChannelEdge - - s.cacheMu.RLock() - - err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), - func(db SQLQueries) error { - rows, err := queryChannels(db) - if err != nil { - return err - } - - hasMore = len(rows) == batchSize - - for _, row := range rows { - err := processRow( - ctx, db, row, &batch, - ) - if err != nil { - return err - } - } - - return nil - }, func() { - batch = nil - edgesSeen = make( - map[uint64]struct{}, - ) - edgesToCache = make( - map[uint64]ChannelEdge, - ) - }, - ) - - s.cacheMu.RUnlock() - - if err != nil { - log.Errorf("ChanUpdatesInHorizon(v2) "+ - "batch error: %v", err) - - yield(ChannelEdge{}, err) - - return - } - - for _, edge := range batch { - if !yield(edge, nil) { - return - } - } - - s.updateChanCacheBatch(gossipV2, edgesToCache) - edgesToCache = make(map[uint64]ChannelEdge) - - if len(batch) == 0 { - break - } - } - - if total > 0 { - log.Debugf("ChanUpdatesInHorizon(v2) hit "+ - "percentage: %.2f (%d/%d)", - float64(hits)*100/float64(total), hits, - total) - } else { - log.Debugf("ChanUpdatesInHorizon(v2) returned " + - "no edges in horizon") + log.Debugf("ChanUpdatesInHorizon returned no edges "+ + "in horizon (%s, %s)", startTime, endTime) } } } // ForEachNodeCached is similar to forEachNode, but it returns DirectedChannel -// data to the call-back. +// data to the call-back. If withAddrs is true, then the call-back will also be +// provided with the addresses associated with the node. The address retrieval +// result in an additional round-trip to the database, so it should only be used +// if the addresses are actually needed. // -// NOTE: part of the Store interface. -func (s *SQLStore) ForEachNodeCached(ctx context.Context, - v lnwire.GossipVersion, - cb func(ctx context.Context, node route.Vertex, +// NOTE: part of the V1Store interface. +func (s *SQLStore) ForEachNodeCached(ctx context.Context, withAddrs bool, + cb func(ctx context.Context, node route.Vertex, addrs []net.Addr, chans map[uint64]*DirectedChannel) error, reset func()) error { type nodeCachedBatchData struct { features map[int64][]int + addrs map[int64][]nodeAddress chanBatchData *batchChannelData chanMap map[int64][]sqlc.ListChannelsForNodeIDsRow } @@ -1740,7 +1294,7 @@ func (s *SQLStore) ForEachNodeCached(ctx context.Context, return db.ListNodeIDsAndPubKeys( ctx, sqlc.ListNodeIDsAndPubKeysParams{ - Version: int16(v), + Version: int16(ProtocolV1), ID: lastID, Limit: limit, }, @@ -1761,11 +1315,24 @@ func (s *SQLStore) ForEachNodeCached(ctx context.Context, "node features: %w", err) } + // Maybe fetch the node's addresses if requested. + var nodeAddrs map[int64][]nodeAddress + if withAddrs { + nodeAddrs, err = batchLoadNodeAddressesHelper( + ctx, s.cfg.QueryCfg, db, nodeIDs, + ) + if err != nil { + return nil, fmt.Errorf("unable to "+ + "batch load node "+ + "addresses: %w", err) + } + } + // Batch load ALL unique channels for ALL nodes in this // page. allChannels, err := db.ListChannelsForNodeIDs( ctx, sqlc.ListChannelsForNodeIDsParams{ - Version: int16(lnwire.GossipVersion1), + Version: int16(ProtocolV1), Node1Ids: nodeIDs, Node2Ids: nodeIDs, }, @@ -1849,6 +1416,7 @@ func (s *SQLStore) ForEachNodeCached(ctx context.Context, return &nodeCachedBatchData{ features: nodeFeatures, + addrs: nodeAddrs, chanBatchData: channelBatchData, chanMap: nodeChannelMap, }, nil @@ -1892,7 +1460,15 @@ func (s *SQLStore) ForEachNodeCached(ctx context.Context, channels[directedChan.ChannelID] = directedChan } - return cb(ctx, nodePub, channels) + addrs, err := buildNodeAddresses( + batchData.addrs[nodeData.ID], + ) + if err != nil { + return fmt.Errorf("unable to build node "+ + "addresses: %w", err) + } + + return cb(ctx, nodePub, addrs, channels) } return sqldb.ExecuteCollectAndBatchWithSharedDataQuery( @@ -1922,14 +1498,11 @@ func (s *SQLStore) ForEachNodeCached(ctx context.Context, // // NOTE: this method is like ForEachChannel but fetches only the data // required for the graph cache. -func (s *SQLStore) ForEachChannelCacheable(ctx context.Context, - v lnwire.GossipVersion, - cb func(*models.CachedEdgeInfo, *models.CachedEdgePolicy, - *models.CachedEdgePolicy) error, reset func()) error { +func (s *SQLStore) ForEachChannelCacheable(cb func(*models.CachedEdgeInfo, + *models.CachedEdgePolicy, *models.CachedEdgePolicy) error, + reset func()) error { - if !isKnownGossipVersion(v) { - return fmt.Errorf("unsupported gossip version: %d", v) - } + ctx := context.TODO() handleChannel := func(_ context.Context, row sqlc.ListChannelsWithPoliciesForCachePaginatedRow) error { @@ -1974,7 +1547,7 @@ func (s *SQLStore) ForEachChannelCacheable(ctx context.Context, return db.ListChannelsWithPoliciesForCachePaginated( ctx, sqlc.ListChannelsWithPoliciesForCachePaginatedParams{ - Version: int16(v), + Version: int16(ProtocolV1), ID: lastID, Limit: limit, }, @@ -1998,18 +1571,13 @@ func (s *SQLStore) ForEachChannelCacheable(ctx context.Context, // for that particular channel edge routing policy will be passed into the // callback. // -// NOTE: part of the Store interface. +// NOTE: part of the V1Store interface. func (s *SQLStore) ForEachChannel(ctx context.Context, - v lnwire.GossipVersion, cb func(*models.ChannelEdgeInfo, - *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error, - reset func()) error { - - if !isKnownGossipVersion(v) { - return fmt.Errorf("unsupported gossip version: %d", v) - } + cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, + *models.ChannelEdgePolicy) error, reset func()) error { return s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - return forEachChannelWithPolicies(ctx, db, s.cfg, v, cb) + return forEachChannelWithPolicies(ctx, db, s.cfg, cb) }, reset) } @@ -2021,12 +1589,12 @@ func (s *SQLStore) ForEachChannel(ctx context.Context, // timestamp info of the latest received channel update messages of the channel // will be included in the response. // -// NOTE: This is part of the Store interface. -func (s *SQLStore) FilterChannelRange(ctx context.Context, - v lnwire.GossipVersion, startHeight, endHeight uint32, +// NOTE: This is part of the V1Store interface. +func (s *SQLStore) FilterChannelRange(startHeight, endHeight uint32, withTimestamps bool) ([]BlockChannelRange, error) { var ( + ctx = context.TODO() startSCID = &lnwire.ShortChannelID{ BlockHeight: startHeight, } @@ -2046,48 +1614,24 @@ func (s *SQLStore) FilterChannelRange(ctx context.Context, // and add those timestamps to the collected channel. channelsPerBlock := make(map[uint32][]ChannelUpdateInfo) err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - var ( - dbChans []sqlc.GraphChannel - chanErr error + dbChans, err := db.GetPublicV1ChannelsBySCID( + ctx, sqlc.GetPublicV1ChannelsBySCIDParams{ + StartScid: chanIDStart, + EndScid: chanIDEnd, + }, ) - - switch v { - case gossipV1: - dbChans, chanErr = db.GetPublicV1ChannelsBySCID( - ctx, sqlc.GetPublicV1ChannelsBySCIDParams{ - StartScid: chanIDStart, - EndScid: chanIDEnd, - }, - ) - case gossipV2: - dbChans, chanErr = db.GetPublicV2ChannelsBySCID( - ctx, sqlc.GetPublicV2ChannelsBySCIDParams{ - StartScid: chanIDStart, - EndScid: chanIDEnd, - }, - ) - default: - return fmt.Errorf("unsupported gossip version: %d", v) - } - if chanErr != nil { + if err != nil { return fmt.Errorf("unable to fetch channel range: %w", - chanErr) + err) } for _, dbChan := range dbChans { cid := lnwire.NewShortChanIDFromInt( byteOrder.Uint64(dbChan.Scid), ) - - var chanInfo ChannelUpdateInfo - switch v { - case gossipV1: - chanInfo = NewV1ChannelUpdateInfo( - cid, time.Time{}, time.Time{}, - ) - case gossipV2: - chanInfo = NewV2ChannelUpdateInfo(cid, 0, 0) - } + chanInfo := NewChannelUpdateInfo( + cid, time.Time{}, time.Time{}, + ) if !withTimestamps { channelsPerBlock[cid.BlockHeight] = append( @@ -2101,7 +1645,7 @@ func (s *SQLStore) FilterChannelRange(ctx context.Context, //nolint:ll node1Policy, err := db.GetChannelPolicyByChannelAndNode( ctx, sqlc.GetChannelPolicyByChannelAndNodeParams{ - Version: int16(v), + Version: int16(ProtocolV1), ChannelID: dbChan.ID, NodeID: dbChan.NodeID1, }, @@ -2110,25 +1654,15 @@ func (s *SQLStore) FilterChannelRange(ctx context.Context, return fmt.Errorf("unable to fetch node1 "+ "policy: %w", err) } else if err == nil { - n1Update := node1Policy.LastUpdate.Int64 - n1Height := node1Policy.BlockHeight.Int64 - - switch v { - case gossipV1: - chanInfo.Node1Freshness = - lnwire.UnixTimestamp(n1Update) - case gossipV2: - chanInfo.Node1Freshness = - lnwire.BlockHeightTimestamp( - n1Height, - ) - } + chanInfo.Node1UpdateTimestamp = time.Unix( + node1Policy.LastUpdate.Int64, 0, + ) } //nolint:ll node2Policy, err := db.GetChannelPolicyByChannelAndNode( ctx, sqlc.GetChannelPolicyByChannelAndNodeParams{ - Version: int16(v), + Version: int16(ProtocolV1), ChannelID: dbChan.ID, NodeID: dbChan.NodeID2, }, @@ -2137,19 +1671,9 @@ func (s *SQLStore) FilterChannelRange(ctx context.Context, return fmt.Errorf("unable to fetch node2 "+ "policy: %w", err) } else if err == nil { - n2Update := node2Policy.LastUpdate.Int64 - n2Height := node2Policy.BlockHeight.Int64 - - switch v { - case gossipV1: - chanInfo.Node2Freshness = - lnwire.UnixTimestamp(n2Update) - case gossipV2: - chanInfo.Node2Freshness = - lnwire.BlockHeightTimestamp( - n2Height, - ) - } + chanInfo.Node2UpdateTimestamp = time.Unix( + node2Policy.LastUpdate.Int64, 0, + ) } channelsPerBlock[cid.BlockHeight] = append( @@ -2182,16 +1706,14 @@ func (s *SQLStore) FilterChannelRange(ctx context.Context, } // MarkEdgeZombie attempts to mark a channel identified by its channel ID as a -// zombie for the given gossip version. This method is used on an ad-hoc basis, -// when channels need to be marked as zombies outside the normal pruning cycle. +// zombie. This method is used on an ad-hoc basis, when channels need to be +// marked as zombies outside the normal pruning cycle. // -// NOTE: part of the Store interface. -func (s *SQLStore) MarkEdgeZombie(ctx context.Context, v lnwire.GossipVersion, - chanID uint64, pubKey1, pubKey2 [33]byte) error { +// NOTE: part of the V1Store interface. +func (s *SQLStore) MarkEdgeZombie(chanID uint64, + pubKey1, pubKey2 [33]byte) error { - if !isKnownGossipVersion(v) { - return fmt.Errorf("unsupported gossip version: %d", v) - } + ctx := context.TODO() s.cacheMu.Lock() defer s.cacheMu.Unlock() @@ -2201,7 +1723,7 @@ func (s *SQLStore) MarkEdgeZombie(ctx context.Context, v lnwire.GossipVersion, err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { return db.UpsertZombieChannel( ctx, sqlc.UpsertZombieChannelParams{ - Version: int16(v), + Version: int16(ProtocolV1), Scid: chanIDB, NodeKey1: pubKey1[:], NodeKey2: pubKey2[:], @@ -2213,33 +1735,29 @@ func (s *SQLStore) MarkEdgeZombie(ctx context.Context, v lnwire.GossipVersion, "(channel_id=%d): %w", chanID, err) } - s.rejectCache.remove(v, chanID) - s.chanCache.remove(v, chanID) + s.rejectCache.remove(chanID) + s.chanCache.remove(chanID) return nil } -// MarkEdgeLive clears an edge from our zombie index for the given gossip -// version, deeming it as live. +// MarkEdgeLive clears an edge from our zombie index, deeming it as live. // -// NOTE: part of the Store interface. -func (s *SQLStore) MarkEdgeLive(ctx context.Context, - v lnwire.GossipVersion, chanID uint64) error { - +// NOTE: part of the V1Store interface. +func (s *SQLStore) MarkEdgeLive(chanID uint64) error { s.cacheMu.Lock() defer s.cacheMu.Unlock() - if !isKnownGossipVersion(v) { - return fmt.Errorf("unsupported gossip version: %d", v) - } - - chanIDB := channelIDToBytes(chanID) + var ( + ctx = context.TODO() + chanIDB = channelIDToBytes(chanID) + ) err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { res, err := db.DeleteZombieChannel( ctx, sqlc.DeleteZombieChannelParams{ Scid: chanIDB, - Version: int16(v), + Version: int16(ProtocolV1), }, ) if err != nil { @@ -2266,8 +1784,8 @@ func (s *SQLStore) MarkEdgeLive(ctx context.Context, "(channel_id=%d): %w", chanID, err) } - s.rejectCache.remove(v, chanID) - s.chanCache.remove(v, chanID) + s.rejectCache.remove(chanID) + s.chanCache.remove(chanID) return err } @@ -2276,26 +1794,22 @@ func (s *SQLStore) MarkEdgeLive(ctx context.Context, // zombie, then the two node public keys corresponding to this edge are also // returned. // -// NOTE: part of the Store interface. -func (s *SQLStore) IsZombieEdge(ctx context.Context, v lnwire.GossipVersion, - chanID uint64) (bool, [33]byte, [33]byte, error) { +// NOTE: part of the V1Store interface. +func (s *SQLStore) IsZombieEdge(chanID uint64) (bool, [33]byte, [33]byte, + error) { var ( + ctx = context.TODO() isZombie bool pubKey1, pubKey2 route.Vertex chanIDB = channelIDToBytes(chanID) ) - if !isKnownGossipVersion(v) { - return false, [33]byte{}, [33]byte{}, - fmt.Errorf("unsupported gossip version: %d", v) - } - err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { zombie, err := db.GetZombieChannel( ctx, sqlc.GetZombieChannelParams{ Scid: chanIDB, - Version: int16(v), + Version: int16(ProtocolV1), }, ) if errors.Is(err, sql.ErrNoRows) { @@ -2323,18 +1837,14 @@ func (s *SQLStore) IsZombieEdge(ctx context.Context, v lnwire.GossipVersion, // NumZombies returns the current number of zombie channels in the graph. // -// NOTE: part of the Store interface. -func (s *SQLStore) NumZombies( - ctx context.Context, v lnwire.GossipVersion, -) (uint64, error) { - +// NOTE: part of the V1Store interface. +func (s *SQLStore) NumZombies() (uint64, error) { var ( + ctx = context.TODO() numZombies uint64 ) err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - count, err := db.CountZombieChannels( - ctx, int16(v), - ) + count, err := db.CountZombieChannels(ctx, int16(ProtocolV1)) if err != nil { return fmt.Errorf("unable to count zombie channels: %w", err) @@ -2360,11 +1870,9 @@ func (s *SQLStore) NumZombies( // that resurrects the channel from its zombie state. The markZombie bool // denotes whether to mark the channel as a zombie. // -// NOTE: part of the Store interface. -func (s *SQLStore) DeleteChannelEdges(ctx context.Context, - v lnwire.GossipVersion, strictZombiePruning, markZombie bool, - chanIDs ...uint64) ( - []*models.ChannelEdgeInfo, error) { +// NOTE: part of the V1Store interface. +func (s *SQLStore) DeleteChannelEdges(strictZombiePruning, markZombie bool, + chanIDs ...uint64) ([]*models.ChannelEdgeInfo, error) { s.cacheMu.Lock() defer s.cacheMu.Unlock() @@ -2376,7 +1884,10 @@ func (s *SQLStore) DeleteChannelEdges(ctx context.Context, chanLookup[chanID] = struct{}{} } - var edges []*models.ChannelEdgeInfo + var ( + ctx = context.TODO() + edges []*models.ChannelEdgeInfo + ) err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { // First, collect all channel rows. var channelRows []sqlc.GetChannelsBySCIDWithPoliciesRow @@ -2394,7 +1905,7 @@ func (s *SQLStore) DeleteChannelEdges(ctx context.Context, } err := s.forEachChanWithPoliciesInSCIDList( - ctx, db, v, chanCallBack, chanIDs, + ctx, db, chanCallBack, chanIDs, ) if err != nil { return err @@ -2422,7 +1933,7 @@ func (s *SQLStore) DeleteChannelEdges(ctx context.Context, scid := byteOrder.Uint64(row.GraphChannel.Scid) err := handleZombieMarking( - ctx, db, v, row, edges[i], + ctx, db, row, edges[i], strictZombiePruning, scid, ) if err != nil { @@ -2447,8 +1958,8 @@ func (s *SQLStore) DeleteChannelEdges(ctx context.Context, } for _, chanID := range chanIDs { - s.rejectCache.remove(v, chanID) - s.chanCache.remove(v, chanID) + s.rejectCache.remove(chanID) + s.chanCache.remove(chanID) } return edges, nil @@ -2464,29 +1975,22 @@ func (s *SQLStore) DeleteChannelEdges(ctx context.Context, // within the database. In this case, the ChannelEdgePolicy's will be nil, and // the ChannelEdgeInfo will only include the public keys of each node. // -// NOTE: part of the Store interface. -func (s *SQLStore) FetchChannelEdgesByID(ctx context.Context, - v lnwire.GossipVersion, chanID uint64) ( +// NOTE: part of the V1Store interface. +func (s *SQLStore) FetchChannelEdgesByID(chanID uint64) ( *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy, error) { var ( + ctx = context.TODO() edge *models.ChannelEdgeInfo policy1, policy2 *models.ChannelEdgePolicy chanIDB = channelIDToBytes(chanID) ) - - if !isKnownGossipVersion(v) { - return nil, nil, nil, fmt.Errorf( - "unsupported gossip version: %d", v, - ) - } - err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { row, err := db.GetChannelBySCIDWithPolicies( ctx, sqlc.GetChannelBySCIDWithPoliciesParams{ Scid: chanIDB, - Version: int16(v), + Version: int16(ProtocolV1), }, ) if errors.Is(err, sql.ErrNoRows) { @@ -2495,7 +1999,7 @@ func (s *SQLStore) FetchChannelEdgesByID(ctx context.Context, zombie, err := db.GetZombieChannel( ctx, sqlc.GetZombieChannelParams{ Scid: chanIDB, - Version: int16(v), + Version: int16(ProtocolV1), }, ) if errors.Is(err, sql.ErrNoRows) { @@ -2510,29 +2014,9 @@ func (s *SQLStore) FetchChannelEdgesByID(ctx context.Context, // populate the edge info with the public keys of each // party as this is the only information we have about // it. - node1, err := route.NewVertexFromBytes(zombie.NodeKey1) - if err != nil { - return err - } - node2, err := route.NewVertexFromBytes(zombie.NodeKey2) - if err != nil { - return err - } - switch v { - case gossipV1: - edge, err = models.NewV1Channel( - 0, chainhash.Hash{}, node1, - node2, &models.ChannelV1Fields{}, - ) - case gossipV2: - edge, err = models.NewV2Channel( - 0, chainhash.Hash{}, node1, - node2, &models.ChannelV2Fields{}, - ) - } - if err != nil { - return err - } + edge = &models.ChannelEdgeInfo{} + copy(edge.NodeKey1Bytes[:], zombie.NodeKey1) + copy(edge.NodeKey2Bytes[:], zombie.NodeKey2) return ErrZombieEdge } else if err != nil { @@ -2588,28 +2072,21 @@ func (s *SQLStore) FetchChannelEdgesByID(ctx context.Context, // information for the channel itself is returned as well as two structs that // contain the routing policies for the channel in either direction. // -// NOTE: part of the Store interface. -func (s *SQLStore) FetchChannelEdgesByOutpoint(ctx context.Context, - v lnwire.GossipVersion, op *wire.OutPoint) ( +// NOTE: part of the V1Store interface. +func (s *SQLStore) FetchChannelEdgesByOutpoint(op *wire.OutPoint) ( *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy, error) { var ( + ctx = context.TODO() edge *models.ChannelEdgeInfo policy1, policy2 *models.ChannelEdgePolicy ) - - if !isKnownGossipVersion(v) { - return nil, nil, nil, fmt.Errorf( - "unsupported gossip version: %d", v, - ) - } - err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { row, err := db.GetChannelByOutpointWithPolicies( ctx, sqlc.GetChannelByOutpointWithPoliciesParams{ Outpoint: op.String(), - Version: int16(v), + Version: int16(ProtocolV1), }, ) if errors.Is(err, sql.ErrNoRows) { @@ -2658,16 +2135,18 @@ func (s *SQLStore) FetchChannelEdgesByOutpoint(ctx context.Context, return edge, policy1, policy2, nil } -// HasV1ChannelEdge returns true if the database knows of a channel edge -// with the passed channel ID, and false otherwise. If an edge with that ID -// is found within the graph, then two time stamps representing the last time -// the edge was updated for both directed edges are returned along with the -// boolean. If it is not found, then the zombie index is checked and its -// result is returned as the second boolean. +// HasChannelEdge returns true if the database knows of a channel edge with the +// passed channel ID, and false otherwise. If an edge with that ID is found +// within the graph, then two time stamps representing the last time the edge +// was updated for both directed edges are returned along with the boolean. If +// it is not found, then the zombie index is checked and its result is returned +// as the second boolean. // -// NOTE: part of the Store interface. -func (s *SQLStore) HasV1ChannelEdge(ctx context.Context, - chanID uint64) (time.Time, time.Time, bool, bool, error) { +// NOTE: part of the V1Store interface. +func (s *SQLStore) HasChannelEdge(chanID uint64) (time.Time, time.Time, bool, + bool, error) { + + ctx := context.TODO() var ( exists bool @@ -2679,7 +2158,7 @@ func (s *SQLStore) HasV1ChannelEdge(ctx context.Context, // We'll query the cache with the shared lock held to allow multiple // readers to access values in the cache concurrently if they exist. s.cacheMu.RLock() - if entry, ok := s.rejectCache.get(gossipV1, chanID); ok { + if entry, ok := s.rejectCache.get(chanID); ok { s.cacheMu.RUnlock() node1LastUpdate = time.Unix(entry.upd1Time, 0) node2LastUpdate = time.Unix(entry.upd2Time, 0) @@ -2695,7 +2174,7 @@ func (s *SQLStore) HasV1ChannelEdge(ctx context.Context, // The item was not found with the shared lock, so we'll acquire the // exclusive lock and check the cache again in case another method added // the entry to the cache while no lock was held. - if entry, ok := s.rejectCache.get(gossipV1, chanID); ok { + if entry, ok := s.rejectCache.get(chanID); ok { node1LastUpdate = time.Unix(entry.upd1Time, 0) node2LastUpdate = time.Unix(entry.upd2Time, 0) exists, isZombie = entry.flags.unpack() @@ -2708,7 +2187,7 @@ func (s *SQLStore) HasV1ChannelEdge(ctx context.Context, channel, err := db.GetChannelBySCID( ctx, sqlc.GetChannelBySCIDParams{ Scid: chanIDB, - Version: int16(gossipV1), + Version: int16(ProtocolV1), }, ) if errors.Is(err, sql.ErrNoRows) { @@ -2716,7 +2195,7 @@ func (s *SQLStore) HasV1ChannelEdge(ctx context.Context, isZombie, err = db.IsZombieChannel( ctx, sqlc.IsZombieChannelParams{ Scid: chanIDB, - Version: int16(gossipV1), + Version: int16(ProtocolV1), }, ) if err != nil { @@ -2733,7 +2212,7 @@ func (s *SQLStore) HasV1ChannelEdge(ctx context.Context, policy1, err := db.GetChannelPolicyByChannelAndNode( ctx, sqlc.GetChannelPolicyByChannelAndNodeParams{ - Version: int16(gossipV1), + Version: int16(ProtocolV1), ChannelID: channel.ID, NodeID: channel.NodeID1, }, @@ -2747,7 +2226,7 @@ func (s *SQLStore) HasV1ChannelEdge(ctx context.Context, policy2, err := db.GetChannelPolicyByChannelAndNode( ctx, sqlc.GetChannelPolicyByChannelAndNodeParams{ - Version: int16(gossipV1), + Version: int16(ProtocolV1), ChannelID: channel.ID, NodeID: channel.NodeID2, }, @@ -2766,183 +2245,30 @@ func (s *SQLStore) HasV1ChannelEdge(ctx context.Context, fmt.Errorf("unable to fetch channel: %w", err) } - s.rejectCache.insert( - gossipV1, chanID, - newRejectCacheEntryV1( - node1LastUpdate, node2LastUpdate, exists, - isZombie, - ), - ) + s.rejectCache.insert(chanID, rejectCacheEntry{ + upd1Time: node1LastUpdate.Unix(), + upd2Time: node2LastUpdate.Unix(), + flags: packRejectFlags(exists, isZombie), + }) return node1LastUpdate, node2LastUpdate, exists, isZombie, nil } -// HasChannelEdge returns true if the database knows of a channel edge with the -// passed channel ID and gossip version, and false otherwise. If an edge with -// that ID is found within the graph, then the zombie index is checked and its -// result is returned as the second boolean. -// -// NOTE: part of the Store interface. -func (s *SQLStore) HasChannelEdge(ctx context.Context, - v lnwire.GossipVersion, chanID uint64) (bool, bool, error) { - - if !isKnownGossipVersion(v) { - return false, false, fmt.Errorf( - "unsupported gossip version: %d", v, - ) - } - - var ( - exists bool - isZombie bool - node1LastUpdate time.Time - node2LastUpdate time.Time - node1Block uint32 - node2Block uint32 - ) - - // We'll query the cache with the shared lock held to allow multiple - // readers to access values in the cache concurrently if they exist. - s.cacheMu.RLock() - if entry, ok := s.rejectCache.get(v, chanID); ok { - s.cacheMu.RUnlock() - exists, isZombie = entry.flags.unpack() - return exists, isZombie, nil - } - s.cacheMu.RUnlock() - - s.cacheMu.Lock() - defer s.cacheMu.Unlock() - - // The item was not found with the shared lock, so we'll acquire the - // exclusive lock and check the cache again in case another method added - // the entry to the cache while no lock was held. - if entry, ok := s.rejectCache.get(v, chanID); ok { - exists, isZombie = entry.flags.unpack() - return exists, isZombie, nil - } - - chanIDB := channelIDToBytes(chanID) - err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - channel, err := db.GetChannelBySCID( - ctx, sqlc.GetChannelBySCIDParams{ - Scid: chanIDB, - Version: int16(v), - }, - ) - if errors.Is(err, sql.ErrNoRows) { - // Check if it is a zombie channel. - isZombie, err = db.IsZombieChannel( - ctx, sqlc.IsZombieChannelParams{ - Scid: chanIDB, - Version: int16(v), - }, - ) - if err != nil { - return fmt.Errorf("could not check if channel "+ - "is zombie: %w", err) - } - - return nil - } else if err != nil { - return fmt.Errorf("unable to fetch channel: %w", err) - } - - exists = true - - policy1, err := db.GetChannelPolicyByChannelAndNode( - ctx, sqlc.GetChannelPolicyByChannelAndNodeParams{ - Version: int16(v), - ChannelID: channel.ID, - NodeID: channel.NodeID1, - }, - ) - if err != nil && !errors.Is(err, sql.ErrNoRows) { - return fmt.Errorf("unable to fetch channel policy: %w", - err) - } else if err == nil { - switch v { - case gossipV1: - if policy1.LastUpdate.Valid { - node1LastUpdate = time.Unix( - policy1.LastUpdate.Int64, 0, - ) - } - case gossipV2: - if policy1.BlockHeight.Valid { - node1Block = uint32( - policy1.BlockHeight.Int64, - ) - } - } - } - - policy2, err := db.GetChannelPolicyByChannelAndNode( - ctx, sqlc.GetChannelPolicyByChannelAndNodeParams{ - Version: int16(v), - ChannelID: channel.ID, - NodeID: channel.NodeID2, - }, - ) - if err != nil && !errors.Is(err, sql.ErrNoRows) { - return fmt.Errorf("unable to fetch channel policy: %w", - err) - } else if err == nil { - switch v { - case gossipV1: - if policy2.LastUpdate.Valid { - node2LastUpdate = time.Unix( - policy2.LastUpdate.Int64, 0, - ) - } - case gossipV2: - if policy2.BlockHeight.Valid { - node2Block = uint32( - policy2.BlockHeight.Int64, - ) - } - } - } - - return nil - }, sqldb.NoOpReset) - if err != nil { - return false, false, - fmt.Errorf("unable to fetch channel: %w", err) - } - - var entry rejectCacheEntry - switch v { - case gossipV1: - entry = newRejectCacheEntryV1( - node1LastUpdate, node2LastUpdate, exists, isZombie, - ) - case gossipV2: - entry = newRejectCacheEntryV2( - node1Block, node2Block, exists, isZombie, - ) - } - s.rejectCache.insert(v, chanID, entry) - - return exists, isZombie, nil -} - // ChannelID attempt to lookup the 8-byte compact channel ID which maps to the // passed channel point (outpoint). If the passed channel doesn't exist within // the database, then ErrEdgeNotFound is returned. // -// NOTE: part of the Store interface. -func (s *SQLStore) ChannelID(ctx context.Context, v lnwire.GossipVersion, - chanPoint *wire.OutPoint) (uint64, error) { - +// NOTE: part of the V1Store interface. +func (s *SQLStore) ChannelID(chanPoint *wire.OutPoint) (uint64, error) { var ( + ctx = context.TODO() channelID uint64 ) err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { chanID, err := db.GetSCIDByOutpoint( ctx, sqlc.GetSCIDByOutpointParams{ Outpoint: chanPoint.String(), - Version: int16(v), + Version: int16(ProtocolV1), }, ) if errors.Is(err, sql.ErrNoRows) { @@ -2967,23 +2293,14 @@ func (s *SQLStore) ChannelID(ctx context.Context, v lnwire.GossipVersion, // given public key is seen as a public node in the graph from the graph's // source node's point of view. // -// NOTE: part of the Store interface. -func (s *SQLStore) IsPublicNode(ctx context.Context, v lnwire.GossipVersion, - pubKey [33]byte) (bool, error) { - - if !isKnownGossipVersion(v) { - return false, fmt.Errorf("unsupported gossip version: %d", v) - } +// NOTE: part of the V1Store interface. +func (s *SQLStore) IsPublicNode(pubKey [33]byte) (bool, error) { + ctx := context.TODO() var isPublic bool err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { var err error - switch v { - case gossipV1: - isPublic, err = db.IsPublicV1Node(ctx, pubKey[:]) - case gossipV2: - isPublic, err = db.IsPublicV2Node(ctx, pubKey[:]) - } + isPublic, err = db.IsPublicV1Node(ctx, pubKey[:]) return err }, sqldb.NoOpReset) @@ -3001,18 +2318,13 @@ func (s *SQLStore) IsPublicNode(ctx context.Context, v lnwire.GossipVersion, // of the query. This can be used to respond to peer queries that are seeking to // fill in gaps in their view of the channel graph. // -// NOTE: part of the Store interface. -func (s *SQLStore) FetchChanInfos(ctx context.Context, - v lnwire.GossipVersion, chanIDs []uint64) ([]ChannelEdge, error) { - +// NOTE: part of the V1Store interface. +func (s *SQLStore) FetchChanInfos(chanIDs []uint64) ([]ChannelEdge, error) { var ( + ctx = context.TODO() edges = make(map[uint64]ChannelEdge) ) err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - if !isKnownGossipVersion(v) { - return fmt.Errorf("unsupported gossip version: %d", v) - } - // First, collect all channel rows. var channelRows []sqlc.GetChannelsBySCIDWithPoliciesRow chanCallBack := func(ctx context.Context, @@ -3023,7 +2335,7 @@ func (s *SQLStore) FetchChanInfos(ctx context.Context, } err := s.forEachChanWithPoliciesInSCIDList( - ctx, db, v, chanCallBack, chanIDs, + ctx, db, chanCallBack, chanIDs, ) if err != nil { return err @@ -3071,7 +2383,7 @@ func (s *SQLStore) FetchChanInfos(ctx context.Context, // GetChannelsBySCIDWithPolicies query that allows us to iterate through // channels in a paginated manner. func (s *SQLStore) forEachChanWithPoliciesInSCIDList(ctx context.Context, - db SQLQueries, v lnwire.GossipVersion, cb func(ctx context.Context, + db SQLQueries, cb func(ctx context.Context, row sqlc.GetChannelsBySCIDWithPoliciesRow) error, chanIDs []uint64) error { @@ -3081,7 +2393,7 @@ func (s *SQLStore) forEachChanWithPoliciesInSCIDList(ctx context.Context, return db.GetChannelsBySCIDWithPolicies( ctx, sqlc.GetChannelsBySCIDWithPoliciesParams{ - Version: int16(v), + Version: int16(ProtocolV1), Scids: scids, }, ) @@ -3100,12 +2412,12 @@ func (s *SQLStore) forEachChanWithPoliciesInSCIDList(ctx context.Context, // channels another peer knows of that we don't. The ChannelUpdateInfos for the // known zombies is also returned. // -// NOTE: part of the Store interface. -func (s *SQLStore) FilterKnownChanIDs(ctx context.Context, - v lnwire.GossipVersion, - chansInfo []ChannelUpdateInfo) ([]uint64, []ChannelUpdateInfo, error) { +// NOTE: part of the V1Store interface. +func (s *SQLStore) FilterKnownChanIDs(chansInfo []ChannelUpdateInfo) ([]uint64, + []ChannelUpdateInfo, error) { var ( + ctx = context.TODO() newChanIDs []uint64 knownZombies []ChannelUpdateInfo infoLookup = make( @@ -3123,8 +2435,7 @@ func (s *SQLStore) FilterKnownChanIDs(ctx context.Context, err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { // The call-back function deletes known channels from // infoLookup, so that we can later check which channels are - // zombies by only looking at the remaining channels in the - // set. + // zombies by only looking at the remaining channels in the set. cb := func(ctx context.Context, channel sqlc.GraphChannel) error { @@ -3133,18 +2444,16 @@ func (s *SQLStore) FilterKnownChanIDs(ctx context.Context, return nil } - err := s.forEachChanInSCIDList( - ctx, db, v, cb, chansInfo, - ) + err := s.forEachChanInSCIDList(ctx, db, cb, chansInfo) if err != nil { - return fmt.Errorf("unable to iterate "+ + return fmt.Errorf("unable to iterate through "+ "channels: %w", err) } // We want to ensure that we deal with the channels in the - // same order that they were passed in, so we iterate over - // the original chansInfo slice and then check if that - // channel is still in the infoLookup map. + // same order that they were passed in, so we iterate over the + // original chansInfo slice and then check if that channel is + // still in the infoLookup map. for _, chanInfo := range chansInfo { channelID := chanInfo.ShortChannelID.ToUint64() if _, ok := infoLookup[channelID]; !ok { @@ -3154,18 +2463,16 @@ func (s *SQLStore) FilterKnownChanIDs(ctx context.Context, isZombie, err := db.IsZombieChannel( ctx, sqlc.IsZombieChannelParams{ Scid: channelIDToBytes(channelID), - Version: int16(v), + Version: int16(ProtocolV1), }, ) if err != nil { - return fmt.Errorf("unable to fetch "+ - "zombie channel: %w", err) + return fmt.Errorf("unable to fetch zombie "+ + "channel: %w", err) } if isZombie { - knownZombies = append( - knownZombies, chanInfo, - ) + knownZombies = append(knownZombies, chanInfo) continue } @@ -3191,11 +2498,10 @@ func (s *SQLStore) FilterKnownChanIDs(ctx context.Context, } // forEachChanInSCIDList is a helper method that executes a paged query -// against the database to fetch all channels of the given gossip version that -// match the passed ChannelUpdateInfo slice. The callback function is called -// for each channel that is found. +// against the database to fetch all channels that match the passed +// ChannelUpdateInfo slice. The callback function is called for each channel +// that is found. func (s *SQLStore) forEachChanInSCIDList(ctx context.Context, db SQLQueries, - v lnwire.GossipVersion, cb func(ctx context.Context, channel sqlc.GraphChannel) error, chansInfo []ChannelUpdateInfo) error { @@ -3204,7 +2510,7 @@ func (s *SQLStore) forEachChanInSCIDList(ctx context.Context, db SQLQueries, return db.GetChannelsBySCIDs( ctx, sqlc.GetChannelsBySCIDsParams{ - Version: int16(v), + Version: int16(ProtocolV1), Scids: scids, }, ) @@ -3230,9 +2536,9 @@ func (s *SQLStore) forEachChanInSCIDList(ctx context.Context, db SQLQueries, // NOTE: this prunes nodes across protocol versions. It will never prune the // source nodes. // -// NOTE: part of the Store interface. -func (s *SQLStore) PruneGraphNodes(ctx context.Context) ( - []route.Vertex, error) { +// NOTE: part of the V1Store interface. +func (s *SQLStore) PruneGraphNodes() ([]route.Vertex, error) { + var ctx = context.TODO() var prunedNodes []route.Vertex err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { @@ -3259,11 +2565,12 @@ func (s *SQLStore) PruneGraphNodes(ctx context.Context) ( // the target block along with any pruned nodes are returned if the function // succeeds without error. // -// NOTE: part of the Store interface. -func (s *SQLStore) PruneGraph(ctx context.Context, - spentOutputs []*wire.OutPoint, blockHash *chainhash.Hash, - blockHeight uint32) ([]*models.ChannelEdgeInfo, []route.Vertex, - error) { +// NOTE: part of the V1Store interface. +func (s *SQLStore) PruneGraph(spentOutputs []*wire.OutPoint, + blockHash *chainhash.Hash, blockHeight uint32) ( + []*models.ChannelEdgeInfo, []route.Vertex, error) { + + ctx := context.TODO() s.cacheMu.Lock() defer s.cacheMu.Unlock() @@ -3351,8 +2658,8 @@ func (s *SQLStore) PruneGraph(ctx context.Context, } for _, channel := range closedChans { - s.rejectCache.remove(channel.Version, channel.ChannelID) - s.chanCache.remove(channel.Version, channel.ChannelID) + s.rejectCache.remove(channel.ChannelID) + s.chanCache.remove(channel.ChannelID) } return closedChans, prunedNodes, nil @@ -3413,161 +2720,57 @@ func (s *SQLStore) deleteChannels(ctx context.Context, db SQLQueries, // returned are the ones that need to be watched on chain to detect channel // closes on the resident blockchain. // -// NOTE: part of the Store interface. -func (s *SQLStore) ChannelView(ctx context.Context, - v lnwire.GossipVersion) ([]EdgePoint, error) { - - var edgePoints []EdgePoint +// NOTE: part of the V1Store interface. +func (s *SQLStore) ChannelView() ([]EdgePoint, error) { + var ( + ctx = context.TODO() + edgePoints []EdgePoint + ) err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - switch v { - case gossipV1: - handleChannel := func(_ context.Context, - channel sqlc.ListChannelsPaginatedRow, - chanFeats map[int64][]int) error { + handleChannel := func(_ context.Context, + channel sqlc.ListChannelsPaginatedRow) error { - key1, err := route.NewVertexFromBytes( - channel.BitcoinKey1, - ) - if err != nil { - return err - } - - key2, err := route.NewVertexFromBytes( - channel.BitcoinKey2, - ) - if err != nil { - return err - } - - // Private taproot channels are currently stored - // as simple v1 channels that only need the - // taproot staging bit to reconstruct the BIP86 - // funding script. They do not carry a custom - // tapscript root on this path. - // - // TODO: Remove this v1 feature-bit workaround - // once private taproot channels have been - // migrated to v2 gossip objects. - feats := lnwire.EmptyFeatureVector() - bits := chanFeats[channel.ID] - for _, bit := range bits { - feats.Set(lnwire.FeatureBit(bit)) - } - - edge := &models.ChannelEdgeInfo{ - Version: gossipV1, - BitcoinKey1Bytes: fn.Some(key1), - BitcoinKey2Bytes: fn.Some(key2), - Features: feats, - } - - pkScript, err := edge.FundingPKScript() - if err != nil { - return err - } - - op, err := wire.NewOutPointFromString( - channel.Outpoint, - ) - if err != nil { - return err - } - - edgePoints = append(edgePoints, EdgePoint{ - FundingPkScript: pkScript, - OutPoint: *op, - }) - - return nil - } - - queryFunc := func(ctx context.Context, lastID int64, - limit int32) ([]sqlc.ListChannelsPaginatedRow, - error) { - - return db.ListChannelsPaginated( - ctx, sqlc.ListChannelsPaginatedParams{ - Version: int16(gossipV1), - ID: lastID, - Limit: limit, - }, - ) - } - - extractCursor := func( - row sqlc.ListChannelsPaginatedRow) int64 { - - return row.ID - } - - collectID := func( - row sqlc.ListChannelsPaginatedRow) (int64, - error) { - - return row.ID, nil - } - - loadChannelFeatures := func(ctx context.Context, - chanIDs []int64) (map[int64][]int, error) { - - return batchLoadChannelFeaturesHelper( - ctx, s.cfg.QueryCfg, db, chanIDs, - ) - } - - return sqldb.ExecuteCollectAndBatchWithSharedDataQuery( - ctx, s.cfg.QueryCfg, int64(-1), queryFunc, - extractCursor, collectID, loadChannelFeatures, - handleChannel, + pkScript, err := genMultiSigP2WSH( + channel.BitcoinKey1, channel.BitcoinKey2, ) - - case gossipV2: - handleChannel := func(_ context.Context, - channel sqlc.ListChannelsPaginatedV2Row) error { - - op, err := wire.NewOutPointFromString( - channel.Outpoint, - ) - if err != nil { - return err - } - - pkScript := channel.FundingPkScript - edgePoints = append(edgePoints, EdgePoint{ - FundingPkScript: pkScript, - OutPoint: *op, - }) - - return nil + if err != nil { + return err } - queryFunc := func(ctx context.Context, lastID int64, - limit int32) ([]sqlc.ListChannelsPaginatedV2Row, - error) { - - return db.ListChannelsPaginatedV2( - ctx, sqlc.ListChannelsPaginatedV2Params{ - ID: lastID, - Limit: limit, - }, - ) + op, err := wire.NewOutPointFromString(channel.Outpoint) + if err != nil { + return err } - extractCursor := func( - row sqlc.ListChannelsPaginatedV2Row) int64 { + edgePoints = append(edgePoints, EdgePoint{ + FundingPkScript: pkScript, + OutPoint: *op, + }) - return row.ID - } - - return sqldb.ExecutePaginatedQuery( - ctx, s.cfg.QueryCfg, int64(-1), queryFunc, - extractCursor, handleChannel, - ) - - default: - return fmt.Errorf("unsupported gossip version: %d", v) + return nil } + + queryFunc := func(ctx context.Context, lastID int64, + limit int32) ([]sqlc.ListChannelsPaginatedRow, error) { + + return db.ListChannelsPaginated( + ctx, sqlc.ListChannelsPaginatedParams{ + Version: int16(ProtocolV1), + ID: lastID, + Limit: limit, + }, + ) + } + + extractCursor := func(row sqlc.ListChannelsPaginatedRow) int64 { + return row.ID + } + + return sqldb.ExecutePaginatedQuery( + ctx, s.cfg.QueryCfg, int64(-1), queryFunc, + extractCursor, handleChannel, + ) }, func() { edgePoints = nil }) @@ -3583,11 +2786,10 @@ func (s *SQLStore) ChannelView(ctx context.Context, // to tell if the graph is currently in sync with the current best known UTXO // state. // -// NOTE: part of the Store interface. -func (s *SQLStore) PruneTip(ctx context.Context) (*chainhash.Hash, uint32, - error) { - +// NOTE: part of the V1Store interface. +func (s *SQLStore) PruneTip() (*chainhash.Hash, uint32, error) { var ( + ctx = context.TODO() tipHash chainhash.Hash tipHeight uint32 ) @@ -3646,9 +2848,11 @@ func (s *SQLStore) pruneGraphNodes(ctx context.Context, // Channels that were removed from the graph resulting from the // disconnected block are returned. // -// NOTE: part of the Store interface. -func (s *SQLStore) DisconnectBlockAtHeight(ctx context.Context, - height uint32) ([]*models.ChannelEdgeInfo, error) { +// NOTE: part of the V1Store interface. +func (s *SQLStore) DisconnectBlockAtHeight(height uint32) ( + []*models.ChannelEdgeInfo, error) { + + ctx := context.TODO() var ( // Every channel having a ShortChannelID starting at 'height' @@ -3722,8 +2926,8 @@ func (s *SQLStore) DisconnectBlockAtHeight(ctx context.Context, s.cacheMu.Lock() for _, channel := range removedChans { - s.rejectCache.remove(channel.Version, channel.ChannelID) - s.chanCache.remove(channel.Version, channel.ChannelID) + s.rejectCache.remove(channel.ChannelID) + s.chanCache.remove(channel.ChannelID) } s.cacheMu.Unlock() @@ -3732,48 +2936,25 @@ func (s *SQLStore) DisconnectBlockAtHeight(ctx context.Context, // AddEdgeProof sets the proof of an existing edge in the graph database. // -// NOTE: part of the Store interface. -func (s *SQLStore) AddEdgeProof(ctx context.Context, - scid lnwire.ShortChannelID, proof *models.ChannelAuthProof) error { - - if !isKnownGossipVersion(proof.Version) { - return fmt.Errorf("unsupported gossip version: %d", - proof.Version) - } +// NOTE: part of the V1Store interface. +func (s *SQLStore) AddEdgeProof(scid lnwire.ShortChannelID, + proof *models.ChannelAuthProof) error { var ( + ctx = context.TODO() scidBytes = channelIDToBytes(scid.ToUint64()) ) err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { - var ( - res sql.Result - err error + res, err := db.AddV1ChannelProof( + ctx, sqlc.AddV1ChannelProofParams{ + Scid: scidBytes, + Node1Signature: proof.NodeSig1Bytes, + Node2Signature: proof.NodeSig2Bytes, + Bitcoin1Signature: proof.BitcoinSig1Bytes, + Bitcoin2Signature: proof.BitcoinSig2Bytes, + }, ) - switch proof.Version { - case gossipV1: - res, err = db.AddV1ChannelProof( - ctx, sqlc.AddV1ChannelProofParams{ - Scid: scidBytes, - Node1Signature: proof.NodeSig1(), - Node2Signature: proof.NodeSig2(), - Bitcoin1Signature: proof.BitcoinSig1(), - Bitcoin2Signature: proof.BitcoinSig2(), - }, - ) - - case gossipV2: - res, err = db.AddV2ChannelProof( - ctx, sqlc.AddV2ChannelProofParams{ - Scid: scidBytes, - Signature: proof.Sig(), - }, - ) - - default: - return fmt.Errorf("unsupported gossip version: %d", - proof.Version) - } if err != nil { return fmt.Errorf("unable to add edge proof: %w", err) } @@ -3805,11 +2986,10 @@ func (s *SQLStore) AddEdgeProof(ctx context.Context, // that we can ignore channel announcements that we know to be closed without // having to validate them and fetch a block. // -// NOTE: part of the Store interface. -func (s *SQLStore) PutClosedScid(ctx context.Context, - scid lnwire.ShortChannelID) error { - +// NOTE: part of the V1Store interface. +func (s *SQLStore) PutClosedScid(scid lnwire.ShortChannelID) error { var ( + ctx = context.TODO() chanIDB = channelIDToBytes(scid.ToUint64()) ) @@ -3821,11 +3001,10 @@ func (s *SQLStore) PutClosedScid(ctx context.Context, // IsClosedScid checks whether a channel identified by the passed in scid is // closed. This helps avoid having to perform expensive validation checks. // -// NOTE: part of the Store interface. -func (s *SQLStore) IsClosedScid(ctx context.Context, - scid lnwire.ShortChannelID) (bool, error) { - +// NOTE: part of the V1Store interface. +func (s *SQLStore) IsClosedScid(scid lnwire.ShortChannelID) (bool, error) { var ( + ctx = context.TODO() isClosed bool chanIDB = channelIDToBytes(scid.ToUint64()) ) @@ -3850,9 +3029,11 @@ func (s *SQLStore) IsClosedScid(ctx context.Context, // GraphSession will provide the call-back with access to a NodeTraverser // instance which can be used to perform queries against the channel graph. // -// NOTE: part of the Store interface. -func (s *SQLStore) GraphSession(ctx context.Context, - cb func(graph NodeTraverser) error, reset func()) error { +// NOTE: part of the V1Store interface. +func (s *SQLStore) GraphSession(cb func(graph NodeTraverser) error, + reset func()) error { + + var ctx = context.TODO() return s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { return cb(newSQLNodeTraverser(db, s.cfg.ChainHash)) @@ -3884,24 +3065,24 @@ func newSQLNodeTraverser(db SQLQueries, // node. // // NOTE: Part of the NodeTraverser interface. -func (s *sqlNodeTraverser) ForEachNodeDirectedChannel( - ctx context.Context, nodePub route.Vertex, +func (s *sqlNodeTraverser) ForEachNodeDirectedChannel(nodePub route.Vertex, cb func(channel *DirectedChannel) error, _ func()) error { - return forEachNodeDirectedChannel( - ctx, s.db, lnwire.GossipVersion1, nodePub, cb, - ) + ctx := context.TODO() + + return forEachNodeDirectedChannel(ctx, s.db, nodePub, cb) } // FetchNodeFeatures returns the features of the given node. If the node is // unknown, assume no additional features are supported. // // NOTE: Part of the NodeTraverser interface. -func (s *sqlNodeTraverser) FetchNodeFeatures(ctx context.Context, - nodePub route.Vertex) ( +func (s *sqlNodeTraverser) FetchNodeFeatures(nodePub route.Vertex) ( *lnwire.FeatureVector, error) { - return fetchNodeFeatures(ctx, s.db, lnwire.GossipVersion1, nodePub) + ctx := context.TODO() + + return fetchNodeFeatures(ctx, s.db, nodePub) } // forEachNodeDirectedChannel iterates through all channels of a given @@ -3909,8 +3090,7 @@ func (s *sqlNodeTraverser) FetchNodeFeatures(ctx context.Context, // channel and its incoming policy. If the node is not found, no error is // returned. func forEachNodeDirectedChannel(ctx context.Context, db SQLQueries, - v lnwire.GossipVersion, nodePub route.Vertex, - cb func(channel *DirectedChannel) error) error { + nodePub route.Vertex, cb func(channel *DirectedChannel) error) error { toNodeCallback := func() route.Vertex { return nodePub @@ -3918,7 +3098,7 @@ func forEachNodeDirectedChannel(ctx context.Context, db SQLQueries, dbID, err := db.GetNodeIDByPubKey( ctx, sqlc.GetNodeIDByPubKeyParams{ - Version: int16(v), + Version: int16(ProtocolV1), PubKey: nodePub[:], }, ) @@ -3930,7 +3110,7 @@ func forEachNodeDirectedChannel(ctx context.Context, db SQLQueries, rows, err := db.ListChannelsByNodeID( ctx, sqlc.ListChannelsByNodeIDParams{ - Version: int16(v), + Version: int16(ProtocolV1), NodeID1: dbID, }, ) @@ -4017,12 +3197,11 @@ func forEachNodeDirectedChannel(ctx context.Context, db SQLQueries, return nil } -// forEachNodeCacheable fetches all node IDs and pub keys from the database, +// forEachNodeCacheable fetches all V1 node IDs and pub keys from the database, // and executes the provided callback for each node. It does so via pagination // along with batch loading of the node feature bits. func forEachNodeCacheable(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, v lnwire.GossipVersion, - processNode func(nodeID int64, nodePub route.Vertex, + db SQLQueries, processNode func(nodeID int64, nodePub route.Vertex, features *lnwire.FeatureVector) error) error { handleNode := func(_ context.Context, @@ -4047,7 +3226,7 @@ func forEachNodeCacheable(ctx context.Context, cfg *sqldb.QueryConfig, return db.ListNodeIDsAndPubKeys( ctx, sqlc.ListNodeIDsAndPubKeysParams{ - Version: int16(v), + Version: int16(ProtocolV1), ID: lastID, Limit: limit, }, @@ -4079,15 +3258,14 @@ func forEachNodeCacheable(ctx context.Context, cfg *sqldb.QueryConfig, // edge information, the outgoing policy and the incoming policy for the // channel and node combo. func forEachNodeChannel(ctx context.Context, db SQLQueries, - cfg *SQLStoreConfig, v lnwire.GossipVersion, id int64, - cb func(*models.ChannelEdgeInfo, + cfg *SQLStoreConfig, id int64, cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error) error { - // Get all the channels for this node. + // Get all the V1 channels for this node. rows, err := db.ListChannelsByNodeID( ctx, sqlc.ListChannelsByNodeIDParams{ - Version: int16(v), + Version: int16(ProtocolV1), NodeID1: id, }, ) @@ -4178,16 +3356,10 @@ func updateChanEdgePolicy(ctx context.Context, tx SQLQueries, var ( node1Pub, node2Pub route.Vertex + isNode1 bool chanIDB = channelIDToBytes(edge.ChannelID) - version = edge.Version ) - if !isKnownGossipVersion(version) { - return node1Pub, node2Pub, false, fmt.Errorf( - "unsupported gossip version: %d", version, - ) - } - // Check that this edge policy refers to a channel that we already // know of. We do this explicitly so that we can return the appropriate // ErrEdgeNotFound error if the channel doesn't exist, rather than @@ -4195,7 +3367,7 @@ func updateChanEdgePolicy(ctx context.Context, tx SQLQueries, dbChan, err := tx.GetChannelAndNodesBySCID( ctx, sqlc.GetChannelAndNodesBySCIDParams{ Scid: chanIDB, - Version: int16(version), + Version: int16(ProtocolV1), }, ) if errors.Is(err, sql.ErrNoRows) { @@ -4209,7 +3381,7 @@ func updateChanEdgePolicy(ctx context.Context, tx SQLQueries, copy(node2Pub[:], dbChan.Node2PubKey) // Figure out which node this edge is from. - isNode1 := edge.IsNode1() + isNode1 = edge.ChannelFlags&lnwire.ChanUpdateDirection == 0 nodeID := dbChan.NodeID1 if !isNode1 { nodeID = dbChan.NodeID2 @@ -4224,41 +3396,29 @@ func updateChanEdgePolicy(ctx context.Context, tx SQLQueries, inboundBase = sqldb.SQLInt64(fee.BaseFee) }) - params := sqlc.UpsertEdgePolicyParams{ - Version: int16(version), - ChannelID: dbChan.ID, - NodeID: nodeID, - Timelock: int32(edge.TimeLockDelta), - FeePpm: int64(edge.FeeProportionalMillionths), - BaseFeeMsat: int64(edge.FeeBaseMSat), - MinHtlcMsat: int64(edge.MinHTLC), + id, err := tx.UpsertEdgePolicy(ctx, sqlc.UpsertEdgePolicyParams{ + Version: int16(ProtocolV1), + ChannelID: dbChan.ID, + NodeID: nodeID, + Timelock: int32(edge.TimeLockDelta), + FeePpm: int64(edge.FeeProportionalMillionths), + BaseFeeMsat: int64(edge.FeeBaseMSat), + MinHtlcMsat: int64(edge.MinHTLC), + LastUpdate: sqldb.SQLInt64(edge.LastUpdate.Unix()), + Disabled: sql.NullBool{ + Valid: true, + Bool: edge.IsDisabled(), + }, + MaxHtlcMsat: sql.NullInt64{ + Valid: edge.MessageFlags.HasMaxHtlc(), + Int64: int64(edge.MaxHTLC), + }, MessageFlags: sqldb.SQLInt16(edge.MessageFlags), ChannelFlags: sqldb.SQLInt16(edge.ChannelFlags), InboundBaseFeeMsat: inboundBase, InboundFeeRateMilliMsat: inboundRate, Signature: edge.SigBytes, - } - - switch version { - case gossipV1: - params.LastUpdate = sqldb.SQLInt64(edge.LastUpdate.Unix()) - params.Disabled = sql.NullBool{ - Valid: true, - Bool: edge.IsDisabled(), - } - params.MaxHtlcMsat = sql.NullInt64{ - Valid: edge.MessageFlags.HasMaxHtlc(), - Int64: int64(edge.MaxHTLC), - } - case gossipV2: - params.BlockHeight = sqldb.SQLInt64( - int64(edge.LastBlockHeight), - ) - params.DisableFlags = sqldb.SQLInt16(edge.DisableFlags) - params.MaxHtlcMsat = sqldb.SQLInt64(int64(edge.MaxHTLC)) - } - - id, err := tx.UpsertEdgePolicy(ctx, params) + }) if err != nil { return node1Pub, node2Pub, isNode1, fmt.Errorf("unable to upsert edge policy: %w", err) @@ -4266,14 +3426,10 @@ func updateChanEdgePolicy(ctx context.Context, tx SQLQueries, // Convert the flat extra opaque data into a map of TLV types to // values. - extra := edge.ExtraSignedFields - if version == gossipV1 { - extra, err = marshalExtraOpaqueData(edge.ExtraOpaqueData) - if err != nil { - return node1Pub, node2Pub, false, fmt.Errorf( - "unable to marshal extra opaque data: %w", err, - ) - } + extra, err := marshalExtraOpaqueData(edge.ExtraOpaqueData) + if err != nil { + return node1Pub, node2Pub, false, fmt.Errorf("unable to "+ + "marshal extra opaque data: %w", err) } // Update the channel policy's extra signed fields. @@ -4288,12 +3444,11 @@ func updateChanEdgePolicy(ctx context.Context, tx SQLQueries, // getNodeByPubKey attempts to look up a target node by its public key. func getNodeByPubKey(ctx context.Context, cfg *sqldb.QueryConfig, db SQLQueries, - v lnwire.GossipVersion, pubKey route.Vertex) (int64, *models.Node, - error) { + pubKey route.Vertex) (int64, *models.Node, error) { dbNode, err := db.GetNodeByPubKey( ctx, sqlc.GetNodeByPubKeyParams{ - Version: int16(v), + Version: int16(ProtocolV1), PubKey: pubKey[:], }, ) @@ -4339,19 +3494,6 @@ func buildNode(ctx context.Context, cfg *sqldb.QueryConfig, db SQLQueries, return buildNodeWithBatchData(dbNode, data) } -// isKnownGossipVersion checks whether the provided gossip version is known -// and supported. -func isKnownGossipVersion(v lnwire.GossipVersion) bool { - switch v { - case gossipV1: - return true - case gossipV2: - return true - default: - return false - } -} - // buildNodeWithBatchData builds a models.Node instance // from the provided sqlc.GraphNode and batchNodeData. If the node does have // features/addresses/extra fields, then the corresponding fields are expected @@ -4359,43 +3501,36 @@ func isKnownGossipVersion(v lnwire.GossipVersion) bool { func buildNodeWithBatchData(dbNode sqlc.GraphNode, batchData *batchNodeData) (*models.Node, error) { - v := lnwire.GossipVersion(dbNode.Version) - - if !isKnownGossipVersion(v) { - return nil, fmt.Errorf("unknown node version: %d", v) + if dbNode.Version != int16(ProtocolV1) { + return nil, fmt.Errorf("unsupported node version: %d", + dbNode.Version) } - pub, err := route.NewVertexFromBytes(dbNode.PubKey) - if err != nil { - return nil, fmt.Errorf("unable to parse pubkey: %w", err) - } + var pub [33]byte + copy(pub[:], dbNode.PubKey) - node := models.NewShellNode(v, pub) + node := &models.Node{ + PubKeyBytes: pub, + Features: lnwire.EmptyFeatureVector(), + LastUpdate: time.Unix(0, 0), + } if len(dbNode.Signature) == 0 { return node, nil } + node.HaveNodeAnnouncement = true node.AuthSigBytes = dbNode.Signature + node.Alias = dbNode.Alias.String + node.LastUpdate = time.Unix(dbNode.LastUpdate.Int64, 0) - if dbNode.Alias.Valid { - node.Alias = fn.Some(dbNode.Alias.String) - } - if dbNode.LastUpdate.Valid { - node.LastUpdate = time.Unix(dbNode.LastUpdate.Int64, 0) - } - if dbNode.BlockHeight.Valid { - node.LastBlockHeight = uint32(dbNode.BlockHeight.Int64) - } - + var err error if dbNode.Color.Valid { - nodeColor, err := DecodeHexColor(dbNode.Color.String) + node.Color, err = DecodeHexColor(dbNode.Color.String) if err != nil { return nil, fmt.Errorf("unable to decode color: %w", err) } - - node.Color = fn.Some(nodeColor) } // Use preloaded features. @@ -4419,19 +3554,13 @@ func buildNodeWithBatchData(dbNode sqlc.GraphNode, // Use preloaded extra fields. if extraFields, exists := batchData.extraFields[dbNode.ID]; exists { - if v == gossipV1 { - records := lnwire.CustomRecords(extraFields) - recs, err := records.Serialize() - if err != nil { - return nil, fmt.Errorf("unable to serialize "+ - "extra signed fields: %w", err) - } - - if len(recs) != 0 { - node.ExtraOpaqueData = recs - } - } else if len(extraFields) > 0 { - node.ExtraSignedFields = extraFields + recs, err := lnwire.CustomRecords(extraFields).Serialize() + if err != nil { + return nil, fmt.Errorf("unable to serialize extra "+ + "signed fields: %w", err) + } + if len(recs) != 0 { + node.ExtraOpaqueData = recs } } @@ -4511,13 +3640,10 @@ func upsertNodeAncillaryData(ctx context.Context, db SQLQueries, // Convert the flat extra opaque data into a map of TLV types to // values. - extra := node.ExtraSignedFields - if node.Version == gossipV1 { - extra, err = marshalExtraOpaqueData(node.ExtraOpaqueData) - if err != nil { - return fmt.Errorf("unable to marshal extra opaque "+ - "data: %w", err) - } + extra, err := marshalExtraOpaqueData(node.ExtraOpaqueData) + if err != nil { + return fmt.Errorf("unable to marshal extra opaque data: %w", + err) } // Update the node's extra signed fields. @@ -4532,81 +3658,57 @@ func upsertNodeAncillaryData(ctx context.Context, db SQLQueries, // populateNodeParams populates the common node parameters from a models.Node. // This is a helper for building UpsertNodeParams and UpsertSourceNodeParams. func populateNodeParams(node *models.Node, - setParams func(lastUpdate, lastBlockHeight sql.NullInt64, alias, - colorStr sql.NullString, signature []byte)) error { + setParams func(lastUpdate sql.NullInt64, alias, + colorStr sql.NullString, signature []byte)) { - if !node.HaveAnnouncement() { - return nil + if !node.HaveNodeAnnouncement { + return } - var ( - alias, colorStr sql.NullString - lastUpdate, lastBlockHeight sql.NullInt64 - ) - node.Color.WhenSome(func(rgba color.RGBA) { - colorStr = sqldb.SQLStrValid(EncodeHexColor(rgba)) - }) - node.Alias.WhenSome(func(s string) { - alias = sqldb.SQLStrValid(s) - }) + lastUpdate := sqldb.SQLInt64(node.LastUpdate.Unix()) + alias := sqldb.SQLStrValid(node.Alias) + colorStr := sqldb.SQLStrValid(EncodeHexColor(node.Color)) - switch node.Version { - case gossipV1: - lastUpdate = sqldb.SQLInt64(node.LastUpdate.Unix()) - - case gossipV2: - lastBlockHeight = sqldb.SQLInt64(int64(node.LastBlockHeight)) - - default: - return fmt.Errorf("unknown gossip version: %d", node.Version) - } - - setParams( - lastUpdate, lastBlockHeight, alias, colorStr, node.AuthSigBytes, - ) - - return nil + setParams(lastUpdate, alias, colorStr, node.AuthSigBytes) } // buildNodeUpsertParams builds the parameters for upserting a node using the // strict UpsertNode query (requires timestamp to be increasing). -func buildNodeUpsertParams(node *models.Node) (sqlc.UpsertNodeParams, error) { +func buildNodeUpsertParams(node *models.Node) sqlc.UpsertNodeParams { params := sqlc.UpsertNodeParams{ - Version: int16(node.Version), + Version: int16(ProtocolV1), PubKey: node.PubKeyBytes[:], } - err := populateNodeParams( - node, func(lastUpdate, lastBlockHeight sql.NullInt64, alias, + populateNodeParams( + node, func(lastUpdate sql.NullInt64, alias, colorStr sql.NullString, signature []byte) { params.LastUpdate = lastUpdate - params.BlockHeight = lastBlockHeight params.Alias = alias params.Color = colorStr params.Signature = signature }, ) - return params, err + return params } // buildSourceNodeUpsertParams builds the parameters for upserting the source // node using the lenient UpsertSourceNode query (allows same timestamp). -func buildSourceNodeUpsertParams(node *models.Node) ( - sqlc.UpsertSourceNodeParams, error) { +func buildSourceNodeUpsertParams( + node *models.Node) sqlc.UpsertSourceNodeParams { params := sqlc.UpsertSourceNodeParams{ - Version: int16(node.Version), + Version: int16(ProtocolV1), PubKey: node.PubKeyBytes[:], } - err := populateNodeParams( - node, func(lastUpdate, lastBlock sql.NullInt64, alias, + populateNodeParams( + node, func(lastUpdate sql.NullInt64, alias, colorStr sql.NullString, signature []byte) { - params.BlockHeight = lastBlock params.LastUpdate = lastUpdate params.Alias = alias params.Color = colorStr @@ -4614,7 +3716,7 @@ func buildSourceNodeUpsertParams(node *models.Node) ( }, ) - return params, err + return params } // upsertSourceNode upserts the source node record into the database using a @@ -4625,10 +3727,7 @@ func buildSourceNodeUpsertParams(node *models.Node) ( func upsertSourceNode(ctx context.Context, db SQLQueries, node *models.Node) (int64, error) { - params, err := buildSourceNodeUpsertParams(node) - if err != nil { - return 0, err - } + params := buildSourceNodeUpsertParams(node) nodeID, err := db.UpsertSourceNode(ctx, params) if err != nil { @@ -4637,7 +3736,7 @@ func upsertSourceNode(ctx context.Context, db SQLQueries, } // We can exit here if we don't have the announcement yet. - if !node.HaveAnnouncement() { + if !node.HaveNodeAnnouncement { return nodeID, nil } @@ -4657,14 +3756,7 @@ func upsertSourceNode(ctx context.Context, db SQLQueries, func upsertNode(ctx context.Context, db SQLQueries, node *models.Node) (int64, error) { - if !isKnownGossipVersion(node.Version) { - return 0, fmt.Errorf("unknown gossip version: %d", node.Version) - } - - params, err := buildNodeUpsertParams(node) - if err != nil { - return 0, err - } + params := buildNodeUpsertParams(node) nodeID, err := db.UpsertNode(ctx, params) if err != nil { @@ -4673,7 +3765,7 @@ func upsertNode(ctx context.Context, db SQLQueries, } // We can exit here if we don't have the announcement yet. - if !node.HaveAnnouncement() { + if !node.HaveNodeAnnouncement { return nodeID, nil } @@ -4749,13 +3841,12 @@ func upsertNodeFeatures(ctx context.Context, db SQLQueries, nodeID int64, // fetchNodeFeatures fetches the features for a node with the given public key. func fetchNodeFeatures(ctx context.Context, queries SQLQueries, - v lnwire.GossipVersion, nodePub route.Vertex) (*lnwire.FeatureVector, - error) { + nodePub route.Vertex) (*lnwire.FeatureVector, error) { rows, err := queries.GetNodeFeaturesByPubKey( ctx, sqlc.GetNodeFeaturesByPubKeyParams{ PubKey: nodePub[:], - Version: int16(v), + Version: int16(ProtocolV1), }, ) if err != nil { @@ -4791,8 +3882,7 @@ const ( func collectAddressRecords(addresses []net.Addr) (map[dbAddressType][]string, error) { - // Copy the nodes latest set of addresses. v2 is stored for wire - // fidelity even though lnd no longer produces it. + // Copy the nodes latest set of addresses. newAddresses := map[dbAddressType][]string{ addressTypeIPv4: {}, addressTypeIPv6: {}, @@ -4991,7 +4081,7 @@ type srcNodeInfo struct { // sourceNode returns the DB node ID and pub key of the source node for the // specified protocol version. func (s *SQLStore) getSourceNode(ctx context.Context, db SQLQueries, - version lnwire.GossipVersion) (int64, route.Vertex, error) { + version ProtocolVersion) (int64, route.Vertex, error) { s.srcNodeMu.Lock() defer s.srcNodeMu.Unlock() @@ -5060,16 +4150,14 @@ func marshalExtraOpaqueData(data []byte) (map[uint64][]byte, error) { func insertChannel(ctx context.Context, db SQLQueries, edge *models.ChannelEdgeInfo) error { - v := edge.Version - // Make sure that at least a "shell" entry for each node is present in // the nodes table. - node1DBID, err := maybeCreateShellNode(ctx, db, v, edge.NodeKey1Bytes) + node1DBID, err := maybeCreateShellNode(ctx, db, edge.NodeKey1Bytes) if err != nil { return fmt.Errorf("unable to create shell node: %w", err) } - node2DBID, err := maybeCreateShellNode(ctx, db, v, edge.NodeKey2Bytes) + node2DBID, err := maybeCreateShellNode(ctx, db, edge.NodeKey2Bytes) if err != nil { return fmt.Errorf("unable to create shell node: %w", err) } @@ -5080,34 +4168,23 @@ func insertChannel(ctx context.Context, db SQLQueries, } createParams := sqlc.CreateChannelParams{ - Version: int16(v), - Scid: channelIDToBytes(edge.ChannelID), - NodeID1: node1DBID, - NodeID2: node2DBID, - Outpoint: edge.ChannelPoint.String(), - Capacity: capacity, + Version: int16(ProtocolV1), + Scid: channelIDToBytes(edge.ChannelID), + NodeID1: node1DBID, + NodeID2: node2DBID, + Outpoint: edge.ChannelPoint.String(), + Capacity: capacity, + BitcoinKey1: edge.BitcoinKey1Bytes[:], + BitcoinKey2: edge.BitcoinKey2Bytes[:], } - edge.BitcoinKey1Bytes.WhenSome(func(vertex route.Vertex) { - createParams.BitcoinKey1 = vertex[:] - }) - edge.BitcoinKey2Bytes.WhenSome(func(vertex route.Vertex) { - createParams.BitcoinKey2 = vertex[:] - }) - edge.FundingScript.WhenSome(func(script []byte) { - createParams.FundingPkScript = script - }) - edge.MerkleRootHash.WhenSome(func(hash chainhash.Hash) { - createParams.MerkleRootHash = hash[:] - }) if edge.AuthProof != nil { proof := edge.AuthProof - createParams.Node1Signature = proof.NodeSig1() - createParams.Node2Signature = proof.NodeSig2() - createParams.Bitcoin1Signature = proof.BitcoinSig1() - createParams.Bitcoin2Signature = proof.BitcoinSig2() - createParams.Signature = proof.Sig() + createParams.Node1Signature = proof.NodeSig1Bytes + createParams.Node2Signature = proof.NodeSig2Bytes + createParams.Bitcoin1Signature = proof.BitcoinSig1Bytes + createParams.Bitcoin2Signature = proof.BitcoinSig2Bytes } // Insert the new channel record. @@ -5131,13 +4208,10 @@ func insertChannel(ctx context.Context, db SQLQueries, } // Finally, insert any extra TLV fields in the channel announcement. - extra := edge.ExtraSignedFields - if v == gossipV1 { - extra, err = marshalExtraOpaqueData(edge.ExtraOpaqueData) - if err != nil { - return fmt.Errorf("unable to marshal extra opaque "+ - "data: %w", err) - } + extra, err := marshalExtraOpaqueData(edge.ExtraOpaqueData) + if err != nil { + return fmt.Errorf("unable to marshal extra opaque data: %w", + err) } for tlvType, value := range extra { @@ -5163,12 +4237,12 @@ func insertChannel(ctx context.Context, db SQLQueries, // created. The ID of the node is returned. A shell node only has a protocol // version and public key persisted. func maybeCreateShellNode(ctx context.Context, db SQLQueries, - v lnwire.GossipVersion, pubKey route.Vertex) (int64, error) { + pubKey route.Vertex) (int64, error) { dbNode, err := db.GetNodeByPubKey( ctx, sqlc.GetNodeByPubKeyParams{ PubKey: pubKey[:], - Version: int16(v), + Version: int16(ProtocolV1), }, ) // The node exists. Return the ID. @@ -5181,7 +4255,7 @@ func maybeCreateShellNode(ctx context.Context, db SQLQueries, // Otherwise, the node does not exist, so we create a shell entry for // it. id, err := db.UpsertNode(ctx, sqlc.UpsertNodeParams{ - Version: int16(v), + Version: int16(ProtocolV1), PubKey: pubKey[:], }) if err != nil { @@ -5249,9 +4323,9 @@ func buildEdgeInfoWithBatchData(chain chainhash.Hash, dbChan sqlc.GraphChannel, node1, node2 route.Vertex, batchData *batchChannelData) (*models.ChannelEdgeInfo, error) { - v := lnwire.GossipVersion(dbChan.Version) - if !isKnownGossipVersion(v) { - return nil, fmt.Errorf("unknown channel version: %d", v) + if dbChan.Version != int16(ProtocolV1) { + return nil, fmt.Errorf("unsupported channel version: %d", + dbChan.Version) } // Use pre-loaded features and extras types. @@ -5275,120 +4349,42 @@ func buildEdgeInfoWithBatchData(chain chainhash.Hash, return nil, err } - // Build the appropriate channel based on version. - var channel *models.ChannelEdgeInfo - switch v { - case gossipV1: - // For v1, serialize extras into ExtraOpaqueData. - recs, err := lnwire.CustomRecords(extras).Serialize() - if err != nil { - return nil, fmt.Errorf("unable to serialize extra "+ - "signed fields: %w", err) - } - if recs == nil { - recs = make([]byte, 0) - } + recs, err := lnwire.CustomRecords(extras).Serialize() + if err != nil { + return nil, fmt.Errorf("unable to serialize extra signed "+ + "fields: %w", err) + } + if recs == nil { + recs = make([]byte, 0) + } - // Bitcoin keys are required for v1. - btcKey1, err := route.NewVertexFromBytes(dbChan.BitcoinKey1) - if err != nil { - return nil, err - } - btcKey2, err := route.NewVertexFromBytes(dbChan.BitcoinKey2) - if err != nil { - return nil, err - } + var btcKey1, btcKey2 route.Vertex + copy(btcKey1[:], dbChan.BitcoinKey1) + copy(btcKey2[:], dbChan.BitcoinKey2) - channel, err = models.NewV1Channel( - byteOrder.Uint64(dbChan.Scid), chain, node1, node2, - &models.ChannelV1Fields{ - BitcoinKey1Bytes: btcKey1, - BitcoinKey2Bytes: btcKey2, - ExtraOpaqueData: recs, - }, - models.WithChannelPoint(*op), - models.WithCapacity( - btcutil.Amount(dbChan.Capacity.Int64), - ), - models.WithFeatures(fv.RawFeatureVector), - ) - if err != nil { - return nil, err - } + channel := &models.ChannelEdgeInfo{ + ChainHash: chain, + ChannelID: byteOrder.Uint64(dbChan.Scid), + NodeKey1Bytes: node1, + NodeKey2Bytes: node2, + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + ChannelPoint: *op, + Capacity: btcutil.Amount(dbChan.Capacity.Int64), + Features: fv, + ExtraOpaqueData: recs, + } - // For v1 channels, attach the auth proof if all four - // signatures are present. - if len(dbChan.Bitcoin1Signature) > 0 { - channel.AuthProof = models.NewV1ChannelAuthProof( - dbChan.Node1Signature, - dbChan.Node2Signature, - dbChan.Bitcoin1Signature, - dbChan.Bitcoin2Signature, - ) + // We always set all the signatures at the same time, so we can + // safely check if one signature is present to determine if we have the + // rest of the signatures for the auth proof. + if len(dbChan.Bitcoin1Signature) > 0 { + channel.AuthProof = &models.ChannelAuthProof{ + NodeSig1Bytes: dbChan.Node1Signature, + NodeSig2Bytes: dbChan.Node2Signature, + BitcoinSig1Bytes: dbChan.Bitcoin1Signature, + BitcoinSig2Bytes: dbChan.Bitcoin2Signature, } - - case gossipV2: - v2Fields := &models.ChannelV2Fields{ - ExtraSignedFields: extras, - } - - // For v2, bitcoin keys are optional. - if len(dbChan.BitcoinKey1) > 0 { - btcKey1, err := route.NewVertexFromBytes( - dbChan.BitcoinKey1, - ) - if err != nil { - return nil, err - } - v2Fields.BitcoinKey1Bytes = fn.Some(btcKey1) - } - if len(dbChan.BitcoinKey2) > 0 { - btcKey2, err := route.NewVertexFromBytes( - dbChan.BitcoinKey2, - ) - if err != nil { - return nil, err - } - v2Fields.BitcoinKey2Bytes = fn.Some(btcKey2) - } - - // Parse funding script if present. - if len(dbChan.FundingPkScript) > 0 { - v2Fields.FundingScript = fn.Some(dbChan.FundingPkScript) - } - - // Parse merkle root hash if present. - if len(dbChan.MerkleRootHash) > 0 { - var hash chainhash.Hash - copy(hash[:], dbChan.MerkleRootHash) - v2Fields.MerkleRootHash = fn.Some(hash) - } - - opts := []models.EdgeModifier{ - models.WithChannelPoint(*op), - models.WithCapacity(btcutil.Amount( - dbChan.Capacity.Int64, - )), - models.WithFeatures(fv.RawFeatureVector), - } - - // For v2 channels, attach the auth proof if the signature is - // present. - if len(dbChan.Signature) > 0 { - proof := models.NewV2ChannelAuthProof(dbChan.Signature) - opts = append(opts, models.WithChanProof(proof)) - } - - channel, err = models.NewV2Channel( - byteOrder.Uint64(dbChan.Scid), chain, node1, node2, - v2Fields, opts..., - ) - if err != nil { - return nil, err - } - - default: - return nil, fmt.Errorf("unsupported channel version: %d", v) } return channel, nil @@ -5427,20 +4423,6 @@ func getAndBuildChanPolicies(ctx context.Context, cfg *sqldb.QueryConfig, return nil, nil, nil } - if dbPol1 != nil && - !isKnownGossipVersion(lnwire.GossipVersion(dbPol1.Version)) { - - return nil, nil, fmt.Errorf("unsupported policy1 version: %d", - dbPol1.Version) - } - - if dbPol2 != nil && - !isKnownGossipVersion(lnwire.GossipVersion(dbPol2.Version)) { - - return nil, nil, fmt.Errorf("unsupported policy2 version: %d", - dbPol2.Version) - } - var policyIDs = make([]int64, 0, 2) if dbPol1 != nil { policyIDs = append(policyIDs, dbPol1.ID) @@ -5456,14 +4438,14 @@ func getAndBuildChanPolicies(ctx context.Context, cfg *sqldb.QueryConfig, } pol1, err := buildChanPolicyWithBatchData( - true, dbPol1, channelID, node2, batchData, + dbPol1, channelID, node2, batchData, ) if err != nil { return nil, nil, fmt.Errorf("unable to build policy1: %w", err) } pol2, err := buildChanPolicyWithBatchData( - false, dbPol2, channelID, node1, batchData, + dbPol2, channelID, node1, batchData, ) if err != nil { return nil, nil, fmt.Errorf("unable to build policy2: %w", err) @@ -5481,9 +4463,7 @@ func buildCachedChanPolicies(dbPol1, dbPol2 *sqlc.GraphChannelPolicy, var p1, p2 *models.CachedEdgePolicy if dbPol1 != nil { - policy1, err := buildChanPolicy( - true, *dbPol1, channelID, nil, node2, - ) + policy1, err := buildChanPolicy(*dbPol1, channelID, nil, node2) if err != nil { return nil, nil, err } @@ -5491,9 +4471,7 @@ func buildCachedChanPolicies(dbPol1, dbPol2 *sqlc.GraphChannelPolicy, p1 = models.NewCachedPolicy(policy1) } if dbPol2 != nil { - policy2, err := buildChanPolicy( - false, *dbPol2, channelID, nil, node1, - ) + policy2, err := buildChanPolicy(*dbPol2, channelID, nil, node1) if err != nil { return nil, nil, err } @@ -5506,10 +4484,16 @@ func buildCachedChanPolicies(dbPol1, dbPol2 *sqlc.GraphChannelPolicy, // buildChanPolicy builds a models.ChannelEdgePolicy instance from the // provided sqlc.GraphChannelPolicy and other required information. -func buildChanPolicy(isNode1 bool, dbPolicy sqlc.GraphChannelPolicy, - channelID uint64, extras map[uint64][]byte, +func buildChanPolicy(dbPolicy sqlc.GraphChannelPolicy, channelID uint64, + extras map[uint64][]byte, toNode route.Vertex) (*models.ChannelEdgePolicy, error) { + recs, err := lnwire.CustomRecords(extras).Serialize() + if err != nil { + return nil, fmt.Errorf("unable to serialize extra signed "+ + "fields: %w", err) + } + var inboundFee fn.Option[lnwire.Fee] if dbPolicy.InboundFeeRateMilliMsat.Valid || dbPolicy.InboundBaseFeeMsat.Valid { @@ -5520,11 +4504,18 @@ func buildChanPolicy(isNode1 bool, dbPolicy sqlc.GraphChannelPolicy, }) } - p := &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion(dbPolicy.Version), - SigBytes: dbPolicy.Signature, - ChannelID: channelID, - SecondPeer: !isNode1, + return &models.ChannelEdgePolicy{ + SigBytes: dbPolicy.Signature, + ChannelID: channelID, + LastUpdate: time.Unix( + dbPolicy.LastUpdate.Int64, 0, + ), + MessageFlags: sqldb.ExtractSqlInt16[lnwire.ChanUpdateMsgFlags]( + dbPolicy.MessageFlags, + ), + ChannelFlags: sqldb.ExtractSqlInt16[lnwire.ChanUpdateChanFlags]( + dbPolicy.ChannelFlags, + ), TimeLockDelta: uint16(dbPolicy.Timelock), MinHTLC: lnwire.MilliSatoshi( dbPolicy.MinHtlcMsat, @@ -5538,40 +4529,8 @@ func buildChanPolicy(isNode1 bool, dbPolicy sqlc.GraphChannelPolicy, FeeProportionalMillionths: lnwire.MilliSatoshi(dbPolicy.FeePpm), ToNode: toNode, InboundFee: inboundFee, - } - - if p.Version != gossipV2 { - recs, err := lnwire.CustomRecords(extras).Serialize() - if err != nil { - return nil, fmt.Errorf("unable to serialize extra "+ - "signed fields: %w", err) - } - - p.ExtraOpaqueData = recs - p.LastUpdate = time.Unix(dbPolicy.LastUpdate.Int64, 0) - //nolint:ll - p.MessageFlags = sqldb.ExtractSqlInt16[lnwire.ChanUpdateMsgFlags]( - dbPolicy.MessageFlags, - ) - //nolint:ll - p.ChannelFlags = sqldb.ExtractSqlInt16[lnwire.ChanUpdateChanFlags]( - dbPolicy.ChannelFlags, - ) - } else { - if dbPolicy.BlockHeight.Valid { - p.LastBlockHeight = uint32( - dbPolicy.BlockHeight.Int64, - ) - } - - //nolint:ll - p.DisableFlags = sqldb.ExtractSqlInt16[lnwire.ChanUpdateDisableFlags]( - dbPolicy.DisableFlags, - ) - p.ExtraSignedFields = extras - } - - return p, nil + ExtraOpaqueData: recs, + }, nil } // extractChannelPolicies extracts the sqlc.GraphChannelPolicy records from the give @@ -5588,7 +4547,6 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, case sqlc.ListChannelsWithPoliciesForCachePaginatedRow: if r.Policy1Timelock.Valid { policy1 = &sqlc.GraphChannelPolicy{ - Version: r.Policy1Version.Int16, Timelock: r.Policy1Timelock.Int32, FeePpm: r.Policy1FeePpm.Int64, BaseFeeMsat: r.Policy1BaseFeeMsat.Int64, @@ -5599,13 +4557,10 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, Disabled: r.Policy1Disabled, MessageFlags: r.Policy1MessageFlags, ChannelFlags: r.Policy1ChannelFlags, - BlockHeight: r.Policy1BlockHeight, - DisableFlags: r.Policy1DisableFlags, } } if r.Policy2Timelock.Valid { policy2 = &sqlc.GraphChannelPolicy{ - Version: r.Policy2Version.Int16, Timelock: r.Policy2Timelock.Int32, FeePpm: r.Policy2FeePpm.Int64, BaseFeeMsat: r.Policy2BaseFeeMsat.Int64, @@ -5616,8 +4571,6 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, Disabled: r.Policy2Disabled, MessageFlags: r.Policy2MessageFlags, ChannelFlags: r.Policy2ChannelFlags, - BlockHeight: r.Policy2BlockHeight, - DisableFlags: r.Policy2DisableFlags, } } @@ -5642,8 +4595,6 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy1MessageFlags, ChannelFlags: r.Policy1ChannelFlags, Signature: r.Policy1Signature, - BlockHeight: r.Policy1BlockHeight, - DisableFlags: r.Policy1DisableFlags, } } if r.Policy2ID.Valid { @@ -5664,8 +4615,6 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy2MessageFlags, ChannelFlags: r.Policy2ChannelFlags, Signature: r.Policy2Signature, - BlockHeight: r.Policy2BlockHeight, - DisableFlags: r.Policy2DisableFlags, } } @@ -5690,8 +4639,6 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy1MessageFlags, ChannelFlags: r.Policy1ChannelFlags, Signature: r.Policy1Signature, - BlockHeight: r.Policy1BlockHeight, - DisableFlags: r.Policy1DisableFlags, } } if r.Policy2ID.Valid { @@ -5712,8 +4659,6 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy2MessageFlags, ChannelFlags: r.Policy2ChannelFlags, Signature: r.Policy2Signature, - BlockHeight: r.Policy2BlockHeight, - DisableFlags: r.Policy2DisableFlags, } } @@ -5738,8 +4683,6 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy1MessageFlags, ChannelFlags: r.Policy1ChannelFlags, Signature: r.Policy1Signature, - BlockHeight: r.Policy1BlockHeight, - DisableFlags: r.Policy1DisableFlags, } } if r.Policy2ID.Valid { @@ -5760,8 +4703,6 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy2MessageFlags, ChannelFlags: r.Policy2ChannelFlags, Signature: r.Policy2Signature, - BlockHeight: r.Policy2BlockHeight, - DisableFlags: r.Policy2DisableFlags, } } @@ -5786,8 +4727,6 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy1MessageFlags, ChannelFlags: r.Policy1ChannelFlags, Signature: r.Policy1Signature, - BlockHeight: r.Policy1BlockHeight, - DisableFlags: r.Policy1DisableFlags, } } if r.Policy2ID.Valid { @@ -5808,56 +4747,6 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy2MessageFlags, ChannelFlags: r.Policy2ChannelFlags, Signature: r.Policy2Signature, - BlockHeight: r.Policy2BlockHeight, - DisableFlags: r.Policy2DisableFlags, - } - } - - return policy1, policy2, nil - - case sqlc.GetChannelsByPolicyBlockRangeRow: - if r.Policy1ID.Valid { - policy1 = &sqlc.GraphChannelPolicy{ - ID: r.Policy1ID.Int64, - Version: r.Policy1Version.Int16, - ChannelID: r.GraphChannel.ID, - NodeID: r.Policy1NodeID.Int64, - Timelock: r.Policy1Timelock.Int32, - FeePpm: r.Policy1FeePpm.Int64, - BaseFeeMsat: r.Policy1BaseFeeMsat.Int64, - MinHtlcMsat: r.Policy1MinHtlcMsat.Int64, - MaxHtlcMsat: r.Policy1MaxHtlcMsat, - LastUpdate: r.Policy1LastUpdate, - InboundBaseFeeMsat: r.Policy1InboundBaseFeeMsat, - InboundFeeRateMilliMsat: r.Policy1InboundFeeRateMilliMsat, - Disabled: r.Policy1Disabled, - MessageFlags: r.Policy1MessageFlags, - ChannelFlags: r.Policy1ChannelFlags, - Signature: r.Policy1Signature, - BlockHeight: r.Policy1BlockHeight, - DisableFlags: r.Policy1DisableFlags, - } - } - if r.Policy2ID.Valid { - policy2 = &sqlc.GraphChannelPolicy{ - ID: r.Policy2ID.Int64, - Version: r.Policy2Version.Int16, - ChannelID: r.GraphChannel.ID, - NodeID: r.Policy2NodeID.Int64, - Timelock: r.Policy2Timelock.Int32, - FeePpm: r.Policy2FeePpm.Int64, - BaseFeeMsat: r.Policy2BaseFeeMsat.Int64, - MinHtlcMsat: r.Policy2MinHtlcMsat.Int64, - MaxHtlcMsat: r.Policy2MaxHtlcMsat, - LastUpdate: r.Policy2LastUpdate, - InboundBaseFeeMsat: r.Policy2InboundBaseFeeMsat, - InboundFeeRateMilliMsat: r.Policy2InboundFeeRateMilliMsat, - Disabled: r.Policy2Disabled, - MessageFlags: r.Policy2MessageFlags, - ChannelFlags: r.Policy2ChannelFlags, - Signature: r.Policy2Signature, - BlockHeight: r.Policy2BlockHeight, - DisableFlags: r.Policy2DisableFlags, } } @@ -5882,8 +4771,6 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy1MessageFlags, ChannelFlags: r.Policy1ChannelFlags, Signature: r.Policy1Signature, - BlockHeight: r.Policy1BlockHeight, - DisableFlags: r.Policy1DisableFlags, } } if r.Policy2ID.Valid { @@ -5904,8 +4791,6 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy2MessageFlags, ChannelFlags: r.Policy2ChannelFlags, Signature: r.Policy2Signature, - BlockHeight: r.Policy2BlockHeight, - DisableFlags: r.Policy2DisableFlags, } } @@ -5930,8 +4815,6 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy1MessageFlags, ChannelFlags: r.Policy1ChannelFlags, Signature: r.Policy1Signature, - BlockHeight: r.Policy1BlockHeight, - DisableFlags: r.Policy1DisableFlags, } } if r.Policy2ID.Valid { @@ -5952,8 +4835,6 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy2MessageFlags, ChannelFlags: r.Policy2ChannelFlags, Signature: r.Policy2Signature, - BlockHeight: r.Policy2BlockHeight, - DisableFlags: r.Policy2DisableFlags, } } @@ -5978,8 +4859,6 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy1MessageFlags, ChannelFlags: r.Policy1ChannelFlags, Signature: r.Policy1Signature, - BlockHeight: r.Policy1BlockHeight, - DisableFlags: r.Policy1DisableFlags, } } if r.Policy2ID.Valid { @@ -6000,8 +4879,6 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy2MessageFlags, ChannelFlags: r.Policy2ChannelFlags, Signature: r.Policy2Signature, - BlockHeight: r.Policy2BlockHeight, - DisableFlags: r.Policy2DisableFlags, } } @@ -6026,8 +4903,6 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy1MessageFlags, ChannelFlags: r.Policy1ChannelFlags, Signature: r.Policy1Signature, - BlockHeight: r.Policy1BlockHeight, - DisableFlags: r.Policy1DisableFlags, } } if r.Policy2ID.Valid { @@ -6048,8 +4923,6 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy2MessageFlags, ChannelFlags: r.Policy2ChannelFlags, Signature: r.Policy2Signature, - BlockHeight: r.Policy2BlockHeight, - DisableFlags: r.Policy2DisableFlags, } } @@ -6331,14 +5204,14 @@ func buildChanPoliciesWithBatchData(dbPol1, dbPol2 *sqlc.GraphChannelPolicy, *models.ChannelEdgePolicy, error) { pol1, err := buildChanPolicyWithBatchData( - true, dbPol1, channelID, node2, batchData, + dbPol1, channelID, node2, batchData, ) if err != nil { return nil, nil, fmt.Errorf("unable to build policy1: %w", err) } pol2, err := buildChanPolicyWithBatchData( - false, dbPol2, channelID, node1, batchData, + dbPol2, channelID, node1, batchData, ) if err != nil { return nil, nil, fmt.Errorf("unable to build policy2: %w", err) @@ -6349,10 +5222,9 @@ func buildChanPoliciesWithBatchData(dbPol1, dbPol2 *sqlc.GraphChannelPolicy, // buildChanPolicyWithBatchData builds a models.ChannelEdgePolicy instance from // the provided sqlc.GraphChannelPolicy and the provided batchChannelData. -func buildChanPolicyWithBatchData(isNode1 bool, - dbPol *sqlc.GraphChannelPolicy, channelID uint64, - toNode route.Vertex, batchData *batchChannelData) ( - *models.ChannelEdgePolicy, error) { +func buildChanPolicyWithBatchData(dbPol *sqlc.GraphChannelPolicy, + channelID uint64, toNode route.Vertex, + batchData *batchChannelData) (*models.ChannelEdgePolicy, error) { if dbPol == nil { return nil, nil @@ -6365,7 +5237,7 @@ func buildChanPolicyWithBatchData(isNode1 bool, dbPol1Extras = make(map[uint64][]byte) } - return buildChanPolicy(isNode1, *dbPol, channelID, dbPol1Extras, toNode) + return buildChanPolicy(*dbPol, channelID, dbPol1Extras, toNode) } // batchChannelData holds all the related data for a batch of channels. @@ -6532,7 +5404,7 @@ func batchLoadChannelPolicyExtrasHelper(ctx context.Context, // graph. It uses the provided SQLQueries interface to fetch nodes in batches // and applies the provided processNode function to each node. func forEachNodePaginated(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, protocol lnwire.GossipVersion, + db SQLQueries, protocol ProtocolVersion, processNode func(context.Context, int64, *models.Node) error) error { @@ -6583,8 +5455,8 @@ func forEachNodePaginated(ctx context.Context, cfg *sqldb.QueryConfig, // forEachChannelWithPolicies executes a paginated query to process each channel // with policies in the graph. func forEachChannelWithPolicies(ctx context.Context, db SQLQueries, - cfg *SQLStoreConfig, v lnwire.GossipVersion, - processChannel func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, + cfg *SQLStoreConfig, processChannel func(*models.ChannelEdgeInfo, + *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error) error { type channelBatchIDs struct { @@ -6598,7 +5470,7 @@ func forEachChannelWithPolicies(ctx context.Context, db SQLQueries, return db.ListChannelsWithPoliciesPaginated( ctx, sqlc.ListChannelsWithPoliciesPaginatedParams{ - Version: int16(v), + Version: int16(ProtocolV1), ID: lastID, Limit: limit, }, @@ -6947,19 +5819,12 @@ func batchBuildChannelInfo[T sqlc.ChannelAndNodeIDs](ctx context.Context, // we are in strict zombie pruning mode, and adjusts the node public keys // accordingly based on the last update timestamps of the channel policies. func handleZombieMarking(ctx context.Context, db SQLQueries, - v lnwire.GossipVersion, row sqlc.GetChannelsBySCIDWithPoliciesRow, info *models.ChannelEdgeInfo, strictZombiePruning bool, scid uint64) error { nodeKey1, nodeKey2 := info.NodeKey1Bytes, info.NodeKey2Bytes if strictZombiePruning { - // TODO(elle): update for V2 last update times. - if v != gossipV1 { - return fmt.Errorf("strict zombie pruning only "+ - "supported for gossip v1, got %v", v) - } - var e1UpdateTime, e2UpdateTime *time.Time if row.Policy1LastUpdate.Valid { e1Time := time.Unix(row.Policy1LastUpdate.Int64, 0) @@ -6978,7 +5843,7 @@ func handleZombieMarking(ctx context.Context, db SQLQueries, return db.UpsertZombieChannel( ctx, sqlc.UpsertZombieChannelParams{ - Version: int16(v), + Version: int16(ProtocolV1), Scid: channelIDToBytes(scid), NodeKey1: nodeKey1[:], NodeKey2: nodeKey2[:], diff --git a/graph/db/test_kvdb.go b/graph/db/test_kvdb.go index 22904f858..f325d41f0 100644 --- a/graph/db/test_kvdb.go +++ b/graph/db/test_kvdb.go @@ -9,11 +9,8 @@ import ( "github.com/stretchr/testify/require" ) -// isSQLDB indicates that this build does not use a SQL database. -var isSQLDB = false - // NewTestDB is a helper function that creates an BBolt database for testing. -func NewTestDB(t testing.TB) Store { +func NewTestDB(t testing.TB) V1Store { backend, backendCleanup, err := kvdb.GetTestBackend(t.TempDir(), "cgr") require.NoError(t, err) diff --git a/graph/db/test_postgres.go b/graph/db/test_postgres.go index 375480656..6134f0114 100644 --- a/graph/db/test_postgres.go +++ b/graph/db/test_postgres.go @@ -6,17 +6,14 @@ import ( "database/sql" "testing" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" "github.com/lightningnetwork/lnd/sqldb" "github.com/stretchr/testify/require" ) -// isSQLDB indicates that this build uses a SQL database. -var isSQLDB = true - // NewTestDB is a helper function that creates a SQLStore backed by a SQL // database for testing. -func NewTestDB(t testing.TB) Store { +func NewTestDB(t testing.TB) V1Store { return NewTestDBWithFixture(t, nil) } @@ -34,7 +31,7 @@ func NewTestDBFixture(t *testing.T) *sqldb.TestPgFixture { // NewTestDBWithFixture is a helper function that creates a SQLStore backed by a // SQL database for testing. func NewTestDBWithFixture(t testing.TB, - pgFixture *sqldb.TestPgFixture) Store { + pgFixture *sqldb.TestPgFixture) V1Store { var querier BatchedSQLQueries if pgFixture == nil { diff --git a/graph/db/test_sqlite.go b/graph/db/test_sqlite.go index 50db50473..c1c6d808f 100644 --- a/graph/db/test_sqlite.go +++ b/graph/db/test_sqlite.go @@ -6,17 +6,14 @@ import ( "database/sql" "testing" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" "github.com/lightningnetwork/lnd/sqldb" "github.com/stretchr/testify/require" ) -// isSQLDB indicates that this build uses a SQL database. -var isSQLDB = true - // NewTestDB is a helper function that creates a SQLStore backed by a SQL // database for testing. -func NewTestDB(t testing.TB) Store { +func NewTestDB(t testing.TB) V1Store { return NewTestDBWithFixture(t, nil) } @@ -27,7 +24,7 @@ func NewTestDBFixture(_ *testing.T) *sqldb.TestPgFixture { // NewTestDBWithFixture is a helper function that creates a SQLStore backed by a // SQL database for testing. -func NewTestDBWithFixture(t testing.TB, _ *sqldb.TestPgFixture) Store { +func NewTestDBWithFixture(t testing.TB, _ *sqldb.TestPgFixture) V1Store { store, err := NewSQLStore( &SQLStoreConfig{ ChainHash: *chaincfg.MainNetParams.GenesisHash, diff --git a/graph/interfaces.go b/graph/interfaces.go index bc05d047b..0896a0850 100644 --- a/graph/interfaces.go +++ b/graph/interfaces.go @@ -2,9 +2,13 @@ package graph import ( "context" + "iter" "time" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/batch" + graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" @@ -60,9 +64,9 @@ type ChannelGraphSource interface { IsStaleEdgePolicy(chanID lnwire.ShortChannelID, timestamp time.Time, flags lnwire.ChanUpdateChanFlags) bool - // MarkEdgeLive clears an edge from our zombie index for the given - // gossip version, deeming it as live. - MarkEdgeLive(v lnwire.GossipVersion, chanID lnwire.ShortChannelID) error + // MarkEdgeLive clears an edge from our zombie index, deeming it as + // live. + MarkEdgeLive(chanID lnwire.ShortChannelID) error // ForAllOutgoingChannels is used to iterate over all channels // emanating from the "source" node which is the center of the @@ -92,3 +96,183 @@ type ChannelGraphSource interface { // currently marked as a zombie edge. IsZombieEdge(chanID lnwire.ShortChannelID) (bool, error) } + +// DB is an interface describing a persisted Lightning Network graph. +// +//nolint:interfacebloat +type DB interface { + // PruneTip returns the block height and hash of the latest block that + // has been used to prune channels in the graph. Knowing the "prune tip" + // allows callers to tell if the graph is currently in sync with the + // current best known UTXO state. + PruneTip() (*chainhash.Hash, uint32, error) + + // PruneGraph prunes newly closed channels from the channel graph in + // response to a new block being solved on the network. Any transactions + // which spend the funding output of any known channels within the graph + // will be deleted. Additionally, the "prune tip", or the last block + // which has been used to prune the graph is stored so callers can + // ensure the graph is fully in sync with the current UTXO state. A + // slice of channels that have been closed by the target block are + // returned if the function succeeds without error. + PruneGraph(spentOutputs []*wire.OutPoint, blockHash *chainhash.Hash, + blockHeight uint32) ([]*models.ChannelEdgeInfo, error) + + // ChannelView returns the verifiable edge information for each active + // channel within the known channel graph. The set of UTXO's (along with + // their scripts) returned are the ones that need to be watched on + // chain to detect channel closes on the resident blockchain. + ChannelView() ([]graphdb.EdgePoint, error) + + // PruneGraphNodes is a garbage collection method which attempts to + // prune out any nodes from the channel graph that are currently + // unconnected. This ensure that we only maintain a graph of reachable + // nodes. In the event that a pruned node gains more channels, it will + // be re-added back to the graph. + PruneGraphNodes() error + + // SourceNode returns the source node of the graph. The source node is + // treated as the center node within a star-graph. This method may be + // used to kick off a path finding algorithm in order to explore the + // reachability of another node based off the source node. + SourceNode(ctx context.Context) (*models.Node, error) + + // DisabledChannelIDs returns the channel ids of disabled channels. + // A channel is disabled when two of the associated ChanelEdgePolicies + // have their disabled bit on. + DisabledChannelIDs() ([]uint64, error) + + // FetchChanInfos returns the set of channel edges that correspond to + // the passed channel ID's. If an edge is the query is unknown to the + // database, it will skipped and the result will contain only those + // edges that exist at the time of the query. This can be used to + // respond to peer queries that are seeking to fill in gaps in their + // view of the channel graph. + FetchChanInfos(chanIDs []uint64) ([]graphdb.ChannelEdge, error) + + // ChanUpdatesInHorizon returns all the known channel edges which have + // at least one edge that has an update timestamp within the specified + // horizon. + ChanUpdatesInHorizon(startTime, endTime time.Time, + opts ...graphdb.IteratorOption, + ) iter.Seq2[graphdb.ChannelEdge, error] + + // DeleteChannelEdges removes edges with the given channel IDs from the + // database and marks them as zombies. This ensures that we're unable to + // re-add it to our database once again. If an edge does not exist + // within the database, then ErrEdgeNotFound will be returned. If + // strictZombiePruning is true, then when we mark these edges as + // zombies, we'll set up the keys such that we require the node that + // failed to send the fresh update to be the one that resurrects the + // channel from its zombie state. The markZombie bool denotes whether + // to mark the channel as a zombie. + DeleteChannelEdges(strictZombiePruning, markZombie bool, + chanIDs ...uint64) error + + // DisconnectBlockAtHeight is used to indicate that the block specified + // by the passed height has been disconnected from the main chain. This + // will "rewind" the graph back to the height below, deleting channels + // that are no longer confirmed from the graph. The prune log will be + // set to the last prune height valid for the remaining chain. + // Channels that were removed from the graph resulting from the + // disconnected block are returned. + DisconnectBlockAtHeight(height uint32) ([]*models.ChannelEdgeInfo, + error) + + // HasChannelEdge returns true if the database knows of a channel edge + // with the passed channel ID, and false otherwise. If an edge with that + // ID is found within the graph, then two time stamps representing the + // last time the edge was updated for both directed edges are returned + // along with the boolean. If it is not found, then the zombie index is + // checked and its result is returned as the second boolean. + HasChannelEdge(chanID uint64) (time.Time, time.Time, bool, bool, error) + + // FetchChannelEdgesByID attempts to lookup the two directed edges for + // the channel identified by the channel ID. If the channel can't be + // found, then ErrEdgeNotFound is returned. A struct which houses the + // general information for the channel itself is returned as well as + // two structs that contain the routing policies for the channel in + // either direction. + // + // ErrZombieEdge an be returned if the edge is currently marked as a + // zombie within the database. In this case, the ChannelEdgePolicy's + // will be nil, and the ChannelEdgeInfo will only include the public + // keys of each node. + FetchChannelEdgesByID(chanID uint64) (*models.ChannelEdgeInfo, + *models.ChannelEdgePolicy, *models.ChannelEdgePolicy, error) + + // AddNode adds a vertex/node to the graph database. If the + // node is not in the database from before, this will add a new, + // unconnected one to the graph. If it is present from before, this will + // update that node's information. Note that this method is expected to + // only be called to update an already present node from a node + // announcement, or to insert a node found in a channel update. + AddNode(ctx context.Context, node *models.Node, + op ...batch.SchedulerOption) error + + // AddChannelEdge adds a new (undirected, blank) edge to the graph + // database. An undirected edge from the two target nodes are created. + // The information stored denotes the static attributes of the channel, + // such as the channelID, the keys involved in creation of the channel, + // and the set of features that the channel supports. The chanPoint and + // chanID are used to uniquely identify the edge globally within the + // database. + AddChannelEdge(ctx context.Context, edge *models.ChannelEdgeInfo, + op ...batch.SchedulerOption) error + + // MarkEdgeZombie attempts to mark a channel identified by its channel + // ID as a zombie. This method is used on an ad-hoc basis, when channels + // need to be marked as zombies outside the normal pruning cycle. + MarkEdgeZombie(chanID uint64, pubKey1, pubKey2 [33]byte) error + + // UpdateEdgePolicy updates the edge routing policy for a single + // directed edge within the database for the referenced channel. The + // `flags` attribute within the ChannelEdgePolicy determines which of + // the directed edges are being updated. If the flag is 1, then the + // first node's information is being updated, otherwise it's the second + // node's information. The node ordering is determined by the + // lexicographical ordering of the identity public keys of the nodes on + // either side of the channel. + UpdateEdgePolicy(ctx context.Context, edge *models.ChannelEdgePolicy, + op ...batch.SchedulerOption) error + + // HasNode determines if the graph has a vertex identified by + // the target node identity public key. If the node exists in the + // database, a timestamp of when the data for the node was lasted + // updated is returned along with a true boolean. Otherwise, an empty + // time.Time is returned with a false boolean. + HasNode(ctx context.Context, nodePub [33]byte) (time.Time, bool, error) + + // FetchNode attempts to look up a target node by its identity + // public key. If the node isn't found in the database, then + // ErrGraphNodeNotFound is returned. + FetchNode(ctx context.Context, nodePub route.Vertex) (*models.Node, + error) + + // ForEachNodeChannel iterates through all channels of the given node, + // executing the passed callback with an edge info structure and the + // policies of each end of the channel. The first edge policy is the + // outgoing edge *to* the connecting node, while the second is the + // incoming edge *from* the connecting node. If the callback returns an + // error, then the iteration is halted with the error propagated back up + // to the caller. + // + // Unknown policies are passed into the callback as nil values. + ForEachNodeChannel(ctx context.Context, nodePub route.Vertex, + cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, + *models.ChannelEdgePolicy) error, reset func()) error + + // AddEdgeProof sets the proof of an existing edge in the graph + // database. + AddEdgeProof(chanID lnwire.ShortChannelID, + proof *models.ChannelAuthProof) error + + // IsPublicNode is a helper method that determines whether the node with + // the given public key is seen as a public node in the graph from the + // graph's source node's point of view. + IsPublicNode(pubKey [33]byte) (bool, error) + + // MarkEdgeLive clears an edge from our zombie index, deeming it as + // live. + MarkEdgeLive(chanID uint64) error +} diff --git a/graph/notifications_test.go b/graph/notifications_test.go index 898d951fc..32408424a 100644 --- a/graph/notifications_test.go +++ b/graph/notifications_test.go @@ -13,10 +13,9 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/fn/v2" graphdb "github.com/lightningnetwork/lnd/graph/db" @@ -69,12 +68,12 @@ var ( _ = testSScalar.SetByteSlice(testSBytes) testSig = ecdsa.NewSignature(testRScalar, testSScalar) - testAuthProof = *models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ) + testAuthProof = models.ChannelAuthProof{ + NodeSig1Bytes: testSig.Serialize(), + NodeSig2Bytes: testSig.Serialize(), + BitcoinSig1Bytes: testSig.Serialize(), + BitcoinSig2Bytes: testSig.Serialize(), + } ) func createTestNode(t *testing.T) *models.Node { @@ -84,24 +83,22 @@ func createTestNode(t *testing.T) *models.Node { require.NoError(t, err) pub := priv.PubKey().SerializeCompressed() - n := models.NewV1Node( - route.NewVertex(priv.PubKey()), &models.NodeV1Fields{ - LastUpdate: time.Unix(updateTime, 0), - Addresses: testAddrs, - Color: color.RGBA{1, 2, 3, 0}, - Alias: "kek" + hex.EncodeToString(pub), - AuthSigBytes: testSig.Serialize(), - Features: testFeatures.RawFeatureVector, - }, - ) + n := &models.Node{ + HaveNodeAnnouncement: true, + LastUpdate: time.Unix(updateTime, 0), + Addresses: testAddrs, + Color: color.RGBA{1, 2, 3, 0}, + Alias: "kek" + hex.EncodeToString(pub), + AuthSigBytes: testSig.Serialize(), + Features: testFeatures, + } + copy(n.PubKeyBytes[:], pub) return n } -func randEdgePolicy(t testing.TB, chanID *lnwire.ShortChannelID, - node *models.Node) *models.ChannelEdgePolicy { - - t.Helper() +func randEdgePolicy(chanID *lnwire.ShortChannelID, + node *models.Node) (*models.ChannelEdgePolicy, error) { InboundFee := models.InboundFee{ Base: prand.Int31() * -1, @@ -110,10 +107,11 @@ func randEdgePolicy(t testing.TB, chanID *lnwire.ShortChannelID, inboundFee := InboundFee.ToWire() var extraOpaqueData lnwire.ExtraOpaqueData - require.NoError(t, extraOpaqueData.PackRecords(&inboundFee)) + if err := extraOpaqueData.PackRecords(&inboundFee); err != nil { + return nil, err + } return &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: chanID.ToUint64(), LastUpdate: time.Unix(int64(prand.Int31()), 0), @@ -125,14 +123,12 @@ func randEdgePolicy(t testing.TB, chanID *lnwire.ShortChannelID, ToNode: node.PubKeyBytes, InboundFee: fn.Some(inboundFee), ExtraOpaqueData: extraOpaqueData, - } + }, nil } -func createChannelEdge(t testing.TB, bitcoinKey1, bitcoinKey2 []byte, +func createChannelEdge(bitcoinKey1, bitcoinKey2 []byte, chanValue btcutil.Amount, fundingHeight uint32) ([]byte, *wire.MsgTx, - *wire.OutPoint, *lnwire.ShortChannelID) { - - t.Helper() + *wire.OutPoint, *lnwire.ShortChannelID, error) { fundingTx := wire.NewMsgTx(2) script, tx, err := input.GenFundingPkScript( @@ -140,7 +136,9 @@ func createChannelEdge(t testing.TB, bitcoinKey1, bitcoinKey2 []byte, bitcoinKey2, int64(chanValue), ) - require.NoError(t, err) + if err != nil { + return nil, nil, nil, nil, err + } fundingTx.TxOut = append(fundingTx.TxOut, tx) chanUtxo := wire.OutPoint{ @@ -155,7 +153,7 @@ func createChannelEdge(t testing.TB, bitcoinKey1, bitcoinKey2 []byte, TxPosition: 0, } - return script, fundingTx, &chanUtxo, chanID + return script, fundingTx, &chanUtxo, chanID, nil } type mockChain struct { @@ -429,10 +427,11 @@ func TestEdgeUpdateNotification(t *testing.T) { // First we'll create the utxo for the channel to be "closed" const chanValue = 10000 - script, fundingTx, chanPoint, chanID := createChannelEdge( - t, bitcoinKey1.SerializeCompressed(), + script, fundingTx, chanPoint, chanID, err := createChannelEdge( + bitcoinKey1.SerializeCompressed(), bitcoinKey2.SerializeCompressed(), chanValue, 0, ) + require.NoError(t, err, "unable create channel edge") // We'll also add a record for the block that included our funding // transaction. @@ -448,29 +447,27 @@ func TestEdgeUpdateNotification(t *testing.T) { // Finally, to conclude our test set up, we'll create a channel // update to announce the created channel between the two nodes. - btcKey1 := route.NewVertex(bitcoinKey1) - btcKey2 := route.NewVertex(bitcoinKey2) + edge := &models.ChannelEdgeInfo{ + ChannelID: chanID.ToUint64(), + NodeKey1Bytes: node1.PubKeyBytes, + NodeKey2Bytes: node2.PubKeyBytes, + AuthProof: &models.ChannelAuthProof{ + NodeSig1Bytes: testSig.Serialize(), + NodeSig2Bytes: testSig.Serialize(), + BitcoinSig1Bytes: testSig.Serialize(), + BitcoinSig2Bytes: testSig.Serialize(), + }, + Features: lnwire.EmptyFeatureVector(), + ChannelPoint: *chanPoint, + Capacity: chanValue, + FundingScript: fn.Some(script), + } + copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) + copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) - proof := models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ) - - edge, err := models.NewV1Channel( - chanID.ToUint64(), *chaincfg.SimNetParams.GenesisHash, - node1.PubKeyBytes, node2.PubKeyBytes, &models.ChannelV1Fields{ - BitcoinKey1Bytes: btcKey1, - BitcoinKey2Bytes: btcKey2, - }, models.WithChanProof(proof), - models.WithChannelPoint(*chanPoint), - models.WithCapacity(chanValue), - models.WithFundingScript(script), - ) - require.NoError(t, err) - - require.NoError(t, ctx.builder.AddEdge(ctxb, edge)) + if err := ctx.builder.AddEdge(ctxb, edge); err != nil { + t.Fatalf("unable to add edge: %v", err) + } // With the channel edge now in place, we'll subscribe for topology // notifications. @@ -479,34 +476,64 @@ func TestEdgeUpdateNotification(t *testing.T) { // Create random policy edges that are stemmed to the channel id // created above. - edge1 := randEdgePolicy(t, chanID, node1) + edge1, err := randEdgePolicy(chanID, node1) + require.NoError(t, err, "unable to create a random chan policy") edge1.ChannelFlags = 0 - edge2 := randEdgePolicy(t, chanID, node2) + edge2, err := randEdgePolicy(chanID, node2) + require.NoError(t, err, "unable to create a random chan policy") edge2.ChannelFlags = 1 - require.NoError(t, ctx.builder.UpdateEdge(ctxb, edge1)) - require.NoError(t, ctx.builder.UpdateEdge(ctxb, edge2)) + if err := ctx.builder.UpdateEdge(ctxb, edge1); err != nil { + t.Fatalf("unable to add edge update: %v", err) + } + if err := ctx.builder.UpdateEdge(ctxb, edge2); err != nil { + t.Fatalf("unable to add edge update: %v", err) + } assertEdgeCorrect := func(t *testing.T, edgeUpdate *graphdb.ChannelEdgeUpdate, edgeAnn *models.ChannelEdgePolicy) { - require.Equal(t, edgeAnn.ChannelID, edgeUpdate.ChanID) - require.Equal(t, *chanPoint, edgeUpdate.ChanPoint) + if edgeUpdate.ChanID != edgeAnn.ChannelID { + t.Fatalf("channel ID of edge doesn't match: "+ + "expected %v, got %v", chanID.ToUint64(), edgeUpdate.ChanID) + } + if edgeUpdate.ChanPoint != *chanPoint { + t.Fatalf("channel don't match: expected %v, got %v", + chanPoint, edgeUpdate.ChanPoint) + } // TODO(roasbeef): this is a hack, needs to be removed // after commitment fees are dynamic. - require.EqualValues(t, chanValue, edgeUpdate.Capacity) - require.Equal(t, edgeAnn.MinHTLC, edgeUpdate.MinHTLC) - require.Equal(t, edgeAnn.MaxHTLC, edgeUpdate.MaxHTLC) - require.Equal(t, edgeAnn.FeeBaseMSat, edgeUpdate.BaseFee) - require.Equal( - t, edgeAnn.FeeProportionalMillionths, - edgeUpdate.FeeRate, - ) - require.Equal( - t, edgeAnn.TimeLockDelta, edgeUpdate.TimeLockDelta, - ) + if edgeUpdate.Capacity != chanValue { + t.Fatalf("capacity of edge doesn't match: "+ + "expected %v, got %v", chanValue, edgeUpdate.Capacity) + } + if edgeUpdate.MinHTLC != edgeAnn.MinHTLC { + t.Fatalf("min HTLC of edge doesn't match: "+ + "expected %v, got %v", edgeAnn.MinHTLC, + edgeUpdate.MinHTLC) + } + if edgeUpdate.MaxHTLC != edgeAnn.MaxHTLC { + t.Fatalf("max HTLC of edge doesn't match: "+ + "expected %v, got %v", edgeAnn.MaxHTLC, + edgeUpdate.MaxHTLC) + } + if edgeUpdate.BaseFee != edgeAnn.FeeBaseMSat { + t.Fatalf("base fee of edge doesn't match: "+ + "expected %v, got %v", edgeAnn.FeeBaseMSat, + edgeUpdate.BaseFee) + } + if edgeUpdate.FeeRate != edgeAnn.FeeProportionalMillionths { + t.Fatalf("fee rate of edge doesn't match: "+ + "expected %v, got %v", edgeAnn.FeeProportionalMillionths, + edgeUpdate.FeeRate) + } + if edgeUpdate.TimeLockDelta != edgeAnn.TimeLockDelta { + t.Fatalf("time lock delta of edge doesn't match: "+ + "expected %v, got %v", edgeAnn.TimeLockDelta, + edgeUpdate.TimeLockDelta) + } require.Equal( t, edgeAnn.ExtraOpaqueData, edgeUpdate.ExtraOpaqueData, ) @@ -530,7 +557,10 @@ func TestEdgeUpdateNotification(t *testing.T) { case ntfn := <-ntfnClient.TopologyChanges: // For each processed announcement we should only receive a // single announcement in a batch. - require.Len(t, ntfn.ChannelEdgeUpdates, 1) + if len(ntfn.ChannelEdgeUpdates) != 1 { + t.Fatalf("expected 1 notification, instead have %v", + len(ntfn.ChannelEdgeUpdates)) + } edgeUpdate := ntfn.ChannelEdgeUpdates[0] nodeVertex := route.NewVertex(edgeUpdate.AdvertisingNode) @@ -588,11 +618,12 @@ func TestNodeUpdateNotification(t *testing.T) { // We only accept node announcements from nodes having a known channel, // so create one now. const chanValue = 10000 - script, fundingTx, _, chanID := createChannelEdge( - t, bitcoinKey1.SerializeCompressed(), + script, fundingTx, _, chanID, err := createChannelEdge( + bitcoinKey1.SerializeCompressed(), bitcoinKey2.SerializeCompressed(), chanValue, startingBlockHeight, ) + require.NoError(t, err, "unable create channel edge") // We'll also add a record for the block that included our funding // transaction. @@ -610,29 +641,27 @@ func TestNodeUpdateNotification(t *testing.T) { testFeaturesBuf := new(bytes.Buffer) require.NoError(t, testFeatures.Encode(testFeaturesBuf)) - btcKey1 := route.NewVertex(bitcoinKey1) - btcKey2 := route.NewVertex(bitcoinKey2) - - proof := models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ) - - edge, err := models.NewV1Channel( - chanID.ToUint64(), *chaincfg.SimNetParams.GenesisHash, - node1.PubKeyBytes, node2.PubKeyBytes, &models.ChannelV1Fields{ - BitcoinKey1Bytes: btcKey1, - BitcoinKey2Bytes: btcKey2, - }, models.WithChanProof(proof), - models.WithFundingScript(script), - ) - require.NoError(t, err) + edge := &models.ChannelEdgeInfo{ + ChannelID: chanID.ToUint64(), + NodeKey1Bytes: node1.PubKeyBytes, + NodeKey2Bytes: node2.PubKeyBytes, + Features: lnwire.EmptyFeatureVector(), + AuthProof: &models.ChannelAuthProof{ + NodeSig1Bytes: testSig.Serialize(), + NodeSig2Bytes: testSig.Serialize(), + BitcoinSig1Bytes: testSig.Serialize(), + BitcoinSig2Bytes: testSig.Serialize(), + }, + FundingScript: fn.Some(script), + } + copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) + copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) // Adding the edge will add the nodes to the graph, but with no info // except the pubkey known. - require.NoError(t, ctx.builder.AddEdge(ctxb, edge)) + if err := ctx.builder.AddEdge(ctxb, edge); err != nil { + t.Fatalf("unable to add edge: %v", err) + } // Create a new client to receive notifications. ntfnClient, err := ctx.graph.SubscribeTopology() @@ -640,8 +669,12 @@ func TestNodeUpdateNotification(t *testing.T) { // Change network topology by adding the updated info for the two nodes // to the channel router. - require.NoError(t, ctx.builder.AddNode(ctxb, node1)) - require.NoError(t, ctx.builder.AddNode(ctxb, node2)) + if err := ctx.builder.AddNode(ctxb, node1); err != nil { + t.Fatalf("unable to add node: %v", err) + } + if err := ctx.builder.AddNode(ctxb, node2); err != nil { + t.Fatalf("unable to add node: %v", err) + } assertNodeNtfnCorrect := func(t *testing.T, ann *models.Node, nodeUpdate *graphdb.NetworkNodeUpdate) { @@ -650,8 +683,15 @@ func TestNodeUpdateNotification(t *testing.T) { // The notification received should directly map the // announcement originally sent. - require.Equal(t, ann.Addresses[0], nodeUpdate.Addresses[0]) - require.True(t, nodeUpdate.IdentityKey.IsEqual(nodeKey)) + if nodeUpdate.Addresses[0] != ann.Addresses[0] { + t.Fatalf("node address doesn't match: expected %v, got %v", + nodeUpdate.Addresses[0], ann.Addresses[0]) + } + if !nodeUpdate.IdentityKey.IsEqual(nodeKey) { + t.Fatalf("node identity keys don't match: expected %x, "+ + "got %x", nodeKey.SerializeCompressed(), + nodeUpdate.IdentityKey.SerializeCompressed()) + } featuresBuf := new(bytes.Buffer) require.NoError(t, nodeUpdate.Features.Encode(featuresBuf)) @@ -660,12 +700,15 @@ func TestNodeUpdateNotification(t *testing.T) { t, testFeaturesBuf.Bytes(), featuresBuf.Bytes(), ) - require.Equal(t, nodeUpdate.Alias, ann.Alias.UnwrapOr("")) - require.Equal( - t, nodeUpdate.Color, graphdb.EncodeHexColor( - ann.Color.UnwrapOr(color.RGBA{}), - ), - ) + if nodeUpdate.Alias != ann.Alias { + t.Fatalf("node alias doesn't match: expected %v, got %v", + ann.Alias, nodeUpdate.Alias) + } + if nodeUpdate.Color != graphdb.EncodeHexColor(ann.Color) { + t.Fatalf("node color doesn't match: expected %v, "+ + "got %v", graphdb.EncodeHexColor(ann.Color), + nodeUpdate.Color) + } } // Create lookup map for notifications we are intending to receive. Entries @@ -683,7 +726,10 @@ func TestNodeUpdateNotification(t *testing.T) { case ntfn := <-ntfnClient.TopologyChanges: // For each processed announcement we should only receive a // single announcement in a batch. - require.Len(t, ntfn.NodeUpdates, 1) + if len(ntfn.NodeUpdates) != 1 { + t.Fatalf("expected 1 notification, instead have %v", + len(ntfn.NodeUpdates)) + } nodeUpdate := ntfn.NodeUpdates[0] nodeVertex := route.NewVertex(nodeUpdate.IdentityKey) @@ -721,7 +767,9 @@ func TestNodeUpdateNotification(t *testing.T) { nodeUpdateAnn.LastUpdate = node1.LastUpdate.Add(time.Second) // Add new node topology update to the channel router. - require.NoError(t, ctx.builder.AddNode(ctxb, &nodeUpdateAnn)) + if err := ctx.builder.AddNode(ctxb, &nodeUpdateAnn); err != nil { + t.Fatalf("unable to add node: %v", err) + } // Once again a notification should be received reflecting the up to // date node announcement. @@ -729,7 +777,10 @@ func TestNodeUpdateNotification(t *testing.T) { case ntfn := <-ntfnClient.TopologyChanges: // For each processed announcement we should only receive a // single announcement in a batch. - require.Len(t, ntfn.NodeUpdates, 1) + if len(ntfn.NodeUpdates) != 1 { + t.Fatalf("expected 1 notification, instead have %v", + len(ntfn.NodeUpdates)) + } nodeUpdate := ntfn.NodeUpdates[0] assertNodeNtfnCorrect(t, &nodeUpdateAnn, nodeUpdate) @@ -754,11 +805,12 @@ func TestNotificationCancellation(t *testing.T) { // We'll create the utxo for a new channel. const chanValue = 10000 - script, fundingTx, chanPoint, chanID := createChannelEdge( - t, bitcoinKey1.SerializeCompressed(), + script, fundingTx, chanPoint, chanID, err := createChannelEdge( + bitcoinKey1.SerializeCompressed(), bitcoinKey2.SerializeCompressed(), chanValue, startingBlockHeight, ) + require.NoError(t, err, "unable create channel edge") // We'll also add a record for the block that included our funding // transaction. @@ -778,33 +830,34 @@ func TestNotificationCancellation(t *testing.T) { // to the client. ntfnClient.Cancel() - btcKey1 := route.NewVertex(bitcoinKey1) - btcKey2 := route.NewVertex(bitcoinKey2) + edge := &models.ChannelEdgeInfo{ + ChannelID: chanID.ToUint64(), + NodeKey1Bytes: node1.PubKeyBytes, + NodeKey2Bytes: node2.PubKeyBytes, + AuthProof: &models.ChannelAuthProof{ + NodeSig1Bytes: testSig.Serialize(), + NodeSig2Bytes: testSig.Serialize(), + BitcoinSig1Bytes: testSig.Serialize(), + BitcoinSig2Bytes: testSig.Serialize(), + }, + Features: lnwire.EmptyFeatureVector(), + ChannelPoint: *chanPoint, + Capacity: chanValue, + FundingScript: fn.Some(script), + } + copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) + copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + if err := ctx.builder.AddEdge(ctxb, edge); err != nil { + t.Fatalf("unable to add edge: %v", err) + } - proof := models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ) + if err := ctx.builder.AddNode(ctxb, node1); err != nil { + t.Fatalf("unable to add node: %v", err) + } - edge, err := models.NewV1Channel( - chanID.ToUint64(), *chaincfg.SimNetParams.GenesisHash, - node1.PubKeyBytes, node2.PubKeyBytes, &models.ChannelV1Fields{ - BitcoinKey1Bytes: btcKey1, - BitcoinKey2Bytes: btcKey2, - }, models.WithChanProof(proof), - models.WithChannelPoint(*chanPoint), - models.WithCapacity(chanValue), - models.WithFundingScript(script), - ) - require.NoError(t, err) - - require.NoError(t, ctx.builder.AddEdge(ctxb, edge)) - - require.NoError(t, ctx.builder.AddNode(ctxb, node1)) - - require.NoError(t, ctx.builder.AddNode(ctxb, node2)) + if err := ctx.builder.AddNode(ctxb, node2); err != nil { + t.Fatalf("unable to add node: %v", err) + } select { // The notifications shouldn't be sent, however, the channel should be @@ -832,11 +885,12 @@ func TestChannelCloseNotification(t *testing.T) { // First we'll create the utxo for the channel to be "closed" const chanValue = 10000 - script, fundingTx, chanUtxo, chanID := createChannelEdge( - t, bitcoinKey1.SerializeCompressed(), + script, fundingTx, chanUtxo, chanID, err := createChannelEdge( + bitcoinKey1.SerializeCompressed(), bitcoinKey2.SerializeCompressed(), chanValue, startingBlockHeight, ) + require.NoError(t, err, "unable create channel edge") // We'll also add a record for the block that included our funding // transaction. @@ -852,29 +906,26 @@ func TestChannelCloseNotification(t *testing.T) { // Finally, to conclude our test set up, we'll create a channel // announcement to announce the created channel between the two nodes. - btcKey1 := route.NewVertex(bitcoinKey1) - btcKey2 := route.NewVertex(bitcoinKey2) - - proof := models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ) - - edge, err := models.NewV1Channel( - chanID.ToUint64(), *chaincfg.SimNetParams.GenesisHash, - node1.PubKeyBytes, node2.PubKeyBytes, &models.ChannelV1Fields{ - BitcoinKey1Bytes: btcKey1, - BitcoinKey2Bytes: btcKey2, - }, models.WithChanProof(proof), - models.WithChannelPoint(*chanUtxo), - models.WithCapacity(chanValue), - models.WithFundingScript(script), - ) - require.NoError(t, err) - - require.NoError(t, ctx.builder.AddEdge(ctxb, edge)) + edge := &models.ChannelEdgeInfo{ + ChannelID: chanID.ToUint64(), + NodeKey1Bytes: node1.PubKeyBytes, + NodeKey2Bytes: node2.PubKeyBytes, + AuthProof: &models.ChannelAuthProof{ + NodeSig1Bytes: testSig.Serialize(), + NodeSig2Bytes: testSig.Serialize(), + BitcoinSig1Bytes: testSig.Serialize(), + BitcoinSig2Bytes: testSig.Serialize(), + }, + Features: lnwire.EmptyFeatureVector(), + ChannelPoint: *chanUtxo, + Capacity: chanValue, + FundingScript: fn.Some(script), + } + copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) + copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + if err := ctx.builder.AddEdge(ctxb, edge); err != nil { + t.Fatalf("unable to add edge: %v", err) + } // With the channel edge now in place, we'll subscribe for topology // notifications. @@ -907,19 +958,35 @@ func TestChannelCloseNotification(t *testing.T) { // We should have exactly a single notification for the channel // "closed" above. closedChans := ntfn.ClosedChannels - require.NotEmpty(t, closedChans) - require.Len(t, closedChans, 1) + if len(closedChans) == 0 { + t.Fatal("close channel ntfn not populated") + } else if len(closedChans) != 1 { + t.Fatalf("only one should have been detected as closed, "+ + "instead %v were", len(closedChans)) + } // Ensure that the notification we received includes the proper // update the for the channel that was closed in the generated // block. closedChan := closedChans[0] - require.Equal(t, chanID.ToUint64(), closedChan.ChanID) + if closedChan.ChanID != chanID.ToUint64() { + t.Fatalf("channel ID of closed channel doesn't match: "+ + "expected %v, got %v", chanID.ToUint64(), closedChan.ChanID) + } // TODO(roasbeef): this is a hack, needs to be removed // after commitment fees are dynamic. - require.EqualValues(t, chanValue, closedChan.Capacity) - require.Equal(t, blockHeight, closedChan.ClosedHeight) - require.Equal(t, *chanUtxo, closedChan.ChanPoint) + if closedChan.Capacity != chanValue { + t.Fatalf("capacity of closed channel doesn't match: "+ + "expected %v, got %v", chanValue, closedChan.Capacity) + } + if closedChan.ClosedHeight != blockHeight { + t.Fatalf("close height of closed channel doesn't match: "+ + "expected %v, got %v", blockHeight, closedChan.ClosedHeight) + } + if closedChan.ChanPoint != *chanUtxo { + t.Fatalf("chan point of closed channel doesn't match: "+ + "expected %v, got %v", chanUtxo, closedChan.ChanPoint) + } case <-time.After(time.Second * 5): t.Fatal("notification not sent") @@ -978,7 +1045,7 @@ func TestEncodeHexColor(t *testing.T) { type testCtx struct { builder *Builder - graph *graphdb.VersionedGraph + graph *graphdb.ChannelGraph aliases map[string]route.Vertex @@ -995,9 +1062,7 @@ type testCtx struct { func createTestCtxSingleNode(t *testing.T, startingHeight uint32) *testCtx { - graph := graphdb.NewVersionedGraph( - graphdb.MakeTestGraph(t), lnwire.GossipVersion1, - ) + graph := graphdb.MakeTestGraph(t) sourceNode := createTestNode(t) require.NoError(t, @@ -1024,7 +1089,7 @@ func (c *testCtx) RestartBuilder(t *testing.T) { // start it. builder, err := NewBuilder(&Config{ SelfNode: selfNode.PubKeyBytes, - Graph: c.graph.ChannelGraph, + Graph: c.graph, Chain: c.chain, ChainView: c.chainView, Notifier: c.builder.cfg.Notifier, @@ -1046,7 +1111,7 @@ func (c *testCtx) RestartBuilder(t *testing.T) { } type testGraphInstance struct { - graph *graphdb.VersionedGraph + graph *graphdb.ChannelGraph // aliasMap is a map from a node's alias to its public key. This type is // provided in order to allow easily look up from the human memorable @@ -1095,7 +1160,7 @@ func createTestCtxFromGraphInstanceAssumeValid(t *testing.T, graphBuilder, err := NewBuilder(&Config{ SelfNode: selfnode.PubKeyBytes, - Graph: graphInstance.graph.ChannelGraph, + Graph: graphInstance.graph, Chain: chain, ChainView: chainView, Notifier: notifier, diff --git a/healthcheck/go.mod b/healthcheck/go.mod index b2db2d8a1..4c562bdd6 100644 --- a/healthcheck/go.mod +++ b/healthcheck/go.mod @@ -3,24 +3,25 @@ module github.com/lightningnetwork/lnd/healthcheck require ( github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084 github.com/lightningnetwork/lnd/ticker v1.1.0 - github.com/lightningnetwork/lnd/tor v1.1.8-0.20260615022959-a067468f0f45 - github.com/stretchr/testify v1.10.0 - golang.org/x/sys v0.35.0 + github.com/lightningnetwork/lnd/tor v1.0.0 + github.com/stretchr/testify v1.8.4 + golang.org/x/sys v0.32.0 ) require ( - github.com/btcsuite/btcd v0.26.0 // indirect - github.com/btcsuite/btcd/chaincfg/v2 v2.0.0 // indirect - github.com/btcsuite/btcd/chainhash/v2 v2.0.0 // indirect - github.com/btcsuite/btcd/wire/v2 v2.0.0 // indirect - github.com/btcsuite/btclog v1.0.0 // indirect + github.com/btcsuite/btcd v0.24.2 // indirect + github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect + github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/kr/text v0.2.0 // indirect + github.com/kr/pretty v0.3.0 // indirect github.com/miekg/dns v1.1.43 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/crypto v0.40.0 // indirect - golang.org/x/net v0.41.0 // indirect + github.com/rogpeppe/go-internal v1.9.0 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/net v0.39.0 // indirect + golang.org/x/sync v0.2.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) -go 1.25.11 +go 1.24.11 diff --git a/healthcheck/go.sum b/healthcheck/go.sum index a0f96473e..9db4c65b6 100644 --- a/healthcheck/go.sum +++ b/healthcheck/go.sum @@ -1,53 +1,143 @@ -github.com/btcsuite/btcd v0.26.0 h1:yntnSshlG3+H7dTwIOR4LTFXDPojVBsFORBNN5y5c/c= -github.com/btcsuite/btcd v0.26.0/go.mod h1:7ft7+a/MoJHFouFopCb1zyiR9IWPlrcPVn6K/lJ1dcA= -github.com/btcsuite/btcd/chaincfg/v2 v2.0.0 h1:M/RTtXfXA9odC1RUEOyZFXj/NXKVHPYZXVjb60xTOok= -github.com/btcsuite/btcd/chaincfg/v2 v2.0.0/go.mod h1:rHgHIXYYfn70m25a+BJ9f9z7VZAsTiDQGB2XYaippGQ= -github.com/btcsuite/btcd/chainhash/v2 v2.0.0 h1:PMLlSloHJuEeB80XG9EjpXWNEKAZAMLl6YHZ6YsEuoA= -github.com/btcsuite/btcd/chainhash/v2 v2.0.0/go.mod h1:mKxcZ7oGTXE7IRV+sS9hP4EVBwc/SzfNR+52IsOP9j8= -github.com/btcsuite/btcd/wire/v2 v2.0.0 h1:mYSKzZZ0a1sK+aMhXzfDSVsSzRkWkU3x2U04TFRS2z8= -github.com/btcsuite/btcd/wire/v2 v2.0.0/go.mod h1:bGxkPkk8IiDvUo1D96wE03llBIk7p2MdWYRyAQwLmqM= -github.com/btcsuite/btclog v1.0.0 h1:sEkpKJMmfGiyZjADwEIgB1NSwMyfdD1FB8v6+w1T0Ns= -github.com/btcsuite/btclog v1.0.0/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.22.0-beta.0.20220207191057-4dc4ff7963b4/go.mod h1:7alexyj/lHlOtr2PJK7L/+HDJZpcGDn/pAU98r7DY08= +github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= +github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c h1:4HxD1lBUGUddhzgaNgrCPsFWd7cGYNpeFUgd9ZIgyM0= +github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ= github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084 h1:y3bvkt8ki0KX35eUEU8XShRHusz1S+55QwXUTmxn888= github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lightningnetwork/lnd/ticker v1.1.0 h1:ShoBiRP3pIxZHaETndfQ5kEe+S4NdAY1hiX7YbZ4QE4= github.com/lightningnetwork/lnd/ticker v1.1.0/go.mod h1:ubqbSVCn6RlE0LazXuBr7/Zi6QT0uQo++OgIRBxQUrk= -github.com/lightningnetwork/lnd/tor v1.1.8-0.20260615022959-a067468f0f45 h1:pHNN29jqVEfBcqHpeDJkFWnuT+1/uT91Vy6WLTqz1EA= -github.com/lightningnetwork/lnd/tor v1.1.8-0.20260615022959-a067468f0f45/go.mod h1:4h0LRuVGY8W9/HP32McLcaHwk6N/T5LJxSUSVYd4TKs= +github.com/lightningnetwork/lnd/tor v1.0.0 h1:wvEc7I+Y7IOtPglVP3cVBbYhiVhc7uTd7cMF9gQRzwA= +github.com/lightningnetwork/lnd/tor v1.0.0/go.mod h1:RDtaAdwfAm+ONuPYwUhNIH1RAvKPv+75lHPOegUcz64= github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/htlcswitch/circuit_map.go b/htlcswitch/circuit_map.go index 299abffcd..15d4b5ffc 100644 --- a/htlcswitch/circuit_map.go +++ b/htlcswitch/circuit_map.go @@ -6,7 +6,7 @@ import ( "fmt" "sync" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnutils" @@ -203,12 +203,12 @@ type CircuitMapConfig struct { // FetchAllOpenChannels is a function that fetches all currently open // channels from the channel database. - FetchAllOpenChannels func() ([]*chanstate.OpenChannel, error) + FetchAllOpenChannels func() ([]*channeldb.OpenChannel, error) // FetchClosedChannels is a function that fetches all closed channels // from the channel database. FetchClosedChannels func( - pendingOnly bool) ([]*chanstate.ChannelCloseSummary, error) + pendingOnly bool) ([]*channeldb.ChannelCloseSummary, error) // ExtractErrorEncrypter derives the shared secret used to encrypt // errors from the obfuscator's ephemeral public key. diff --git a/htlcswitch/circuit_map_test.go b/htlcswitch/circuit_map_test.go index 7b93fb0be..9bbb2c051 100644 --- a/htlcswitch/circuit_map_test.go +++ b/htlcswitch/circuit_map_test.go @@ -6,10 +6,9 @@ import ( "io" "testing" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" @@ -363,7 +362,7 @@ func createTestCloseChannelSummery(tx kvdb.RwTx, isPending bool, } outputPoint := wire.OutPoint{Hash: hash1, Index: 1} - ccs := &chanstate.ChannelCloseSummary{ + ccs := &channeldb.ChannelCloseSummary{ ChanPoint: outputPoint, ShortChanID: chanID, ChainHash: hash1, @@ -372,7 +371,7 @@ func createTestCloseChannelSummery(tx kvdb.RwTx, isPending bool, RemotePub: testEphemeralKey, Capacity: btcutil.Amount(10000), SettledBalance: btcutil.Amount(50000), - CloseType: chanstate.RemoteForceClose, + CloseType: channeldb.RemoteForceClose, IsPending: isPending, } var b bytes.Buffer @@ -390,7 +389,7 @@ func createTestCloseChannelSummery(tx kvdb.RwTx, isPending bool, func serializeChannelCloseSummary( w io.Writer, - cs *chanstate.ChannelCloseSummary) error { + cs *channeldb.ChannelCloseSummary) error { err := channeldb.WriteElements( w, diff --git a/htlcswitch/circuit_test.go b/htlcswitch/circuit_test.go index f21e97367..ddad11aca 100644 --- a/htlcswitch/circuit_test.go +++ b/htlcswitch/circuit_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch" diff --git a/htlcswitch/hop/forwarding_info.go b/htlcswitch/hop/forwarding_info.go index ed0a95d37..539e0db1f 100644 --- a/htlcswitch/hop/forwarding_info.go +++ b/htlcswitch/hop/forwarding_info.go @@ -1,8 +1,7 @@ package hop import ( - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/lightningnetwork/lnd/fn/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/lnwire" ) @@ -12,14 +11,10 @@ import ( // received within the incoming HTLC, to ensure that the prior hop didn't // tamper with the end-to-end routing information at all. type ForwardingInfo struct { - // NextHop identifies the next hop the HTLC should be forwarded to. In - // the common case it is a Left holding the short channel ID of the - // outgoing channel. For a blinded route whose recipient identifies the - // next hop by node ID (next_node_id) it is a Right holding the next - // node's compressed public key, which the switch's non-strict - // forwarding logic resolves to one of our channels with that peer. The - // zero value is a Left equal to hop.Exit, which denotes the exit hop. - NextHop fn.Either[lnwire.ShortChannelID, [33]byte] + // NextHop is the channel ID of the next hop. The received HTLC should + // be forwarded to this particular channel in order to continue the + // end-to-end route. + NextHop lnwire.ShortChannelID // AmountToForward is the amount of milli-satoshis that the receiving // node should forward to the next hop. @@ -40,51 +35,6 @@ type ForwardingInfo struct { PathID *chainhash.Hash } -// NewChannelNextHop returns a next-hop value that identifies the outgoing -// channel by its short channel ID, which is the common case. -func NewChannelNextHop( - scid lnwire.ShortChannelID) fn.Either[lnwire.ShortChannelID, [33]byte] { - - return fn.NewLeft[lnwire.ShortChannelID, [33]byte](scid) -} - -// NewNodeNextHop returns a next-hop value that identifies the next hop by the -// next node's compressed public key, as used by blinded routes that set -// next_node_id instead of a short channel ID. -func NewNodeNextHop( - nodeID [33]byte) fn.Either[lnwire.ShortChannelID, [33]byte] { - - return fn.NewRight[lnwire.ShortChannelID, [33]byte](nodeID) -} - -// IsExit returns true if this forwarding info denotes the exit hop, i.e. we are -// the final recipient of the HTLC. This is the case when the next hop is a -// short channel ID equal to hop.Exit. A node-ID next hop (used by some blinded -// routes) is always a forward, never the exit hop. -func (f ForwardingInfo) IsExit() bool { - var isExit bool - f.NextHop.WhenLeft(func(scid lnwire.ShortChannelID) { - isExit = scid == Exit - }) - - return isExit -} - -// NextHopChannel returns the short channel ID of the outgoing channel when the -// next hop is identified by channel ID (the common case). It returns None when -// the next hop is identified by node ID instead, in which case the outgoing -// channel is selected by the switch's non-strict forwarding. -func (f ForwardingInfo) NextHopChannel() fn.Option[lnwire.ShortChannelID] { - return f.NextHop.LeftToSome() -} - -// NextHopNode returns the next hop's compressed pubkey when it is identified by -// node ID (blinded routes via next_node_id), or None when identified by -// channel. -func (f ForwardingInfo) NextHopNode() fn.Option[[33]byte] { - return f.NextHop.RightToSome() -} - // FinalHtlcValidationResult describes the result of checking a final-hop // HTLC against the onion payload and supported final-hop CLTV range. type FinalHtlcValidationResult uint8 diff --git a/htlcswitch/hop/forwarding_info_test.go b/htlcswitch/hop/forwarding_info_test.go index 3ca5fbe3d..82a5ad0c6 100644 --- a/htlcswitch/hop/forwarding_info_test.go +++ b/htlcswitch/hop/forwarding_info_test.go @@ -3,7 +3,6 @@ package hop import ( "testing" - "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" ) @@ -22,7 +21,7 @@ func TestValidateFinalHtlc(t *testing.T) { fwdInfo := ForwardingInfo{ AmountToForward: amount, OutgoingCLTV: expiry, - NextHop: NewChannelNextHop(Exit), + NextHop: Exit, } testCases := []struct { @@ -116,7 +115,7 @@ func TestValidateFinalHtlc(t *testing.T) { fwdInfo: ForwardingInfo{ AmountToForward: amount, OutgoingCLTV: expiry + maxCltvDelta + 2, - NextHop: NewChannelNextHop(Exit), + NextHop: Exit, }, validateAmount: true, expected: FinalHtlcInvalidCltv, @@ -136,41 +135,3 @@ func TestValidateFinalHtlc(t *testing.T) { }) } } - -// TestForwardingInfoNextHop asserts the next-hop accessors for both the short -// channel ID (Left) and node ID (Right) representations, including the -// invariant that the zero-value ForwardingInfo denotes the exit hop. -func TestForwardingInfoNextHop(t *testing.T) { - t.Parallel() - - scid := lnwire.NewShortChanIDFromInt(12345) - nodeID := [33]byte{0x02} - - // The zero-value ForwardingInfo must denote the exit hop, since its - // NextHop is a Left equal to hop.Exit. Callers rely on this to detect - // that we are the final recipient. - zero := ForwardingInfo{} - require.True(t, zero.IsExit(), "zero value must be the exit hop") - require.Equal( - t, fn.Some(Exit), zero.NextHopChannel(), - "zero value must expose the Exit channel", - ) - - // An explicit channel next hop equal to Exit is likewise the exit hop. - exit := ForwardingInfo{NextHop: NewChannelNextHop(Exit)} - require.True(t, exit.IsExit()) - - // A channel next hop with a real SCID is a forward, and exposes that - // SCID through NextHopChannel. - channel := ForwardingInfo{NextHop: NewChannelNextHop(scid)} - require.False(t, channel.IsExit()) - require.Equal(t, fn.Some(scid), channel.NextHopChannel()) - - // A node-ID next hop is always a forward and never exposes an outgoing - // channel, since the switch selects one via non-strict forwarding. - node := ForwardingInfo{NextHop: NewNodeNextHop(nodeID)} - require.False(t, node.IsExit()) - require.Equal( - t, fn.None[lnwire.ShortChannelID](), node.NextHopChannel(), - ) -} diff --git a/htlcswitch/hop/fuzz_test.go b/htlcswitch/hop/fuzz_test.go index cbb4619a1..525194c38 100644 --- a/htlcswitch/hop/fuzz_test.go +++ b/htlcswitch/hop/fuzz_test.go @@ -39,25 +39,21 @@ func FuzzHopData(f *testing.F) { func FuzzHopPayload(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { - if len(data) > sphinx.MaxRoutingPayloadSize { + if len(data) > sphinx.MaxPayloadSize { return } r := bytes.NewReader(data) - var hopPayload1, hopPayload2 *sphinx.HopPayload - tlvGuaranteed := false + var hopPayload1, hopPayload2 sphinx.HopPayload - hopPayload1, err := sphinx.DecodeHopPayload(r, tlvGuaranteed) - if err != nil { + if err := hopPayload1.Decode(r); err != nil { return } var b bytes.Buffer require.NoError(t, hopPayload1.Encode(&b)) - - hopPayload2, err = sphinx.DecodeHopPayload(&b, tlvGuaranteed) - require.NoError(t, err) + require.NoError(t, hopPayload2.Decode(&b)) require.Equal(t, hopPayload1, hopPayload2) }) @@ -96,7 +92,7 @@ func hopFromPayload(p *Payload) (*route.Hop, uint64) { BlindingPoint: p.blindingPoint, CustomRecords: p.customRecords, TotalAmtMsat: p.totalAmtMsat, - }, p.FwdInfo.NextHop.UnwrapLeftOr(Exit).ToUint64() + }, p.FwdInfo.NextHop.ToUint64() } // FuzzPayloadFinal fuzzes final hop payloads, providing the additional context @@ -133,7 +129,7 @@ func FuzzPayloadIntermediateNoBlinding(f *testing.F) { func fuzzPayload(f *testing.F, finalPayload, updateAddBlinded bool) { f.Fuzz(func(t *testing.T, data []byte) { - if len(data) > sphinx.MaxRoutingPayloadSize { + if len(data) > sphinx.MaxPayloadSize { return } diff --git a/htlcswitch/hop/iterator.go b/htlcswitch/hop/iterator.go index f6f0393a7..cf04b88a1 100644 --- a/htlcswitch/hop/iterator.go +++ b/htlcswitch/hop/iterator.go @@ -8,9 +8,8 @@ import ( "sync" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" sphinx "github.com/lightningnetwork/lightning-onion" - "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/tlv" @@ -232,13 +231,6 @@ func parseAndValidateRecipientData(r *sphinxHopIterator, payload *Payload, return nil, routeRole, err } - // BOLT 4 requires a blinded hop to set exactly one of short_channel_id - // or next_node_id. Reject a hop that sets both here. - if routeData.ShortChannelID.IsSome() && routeData.NextNodeID.IsSome() { - return nil, routeRole, fmt.Errorf("blinded hop sets both " + - "short channel ID and next node ID") - } - // This is the final node in the blinded route. if isFinal { return deriveBlindedRouteFinalHopForwardingInfo( @@ -326,35 +318,14 @@ func deriveBlindedRouteForwardingInfo(r *sphinxHopIterator, ) } - // Determine the next hop. The recipient identifies it either by a short - // channel ID (the common case) or, as some implementations do for - // blinded routes, by the next node's ID (next_node_id). Setting both is - // already rejected upstream, and the dummy hop check above has handled - // a next_node_id that points at us. - var nextHop fn.Either[lnwire.ShortChannelID, [33]byte] - switch { - case routeData.ShortChannelID.IsSome(): - scid := routeData.ShortChannelID.UnwrapOr( - routeData.ShortChannelID.Zero(), - ) - nextHop = NewChannelNextHop(scid.Val) - - case routeData.NextNodeID.IsSome(): - nodeID := routeData.NextNodeID.UnwrapOr( - routeData.NextNodeID.Zero(), - ) - var pubKey [33]byte - copy(pubKey[:], nodeID.Val.SerializeCompressed()) - - nextHop = NewNodeNextHop(pubKey) - - default: - return nil, routeRole, fmt.Errorf("next hop not set for " + - "non-final blinded hop") + nextSCID, err := routeData.ShortChannelID.UnwrapOrErr( + fmt.Errorf("next SCID not set for non-final blinded hop"), + ) + if err != nil { + return nil, routeRole, err } - payload.FwdInfo = ForwardingInfo{ - NextHop: nextHop, + NextHop: nextSCID.Val, AmountToForward: fwdAmt, OutgoingCLTV: r.blindingKit.IncomingCltv - uint32( relayInfo.Val.CltvExpiryDelta, diff --git a/htlcswitch/hop/iterator_test.go b/htlcswitch/hop/iterator_test.go index 2297de654..b132a046d 100644 --- a/htlcswitch/hop/iterator_test.go +++ b/htlcswitch/hop/iterator_test.go @@ -9,7 +9,6 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/davecgh/go-spew/spew" sphinx "github.com/lightningnetwork/lightning-onion" - "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/tlv" @@ -34,9 +33,7 @@ func TestSphinxHopIteratorForwardingInstructions(t *testing.T) { // extract each type, no matter the payload type. nextAddrInt := binary.BigEndian.Uint64(hopData.NextAddress[:]) expectedFwdInfo := ForwardingInfo{ - NextHop: NewChannelNextHop( - lnwire.NewShortChanIDFromInt(nextAddrInt), - ), + NextHop: lnwire.NewShortChanIDFromInt(nextAddrInt), AmountToForward: lnwire.MilliSatoshi(hopData.ForwardAmount), OutgoingCLTV: hopData.OutgoingCltv, } @@ -142,6 +139,7 @@ func TestForwardingAmountCalc(t *testing.T) { } for _, testCase := range tests { + testCase := testCase t.Run(testCase.name, func(t *testing.T) { t.Parallel() @@ -305,378 +303,3 @@ func TestParseAndValidateRecipientData(t *testing.T) { }) } } - -// TestDeriveBlindedRouteNextHop asserts how a non-final blinded hop's next hop -// is derived from the recipient data: a short channel ID becomes a Left, a -// next_node_id becomes a Right, having both set is rejected with an error, and -// the absence of both is also an error. -func TestDeriveBlindedRouteNextHop(t *testing.T) { - t.Parallel() - - nodeKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - nextNodeKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - nextNodePub := nextNodeKey.PubKey() - - var nextNodeRaw [33]byte - copy(nextNodeRaw[:], nextNodePub.SerializeCompressed()) - - scid := lnwire.NewShortChanIDFromInt(1500) - - relayInfo := tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType10]( - record.PaymentRelayInfo{ - CltvExpiryDelta: 10, - BaseFee: 100, - FeeRate: 0, - }, - )) - constraints := tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType12]( - record.PaymentConstraints{ - MaxCltvExpiry: 1000, - HtlcMinimumMsat: lnwire.MilliSatoshi(1), - }, - )) - scidRecord := tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType2](scid)) - nodeIDRecord := tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType4](nextNodePub), - ) - - tests := []struct { - name string - data *record.BlindedRouteData - expectedHop fn.Either[lnwire.ShortChannelID, [33]byte] - expectedErr string - }{ - { - name: "short channel id only", - data: &record.BlindedRouteData{ - ShortChannelID: scidRecord, - RelayInfo: relayInfo, - Constraints: constraints, - }, - expectedHop: NewChannelNextHop(scid), - }, - { - name: "next node id only", - data: &record.BlindedRouteData{ - NextNodeID: nodeIDRecord, - RelayInfo: relayInfo, - Constraints: constraints, - }, - expectedHop: NewNodeNextHop(nextNodeRaw), - }, - { - // BOLT 4 requires a non-final blinded hop to set - // exactly one of short_channel_id or next_node_id, so - // setting both must be rejected. - name: "both present is an error", - data: &record.BlindedRouteData{ - ShortChannelID: scidRecord, - NextNodeID: nodeIDRecord, - RelayInfo: relayInfo, - Constraints: constraints, - }, - expectedErr: "both short channel ID and next node ID", - }, - { - name: "neither present", - data: &record.BlindedRouteData{ - RelayInfo: relayInfo, - Constraints: constraints, - }, - expectedErr: "next hop not set", - }, - } - - for _, testCase := range tests { - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - data, err := record.EncodeBlindedRouteData( - testCase.data, - ) - require.NoError(t, err) - - kit := BlindingKit{ - Processor: &mockProcessor{}, - IncomingAmount: 10000, - IncomingCltv: 500, - UpdateAddBlinding: tlv.SomeRecordT( - //nolint:ll - tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](&btcec.PublicKey{}), - ), - } - iterator := &sphinxHopIterator{ - blindingKit: kit, - router: sphinx.NewRouter( - &sphinx.PrivKeyECDH{PrivKey: nodeKey}, - sphinx.NewMemoryReplayLog(), - ), - } - - payload, _, err := parseAndValidateRecipientData( - iterator, &Payload{encryptedData: data}, - false, RouteRoleCleartext, - ) - - if testCase.expectedErr != "" { - require.ErrorContains( - t, err, testCase.expectedErr, - ) - - return - } - - require.NoError(t, err) - require.Equal( - t, testCase.expectedHop, - payload.FwdInfo.NextHop, - ) - }) - } -} - -// TestBlindedHopBothNextHopFieldsRejected asserts that a blinded hop setting -// both short_channel_id and next_node_id is rejected for a final hop and for a -// dummy hop (next_node_id == our own pubkey), not just an intermediate hop. The -// mutual-exclusivity check runs before the final-hop and dummy-hop branches, so -// none of them accept a hop that violates BOLT 4. The intermediate case is -// already covered by TestDeriveBlindedRouteNextHop. -func TestBlindedHopBothNextHopFieldsRejected(t *testing.T) { - t.Parallel() - - nodeKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - nodePub := nodeKey.PubKey() - - // Route data that sets both short_channel_id and next_node_id. The node - // ID is our own pubkey, which for a non-final hop would otherwise - // signal a dummy hop; the both-set check must still fire first. - bothData := &record.BlindedRouteData{ - ShortChannelID: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType2]( - lnwire.NewShortChanIDFromInt(1500), - ), - ), - NextNodeID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType4](nodePub), - ), - RelayInfo: tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType10]( - record.PaymentRelayInfo{ - CltvExpiryDelta: 10, - BaseFee: 100, - FeeRate: 0, - }, - )), - Constraints: tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType12]( - record.PaymentConstraints{ - MaxCltvExpiry: 1000, - HtlcMinimumMsat: lnwire.MilliSatoshi(1), - }, - )), - } - data, err := record.EncodeBlindedRouteData(bothData) - require.NoError(t, err) - - // Both the dummy/forwarding path (isFinal=false, next_node_id points at - // us) and the final path (isFinal=true) must reject the hop. - for _, isFinal := range []bool{false, true} { - name := "forwarding hop" - if isFinal { - name = "final hop" - } - - t.Run(name, func(t *testing.T) { - kit := BlindingKit{ - Processor: &mockProcessor{}, - IncomingAmount: 10000, - IncomingCltv: 500, - UpdateAddBlinding: tlv.SomeRecordT( - //nolint:ll - tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](&btcec.PublicKey{}), - ), - } - iterator := &sphinxHopIterator{ - blindingKit: kit, - router: sphinx.NewRouter( - &sphinx.PrivKeyECDH{PrivKey: nodeKey}, - sphinx.NewMemoryReplayLog(), - ), - } - - _, _, err := parseAndValidateRecipientData( - iterator, &Payload{encryptedData: data}, - isFinal, RouteRoleCleartext, - ) - require.ErrorContains( - t, err, - "both short channel ID and next node ID", - ) - }) - } -} - -// TestBlindedRouteDummyHopPeeledLocally asserts that a blinded route hop where -// next_node_id is our own public key is recognized as a dummy hop and is peeled -// locally rather than falling through to the generic next_node_id forwarding -// branch. -func TestBlindedRouteDummyHopPeeledLocally(t *testing.T) { - t.Parallel() - - // Construct a realistic onion packet that contains a blinded final hop. - // We'll use this to test that we can peel a dummy hop locally and - // extract the forwarding information from the decrypted final hop's - // payload. - nodeKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - nodePub := nodeKey.PubKey() - - relayInfo := tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType10]( - record.PaymentRelayInfo{ - CltvExpiryDelta: 10, - BaseFee: 100, - FeeRate: 0, - }, - )) - constraints := tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType12]( - record.PaymentConstraints{ - MaxCltvExpiry: 1000, - HtlcMinimumMsat: lnwire.MilliSatoshi(1), - }, - )) - - // Set next_node_id to our own public key. This signals a dummy hop. - nodeIDRecord := tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType4](nodePub), - ) - - // We'll generate a valid, cryptographically blinded final hop's payload - // using sphinx.BuildBlindedPath. This contains the PathID. - secret := make([]byte, 32) - secret[0] = 2 - finalHopData := &record.BlindedRouteData{ - PathID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType6](secret), - ), - } - finalHopDataBytes, err := record.EncodeBlindedRouteData(finalHopData) - require.NoError(t, err) - - hopInfo := &sphinx.HopInfo{ - NodePub: nodePub, - PlainText: finalHopDataBytes, - } - - blindingSessionKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - blindedPathInfo, err := sphinx.BuildBlindedPath( - blindingSessionKey, []*sphinx.HopInfo{hopInfo}, - ) - require.NoError(t, err) - - // Since we are peeling a dummy hop locally, we want the next blinding - // override to be the blinding point generated for our blinded final - // hop. - dummyHopData := &record.BlindedRouteData{ - NextNodeID: nodeIDRecord, - RelayInfo: relayInfo, - Constraints: constraints, - NextBlindingOverride: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType8]( - blindedPathInfo.Path.BlindingPoint, - ), - ), - } - - data, err := record.EncodeBlindedRouteData(dummyHopData) - require.NoError(t, err) - - // Encode a valid TLV payload for the next hop (which we will peel). - var hop2Buffer bytes.Buffer - amt := uint64(10000) - cltv := uint32(500) - encryptedDataRecord := record.NewEncryptedDataRecord( - &blindedPathInfo.Path.BlindedHops[0].CipherText, - ) - tlvRecords := []tlv.Record{ - record.NewAmtToFwdRecord(&amt), - record.NewLockTimeRecord(&cltv), - encryptedDataRecord, - } - tlvStream, err := tlv.NewStream(tlvRecords...) - require.NoError(t, err) - err = tlvStream.Encode(&hop2Buffer) - require.NoError(t, err) - - hopPayload, err := sphinx.NewTLVHopPayload(hop2Buffer.Bytes()) - require.NoError(t, err) - - // Create a valid 1-hop onion path using our blinded public key. - var paymentPath sphinx.PaymentPath - paymentPath[0] = sphinx.OnionHop{ - NodePub: *blindedPathInfo.Path.BlindedHops[0].BlindedNodePub, - HopPayload: hopPayload, - } - - sessionKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - rHash := [32]byte{1} - - // Generate a cryptographically valid onion packet for this path. - onionPacket, err := sphinx.NewOnionPacket( - &paymentPath, sessionKey, rHash[:], - sphinx.DeterministicPacketFiller, - ) - require.NoError(t, err) - - // Simulate an incoming HTLC with a blinding point and a valid onion - // packet. The blinding point is used to decrypt the dummy hop's - // payload, which contains the blinding point for the next hop (the - // blinded final hop). - kit := BlindingKit{ - Processor: &mockProcessor{}, - IncomingAmount: 12000, - IncomingCltv: 510, - UpdateAddBlinding: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType]( - nodePub, - ), - ), - } - - iterator := &sphinxHopIterator{ - blindingKit: kit, - rHash: rHash[:], - router: sphinx.NewRouter( - &sphinx.PrivKeyECDH{PrivKey: nodeKey}, - sphinx.NewMemoryReplayLog(), - ), - // Set our valid onion packet to be peeled. - processedPacket: &sphinx.ProcessedPacket{ - NextPacket: onionPacket, - }, - } - - // When we parse and validate the recipient data, it should enter the - // dummy-hop peeling path. Since our onion packet is valid and matches - // our private key, it should be successfully peeled and parsed. - pld, _, err := parseAndValidateRecipientData( - iterator, &Payload{encryptedData: data}, - false, RouteRoleCleartext, - ) - - // Assert that we successfully peeled the dummy hop and extracted the - // decrypted final payload. - require.NoError(t, err) - require.NotNil(t, pld) - - fwdInfo := pld.ForwardingInfo() - require.Equal(t, lnwire.MilliSatoshi(0), fwdInfo.AmountToForward) - require.Equal(t, uint32(0), fwdInfo.OutgoingCLTV) - require.NotNil(t, fwdInfo.PathID) - require.Equal(t, secret, fwdInfo.PathID[:]) -} diff --git a/htlcswitch/hop/payload.go b/htlcswitch/hop/payload.go index 5f6046775..14a0813e8 100644 --- a/htlcswitch/hop/payload.go +++ b/htlcswitch/hop/payload.go @@ -6,7 +6,7 @@ import ( "io" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" @@ -126,9 +126,7 @@ func NewLegacyPayload(f *sphinx.HopData) *Payload { return &Payload{ FwdInfo: ForwardingInfo{ - NextHop: NewChannelNextHop( - lnwire.NewShortChanIDFromInt(nextHop), - ), + NextHop: lnwire.NewShortChanIDFromInt(nextHop), AmountToForward: lnwire.MilliSatoshi(f.ForwardAmount), OutgoingCLTV: f.OutgoingCltv, }, @@ -203,9 +201,7 @@ func ParseTLVPayload(r io.Reader) (*Payload, map[tlv.Type][]byte, error) { return &Payload{ FwdInfo: ForwardingInfo{ - NextHop: NewChannelNextHop( - lnwire.NewShortChanIDFromInt(cid), - ), + NextHop: lnwire.NewShortChanIDFromInt(cid), AmountToForward: lnwire.MilliSatoshi(amt), OutgoingCLTV: cltv, }, diff --git a/htlcswitch/hop/payload_test.go b/htlcswitch/hop/payload_test.go index 7b3e56836..bd0081cb9 100644 --- a/htlcswitch/hop/payload_test.go +++ b/htlcswitch/hop/payload_test.go @@ -758,6 +758,7 @@ func TestValidateBlindedRouteData(t *testing.T) { } for _, testCase := range tests { + testCase := testCase t.Run(testCase.name, func(t *testing.T) { err := hop.ValidateBlindedRouteData( diff --git a/htlcswitch/htlcnotifier.go b/htlcswitch/htlcnotifier.go index ac9bb3b06..4d4d33374 100644 --- a/htlcswitch/htlcnotifier.go +++ b/htlcswitch/htlcnotifier.go @@ -466,14 +466,6 @@ func getEventType(pkt *htlcPacket) HtlcEventType { case pkt.incomingChanID == hop.Source: return HtlcEventTypeSend - // A node-ID (pubkey) next hop has no outgoing SCID until the switch - // selects one, so outgoingChanID may still be hop.Exit on an early - // failure. Such a hop is always a forward, never the exit, so classify - // it before the hop.Exit check to avoid reporting a forward as a - // receive. - case pkt.outgoingHop.IsRight(): - return HtlcEventTypeForward - case pkt.outgoingChanID == hop.Exit: return HtlcEventTypeReceive diff --git a/htlcswitch/htlcnotifier_test.go b/htlcswitch/htlcnotifier_test.go deleted file mode 100644 index f1f07225e..000000000 --- a/htlcswitch/htlcnotifier_test.go +++ /dev/null @@ -1,139 +0,0 @@ -package htlcswitch - -import ( - "testing" - "time" - - "github.com/lightningnetwork/lnd/htlcswitch/hop" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/stretchr/testify/require" -) - -// TestGetEventType asserts how getEventType classifies an htlcPacket as a send, -// receive or forward event. -func TestGetEventType(t *testing.T) { - t.Parallel() - - var nodeID [33]byte - nodeID[0] = 0x02 - - tests := []struct { - name string - pkt *htlcPacket - want HtlcEventType - }{ - { - name: "send", - pkt: &htlcPacket{incomingChanID: hop.Source}, - want: HtlcEventTypeSend, - }, - { - name: "receive at exit hop", - pkt: &htlcPacket{ - incomingChanID: lnwire.NewShortChanIDFromInt(1), - outgoingChanID: hop.Exit, - }, - want: HtlcEventTypeReceive, - }, - { - name: "forward by channel ID", - pkt: &htlcPacket{ - incomingChanID: lnwire.NewShortChanIDFromInt(1), - outgoingChanID: lnwire.NewShortChanIDFromInt(2), - }, - want: HtlcEventTypeForward, - }, - { - // A node-ID forward that failed before channel - // selection has outgoingChanID == hop.Exit but a Right - // (pubkey) next hop, so it must classify as a forward. - name: "forward by node ID before selection", - pkt: &htlcPacket{ - incomingChanID: lnwire.NewShortChanIDFromInt(1), - outgoingChanID: hop.Exit, - outgoingHop: hop.NewNodeNextHop(nodeID), - }, - want: HtlcEventTypeForward, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - require.Equal(t, tc.want, getEventType(tc.pkt)) - }) - } -} - -// TestGetEventTypeNodeIDReconstructedPackets asserts that node-ID forward -// packets reconstructed via failAddPacket and interceptedForward.resolve -// preserve outgoingHop and are correctly classified as HtlcEventTypeForward by -// getEventType. -func TestGetEventTypeNodeIDReconstructedPackets(t *testing.T) { - t.Parallel() - - var nodeID [33]byte - nodeID[0] = 0x02 - - inChanID := lnwire.NewShortChanIDFromInt(1) - chanID := lnwire.ChannelID{1} - - // Create a switch with a mailOrchestrator and mailbox. - s := &Switch{ - mailOrchestrator: newMailOrchestrator(&mailOrchConfig{}), - } - mailbox := s.mailOrchestrator.GetOrCreateMailBox(chanID, inChanID) - s.mailOrchestrator.BindLiveShortChanID(mailbox, chanID, inChanID) - - // 1. Verify failAddPacket reconstruction. - origPkt := &htlcPacket{ - incomingChanID: inChanID, - incomingHTLCID: 42, - outgoingChanID: hop.Exit, - outgoingHop: hop.NewNodeNextHop(nodeID), - obfuscator: NewMockObfuscator(), - } - linkErr := NewLinkError(&lnwire.FailUnknownNextPeer{}) - - err := s.failAddPacket(origPkt, linkErr) - require.Equal(t, linkErr, err) - - select { - case failPkt := <-mailbox.PacketOutBox(): - require.True(t, failPkt.outgoingHop.IsRight()) - require.Equal( - t, HtlcEventTypeForward, getEventType(failPkt), - "failAddPacket must classify as forward", - ) - case <-time.After(time.Second): - t.Fatal("failAddPacket did not deliver packet to mailbox") - } - - // 2. Verify interceptedForward.resolve reconstruction. - resolvePkt := &htlcPacket{ - incomingChanID: inChanID, - incomingHTLCID: 43, - outgoingChanID: hop.Exit, - outgoingHop: hop.NewNodeNextHop(nodeID), - obfuscator: NewMockObfuscator(), - } - fwd := &interceptedForward{ - htlcSwitch: s, - packet: resolvePkt, - } - - err = fwd.resolve(&lnwire.UpdateFailHTLC{}) - require.NoError(t, err) - - select { - case resPkt := <-mailbox.PacketOutBox(): - require.True(t, resPkt.outgoingHop.IsRight()) - require.Equal( - t, HtlcEventTypeForward, getEventType(resPkt), - "interceptedForward.resolve must classify as forward", - ) - case <-time.After(time.Second): - t.Fatal("resolve did not deliver packet to mailbox") - } -} diff --git a/htlcswitch/interceptable_switch.go b/htlcswitch/interceptable_switch.go index eea81078e..ac2d24ccc 100644 --- a/htlcswitch/interceptable_switch.go +++ b/htlcswitch/interceptable_switch.go @@ -4,7 +4,6 @@ import ( "crypto/sha256" "errors" "fmt" - "math" "sync" "sync/atomic" @@ -662,35 +661,13 @@ func (s *InterceptableSwitch) removeOnChainIntercept(key models.CircuitKey) { } } -// handleExpired checks that the htlc's expiry is within the range that can be -// offered to the interceptor. Expiries near the channel force-close broadcast -// height and expiries whose auto-fail height cannot be represented are failed -// back. +// handleExpired checks that the htlc isn't too close to the channel +// force-close broadcast height. If it is, it is cancelled back. func (s *InterceptableSwitch) handleExpired(fwd *interceptedForward) ( bool, error) { height := uint32(s.currentHeight) - incomingTimeout := fwd.packet.incomingTimeout - - // The interceptor auto-fail height is the incoming timeout less the - // reject delta and is exposed as an int32 block height. Calculate it in - // int64 so that we can check the representable range before conversion. - autoFailHeight := int64(incomingTimeout) - int64(s.cltvRejectDelta) - if autoFailHeight > math.MaxInt32 { - log.Debugf("Interception rejected because htlc expires too "+ - "far in the future: circuit=%v, height=%v, "+ - "incoming_timeout=%v", fwd.packet.inKey(), height, - incomingTimeout) - - err := fwd.FailWithCode(lnwire.CodeExpiryTooFar) - if err != nil { - return false, err - } - - return true, nil - } - - if incomingTimeout >= height+s.cltvInterceptDelta { + if fwd.packet.incomingTimeout >= height+s.cltvInterceptDelta { return false, nil } @@ -698,7 +675,7 @@ func (s *InterceptableSwitch) handleExpired(fwd *interceptedForward) ( "expires too soon: circuit=%v, "+ "height=%v, incoming_timeout=%v", fwd.packet.inKey(), height, - incomingTimeout) + fwd.packet.incomingTimeout) err := fwd.FailWithCode( lnwire.CodeExpiryTooSoon, @@ -728,7 +705,6 @@ func (f *interceptedForward) Packet() InterceptedPacket { HtlcID: f.packet.incomingHTLCID, }, OutgoingChanID: f.packet.outgoingChanID, - OutgoingNodeID: f.packet.outgoingHop.RightToSome(), Hash: f.htlc.PaymentHash, OutgoingExpiry: f.htlc.Expiry, OutgoingAmount: f.htlc.Amount, @@ -882,9 +858,6 @@ func (f *interceptedForward) FailWithCode(code lnwire.FailCode) error { failureMsg = lnwire.NewExpiryTooSoon(*update) - case lnwire.CodeExpiryTooFar: - failureMsg = &lnwire.FailExpiryTooFar{} - default: return ErrUnsupportedFailureCode } @@ -918,7 +891,6 @@ func (f *interceptedForward) resolve(message lnwire.Message) error { incomingChanID: f.packet.incomingChanID, incomingHTLCID: f.packet.incomingHTLCID, outgoingChanID: f.packet.outgoingChanID, - outgoingHop: f.packet.outgoingHop, outgoingHTLCID: f.packet.outgoingHTLCID, isResolution: true, circuit: f.packet.circuit, diff --git a/htlcswitch/interfaces.go b/htlcswitch/interfaces.go index 2a2b834cb..6a56b181e 100644 --- a/htlcswitch/interfaces.go +++ b/htlcswitch/interfaces.go @@ -3,10 +3,9 @@ package htlcswitch import ( "context" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/invoices" @@ -355,7 +354,7 @@ type TowerClient interface { // parameters within the client. This should be called during link // startup to ensure that the client is able to support the link during // operation. - RegisterChannel(lnwire.ChannelID, chanstate.ChannelType) error + RegisterChannel(lnwire.ChannelID, channeldb.ChannelType) error // BackupState initiates a request to back up a particular revoked // state. If the method returns nil, the backup is guaranteed to be @@ -382,14 +381,6 @@ type InterceptableHtlcForwarder interface { // and resolve it later or let the switch execute its default behavior. type ForwardInterceptor func(InterceptedPacket) error -// NodeIDForwardSCID is the sentinel outgoing SCID reported to HTLC interceptor -// clients (at the RPC boundary) for a next hop identified by node ID (BOLT 4 -// next_node_id) rather than by channel. All bits are set, an out-of-range value -// that can never match a real or alias channel, so a client switching on a zero -// SCID to detect the exit hop does not read the forward as a final receive. The -// pubkey is in InterceptedPacket.OutgoingNodeID. -const NodeIDForwardSCID uint64 = ^uint64(0) - // InterceptedPacket contains the relevant information for the interceptor about // an HTLC. type InterceptedPacket struct { @@ -397,17 +388,9 @@ type InterceptedPacket struct { // packet. IncomingCircuit models.CircuitKey - // OutgoingChanID is the destination channel for this packet. For a - // node-ID next hop with no concrete channel known yet it is hop.Exit - // and OutgoingNodeID holds the pubkey; the RPC layer maps that to the - // NodeIDForwardSCID sentinel before reporting it to a client. + // OutgoingChanID is the destination channel for this packet. OutgoingChanID lnwire.ShortChannelID - // OutgoingNodeID is the next hop's compressed pubkey for a blinded - // route that identifies it by node ID (next_node_id). None in the - // common channel-ID case. - OutgoingNodeID fn.Option[[33]byte] - // Hash is the payment hash of the htlc. Hash lntypes.Hash diff --git a/htlcswitch/link.go b/htlcswitch/link.go index 1fbb4c2f8..056403cb3 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -12,11 +12,10 @@ import ( "sync/atomic" "time" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btclog/v2" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" @@ -257,10 +256,6 @@ type ChannelLinkConfig struct { // ChannelNotifier when a channel link become inactive. NotifyInactiveLinkEvent func(wire.OutPoint) - // NotifyChannelUpdate allows the link to tell the ChannelNotifier when - // a channel's state has been updated. - NotifyChannelUpdate func(*chanstate.OpenChannel) - // HtlcNotifier is an instance of a htlcNotifier which we will pipe htlc // events through. HtlcNotifier htlcNotifier @@ -295,9 +290,9 @@ type ChannelLinkConfig struct { // restrict the flow of HTLCs and fee updates. MaxFeeExposure lnwire.MilliSatoshi - // ShouldFwdExpAccountability is a closure that indicates whether the - // link should forward experimental accountability signals. - ShouldFwdExpAccountability func() bool + // ShouldFwdExpEndorsement is a closure that indicates whether the link + // should forward experimental endorsement signals. + ShouldFwdExpEndorsement func() bool // AuxTrafficShaper is an optional auxiliary traffic shaper that can be // used to manage the bandwidth of the link. @@ -363,14 +358,6 @@ type channelLink struct { // forwarded sent by the switch. mailBox MailBox - // mailBoxIngressMtx guards mailBoxIngressFailed and serializes peer - // message admission into the mailbox. - mailBoxIngressMtx sync.Mutex - - // mailBoxIngressFailed is set after the first peer message admission - // failure so later messages cannot be processed across a gap. - mailBoxIngressFailed bool - // upstream is a channel that new messages sent from the remote peer to // the local peer will be sent across. upstream chan lnwire.Message @@ -403,11 +390,6 @@ type channelLink struct { // log is a link-specific logging instance. log btclog.Logger - // warningLogged and unknownMessageLogged track whether each non-fatal - // message class has already been recorded for this link lifetime. - warningLogged bool - unknownMessageLogged bool - // isOutgoingAddBlocked tracks whether the channelLink can send an // UpdateAddHTLC. isOutgoingAddBlocked atomic.Bool @@ -643,20 +625,8 @@ func (l *channelLink) Stop() { l.log.Info("stopping") - // Stop the htlcManager goroutine first. This is critical: htlcManager - // is the sole caller of NotifyExitHopHtlc, which registers new hodl - // subscriptions. We must guarantee it has fully exited before we - // remove subscriptions and stop the hodlQueue. Without this ordering, - // a RevokeAndAck processed in the race window between hodlQueue.Stop() - // and cg.Quit() can register an orphaned subscription against a dead - // queue, causing notifyHodlSubscribers to block permanently and - // deadlock the entire invoice registry. - l.cg.Quit() - l.cg.WgWait() - - // htlcManager has fully exited — no new hodl subscriptions can be - // registered from this point on. It is now safe to remove all - // subscriptions and tear down the queue. + // As the link is stopping, we are no longer interested in htlc + // resolutions coming from the invoice registry. l.cfg.Registry.HodlUnsubscribeAll(l.hodlQueue.ChanIn()) if l.cfg.ChainEvents.Cancel != nil { @@ -677,6 +647,9 @@ func (l *channelLink) Stop() { l.hodlQueue.Stop() } + l.cg.Quit() + l.cg.WgWait() + // Now that the htlcManager has completely exited, reset the packet // courier. This allows the mailbox to revaluate any lingering Adds that // were delivered but didn't make it on a commitment to be failed back @@ -932,8 +905,7 @@ func (l *channelLink) syncChanStates(ctx context.Context) error { // First, we'll generate our ChanSync message to send to the other // side. Based on this message, the remote party will decide if they - // need to retransmit any data or not. The nonce format is derived from - // the channel type internally. + // need to retransmit any data or not. localChanSyncMsg, err := chanState.ChanSyncMsg() if err != nil { return fmt.Errorf("unable to generate chan sync message for "+ @@ -983,21 +955,10 @@ func (l *channelLink) syncChanStates(ctx context.Context) error { // If this is a taproot channel, then we'll send the // very same nonce that we sent above, as they should - // take the latest verification nonce we send. We use - // LocalVerNonce to extract from the correct field - // (map-based for final, legacy for staging). + // take the latest verification nonce we send. if chanState.ChanType.IsTaproot() { - nonce, err := localChanSyncMsg.LocalVerNonce( - chanState.FundingOutpoint.Hash, - ) - if err != nil { - return fmt.Errorf("unable to "+ - "extract nonce for "+ - "channel_ready resend: %w", - err) - } - - channelReadyMsg.NextLocalNonce = lnwire.SomeMusig2Nonce(nonce) //nolint:ll + //nolint:ll + channelReadyMsg.NextLocalNonce = localChanSyncMsg.LocalNonce } // For channels that negotiated the option-scid-alias @@ -1852,13 +1813,6 @@ func (l *channelLink) handleUpstreamMsg(ctx context.Context, case *lnwire.CommitSig: err = l.processRemoteCommitSig(ctx, msg) - // At this point our local commitment state has been irrevocably - // committed to and our balances are updated. We notify our - // subscribers that the channel state has been updated. - if err == nil { - l.cfg.NotifyChannelUpdate(l.channel.ChannelState()) - } - case *lnwire.RevokeAndAck: err = l.processRemoteRevokeAndAck(ctx, msg) @@ -1875,20 +1829,14 @@ func (l *channelLink) handleUpstreamMsg(ctx context.Context, // log it and move on. We choose not to disconnect from our peer, // although we "MAY" do so according to the specification. case *lnwire.Warning: - if !l.warningLogged { - l.log.Warnf("received warning message from peer: %v", - msg.Warning()) - l.warningLogged = true - } + l.log.Warnf("received warning message from peer: %v", + msg.Warning()) case *lnwire.Error: l.processRemoteError(msg) default: - if !l.unknownMessageLogged { - l.log.Warnf("received unknown message of type %T", msg) - l.unknownMessageLogged = true - } + l.log.Warnf("received unknown message of type %T", msg) } if err != nil { @@ -2392,7 +2340,7 @@ type dustClosure func(feerate chainfee.SatPerKWeight, incoming bool, whoseCommit lntypes.ChannelParty, amt btcutil.Amount) bool // dustHelper is used to construct the dustClosure. -func dustHelper(chantype chanstate.ChannelType, localDustLimit, +func dustHelper(chantype channeldb.ChannelType, localDustLimit, remoteDustLimit btcutil.Amount) dustClosure { isDust := func(feerate chainfee.SatPerKWeight, incoming bool, @@ -2687,10 +2635,7 @@ func (l *channelLink) canSendHtlc(policy models.ForwardingPolicy, htlcBlob = fn.Some(blob) } - // Check if this link can handle the traffic. - return l.AuxBandwidth( - amt, l.ShortChanID(), htlcBlob, ts, - ) + return l.AuxBandwidth(amt, originalScid, htlcBlob, ts) }, ).Unpack() if externalErr != nil { @@ -2823,23 +2768,10 @@ func (l *channelLink) HandleChannelUpdate(message lnwire.Message) { default: } - l.mailBoxIngressMtx.Lock() - if l.mailBoxIngressFailed { - l.mailBoxIngressMtx.Unlock() - return - } - err := l.mailBox.AddMessage(message) - if err == nil { - l.mailBoxIngressMtx.Unlock() - return + if err != nil { + l.log.Errorf("failed to add Message to mailbox: %v", err) } - - l.mailBoxIngressFailed = true - l.mailBoxIngressMtx.Unlock() - - l.log.Errorf("failed to add Message to mailbox: %v", err) - go l.cfg.Peer.Disconnect(err) } // updateChannelFee updates the commitment fee-per-kw on this channel by @@ -3221,8 +3153,8 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) { continue } - switch { - case fwdInfo.IsExit(): + switch fwdInfo.NextHop { + case hop.Exit: err := l.processExitHop( add, sourceRef, obfuscator, fwdInfo, heightNow, pld, @@ -3246,11 +3178,11 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) { continue } - accountableValue := l.experimentalAccountability( + endorseValue := l.experimentalEndorsement( record.CustomSet(add.CustomRecords), ) - accountableType := uint64( - lnwire.ExperimentalAccountableType, + endorseType := uint64( + lnwire.ExperimentalEndorsementType, ) switch fwdPkg.State { @@ -3274,9 +3206,9 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) { BlindingPoint: fwdInfo.NextBlinding, } - accountableValue.WhenSome(func(e byte) { + endorseValue.WhenSome(func(e byte) { custRecords := map[uint64][]byte{ - accountableType: {e}, + endorseType: {e}, } outgoingAdd.CustomRecords = custRecords @@ -3300,8 +3232,7 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) { updatePacket := &htlcPacket{ incomingChanID: l.ShortChanID(), incomingHTLCID: add.ID, - outgoingChanID: fwdInfo.NextHopChannel().UnwrapOr(hop.Exit), - outgoingHop: fwdInfo.NextHop, + outgoingChanID: fwdInfo.NextHop, sourceRef: &sourceRef, incomingAmount: add.Amount, amount: outgoingAdd.Amount, @@ -3333,9 +3264,9 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) { BlindingPoint: fwdInfo.NextBlinding, } - accountableValue.WhenSome(func(e byte) { + endorseValue.WhenSome(func(e byte) { addMsg.CustomRecords = map[uint64][]byte{ - accountableType: {e}, + endorseType: {e}, } }) @@ -3378,8 +3309,7 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) { updatePacket := &htlcPacket{ incomingChanID: l.ShortChanID(), incomingHTLCID: add.ID, - outgoingChanID: fwdInfo.NextHopChannel().UnwrapOr(hop.Exit), - outgoingHop: fwdInfo.NextHop, + outgoingChanID: fwdInfo.NextHop, sourceRef: &sourceRef, incomingAmount: add.Amount, amount: addMsg.Amount, @@ -3425,42 +3355,44 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) { l.forwardBatch(reforward, switchPackets...) } -// experimentalAccountability returns the value to set for our outgoing -// experimental accountable field. It only considers the accountability bit, -// other custom records present are not considered for forwarding. -func (l *channelLink) experimentalAccountability( +// experimentalEndorsement returns the value to set for our outgoing +// experimental endorsement field, and a boolean indicating whether it should +// be populated on the outgoing htlc. +func (l *channelLink) experimentalEndorsement( customUpdateAdd record.CustomSet) fn.Option[byte] { - if !l.cfg.ShouldFwdExpAccountability() { + // Only relay experimental signal if we are within the experiment + // period. + if !l.cfg.ShouldFwdExpEndorsement() { return fn.None[byte]() } // If we don't have any custom records or the experimental field is // not set, just forward a zero value. if len(customUpdateAdd) == 0 { - return fn.Some[byte](lnwire.ExperimentalUnaccountable) + return fn.Some[byte](lnwire.ExperimentalUnendorsed) } - t := uint64(lnwire.ExperimentalAccountableType) + t := uint64(lnwire.ExperimentalEndorsementType) value, set := customUpdateAdd[t] if !set { - return fn.Some[byte](lnwire.ExperimentalUnaccountable) + return fn.Some[byte](lnwire.ExperimentalUnendorsed) } // We expect at least one byte for this field, consider it invalid if // it has no data and just forward a zero value. if len(value) == 0 { - return fn.Some[byte](lnwire.ExperimentalUnaccountable) + return fn.Some[byte](lnwire.ExperimentalUnendorsed) } - // Only forward accountable if the incoming link is accountable. - if value[0] == lnwire.ExperimentalAccountable { - return fn.Some[byte](lnwire.ExperimentalAccountable) + // Only forward endorsed if the incoming link is endorsed. + if value[0] == lnwire.ExperimentalEndorsed { + return fn.Some[byte](lnwire.ExperimentalEndorsed) } - // Forward as unaccountable otherwise, including cases where we've + // Forward as unendorsed otherwise, including cases where we've // received an invalid value that uses more than 3 bits of information. - return fn.Some[byte](lnwire.ExperimentalUnaccountable) + return fn.Some[byte](lnwire.ExperimentalUnendorsed) } // processExitHop handles an htlc for which this link is the exit hop. It @@ -4615,16 +4547,6 @@ func (l *channelLink) processRemoteRevokeAndAck(ctx context.Context, // processRemoteUpdateFee takes an `UpdateFee` msg sent from the remote and // processes it. func (l *channelLink) processRemoteUpdateFee(msg *lnwire.UpdateFee) error { - // BOLT 2 only permits the channel initiator to send fee updates. - // Validate the sender's role before applying message-specific - // calculations. - if l.channel.IsInitiator() { - err := fmt.Errorf("received fee update as initiator") - l.failf(LinkFailureError{code: ErrInvalidUpdate}, "%v", err) - - return err - } - // Check and see if their proposed fee-rate would make us exceed the fee // threshold. fee := chainfee.SatPerKWeight(msg.FeePerKw) @@ -4643,9 +4565,8 @@ func (l *channelLink) processRemoteUpdateFee(msg *lnwire.UpdateFee) error { if isDust { // The proposed fee-rate makes us exceed the fee threshold. - err := fmt.Errorf("fee threshold exceeded") - l.failf(LinkFailureError{code: ErrInternalError}, "%v", err) - + l.failf(LinkFailureError{code: ErrInternalError}, + "fee threshold exceeded: %v", err) return err } @@ -4654,7 +4575,6 @@ func (l *channelLink) processRemoteUpdateFee(msg *lnwire.UpdateFee) error { if err := l.channel.ReceiveUpdateFee(fee); err != nil { l.failf(LinkFailureError{code: ErrInvalidUpdate}, "error receiving fee update: %v", err) - return err } diff --git a/htlcswitch/link_fee_update_test.go b/htlcswitch/link_fee_update_test.go deleted file mode 100644 index d8daafa8e..000000000 --- a/htlcswitch/link_fee_update_test.go +++ /dev/null @@ -1,306 +0,0 @@ -package htlcswitch - -import ( - "bytes" - "errors" - "strings" - "sync" - "testing" - "time" - - "github.com/btcsuite/btclog/v2" - "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/lnpeer" - "github.com/lightningnetwork/lnd/lnwallet" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/stretchr/testify/require" -) - -// mailboxAdmissionPeer records disconnect requests made by a channel link. -type mailboxAdmissionPeer struct { - *lnpeer.MockPeer - - disconnected chan error -} - -// Disconnect records the error supplied by the channel link. -func (p *mailboxAdmissionPeer) Disconnect(err error) { - p.disconnected <- err -} - -// mailboxAdmissionTestBox fails its first message admission and records the -// number of admission attempts. -type mailboxAdmissionTestBox struct { - MailBox - - mu sync.Mutex - addCalls int -} - -// AddMessage records an admission attempt and fails the first one. -func (m *mailboxAdmissionTestBox) AddMessage(lnwire.Message) error { - m.mu.Lock() - defer m.mu.Unlock() - - m.addCalls++ - if m.addCalls == 1 { - return errWireMessageQueueFull - } - - return nil -} - -// calls returns the number of message admission attempts. -func (m *mailboxAdmissionTestBox) calls() int { - m.mu.Lock() - defer m.mu.Unlock() - - return m.addCalls -} - -// newLinkCapturingLogger returns a logger backed by an in-memory buffer. -func newLinkCapturingLogger() (btclog.Logger, *bytes.Buffer) { - buf := &bytes.Buffer{} - handler := btclog.NewDefaultHandler(buf, btclog.WithNoTimestamp()) - - return btclog.NewSLogger(handler), buf -} - -// TestProcessRemoteUpdateFeeRoleValidation checks that fee update role -// validation is performed at the link boundary. -func TestProcessRemoteUpdateFeeRoleValidation(t *testing.T) { - t.Parallel() - - aliceChannel, bobChannel, err := lnwallet.CreateTestChannels( - t, channeldb.SingleFunderTweaklessBit, - ) - require.NoError(t, err) - - newLink := func(channel *lnwallet.LightningChannel) *channelLink { - link, ok := NewChannelLink(ChannelLinkConfig{ - DisallowQuiescence: true, - OnChannelFailure: func(lnwire.ChannelID, - lnwire.ShortChannelID, LinkFailureError) { - }, - }, channel).(*channelLink) - require.True(t, ok) - - return link - } - - t.Run("unauthorized sender", func(t *testing.T) { - link := newLink(aliceChannel) - - err := link.processRemoteUpdateFee(&lnwire.UpdateFee{}) - require.EqualError(t, err, "received fee update as initiator") - require.True(t, link.failed) - }) - - t.Run("authorized sender", func(t *testing.T) { - link := newLink(bobChannel) - mailbox := newMemoryMailBox(&mailBoxConfig{}) - link.mailBox = mailbox - - feeRate := bobChannel.CommitFeeRate() + 1 - err := link.processRemoteUpdateFee(&lnwire.UpdateFee{ - FeePerKw: uint32(feeRate), - }) - require.NoError(t, err) - require.False(t, link.failed) - require.True(t, bobChannel.NeedCommitment()) - require.Equal(t, feeRate, mailbox.feeRate) - }) -} - -// TestProcessRemoteUpdateFeeExposureError checks that exceeding the fee -// exposure limit returns the error used to fail the link. -func TestProcessRemoteUpdateFeeExposureError(t *testing.T) { - t.Parallel() - - _, bobChannel, err := lnwallet.CreateTestChannels( - t, channeldb.SingleFunderTweaklessBit, - ) - require.NoError(t, err) - - link, ok := NewChannelLink(ChannelLinkConfig{ - DisallowQuiescence: true, - MaxFeeExposure: 1, - OnChannelFailure: func(lnwire.ChannelID, - lnwire.ShortChannelID, LinkFailureError) { - }, - }, bobChannel).(*channelLink) - require.True(t, ok) - - err = link.processRemoteUpdateFee(&lnwire.UpdateFee{ - FeePerKw: 1000, - }) - require.EqualError(t, err, "fee threshold exceeded") - require.True(t, link.failed) -} - -// TestLinkLogDeduplication checks that repeated non-fatal message classes are -// only recorded once during a link lifetime. -func TestLinkLogDeduplication(t *testing.T) { - t.Parallel() - - aliceChannel, _, err := lnwallet.CreateTestChannels( - t, channeldb.SingleFunderTweaklessBit, - ) - require.NoError(t, err) - - link, ok := NewChannelLink(ChannelLinkConfig{ - DisallowQuiescence: true, - }, aliceChannel).(*channelLink) - require.True(t, ok) - logger, logBuffer := newLinkCapturingLogger() - link.log = logger - - for i := 0; i < 2; i++ { - link.handleUpstreamMsg(t.Context(), &lnwire.Warning{}) - link.handleUpstreamMsg( - t.Context(), &lnwire.ChannelReestablish{}, - ) - } - - warningCount := strings.Count( - logBuffer.String(), "received warning message from peer", - ) - require.Equal(t, 1, warningCount) - require.Equal( - t, 1, strings.Count( - logBuffer.String(), "received unknown message of type", - ), - ) -} - -// TestChannelMessageAdmissionError checks that an admission error reconnects -// the ordered channel message stream instead of omitting a message. -func TestChannelMessageAdmissionError(t *testing.T) { - t.Parallel() - - aliceChannel, _, err := lnwallet.CreateTestChannels( - t, channeldb.SingleFunderTweaklessBit, - ) - require.NoError(t, err) - - peer := &mailboxAdmissionPeer{ - MockPeer: &lnpeer.MockPeer{}, - disconnected: make(chan error, 1), - } - link, ok := NewChannelLink(ChannelLinkConfig{ - Peer: peer, - DisallowQuiescence: true, - }, aliceChannel).(*channelLink) - require.True(t, ok) - - mailbox := newMemoryMailBox(&mailBoxConfig{}) - link.mailBox = mailbox - for i := 0; i < maxWireMessages; i++ { - require.NoError(t, mailbox.AddMessage(&lnwire.UpdateFee{})) - } - - link.HandleChannelUpdate(&lnwire.UpdateFee{}) - - select { - case err := <-peer.disconnected: - require.ErrorIs(t, err, errWireMessageQueueFull) - - case <-time.After(time.Second): - t.Fatal("mailbox admission error did not disconnect peer") - } -} - -// TestChannelMessageAdmissionFailureLatch checks that a link stops admitting -// peer messages after its first mailbox admission failure. -func TestChannelMessageAdmissionFailureLatch(t *testing.T) { - t.Parallel() - - aliceChannel, _, err := lnwallet.CreateTestChannels( - t, channeldb.SingleFunderTweaklessBit, - ) - require.NoError(t, err) - - peer := &mailboxAdmissionPeer{ - MockPeer: &lnpeer.MockPeer{}, - disconnected: make(chan error, 2), - } - link, ok := NewChannelLink(ChannelLinkConfig{ - Peer: peer, - DisallowQuiescence: true, - }, aliceChannel).(*channelLink) - require.True(t, ok) - - mailbox := &mailboxAdmissionTestBox{} - link.mailBox = mailbox - logger, logBuffer := newLinkCapturingLogger() - link.log = logger - - link.HandleChannelUpdate(&lnwire.UpdateFee{}) - - select { - case err := <-peer.disconnected: - require.ErrorIs(t, err, errWireMessageQueueFull) - - case <-time.After(time.Second): - t.Fatal("mailbox admission error did not disconnect peer") - } - - link.HandleChannelUpdate(&lnwire.CommitSig{}) - - require.Equal(t, 1, mailbox.calls()) - require.Equal( - t, 1, strings.Count( - logBuffer.String(), "failed to add Message to mailbox", - ), - ) - select { - case err := <-peer.disconnected: - t.Fatalf("unexpected second disconnect: %v", err) - - default: - } -} - -// TestChannelMessageSizeAdmissionError checks that a message-size admission -// error reconnects the ordered channel message stream. -func TestChannelMessageSizeAdmissionError(t *testing.T) { - t.Parallel() - - aliceChannel, _, err := lnwallet.CreateTestChannels( - t, channeldb.SingleFunderTweaklessBit, - ) - require.NoError(t, err) - - peer := &mailboxAdmissionPeer{ - MockPeer: &lnpeer.MockPeer{}, - disconnected: make(chan error, 1), - } - link, ok := NewChannelLink(ChannelLinkConfig{ - Peer: peer, - DisallowQuiescence: true, - }, aliceChannel).(*channelLink) - require.True(t, ok) - - mailbox := newMemoryMailBox(&mailBoxConfig{}) - link.mailBox = mailbox - msg := &lnwire.Warning{ - Data: make([]byte, lnwire.MaxMsgBody-40), - } - for { - err := mailbox.AddMessage(msg) - if errors.Is(err, errWireMessageQueueFull) { - break - } - require.NoError(t, err) - } - - link.HandleChannelUpdate(msg) - - select { - case err := <-peer.disconnected: - require.ErrorIs(t, err, errWireMessageQueueFull) - - case <-time.After(time.Second): - t.Fatal("message-size admission error did not disconnect peer") - } -} diff --git a/htlcswitch/link_test.go b/htlcswitch/link_test.go index 2e5fdfb5d..a64942d5c 100644 --- a/htlcswitch/link_test.go +++ b/htlcswitch/link_test.go @@ -18,14 +18,13 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/channeldb" - cstate "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" @@ -33,6 +32,7 @@ import ( "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/input" invpkg "github.com/lightningnetwork/lnd/invoices" + "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lntypes" @@ -40,7 +40,6 @@ import ( "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/ticker" - "github.com/lightningnetwork/lnd/tlv" "github.com/stretchr/testify/require" ) @@ -777,9 +776,8 @@ func testChannelLinkInboundFee(t *testing.T, //nolint:thelper hops := []*hop.Payload{ { FwdInfo: hop.ForwardingInfo{ - NextHop: hop.NewChannelNextHop( - n.carolChannelLink.ShortChanID(), - ), + NextHop: n.carolChannelLink. + ShortChanID(), AmountToForward: 1_000_000, OutgoingCLTV: 106, }, @@ -924,6 +922,7 @@ func TestChannelLinkCancelFullCommitment(t *testing.T) { // Now, settle all htlcs held by bob and clear the commitment of htlcs. for _, preimage := range preimages { + preimage := preimage // It's possible that the HTLCs have not been delivered to the // invoice registry at this point, so we poll until we are able @@ -2175,7 +2174,7 @@ func newSingleLinkTestHarness(t *testing.T, chanAmt, pCache := newMockPreimageCache() - aliceDb := testChannelStateDB(t, aliceLc.channel).GetParentDB() + aliceDb := aliceLc.channel.State().Db.GetParentDB() aliceSwitch, err := initSwitchWithDB(testStartingHeight, aliceDb) if err != nil { return singleLinkTestHarness{}, err @@ -2235,19 +2234,18 @@ func newSingleLinkTestHarness(t *testing.T, chanAmt, PendingCommitTicker: ticker.New(time.Minute), // Make the BatchSize and Min/MaxUpdateTimeout large enough // to not trigger commit updates automatically during tests. - BatchSize: 10000, - MinUpdateTimeout: 30 * time.Minute, - MaxUpdateTimeout: 40 * time.Minute, - MaxOutgoingCltvExpiry: DefaultMaxOutgoingCltvExpiry, - MaxFeeAllocation: DefaultMaxLinkFeeAllocation, - NotifyActiveLink: func(wire.OutPoint) {}, - NotifyActiveChannel: func(wire.OutPoint) {}, - NotifyChannelUpdate: func(*cstate.OpenChannel) {}, - NotifyInactiveChannel: func(wire.OutPoint) {}, - NotifyInactiveLinkEvent: func(wire.OutPoint) {}, - HtlcNotifier: aliceSwitch.cfg.HtlcNotifier, - GetAliases: getAliases, - ShouldFwdExpAccountability: func() bool { return true }, + BatchSize: 10000, + MinUpdateTimeout: 30 * time.Minute, + MaxUpdateTimeout: 40 * time.Minute, + MaxOutgoingCltvExpiry: DefaultMaxOutgoingCltvExpiry, + MaxFeeAllocation: DefaultMaxLinkFeeAllocation, + NotifyActiveLink: func(wire.OutPoint) {}, + NotifyActiveChannel: func(wire.OutPoint) {}, + NotifyInactiveChannel: func(wire.OutPoint) {}, + NotifyInactiveLinkEvent: func(wire.OutPoint) {}, + HtlcNotifier: aliceSwitch.cfg.HtlcNotifier, + GetAliases: getAliases, + ShouldFwdExpEndorsement: func() bool { return true }, } aliceLink := NewChannelLink(aliceCfg, aliceLc.channel) @@ -4855,7 +4853,7 @@ func (h *persistentLinkHarness) restartLink( pCache = newMockPreimageCache() ) - aliceDb := testChannelStateDB(t, aliceChannel).GetParentDB() + aliceDb := aliceChannel.State().Db.GetParentDB() if restartSwitch { var err error h.hSwitch, err = initSwitchWithDB(testStartingHeight, aliceDb) @@ -4926,18 +4924,17 @@ func (h *persistentLinkHarness) restartLink( MinUpdateTimeout: 30 * time.Minute, MaxUpdateTimeout: 40 * time.Minute, // Set any hodl flags requested for the new link. - HodlMask: hodl.MaskFromFlags(hodlFlags...), - MaxOutgoingCltvExpiry: DefaultMaxOutgoingCltvExpiry, - MaxFeeAllocation: DefaultMaxLinkFeeAllocation, - NotifyActiveLink: func(wire.OutPoint) {}, - NotifyActiveChannel: func(wire.OutPoint) {}, - NotifyInactiveChannel: func(wire.OutPoint) {}, - NotifyInactiveLinkEvent: func(wire.OutPoint) {}, - NotifyChannelUpdate: func(*cstate.OpenChannel) {}, - HtlcNotifier: h.hSwitch.cfg.HtlcNotifier, - SyncStates: syncStates, - GetAliases: getAliases, - ShouldFwdExpAccountability: func() bool { return true }, + HodlMask: hodl.MaskFromFlags(hodlFlags...), + MaxOutgoingCltvExpiry: DefaultMaxOutgoingCltvExpiry, + MaxFeeAllocation: DefaultMaxLinkFeeAllocation, + NotifyActiveLink: func(wire.OutPoint) {}, + NotifyActiveChannel: func(wire.OutPoint) {}, + NotifyInactiveChannel: func(wire.OutPoint) {}, + NotifyInactiveLinkEvent: func(wire.OutPoint) {}, + HtlcNotifier: h.hSwitch.cfg.HtlcNotifier, + SyncStates: syncStates, + GetAliases: getAliases, + ShouldFwdExpEndorsement: func() bool { return true }, } aliceLink := NewChannelLink(aliceCfg, aliceChannel) @@ -5770,20 +5767,42 @@ func TestChannelLinkCleanupSpuriousResponses(t *testing.T) { } } -// mockFailLoadFwdPkgStore wraps a real channel state store and overrides only -// LoadFwdPkgs. This lets the link startup test inject a forwarding-package -// load failure through OpenChannel.Db without replacing the rest of the store. -type mockFailLoadFwdPkgStore struct { - cstate.Store +type mockPackager struct { + failLoadFwdPkgs bool } -// LoadFwdPkgs fails the forwarding-package load to exercise link startup -// failure handling while all other store methods delegate to the embedded -// store. -func (m *mockFailLoadFwdPkgStore) LoadFwdPkgs( - *cstate.OpenChannel) ([]*channeldb.FwdPkg, error) { +func (*mockPackager) AddFwdPkg(tx kvdb.RwTx, fwdPkg *channeldb.FwdPkg) error { + return nil +} - return nil, fmt.Errorf("failing LoadFwdPkgs") +func (*mockPackager) SetFwdFilter(tx kvdb.RwTx, height uint64, + fwdFilter *channeldb.PkgFilter) error { + return nil +} + +func (*mockPackager) AckAddHtlcs(tx kvdb.RwTx, + addRefs ...channeldb.AddRef) error { + return nil +} + +func (m *mockPackager) LoadFwdPkgs(tx kvdb.RTx) ([]*channeldb.FwdPkg, error) { + if m.failLoadFwdPkgs { + return nil, fmt.Errorf("failing LoadFwdPkgs") + } + return nil, nil +} + +func (*mockPackager) RemovePkg(tx kvdb.RwTx, height uint64) error { + return nil +} + +func (*mockPackager) Wipe(tx kvdb.RwTx) error { + return nil +} + +func (*mockPackager) AckSettleFails(tx kvdb.RwTx, + settleFailRefs ...channeldb.SettleFailRef) error { + return nil } // TestChannelLinkFail tests that we will fail the channel, and force close the @@ -5859,10 +5878,10 @@ func TestChannelLinkFail(t *testing.T) { func(c *channelLink) { // We make the call to resolveFwdPkgs fail by // making the underlying forwarder fail. - state := c.channel.State() - state.Db = &mockFailLoadFwdPkgStore{ - Store: state.Db, + pkg := &mockPackager{ + failLoadFwdPkgs: true, } + c.channel.State().Packager = pkg }, func(*testing.T, *Switch, *channelLink, *lnwallet.LightningChannel) { @@ -6375,134 +6394,6 @@ func TestCheckHtlcForward(t *testing.T) { }) } -// recordingAuxShaper is a minimal AuxTrafficShaper that records the channel id -// it is asked about and declines to handle the traffic, so the normal -// forwarding path proceeds. Only the methods reached by CheckHtlcForward are -// implemented; the rest are inherited from the embedded (nil) interface and -// must never be called. -type recordingAuxShaper struct { - AuxTrafficShaper - - gotCID lnwire.ShortChannelID -} - -// ShouldHandleTraffic records the short channel ID passed to the shaper. -func (a *recordingAuxShaper) ShouldHandleTraffic(cid lnwire.ShortChannelID, - _, _ fn.Option[tlv.Blob]) (bool, error) { - - a.gotCID = cid - - return false, nil -} - -// IsCustomHTLC returns false as recordingAuxShaper handles standard HTLCs. -func (a *recordingAuxShaper) IsCustomHTLC(_ lnwire.CustomRecords) bool { - return false -} - -// TestCheckHtlcForwardAuxShaperChannel asserts that during non-strict -// forwarding the aux traffic shaper is keyed on the channel actually being -// evaluated (the link's own SCID), not the sender-requested SCID, which fixes -// both the node-ID/blinded path (where no SCID is requested) and pre-existing -// parallel-channel forwarding. It also asserts the real SCID handed to the -// shaper never leaks into the sender-facing channel_update, which continues to -// reference the requested (alias) SCID. -func TestCheckHtlcForwardAuxShaperChannel(t *testing.T) { - t.Parallel() - - const ( - chanScid = 42 - requestedScid = 99 - ) - - fetchLastChannelUpdate := func(lnwire.ShortChannelID) ( - *lnwire.ChannelUpdate1, error) { - - return &lnwire.ChannelUpdate1{}, nil - } - - // Record the SCID used to build the returned channel_update on failure. - var updateScid lnwire.ShortChannelID - failAliasUpdate := func(sid lnwire.ShortChannelID, - incoming bool) *lnwire.ChannelUpdate1 { - - updateScid = sid - - return &lnwire.ChannelUpdate1{ - ShortChannelID: sid, - } - } - - testChannel, _, err := createTestChannel( - t, alicePrivKey, bobPrivKey, 100000, 100000, 1000, 1000, - lnwire.NewShortChanIDFromInt(chanScid), - ) - require.NoError(t, err) - - shaper := &recordingAuxShaper{} - link := channelLink{ - cfg: ChannelLinkConfig{ - FwrdingPolicy: models.ForwardingPolicy{ - TimeLockDelta: 20, - MinHTLCOut: 500, - MaxHTLC: 1000, - BaseFee: 10, - }, - FetchLastChannelUpdate: fetchLastChannelUpdate, - MaxOutgoingCltvExpiry: DefaultMaxOutgoingCltvExpiry, - HtlcNotifier: &mockHTLCNotifier{}, - }, - log: log, - channel: testChannel.channel, - } - link.cfg.AuxTrafficShaper = fn.Some[AuxTrafficShaper](shaper) - link.attachFailAliasUpdate(failAliasUpdate) - - require.Equal( - t, lnwire.NewShortChanIDFromInt(chanScid), link.ShortChanID(), - ) - - var hash [32]byte - requested := lnwire.NewShortChanIDFromInt(requestedScid) - - // A satisfiable forward: the shaper must be queried about the channel - // being evaluated (the link's own SCID), not the requested SCID. - result := link.CheckHtlcForward( - hash, 1500, 1000, 200, 150, models.InboundFee{}, 0, requested, - nil, - ) - require.Nil(t, result, "expected policy to be satisfied") - require.Equal( - t, link.ShortChanID(), shaper.gotCID, - "aux shaper must be keyed on the evaluated channel", - ) - require.NotEqual( - t, requested, shaper.gotCID, - "aux shaper must not be keyed on the requested SCID", - ) - - // A failing forward: the returned channel_update must reference the - // requested (alias) SCID, never the real channel SCID handed to the - // shaper. - result = link.CheckHtlcForward( - hash, 100, 50, 200, 150, models.InboundFee{}, 0, requested, nil, - ) - require.NotNil(t, result) - require.Equal( - t, requested, updateScid, - "channel_update must reference the requested SCID, not the "+ - "real channel SCID", - ) - - wireErr := result.WireMessage() - failAmt, ok := wireErr.(*lnwire.FailAmountBelowMinimum) - require.True(t, ok, "expected FailAmountBelowMinimum failure") - require.Equal( - t, requested, failAmt.Update.ShortChannelID, - "failure update must carry the requested SCID", - ) -} - // TestChannelLinkCanceledInvoice in this test checks the interaction // between Alice and Bob for a canceled invoice. func TestChannelLinkCanceledInvoice(t *testing.T) { diff --git a/htlcswitch/mailbox.go b/htlcswitch/mailbox.go index e52daca56..b283825dd 100644 --- a/htlcswitch/mailbox.go +++ b/htlcswitch/mailbox.go @@ -14,16 +14,6 @@ import ( "github.com/lightningnetwork/lnd/lnwire" ) -const ( - // maxWireMessages is the maximum number of ordered messages that can - // wait for a channel link. It accommodates a full commitment batch. - maxWireMessages = 1000 - - // maxWireBytes bounds the encoded size of messages that can wait for a - // channel link. - maxWireBytes = 4 * 1024 * 1024 -) - var ( // ErrMailBoxShuttingDown is returned when the mailbox is interrupted by // a shutdown request. @@ -32,12 +22,6 @@ var ( // ErrPacketAlreadyExists signals that an attempt to add a packet failed // because it already exists in the mailbox. ErrPacketAlreadyExists = errors.New("mailbox already has packet") - - // errWireMessageQueueFull signals that the wire-message queue has - // reached one of its admission budgets. - errWireMessageQueueFull = errors.New( - "mailbox wire message queue is full", - ) ) // MailBox is an interface which represents a concurrent-safe, in-order @@ -138,7 +122,6 @@ type memoryMailBox struct { cfg *mailBoxConfig wireMessages *list.List - wireBytes uint32 wireMtx sync.Mutex wireCond *sync.Cond @@ -177,13 +160,6 @@ type memoryMailBox struct { isDust dustClosure } -// queuedWireMessage stores a wire message and its encoded size charged to the -// wire-message budget. -type queuedWireMessage struct { - msg lnwire.Message - size uint32 -} - // newMemoryMailBox creates a new instance of the memoryMailBox. func newMemoryMailBox(cfg *mailBoxConfig) *memoryMailBox { box := &memoryMailBox{ @@ -407,7 +383,6 @@ func (m *memoryMailBox) wireMailCourier() { select { case msgDone := <-m.msgReset: m.wireMessages.Init() - m.wireBytes = 0 close(msgDone) case <-m.quit: m.wireCond.L.Unlock() @@ -422,9 +397,7 @@ func (m *memoryMailBox) wireMailCourier() { entry := m.wireMessages.Front() //nolint:forcetypeassert - queuedMsg := m.wireMessages.Remove(entry).(*queuedWireMessage) - m.wireBytes -= queuedMsg.size - nextMsg := queuedMsg.msg + nextMsg := m.wireMessages.Remove(entry).(lnwire.Message) // Now that we're done with the condition, we can unlock it to // allow any callers to append to the end of our target queue. @@ -438,7 +411,6 @@ func (m *memoryMailBox) wireMailCourier() { case msgDone := <-m.msgReset: m.wireCond.L.Lock() m.wireMessages.Init() - m.wireBytes = 0 m.wireCond.L.Unlock() close(msgDone) @@ -588,28 +560,10 @@ func (m *memoryMailBox) pktMailCourier() { // NOTE: This method is safe for concrete use and part of the MailBox // interface. func (m *memoryMailBox) AddMessage(msg lnwire.Message) error { - msgSize, err := wireMessageSize(msg) - if err != nil { - return fmt.Errorf( - "unable to determine wire message size: %w", err, - ) - } - // First, we'll lock the condition, and add the message to the end of // the wire message inbox. m.wireCond.L.Lock() - if m.wireMessages.Len() >= maxWireMessages || - m.wireBytes+msgSize > maxWireBytes { - - m.wireCond.L.Unlock() - return errWireMessageQueueFull - } - - m.wireMessages.PushBack(&queuedWireMessage{ - msg: msg, - size: msgSize, - }) - m.wireBytes += msgSize + m.wireMessages.PushBack(msg) m.wireCond.L.Unlock() // With the message added, we signal to the mailCourier that there are @@ -619,16 +573,6 @@ func (m *memoryMailBox) AddMessage(msg lnwire.Message) error { return nil } -// wireMessageSize returns the serialized bytes charged to the wire-message -// budget. -func wireMessageSize(msg lnwire.Message) (uint32, error) { - if sizeableMsg, ok := msg.(lnwire.SizeableMessage); ok { - return sizeableMsg.SerializedSize() - } - - return lnwire.MessageSerializedSize(msg) -} - // AddPacket appends a new message to the end of the packet queue. // // NOTE: This method is safe for concrete use and part of the MailBox @@ -755,18 +699,12 @@ func (m *memoryMailBox) FailAdd(pkt *htlcPacket) { reason lnwire.OpaqueReason ) - var failure lnwire.FailureMessage - if pkt.outgoingHop.IsRight() { - // A node-ID next hop has no requested outgoing channel. - // Returning a channel_update could leak a private channel's - // SCID if the failure reason is persisted before blinding - // error processing or replayed during channel reestablishment. - failure = &lnwire.FailUnknownNextPeer{} - } else { - failure = m.cfg.failMailboxUpdate( - pkt.originalOutgoingChanID, m.cfg.shortChanID, - ) - } + // Create a temporary channel failure which we will send back to our + // peer if this is a forward, or report to the user if the failed + // payment was locally initiated. + failure := m.cfg.failMailboxUpdate( + pkt.originalOutgoingChanID, m.cfg.shortChanID, + ) // If the payment was locally initiated (which is indicated by a nil // obfuscator), we do not need to encrypt it back to the sender. @@ -799,8 +737,6 @@ func (m *memoryMailBox) FailAdd(pkt *htlcPacket) { failPkt := &htlcPacket{ incomingChanID: pkt.incomingChanID, incomingHTLCID: pkt.incomingHTLCID, - outgoingChanID: pkt.outgoingChanID, - outgoingHop: pkt.outgoingHop, circuit: pkt.circuit, sourceRef: pkt.sourceRef, hasSource: true, diff --git a/htlcswitch/mailbox_test.go b/htlcswitch/mailbox_test.go index 254a16469..57a581c4b 100644 --- a/htlcswitch/mailbox_test.go +++ b/htlcswitch/mailbox_test.go @@ -1,19 +1,15 @@ package htlcswitch import ( - "errors" prand "math/rand" "reflect" "testing" "time" - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/clock" - "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnmock" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" @@ -170,96 +166,6 @@ func TestMailBoxCouriers(t *testing.T) { } } -// TestMailBoxAdmissionBudgets checks message-count and serialized-size -// admission behavior for the wire-message queue. -func TestMailBoxAdmissionBudgets(t *testing.T) { - t.Parallel() - - t.Run("message count", func(t *testing.T) { - mailbox := newMemoryMailBox(&mailBoxConfig{}) - msg := &lnwire.UpdateFee{} - - for i := 0; i < maxWireMessages; i++ { - require.NoError(t, mailbox.AddMessage(msg)) - } - - require.ErrorIs( - t, mailbox.AddMessage(msg), errWireMessageQueueFull, - ) - require.Equal(t, maxWireMessages, mailbox.wireMessages.Len()) - require.LessOrEqual( - t, mailbox.wireBytes, uint32(maxWireBytes), - ) - }) - - t.Run("encoded bytes", func(t *testing.T) { - mailbox := newMemoryMailBox(&mailBoxConfig{}) - msg := &lnwire.Warning{ - Data: make([]byte, lnwire.MaxMsgBody-40), - } - - for { - err := mailbox.AddMessage(msg) - if errors.Is(err, errWireMessageQueueFull) { - break - } - require.NoError(t, err) - } - - require.Less(t, mailbox.wireMessages.Len(), maxWireMessages) - require.LessOrEqual( - t, mailbox.wireBytes, uint32(maxWireBytes), - ) - }) - - t.Run("commitment message sizes", func(t *testing.T) { - _, pubKey := btcec.PrivKeyFromBytes(make([]byte, 32)) - extraData := lnwire.ExtraOpaqueData{ - 0xfe, 0x00, 0x01, 0x00, 0x00, 0x03, 0x01, 0x02, 0x03, - } - - messages := []lnwire.Message{ - &lnwire.CommitSig{ExtraData: extraData}, - &lnwire.RevokeAndAck{ - NextRevocationKey: pubKey, - ExtraData: extraData, - }, - &lnwire.Stfu{ExtraData: extraData}, - } - for _, msg := range messages { - mailbox := newMemoryMailBox(&mailBoxConfig{}) - sizeableMsg, ok := msg.(lnwire.SizeableMessage) - require.True(t, ok) - - expectedSize, err := sizeableMsg.SerializedSize() - require.NoError(t, err) - - require.NoError(t, mailbox.AddMessage(msg)) - require.Equal(t, expectedSize, mailbox.wireBytes) - } - }) - - t.Run("reset restores byte budget", func(t *testing.T) { - mailbox := newMemoryMailBox(&mailBoxConfig{}) - mailbox.Start() - t.Cleanup(mailbox.Stop) - - msg := &lnwire.Warning{ - Data: make([]byte, lnwire.MaxMsgBody-40), - } - for { - err := mailbox.AddMessage(msg) - if errors.Is(err, errWireMessageQueueFull) { - break - } - require.NoError(t, err) - } - - require.NoError(t, mailbox.ResetMessages()) - require.NoError(t, mailbox.AddMessage(msg)) - }) -} - // TestMailBoxResetAfterShutdown tests that ResetMessages and ResetPackets // return ErrMailBoxShuttingDown after the mailbox has been stopped. func TestMailBoxResetAfterShutdown(t *testing.T) { @@ -370,17 +276,6 @@ func (c *mailboxContext) sendAdds(start, num int) []*htlcPacket { ID: uint64(start + i), }, } - if i%2 == 0 { - pkt.outgoingHop = fn.NewLeft[ - lnwire.ShortChannelID, [33]byte, - ](pkt.outgoingChanID) - } else { - var nodeID [33]byte - prand.Read(nodeID[:]) - pkt.outgoingHop = fn.NewRight[ - lnwire.ShortChannelID, [33]byte, - ](nodeID) - } sentPackets[i] = pkt err := c.mailbox.AddPacket(pkt) @@ -418,14 +313,6 @@ func (c *mailboxContext) checkFails(adds []*htlcPacket) { select { case fail := <-c.forwards: if add.inKey() == fail.inKey() { - require.Equal( - c.t, add.outgoingChanID, - fail.outgoingChanID, - ) - require.Equal( - c.t, add.outgoingHop, - fail.outgoingHop, - ) continue } c.t.Fatalf("inkey mismatch #%d, add: %v vs fail: %v", @@ -699,7 +586,7 @@ func TestMailBoxDustHandling(t *testing.T) { }) } -func testMailBoxDust(t *testing.T, chantype chanstate.ChannelType) { +func testMailBoxDust(t *testing.T, chantype channeldb.ChannelType) { t.Parallel() ctx := newMailboxContext(t, time.Now(), testExpiry) @@ -941,54 +828,3 @@ func TestMailOrchestrator(t *testing.T) { spew.Sdump(sentPackets), spew.Sdump(recvdPackets)) } } - -// TestMailBoxFailAddNodeID asserts that FailAdd for a node-ID hop returns a -// FailUnknownNextPeer failure without a channel update. -func TestMailBoxFailAddNodeID(t *testing.T) { - ctx := newMailboxContext(t, time.Now(), time.Minute) - - var nodeID [33]byte - nodeID[0] = 0x02 - - pkt := &htlcPacket{ - incomingChanID: lnwire.NewShortChanIDFromInt(1), - incomingHTLCID: 1, - outgoingHop: fn.NewRight[lnwire.ShortChannelID, [33]byte]( - nodeID, - ), - htlc: &lnwire.UpdateAddHTLC{ - ID: 1, - }, - } - - require.NoError(t, ctx.mailbox.AddPacket(pkt)) - - // Pull packet from mailbox to simulate link delivery. - select { - case <-ctx.mailbox.PacketOutBox(): - case <-time.After(50 * time.Millisecond): - t.Fatal("timeout waiting for packet outbox") - } - - // Fail the packet via FailAdd. - ctx.mailbox.FailAdd(pkt) - - select { - case pktResponse := <-ctx.forwards: - require.Equal(t, pkt.incomingChanID, pktResponse.incomingChanID) - require.Equal(t, pkt.incomingHTLCID, pktResponse.incomingHTLCID) - require.Equal(t, pkt.outgoingChanID, pktResponse.outgoingChanID) - require.Equal(t, pkt.outgoingHop, pktResponse.outgoingHop) - require.NotNil(t, pktResponse.linkFailure) - - var unknownNextPeer *lnwire.FailUnknownNextPeer - require.ErrorAs( - t, pktResponse.linkFailure.WireMessage(), - &unknownNextPeer, - "expected FailUnknownNextPeer for node-ID FailAdd", - ) - - case <-time.After(50 * time.Millisecond): - t.Fatal("timeout waiting for packet response") - } -} diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index 9d201d585..dbab96727 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -17,12 +17,11 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/fn/v2" @@ -75,7 +74,7 @@ func (m *mockPreimageCache) AddPreimages(preimages ...lntypes.Preimage) error { } func (m *mockPreimageCache) SubscribeUpdates( - chanID lnwire.ShortChannelID, htlc *chanstate.HTLC, + chanID lnwire.ShortChannelID, htlc *channeldb.HTLC, payload *hop.Payload, nextHopOnionBlob []byte) (*contractcourt.WitnessSubscription, error) { @@ -368,13 +367,7 @@ func (r *mockHopIterator) EncodeNextHop(w io.Writer) error { } func encodeFwdInfo(w io.Writer, f *hop.ForwardingInfo) error { - if f.NextHop.IsRight() { - return fmt.Errorf("mock serialization does not support " + - "node-ID next hop") - } - - nextHop := f.NextHopChannel().UnwrapOr(hop.Exit) - if err := binary.Write(w, binary.BigEndian, nextHop); err != nil { + if err := binary.Write(w, binary.BigEndian, f.NextHop); err != nil { return err } @@ -516,8 +509,7 @@ func (p *mockIteratorDecoder) DecodeHopIterator(r io.Reader, rHash []byte, } var nextHopBytes [8]byte - scid := f.NextHopChannel().UnwrapOr(hop.Exit) - binary.BigEndian.PutUint64(nextHopBytes[:], scid.ToUint64()) + binary.BigEndian.PutUint64(nextHopBytes[:], f.NextHop.ToUint64()) hops[i] = hop.NewLegacyPayload(&sphinx.HopData{ Realm: [1]byte{}, // hop.BitcoinNetwork @@ -570,11 +562,9 @@ func (p *mockIteratorDecoder) DecodeHopIterators(id []byte, } func decodeFwdInfo(r io.Reader, f *hop.ForwardingInfo) error { - var nextHop lnwire.ShortChannelID - if err := binary.Read(r, binary.BigEndian, &nextHop); err != nil { + if err := binary.Read(r, binary.BigEndian, &f.NextHop); err != nil { return err } - f.NextHop = hop.NewChannelNextHop(nextHop) if err := binary.Read(r, binary.BigEndian, &f.AmountToForward); err != nil { return err diff --git a/htlcswitch/packet.go b/htlcswitch/packet.go index 9af7e3432..ed5f82588 100644 --- a/htlcswitch/packet.go +++ b/htlcswitch/packet.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/lnwire" @@ -19,23 +18,9 @@ type htlcPacket struct { incomingChanID lnwire.ShortChannelID // outgoingChanID is the ID of the channel that we have offered or will - // offer an outgoing HTLC on. It is mutable and may remain zero - // (hop.Exit) until non-strict forwarding resolves a node-ID next hop to - // a concrete channel, or may differ from the requested SCID after - // non-strict load-balancing. A zero outgoingChanID alone does not imply - // an exit hop: if outgoingHop is a Right (node ID), the HTLC is a - // forward whose outgoing channel has not yet been selected. + // offer an outgoing HTLC on. outgoingChanID lnwire.ShortChannelID - // outgoingHop carries the immutable next-hop instruction decoded from - // the onion payload, following the same encoding as - // hop.ForwardingInfo.NextHop. The three possible cases are: - // 1. Left(scid) where scid != Exit: a channel-addressed forward. - // 2. Right(pubkey): a node-addressed forward for a blinded route, - // resolved to an active link via non-strict forwarding. - // 3. Left(Exit): a final receive at the destination/receiver node. - outgoingHop fn.Either[lnwire.ShortChannelID, [33]byte] - // incomingHTLCID is the ID of the HTLC that we have received from the peer // on the incoming channel. incomingHTLCID uint64 @@ -119,10 +104,11 @@ type htlcPacket struct { // in the incoming update_add_htlc wire message. inWireCustomRecords lnwire.CustomRecords - // originalOutgoingChanID is used when sending back failure messages. It - // retains the original sender-facing requested SCID for forwarded Adds, - // including option_scid_alias channels. This prevents exposing the - // evaluated link's concrete SCID or alias in channel_update failures. + // originalOutgoingChanID is used when sending back failure messages. + // It is only used for forwarded Adds on option_scid_alias channels. + // This is to avoid possible confusion if a payer uses the public SCID + // but receives a channel_update with the alias SCID. Instead, the + // payer should receive a channel_update with the public SCID. originalOutgoingChanID lnwire.ShortChannelID // inboundFee is the fee schedule of the incoming channel. diff --git a/htlcswitch/switch.go b/htlcswitch/switch.go index 3b6e3f9cd..a3aae809b 100644 --- a/htlcswitch/switch.go +++ b/htlcswitch/switch.go @@ -11,11 +11,10 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/fn/v2" @@ -151,16 +150,16 @@ type Config struct { // FetchAllOpenChannels is a function that fetches all currently open // channels from the channel database. - FetchAllOpenChannels func() ([]*chanstate.OpenChannel, error) + FetchAllOpenChannels func() ([]*channeldb.OpenChannel, error) // FetchAllChannels is a function that fetches all pending open, open, // and waiting close channels from the database. - FetchAllChannels func() ([]*chanstate.OpenChannel, error) + FetchAllChannels func() ([]*channeldb.OpenChannel, error) // FetchClosedChannels is a function that fetches all closed channels // from the channel database. FetchClosedChannels func( - pendingOnly bool) ([]*chanstate.ChannelCloseSummary, error) + pendingOnly bool) ([]*channeldb.ChannelCloseSummary, error) // SwitchPackager provides access to the forwarding packages of all // active channels. This gives the switch the ability to read arbitrary @@ -1251,7 +1250,6 @@ func (s *Switch) failAddPacket(packet *htlcPacket, failure *LinkError) error { incomingChanID: packet.incomingChanID, incomingHTLCID: packet.incomingHTLCID, outgoingChanID: packet.outgoingChanID, - outgoingHop: packet.outgoingHop, outgoingHTLCID: packet.outgoingHTLCID, incomingAmount: packet.incomingAmount, amount: packet.amount, @@ -2864,95 +2862,42 @@ func (s *Switch) handlePacketAdd(packet *htlcPacket, return s.failAddPacket(packet, failure) } - // Collect the links that could carry this HTLC to the next hop. - // Non-strict forwarding then load-balances across our channels to that - // peer. A short channel ID maps to a link and its peer, while a blinded - // node-ID next hop resolves the peer directly. A node-ID hop has no - // sender-specified channel, so outgoingChanID stays hop.Exit until - // selection. - var interfaceLinks []ChannelLink - if packet.outgoingHop.IsLeft() { - // Before we attempt to find a non-strict forwarding path for - // this htlc, check whether the htlc is being routed over the - // same incoming and outgoing channel. If our node does not - // allow forwards of this nature, we fail the htlc early. This - // check is in place to disallow inefficiently routed htlcs from - // locking up our balance. With channels where the - // option-scid-alias feature was negotiated, we also have to be - // sure that the IDs aren't the same since one or both could be - // an alias. - linkErr := s.checkCircularForward( - packet.incomingChanID, packet.outgoingChanID, - s.cfg.AllowCircularRoute, htlc.PaymentHash, - ) - if linkErr != nil { - return s.failAddPacket(packet, linkErr) - } - - s.indexMtx.RLock() - targetLink, err := s.getLinkByMapping(packet) - if err != nil { - s.indexMtx.RUnlock() - - log.Debugf("unable to find link with "+ - "destination %v", packet.outgoingChanID) - - // If packet was forwarded from another channel link - // then we should notify this link that some error - // occurred. - linkError := NewLinkError( - &lnwire.FailUnknownNextPeer{}, - ) - - return s.failAddPacket(packet, linkError) - } - - // NOTE: for the SCID path, we fetch all links to the target - // peer. If parallel channels exist to the incoming peer, the - // candidate set may include the incoming channel even when a - // different SCID was requested. - targetPeer := targetLink.PeerPubKey() - interfaceLinks, _ = s.getLinks(targetPeer) - s.indexMtx.RUnlock() - } else { - // A blinded node-ID next hop identifies the peer directly, so - // resolve its links and let non-strict forwarding load-balance - // across our channels to that peer. - peerKey := packet.outgoingHop.UnwrapRightOr([33]byte{}) - - s.indexMtx.RLock() - interfaceLinks, _ = s.getLinks(peerKey) - s.indexMtx.RUnlock() - - // Drop links that would form a disallowed circular route, so - // selection can't later land on the incoming channel. - var nonCircularLinks []ChannelLink - for _, link := range interfaceLinks { - linkErr := s.checkCircularForward( - packet.incomingChanID, link.ShortChanID(), - s.cfg.AllowCircularRoute, htlc.PaymentHash, - ) - if linkErr == nil { - nonCircularLinks = append( - nonCircularLinks, link, - ) - } - } - interfaceLinks = nonCircularLinks - - // Without a usable link to the peer (none exist, or all would - // be circular) we cannot forward. Fail as unknown next peer - // rather than attributing it to a specific channel. - if len(interfaceLinks) == 0 { - log.Debugf("no usable link to peer %x for blinded "+ - "next hop", peerKey) - - return s.failAddPacket(packet, NewLinkError( - &lnwire.FailUnknownNextPeer{}, - )) - } + // Before we attempt to find a non-strict forwarding path for this + // htlc, check whether the htlc is being routed over the same incoming + // and outgoing channel. If our node does not allow forwards of this + // nature, we fail the htlc early. This check is in place to disallow + // inefficiently routed htlcs from locking up our balance. With + // channels where the option-scid-alias feature was negotiated, we also + // have to be sure that the IDs aren't the same since one or both could + // be an alias. + linkErr := s.checkCircularForward( + packet.incomingChanID, packet.outgoingChanID, + s.cfg.AllowCircularRoute, htlc.PaymentHash, + ) + if linkErr != nil { + return s.failAddPacket(packet, linkErr) } + s.indexMtx.RLock() + targetLink, err := s.getLinkByMapping(packet) + if err != nil { + s.indexMtx.RUnlock() + + log.Debugf("unable to find link with "+ + "destination %v", packet.outgoingChanID) + + // If packet was forwarded from another channel link than we + // should notify this link that some error occurred. + linkError := NewLinkError( + &lnwire.FailUnknownNextPeer{}, + ) + + return s.failAddPacket(packet, linkError) + } + targetPeerKey := targetLink.PeerPubKey() + interfaceLinks, _ := s.getLinks(targetPeerKey) + s.indexMtx.RUnlock() + // We'll keep track of any HTLC failures during the link selection // process. This way we can return the error for precise link that the // sender selected, while optimistically trying all links to utilize @@ -2998,18 +2943,6 @@ func (s *Switch) handlePacketAdd(packet *htlcPacket, // current policy, then we'll send back an error, but ensure we send // back the error sourced at the *target* link. if len(destinations) == 0 { - // A node-ID next hop has no requested outgoing channel. - // Returning a per-candidate failure could leak a private - // channel via its channel_update (a probing vector), so fail - // generically. Later errors don't include private data. Defense - // in depth: route blinding error handling hides it too via - // error conversion. - if packet.outgoingHop.IsRight() { - return s.failAddPacket(packet, NewLinkError( - &lnwire.FailUnknownNextPeer{}, - )) - } - // At this point, some or all of the links rejected the HTLC so // we couldn't forward it. So we'll try to look up the error // that came from the source. diff --git a/htlcswitch/switch_test.go b/htlcswitch/switch_test.go index c895d27f3..13563916e 100644 --- a/htlcswitch/switch_test.go +++ b/htlcswitch/switch_test.go @@ -6,13 +6,12 @@ import ( "errors" "fmt" "io" - "math" mrand "math/rand" "reflect" "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" @@ -442,6 +441,7 @@ func TestSwitchForwardMapping(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() testSwitchForwardMapping( @@ -661,6 +661,7 @@ func TestSwitchSendHTLCMapping(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() testSwitchSendHtlcMapping( @@ -1916,6 +1917,7 @@ func TestCircularForwards(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -1989,139 +1991,6 @@ func TestCircularForwards(t *testing.T) { } } -// TestNodeIDNonStrictRouting ensures that when a blinded route identifies the -// next hop by node ID, non-strict forwarding deterministically selects a valid -// outgoing channel to that peer and never fails the HTLC by landing on the -// incoming channel. -func TestNodeIDNonStrictRouting(t *testing.T) { - t.Parallel() - - // bob is both the source of the incoming HTLC and the next hop - // identified by node ID, so we have two channels with bob: the channel - // the HTLC arrives on and a second, valid outgoing channel. - bobPeer, err := newMockServer( - t, "bob", testStartingHeight, nil, testDefaultDelta, - ) - require.NoError(t, err, "unable to create bob server") - - s, err := initSwitchWithTempDB(t, testStartingHeight) - require.NoError(t, err, "unable to init switch") - require.NoError(t, s.Start(), "unable to start switch") - defer func() { _ = s.Stop() }() - - // Disallow circular routes so that forwarding back over the incoming - // channel is rejected. - s.cfg.AllowCircularRoute = false - - incomingChanID, incomingScid := genID() - outgoingChanID, outgoingScid := genID() - - incomingLink := newMockChannelLink( - s, incomingChanID, incomingScid, emptyScid, bobPeer, - true, false, false, false, - ) - outgoingLink := newMockChannelLink( - s, outgoingChanID, outgoingScid, emptyScid, bobPeer, - true, false, false, false, - ) - require.NoError(t, s.AddLink(incomingLink), "unable to add incoming") - require.NoError(t, s.AddLink(outgoingLink), "unable to add outgoing") - - // Forward many HTLCs so that random selection would almost certainly - // land on the incoming channel, which will be sorted out by the switch. - const numHTLCs = 20 - for i := 0; i < numHTLCs; i++ { - var hash [sha256.Size]byte - hash[0] = byte(i) - - packet := &htlcPacket{ - incomingChanID: incomingLink.ShortChanID(), - incomingHTLCID: uint64(i), - outgoingHop: hop.NewNodeNextHop(bobPeer.PubKey()), - htlc: &lnwire.UpdateAddHTLC{ - PaymentHash: hash, - Amount: 1, - }, - obfuscator: NewMockObfuscator(), - } - - require.NoError(t, s.ForwardPackets(nil, packet)) - - select { - case p := <-outgoingLink.packets: - require.Nil(t, p.linkFailure, "unexpected link failure") - require.Equal( - t, outgoingLink.ShortChanID(), - p.outgoingChanID, - "forwarded over wrong channel", - ) - - case <-incomingLink.packets: - t.Fatal("HTLC forwarded over incoming (circular) " + - "channel") - - case <-time.After(time.Second): - t.Fatal("no timely reply from switch") - } - } -} - -// TestNodeIDNonStrictRoutingAllLinksCircular ensures that when a blinded route -// identifies the next hop by node ID, and the only channel we have with that -// peer is the incoming channel (forming a circular route), the switch fails the -// HTLC early upfront. -func TestNodeIDNonStrictRoutingAllLinksCircular(t *testing.T) { - t.Parallel() - - bobPeer, err := newMockServer( - t, "bob", testStartingHeight, nil, testDefaultDelta, - ) - require.NoError(t, err, "unable to create bob server") - - s, err := initSwitchWithTempDB(t, testStartingHeight) - require.NoError(t, err, "unable to init switch") - require.NoError(t, s.Start(), "unable to start switch") - defer func() { _ = s.Stop() }() - - // Disallow circular routes. - s.cfg.AllowCircularRoute = false - - incomingChanID, incomingScid := genID() - incomingLink := newMockChannelLink( - s, incomingChanID, incomingScid, emptyScid, bobPeer, - true, false, false, false, - ) - require.NoError(t, s.AddLink(incomingLink), "unable to add incoming") - - packet := &htlcPacket{ - incomingChanID: incomingLink.ShortChanID(), - incomingHTLCID: 1, - outgoingHop: hop.NewNodeNextHop(bobPeer.PubKey()), - htlc: &lnwire.UpdateAddHTLC{ - PaymentHash: [32]byte{1}, - Amount: 1, - }, - obfuscator: NewMockObfuscator(), - } - - err = s.ForwardPackets(nil, packet) - require.NoError(t, err, "unable to forward packets") - - select { - case p := <-incomingLink.packets: - require.NotNil(t, p.linkFailure, "expected early link failure") - wireErr := p.linkFailure.WireMessage() - var unknownNextPeer *lnwire.FailUnknownNextPeer - require.ErrorAs( - t, wireErr, &unknownNextPeer, - "expected FailUnknownNextPeer", - ) - - case <-time.After(time.Second): - t.Fatal("no timely reply from switch") - } -} - // TestCheckCircularForward tests the error returned by checkCircularForward // in cases where we allow and disallow same channel circular forwards. func TestCheckCircularForward(t *testing.T) { @@ -2230,6 +2099,7 @@ func TestCheckCircularForward(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -2313,6 +2183,7 @@ func TestSkipIneligibleLinksMultiHopForward(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { testSkipIneligibleLinksMultiHopForward(t, &test) }) @@ -3500,6 +3371,7 @@ func TestHtlcNotifier(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { testHtcNotifier( @@ -3889,19 +3761,15 @@ func assertOutgoingLinkReceive(t *testing.T, targetLink *mockChannelLink, } func assertOutgoingLinkReceiveIntercepted(t *testing.T, - targetLink *mockChannelLink) *htlcPacket { + targetLink *mockChannelLink) { t.Helper() select { - case packet := <-targetLink.packets: - return packet - + case <-targetLink.packets: case <-time.After(time.Second): t.Fatal("request was not propagated to destination") } - - return nil } type interceptableSwitchTestContext struct { @@ -4368,70 +4236,6 @@ func TestInterceptableSwitchWatchDog(t *testing.T) { })) } -// TestInterceptableSwitchExpiryTooFar asserts that an intercepted forward with -// an incoming expiry outside the supported auto-fail height range is failed -// back and that subsequent forwards can still be intercepted. -func TestInterceptableSwitchExpiryTooFar(t *testing.T) { - t.Parallel() - - c := newInterceptableSwitchTestContext(t) - defer c.finish() - - notifier := &mock.ChainNotifier{ - EpochChan: make(chan *chainntnfs.BlockEpoch, 1), - } - notifier.EpochChan <- &chainntnfs.BlockEpoch{Height: testStartingHeight} - - switchForwardInterceptor, err := NewInterceptableSwitch( - &InterceptableSwitchConfig{ - Switch: c.s, - CltvRejectDelta: c.cltvRejectDelta, - CltvInterceptDelta: c.cltvInterceptDelta, - Notifier: notifier, - }, - ) - require.NoError(t, err) - require.NoError(t, switchForwardInterceptor.Start()) - - switchForwardInterceptor.SetInterceptor( - c.forwardInterceptor.InterceptForwardHtlc, - ) - linkQuit := make(chan struct{}) - - packet := c.createTestPacket() - packet.incomingTimeout = math.MaxUint32 - - err = switchForwardInterceptor.ForwardPackets(linkQuit, false, packet) - require.NoError(t, err, "can't forward htlc packet") - - // The forward is failed back rather than being intercepted or sent to - // the outgoing link. - assertOutgoingLinkReceive(t, c.bobChannelLink, false) - failPacket := assertOutgoingLinkReceiveIntercepted( - t, c.aliceChannelLink, - ) - failHtlc, ok := failPacket.htlc.(*lnwire.UpdateFailHTLC) - require.True(t, ok) - - fwdErr, err := newMockDeobfuscator().DecryptError(failHtlc.Reason) - require.NoError(t, err) - require.IsType(t, &lnwire.FailExpiryTooFar{}, fwdErr.WireMessage()) - assertNumCircuits(t, c.s, 0, 0) - - // A later forward with a representable auto-fail height is intercepted - // normally. - require.NoError(t, switchForwardInterceptor.ForwardPackets( - linkQuit, false, c.createTestPacket(), - )) - - intercepted := c.forwardInterceptor.getIntercepted() - require.Equal(t, - int32(testStartingHeight+c.cltvInterceptDelta+1- - c.cltvRejectDelta), - intercepted.AutoFailHeight(), - ) -} - // TestSwitchDustForwarding tests that the switch properly fails HTLC's which // have incoming or outgoing links that breach their fee thresholds. func TestSwitchDustForwarding(t *testing.T) { @@ -5060,6 +4864,7 @@ func TestSwitchForwardFailAlias(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { testSwitchForwardFailAlias(t, test.zeroConf) @@ -5269,6 +5074,7 @@ func TestSwitchAliasFailAdd(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { testSwitchAliasFailAdd( @@ -5457,6 +5263,7 @@ func TestSwitchHandlePacketForward(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { testSwitchHandlePacketForward( @@ -5613,6 +5420,7 @@ func TestSwitchAliasInterceptFail(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { testSwitchAliasInterceptFail(t, test.zeroConf) diff --git a/htlcswitch/test_utils.go b/htlcswitch/test_utils.go index 32d651f7e..bdb365d3c 100644 --- a/htlcswitch/test_utils.go +++ b/htlcswitch/test_utils.go @@ -19,12 +19,11 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/htlcswitch/hop" @@ -44,20 +43,6 @@ import ( "github.com/stretchr/testify/require" ) -// testChannelStateDB extracts the ChannelStateDB from the test channel. -func testChannelStateDB(t testing.TB, - channel *lnwallet.LightningChannel) *channeldb.ChannelStateDB { - - t.Helper() - - cdb, ok := channel.State().Db.(*channeldb.ChannelStateDB) - if !ok { - t.Fatalf("expected ChannelStateDB, got %T", channel.State().Db) - } - - return cdb -} - // maxInflightHtlcs specifies the max number of inflight HTLCs. This number is // chosen to be smaller than the default 483 so the test can run faster. const maxInflightHtlcs = 50 @@ -306,7 +291,7 @@ func createTestChannel(t *testing.T, alicePrivKey, bobPrivKey []byte, CommitSig: bytes.Repeat([]byte{1}, 71), } - aliceChannelState := &chanstate.OpenChannel{ + aliceChannelState := &channeldb.OpenChannel{ LocalChanCfg: aliceCfg, RemoteChanCfg: bobCfg, IdentityPub: aliceKeyPub, @@ -321,10 +306,11 @@ func createTestChannel(t *testing.T, alicePrivKey, bobPrivKey []byte, RemoteCommitment: aliceCommit, ShortChannelID: chanID, Db: dbAlice.ChannelStateDB(), + Packager: channeldb.NewChannelPackager(chanID), FundingTxn: channels.TestFundingTx, } - bobChannelState := &chanstate.OpenChannel{ + bobChannelState := &channeldb.OpenChannel{ LocalChanCfg: bobCfg, RemoteChanCfg: aliceCfg, IdentityPub: bobKeyPub, @@ -339,6 +325,7 @@ func createTestChannel(t *testing.T, alicePrivKey, bobPrivKey []byte, RemoteCommitment: bobCommit, ShortChannelID: chanID, Db: dbBob.ChannelStateDB(), + Packager: channeldb.NewChannelPackager(chanID), } if err := aliceChannelState.SyncPending(bobAddr, broadcastHeight); err != nil { @@ -416,7 +403,7 @@ func createTestChannel(t *testing.T, alicePrivKey, bobPrivKey []byte, "channel: %w", err) } - var aliceStoredChannel *chanstate.OpenChannel + var aliceStoredChannel *channeldb.OpenChannel for _, channel := range aliceStoredChannels { if channel.FundingOutpoint.String() == prevOut.String() { aliceStoredChannel = channel @@ -464,7 +451,7 @@ func createTestChannel(t *testing.T, alicePrivKey, bobPrivKey []byte, "%w", err) } - var bobStoredChannel *chanstate.OpenChannel + var bobStoredChannel *channeldb.OpenChannel for _, channel := range bobStoredChannels { if channel.FundingOutpoint.String() == prevOut.String() { bobStoredChannel = channel @@ -967,9 +954,9 @@ func newThreeHopNetwork(t testing.TB, aliceChannel, firstBobChannel, secondBobChannel, carolChannel *lnwallet.LightningChannel, startingHeight uint32, opts ...serverOption) *threeHopNetwork { - aliceDb := testChannelStateDB(t, aliceChannel).GetParentDB() - bobDb := testChannelStateDB(t, firstBobChannel).GetParentDB() - carolDb := testChannelStateDB(t, carolChannel).GetParentDB() + aliceDb := aliceChannel.State().Db.GetParentDB() + bobDb := firstBobChannel.State().Db.GetParentDB() + carolDb := carolChannel.State().Db.GetParentDB() hopNetwork := newHopNetwork() @@ -1170,28 +1157,27 @@ func (h *hopNetwork) createChannelLink(server, peer *mockServer, UpdateContractSignals: func(*contractcourt.ContractSignals) error { return nil }, - NotifyContractUpdate: notifyContractUpdate, - ChainEvents: &contractcourt.ChainEventSubscription{}, - SyncStates: true, - BatchSize: 10, - BatchTicker: ticker.NewForce(testBatchTimeout), - FwdPkgGCTicker: ticker.NewForce(fwdPkgTimeout), - PendingCommitTicker: ticker.New(2 * time.Minute), - MinUpdateTimeout: minFeeUpdateTimeout, - MaxUpdateTimeout: maxFeeUpdateTimeout, - OnChannelFailure: func(lnwire.ChannelID, lnwire.ShortChannelID, LinkFailureError) {}, - OutgoingCltvRejectDelta: 3, - MaxOutgoingCltvExpiry: DefaultMaxOutgoingCltvExpiry, - MaxFeeAllocation: DefaultMaxLinkFeeAllocation, - MaxAnchorsCommitFeeRate: chainfee.SatPerKVByte(10 * 1000).FeePerKWeight(), - NotifyActiveLink: func(wire.OutPoint) {}, - NotifyActiveChannel: func(wire.OutPoint) {}, - NotifyInactiveChannel: func(wire.OutPoint) {}, - NotifyInactiveLinkEvent: func(wire.OutPoint) {}, - NotifyChannelUpdate: func(*chanstate.OpenChannel) {}, - HtlcNotifier: server.htlcSwitch.cfg.HtlcNotifier, - GetAliases: getAliases, - ShouldFwdExpAccountability: func() bool { return true }, + NotifyContractUpdate: notifyContractUpdate, + ChainEvents: &contractcourt.ChainEventSubscription{}, + SyncStates: true, + BatchSize: 10, + BatchTicker: ticker.NewForce(testBatchTimeout), + FwdPkgGCTicker: ticker.NewForce(fwdPkgTimeout), + PendingCommitTicker: ticker.New(2 * time.Minute), + MinUpdateTimeout: minFeeUpdateTimeout, + MaxUpdateTimeout: maxFeeUpdateTimeout, + OnChannelFailure: func(lnwire.ChannelID, lnwire.ShortChannelID, LinkFailureError) {}, + OutgoingCltvRejectDelta: 3, + MaxOutgoingCltvExpiry: DefaultMaxOutgoingCltvExpiry, + MaxFeeAllocation: DefaultMaxLinkFeeAllocation, + MaxAnchorsCommitFeeRate: chainfee.SatPerKVByte(10 * 1000).FeePerKWeight(), + NotifyActiveLink: func(wire.OutPoint) {}, + NotifyActiveChannel: func(wire.OutPoint) {}, + NotifyInactiveChannel: func(wire.OutPoint) {}, + NotifyInactiveLinkEvent: func(wire.OutPoint) {}, + HtlcNotifier: server.htlcSwitch.cfg.HtlcNotifier, + GetAliases: getAliases, + ShouldFwdExpEndorsement: func() bool { return true }, }, channel, ) @@ -1246,8 +1232,8 @@ func newTwoHopNetwork(t testing.TB, aliceChannel, bobChannel *lnwallet.LightningChannel, startingHeight uint32) *twoHopNetwork { - aliceDb := testChannelStateDB(t, aliceChannel).GetParentDB() - bobDb := testChannelStateDB(t, bobChannel).GetParentDB() + aliceDb := aliceChannel.State().Db.GetParentDB() + bobDb := bobChannel.State().Db.GetParentDB() hopNetwork := newHopNetwork() diff --git a/input/fuzz_script_is_op_return_test.go b/input/fuzz_script_is_op_return_test.go index ca6b73066..148d7b02e 100644 --- a/input/fuzz_script_is_op_return_test.go +++ b/input/fuzz_script_is_op_return_test.go @@ -3,8 +3,8 @@ package input import ( "testing" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/txscript/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" "github.com/stretchr/testify/require" ) diff --git a/input/input.go b/input/input.go index 13e6dc378..4a9a4b55c 100644 --- a/input/input.go +++ b/input/input.go @@ -3,9 +3,9 @@ package input import ( "fmt" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/tlv" @@ -339,25 +339,6 @@ func MakeTaprootHtlcSucceedInput(op *wire.OutPoint, signDesc *SignDescriptor, } } -// MakeTaprootHtlcSucceedInputFinal creates a new HtlcSucceedInput that can be -// used to spend an HTLC output for a production taproot channel on the remote -// party's commitment transaction. -func MakeTaprootHtlcSucceedInputFinal(op *wire.OutPoint, - signDesc *SignDescriptor, preimage []byte, heightHint, - blocksToMaturity uint32, opts ...InputOpt) HtlcSucceedInput { - - input := MakeBaseInput( - op, TaprootHtlcAcceptedRemoteSuccessFinal, signDesc, - heightHint, nil, opts..., - ) - input.blockToMaturity = blocksToMaturity - - return HtlcSucceedInput{ - inputKit: input.inputKit, - preimage: preimage, - } -} - // CraftInputScript returns a valid set of input scripts allowing this output // to be spent. The returns input scripts should target the input at location // txIndex within the passed transaction. The input scripts generated by this diff --git a/input/mocks.go b/input/mocks.go index f8ec38a90..6d90bc28d 100644 --- a/input/mocks.go +++ b/input/mocks.go @@ -6,8 +6,8 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lntypes" @@ -258,29 +258,6 @@ func (m *MockInputSigner) MuSig2RegisterNonces(versio MuSig2SessionID, return args.Bool(0), args.Error(1) } -// MuSig2RegisterCombinedNonce registers a pre-aggregated combined nonce for a -// session identified by its ID. -func (m *MockInputSigner) MuSig2RegisterCombinedNonce(sessionID MuSig2SessionID, - combinedNonce [musig2.PubNonceSize]byte) error { - - args := m.Called(sessionID, combinedNonce) - - return args.Error(0) -} - -// MuSig2GetCombinedNonce retrieves the combined nonce for a session identified -// by its ID. -func (m *MockInputSigner) MuSig2GetCombinedNonce(sessionID MuSig2SessionID) ( - [musig2.PubNonceSize]byte, error) { - - args := m.Called(sessionID) - if args.Get(0) == nil { - return [musig2.PubNonceSize]byte{}, args.Error(1) - } - - return args.Get(0).([musig2.PubNonceSize]byte), args.Error(1) -} - // MuSig2Sign creates a partial signature using the local signing key that was // specified when the session was created. func (m *MockInputSigner) MuSig2Sign(sessionID MuSig2SessionID, diff --git a/input/musig2.go b/input/musig2.go index a085579e6..589152282 100644 --- a/input/musig2.go +++ b/input/musig2.go @@ -64,25 +64,6 @@ type MuSig2Signer interface { MuSig2RegisterNonces(MuSig2SessionID, [][musig2.PubNonceSize]byte) (bool, error) - // MuSig2RegisterCombinedNonce registers a pre-aggregated combined nonce - // for a session identified by its ID. This is an alternative to - // MuSig2RegisterNonces and is used when a coordinator has already - // aggregated all individual nonces and wants to distribute the combined - // nonce to participants. - // - // NOTE: This method is mutually exclusive with MuSig2RegisterNonces for - // the same session. Once this method is called, MuSig2RegisterNonces - // will return an error if called later for the same session. - MuSig2RegisterCombinedNonce(MuSig2SessionID, - [musig2.PubNonceSize]byte) error - - // MuSig2GetCombinedNonce retrieves the combined nonce for a session - // identified by its ID. This will be available after either all - // individual nonces have been registered via MuSig2RegisterNonces, or a - // combined nonce has been registered via MuSig2RegisterCombinedNonce. - MuSig2GetCombinedNonce(MuSig2SessionID) ([musig2.PubNonceSize]byte, - error) - // MuSig2Sign creates a partial signature using the local signing key // that was specified when the session was created. This can only be // called when all public nonces of all participants are known and have @@ -149,26 +130,6 @@ type MuSig2Session interface { // of signers. This method returns true once all the public nonces have // been accounted for. RegisterPubNonce(nonce [musig2.PubNonceSize]byte) (bool, error) - - // CombinedNonce returns the combined/aggregated public nonce for the - // session. This will be available after either all individual nonces - // have been registered via RegisterPubNonce, or a combined nonce has - // been registered via RegisterCombinedNonce. - // - // If the combined nonce is not yet available, this method returns an - // error. - CombinedNonce() ([musig2.PubNonceSize]byte, error) - - // RegisterCombinedNonce allows a caller to directly register a - // pre-aggregated nonce that was generated externally. This is useful - // in coordinator-based protocols where the coordinator aggregates all - // nonces and distributes the combined nonce to participants. - // - // NOTE: This method is mutually exclusive with RegisterPubNonce. Once - // this method is called, RegisterPubNonce will return an error if - // called later. Similarly, if RegisterPubNonce has already been called, - // this method will return an error. - RegisterCombinedNonce(combinedNonce [musig2.PubNonceSize]byte) error } // MuSig2SessionInfo is a struct for keeping track of a signing session diff --git a/input/musig2_session_manager.go b/input/musig2_session_manager.go index cb459ea6e..b2cac4899 100644 --- a/input/musig2_session_manager.go +++ b/input/musig2_session_manager.go @@ -302,70 +302,3 @@ func (m *MusigSessionManager) MuSig2RegisterNonces(sessionID MuSig2SessionID, return session.HaveAllNonces, nil } - -// MuSig2RegisterCombinedNonce registers a pre-aggregated combined nonce for a -// session identified by its ID. This is an alternative to MuSig2RegisterNonces -// and is used when a coordinator has already aggregated all individual nonces -// and wants to distribute the combined nonce to participants. -// -// NOTE: This method is mutually exclusive with MuSig2RegisterNonces for the -// same session. Once this method is called, MuSig2RegisterNonces will return -// an error if called later for the same session. -func (m *MusigSessionManager) MuSig2RegisterCombinedNonce( - sessionID MuSig2SessionID, - combinedNonce [musig2.PubNonceSize]byte) error { - - // Hold the lock during the whole operation. - m.sessionMtx.Lock(sessionID) - defer m.sessionMtx.Unlock(sessionID) - - // Load the session. - session, ok := m.musig2Sessions.Load(sessionID) - if !ok { - return fmt.Errorf("session with ID %x not found", sessionID[:]) - } - - // Check if we already have all nonces. - if session.HaveAllNonces { - return fmt.Errorf("already have all nonces") - } - - // Delegate to the version-specific implementation. - err := session.session.RegisterCombinedNonce(combinedNonce) - if err != nil { - return fmt.Errorf("error registering combined nonce: %w", err) - } - - // Mark that we have all nonces now. - session.HaveAllNonces = true - - return nil -} - -// MuSig2GetCombinedNonce retrieves the combined nonce for a session identified -// by its ID. This will be available after either all individual nonces have -// been registered via MuSig2RegisterNonces, or a combined nonce has been -// registered via MuSig2RegisterCombinedNonce. -func (m *MusigSessionManager) MuSig2GetCombinedNonce( - sessionID MuSig2SessionID) ([musig2.PubNonceSize]byte, error) { - - // Hold the lock during the operation. - m.sessionMtx.Lock(sessionID) - defer m.sessionMtx.Unlock(sessionID) - - // Load the session. - session, ok := m.musig2Sessions.Load(sessionID) - if !ok { - return [musig2.PubNonceSize]byte{}, fmt.Errorf("session with "+ - "ID %x not found", sessionID[:]) - } - - // Get the combined nonce from the session. - combinedNonce, err := session.session.CombinedNonce() - if err != nil { - return [musig2.PubNonceSize]byte{}, fmt.Errorf("error getting "+ - "combined nonce: %w", err) - } - - return combinedNonce, nil -} diff --git a/input/musig2_test.go b/input/musig2_test.go index dea5d3d31..1fccd1686 100644 --- a/input/musig2_test.go +++ b/input/musig2_test.go @@ -178,6 +178,7 @@ func TestMuSig2CombineKeys(t *testing.T) { }} for _, tc := range testCases { + tc := tc t.Run(tc.name, func(tt *testing.T) { tt.Parallel() diff --git a/input/script_desc.go b/input/script_desc.go index 3b566e2e6..17b58b721 100644 --- a/input/script_desc.go +++ b/input/script_desc.go @@ -4,7 +4,7 @@ import ( "errors" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/txscript/v2" + "github.com/btcsuite/btcd/txscript" "github.com/lightningnetwork/lnd/lnutils" ) diff --git a/input/script_utils.go b/input/script_utils.go index f42d2be26..0b38fad35 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -6,24 +6,20 @@ import ( "encoding/hex" "fmt" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnutils" "golang.org/x/crypto/ripemd160" ) -// TemplateParams is a type alias for the map[string]interface{} type used -// with txscript.ScriptTemplate to make code more readable. -type TemplateParams map[string]interface{} - var ( // TODO(roasbeef): remove these and use the one's defined in txscript // within testnet-L. @@ -87,62 +83,69 @@ func ParseSignature(rawSig []byte) (Signature, error) { // WitnessScriptHash generates a pay-to-witness-script-hash public key script // paying to a version 0 witness program paying to the passed redeem script. func WitnessScriptHash(witnessScript []byte) ([]byte, error) { - scriptHash := sha256.Sum256(witnessScript) - return txscript.ScriptTemplate( - `OP_0 {{ hex .ScriptHash }}`, - txscript.WithScriptTemplateParams(TemplateParams{ - "ScriptHash": scriptHash[:], - }), + bldr := txscript.NewScriptBuilder( + txscript.WithScriptAllocSize(P2WSHSize), ) + + bldr.AddOp(txscript.OP_0) + scriptHash := sha256.Sum256(witnessScript) + bldr.AddData(scriptHash[:]) + return bldr.Script() } // WitnessPubKeyHash generates a pay-to-witness-pubkey-hash public key script // paying to a version 0 witness program containing the passed serialized // public key. func WitnessPubKeyHash(pubkey []byte) ([]byte, error) { - pkhash := address.Hash160(pubkey) - return txscript.ScriptTemplate( - `OP_0 {{ hex .PKHash }}`, - txscript.WithScriptTemplateParams(TemplateParams{ - "PKHash": pkhash, - }), + bldr := txscript.NewScriptBuilder( + txscript.WithScriptAllocSize(P2WPKHSize), ) + + bldr.AddOp(txscript.OP_0) + pkhash := btcutil.Hash160(pubkey) + bldr.AddData(pkhash) + return bldr.Script() } // GenerateP2SH generates a pay-to-script-hash public key script paying to the // passed redeem script. func GenerateP2SH(script []byte) ([]byte, error) { - scriptHash := address.Hash160(script) - return txscript.ScriptTemplate( - `OP_HASH160 {{ hex .ScriptHash }} OP_EQUAL`, - txscript.WithScriptTemplateParams(TemplateParams{ - "ScriptHash": scriptHash, - }), + bldr := txscript.NewScriptBuilder( + txscript.WithScriptAllocSize(NestedP2WPKHSize), ) + + bldr.AddOp(txscript.OP_HASH160) + scripthash := btcutil.Hash160(script) + bldr.AddData(scripthash) + bldr.AddOp(txscript.OP_EQUAL) + return bldr.Script() } // GenerateP2PKH generates a pay-to-public-key-hash public key script paying to // the passed serialized public key. func GenerateP2PKH(pubkey []byte) ([]byte, error) { - pkHash := address.Hash160(pubkey) - return txscript.ScriptTemplate( - `OP_DUP OP_HASH160 {{ hex .pkh }} OP_EQUALVERIFY OP_CHECKSIG`, - txscript.WithScriptTemplateParams(TemplateParams{ - "pkh": pkHash, - }), + bldr := txscript.NewScriptBuilder( + txscript.WithScriptAllocSize(P2PKHSize), ) + + bldr.AddOp(txscript.OP_DUP) + bldr.AddOp(txscript.OP_HASH160) + pkhash := btcutil.Hash160(pubkey) + bldr.AddData(pkhash) + bldr.AddOp(txscript.OP_EQUALVERIFY) + bldr.AddOp(txscript.OP_CHECKSIG) + return bldr.Script() } // GenerateUnknownWitness generates the maximum-sized witness public key script // consisting of a version push and a 40-byte data push. func GenerateUnknownWitness() ([]byte, error) { + bldr := txscript.NewScriptBuilder() + + bldr.AddOp(txscript.OP_0) witnessScript := make([]byte, 40) - return txscript.ScriptTemplate( - `OP_0 {{ hex .WitnessScript }}`, - txscript.WithScriptTemplateParams(TemplateParams{ - "WitnessScript": witnessScript, - }), - ) + bldr.AddData(witnessScript) + return bldr.Script() } // GenMultiSigScript generates the non-p2sh'd multisig script for 2 of 2 @@ -161,13 +164,15 @@ func GenMultiSigScript(aPub, bPub []byte) ([]byte, error) { aPub, bPub = bPub, aPub } - return txscript.ScriptTemplate( - `OP_2 {{ hex .pubA }} {{ hex .pubB }} OP_2 OP_CHECKMULTISIG`, - txscript.WithScriptTemplateParams(TemplateParams{ - "pubA": aPub, - "pubB": bPub, - }), - ) + bldr := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( + MultiSigSize, + )) + bldr.AddOp(txscript.OP_2) + bldr.AddData(aPub) // Add both pubkeys (sorted). + bldr.AddData(bPub) + bldr.AddOp(txscript.OP_2) + bldr.AddOp(txscript.OP_CHECKMULTISIG) + return bldr.Script() } // GenFundingPkScript creates a redeem script, and its matching p2wsh @@ -333,54 +338,92 @@ func SenderHTLCScript(senderHtlcKey, receiverHtlcKey, revocationKey *btcec.PublicKey, paymentHash []byte, confirmedSpend bool) ([]byte, error) { - // Build the base script template. The script structure is: - // - Revocation path: hash the top-of-stack item and check if it - // matches the revocation key hash. If so, checksig. - // - Else branch: push the receiver's key, swap to expose the - // witness item, and check its size. - // - If not 32 bytes (timeout path): drop, then 2-of-2 multisig - // with sender+receiver HTLC keys. - // - If 32 bytes (preimage path): hash160-verify, then checksig - // with the receiver's key. - // - Optional 1-block CSV for confirmed spend. - scriptTemplate := ` - OP_DUP OP_HASH160 {{ hex .RevKeyHash }} OP_EQUAL - OP_IF - OP_CHECKSIG - OP_ELSE - {{ hex .ReceiverKey }} - OP_SWAP - OP_SIZE 32 OP_EQUAL - OP_NOTIF - OP_DROP 2 OP_SWAP - {{ hex .SenderKey }} 2 OP_CHECKMULTISIG - OP_ELSE - OP_HASH160 {{ hex .PaymentHashRipemd }} - OP_EQUALVERIFY - OP_CHECKSIG - OP_ENDIF - ` + builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( + OfferedHtlcScriptSizeConfirmed, + )) + + // The opening operations are used to determine if this is the receiver + // of the HTLC attempting to sweep all the funds due to a contract + // breach. In this case, they'll place the revocation key at the top of + // the stack. + builder.AddOp(txscript.OP_DUP) + builder.AddOp(txscript.OP_HASH160) + builder.AddData(btcutil.Hash160(revocationKey.SerializeCompressed())) + builder.AddOp(txscript.OP_EQUAL) + + // If the hash matches, then this is the revocation clause. The output + // can be spent if the check sig operation passes. + builder.AddOp(txscript.OP_IF) + builder.AddOp(txscript.OP_CHECKSIG) + + // Otherwise, this may either be the receiver of the HTLC claiming with + // the pre-image, or the sender of the HTLC sweeping the output after + // it has timed out. + builder.AddOp(txscript.OP_ELSE) + + // We'll do a bit of set up by pushing the receiver's key on the top of + // the stack. This will be needed later if we decide that this is the + // sender activating the time out clause with the HTLC timeout + // transaction. + builder.AddData(receiverHtlcKey.SerializeCompressed()) + + // Atm, the top item of the stack is the receiverKey's so we use a swap + // to expose what is either the payment pre-image or a signature. + builder.AddOp(txscript.OP_SWAP) + + // With the top item swapped, check if it's 32 bytes. If so, then this + // *may* be the payment pre-image. + builder.AddOp(txscript.OP_SIZE) + builder.AddInt64(32) + builder.AddOp(txscript.OP_EQUAL) + + // If it isn't then this might be the sender of the HTLC activating the + // time out clause. + builder.AddOp(txscript.OP_NOTIF) + + // We'll drop the OP_IF return value off the top of the stack so we can + // reconstruct the multi-sig script used as an off-chain covenant. If + // two valid signatures are provided, then the output will be deemed as + // spendable. + builder.AddOp(txscript.OP_DROP) + builder.AddOp(txscript.OP_2) + builder.AddOp(txscript.OP_SWAP) + builder.AddData(senderHtlcKey.SerializeCompressed()) + builder.AddOp(txscript.OP_2) + builder.AddOp(txscript.OP_CHECKMULTISIG) + + // Otherwise, then the only other case is that this is the receiver of + // the HTLC sweeping it on-chain with the payment pre-image. + builder.AddOp(txscript.OP_ELSE) + + // Hash the top item of the stack and compare it with the hash160 of + // the payment hash, which is already the sha256 of the payment + // pre-image. By using this little trick we're able to save space + // on-chain as the witness includes a 20-byte hash rather than a + // 32-byte hash. + builder.AddOp(txscript.OP_HASH160) + builder.AddData(Ripemd160H(paymentHash)) + builder.AddOp(txscript.OP_EQUALVERIFY) + + // This checks the receiver's signature so that a third party with + // knowledge of the payment preimage still cannot steal the output. + builder.AddOp(txscript.OP_CHECKSIG) + + // Close out the OP_IF statement above. + builder.AddOp(txscript.OP_ENDIF) // Add 1 block CSV delay if a confirmation is required for the // non-revocation clauses. if confirmedSpend { - scriptTemplate += ` - OP_1 OP_CHECKSEQUENCEVERIFY OP_DROP` + builder.AddOp(txscript.OP_1) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_DROP) } - // Close out the top level if statement. - scriptTemplate += ` OP_ENDIF` + // Close out the OP_IF statement at the top of the script. + builder.AddOp(txscript.OP_ENDIF) - // Use the ScriptTemplate function with the properly formatted template - return txscript.ScriptTemplate( - scriptTemplate, - txscript.WithScriptTemplateParams(TemplateParams{ - "RevKeyHash": address.Hash160(revocationKey.SerializeCompressed()), //nolint:ll - "ReceiverKey": receiverHtlcKey.SerializeCompressed(), //nolint:ll - "SenderKey": senderHtlcKey.SerializeCompressed(), //nolint:ll - "PaymentHashRipemd": Ripemd160H(paymentHash), - }), - ) + return builder.Script() } // SenderHtlcSpendRevokeWithKey constructs a valid witness allowing the receiver of an @@ -514,18 +557,16 @@ func SenderHtlcSpendTimeout(receiverSig Signature, // OP_CHECKSIGVERIFY // OP_CHECKSIG func SenderHTLCTapLeafTimeout(senderHtlcKey, - receiverHtlcKey *btcec.PublicKey, - _ ...TaprootScriptOpt) (txscript.TapLeaf, error) { + receiverHtlcKey *btcec.PublicKey) (txscript.TapLeaf, error) { - timeoutLeafScript, err := txscript.ScriptTemplate( - ` - {{ hex .SenderKey }} OP_CHECKSIGVERIFY - {{ hex .ReceiverKey }} OP_CHECKSIG`, - txscript.WithScriptTemplateParams(TemplateParams{ - "SenderKey": schnorr.SerializePubKey(senderHtlcKey), - "ReceiverKey": schnorr.SerializePubKey(receiverHtlcKey), - }), - ) + builder := txscript.NewScriptBuilder() + + builder.AddData(schnorr.SerializePubKey(senderHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIGVERIFY) + builder.AddData(schnorr.SerializePubKey(receiverHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIG) + + timeoutLeafScript, err := builder.Script() if err != nil { return txscript.TapLeaf{}, err } @@ -542,41 +583,30 @@ func SenderHTLCTapLeafTimeout(senderHtlcKey, // OP_CHECKSIG // 1 OP_CHECKSEQUENCEVERIFY OP_DROP func SenderHTLCTapLeafSuccess(receiverHtlcKey *btcec.PublicKey, - paymentHash []byte, - opts ...TaprootScriptOpt) (txscript.TapLeaf, error) { + paymentHash []byte) (txscript.TapLeaf, error) { - opt := defaultTaprootScriptOpt() - for _, o := range opts { - o(opt) - } + builder := txscript.NewScriptBuilder() - // Check pre-image size (32 bytes), verify hash, then verify the - // remote party's signature with a 1-block CSV delay. - var scriptTemplate string - switch { - case opt.prodScript: - scriptTemplate = ` - OP_SIZE 32 OP_EQUALVERIFY - OP_HASH160 {{ hex .PaymentHashRipemd }} OP_EQUALVERIFY - {{ hex .ReceiverKey }} OP_CHECKSIGVERIFY - OP_1 OP_CHECKSEQUENCEVERIFY` - default: - scriptTemplate = ` - OP_SIZE 32 OP_EQUALVERIFY - OP_HASH160 {{ hex .PaymentHashRipemd }} OP_EQUALVERIFY - {{ hex .ReceiverKey }} OP_CHECKSIG - OP_1 OP_CHECKSEQUENCEVERIFY OP_DROP` - } + // Check that the pre-image is 32 bytes as required. + builder.AddOp(txscript.OP_SIZE) + builder.AddInt64(32) + builder.AddOp(txscript.OP_EQUALVERIFY) - successLeafScript, err := txscript.ScriptTemplate( - scriptTemplate, - txscript.WithScriptTemplateParams(TemplateParams{ - "PaymentHashRipemd": Ripemd160H(paymentHash), - "ReceiverKey": schnorr.SerializePubKey( - receiverHtlcKey, - ), - }), - ) + // Check that the specified pre-image matches what we hard code into + // the script. + builder.AddOp(txscript.OP_HASH160) + builder.AddData(Ripemd160H(paymentHash)) + builder.AddOp(txscript.OP_EQUALVERIFY) + + // Verify the remote party's signature, then make them wait 1 block + // after confirmation to properly sweep. + builder.AddData(schnorr.SerializePubKey(receiverHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIG) + builder.AddOp(txscript.OP_1) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_DROP) + + successLeafScript, err := builder.Script() if err != nil { return txscript.TapLeaf{}, err } @@ -704,19 +734,18 @@ var _ TapscriptDescriptor = (*HtlcScriptTree)(nil) // the HTLC key for HTLCs on the sender's commitment. func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, revokeKey *btcec.PublicKey, payHash []byte, hType htlcType, - auxLeaf AuxTapLeaf, - opts ...TaprootScriptOpt) (*HtlcScriptTree, error) { + auxLeaf AuxTapLeaf) (*HtlcScriptTree, error) { // First, we'll obtain the tap leaves for both the success and timeout // path. successTapLeaf, err := SenderHTLCTapLeafSuccess( - receiverHtlcKey, payHash, opts..., + receiverHtlcKey, payHash, ) if err != nil { return nil, err } timeoutTapLeaf, err := SenderHTLCTapLeafTimeout( - senderHtlcKey, receiverHtlcKey, opts..., + senderHtlcKey, receiverHtlcKey, ) if err != nil { return nil, err @@ -783,8 +812,8 @@ func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, // unilaterally spend the created output. func SenderHTLCScriptTaproot(senderHtlcKey, receiverHtlcKey, revokeKey *btcec.PublicKey, payHash []byte, - whoseCommit lntypes.ChannelParty, auxLeaf AuxTapLeaf, - opts ...TaprootScriptOpt) (*HtlcScriptTree, error) { + whoseCommit lntypes.ChannelParty, auxLeaf AuxTapLeaf) (*HtlcScriptTree, + error) { var hType htlcType if whoseCommit.IsLocal() { @@ -798,7 +827,7 @@ func SenderHTLCScriptTaproot(senderHtlcKey, receiverHtlcKey, // tap leaf paths. return senderHtlcTapScriptTree( senderHtlcKey, receiverHtlcKey, revokeKey, payHash, hType, - auxLeaf, opts..., + auxLeaf, ) } @@ -958,57 +987,101 @@ func ReceiverHTLCScript(cltvExpiry uint32, senderHtlcKey, receiverHtlcKey, revocationKey *btcec.PublicKey, paymentHash []byte, confirmedSpend bool) ([]byte, error) { - // The script structure mirrors SenderHTLCScript but from the - // receiver's perspective: - // - Revocation path: DUP+HASH160 check, then checksig. - // - Else: push sender key, swap, check size. - // - If 32 bytes (preimage path): hash160-verify the preimage, - // then 2-of-2 multisig with sender+receiver keys. - // - If not 32 bytes (timeout path): drop, CLTV check, checksig. - // - Optional 1-block CSV for confirmed spend. - scriptTemplate := ` - OP_DUP OP_HASH160 {{ hex .RevKeyHash }} OP_EQUAL - OP_IF - OP_CHECKSIG - OP_ELSE - {{ hex .SenderKey }} - OP_SWAP - OP_SIZE 32 OP_EQUAL - OP_IF - OP_HASH160 {{ hex .PaymentHashRipemd }} - OP_EQUALVERIFY - OP_2 OP_SWAP {{ hex .ReceiverKey }} - OP_2 OP_CHECKMULTISIG - OP_ELSE - OP_DROP - {{ .CltvExpiry }} OP_CHECKLOCKTIMEVERIFY - OP_DROP OP_CHECKSIG - OP_ENDIF - ` + builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( + AcceptedHtlcScriptSizeConfirmed, + )) + + // The opening operations are used to determine if this is the sender + // of the HTLC attempting to sweep all the funds due to a contract + // breach. In this case, they'll place the revocation key at the top of + // the stack. + builder.AddOp(txscript.OP_DUP) + builder.AddOp(txscript.OP_HASH160) + builder.AddData(btcutil.Hash160(revocationKey.SerializeCompressed())) + builder.AddOp(txscript.OP_EQUAL) + + // If the hash matches, then this is the revocation clause. The output + // can be spent if the check sig operation passes. + builder.AddOp(txscript.OP_IF) + builder.AddOp(txscript.OP_CHECKSIG) + + // Otherwise, this may either be the receiver of the HTLC starting the + // claiming process via the second level HTLC success transaction and + // the pre-image, or the sender of the HTLC sweeping the output after + // it has timed out. + builder.AddOp(txscript.OP_ELSE) + + // We'll do a bit of set up by pushing the sender's key on the top of + // the stack. This will be needed later if we decide that this is the + // receiver transitioning the output to the claim state using their + // second-level HTLC success transaction. + builder.AddData(senderHtlcKey.SerializeCompressed()) + + // Atm, the top item of the stack is the sender's key so we use a swap + // to expose what is either the payment pre-image or something else. + builder.AddOp(txscript.OP_SWAP) + + // With the top item swapped, check if it's 32 bytes. If so, then this + // *may* be the payment pre-image. + builder.AddOp(txscript.OP_SIZE) + builder.AddInt64(32) + builder.AddOp(txscript.OP_EQUAL) + + // If the item on the top of the stack is 32-bytes, then it is the + // proper size, so this indicates that the receiver of the HTLC is + // attempting to claim the output on-chain by transitioning the state + // of the HTLC to delay+claim. + builder.AddOp(txscript.OP_IF) + + // Next we'll hash the item on the top of the stack, if it matches the + // payment pre-image, then we'll continue. Otherwise, we'll end the + // script here as this is the invalid payment pre-image. + builder.AddOp(txscript.OP_HASH160) + builder.AddData(Ripemd160H(paymentHash)) + builder.AddOp(txscript.OP_EQUALVERIFY) + + // If the payment hash matches, then we'll also need to satisfy the + // multi-sig covenant by providing both signatures of the sender and + // receiver. If the convenient is met, then we'll allow the spending of + // this output, but only by the HTLC success transaction. + builder.AddOp(txscript.OP_2) + builder.AddOp(txscript.OP_SWAP) + builder.AddData(receiverHtlcKey.SerializeCompressed()) + builder.AddOp(txscript.OP_2) + builder.AddOp(txscript.OP_CHECKMULTISIG) + + // Otherwise, this might be the sender of the HTLC attempting to sweep + // it on-chain after the timeout. + builder.AddOp(txscript.OP_ELSE) + + // We'll drop the extra item (which is the output from evaluating the + // OP_EQUAL) above from the stack. + builder.AddOp(txscript.OP_DROP) + + // With that item dropped off, we can now enforce the absolute + // lock-time required to timeout the HTLC. If the time has passed, then + // we'll proceed with a checksig to ensure that this is actually the + // sender of he original HTLC. + builder.AddInt64(int64(cltvExpiry)) + builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) + builder.AddOp(txscript.OP_DROP) + builder.AddOp(txscript.OP_CHECKSIG) + + // Close out the inner if statement. + builder.AddOp(txscript.OP_ENDIF) // Add 1 block CSV delay for non-revocation clauses if confirmation is // required. if confirmedSpend { - scriptTemplate += ` - OP_1 OP_CHECKSEQUENCEVERIFY OP_DROP` + builder.AddOp(txscript.OP_1) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_DROP) } // Close out the outer if statement. - scriptTemplate += ` OP_ENDIF` + builder.AddOp(txscript.OP_ENDIF) - // Use the ScriptTemplate function with the properly formatted template - return txscript.ScriptTemplate( - scriptTemplate, - txscript.WithScriptTemplateParams(TemplateParams{ - "RevKeyHash": address.Hash160( - revocationKey.SerializeCompressed(), - ), - "SenderKey": senderHtlcKey.SerializeCompressed(), //nolint:ll - "ReceiverKey": receiverHtlcKey.SerializeCompressed(), //nolint:ll - "PaymentHashRipemd": Ripemd160H(paymentHash), - "CltvExpiry": int64(cltvExpiry), - }), - ) + return builder.Script() } // ReceiverHtlcSpendRedeem constructs a valid witness allowing the receiver of @@ -1155,37 +1228,25 @@ func ReceiverHtlcSpendTimeout(signer Signer, signDesc *SignDescriptor, // 1 OP_CHECKSEQUENCEVERIFY OP_DROP // OP_CHECKLOCKTIMEVERIFY OP_DROP func ReceiverHtlcTapLeafTimeout(senderHtlcKey *btcec.PublicKey, - cltvExpiry uint32, opts ...TaprootScriptOpt) (txscript.TapLeaf, error) { + cltvExpiry uint32) (txscript.TapLeaf, error) { - opt := defaultTaprootScriptOpt() - for _, o := range opts { - o(opt) - } + builder := txscript.NewScriptBuilder() - // Verify sender signature, enforce 1-block CSV, then verify CLTV - // expiry. The prod script variant uses CHECKSIGVERIFY/VERIFY instead - // of CHECKSIG+DROP patterns. - var scriptTemplate string - switch { - case opt.prodScript: - scriptTemplate = ` - {{ hex .SenderKey }} OP_CHECKSIGVERIFY - OP_1 OP_CHECKSEQUENCEVERIFY OP_VERIFY - {{ .CltvExpiry }} OP_CHECKLOCKTIMEVERIFY` - default: - scriptTemplate = ` - {{ hex .SenderKey }} OP_CHECKSIG - OP_1 OP_CHECKSEQUENCEVERIFY OP_DROP - {{ .CltvExpiry }} OP_CHECKLOCKTIMEVERIFY OP_DROP` - } + // The first part of the script will verify a signature from the + // sender authorizing the spend (the timeout). + builder.AddData(schnorr.SerializePubKey(senderHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIG) + builder.AddOp(txscript.OP_1) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_DROP) - timeoutLeafScript, err := txscript.ScriptTemplate( - scriptTemplate, - txscript.WithScriptTemplateParams(TemplateParams{ - "SenderKey": schnorr.SerializePubKey(senderHtlcKey), - "CltvExpiry": int64(cltvExpiry), - }), - ) + // The second portion will ensure that the CLTV expiry on the spending + // transaction is correct. + builder.AddInt64(int64(cltvExpiry)) + builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) + builder.AddOp(txscript.OP_DROP) + + timeoutLeafScript, err := builder.Script() if err != nil { return txscript.TapLeaf{}, err } @@ -1203,23 +1264,29 @@ func ReceiverHtlcTapLeafTimeout(senderHtlcKey *btcec.PublicKey, // OP_CHECKSIG func ReceiverHtlcTapLeafSuccess(receiverHtlcKey *btcec.PublicKey, senderHtlcKey *btcec.PublicKey, - paymentHash []byte, - _ ...TaprootScriptOpt) (txscript.TapLeaf, error) { + paymentHash []byte) (txscript.TapLeaf, error) { - successLeafScript, err := txscript.ScriptTemplate( - ` - OP_SIZE 32 OP_EQUALVERIFY - OP_HASH160 {{ hex .PaymentHashRipemd }} OP_EQUALVERIFY - {{ hex .ReceiverKey }} OP_CHECKSIGVERIFY - {{ hex .SenderKey }} OP_CHECKSIG`, - txscript.WithScriptTemplateParams(TemplateParams{ - "PaymentHashRipemd": Ripemd160H(paymentHash), - "ReceiverKey": schnorr.SerializePubKey( - receiverHtlcKey, - ), - "SenderKey": schnorr.SerializePubKey(senderHtlcKey), - }), - ) + builder := txscript.NewScriptBuilder() + + // Check that the pre-image is 32 bytes as required. + builder.AddOp(txscript.OP_SIZE) + builder.AddInt64(32) + builder.AddOp(txscript.OP_EQUALVERIFY) + + // Check that the specified pre-image matches what we hard code into + // the script. + builder.AddOp(txscript.OP_HASH160) + builder.AddData(Ripemd160H(paymentHash)) + builder.AddOp(txscript.OP_EQUALVERIFY) + + // Verify the "2-of-2" multi-sig that requires both parties to sign + // off. + builder.AddData(schnorr.SerializePubKey(receiverHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIGVERIFY) + builder.AddData(schnorr.SerializePubKey(senderHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIG) + + successLeafScript, err := builder.Script() if err != nil { return txscript.TapLeaf{}, err } @@ -1231,19 +1298,18 @@ func ReceiverHtlcTapLeafSuccess(receiverHtlcKey *btcec.PublicKey, // the HTLC key for HTLCs on the receiver's commitment. func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, revokeKey *btcec.PublicKey, payHash []byte, cltvExpiry uint32, - hType htlcType, auxLeaf AuxTapLeaf, - opts ...TaprootScriptOpt) (*HtlcScriptTree, error) { + hType htlcType, auxLeaf AuxTapLeaf) (*HtlcScriptTree, error) { // First, we'll obtain the tap leaves for both the success and timeout // path. successTapLeaf, err := ReceiverHtlcTapLeafSuccess( - receiverHtlcKey, senderHtlcKey, payHash, opts..., + receiverHtlcKey, senderHtlcKey, payHash, ) if err != nil { return nil, err } timeoutTapLeaf, err := ReceiverHtlcTapLeafTimeout( - senderHtlcKey, cltvExpiry, opts..., + senderHtlcKey, cltvExpiry, ) if err != nil { return nil, err @@ -1311,7 +1377,7 @@ func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, func ReceiverHTLCScriptTaproot(cltvExpiry uint32, senderHtlcKey, receiverHtlcKey, revocationKey *btcec.PublicKey, payHash []byte, whoseCommit lntypes.ChannelParty, - auxLeaf AuxTapLeaf, opts ...TaprootScriptOpt) (*HtlcScriptTree, error) { + auxLeaf AuxTapLeaf) (*HtlcScriptTree, error) { var hType htlcType if whoseCommit.IsLocal() { @@ -1325,7 +1391,7 @@ func ReceiverHTLCScriptTaproot(cltvExpiry uint32, // tap leaf paths. return receiverHtlcTapScriptTree( senderHtlcKey, receiverHtlcKey, revocationKey, payHash, - cltvExpiry, hType, auxLeaf, opts..., + cltvExpiry, hType, auxLeaf, ) } @@ -1481,23 +1547,43 @@ func ReceiverHTLCScriptTaprootRevoke(signer Signer, signDesc *SignDescriptor, func SecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey, csvDelay uint32) ([]byte, error) { + builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( + ToLocalScriptSize, + )) + + // If this is the revocation clause for this script is to be executed, + // the spender will push a 1, forcing us to hit the true clause of this + // if statement. + builder.AddOp(txscript.OP_IF) + + // If this is the revocation case, then we'll push the revocation + // public key on the stack. + builder.AddData(revocationKey.SerializeCompressed()) + + // Otherwise, this is either the sender or receiver of the HTLC + // attempting to claim the HTLC output. + builder.AddOp(txscript.OP_ELSE) + + // In order to give the other party time to execute the revocation + // clause above, we require a relative timeout to pass before the + // output can be spent. + builder.AddInt64(int64(csvDelay)) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_DROP) + + // If the relative timelock passes, then we'll add the delay key to the + // stack to ensure that we properly authenticate the spending party. + builder.AddData(delayKey.SerializeCompressed()) + + // Close out the if statement. + builder.AddOp(txscript.OP_ENDIF) + // In either case, we'll ensure that only either the party possessing // the revocation private key, or the delay private key is able to // spend this output. - return txscript.ScriptTemplate( - ` - OP_IF - {{ hex .RevokeKey }} - OP_ELSE - {{ .CsvDelay }} OP_CHECKSEQUENCEVERIFY OP_DROP - {{ hex .DelayKey }} - OP_ENDIF OP_CHECKSIG`, - txscript.WithScriptTemplateParams(TemplateParams{ - "RevokeKey": revocationKey.SerializeCompressed(), - "CsvDelay": int64(csvDelay), - "DelayKey": delayKey.SerializeCompressed(), - }), - ) + builder.AddOp(txscript.OP_CHECKSIG) + + return builder.Script() } // TODO(roasbeef): move all taproot stuff to new file? @@ -1510,36 +1596,22 @@ func SecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey, // OP_CHECKSIG // OP_CHECKSEQUENCEVERIFY OP_DROP func TaprootSecondLevelTapLeaf(delayKey *btcec.PublicKey, - csvDelay uint32, opts ...TaprootScriptOpt) (txscript.TapLeaf, error) { + csvDelay uint32) (txscript.TapLeaf, error) { - opt := defaultTaprootScriptOpt() - for _, o := range opts { - o(opt) - } + builder := txscript.NewScriptBuilder() // Ensure the proper party can sign for this output. + builder.AddData(schnorr.SerializePubKey(delayKey)) + builder.AddOp(txscript.OP_CHECKSIG) + // Assuming the above passes, then we'll now ensure that the CSV delay // has been upheld, dropping the int we pushed on. If the sig above is // valid, then a 1 will be left on the stack. - var scriptTemplate string - switch { - case opt.prodScript: - scriptTemplate = ` - {{ hex .DelayKey }} OP_CHECKSIGVERIFY - {{ .CsvDelay }} OP_CHECKSEQUENCEVERIFY` - default: - scriptTemplate = ` - {{ hex .DelayKey }} OP_CHECKSIG - {{ .CsvDelay }} OP_CHECKSEQUENCEVERIFY OP_DROP` - } + builder.AddInt64(int64(csvDelay)) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_DROP) - secondLevelLeafScript, err := txscript.ScriptTemplate( - scriptTemplate, - txscript.WithScriptTemplateParams(TemplateParams{ - "DelayKey": schnorr.SerializePubKey(delayKey), - "CsvDelay": int64(csvDelay), - }), - ) + secondLevelLeafScript, err := builder.Script() if err != nil { return txscript.TapLeaf{}, err } @@ -1549,15 +1621,12 @@ func TaprootSecondLevelTapLeaf(delayKey *btcec.PublicKey, // SecondLevelHtlcTapscriptTree construct the indexed tapscript tree needed to // generate the tap tweak to create the final output and also control block. -func SecondLevelHtlcTapscriptTree(delayKey *btcec.PublicKey, - csvDelay uint32, auxLeaf AuxTapLeaf, - opts ...TaprootScriptOpt) (*txscript.IndexedTapScriptTree, error) { +func SecondLevelHtlcTapscriptTree(delayKey *btcec.PublicKey, csvDelay uint32, + auxLeaf AuxTapLeaf) (*txscript.IndexedTapScriptTree, error) { // First grab the second level leaf script we need to create the top // level output. - secondLevelTapLeaf, err := TaprootSecondLevelTapLeaf( - delayKey, csvDelay, opts..., - ) + secondLevelTapLeaf, err := TaprootSecondLevelTapLeaf(delayKey, csvDelay) if err != nil { return nil, err } @@ -1589,13 +1658,12 @@ func SecondLevelHtlcTapscriptTree(delayKey *btcec.PublicKey, // // The keyspend path require knowledge of the top level revocation private key. func TaprootSecondLevelHtlcScript(revokeKey, delayKey *btcec.PublicKey, - csvDelay uint32, auxLeaf AuxTapLeaf, - opts ...TaprootScriptOpt) (*btcec.PublicKey, error) { + csvDelay uint32, auxLeaf AuxTapLeaf) (*btcec.PublicKey, error) { // First, we'll make the tapscript tree that commits to the redemption // path. tapScriptTree, err := SecondLevelHtlcTapscriptTree( - delayKey, csvDelay, auxLeaf, opts..., + delayKey, csvDelay, auxLeaf, ) if err != nil { return nil, err @@ -1629,13 +1697,12 @@ type SecondLevelScriptTree struct { // TaprootSecondLevelScriptTree constructs the tapscript tree used to spend the // second level HTLC output. func TaprootSecondLevelScriptTree(revokeKey, delayKey *btcec.PublicKey, - csvDelay uint32, auxLeaf AuxTapLeaf, - opts ...TaprootScriptOpt) (*SecondLevelScriptTree, error) { + csvDelay uint32, auxLeaf AuxTapLeaf) (*SecondLevelScriptTree, error) { // First, we'll make the tapscript tree that commits to the redemption // path. tapScriptTree, err := SecondLevelHtlcTapscriptTree( - delayKey, csvDelay, auxLeaf, opts..., + delayKey, csvDelay, auxLeaf, ) if err != nil { return nil, err @@ -1813,26 +1880,50 @@ func TaprootHtlcSpendSuccess(signer Signer, signDesc *SignDescriptor, func LeaseSecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey, csvDelay, cltvExpiry uint32) ([]byte, error) { - // Build a script template with conditional paths for revocation and - // normal spending If this is the revocation clause, the spender will - // push a 1, forcing the first path Otherwise, this is either the sender - // or receiver of the HTLC attempting to claim - return txscript.ScriptTemplate( - ` - OP_IF - {{ hex .RevokeKey }} - OP_ELSE - {{ .CltvExpiry }} OP_CHECKLOCKTIMEVERIFY OP_DROP - {{ .CsvDelay }} OP_CHECKSEQUENCEVERIFY OP_DROP - {{ hex .DelayKey }} - OP_ENDIF OP_CHECKSIG`, - txscript.WithScriptTemplateParams(TemplateParams{ - "RevokeKey": revocationKey.SerializeCompressed(), - "CltvExpiry": int64(cltvExpiry), - "CsvDelay": int64(csvDelay), - "DelayKey": delayKey.SerializeCompressed(), - }), - ) + builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( + ToLocalScriptSize + LeaseWitnessScriptSizeOverhead, + )) + + // If this is the revocation clause for this script is to be executed, + // the spender will push a 1, forcing us to hit the true clause of this + // if statement. + builder.AddOp(txscript.OP_IF) + + // If this this is the revocation case, then we'll push the revocation + // public key on the stack. + builder.AddData(revocationKey.SerializeCompressed()) + + // Otherwise, this is either the sender or receiver of the HTLC + // attempting to claim the HTLC output. + builder.AddOp(txscript.OP_ELSE) + + // The channel initiator always has the additional channel lease + // expiration constraint for outputs that pay to them which must be + // satisfied. + builder.AddInt64(int64(cltvExpiry)) + builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) + builder.AddOp(txscript.OP_DROP) + + // In order to give the other party time to execute the revocation + // clause above, we require a relative timeout to pass before the + // output can be spent. + builder.AddInt64(int64(csvDelay)) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_DROP) + + // If the relative timelock passes, then we'll add the delay key to the + // stack to ensure that we properly authenticate the spending party. + builder.AddData(delayKey.SerializeCompressed()) + + // Close out the if statement. + builder.AddOp(txscript.OP_ENDIF) + + // In either case, we'll ensure that only either the party possessing + // the revocation private key, or the delay private key is able to + // spend this output. + builder.AddOp(txscript.OP_CHECKSIG) + + return builder.Script() } // HtlcSpendSuccess spends a second-level HTLC output. This function is to be @@ -1949,9 +2040,8 @@ func LockTimeToSequence(isSeconds bool, locktime uint32) uint32 { // CommitScriptToSelf constructs the public key script for the output on the // commitment transaction paying to the "owner" of said commitment transaction. -// If the other party obtains the revocation private key for this commitment, -// then they can claim all the settled funds in the channel, plus the unsettled -// funds. +// If the other party learns of the preimage to the revocation hash, then they +// can claim all the settled funds in the channel, plus the unsettled funds. // // Possible Input Scripts: // @@ -1975,21 +2065,32 @@ func CommitScriptToSelf(csvTimeout uint32, selfKey, revokeKey *btcec.PublicKey) // have divulged the revocation hash, allowing them to homomorphically // derive the proper private key which corresponds to the revoke public // key. - return txscript.ScriptTemplate( - ` - OP_IF - {{ hex .RevokeKey }} - OP_ELSE - {{ .CsvTimeout }} OP_CHECKSEQUENCEVERIFY OP_DROP - {{ hex .SelfKey }} - OP_ENDIF - OP_CHECKSIG`, - txscript.WithScriptTemplateParams(TemplateParams{ - "RevokeKey": revokeKey.SerializeCompressed(), - "CsvTimeout": int64(csvTimeout), - "SelfKey": selfKey.SerializeCompressed(), - }), - ) + builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( + ToLocalScriptSize, + )) + + builder.AddOp(txscript.OP_IF) + + // If a valid signature using the revocation key is presented, then + // allow an immediate spend provided the proper signature. + builder.AddData(revokeKey.SerializeCompressed()) + + builder.AddOp(txscript.OP_ELSE) + + // Otherwise, we can re-claim our funds after a CSV delay of + // 'csvTimeout' timeout blocks, and a valid signature. + builder.AddInt64(int64(csvTimeout)) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_DROP) + builder.AddData(selfKey.SerializeCompressed()) + + builder.AddOp(txscript.OP_ENDIF) + + // Finally, we'll validate the signature against the public key that's + // left on the top of the stack. + builder.AddOp(txscript.OP_CHECKSIG) + + return builder.Script() } // CommitScriptTree holds the taproot output key (in this case the revocation @@ -2072,52 +2173,22 @@ func (c *CommitScriptTree) Tree() ScriptTree { return c.ScriptTree } -// taprootScriptOpts is a set of options that modify the behavior of the way we -// create taproot scripts. -type taprootScriptOpts struct { - prodScript bool -} - -// TaprootScriptOpt is a functional option that allows us to modify the behavior -// of the taproot script creation. -type TaprootScriptOpt func(*taprootScriptOpts) - -// defaultTaprootScriptOpt is the default set of options that we use when -// creating taproot scripts. -func defaultTaprootScriptOpt() *taprootScriptOpts { - return &taprootScriptOpts{ - prodScript: false, - } -} - -// WithProdScripts is a functional option that allows us to create scripts to -// match the final version of the taproot channels. -func WithProdScripts() func(*taprootScriptOpts) { - return func(o *taprootScriptOpts) { - o.prodScript = true - } -} - // NewLocalCommitScriptTree returns a new CommitScript tree that can be used to // create and spend the commitment output for the local party. func NewLocalCommitScriptTree(csvTimeout uint32, selfKey, - revokeKey *btcec.PublicKey, auxLeaf AuxTapLeaf, - opts ...TaprootScriptOpt) (*CommitScriptTree, error) { + revokeKey *btcec.PublicKey, auxLeaf AuxTapLeaf) (*CommitScriptTree, + error) { // First, we'll need to construct the tapLeaf that'll be our delay CSV // clause. - delayScript, err := TaprootLocalCommitDelayScript( - csvTimeout, selfKey, opts..., - ) + delayScript, err := TaprootLocalCommitDelayScript(csvTimeout, selfKey) if err != nil { return nil, err } // Next, we'll need to construct the revocation path, which is just a // simple checksig script. - revokeScript, err := TaprootLocalCommitRevokeScript( - selfKey, revokeKey, opts..., - ) + revokeScript, err := TaprootLocalCommitRevokeScript(selfKey, revokeKey) if err != nil { return nil, err } @@ -2157,48 +2228,30 @@ func NewLocalCommitScriptTree(csvTimeout uint32, selfKey, // TaprootLocalCommitDelayScript builds the tap leaf with the CSV delay script // for the to-local output. func TaprootLocalCommitDelayScript(csvTimeout uint32, - selfKey *btcec.PublicKey, opts ...TaprootScriptOpt) ([]byte, error) { + selfKey *btcec.PublicKey) ([]byte, error) { - opt := defaultTaprootScriptOpt() - for _, o := range opts { - o(opt) - } + builder := txscript.NewScriptBuilder() + builder.AddData(schnorr.SerializePubKey(selfKey)) + builder.AddOp(txscript.OP_CHECKSIG) + builder.AddInt64(int64(csvTimeout)) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_DROP) - var scriptTemplate string - switch { - case opt.prodScript: - scriptTemplate = ` - {{ hex .SelfKey }} OP_CHECKSIGVERIFY - {{ .CsvTimeout }} OP_CHECKSEQUENCEVERIFY` - default: - scriptTemplate = ` - {{ hex .SelfKey }} OP_CHECKSIG - {{ .CsvTimeout }} OP_CHECKSEQUENCEVERIFY OP_DROP` - } - - return txscript.ScriptTemplate( - scriptTemplate, - txscript.WithScriptTemplateParams(TemplateParams{ - "SelfKey": schnorr.SerializePubKey(selfKey), - "CsvTimeout": int64(csvTimeout), - }), - ) + return builder.Script() } // TaprootLocalCommitRevokeScript builds the tap leaf with the revocation path // for the to-local output. -func TaprootLocalCommitRevokeScript(selfKey, revokeKey *btcec.PublicKey, - _ ...TaprootScriptOpt) ([]byte, error) { +func TaprootLocalCommitRevokeScript(selfKey, revokeKey *btcec.PublicKey) ( + []byte, error) { - return txscript.ScriptTemplate( - ` - {{ hex .SelfKey }} OP_DROP - {{ hex .RevokeKey }} OP_CHECKSIG`, - txscript.WithScriptTemplateParams(TemplateParams{ - "SelfKey": schnorr.SerializePubKey(selfKey), - "RevokeKey": schnorr.SerializePubKey(revokeKey), - }), - ) + builder := txscript.NewScriptBuilder() + builder.AddData(schnorr.SerializePubKey(selfKey)) + builder.AddOp(txscript.OP_DROP) + builder.AddData(schnorr.SerializePubKey(revokeKey)) + builder.AddOp(txscript.OP_CHECKSIG) + + return builder.Script() } // TaprootCommitScriptToSelf creates the taproot witness program that commits @@ -2349,9 +2402,8 @@ func TaprootCommitSpendRevoke(signer Signer, signDesc *SignDescriptor, // LeaseCommitScriptToSelf constructs the public key script for the output on the // commitment transaction paying to the "owner" of said commitment transaction. -// If the other party obtains the revocation private key for this commitment, -// then they can claim all the settled funds in the channel, plus the unsettled -// funds. +// If the other party learns of the preimage to the revocation hash, then they +// can claim all the settled funds in the channel, plus the unsettled funds. // // Possible Input Scripts: // @@ -2378,23 +2430,38 @@ func LeaseCommitScriptToSelf(selfKey, revokeKey *btcec.PublicKey, // have divulged the revocation hash, allowing them to homomorphically // derive the proper private key which corresponds to the revoke public // key. - return txscript.ScriptTemplate( - ` - OP_IF - {{ hex .RevokeKey }} - OP_ELSE - {{ .LeaseExpiry }} OP_CHECKLOCKTIMEVERIFY OP_DROP - {{ .CsvTimeout }} OP_CHECKSEQUENCEVERIFY OP_DROP - {{ hex .SelfKey }} - OP_ENDIF - OP_CHECKSIG`, - txscript.WithScriptTemplateParams(TemplateParams{ - "RevokeKey": revokeKey.SerializeCompressed(), - "LeaseExpiry": int64(leaseExpiry), - "CsvTimeout": int64(csvTimeout), - "SelfKey": selfKey.SerializeCompressed(), - }), - ) + builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( + ToLocalScriptSize + LeaseWitnessScriptSizeOverhead, + )) + + builder.AddOp(txscript.OP_IF) + + // If a valid signature using the revocation key is presented, then + // allow an immediate spend provided the proper signature. + builder.AddData(revokeKey.SerializeCompressed()) + + builder.AddOp(txscript.OP_ELSE) + + // Otherwise, we can re-claim our funds after once the CLTV lease + // maturity has been met, along with the CSV delay of 'csvTimeout' + // timeout blocks, and a valid signature. + builder.AddInt64(int64(leaseExpiry)) + builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) + builder.AddOp(txscript.OP_DROP) + + builder.AddInt64(int64(csvTimeout)) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_DROP) + + builder.AddData(selfKey.SerializeCompressed()) + + builder.AddOp(txscript.OP_ENDIF) + + // Finally, we'll validate the signature against the public key that's + // left on the top of the stack. + builder.AddOp(txscript.OP_CHECKSIG) + + return builder.Script() } // CommitSpendTimeout constructs a valid witness allowing the owner of a @@ -2510,12 +2577,13 @@ func CommitSpendNoDelay(signer Signer, signDesc *SignDescriptor, // p2wkh output spendable immediately, requiring no contestation period. func CommitScriptUnencumbered(key *btcec.PublicKey) ([]byte, error) { // This script goes to the "other" party, and is spendable immediately. - return txscript.ScriptTemplate( - `OP_0 {{ hex .PKHash }}`, - txscript.WithScriptTemplateParams(TemplateParams{ - "PKHash": address.Hash160(key.SerializeCompressed()), - }), - ) + builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( + P2WPKHSize, + )) + builder.AddOp(txscript.OP_0) + builder.AddData(btcutil.Hash160(key.SerializeCompressed())) + + return builder.Script() } // CommitScriptToRemoteConfirmed constructs the script for the output on the @@ -2531,48 +2599,36 @@ func CommitScriptUnencumbered(key *btcec.PublicKey) ([]byte, error) { // OP_CHECKSIGVERIFY // 1 OP_CHECKSEQUENCEVERIFY func CommitScriptToRemoteConfirmed(key *btcec.PublicKey) ([]byte, error) { - // Only the given key can spend the output after one confirmation. - return txscript.ScriptTemplate( - ` - {{ hex .Key }} OP_CHECKSIGVERIFY - OP_1 OP_CHECKSEQUENCEVERIFY`, - txscript.WithScriptTemplateParams(TemplateParams{ - "Key": key.SerializeCompressed(), - }), - ) + builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( + ToRemoteConfirmedScriptSize, + )) + + // Only the given key can spend the output. + builder.AddData(key.SerializeCompressed()) + builder.AddOp(txscript.OP_CHECKSIGVERIFY) + + // Check that the it has one confirmation. + builder.AddOp(txscript.OP_1) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + + return builder.Script() } // NewRemoteCommitScriptTree constructs a new script tree for the remote party // to sweep their funds after a hard coded 1 block delay. func NewRemoteCommitScriptTree(remoteKey *btcec.PublicKey, - auxLeaf AuxTapLeaf, - opts ...TaprootScriptOpt) (*CommitScriptTree, error) { - - opt := defaultTaprootScriptOpt() - for _, o := range opts { - o(opt) - } + auxLeaf AuxTapLeaf) (*CommitScriptTree, error) { // First, construct the remote party's tapscript they'll use to sweep // their outputs. - var scriptTemplate string - switch { - case opt.prodScript: - scriptTemplate = ` - {{ hex .RemoteKey }} OP_CHECKSIGVERIFY - OP_1 OP_CHECKSEQUENCEVERIFY` - default: - scriptTemplate = ` - {{ hex .RemoteKey }} OP_CHECKSIG - OP_1 OP_CHECKSEQUENCEVERIFY OP_DROP` - } + builder := txscript.NewScriptBuilder() + builder.AddData(schnorr.SerializePubKey(remoteKey)) + builder.AddOp(txscript.OP_CHECKSIG) + builder.AddOp(txscript.OP_1) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_DROP) - remoteScript, err := txscript.ScriptTemplate( - scriptTemplate, - txscript.WithScriptTemplateParams(TemplateParams{ - "RemoteKey": schnorr.SerializePubKey(remoteKey), - }), - ) + remoteScript, err := builder.Script() if err != nil { return nil, err } @@ -2687,18 +2743,24 @@ func TaprootCommitRemoteSpend(signer Signer, signDesc *SignDescriptor, func LeaseCommitScriptToRemoteConfirmed(key *btcec.PublicKey, leaseExpiry uint32) ([]byte, error) { - // This script adds lease expiration constraint in addition to the - // standard remote confirmed script requirements. - return txscript.ScriptTemplate( - ` - {{ hex .Key }} OP_CHECKSIGVERIFY - {{ .LeaseExpiry }} OP_CHECKLOCKTIMEVERIFY OP_DROP - OP_1 OP_CHECKSEQUENCEVERIFY`, - txscript.WithScriptTemplateParams(TemplateParams{ - "Key": key.SerializeCompressed(), - "LeaseExpiry": int64(leaseExpiry), - }), - ) + builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(45)) + + // Only the given key can spend the output. + builder.AddData(key.SerializeCompressed()) + builder.AddOp(txscript.OP_CHECKSIGVERIFY) + + // The channel initiator always has the additional channel lease + // expiration constraint for outputs that pay to them which must be + // satisfied. + builder.AddInt64(int64(leaseExpiry)) + builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) + builder.AddOp(txscript.OP_DROP) + + // Check that it has one confirmation. + builder.AddOp(txscript.OP_1) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + + return builder.Script() } // CommitSpendToRemoteConfirmed constructs a valid witness allowing a node to @@ -2743,20 +2805,24 @@ func CommitSpendToRemoteConfirmed(signer Signer, signDesc *SignDescriptor, // OP_16 OP_CSV // OP_ENDIF func CommitScriptAnchor(key *btcec.PublicKey) ([]byte, error) { - // Build the anchor script with two possible spending paths: - // 1. Spend immediately with key (the normal path) - // 2. Spend after 16 confirmations by anyone (the alternative path) - return txscript.ScriptTemplate( - ` - {{ hex .Key }} OP_CHECKSIG - OP_IFDUP - OP_NOTIF - OP_16 OP_CHECKSEQUENCEVERIFY - OP_ENDIF`, - txscript.WithScriptTemplateParams(TemplateParams{ - "Key": key.SerializeCompressed(), - }), - ) + builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( + AnchorScriptSize, + )) + + // Spend immediately with key. + builder.AddData(key.SerializeCompressed()) + builder.AddOp(txscript.OP_CHECKSIG) + + // Duplicate the value if true, since it will be consumed by the NOTIF. + builder.AddOp(txscript.OP_IFDUP) + + // Otherwise spendable by anyone after 16 confirmations. + builder.AddOp(txscript.OP_NOTIF) + builder.AddOp(txscript.OP_16) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_ENDIF) + + return builder.Script() } // AnchorScriptTree holds all the contents needed to sweep a taproot anchor @@ -2775,9 +2841,11 @@ func NewAnchorScriptTree( // The main script used is just a OP_16 CSV (anyone can sweep after 16 // blocks). - anchorScript, err := txscript.ScriptTemplate( - `OP_16 OP_CHECKSEQUENCEVERIFY`, - ) + builder := txscript.NewScriptBuilder() + builder.AddOp(txscript.OP_16) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + + anchorScript, err := builder.Script() if err != nil { return nil, err } diff --git a/input/script_utils_legacy_test.go b/input/script_utils_legacy_test.go deleted file mode 100644 index 32d6a0b8d..000000000 --- a/input/script_utils_legacy_test.go +++ /dev/null @@ -1,738 +0,0 @@ -package input - -import ( - "bytes" - "crypto/sha256" - "fmt" - - "github.com/btcsuite/btcd/address/v2" - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/txscript/v2" -) - -// legacyWitnessScriptHash generates a pay-to-witness-script-hash public key -// script paying to a version 0 witness program paying to the passed redeem -// script. -func legacyWitnessScriptHash(witnessScript []byte) ([]byte, error) { - bldr := txscript.NewScriptBuilder( - txscript.WithScriptAllocSize(P2WSHSize), - ) - - bldr.AddOp(txscript.OP_0) - scriptHash := sha256.Sum256(witnessScript) - bldr.AddData(scriptHash[:]) - - return bldr.Script() -} - -// legacyWitnessPubKeyHash generates a pay-to-witness-pubkey-hash public key -// script paying to a version 0 witness program containing the passed -// serialized public key. -func legacyWitnessPubKeyHash(pubkey []byte) ([]byte, error) { - bldr := txscript.NewScriptBuilder( - txscript.WithScriptAllocSize(P2WPKHSize), - ) - - bldr.AddOp(txscript.OP_0) - pkhash := address.Hash160(pubkey) - bldr.AddData(pkhash) - - return bldr.Script() -} - -// legacyGenerateP2SH generates a pay-to-script-hash public key script paying -// to the passed redeem script. -func legacyGenerateP2SH(script []byte) ([]byte, error) { - bldr := txscript.NewScriptBuilder( - txscript.WithScriptAllocSize(NestedP2WPKHSize), - ) - - bldr.AddOp(txscript.OP_HASH160) - scripthash := address.Hash160(script) - bldr.AddData(scripthash) - bldr.AddOp(txscript.OP_EQUAL) - - return bldr.Script() -} - -// legacyGenerateP2PKH generates a pay-to-public-key-hash public key script -// paying to the passed serialized public key. -func legacyGenerateP2PKH(pubkey []byte) ([]byte, error) { - bldr := txscript.NewScriptBuilder( - txscript.WithScriptAllocSize(P2PKHSize), - ) - - bldr.AddOp(txscript.OP_DUP) - bldr.AddOp(txscript.OP_HASH160) - pkhash := address.Hash160(pubkey) - bldr.AddData(pkhash) - bldr.AddOp(txscript.OP_EQUALVERIFY) - bldr.AddOp(txscript.OP_CHECKSIG) - - return bldr.Script() -} - -// legacyGenMultiSigScript generates the non-p2sh'd multisig script for 2 of 2 -// pubkeys. -func legacyGenMultiSigScript(aPub, bPub []byte) ([]byte, error) { - if len(aPub) != 33 || len(bPub) != 33 { - return nil, fmt.Errorf("pubkey size error: compressed " + - "pubkeys only") - } - - // Swap to sort pubkeys if needed. Keys are sorted in lexicographical - // order. The signatures within the scriptSig must also adhere to the - // order, ensuring that the signatures for each public key appears in - // the proper order on the stack. - if bytes.Compare(aPub, bPub) == 1 { - aPub, bPub = bPub, aPub - } - - bldr := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( - MultiSigSize, - )) - bldr.AddOp(txscript.OP_2) - bldr.AddData(aPub) // Add both pubkeys (sorted). - bldr.AddData(bPub) - bldr.AddOp(txscript.OP_2) - bldr.AddOp(txscript.OP_CHECKMULTISIG) - - return bldr.Script() -} - -// legacySenderHTLCScript constructs the public key script for an outgoing HTLC -// output payment for the sender's version of the commitment transaction. -func legacySenderHTLCScript(senderHtlcKey, receiverHtlcKey, - revocationKey *btcec.PublicKey, paymentHash []byte, - confirmedSpend bool) ([]byte, error) { - - builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( - OfferedHtlcScriptSizeConfirmed, - )) - - // The opening operations are used to determine if this is the receiver - // of the HTLC attempting to sweep all the funds due to a contract - // breach. In this case, they'll place the revocation key at the top of - // the stack. - builder.AddOp(txscript.OP_DUP) - builder.AddOp(txscript.OP_HASH160) - builder.AddData(address.Hash160(revocationKey.SerializeCompressed())) - builder.AddOp(txscript.OP_EQUAL) - - // If the hash matches, then this is the revocation clause. The output - // can be spent if the check sig operation passes. - builder.AddOp(txscript.OP_IF) - builder.AddOp(txscript.OP_CHECKSIG) - - // Otherwise, this may either be the receiver of the HTLC claiming with - // the pre-image, or the sender of the HTLC sweeping the output after - // it has timed out. - builder.AddOp(txscript.OP_ELSE) - - // We'll do a bit of set up by pushing the receiver's key on the top of - // the stack. This will be needed later if we decide that this is the - // sender activating the time out clause with the HTLC timeout - // transaction. - builder.AddData(receiverHtlcKey.SerializeCompressed()) - - // Atm, the top item of the stack is the receiverKey's so we use a swap - // to expose what is either the payment pre-image or a signature. - builder.AddOp(txscript.OP_SWAP) - - // With the top item swapped, check if it's 32 bytes. If so, then this - // *may* be the payment pre-image. - builder.AddOp(txscript.OP_SIZE) - builder.AddInt64(32) - builder.AddOp(txscript.OP_EQUAL) - - // If it isn't then this might be the sender of the HTLC activating the - // time out clause. - builder.AddOp(txscript.OP_NOTIF) - - // We'll drop the OP_IF return value off the top of the stack so we can - // reconstruct the multi-sig script used as an off-chain covenant. If - // two valid signatures are provided, then the output will be deemed as - // spendable. - builder.AddOp(txscript.OP_DROP) - builder.AddOp(txscript.OP_2) - builder.AddOp(txscript.OP_SWAP) - builder.AddData(senderHtlcKey.SerializeCompressed()) - builder.AddOp(txscript.OP_2) - builder.AddOp(txscript.OP_CHECKMULTISIG) - - // Otherwise, then the only other case is that this is the receiver of - // the HTLC sweeping it on-chain with the payment pre-image. - builder.AddOp(txscript.OP_ELSE) - - // Hash the top item of the stack and compare it with the hash160 of - // the payment hash, which is already the sha256 of the payment - // pre-image. By using this little trick we're able to save space - // on-chain as the witness includes a 20-byte hash rather than a - // 32-byte hash. - builder.AddOp(txscript.OP_HASH160) - builder.AddData(Ripemd160H(paymentHash)) - builder.AddOp(txscript.OP_EQUALVERIFY) - - // This checks the receiver's signature so that a third party with - // knowledge of the payment preimage still cannot steal the output. - builder.AddOp(txscript.OP_CHECKSIG) - - // Close out the OP_IF statement above. - builder.AddOp(txscript.OP_ENDIF) - - // Add 1 block CSV delay if a confirmation is required for the - // non-revocation clauses. - if confirmedSpend { - builder.AddOp(txscript.OP_1) - builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) - builder.AddOp(txscript.OP_DROP) - } - - // Close out the OP_IF statement at the top of the script. - builder.AddOp(txscript.OP_ENDIF) - - return builder.Script() -} - -// legacyReceiverHTLCScript constructs the public key script for an incoming -// HTLC output payment for the receiver's version of the commitment -// transaction. -func legacyReceiverHTLCScript(cltvExpiry uint32, senderHtlcKey, - receiverHtlcKey, revocationKey *btcec.PublicKey, - paymentHash []byte, confirmedSpend bool) ([]byte, error) { - - builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( - AcceptedHtlcScriptSizeConfirmed, - )) - - // The opening operations are used to determine if this is the sender - // of the HTLC attempting to sweep all the funds due to a contract - // breach. In this case, they'll place the revocation key at the top of - // the stack. - builder.AddOp(txscript.OP_DUP) - builder.AddOp(txscript.OP_HASH160) - builder.AddData(address.Hash160(revocationKey.SerializeCompressed())) - builder.AddOp(txscript.OP_EQUAL) - - // If the hash matches, then this is the revocation clause. The output - // can be spent if the check sig operation passes. - builder.AddOp(txscript.OP_IF) - builder.AddOp(txscript.OP_CHECKSIG) - - // Otherwise, this may either be the receiver of the HTLC starting the - // claiming process via the second level HTLC success transaction and - // the pre-image, or the sender of the HTLC sweeping the output after - // it has timed out. - builder.AddOp(txscript.OP_ELSE) - - // We'll do a bit of set up by pushing the sender's key on the top of - // the stack. This will be needed later if we decide that this is the - // receiver transitioning the output to the claim state using their - // second-level HTLC success transaction. - builder.AddData(senderHtlcKey.SerializeCompressed()) - - // Atm, the top item of the stack is the sender's key so we use a swap - // to expose what is either the payment pre-image or something else. - builder.AddOp(txscript.OP_SWAP) - - // With the top item swapped, check if it's 32 bytes. If so, then this - // *may* be the payment pre-image. - builder.AddOp(txscript.OP_SIZE) - builder.AddInt64(32) - builder.AddOp(txscript.OP_EQUAL) - - // If the item on the top of the stack is 32-bytes, then it is the - // proper size, so this indicates that the receiver of the HTLC is - // attempting to claim the output on-chain by transitioning the state - // of the HTLC to delay+claim. - builder.AddOp(txscript.OP_IF) - - // Next we'll hash the item on the top of the stack, if it matches the - // payment pre-image, then we'll continue. Otherwise, we'll end the - // script here as this is the invalid payment pre-image. - builder.AddOp(txscript.OP_HASH160) - builder.AddData(Ripemd160H(paymentHash)) - builder.AddOp(txscript.OP_EQUALVERIFY) - - // If the payment hash matches, then we'll also need to satisfy the - // multi-sig covenant by providing both signatures of the sender and - // receiver. If the convenient is met, then we'll allow the spending of - // this output, but only by the HTLC success transaction. - builder.AddOp(txscript.OP_2) - builder.AddOp(txscript.OP_SWAP) - builder.AddData(receiverHtlcKey.SerializeCompressed()) - builder.AddOp(txscript.OP_2) - builder.AddOp(txscript.OP_CHECKMULTISIG) - - // Otherwise, this might be the sender of the HTLC attempting to sweep - // it on-chain after the timeout. - builder.AddOp(txscript.OP_ELSE) - - // We'll drop the extra item (which is the output from evaluating the - // OP_EQUAL) above from the stack. - builder.AddOp(txscript.OP_DROP) - - // With that item dropped off, we can now enforce the absolute - // lock-time required to timeout the HTLC. If the time has passed, then - // we'll proceed with a checksig to ensure that this is actually the - // sender of he original HTLC. - builder.AddInt64(int64(cltvExpiry)) - builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) - builder.AddOp(txscript.OP_DROP) - builder.AddOp(txscript.OP_CHECKSIG) - - // Close out the inner if statement. - builder.AddOp(txscript.OP_ENDIF) - - // Add 1 block CSV delay for non-revocation clauses if confirmation is - // required. - if confirmedSpend { - builder.AddOp(txscript.OP_1) - builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) - builder.AddOp(txscript.OP_DROP) - } - - // Close out the outer if statement. - builder.AddOp(txscript.OP_ENDIF) - - return builder.Script() -} - -// legacySecondLevelHtlcScript is the uniform script that's used as the output -// for the second-level HTLC transactions. -func legacySecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey, - csvDelay uint32) ([]byte, error) { - - builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( - ToLocalScriptSize, - )) - - // If this is the revocation clause for this script is to be executed, - // the spender will push a 1, forcing us to hit the true clause of this - // if statement. - builder.AddOp(txscript.OP_IF) - - // If this is the revocation case, then we'll push the revocation - // public key on the stack. - builder.AddData(revocationKey.SerializeCompressed()) - - // Otherwise, this is either the sender or receiver of the HTLC - // attempting to claim the HTLC output. - builder.AddOp(txscript.OP_ELSE) - - // In order to give the other party time to execute the revocation - // clause above, we require a relative timeout to pass before the - // output can be spent. - builder.AddInt64(int64(csvDelay)) - builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) - builder.AddOp(txscript.OP_DROP) - - // If the relative timelock passes, then we'll add the delay key to the - // stack to ensure that we properly authenticate the spending party. - builder.AddData(delayKey.SerializeCompressed()) - - // Close out the if statement. - builder.AddOp(txscript.OP_ENDIF) - - // In either case, we'll ensure that only either the party possessing - // the revocation private key, or the delay private key is able to - // spend this output. - builder.AddOp(txscript.OP_CHECKSIG) - - return builder.Script() -} - -// legacyCommitScriptToSelf constructs the public key script for the output on -// the commitment transaction paying to the "owner" of said commitment -// transaction. -func legacyCommitScriptToSelf(csvTimeout uint32, selfKey, - revokeKey *btcec.PublicKey) ([]byte, error) { - // This script is spendable under two conditions: either the - // 'csvTimeout' has passed and we can redeem our funds, or they can - // produce a valid signature with the revocation public key. The - // revocation public key will *only* be known to the other party if we - // have divulged the revocation hash, allowing them to homomorphically - // derive the proper private key which corresponds to the revoke public - // key. - builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( - ToLocalScriptSize, - )) - - builder.AddOp(txscript.OP_IF) - - // If a valid signature using the revocation key is presented, then - // allow an immediate spend provided the proper signature. - builder.AddData(revokeKey.SerializeCompressed()) - - builder.AddOp(txscript.OP_ELSE) - - // Otherwise, we can re-claim our funds after a CSV delay of - // 'csvTimeout' timeout blocks, and a valid signature. - builder.AddInt64(int64(csvTimeout)) - builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) - builder.AddOp(txscript.OP_DROP) - builder.AddData(selfKey.SerializeCompressed()) - - builder.AddOp(txscript.OP_ENDIF) - - // Finally, we'll validate the signature against the public key that's - // left on the top of the stack. - builder.AddOp(txscript.OP_CHECKSIG) - - return builder.Script() -} - -// legacyLeaseCommitScriptToSelf constructs the public key script for the -// output on the commitment transaction paying to the "owner" of said -// commitment transaction, with an additional lease expiry constraint. -func legacyLeaseCommitScriptToSelf(selfKey, revokeKey *btcec.PublicKey, - csvTimeout, leaseExpiry uint32) ([]byte, error) { - - // This script is spendable under two conditions: either the - // 'csvTimeout' has passed and we can redeem our funds, or they can - // produce a valid signature with the revocation public key. The - // revocation public key will *only* be known to the other party if we - // have divulged the revocation hash, allowing them to homomorphically - // derive the proper private key which corresponds to the revoke public - // key. - builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( - ToLocalScriptSize + LeaseWitnessScriptSizeOverhead, - )) - - builder.AddOp(txscript.OP_IF) - - // If a valid signature using the revocation key is presented, then - // allow an immediate spend provided the proper signature. - builder.AddData(revokeKey.SerializeCompressed()) - - builder.AddOp(txscript.OP_ELSE) - - // Otherwise, we can re-claim our funds after once the CLTV lease - // maturity has been met, along with the CSV delay of 'csvTimeout' - // timeout blocks, and a valid signature. - builder.AddInt64(int64(leaseExpiry)) - builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) - builder.AddOp(txscript.OP_DROP) - - builder.AddInt64(int64(csvTimeout)) - builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) - builder.AddOp(txscript.OP_DROP) - - builder.AddData(selfKey.SerializeCompressed()) - - builder.AddOp(txscript.OP_ENDIF) - - // Finally, we'll validate the signature against the public key that's - // left on the top of the stack. - builder.AddOp(txscript.OP_CHECKSIG) - - return builder.Script() -} - -// legacyCommitScriptUnencumbered constructs the public key script on the -// commitment transaction paying to the "other" party. The constructed output -// is a normal p2wkh output spendable immediately, requiring no contestation -// period. -func legacyCommitScriptUnencumbered(key *btcec.PublicKey) ([]byte, error) { - // This script goes to the "other" party, and is spendable immediately. - builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( - P2WPKHSize, - )) - builder.AddOp(txscript.OP_0) - builder.AddData(address.Hash160(key.SerializeCompressed())) - - return builder.Script() -} - -// legacyCommitScriptToRemoteConfirmed constructs the script for the output on -// the commitment transaction paying to the remote party of said commitment -// transaction. The money can only be spend after one confirmation. -func legacyCommitScriptToRemoteConfirmed(key *btcec.PublicKey) ([]byte, error) { - builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( - ToRemoteConfirmedScriptSize, - )) - - // Only the given key can spend the output. - builder.AddData(key.SerializeCompressed()) - builder.AddOp(txscript.OP_CHECKSIGVERIFY) - - // Check that the it has one confirmation. - builder.AddOp(txscript.OP_1) - builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) - - return builder.Script() -} - -// legacyLeaseCommitScriptToRemoteConfirmed constructs the script for the -// output on the commitment transaction paying to the remote party of said -// commitment transaction, with an additional lease expiry constraint. -func legacyLeaseCommitScriptToRemoteConfirmed(key *btcec.PublicKey, - leaseExpiry uint32) ([]byte, error) { - - builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(45)) - - // Only the given key can spend the output. - builder.AddData(key.SerializeCompressed()) - builder.AddOp(txscript.OP_CHECKSIGVERIFY) - - // The channel initiator always has the additional channel lease - // expiration constraint for outputs that pay to them which must be - // satisfied. - builder.AddInt64(int64(leaseExpiry)) - builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) - builder.AddOp(txscript.OP_DROP) - - // Check that it has one confirmation. - builder.AddOp(txscript.OP_1) - builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) - - return builder.Script() -} - -// legacyCommitScriptAnchor constructs the script for the anchor output -// spendable by the given key immediately, or by anyone after 16 confirmations. -func legacyCommitScriptAnchor(key *btcec.PublicKey) ([]byte, error) { - builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( - AnchorScriptSize, - )) - - // Spend immediately with key. - builder.AddData(key.SerializeCompressed()) - builder.AddOp(txscript.OP_CHECKSIG) - - // Duplicate the value if true, since it will be consumed by the NOTIF. - builder.AddOp(txscript.OP_IFDUP) - - // Otherwise spendable by anyone after 16 confirmations. - builder.AddOp(txscript.OP_NOTIF) - builder.AddOp(txscript.OP_16) - builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) - builder.AddOp(txscript.OP_ENDIF) - - return builder.Script() -} - -// legacyLeaseSecondLevelHtlcScript is the uniform script that's used as the -// output for the second-level HTLC transactions with a lease expiry -// constraint. -func legacyLeaseSecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey, - csvDelay, cltvExpiry uint32) ([]byte, error) { - - builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( - ToLocalScriptSize + LeaseWitnessScriptSizeOverhead, - )) - - // If this is the revocation clause for this script is to be executed, - // the spender will push a 1, forcing us to hit the true clause of this - // if statement. - builder.AddOp(txscript.OP_IF) - - // If this is the revocation case, then we'll push the revocation - // public key on the stack. - builder.AddData(revocationKey.SerializeCompressed()) - - // Otherwise, this is either the sender or receiver of the HTLC - // attempting to claim the HTLC output. - builder.AddOp(txscript.OP_ELSE) - - // The channel initiator always has the additional channel lease - // expiration constraint for outputs that pay to them which must be - // satisfied. - builder.AddInt64(int64(cltvExpiry)) - builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) - builder.AddOp(txscript.OP_DROP) - - // In order to give the other party time to execute the revocation - // clause above, we require a relative timeout to pass before the - // output can be spent. - builder.AddInt64(int64(csvDelay)) - builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) - builder.AddOp(txscript.OP_DROP) - - // If the relative timelock passes, then we'll add the delay key to the - // stack to ensure that we properly authenticate the spending party. - builder.AddData(delayKey.SerializeCompressed()) - - // Close out the if statement. - builder.AddOp(txscript.OP_ENDIF) - - // In either case, we'll ensure that only either the party possessing - // the revocation private key, or the delay private key is able to - // spend this output. - builder.AddOp(txscript.OP_CHECKSIG) - - return builder.Script() -} - -// legacySenderHTLCTapLeafTimeout returns the full tapscript leaf for the -// timeout path of the sender HTLC. -func legacySenderHTLCTapLeafTimeout(senderHtlcKey, - receiverHtlcKey *btcec.PublicKey) (txscript.TapLeaf, error) { - - builder := txscript.NewScriptBuilder() - - builder.AddData(schnorr.SerializePubKey(senderHtlcKey)) - builder.AddOp(txscript.OP_CHECKSIGVERIFY) - builder.AddData(schnorr.SerializePubKey(receiverHtlcKey)) - builder.AddOp(txscript.OP_CHECKSIG) - - timeoutLeafScript, err := builder.Script() - if err != nil { - return txscript.TapLeaf{}, err - } - - return txscript.NewBaseTapLeaf(timeoutLeafScript), nil -} - -// legacySenderHTLCTapLeafSuccess returns the full tapscript leaf for the -// success path of the sender HTLC. -func legacySenderHTLCTapLeafSuccess(receiverHtlcKey *btcec.PublicKey, - paymentHash []byte) (txscript.TapLeaf, error) { - - builder := txscript.NewScriptBuilder() - - // Check that the pre-image is 32 bytes as required. - builder.AddOp(txscript.OP_SIZE) - builder.AddInt64(32) - builder.AddOp(txscript.OP_EQUALVERIFY) - - // Check that the specified pre-image matches what we hard code into - // the script. - builder.AddOp(txscript.OP_HASH160) - builder.AddData(Ripemd160H(paymentHash)) - builder.AddOp(txscript.OP_EQUALVERIFY) - - // Verify the remote party's signature, then make them wait 1 block - // after confirmation to properly sweep. - builder.AddData(schnorr.SerializePubKey(receiverHtlcKey)) - builder.AddOp(txscript.OP_CHECKSIG) - builder.AddOp(txscript.OP_1) - builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) - builder.AddOp(txscript.OP_DROP) - - successLeafScript, err := builder.Script() - if err != nil { - return txscript.TapLeaf{}, err - } - - return txscript.NewBaseTapLeaf(successLeafScript), nil -} - -// legacyReceiverHtlcTapLeafTimeout returns the full tapscript leaf for the -// timeout path of the receiver HTLC. -func legacyReceiverHtlcTapLeafTimeout(senderHtlcKey *btcec.PublicKey, - cltvExpiry uint32) (txscript.TapLeaf, error) { - - builder := txscript.NewScriptBuilder() - - // The first part of the script will verify a signature from the - // sender authorizing the spend (the timeout). - builder.AddData(schnorr.SerializePubKey(senderHtlcKey)) - builder.AddOp(txscript.OP_CHECKSIG) - builder.AddOp(txscript.OP_1) - builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) - builder.AddOp(txscript.OP_DROP) - - // The second portion will ensure that the CLTV expiry on the spending - // transaction is correct. - builder.AddInt64(int64(cltvExpiry)) - builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) - builder.AddOp(txscript.OP_DROP) - - timeoutLeafScript, err := builder.Script() - if err != nil { - return txscript.TapLeaf{}, err - } - - return txscript.NewBaseTapLeaf(timeoutLeafScript), nil -} - -// legacyReceiverHtlcTapLeafSuccess returns the full tapscript leaf for the -// success path for an HTLC on the receiver's commitment transaction. -func legacyReceiverHtlcTapLeafSuccess(receiverHtlcKey *btcec.PublicKey, - senderHtlcKey *btcec.PublicKey, - paymentHash []byte) (txscript.TapLeaf, error) { - - builder := txscript.NewScriptBuilder() - - // Check that the pre-image is 32 bytes as required. - builder.AddOp(txscript.OP_SIZE) - builder.AddInt64(32) - builder.AddOp(txscript.OP_EQUALVERIFY) - - // Check that the specified pre-image matches what we hard code into - // the script. - builder.AddOp(txscript.OP_HASH160) - builder.AddData(Ripemd160H(paymentHash)) - builder.AddOp(txscript.OP_EQUALVERIFY) - - // Verify the "2-of-2" multi-sig that requires both parties to sign - // off. - builder.AddData(schnorr.SerializePubKey(receiverHtlcKey)) - builder.AddOp(txscript.OP_CHECKSIGVERIFY) - builder.AddData(schnorr.SerializePubKey(senderHtlcKey)) - builder.AddOp(txscript.OP_CHECKSIG) - - successLeafScript, err := builder.Script() - if err != nil { - return txscript.TapLeaf{}, err - } - - return txscript.NewBaseTapLeaf(successLeafScript), nil -} - -// legacyTaprootSecondLevelTapLeaf constructs the tap leaf used as the sole -// script path for a second level HTLC spend. -func legacyTaprootSecondLevelTapLeaf(delayKey *btcec.PublicKey, - csvDelay uint32) (txscript.TapLeaf, error) { - - builder := txscript.NewScriptBuilder() - - // Ensure the proper party can sign for this output. - builder.AddData(schnorr.SerializePubKey(delayKey)) - builder.AddOp(txscript.OP_CHECKSIG) - - // Assuming the above passes, then we'll now ensure that the CSV delay - // has been upheld, dropping the int we pushed on. If the sig above is - // valid, then a 1 will be left on the stack. - builder.AddInt64(int64(csvDelay)) - builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) - builder.AddOp(txscript.OP_DROP) - - secondLevelLeafScript, err := builder.Script() - if err != nil { - return txscript.TapLeaf{}, err - } - - return txscript.NewBaseTapLeaf(secondLevelLeafScript), nil -} - -// legacyTaprootLocalCommitDelayScript builds the tap leaf with the CSV delay -// script for the to-local output. -func legacyTaprootLocalCommitDelayScript(csvTimeout uint32, - selfKey *btcec.PublicKey) ([]byte, error) { - - builder := txscript.NewScriptBuilder() - builder.AddData(schnorr.SerializePubKey(selfKey)) - builder.AddOp(txscript.OP_CHECKSIG) - builder.AddInt64(int64(csvTimeout)) - builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) - builder.AddOp(txscript.OP_DROP) - - return builder.Script() -} - -// legacyTaprootLocalCommitRevokeScript builds the tap leaf with the revocation -// path for the to-local output. -func legacyTaprootLocalCommitRevokeScript(selfKey, revokeKey *btcec.PublicKey) ( - []byte, error) { - - builder := txscript.NewScriptBuilder() - builder.AddData(schnorr.SerializePubKey(selfKey)) - builder.AddOp(txscript.OP_DROP) - builder.AddData(schnorr.SerializePubKey(revokeKey)) - builder.AddOp(txscript.OP_CHECKSIG) - - return builder.Script() -} diff --git a/input/script_utils_template_equiv_test.go b/input/script_utils_template_equiv_test.go deleted file mode 100644 index 4d26b5b7d..000000000 --- a/input/script_utils_template_equiv_test.go +++ /dev/null @@ -1,454 +0,0 @@ -package input - -import ( - "crypto/sha256" - "encoding/hex" - "testing" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/stretchr/testify/require" -) - -// testKeyBytes returns deterministic key bytes for testing. The index parameter -// produces different keys for different roles by deriving private keys from a -// hash and computing the corresponding public key on secp256k1. -func testKeyBytes(t *testing.T, index byte) *btcec.PublicKey { - t.Helper() - - hash := sha256.Sum256([]byte{index}) - privKey, _ := btcec.PrivKeyFromBytes(hash[:]) - - return privKey.PubKey() -} - -// testPaymentHash returns a deterministic 32-byte payment hash. -func testPaymentHash() []byte { - h := sha256.Sum256([]byte("test-payment-preimage")) - return h[:] -} - -// TestTemplateVsBuilderEquivalence verifies that the new ScriptTemplate-based -// functions produce byte-for-byte identical output to the old ScriptBuilder -// versions for all script types. -func TestTemplateVsBuilderEquivalence(t *testing.T) { - t.Parallel() - - // Set up test keys for various roles. - senderKey := testKeyBytes(t, 1) - receiverKey := testKeyBytes(t, 2) - revokeKey := testKeyBytes(t, 3) - selfKey := testKeyBytes(t, 4) - delayKey := testKeyBytes(t, 5) - remoteKey := testKeyBytes(t, 6) - - payHash := testPaymentHash() - - const ( - csvDelay uint32 = 144 - cltvExpiry uint32 = 800000 - leaseExpiry uint32 = 900000 - ) - - t.Run("WitnessScriptHash", func(t *testing.T) { - t.Parallel() - witnessScript := []byte("test-witness-script") - - got, err := WitnessScriptHash(witnessScript) - require.NoError(t, err) - - want, err := legacyWitnessScriptHash(witnessScript) - require.NoError(t, err) - - require.Equal(t, want, got, - "WitnessScriptHash mismatch:\n"+ - " legacy: %x\n template: %x", - want, got, - ) - }) - - t.Run("WitnessPubKeyHash", func(t *testing.T) { - t.Parallel() - pubkey := senderKey.SerializeCompressed() - - got, err := WitnessPubKeyHash(pubkey) - require.NoError(t, err) - - want, err := legacyWitnessPubKeyHash(pubkey) - require.NoError(t, err) - - require.Equal(t, want, got) - }) - - t.Run("GenerateP2SH", func(t *testing.T) { - t.Parallel() - script := []byte("test-redeem-script") - - got, err := GenerateP2SH(script) - require.NoError(t, err) - - want, err := legacyGenerateP2SH(script) - require.NoError(t, err) - - require.Equal(t, want, got) - }) - - t.Run("GenerateP2PKH", func(t *testing.T) { - t.Parallel() - pubkey := senderKey.SerializeCompressed() - - got, err := GenerateP2PKH(pubkey) - require.NoError(t, err) - - want, err := legacyGenerateP2PKH(pubkey) - require.NoError(t, err) - - require.Equal(t, want, got) - }) - - t.Run("GenMultiSigScript", func(t *testing.T) { - t.Parallel() - aPub := senderKey.SerializeCompressed() - bPub := receiverKey.SerializeCompressed() - - got, err := GenMultiSigScript(aPub, bPub) - require.NoError(t, err) - - want, err := legacyGenMultiSigScript(aPub, bPub) - require.NoError(t, err) - - require.Equal(t, want, got) - }) - - t.Run("SenderHTLCScript/confirmed", func(t *testing.T) { - t.Parallel() - - got, err := SenderHTLCScript( - senderKey, receiverKey, revokeKey, payHash, true, - ) - require.NoError(t, err) - - want, err := legacySenderHTLCScript( - senderKey, receiverKey, revokeKey, payHash, true, - ) - require.NoError(t, err) - - require.Equal(t, want, got, - "SenderHTLCScript(confirmed) mismatch:\n"+ - " legacy: %x\n template: %x", - want, got, - ) - }) - - t.Run("SenderHTLCScript/unconfirmed", func(t *testing.T) { - t.Parallel() - - got, err := SenderHTLCScript( - senderKey, receiverKey, revokeKey, payHash, false, - ) - require.NoError(t, err) - - want, err := legacySenderHTLCScript( - senderKey, receiverKey, revokeKey, payHash, false, - ) - require.NoError(t, err) - - require.Equal(t, want, got) - }) - - t.Run("ReceiverHTLCScript/confirmed", func(t *testing.T) { - t.Parallel() - - got, err := ReceiverHTLCScript( - cltvExpiry, senderKey, receiverKey, revokeKey, - payHash, true, - ) - require.NoError(t, err) - - want, err := legacyReceiverHTLCScript( - cltvExpiry, senderKey, receiverKey, revokeKey, - payHash, true, - ) - require.NoError(t, err) - - require.Equal(t, want, got, - "ReceiverHTLCScript(confirmed) mismatch:\n"+ - " legacy: %x\n template: %x", - want, got, - ) - }) - - t.Run("ReceiverHTLCScript/unconfirmed", func(t *testing.T) { - t.Parallel() - - got, err := ReceiverHTLCScript( - cltvExpiry, senderKey, receiverKey, revokeKey, - payHash, false, - ) - require.NoError(t, err) - - want, err := legacyReceiverHTLCScript( - cltvExpiry, senderKey, receiverKey, revokeKey, - payHash, false, - ) - require.NoError(t, err) - - require.Equal(t, want, got) - }) - - t.Run("SecondLevelHtlcScript", func(t *testing.T) { - t.Parallel() - - got, err := SecondLevelHtlcScript( - revokeKey, delayKey, csvDelay, - ) - require.NoError(t, err) - - want, err := legacySecondLevelHtlcScript( - revokeKey, delayKey, csvDelay, - ) - require.NoError(t, err) - - require.Equal(t, want, got) - }) - - t.Run("CommitScriptToSelf", func(t *testing.T) { - t.Parallel() - - got, err := CommitScriptToSelf(csvDelay, selfKey, revokeKey) - require.NoError(t, err) - - want, err := legacyCommitScriptToSelf( - csvDelay, selfKey, revokeKey, - ) - require.NoError(t, err) - - require.Equal(t, want, got) - }) - - t.Run("LeaseCommitScriptToSelf", func(t *testing.T) { - t.Parallel() - - got, err := LeaseCommitScriptToSelf( - selfKey, revokeKey, csvDelay, leaseExpiry, - ) - require.NoError(t, err) - - want, err := legacyLeaseCommitScriptToSelf( - selfKey, revokeKey, csvDelay, leaseExpiry, - ) - require.NoError(t, err) - - require.Equal(t, want, got) - }) - - t.Run("CommitScriptUnencumbered", func(t *testing.T) { - t.Parallel() - - got, err := CommitScriptUnencumbered(remoteKey) - require.NoError(t, err) - - want, err := legacyCommitScriptUnencumbered(remoteKey) - require.NoError(t, err) - - require.Equal(t, want, got) - }) - - t.Run("CommitScriptToRemoteConfirmed", func(t *testing.T) { - t.Parallel() - - got, err := CommitScriptToRemoteConfirmed(remoteKey) - require.NoError(t, err) - - want, err := legacyCommitScriptToRemoteConfirmed(remoteKey) - require.NoError(t, err) - - require.Equal(t, want, got) - }) - - t.Run("LeaseCommitScriptToRemoteConfirmed", func(t *testing.T) { - t.Parallel() - - got, err := LeaseCommitScriptToRemoteConfirmed( - remoteKey, leaseExpiry, - ) - require.NoError(t, err) - - want, err := legacyLeaseCommitScriptToRemoteConfirmed( - remoteKey, leaseExpiry, - ) - require.NoError(t, err) - - require.Equal(t, want, got) - }) - - t.Run("CommitScriptAnchor", func(t *testing.T) { - t.Parallel() - - got, err := CommitScriptAnchor(senderKey) - require.NoError(t, err) - - want, err := legacyCommitScriptAnchor(senderKey) - require.NoError(t, err) - - require.Equal(t, want, got) - }) - - t.Run("LeaseSecondLevelHtlcScript", func(t *testing.T) { - t.Parallel() - - got, err := LeaseSecondLevelHtlcScript( - revokeKey, delayKey, csvDelay, cltvExpiry, - ) - require.NoError(t, err) - - want, err := legacyLeaseSecondLevelHtlcScript( - revokeKey, delayKey, csvDelay, cltvExpiry, - ) - require.NoError(t, err) - - require.Equal(t, want, got) - }) - - // Taproot script equivalence tests. These compare the non-prod - // (default) variant of the template functions against the old builder - // code which also produced the non-prod scripts. - t.Run("SenderHTLCTapLeafTimeout", func(t *testing.T) { - t.Parallel() - - got, err := SenderHTLCTapLeafTimeout(senderKey, receiverKey) - require.NoError(t, err) - - want, err := legacySenderHTLCTapLeafTimeout( - senderKey, receiverKey, - ) - require.NoError(t, err) - - require.Equal(t, want.Script, got.Script) - }) - - t.Run("SenderHTLCTapLeafSuccess", func(t *testing.T) { - t.Parallel() - - got, err := SenderHTLCTapLeafSuccess(receiverKey, payHash) - require.NoError(t, err) - - want, err := legacySenderHTLCTapLeafSuccess( - receiverKey, payHash, - ) - require.NoError(t, err) - - require.Equal(t, want.Script, got.Script) - }) - - t.Run("ReceiverHtlcTapLeafTimeout", func(t *testing.T) { - t.Parallel() - - got, err := ReceiverHtlcTapLeafTimeout( - senderKey, cltvExpiry, - ) - require.NoError(t, err) - - want, err := legacyReceiverHtlcTapLeafTimeout( - senderKey, cltvExpiry, - ) - require.NoError(t, err) - - require.Equal(t, want.Script, got.Script) - }) - - t.Run("ReceiverHtlcTapLeafSuccess", func(t *testing.T) { - t.Parallel() - - got, err := ReceiverHtlcTapLeafSuccess( - receiverKey, senderKey, payHash, - ) - require.NoError(t, err) - - want, err := legacyReceiverHtlcTapLeafSuccess( - receiverKey, senderKey, payHash, - ) - require.NoError(t, err) - - require.Equal(t, want.Script, got.Script) - }) - - t.Run("TaprootSecondLevelTapLeaf", func(t *testing.T) { - t.Parallel() - - got, err := TaprootSecondLevelTapLeaf(delayKey, csvDelay) - require.NoError(t, err) - - want, err := legacyTaprootSecondLevelTapLeaf( - delayKey, csvDelay, - ) - require.NoError(t, err) - - require.Equal(t, want.Script, got.Script) - }) - - t.Run("TaprootLocalCommitDelayScript", func(t *testing.T) { - t.Parallel() - - got, err := TaprootLocalCommitDelayScript( - csvDelay, selfKey, - ) - require.NoError(t, err) - - want, err := legacyTaprootLocalCommitDelayScript( - csvDelay, selfKey, - ) - require.NoError(t, err) - - require.Equal(t, want, got, - "TaprootLocalCommitDelayScript mismatch:\n"+ - " legacy: %x\n template: %x", - want, got, - ) - }) - - t.Run("TaprootLocalCommitRevokeScript", func(t *testing.T) { - t.Parallel() - - got, err := TaprootLocalCommitRevokeScript( - selfKey, revokeKey, - ) - require.NoError(t, err) - - want, err := legacyTaprootLocalCommitRevokeScript( - selfKey, revokeKey, - ) - require.NoError(t, err) - - require.Equal(t, want, got) - }) - - // Log a summary of all scripts tested for visual inspection. - t.Log("All 22 template vs builder script equivalence checks passed") -} - -// TestTemplateScriptDisassembly provides human-readable output of a few key -// scripts to make it easy to verify correctness visually. -func TestTemplateScriptDisassembly(t *testing.T) { - t.Parallel() - - senderKey := testKeyBytes(t, 1) - receiverKey := testKeyBytes(t, 2) - revokeKey := testKeyBytes(t, 3) - payHash := testPaymentHash() - - // SenderHTLCScript with confirmed spend. - script, err := SenderHTLCScript( - senderKey, receiverKey, revokeKey, payHash, true, - ) - require.NoError(t, err) - t.Logf("SenderHTLCScript (confirmed):\n %s", - hex.EncodeToString(script)) - - // ReceiverHTLCScript with confirmed spend. - script, err = ReceiverHTLCScript( - 800000, senderKey, receiverKey, revokeKey, payHash, true, - ) - require.NoError(t, err) - t.Logf("ReceiverHTLCScript (confirmed):\n %s", - hex.EncodeToString(script)) -} diff --git a/input/script_utils_test.go b/input/script_utils_test.go index 9616948a0..92eb80699 100644 --- a/input/script_utils_test.go +++ b/input/script_utils_test.go @@ -9,12 +9,11 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/keychain" - "github.com/lightningnetwork/lnd/lntypes" "github.com/stretchr/testify/require" ) @@ -2250,128 +2249,3 @@ func runScriptAllocTest(dummyData, randomPubBytes []byte, return nil } - -// TestTaprootHtlcScriptGeneration tests that taproot HTLC scripts can be -// generated with both staging and production script options. -func TestTaprootHtlcScriptGeneration(t *testing.T) { - t.Parallel() - - // Generate test keys. - senderKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - senderPubKey := senderKey.PubKey() - - receiverKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - receiverPubKey := receiverKey.PubKey() - - revokeKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - revokePubKey := revokeKey.PubKey() - - // Test constants. - cltvExpiry := uint32(500000) - hashBytes := make([]byte, 32) - copy(hashBytes, []byte("test payment hash")) - - // Use empty auxiliary leaf and local commit. - auxLeaf := NoneTapLeaf() - whoseCommit := lntypes.Local - - // Test SenderHTLCScriptTaproot with staging vs production scripts. - stagingSenderScript, err := SenderHTLCScriptTaproot( - senderPubKey, receiverPubKey, revokePubKey, hashBytes, - whoseCommit, auxLeaf, - ) - require.NoError(t, err) - - prodSenderScript, err := SenderHTLCScriptTaproot( - senderPubKey, receiverPubKey, revokePubKey, hashBytes, - whoseCommit, auxLeaf, WithProdScripts(), - ) - require.NoError(t, err) - - // Verify that both script trees are generated successfully. - require.NotNil(t, - stagingSenderScript, "staging sender script should "+ - "be generated", - ) - require.NotNil(t, - prodSenderScript, "production sender script should "+ - "be generated", - ) - - // Test ReceiverHTLCScriptTaproot with staging vs production scripts. - stagingReceiverScript, err := ReceiverHTLCScriptTaproot( - cltvExpiry, senderPubKey, receiverPubKey, revokePubKey, - hashBytes, - whoseCommit, auxLeaf, - ) - require.NoError(t, err) - - prodReceiverScript, err := ReceiverHTLCScriptTaproot( - cltvExpiry, senderPubKey, receiverPubKey, revokePubKey, - hashBytes, - whoseCommit, auxLeaf, WithProdScripts(), - ) - require.NoError(t, err) - - // Verify that both script trees are generated successfully. - require.NotNil(t, stagingReceiverScript, - "staging receiver script should be generated") - require.NotNil(t, prodReceiverScript, - "production receiver script should be generated") - - // Scripts should be different between staging and production. - // Note: The sender success script (redeemed by receiver) - // should differ. - require.NotEqual(t, - stagingSenderScript.SuccessTapLeaf.Script, - prodSenderScript.SuccessTapLeaf.Script, - "staging and production sender success scripts should differ", - ) - require.NotEqual(t, stagingReceiverScript.TimeoutTapLeaf.Script, - prodReceiverScript.TimeoutTapLeaf.Script, - "staging and production receiver timeout scripts should differ") - - // Production scripts should be smaller due to - // OP_CHECKSIGVERIFY optimizations. - require.Less(t, len(prodSenderScript.SuccessTapLeaf.Script), - len(stagingSenderScript.SuccessTapLeaf.Script), - "production sender success script should be smaller "+ - "than staging", - ) - require.Less(t, len(prodReceiverScript.TimeoutTapLeaf.Script), - len(stagingReceiverScript.TimeoutTapLeaf.Script), - "production receiver timeout script should be smaller "+ - "than staging", - ) - - // Verify the script trees have non-empty script bytes. Using - // require.NotNil on TapLeaf value types is always true, so we - // check Script bytes directly. - require.NotEmpty(t, stagingSenderScript.TimeoutTapLeaf.Script, - "staging sender timeout leaf should have script bytes") - require.NotEmpty(t, prodSenderScript.TimeoutTapLeaf.Script, - "production sender timeout leaf should have script bytes") - require.NotEmpty(t, stagingReceiverScript.SuccessTapLeaf.Script, - "staging receiver success leaf should have script bytes") - require.NotEmpty(t, prodReceiverScript.SuccessTapLeaf.Script, - "production receiver success leaf should have script bytes") - - // The timeout leaf for sender HTLC is unchanged between staging - // and production (SenderHTLCTapLeafTimeout ignores script opts). - require.Equal(t, - stagingSenderScript.TimeoutTapLeaf.Script, - prodSenderScript.TimeoutTapLeaf.Script, - "sender timeout leaf should be identical across variants", - ) - - // The success leaf for receiver HTLC is unchanged between staging - // and production (ReceiverHtlcTapLeafSuccess ignores script opts). - require.Equal(t, - stagingReceiverScript.SuccessTapLeaf.Script, - prodReceiverScript.SuccessTapLeaf.Script, - "receiver success leaf should be identical across variants", - ) -} diff --git a/input/signdescriptor.go b/input/signdescriptor.go index 7e77326b4..a01c939ae 100644 --- a/input/signdescriptor.go +++ b/input/signdescriptor.go @@ -7,8 +7,8 @@ import ( "io" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/keychain" ) diff --git a/input/signdescriptor_test.go b/input/signdescriptor_test.go index ddffaacf5..a929e3a16 100644 --- a/input/signdescriptor_test.go +++ b/input/signdescriptor_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/keychain" ) diff --git a/input/signer.go b/input/signer.go index 978a40f73..1cf4003b0 100644 --- a/input/signer.go +++ b/input/signer.go @@ -1,7 +1,7 @@ package input import ( - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" ) // Signer represents an abstract object capable of generating raw signatures as diff --git a/input/size.go b/input/size.go index 3618f204f..f1c56ff82 100644 --- a/input/size.go +++ b/input/size.go @@ -2,8 +2,8 @@ package input import ( "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/lightningnetwork/lnd/lntypes" ) @@ -275,30 +275,18 @@ const ( HtlcTimeoutWeight = 663 // TaprootHtlcTimeoutWeight is the total weight of the taproot HTLC - // timeout transaction (using staging scripts). + // timeout transaction. TaprootHtlcTimeoutWeight = 645 - // TaprootHtlcTimeoutWeightFinal is the total weight of the taproot HTLC - // timeout transaction using production scripts (with OP_CHECKSIGVERIFY - // instead of OP_CHECKSIG + OP_DROP). This is not referenced at runtime - // because taproot channels use zero-fee HTLC transactions. - TaprootHtlcTimeoutWeightFinal = 641 - // HtlcSuccessWeight 703 weight // HtlcSuccessWeight is the weight of the HTLC success transaction // which will transition an incoming HTLC to the delay-and-claim state. HtlcSuccessWeight = 703 // TaprootHtlcSuccessWeight is the total weight of the taproot HTLC - // success transaction (using staging scripts). + // success transaction. TaprootHtlcSuccessWeight = 705 - // TaprootHtlcSuccessWeightFinal is the total weight of the taproot HTLC - // success transaction using production scripts (with OP_CHECKSIGVERIFY - // instead of OP_CHECKSIG + OP_DROP). This is not referenced at runtime - // because taproot channels use zero-fee HTLC transactions. - TaprootHtlcSuccessWeightFinal = 701 - // HtlcConfirmedScriptOverhead 3 bytes // HtlcConfirmedScriptOverhead is the extra length of an HTLC script // that requires confirmation before it can be spent. These extra bytes @@ -637,16 +625,6 @@ const ( TaprootToLocalWitnessSize = 1 + 1 + 65 + 1 + TaprootToLocalScriptSize + 1 + TaprootBaseControlBlockWitnessSize + 32 - // TaprootToLocalScriptSizeFinal: 40 bytes (production scripts). - // Replaces OP_CHECKSIG + OP_CSV + OP_DROP with - // OP_CHECKSIGVERIFY + OP_CSV, saving 1 byte. - TaprootToLocalScriptSizeFinal = TaprootToLocalScriptSize - 1 - - // TaprootToLocalWitnessSizeFinal: 174 bytes (production scripts). - TaprootToLocalWitnessSizeFinal = 1 + 1 + 65 + 1 + - TaprootToLocalScriptSizeFinal + - 1 + TaprootBaseControlBlockWitnessSize + 32 - // TaprootToLocalRevokeScriptSize: 68 bytes // - OP_DATA: 1 byte // - local key: 32 bytes @@ -689,16 +667,6 @@ const ( TaprootToRemoteScriptSize + 1 + TaprootBaseControlBlockWitnessSize) - // TaprootToRemoteScriptSizeFinal: 36 bytes (production scripts). - // Replaces OP_CHECKSIG + OP_1 + OP_CSV + OP_DROP with - // OP_CHECKSIGVERIFY + OP_1 + OP_CSV, saving 1 byte. - TaprootToRemoteScriptSizeFinal = TaprootToRemoteScriptSize - 1 - - // TaprootToRemoteWitnessSizeFinal: 138 bytes (production scripts). - TaprootToRemoteWitnessSizeFinal = (1 + 1 + 65 + 1 + - TaprootToRemoteScriptSizeFinal + 1 + - TaprootBaseControlBlockWitnessSize) - // TaprootAnchorWitnessSize: 67 bytes // // In this case, we use the custom sighash size to give the most @@ -727,17 +695,6 @@ const ( TaprootSecondLevelHtlcScriptSize + 1 + TaprootBaseControlBlockWitnessSize - // TaprootSecondLevelHtlcScriptSizeFinal: 40 bytes (production - // scripts). Replaces OP_CHECKSIG + OP_DROP with OP_CHECKSIGVERIFY, - // saving 1 byte. - //nolint:ll - TaprootSecondLevelHtlcScriptSizeFinal = TaprootSecondLevelHtlcScriptSize - 1 - - // TaprootSecondLevelHtlcWitnessSizeFinal: production scripts. - TaprootSecondLevelHtlcWitnessSizeFinal = 1 + 1 + 65 + 1 + - TaprootSecondLevelHtlcScriptSizeFinal + 1 + - TaprootBaseControlBlockWitnessSize - // TaprootSecondLevelRevokeWitnessSize // - number_of_witness_elements: 1 byte // - sig_len: 1 byte @@ -772,13 +729,6 @@ const ( TaprootHtlcOfferedRemoteTimeoutScriptSize = (1 + 32 + 1 + 1 + 1 + 1 + 1 + 4 + 1 + 1) - // TaprootHtlcOfferedRemoteTimeoutScriptSizeFinal: 43 bytes - // (production scripts). Replaces OP_CHECKSIG with OP_CHECKSIGVERIFY - // (saves 1 byte by dropping the trailing OP_DROP), but - // OP_CHECKSEQUENCEVERIFY + OP_DROP becomes CSV + OP_VERIFY (same - // size). Net saving: 1 byte. - TaprootHtlcOfferedRemoteTimeoutScriptSizeFinal = TaprootHtlcOfferedRemoteTimeoutScriptSize - 1 //nolint:ll - // TaprootHtlcOfferedRemoteTimeoutwitSize: 176 bytes // - number_of_witness_elements: 1 byte // - sig_len: 1 byte @@ -792,12 +742,6 @@ const ( TaprootHtlcOfferedRemoteTimeoutScriptSize + 1 + TaprootBaseControlBlockWitnessSize + 32 - // TaprootHtlcOfferedRemoteTimeoutWitnessSizeFinal: 174 bytes - // (production scripts). - TaprootHtlcOfferedRemoteTimeoutWitnessSizeFinal = 1 + 1 + 65 + 1 + - TaprootHtlcOfferedRemoteTimeoutScriptSizeFinal + 1 + - TaprootBaseControlBlockWitnessSize + 32 - // TaprootHtlcOfferedLocalTmeoutScriptSize: // - OP_DATA: 1 byte (pub key len) // - local_key: 32 bytes @@ -807,12 +751,6 @@ const ( // - OP_CHECKSIG: 1 byte TaprootHtlcOfferedLocalTimeoutScriptSize = 1 + 32 + 1 + 1 + 32 + 1 - // TaprootHtlcOfferedLocalTimeoutScriptSizeFinal is the same as the - // staging version since SenderHTLCTapLeafTimeout ignores script - // options (the script is already identical between staging and - // production). - TaprootHtlcOfferedLocalTimeoutScriptSizeFinal = TaprootHtlcOfferedLocalTimeoutScriptSize //nolint:ll - // TaprootOfferedLocalTimeoutWitnessSize // - number_of_witness_elements: 1 byte // - sig_len: 1 byte @@ -828,15 +766,6 @@ const ( TaprootHtlcOfferedLocalTimeoutScriptSize + 1 + TaprootBaseControlBlockWitnessSize + 32 - // TaprootOfferedLocalTimeoutWitnessSizeFinal: 235 bytes - // (production scripts). Not currently referenced because there is no - // dedicated Final witness type for this spending path — the script is - // identical between staging and final as SenderHTLCTapLeafTimeout - // ignores script options. - TaprootOfferedLocalTimeoutWitnessSizeFinal = 1 + 1 + 65 + 1 + 65 + 1 + - TaprootHtlcOfferedLocalTimeoutScriptSizeFinal + 1 + - TaprootBaseControlBlockWitnessSize + 32 - // TaprootHtlcAcceptedRemoteSuccessScriptSize: // - OP_SIZE: 1 byte // - OP_DATA: 1 byte @@ -855,11 +784,6 @@ const ( TaprootHtlcAcceptedRemoteSuccessScriptSize = 1 + 1 + 1 + 1 + 1 + 1 + 1 + 20 + 1 + 32 + 1 + 1 + 1 + 1 - // TaprootHtlcAcceptedRemoteSuccessScriptSizeFinal: 43 bytes - // (production scripts). Replaces OP_CHECKSIG + OP_CSV + OP_DROP with - // OP_CHECKSIGVERIFY + OP_CSV, saving 1 byte (the trailing OP_DROP). - TaprootHtlcAcceptedRemoteSuccessScriptSizeFinal = TaprootHtlcAcceptedRemoteSuccessScriptSize - 1 //nolint:ll - // TaprootHtlcAcceptedRemoteSuccessScriptSize: // - number_of_witness_elements: 1 byte // - sig_len: 1 byte @@ -875,13 +799,6 @@ const ( TaprootHtlcAcceptedRemoteSuccessScriptSize + 1 + TaprootBaseControlBlockWitnessSize + 32 - // TaprootHtlcAcceptedRemoteSuccessWitnessSizeFinal: 166 bytes - // (production scripts). - TaprootHtlcAcceptedRemoteSuccessWitnessSizeFinal = 1 + 1 + 65 + - 1 + 32 + 1 + - TaprootHtlcAcceptedRemoteSuccessScriptSizeFinal + 1 + - TaprootBaseControlBlockWitnessSize + 32 - // TaprootHtlcAcceptedLocalSuccessScriptSize: // - OP_SIZE: 1 byte // - OP_DATA: 1 byte @@ -900,12 +817,6 @@ const ( TaprootHtlcAcceptedLocalSuccessScriptSize = 1 + 1 + 1 + 1 + 1 + 1 + 20 + 1 + 1 + 32 + 1 + 1 + 32 + 1 - // TaprootHtlcAcceptedLocalSuccessScriptSizeFinal is the same as the - // staging version since ReceiverHtlcTapLeafSuccess ignores script - // options (the script is already identical between staging and - // production). - TaprootHtlcAcceptedLocalSuccessScriptSizeFinal = TaprootHtlcAcceptedLocalSuccessScriptSize //nolint:ll - // TaprootHtlcAcceptedLocalSuccessWitnessSize: // - number_of_witness_elements: 1 byte // - sig_len: 1 byte @@ -922,16 +833,6 @@ const ( TaprootHtlcAcceptedLocalSuccessWitnessSize = 1 + 1 + 65 + 1 + 65 + 1 + 32 + 1 + TaprootHtlcAcceptedLocalSuccessScriptSize + 1 + TaprootBaseControlBlockWitnessSize + 32 - - // TaprootHtlcAcceptedLocalSuccessWitnessSizeFinal: 271 - // bytes (production scripts). Not currently referenced because there - // is no dedicated Final witness type for this spending path — the - // script is identical between staging and final as - // ReceiverHtlcTapLeafSuccess ignores script options. - TaprootHtlcAcceptedLocalSuccessWitnessSizeFinal = 1 + 1 + - 65 + 1 + 65 + 1 + 32 + 1 + - TaprootHtlcAcceptedLocalSuccessScriptSizeFinal + 1 + - TaprootBaseControlBlockWitnessSize + 32 ) // EstimateCommitTxWeight estimate commitment transaction weight depending on diff --git a/input/size_test.go b/input/size_test.go index 7b1936407..2fba2c7b2 100644 --- a/input/size_test.go +++ b/input/size_test.go @@ -3,14 +3,13 @@ package input_test import ( "testing" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -60,25 +59,25 @@ var ( func TestTxWeightEstimator(t *testing.T) { netParams := &chaincfg.MainNetParams - p2pkhAddr, err := address.NewAddressPubKeyHash( + p2pkhAddr, err := btcutil.NewAddressPubKeyHash( make([]byte, 20), netParams) require.NoError(t, err, "Failed to generate address") p2pkhScript, err := txscript.PayToAddrScript(p2pkhAddr) require.NoError(t, err, "Failed to generate scriptPubKey") - p2wkhAddr, err := address.NewAddressWitnessPubKeyHash( + p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash( make([]byte, 20), netParams) require.NoError(t, err, "Failed to generate address") p2wkhScript, err := txscript.PayToAddrScript(p2wkhAddr) require.NoError(t, err, "Failed to generate scriptPubKey") - p2wshAddr, err := address.NewAddressWitnessScriptHash( + p2wshAddr, err := btcutil.NewAddressWitnessScriptHash( make([]byte, 32), netParams) require.NoError(t, err, "Failed to generate address") p2wshScript, err := txscript.PayToAddrScript(p2wshAddr) require.NoError(t, err, "Failed to generate scriptPubKey") - p2shAddr, err := address.NewAddressScriptHash([]byte{0}, netParams) + p2shAddr, err := btcutil.NewAddressScriptHash([]byte{0}, netParams) require.NoError(t, err, "Failed to generate address") p2shScript, err := txscript.PayToAddrScript(p2shAddr) require.NoError(t, err, "Failed to generate scriptPubKey") @@ -1020,49 +1019,6 @@ var witnessSizeTests = []witnessSizeTest{ return witness }, }, - { - name: "taproot second level htlc success+timeout final", - expSize: input.TaprootSecondLevelHtlcWitnessSizeFinal, - genWitness: func(t *testing.T) wire.TxWitness { - testKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - signer := &dummySigner{} - - scriptTree, err := input.SecondLevelHtlcTapscriptTree( - testKey.PubKey(), testCSVDelay, - input.NoneTapLeaf(), - input.WithProdScripts(), - ) - require.NoError(t, err) - - tapScriptRoot := scriptTree.RootNode.TapHash() - - revokeKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - tapLeaf := scriptTree.LeafMerkleProofs[0].TapLeaf - witnessScript := tapLeaf.Script - signDesc := &input.SignDescriptor{ - KeyDesc: keychain.KeyDescriptor{ - PubKey: revokeKey.PubKey(), - }, - WitnessScript: witnessScript, - HashType: txscript.SigHashAll, - InputIndex: 0, - SignMethod: input.TaprootKeySpendSignMethod, - TapTweak: tapScriptRoot[:], - } - - witness, err := input.TaprootHtlcSpendSuccess( - signer, signDesc, testTx, revokeKey.PubKey(), - scriptTree, - ) - require.NoError(t, err) - - return witness - }, - }, { name: "taproot second level htlc revoke", expSize: input.TaprootSecondLevelRevokeWitnessSize, @@ -1397,177 +1353,6 @@ var witnessSizeTests = []witnessSizeTest{ ) require.NoError(t, err) - return witness - }, - }, - // Production taproot (Final) variants. These use WithProdScripts() - // which replaces OP_CHECKSIG + OP_DROP with OP_CHECKSIGVERIFY in - // the remote timeout and remote success scripts. - { - name: "taproot to local sweep final", - expSize: input.TaprootToLocalWitnessSizeFinal, - genWitness: func(t *testing.T) wire.TxWitness { - testKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - signer := &dummySigner{} - commitScriptTree, err := input.NewLocalCommitScriptTree( - testCSVDelay, testKey.PubKey(), - testKey.PubKey(), input.NoneTapLeaf(), - input.WithProdScripts(), - ) - require.NoError(t, err) - - signDesc := &input.SignDescriptor{ - KeyDesc: keychain.KeyDescriptor{ - PubKey: testKey.PubKey(), - }, - WitnessScript: commitScriptTree. - SettleLeaf.Script, - HashType: txscript.SigHashAll, - InputIndex: 0, - SignMethod: input. - TaprootScriptSpendSignMethod, - } - - witness, err := input.TaprootCommitSpendSuccess( - signer, signDesc, testTx, - commitScriptTree.TapscriptTree, - ) - require.NoError(t, err) - - return witness - }, - }, - { - name: "taproot to remote sweep final", - expSize: input.TaprootToRemoteWitnessSizeFinal, - genWitness: func(t *testing.T) wire.TxWitness { - testKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - signer := &dummySigner{} - cst, err := input.NewRemoteCommitScriptTree( - testKey.PubKey(), - input.NoneTapLeaf(), - input.WithProdScripts(), - ) - require.NoError(t, err) - - signDesc := &input.SignDescriptor{ - KeyDesc: keychain.KeyDescriptor{ - PubKey: testKey.PubKey(), - }, - WitnessScript: cst.SettleLeaf.Script, - HashType: txscript.SigHashAll, - InputIndex: 0, - SignMethod: input. - TaprootScriptSpendSignMethod, - } - - witness, err := input.TaprootCommitRemoteSpend( - signer, signDesc, testTx, - cst.TapscriptTree, - ) - require.NoError(t, err) - - return witness - }, - }, - { - name: "taproot offered remote timeout final", - expSize: input.TaprootHtlcOfferedRemoteTimeoutWitnessSizeFinal, - genWitness: func(t *testing.T) wire.TxWitness { - senderKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - receiverKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - revokeKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - var payHash [32]byte - - signer := &dummySigner{} - - htlcScriptTree, err := input.ReceiverHTLCScriptTaproot( - testCLTVExpiry, senderKey.PubKey(), - receiverKey.PubKey(), revokeKey.PubKey(), - payHash[:], lntypes.Remote, - input.NoneTapLeaf(), - input.WithProdScripts(), - ) - require.NoError(t, err) - - timeoutLeaf := htlcScriptTree.TimeoutTapLeaf - - signDesc := &input.SignDescriptor{ - KeyDesc: keychain.KeyDescriptor{ - PubKey: senderKey.PubKey(), - }, - WitnessScript: timeoutLeaf.Script, - HashType: txscript.SigHashAll, - InputIndex: 0, - SignMethod: input. - TaprootScriptSpendSignMethod, - } - - witness, err := input.ReceiverHTLCScriptTaprootTimeout( - signer, signDesc, testTx, testCLTVExpiry, - revokeKey.PubKey(), - htlcScriptTree.TapscriptTree, - ) - require.NoError(t, err) - - return witness - }, - }, - { - name: "taproot accepted remote success final", - expSize: input.TaprootHtlcAcceptedRemoteSuccessWitnessSizeFinal, - genWitness: func(t *testing.T) wire.TxWitness { - senderKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - receiverKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - revokeKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - var payHash [32]byte - - signer := &dummySigner{} - - htlcScriptTree, err := input.SenderHTLCScriptTaproot( - senderKey.PubKey(), receiverKey.PubKey(), - revokeKey.PubKey(), payHash[:], - lntypes.Remote, input.NoneTapLeaf(), - input.WithProdScripts(), - ) - require.NoError(t, err) - - successLeaf := htlcScriptTree.SuccessTapLeaf - scriptTree := htlcScriptTree.TapscriptTree - - signDesc := &input.SignDescriptor{ - KeyDesc: keychain.KeyDescriptor{ - PubKey: receiverKey.PubKey(), - }, - WitnessScript: successLeaf.Script, - HashType: txscript.SigHashAll, - InputIndex: 0, - SignMethod: input. - TaprootScriptSpendSignMethod, - } - - witness, err := input.SenderHTLCScriptTaprootRedeem( - signer, signDesc, testTx, testPreimage, - revokeKey.PubKey(), scriptTree, - ) - require.NoError(t, err) - return witness }, }, @@ -1580,6 +1365,7 @@ var witnessSizeTests = []witnessSizeTest{ // aren't under estimating or our transactions could get stuck. func TestWitnessSizes(t *testing.T) { for _, test := range witnessSizeTests { + test := test t.Run(test.name, func(t *testing.T) { size := test.genWitness(t).SerializeSize() if size != test.expSize { @@ -1793,6 +1579,7 @@ var txSizeTests = []txSizeTest{ // TestTxSizes asserts the correctness of our magic tx size constants. func TestTxSizes(t *testing.T) { for _, test := range txSizeTests { + test := test t.Run(test.name, func(t *testing.T) { tx := test.genTx(t) @@ -1806,146 +1593,3 @@ func TestTxSizes(t *testing.T) { }) } } - -// TestTaprootScriptOptions tests that both staging and production taproot -// scripts can be generated successfully and that they produce different -// script trees. -func TestTaprootScriptOptions(t *testing.T) { - // Generate test keys. - senderKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - receiverKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - revokeKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - var payHash [32]byte - copy(payHash[:], "testhash") - - // Test SenderHTLCScriptTaproot with different options. - t.Run("SenderHTLC staging vs production", func(t *testing.T) { - // Generate staging script (default). - stagingScript, err := input.SenderHTLCScriptTaproot( - senderKey.PubKey(), receiverKey.PubKey(), - revokeKey.PubKey(), payHash[:], lntypes.Remote, - input.NoneTapLeaf(), - ) - require.NoError(t, err) - - // Generate production script. - prodScript, err := input.SenderHTLCScriptTaproot( - senderKey.PubKey(), receiverKey.PubKey(), - revokeKey.PubKey(), payHash[:], lntypes.Remote, - input.NoneTapLeaf(), input.WithProdScripts(), - ) - require.NoError(t, err) - - // For sender HTLC, only the success script (redeemed - // by receiver) differs. - require.NotEqual(t, stagingScript.SuccessTapLeaf.Script, - prodScript.SuccessTapLeaf.Script, - "staging and production sender success "+ - "scripts should differ", - ) - - // Production success script should be smaller due to - // OP_CHECKSIGVERIFY optimizations. - require.Less(t, len(prodScript.SuccessTapLeaf.Script), - len(stagingScript.SuccessTapLeaf.Script), - "production sender success script "+ - "should be smaller than staging", - ) - - // Both should have valid tapscript trees. - require.NotNil(t, stagingScript.TapscriptTree) - require.NotNil(t, prodScript.TapscriptTree) - }) - - // Test ReceiverHTLCScriptTaproot with different options. - t.Run("ReceiverHTLC staging vs production", func(t *testing.T) { - cltvExpiry := uint32(500000) - - // Generate staging script (default). - stagingScript, err := input.ReceiverHTLCScriptTaproot( - cltvExpiry, senderKey.PubKey(), receiverKey.PubKey(), - revokeKey.PubKey(), payHash[:], lntypes.Remote, - input.NoneTapLeaf(), - ) - require.NoError(t, err) - - // Generate production script. - prodScript, err := input.ReceiverHTLCScriptTaproot( - cltvExpiry, senderKey.PubKey(), receiverKey.PubKey(), - revokeKey.PubKey(), payHash[:], lntypes.Remote, - input.NoneTapLeaf(), input.WithProdScripts(), - ) - require.NoError(t, err) - - // For receiver HTLC, the timeout script (sender - // reclaims) should differ. - require.NotEqual(t, stagingScript.TimeoutTapLeaf.Script, - prodScript.TimeoutTapLeaf.Script, - "staging and production receiver "+ - "timeout scripts should differ", - ) - - // Production timeout script should be smaller due to - // OP_CHECKSIGVERIFY optimizations. - require.Less(t, len(prodScript.TimeoutTapLeaf.Script), - len(stagingScript.TimeoutTapLeaf.Script), - "production receiver timeout script "+ - "should be smaller than staging", - ) - - // Both should have valid tapscript trees. - require.NotNil(t, stagingScript.TapscriptTree) - require.NotNil(t, prodScript.TapscriptTree) - }) - - // Test commit scripts with different options. - t.Run("CommitScript staging vs production", func(t *testing.T) { - csvDelay := uint32(144) - - // Generate staging script (default). - stagingScript, err := input.NewLocalCommitScriptTree( - csvDelay, senderKey.PubKey(), revokeKey.PubKey(), - input.NoneTapLeaf(), - ) - require.NoError(t, err) - - // Generate production script. - prodScript, err := input.NewLocalCommitScriptTree( - csvDelay, senderKey.PubKey(), revokeKey.PubKey(), - input.NoneTapLeaf(), input.WithProdScripts(), - ) - require.NoError(t, err) - - // Only the settle script should differ between staging - // and production. - // The revocation script doesn't implement production - // optimizations. - require.NotEqual(t, stagingScript.SettleLeaf.Script, - prodScript.SettleLeaf.Script, - "staging and production settle scripts should differ") - - // Revocation scripts should be identical (no production - // optimization). - require.Equal(t, stagingScript.RevocationLeaf.Script, - prodScript.RevocationLeaf.Script, - "revocation scripts should be identical "+ - "between staging and "+ - "production") - - // Production settle script should be smaller due to - // OP_CHECKSIGVERIFY optimizations. - require.Less(t, len(prodScript.SettleLeaf.Script), - len(stagingScript.SettleLeaf.Script), - "production settle script should "+ - "be smaller than staging", - ) - - // Both should have valid tapscript trees. - require.NotNil(t, stagingScript.TapscriptTree) - require.NotNil(t, prodScript.TapscriptTree) - }) -} diff --git a/input/taproot.go b/input/taproot.go index 32a634af0..5ca4dd0c6 100644 --- a/input/taproot.go +++ b/input/taproot.go @@ -5,8 +5,8 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/lightningnetwork/lnd/fn/v2" ) @@ -169,10 +169,10 @@ func TapscriptFullKeyOnly(taprootKey *btcec.PublicKey) *waddrmgr.Tapscript { // witness program. The passed public key will be serialized as an x-only key // to create the witness program. func PayToTaprootScript(taprootKey *btcec.PublicKey) ([]byte, error) { - return txscript.ScriptTemplate( - `OP_1 {{ hex .TaprootKey }}`, - txscript.WithScriptTemplateParams(TemplateParams{ - "TaprootKey": schnorr.SerializePubKey(taprootKey), - }), - ) + builder := txscript.NewScriptBuilder() + + builder.AddOp(txscript.OP_1) + builder.AddData(schnorr.SerializePubKey(taprootKey)) + + return builder.Script() } diff --git a/input/taproot_test.go b/input/taproot_test.go index e1eef69d5..3a1e00037 100644 --- a/input/taproot_test.go +++ b/input/taproot_test.go @@ -7,9 +7,9 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lntypes" @@ -35,8 +35,7 @@ type testSenderHtlcScriptTree struct { } func newTestSenderHtlcScriptTree(t *testing.T, - auxLeaf AuxTapLeaf, - opts ...TaprootScriptOpt) *testSenderHtlcScriptTree { + auxLeaf AuxTapLeaf) *testSenderHtlcScriptTree { var preImage lntypes.Preimage _, err := rand.Read(preImage[:]) @@ -52,9 +51,9 @@ func newTestSenderHtlcScriptTree(t *testing.T, require.NoError(t, err) payHash := preImage.Hash() - htlcScriptTree, err := senderHtlcTapScriptTree( + htlcScriptTree, err := SenderHTLCScriptTaproot( senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(), - payHash[:], htlcRemoteIncoming, auxLeaf, opts..., + payHash[:], lntypes.Remote, auxLeaf, ) require.NoError(t, err) @@ -213,11 +212,9 @@ func htlcSenderTimeoutWitnessGen(sigHash txscript.SigHashType, } } -func testTaprootSenderHtlcSpend(t *testing.T, auxLeaf AuxTapLeaf, - opts ...TaprootScriptOpt) { - +func testTaprootSenderHtlcSpend(t *testing.T, auxLeaf AuxTapLeaf) { // First, create a new test script tree. - htlcScriptTree := newTestSenderHtlcScriptTree(t, auxLeaf, opts...) + htlcScriptTree := newTestSenderHtlcScriptTree(t, auxLeaf) spendTx := wire.NewMsgTx(2) spendTx.AddTxIn(&wire.TxIn{}) @@ -388,6 +385,8 @@ func testTaprootSenderHtlcSpend(t *testing.T, auxLeaf AuxTapLeaf, } for i, testCase := range testCases { + i := i + testCase := testCase spendTxCopy := spendTx.Copy() @@ -440,34 +439,17 @@ func TestTaprootSenderHtlcSpend(t *testing.T) { t.Parallel() for _, hasAuxLeaf := range []bool{true, false} { - for _, prodScript := range []bool{false, true} { - name := fmt.Sprintf( - "aux_leaf=%v/prod_script=%v", - hasAuxLeaf, prodScript, - ) - t.Run(name, func(t *testing.T) { - var auxLeaf AuxTapLeaf - if hasAuxLeaf { - leaf := bytes.Repeat( - []byte{0x01}, 32, - ) - auxLeaf = fn.Some( - txscript.NewBaseTapLeaf(leaf), - ) - } + name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf) + t.Run(name, func(t *testing.T) { + var auxLeaf AuxTapLeaf + if hasAuxLeaf { + auxLeaf = fn.Some(txscript.NewBaseTapLeaf( + bytes.Repeat([]byte{0x01}, 32), + )) + } - var opts []TaprootScriptOpt - if prodScript { - opts = append( - opts, WithProdScripts(), - ) - } - - testTaprootSenderHtlcSpend( - t, auxLeaf, opts..., - ) - }) - } + testTaprootSenderHtlcSpend(t, auxLeaf) + }) } } @@ -492,8 +474,7 @@ type testReceiverHtlcScriptTree struct { } func newTestReceiverHtlcScriptTree(t *testing.T, - auxLeaf AuxTapLeaf, - opts ...TaprootScriptOpt) *testReceiverHtlcScriptTree { + auxLeaf AuxTapLeaf) *testReceiverHtlcScriptTree { var preImage lntypes.Preimage _, err := rand.Read(preImage[:]) @@ -511,10 +492,9 @@ func newTestReceiverHtlcScriptTree(t *testing.T, const cltvExpiry = 144 payHash := preImage.Hash() - htlcScriptTree, err := receiverHtlcTapScriptTree( - senderKey.PubKey(), receiverKey.PubKey(), - revokeKey.PubKey(), payHash[:], cltvExpiry, - htlcRemoteOutgoing, auxLeaf, opts..., + htlcScriptTree, err := ReceiverHTLCScriptTaproot( + cltvExpiry, senderKey.PubKey(), receiverKey.PubKey(), + revokeKey.PubKey(), payHash[:], lntypes.Remote, auxLeaf, ) require.NoError(t, err) @@ -672,13 +652,11 @@ func htlcReceiverSuccessWitnessGen(sigHash txscript.SigHashType, } } -func testTaprootReceiverHtlcSpend(t *testing.T, auxLeaf AuxTapLeaf, - opts ...TaprootScriptOpt) { - +func testTaprootReceiverHtlcSpend(t *testing.T, auxLeaf AuxTapLeaf) { // We'll start by creating the HTLC script tree (contains all 3 valid // spend paths), and also a mock spend transaction that we'll be // signing below. - htlcScriptTree := newTestReceiverHtlcScriptTree(t, auxLeaf, opts...) + htlcScriptTree := newTestReceiverHtlcScriptTree(t, auxLeaf) // TODO(roasbeef): issue with revoke key??? ctrl block even/odd @@ -881,6 +859,8 @@ func testTaprootReceiverHtlcSpend(t *testing.T, auxLeaf AuxTapLeaf, }, } for i, testCase := range testCases { + i := i + testCase := testCase spendTxCopy := spendTx.Copy() t.Run(testCase.name, func(t *testing.T) { @@ -936,34 +916,19 @@ func TestTaprootReceiverHtlcSpend(t *testing.T) { t.Parallel() for _, hasAuxLeaf := range []bool{true, false} { - for _, prodScript := range []bool{false, true} { - name := fmt.Sprintf( - "aux_leaf=%v/prod_script=%v", - hasAuxLeaf, prodScript, - ) - t.Run(name, func(t *testing.T) { - var auxLeaf AuxTapLeaf - if hasAuxLeaf { - leaf := bytes.Repeat( - []byte{0x01}, 32, - ) - auxLeaf = fn.Some( - txscript.NewBaseTapLeaf(leaf), - ) - } - - var opts []TaprootScriptOpt - if prodScript { - opts = append( - opts, WithProdScripts(), - ) - } - - testTaprootReceiverHtlcSpend( - t, auxLeaf, opts..., + name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf) + t.Run(name, func(t *testing.T) { + var auxLeaf AuxTapLeaf + if hasAuxLeaf { + auxLeaf = fn.Some( + txscript.NewBaseTapLeaf( + bytes.Repeat([]byte{0x01}, 32), + ), ) - }) - } + } + + testTaprootReceiverHtlcSpend(t, auxLeaf) + }) } } @@ -982,8 +947,7 @@ type testCommitScriptTree struct { } func newTestCommitScriptTree(local bool, - auxLeaf AuxTapLeaf, - opts ...TaprootScriptOpt) (*testCommitScriptTree, error) { + auxLeaf AuxTapLeaf) (*testCommitScriptTree, error) { selfKey, err := btcec.NewPrivateKey() if err != nil { @@ -1004,11 +968,11 @@ func newTestCommitScriptTree(local bool, if local { commitScriptTree, err = NewLocalCommitScriptTree( csvDelay, selfKey.PubKey(), revokeKey.PubKey(), - auxLeaf, opts..., + auxLeaf, ) } else { commitScriptTree, err = NewRemoteCommitScriptTree( - selfKey.PubKey(), auxLeaf, opts..., + selfKey.PubKey(), auxLeaf, ) } if err != nil { @@ -1100,12 +1064,8 @@ func localCommitRevokeWitGen(sigHash txscript.SigHashType, } } -func testTaprootCommitScriptToSelf(t *testing.T, auxLeaf AuxTapLeaf, - opts ...TaprootScriptOpt) { - - commitScriptTree, err := newTestCommitScriptTree( - true, auxLeaf, opts..., - ) +func testTaprootCommitScriptToSelf(t *testing.T, auxLeaf AuxTapLeaf) { + commitScriptTree, err := newTestCommitScriptTree(true, auxLeaf) require.NoError(t, err) spendTx := wire.NewMsgTx(2) @@ -1221,6 +1181,8 @@ func testTaprootCommitScriptToSelf(t *testing.T, auxLeaf AuxTapLeaf, } for i, testCase := range testCases { + i := i + testCase := testCase spendTxCopy := spendTx.Copy() t.Run(testCase.name, func(t *testing.T) { @@ -1271,34 +1233,17 @@ func TestTaprootCommitScriptToSelf(t *testing.T) { t.Parallel() for _, hasAuxLeaf := range []bool{true, false} { - for _, prodScript := range []bool{false, true} { - name := fmt.Sprintf( - "aux_leaf=%v/prod_script=%v", - hasAuxLeaf, prodScript, - ) - t.Run(name, func(t *testing.T) { - var auxLeaf AuxTapLeaf - if hasAuxLeaf { - leaf := bytes.Repeat( - []byte{0x01}, 32, - ) - auxLeaf = fn.Some( - txscript.NewBaseTapLeaf(leaf), - ) - } + name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf) + t.Run(name, func(t *testing.T) { + var auxLeaf AuxTapLeaf + if hasAuxLeaf { + auxLeaf = fn.Some(txscript.NewBaseTapLeaf( + bytes.Repeat([]byte{0x01}, 32), + )) + } - var opts []TaprootScriptOpt - if prodScript { - opts = append( - opts, WithProdScripts(), - ) - } - - testTaprootCommitScriptToSelf( - t, auxLeaf, opts..., - ) - }) - } + testTaprootCommitScriptToSelf(t, auxLeaf) + }) } } @@ -1335,12 +1280,8 @@ func remoteCommitSweepWitGen(sigHash txscript.SigHashType, } } -func testTaprootCommitScriptRemote(t *testing.T, auxLeaf AuxTapLeaf, - opts ...TaprootScriptOpt) { - - commitScriptTree, err := newTestCommitScriptTree( - false, auxLeaf, opts..., - ) +func testTaprootCommitScriptRemote(t *testing.T, auxLeaf AuxTapLeaf) { + commitScriptTree, err := newTestCommitScriptTree(false, auxLeaf) require.NoError(t, err) spendTx := wire.NewMsgTx(2) @@ -1433,6 +1374,8 @@ func testTaprootCommitScriptRemote(t *testing.T, auxLeaf AuxTapLeaf, } for i, testCase := range testCases { + i := i + testCase := testCase spendTxCopy := spendTx.Copy() t.Run(testCase.name, func(t *testing.T) { @@ -1483,34 +1426,17 @@ func TestTaprootCommitScriptRemote(t *testing.T) { t.Parallel() for _, hasAuxLeaf := range []bool{true, false} { - for _, prodScript := range []bool{false, true} { - name := fmt.Sprintf( - "aux_leaf=%v/prod_script=%v", - hasAuxLeaf, prodScript, - ) - t.Run(name, func(t *testing.T) { - var auxLeaf AuxTapLeaf - if hasAuxLeaf { - leaf := bytes.Repeat( - []byte{0x01}, 32, - ) - auxLeaf = fn.Some( - txscript.NewBaseTapLeaf(leaf), - ) - } + name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf) + t.Run(name, func(t *testing.T) { + var auxLeaf AuxTapLeaf + if hasAuxLeaf { + auxLeaf = fn.Some(txscript.NewBaseTapLeaf( + bytes.Repeat([]byte{0x01}, 32), + )) + } - var opts []TaprootScriptOpt - if prodScript { - opts = append( - opts, WithProdScripts(), - ) - } - - testTaprootCommitScriptRemote( - t, auxLeaf, opts..., - ) - }) - } + testTaprootCommitScriptRemote(t, auxLeaf) + }) } } @@ -1687,6 +1613,8 @@ func TestTaprootAnchorScript(t *testing.T) { } for i, testCase := range testCases { + i := i + testCase := testCase spendTxCopy := spendTx.Copy() t.Run(testCase.name, func(t *testing.T) { @@ -1748,8 +1676,7 @@ type testSecondLevelHtlcTree struct { } func newTestSecondLevelHtlcTree(t *testing.T, - auxLeaf AuxTapLeaf, - opts ...TaprootScriptOpt) *testSecondLevelHtlcTree { + auxLeaf AuxTapLeaf) *testSecondLevelHtlcTree { delayKey, err := btcec.NewPrivateKey() require.NoError(t, err) @@ -1760,7 +1687,7 @@ func newTestSecondLevelHtlcTree(t *testing.T, const csvDelay = 6 scriptTree, err := SecondLevelHtlcTapscriptTree( - delayKey.PubKey(), csvDelay, auxLeaf, opts..., + delayKey.PubKey(), csvDelay, auxLeaf, ) require.NoError(t, err) @@ -1856,10 +1783,8 @@ func secondLevelHtlcRevokeWitnessgen(sigHash txscript.SigHashType, } } -func testTaprootSecondLevelHtlcScript(t *testing.T, auxLeaf AuxTapLeaf, - opts ...TaprootScriptOpt) { - - htlcScriptTree := newTestSecondLevelHtlcTree(t, auxLeaf, opts...) +func testTaprootSecondLevelHtlcScript(t *testing.T, auxLeaf AuxTapLeaf) { + htlcScriptTree := newTestSecondLevelHtlcTree(t, auxLeaf) spendTx := wire.NewMsgTx(2) spendTx.AddTxIn(&wire.TxIn{}) @@ -1974,6 +1899,8 @@ func testTaprootSecondLevelHtlcScript(t *testing.T, auxLeaf AuxTapLeaf, } for i, testCase := range testCases { + i := i + testCase := testCase spendTxCopy := spendTx.Copy() t.Run(testCase.name, func(t *testing.T) { @@ -2024,33 +1951,16 @@ func TestTaprootSecondLevelHtlcScript(t *testing.T) { t.Parallel() for _, hasAuxLeaf := range []bool{true, false} { - for _, prodScript := range []bool{false, true} { - name := fmt.Sprintf( - "aux_leaf=%v/prod_script=%v", - hasAuxLeaf, prodScript, - ) - t.Run(name, func(t *testing.T) { - var auxLeaf AuxTapLeaf - if hasAuxLeaf { - leaf := bytes.Repeat( - []byte{0x01}, 32, - ) - auxLeaf = fn.Some( - txscript.NewBaseTapLeaf(leaf), - ) - } + name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf) + t.Run(name, func(t *testing.T) { + var auxLeaf AuxTapLeaf + if hasAuxLeaf { + auxLeaf = fn.Some(txscript.NewBaseTapLeaf( + bytes.Repeat([]byte{0x01}, 32), + )) + } - var opts []TaprootScriptOpt - if prodScript { - opts = append( - opts, WithProdScripts(), - ) - } - - testTaprootSecondLevelHtlcScript( - t, auxLeaf, opts..., - ) - }) - } + testTaprootSecondLevelHtlcScript(t, auxLeaf) + }) } } diff --git a/input/test_utils.go b/input/test_utils.go index 35e8bd2f4..682dfdcd4 100644 --- a/input/test_utils.go +++ b/input/test_utils.go @@ -5,14 +5,14 @@ import ( "encoding/hex" "fmt" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/keychain" ) @@ -84,7 +84,7 @@ func (m *MockSigner) SignOutputRaw(tx *wire.MsgTx, pubkey = DeriveRevocationPubkey(pubkey, signDesc.DoubleTweak.PubKey()) } - hash160 := address.Hash160(pubkey.SerializeCompressed()) + hash160 := btcutil.Hash160(pubkey.SerializeCompressed()) privKey := m.findKey(hash160, signDesc.SingleTweak, signDesc.DoubleTweak) if privKey == nil { return nil, fmt.Errorf("mock signer does not have key") @@ -223,9 +223,7 @@ func (m *MockSigner) findKey(needleHash160 []byte, singleTweak []byte, for _, privkey := range m.Privkeys { // First check whether public key is directly derived from // private key. - hash160 := address.Hash160( - privkey.PubKey().SerializeCompressed(), - ) + hash160 := btcutil.Hash160(privkey.PubKey().SerializeCompressed()) if bytes.Equal(hash160, needleHash160) { return privkey } @@ -240,9 +238,7 @@ func (m *MockSigner) findKey(needleHash160 []byte, singleTweak []byte, default: continue } - hash160 = address.Hash160( - privkey.PubKey().SerializeCompressed(), - ) + hash160 = btcutil.Hash160(privkey.PubKey().SerializeCompressed()) if bytes.Equal(hash160, needleHash160) { return privkey } diff --git a/input/txout.go b/input/txout.go index 1322e356c..381c6be97 100644 --- a/input/txout.go +++ b/input/txout.go @@ -4,7 +4,7 @@ import ( "encoding/binary" "io" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" ) // writeTxOut serializes a wire.TxOut struct into the passed io.Writer stream. diff --git a/input/txout_test.go b/input/txout_test.go index 9e98e739b..5587f1926 100644 --- a/input/txout_test.go +++ b/input/txout_test.go @@ -5,7 +5,7 @@ import ( "reflect" "testing" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" ) func TestTxOutSerialization(t *testing.T) { diff --git a/input/witnessgen.go b/input/witnessgen.go index 612cdc36c..c49328afc 100644 --- a/input/witnessgen.go +++ b/input/witnessgen.go @@ -3,8 +3,8 @@ package input import ( "fmt" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lntypes" ) @@ -255,53 +255,6 @@ const ( // settled output of a malicious counterparty's who broadcasts a // revoked taproot commitment transaction. TaprootCommitmentRevoke StandardWitnessType = 34 - - // TaprootLocalCommitSpendFinal is a witness type that - // allows us to spend our settled local commitment after - // a CSV delay when we force close a final taproot - // channel (using production scripts). - TaprootLocalCommitSpendFinal StandardWitnessType = 35 - - // TaprootRemoteCommitSpendFinal is a witness type that - // allows us to spend our settled remote commitment - // after a CSV delay when the remote party has force - // closed a final taproot channel (using production - // scripts). - TaprootRemoteCommitSpendFinal StandardWitnessType = 36 - - // TaprootHtlcOfferedTimeoutSecondLevelFinal is a - // witness that allows us to timeout an HTLC we offered - // to the remote party on our commitment transaction - // for final taproot channels (using production - // scripts). - TaprootHtlcOfferedTimeoutSecondLevelFinal StandardWitnessType = 37 - - // TaprootHtlcAcceptedSuccessSecondLevelFinal is a - // witness that allows us to sweep an HTLC we accepted - // on our commitment transaction after we go to the - // second level on chain for final taproot channels - // (using production scripts). - TaprootHtlcAcceptedSuccessSecondLevelFinal StandardWitnessType = 38 - - // TaprootHtlcOfferedRemoteTimeoutFinal is a witness - // that allows us to sweep an HTLC we offered to the - // remote party that lies on the commitment transaction - // for the remote party for final taproot channels - // (using production scripts). - TaprootHtlcOfferedRemoteTimeoutFinal StandardWitnessType = 39 - - // TaprootHtlcAcceptedRemoteSuccessFinal is a witness - // that allows us to sweep an HTLC that was offered to - // us by the remote party for final taproot channels - // (using production scripts). - TaprootHtlcAcceptedRemoteSuccessFinal StandardWitnessType = 40 - - // TaprootCommitmentRevokeFinal is a witness that - // allows us to sweep the settled output of a malicious - // counterparty's who broadcasts a revoked final - // taproot commitment transaction (using production - // scripts). - TaprootCommitmentRevokeFinal StandardWitnessType = 41 ) // String returns a human readable version of the target WitnessType. @@ -414,27 +367,6 @@ func (wt StandardWitnessType) String() string { case TaprootCommitmentRevoke: return "TaprootCommitmentRevoke" - case TaprootLocalCommitSpendFinal: - return "TaprootLocalCommitSpendFinal" - - case TaprootRemoteCommitSpendFinal: - return "TaprootRemoteCommitSpendFinal" - - case TaprootHtlcOfferedTimeoutSecondLevelFinal: - return "TaprootHtlcOfferedTimeoutSecondLevelFinal" - - case TaprootHtlcAcceptedSuccessSecondLevelFinal: - return "TaprootHtlcAcceptedSuccessSecondLevelFinal" - - case TaprootHtlcOfferedRemoteTimeoutFinal: - return "TaprootHtlcOfferedRemoteTimeoutFinal" - - case TaprootHtlcAcceptedRemoteSuccessFinal: - return "TaprootHtlcAcceptedRemoteSuccessFinal" - - case TaprootCommitmentRevokeFinal: - return "TaprootCommitmentRevokeFinal" - default: return fmt.Sprintf("Unknown WitnessType: %v", uint32(wt)) } @@ -584,16 +516,9 @@ func (wt StandardWitnessType) WitnessGenerator(signer Signer, case NestedWitnessKeyHash: return signer.ComputeInputScript(tx, desc) - case TaprootLocalCommitSpend, TaprootLocalCommitSpendFinal: - // Production (Final) taproot witness types share the - // same generation logic as their staging counterparts. - // The script differences between staging and final - // are captured in the SignDescriptor's script tree - // (ControlBlock, TapLeaf, etc.) at commitment - // construction time, so the witness generator here - // simply signs against whatever scripts were - // pre-populated — no channel-type awareness is needed - // at this layer. + case TaprootLocalCommitSpend: + // Ensure that the sign desc has the proper sign method + // set, and a valid prev output fetcher. desc.SignMethod = TaprootScriptSpendSignMethod // The control block bytes must be set at this point. @@ -613,7 +538,7 @@ func (wt StandardWitnessType) WitnessGenerator(signer Signer, Witness: witness, }, nil - case TaprootRemoteCommitSpend, TaprootRemoteCommitSpendFinal: + case TaprootRemoteCommitSpend: // Ensure that the sign desc has the proper sign method // set, and a valid prev output fetcher. desc.SignMethod = TaprootScriptSpendSignMethod @@ -658,9 +583,7 @@ func (wt StandardWitnessType) WitnessGenerator(signer Signer, }, nil case TaprootHtlcOfferedTimeoutSecondLevel, - TaprootHtlcAcceptedSuccessSecondLevel, - TaprootHtlcOfferedTimeoutSecondLevelFinal, - TaprootHtlcAcceptedSuccessSecondLevelFinal: + TaprootHtlcAcceptedSuccessSecondLevel: // Ensure that the sign desc has the proper sign method // set, and a valid prev output fetcher. desc.SignMethod = TaprootScriptSpendSignMethod @@ -748,8 +671,7 @@ func (wt StandardWitnessType) WitnessGenerator(signer Signer, Witness: witness, }, nil - case TaprootHtlcOfferedRemoteTimeout, - TaprootHtlcOfferedRemoteTimeoutFinal: + case TaprootHtlcOfferedRemoteTimeout: // Ensure that the sign desc has the proper sign method // set, and a valid prev output fetcher. desc.SignMethod = TaprootScriptSpendSignMethod @@ -771,7 +693,7 @@ func (wt StandardWitnessType) WitnessGenerator(signer Signer, Witness: witness, }, nil - case TaprootCommitmentRevoke, TaprootCommitmentRevokeFinal: + case TaprootCommitmentRevoke: // Ensure that the sign desc has the proper sign method // set, and a valid prev output fetcher. desc.SignMethod = TaprootScriptSpendSignMethod @@ -793,26 +715,6 @@ func (wt StandardWitnessType) WitnessGenerator(signer Signer, Witness: witness, }, nil - case TaprootHtlcAcceptedRemoteSuccess, - TaprootHtlcAcceptedRemoteSuccessFinal: - desc.SignMethod = TaprootScriptSpendSignMethod - - if desc.ControlBlock == nil { - return nil, fmt.Errorf("control block " + - "must be set for taproot spend") - } - - witness, err := SenderHTLCScriptTaprootRedeem( - signer, desc, tx, nil, nil, nil, - ) - if err != nil { - return nil, err - } - - return &Script{ - Witness: witness, - }, nil - default: return nil, fmt.Errorf("unknown witness type: %v", wt) } @@ -930,17 +832,11 @@ func (wt StandardWitnessType) SizeUpperBound() (lntypes.WeightUnit, case TaprootLocalCommitSpend: return TaprootToLocalWitnessSize, false, nil - case TaprootLocalCommitSpendFinal: - return TaprootToLocalWitnessSizeFinal, false, nil - - // Sweeping a self output after the remote party force closes. Must + // Sweeping a self output after the remote party fro ce closes. Must // wait 1 CSV. case TaprootRemoteCommitSpend: return TaprootToRemoteWitnessSize, false, nil - case TaprootRemoteCommitSpendFinal: - return TaprootToRemoteWitnessSizeFinal, false, nil - // Sweeping our anchor output with a key spend witness. case TaprootAnchorSweepSpend: return TaprootAnchorWitnessSize, false, nil @@ -950,11 +846,6 @@ func (wt StandardWitnessType) SizeUpperBound() (lntypes.WeightUnit, return TaprootSecondLevelHtlcWitnessSize, false, nil - case TaprootHtlcOfferedTimeoutSecondLevelFinal, - TaprootHtlcAcceptedSuccessSecondLevelFinal: - - return TaprootSecondLevelHtlcWitnessSizeFinal, false, nil - case TaprootHtlcSecondLevelRevoke: return TaprootSecondLevelRevokeWitnessSize, false, nil @@ -967,24 +858,16 @@ func (wt StandardWitnessType) SizeUpperBound() (lntypes.WeightUnit, case TaprootHtlcOfferedRemoteTimeout: return TaprootHtlcOfferedRemoteTimeoutWitnessSize, false, nil - case TaprootHtlcOfferedRemoteTimeoutFinal: - return TaprootHtlcOfferedRemoteTimeoutWitnessSizeFinal, false, - nil - case TaprootHtlcLocalOfferedTimeout: return TaprootOfferedLocalTimeoutWitnessSize, false, nil case TaprootHtlcAcceptedRemoteSuccess: return TaprootHtlcAcceptedRemoteSuccessWitnessSize, false, nil - case TaprootHtlcAcceptedRemoteSuccessFinal: - return TaprootHtlcAcceptedRemoteSuccessWitnessSizeFinal, false, - nil - case TaprootHtlcAcceptedLocalSuccess: return TaprootHtlcAcceptedLocalSuccessWitnessSize, false, nil - case TaprootCommitmentRevoke, TaprootCommitmentRevokeFinal: + case TaprootCommitmentRevoke: return TaprootToLocalRevokeWitnessSize, false, nil } diff --git a/internal/musig2v040/README.md b/internal/musig2v040/README.md index c75f2bb17..bd248d267 100644 --- a/internal/musig2v040/README.md +++ b/internal/musig2v040/README.md @@ -8,17 +8,3 @@ This corresponds to the [MuSig2 BIP specification version of We only keep this code here to allow implementing a backward compatible, versioned MuSig2 RPC. - -## Unsupported Methods - -The following methods from the newer MuSig2 specifications are not supported in -this legacy v0.4.0 implementation and will return `ErrUnsupportedMethod` if -called: - -- `CombinedNonce()`: Returns error instead of the combined nonce. -- `RegisterCombinedNonce()`: Returns error instead of registering a - pre-aggregated combined nonce. - -These methods are only available when using MuSig2 v1.0.0rc2 or later. To use -these features, create sessions with `MuSig2Version100RC2` instead of -`MuSig2Version040`. diff --git a/internal/musig2v040/context.go b/internal/musig2v040/context.go index 96eeeca72..fe41c4250 100644 --- a/internal/musig2v040/context.go +++ b/internal/musig2v040/context.go @@ -59,11 +59,6 @@ var ( // ErrNotEnoughSigners is returned if a caller attempts to obtain an // early nonce when it wasn't specified ErrNoEarlyNonce = fmt.Errorf("no early nonce available") - - // ErrUnsupportedMethod is returned when calling methods that are not - // supported in the legacy v0.4.0 implementation. - ErrUnsupportedMethod = fmt.Errorf("method not supported in MuSig2 " + - "v0.4.0") ) // Context is a managed signing context for musig2. It takes care of things @@ -673,15 +668,3 @@ func (s *Session) CombineSig(sig *PartialSignature) (bool, error) { func (s *Session) FinalSig() *schnorr.Signature { return s.finalSig } - -// CombinedNonce is not supported in the legacy v0.4.0 implementation and will -// always return an error. -func (s *Session) CombinedNonce() ([PubNonceSize]byte, error) { - return [PubNonceSize]byte{}, ErrUnsupportedMethod -} - -// RegisterCombinedNonce is not supported in the legacy v0.4.0 implementation -// and will always return an error. -func (s *Session) RegisterCombinedNonce(_ [PubNonceSize]byte) error { - return ErrUnsupportedMethod -} diff --git a/internal/musig2v040/keys.go b/internal/musig2v040/keys.go index 96a448744..9016f0b56 100644 --- a/internal/musig2v040/keys.go +++ b/internal/musig2v040/keys.go @@ -9,7 +9,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" secp "github.com/decred/dcrd/dcrec/secp256k1/v4" ) diff --git a/internal/musig2v040/musig2_test.go b/internal/musig2v040/musig2_test.go index fa2e1bc5c..42e84c781 100644 --- a/internal/musig2v040/musig2_test.go +++ b/internal/musig2v040/musig2_test.go @@ -16,7 +16,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/decred/dcrd/dcrec/secp256k1/v4" ) @@ -1048,6 +1048,7 @@ func testMultiPartySign(t *testing.T, taprootTweak []byte, // signer. var wg sync.WaitGroup for i, signCtx := range signers { + signCtx := signCtx wg.Add(1) go func(idx int, signer *Session) { diff --git a/internal/musig2v040/nonces.go b/internal/musig2v040/nonces.go index 2e13d6417..86ca68318 100644 --- a/internal/musig2v040/nonces.go +++ b/internal/musig2v040/nonces.go @@ -10,7 +10,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" ) const ( diff --git a/internal/musig2v040/sign.go b/internal/musig2v040/sign.go index ad070bf31..b1236c3a9 100644 --- a/internal/musig2v040/sign.go +++ b/internal/musig2v040/sign.go @@ -9,7 +9,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" secp "github.com/decred/dcrd/dcrec/secp256k1/v4" ) diff --git a/invoices/interface.go b/invoices/interface.go index b963b3973..567df454c 100644 --- a/invoices/interface.go +++ b/invoices/interface.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" diff --git a/invoices/invoice_expiry_watcher.go b/invoices/invoice_expiry_watcher.go index 8549dded8..d7659dce3 100644 --- a/invoices/invoice_expiry_watcher.go +++ b/invoices/invoice_expiry_watcher.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/lntypes" diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index 8ad92e1ca..8815bb374 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -196,6 +196,7 @@ func (i *InvoiceRegistry) scanInvoicesOnStart(ctx context.Context) error { var pending []invoiceExpiry for paymentHash, invoice := range pendingInvoices { + invoice := invoice expiryRef := makeInvoiceExpiry(paymentHash, &invoice) if expiryRef != nil { pending = append(pending, expiryRef) @@ -508,6 +509,7 @@ func (i *InvoiceRegistry) deliverBacklogEvents(ctx context.Context, for _, addEvent := range addEvents { // We re-bind the loop variable to ensure we don't hold onto // the loop reference causing is to point to the same item. + addEvent := addEvent select { case client.ntfnQueue.ChanIn() <- &invoiceEvent{ @@ -521,6 +523,7 @@ func (i *InvoiceRegistry) deliverBacklogEvents(ctx context.Context, for _, settleEvent := range settleEvents { // We re-bind the loop variable to ensure we don't hold onto // the loop reference causing is to point to the same item. + settleEvent := settleEvent select { case client.ntfnQueue.ChanIn() <- &invoiceEvent{ @@ -1578,7 +1581,7 @@ func (i *InvoiceRegistry) notifyClients(hash lntypes.Hash, // invoiceSubscriptionKit defines that are common to both all invoice // subscribers and single invoice subscribers. type invoiceSubscriptionKit struct { - id uint32 + id uint32 // nolint:structcheck // quit is a chan mouted to InvoiceRegistry that signals a shutdown. quit chan struct{} diff --git a/invoices/invoiceregistry_test.go b/invoices/invoiceregistry_test.go index f3026b281..5e13f8735 100644 --- a/invoices/invoiceregistry_test.go +++ b/invoices/invoiceregistry_test.go @@ -98,10 +98,6 @@ func TestInvoiceRegistry(t *testing.T) { name: "AMPWithoutMPPPayload", test: testAMPWithoutMPPPayload, }, - { - name: "AMPWithoutMPPExistingInvoice", - test: testAMPWithoutMPPExistingInvoice, - }, { name: "SpontaneousAmpPayment", test: testSpontaneousAmpPayment, @@ -163,6 +159,7 @@ func TestInvoiceRegistry(t *testing.T) { } for _, test := range testList { + test := test t.Run(test.name+"_KV", func(t *testing.T) { test.test(t, makeKeyValueDB) @@ -1881,46 +1878,6 @@ func testAMPWithoutMPPPayload(t *testing.T, checkFailResolution(t, resolution, invpkg.ResultAmpError) } -// testAMPWithoutMPPExistingInvoice checks AMP handling for an existing invoice -// when spontaneous AMP payments are disabled. -func testAMPWithoutMPPExistingInvoice(t *testing.T, - makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) { - - t.Parallel() - defer timeout()() - - cfg := defaultRegistryConfig() - cfg.AcceptAMP = false - ctx := newTestContext(t, &cfg, makeDB) - ctxb := t.Context() - - invoice := newInvoice(t, false, true) - _, err := ctx.registry.AddInvoice( - ctxb, invoice, testInvoicePaymentHash, - ) - require.NoError(t, err) - - payload := &mockPayload{ - amp: record.NewAMP([32]byte{}, [32]byte{}, 0), - } - - hodlChan := make(chan interface{}, 1) - resolution, err := ctx.registry.NotifyExitHopHtlc( - testInvoicePaymentHash, invoice.Terms.Value, testHtlcExpiry, - testCurrentHeight, getCircuitKey(10), hodlChan, nil, payload, - ) - require.NoError(t, err) - require.NotNil(t, resolution) - checkFailResolution(t, resolution, invpkg.ResultAmpError) - - storedInvoice, err := ctx.registry.LookupInvoice( - ctxb, testInvoicePaymentHash, - ) - require.NoError(t, err) - require.Equal(t, invpkg.ContractOpen, storedInvoice.State) - require.Empty(t, storedInvoice.Htlcs) -} - // testSpontaneousAmpPayment tests receiving a spontaneous AMP payment with both // valid and invalid reconstructions. func testSpontaneousAmpPayment(t *testing.T, @@ -1967,6 +1924,7 @@ func testSpontaneousAmpPayment(t *testing.T, } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { testSpontaneousAmpPaymentImpl( t, test.ampEnabled, test.failReconstruction, @@ -2364,7 +2322,7 @@ func testFailPartialAMPPayment(t *testing.T, "expected MPPTimeout, got: %v", failRes.Outcome, ) - case <-time.After(testTimeoutLong): + case <-time.After(testTimeout): t.Fatal("timeout waiting for HTLC resolution") } @@ -2381,7 +2339,7 @@ func testFailPartialAMPPayment(t *testing.T, "expected MPPTimeout, got: %v", failRes.Outcome, ) - case <-time.After(testTimeoutLong): + case <-time.After(testTimeout): t.Fatal("timeout waiting for HTLC resolution") } @@ -2450,7 +2408,7 @@ func testFailPartialAMPPayment(t *testing.T, "expected MPPTimeout, got: %v", failRes.Outcome, ) - case <-time.After(testTimeoutLong): + case <-time.After(testTimeout): t.Fatal("timeout waiting for HTLC resolution") } diff --git a/invoices/invoices_test.go b/invoices/invoices_test.go index 5cfb76c58..e4e41423e 100644 --- a/invoices/invoices_test.go +++ b/invoices/invoices_test.go @@ -166,10 +166,6 @@ func TestInvoices(t *testing.T) { name: "FetchPendingInvoices", test: testFetchPendingInvoices, }, - { - name: "FetchPendingInvoicesAccepted", - test: testFetchPendingInvoicesAccepted, - }, { name: "DuplicateSettleInvoice", test: testDuplicateSettleInvoice, @@ -261,6 +257,7 @@ func TestInvoices(t *testing.T) { } for _, test := range testList { + test := test t.Run(test.name+"_KV", func(t *testing.T) { test.test(t, makeKeyValueDB) }) @@ -339,6 +336,7 @@ func testInvoiceWorkflow(t *testing.T, t.Parallel() for _, test := range invWorkflowTests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() testInvoiceWorkflowImpl(t, test, makeDB) @@ -1290,115 +1288,6 @@ func testFetchPendingInvoices(t *testing.T, require.Equal(t, pendingInvoices, pending) } -// testFetchPendingInvoicesAccepted verifies that FetchPendingInvoices returns -// invoices in both ContractOpen (state 0) and ContractAccepted (state 3) -// states, and that ContractSettled (state 1) and ContractCanceled (state 2) -// invoices are excluded. This specifically exercises the `state IN (0, 3)` -// predicate in the underlying SQL query. -func testFetchPendingInvoicesAccepted(t *testing.T, - makeDB func(t *testing.T) invpkg.InvoiceDB) { - - t.Parallel() - db := makeDB(t) - ctxb := t.Context() - - amt := lnwire.MilliSatoshi(1000) - - // Add an invoice that stays in ContractOpen state. - openInvoice, err := randInvoice(amt) - require.NoError(t, err) - openHash := openInvoice.Terms.PaymentPreimage.Hash() - _, err = db.AddInvoice(ctxb, openInvoice, openHash) - require.NoError(t, err) - - // Add a second invoice and transition it to ContractAccepted by - // adding an HTLC while setting the new invoice state in a single - // UpdateInvoice call (addHTLCs processes the HTLC list before - // validating the state transition, so the empty-set check passes). - acceptedInvoice, err := randInvoice(amt) - require.NoError(t, err) - acceptedHash := acceptedInvoice.Terms.PaymentPreimage.Hash() - _, err = db.AddInvoice(ctxb, acceptedInvoice, acceptedHash) - require.NoError(t, err) - - acceptKey := models.CircuitKey{HtlcID: 1} - acceptRef := invpkg.InvoiceRefByHash(acceptedHash) - addHtlcs := map[models.CircuitKey]*invpkg.HtlcAcceptDesc{ - acceptKey: { - Amt: amt, - CustomRecords: make( - record.CustomSet, - ), - }, - } - dbAccepted, err := db.UpdateInvoice( - ctxb, acceptRef, nil, - func(inv *invpkg.Invoice) (*invpkg.InvoiceUpdateDesc, error) { - return &invpkg.InvoiceUpdateDesc{ - UpdateType: invpkg.AddHTLCsUpdate, - State: &invpkg.InvoiceStateUpdateDesc{ - NewState: invpkg.ContractAccepted, - }, - AddHtlcs: addHtlcs, - }, nil - }, - ) - require.NoError(t, err) - require.Equal(t, invpkg.ContractAccepted, dbAccepted.State) - - // Add a settled invoice – it must NOT appear in the pending result. - settledInvoice, err := randInvoice(amt) - require.NoError(t, err) - settledHash := settledInvoice.Terms.PaymentPreimage.Hash() - _, err = db.AddInvoice(ctxb, settledInvoice, settledHash) - require.NoError(t, err) - _, err = db.UpdateInvoice( - ctxb, invpkg.InvoiceRefByHash(settledHash), nil, - getUpdateInvoice(2, amt), - ) - require.NoError(t, err) - - // Add a canceled invoice – it must also NOT appear in the pending - // result, verifying that state 2 (ContractCanceled) is excluded by - // the `state IN (0, 3)` SQL predicate. - canceledInvoice, err := randInvoice(amt) - require.NoError(t, err) - canceledHash := canceledInvoice.Terms.PaymentPreimage.Hash() - _, err = db.AddInvoice(ctxb, canceledInvoice, canceledHash) - require.NoError(t, err) - _, err = db.UpdateInvoice( - ctxb, invpkg.InvoiceRefByHash(canceledHash), nil, - func(inv *invpkg.Invoice) (*invpkg.InvoiceUpdateDesc, error) { - return &invpkg.InvoiceUpdateDesc{ - UpdateType: invpkg.CancelInvoiceUpdate, - State: &invpkg.InvoiceStateUpdateDesc{ - NewState: invpkg.ContractCanceled, - }, - }, nil - }, - ) - require.NoError(t, err) - - // FetchPendingInvoices must return exactly the two pending invoices. - pending, err := db.FetchPendingInvoices(ctxb) - require.NoError(t, err) - require.Len(t, pending, 2) - - _, hasOpen := pending[openHash] - require.True(t, hasOpen, "ContractOpen invoice missing from results") - - _, hasAccepted := pending[acceptedHash] - require.True(t, hasAccepted, "ContractAccepted invoice missing") - - require.NotContains(t, pending, settledHash, - "ContractSettled invoice should not appear in pending results") - require.NotContains(t, pending, canceledHash, - "ContractCanceled invoice should not appear in pending results") - - require.Equal(t, invpkg.ContractOpen, pending[openHash].State) - require.Equal(t, invpkg.ContractAccepted, pending[acceptedHash].State) -} - // testDuplicateSettleInvoice tests that if we add a new invoice and settle it // twice, then the second time we also receive the invoice that we settled as a // return argument. @@ -2616,6 +2505,7 @@ func testUpdateHTLCPreimages(t *testing.T, } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() testUpdateHTLCPreimagesImpl(t, test, makeDB) @@ -2813,7 +2703,7 @@ func testDeleteCanceledInvoices(t *testing.T, // Cancel every second invoice. if i%2 == 0 { - _, err = db.UpdateInvoice( + invoice, err = db.UpdateInvoice( ctxb, invpkg.InvoiceRefByHash(paymentHash), nil, updateFunc, ) diff --git a/invoices/kv_sql_migration_test.go b/invoices/kv_sql_migration_test.go index d14a63f58..709a3c8e1 100644 --- a/invoices/kv_sql_migration_test.go +++ b/invoices/kv_sql_migration_test.go @@ -134,6 +134,7 @@ func TestMigrationWithChannelDB(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { var kvStore *channeldb.DB diff --git a/invoices/sql_store.go b/invoices/sql_store.go index 3d377cb8e..ff718ba1e 100644 --- a/invoices/sql_store.go +++ b/invoices/sql_store.go @@ -1,7 +1,6 @@ package invoices import ( - "bytes" "context" "crypto/sha256" "database/sql" @@ -14,6 +13,7 @@ import ( "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/sqldb" @@ -30,24 +30,6 @@ const ( invoiceProgressLogInterval = 30 * time.Second ) -var ( - // invoiceCreatedAfterDefault is the lower-bound sentinel for the - // created_at timestamp filter used by FilterInvoicesForward and - // FilterInvoicesReverse. time.Unix(0, 0) precedes any real invoice - // creation date, so passing this value tells the planner "no lower - // bound" while still providing a concrete, non-nullable parameter. - invoiceCreatedAfterDefault = time.Unix(0, 0).UTC() - - // invoiceCreatedBeforeDefault is the upper-bound sentinel for the - // created_at timestamp filter. Year 9999 lies far beyond any - // foreseeable invoice creation date, so passing this value tells the - // planner "no upper bound" while still keeping the parameter - // non-nullable. - invoiceCreatedBeforeDefault = time.Date( - 9999, 12, 31, 23, 59, 59, 0, time.UTC, - ) -) - // SQLInvoiceQueries is an interface that defines the set of operations that can // be executed against the invoice SQL database. type SQLInvoiceQueries interface { //nolint:interfacebloat @@ -67,45 +49,15 @@ type SQLInvoiceQueries interface { //nolint:interfacebloat InsertInvoiceHTLCCustomRecord(ctx context.Context, arg sqlc.InsertInvoiceHTLCCustomRecordParams) error - // FetchPendingInvoices returns all open/accepted invoices ordered by - // id ascending. It replaces the old catch-all FilterInvoices for the - // pending-only path and lets the planner use invoices_state_idx. - FetchPendingInvoices(ctx context.Context, - arg sqlc.FetchPendingInvoicesParams) ([]sqlc.Invoice, error) + FilterInvoices(ctx context.Context, + arg sqlc.FilterInvoicesParams) ([]sqlc.Invoice, error) - // FilterInvoicesBySettleIndex returns settled invoices whose - // settle_index is >= the given bound, ordered by id ascending. The - // caller must always supply a concrete lower bound so the planner can - // use invoices_settle_index_idx. - FilterInvoicesBySettleIndex(ctx context.Context, - arg sqlc.FilterInvoicesBySettleIndexParams) ([]sqlc.Invoice, - error) - - // FilterInvoicesByAddIndex returns invoices whose primary-key id is >= - // the given bound, ordered by id ascending. Because id is the primary - // key, this is always a range scan on the clustered index. - FilterInvoicesByAddIndex(ctx context.Context, - arg sqlc.FilterInvoicesByAddIndexParams) ([]sqlc.Invoice, error) - - // FilterInvoicesForward returns invoices in ascending id order. All - // parameters are non-nullable so the planner always sees plain range - // predicates. Callers must supply Go-side defaults for unused filters - // (see FilterInvoicesForwardParams). - FilterInvoicesForward(ctx context.Context, - arg sqlc.FilterInvoicesForwardParams) ([]sqlc.Invoice, error) - - // FilterInvoicesReverse is the descending counterpart of - // FilterInvoicesForward. See FilterInvoicesForwardParams for the - // expected Go-side defaults. - FilterInvoicesReverse(ctx context.Context, - arg sqlc.FilterInvoicesReverseParams) ([]sqlc.Invoice, error) + GetInvoice(ctx context.Context, + arg sqlc.GetInvoiceParams) ([]sqlc.Invoice, error) GetInvoiceByHash(ctx context.Context, hash []byte) (sqlc.Invoice, error) - GetInvoiceByAddr(ctx context.Context, - paymentAddr []byte) (sqlc.Invoice, error) - GetInvoiceBySetID(ctx context.Context, setID []byte) ([]sqlc.Invoice, error) @@ -398,76 +350,73 @@ func getInvoiceByRef(ctx context.Context, return sqlc.Invoice{}, ErrInvoiceNotFound } - // If the reference contains a payment hash we can look up the invoice - // directly by hash using the unique index, avoiding a full table scan. - // The hash alone uniquely identifies any invoice so additional fields - // in the ref (payment address, set ID) are not needed for the lookup. - if ref.PayHash() != nil { + // If the reference is a hash only, we can look up the invoice directly + // by the payment hash which is faster. + if ref.IsHashOnly() { invoice, err := db.GetInvoiceByHash(ctx, ref.PayHash()[:]) if errors.Is(err, sql.ErrNoRows) { return sqlc.Invoice{}, ErrInvoiceNotFound } - if err != nil { - return sqlc.Invoice{}, fmt.Errorf("unable to fetch "+ - "invoice by hash: %w", err) - } - // If the ref also specifies a payment address, verify it - // matches the invoice found by hash. A mismatch means the ref - // is equivocating — the hash points to one invoice and the - // address points to another. - payAddr := ref.PayAddr() - if payAddr != nil && *payAddr != BlankPayAddr { - if !bytes.Equal(invoice.PaymentAddr, payAddr[:]) { - return sqlc.Invoice{}, ErrInvRefEquivocation - } - } - - return invoice, nil + return invoice, err } - // If the reference contains a payment address (AMP payments), look up - // directly by payment address using the unique index. - // - // NOTE: Pre-0.8 invoices do not have a payment address, and blank - // payment addresses are a special case for legacy keysend invoices. - // Those are handled by the hash fast path above. - payAddr := ref.PayAddr() - if payAddr != nil && *payAddr != BlankPayAddr { - invoice, err := db.GetInvoiceByAddr(ctx, payAddr[:]) - if errors.Is(err, sql.ErrNoRows) { - return sqlc.Invoice{}, ErrInvoiceNotFound - } - if err != nil { - return sqlc.Invoice{}, fmt.Errorf("unable to fetch "+ - "invoice by payment address: %w", err) - } + // Otherwise the reference may include more fields, so we'll need to + // assemble the query parameters based on the fields that are set. + var params sqlc.GetInvoiceParams - return invoice, nil + if ref.PayHash() != nil { + params.Hash = ref.PayHash()[:] } - // If only the set ID is given, look up via the AMP sub-invoice index. + // Newer invoices (0.11 and up) are indexed by payment address in + // addition to payment hash, but pre 0.8 invoices do not have one at + // all. Only allow lookups for payment address if it is not a blank + // payment address, which is a special-cased value for legacy keysend + // invoices. + if ref.PayAddr() != nil && *ref.PayAddr() != BlankPayAddr { + params.PaymentAddr = ref.PayAddr()[:] + } + + // If the reference has a set ID we'll fetch the invoice which has the + // corresponding AMP sub invoice. if ref.SetID() != nil { - rows, err := db.GetInvoiceBySetID(ctx, ref.SetID()[:]) - if err != nil { - return sqlc.Invoice{}, fmt.Errorf("unable to fetch "+ - "invoice: %w", err) - } - - if len(rows) == 0 { - return sqlc.Invoice{}, ErrInvoiceNotFound - } - - if len(rows) > 1 { - return sqlc.Invoice{}, fmt.Errorf("ambiguous "+ - "invoice ref: set_id=%x matches %d invoices", - ref.SetID(), len(rows)) - } - - return rows[0], nil + params.SetID = ref.SetID()[:] } - return sqlc.Invoice{}, ErrInvoiceNotFound + var ( + rows []sqlc.Invoice + err error + ) + + // We need to split the query based on how we intend to look up the + // invoice. If only the set ID is given then we want to have an exact + // match on the set ID. If other fields are given, we want to match on + // those fields and the set ID but with a less strict join condition. + if params.Hash == nil && params.PaymentAddr == nil && + params.SetID != nil { + + rows, err = db.GetInvoiceBySetID(ctx, params.SetID) + } else { + rows, err = db.GetInvoice(ctx, params) + } + + switch { + case len(rows) == 0: + return sqlc.Invoice{}, ErrInvoiceNotFound + + case len(rows) > 1: + // In case the reference is ambiguous, meaning it matches more + // than one invoice, we'll return an error. + return sqlc.Invoice{}, fmt.Errorf("ambiguous invoice ref: "+ + "%s: %s", ref.String(), lnutils.SpewLogClosure(rows)) + + case err != nil: + return sqlc.Invoice{}, fmt.Errorf("unable to fetch invoice: %w", + err) + } + + return rows[0], nil } // fetchInvoice fetches the common invoice data and the AMP state for the @@ -771,17 +720,17 @@ func (i *SQLStore) FetchPendingInvoices(ctx context.Context) ( readTxOpt := sqldb.ReadTxOpt() err := i.db.ExecTx(ctx, readTxOpt, func(db SQLInvoiceQueries) error { - var cursor int64 - limit := int32(i.opts.paginationLimit) - for { - params := sqlc.FetchPendingInvoicesParams{ - IDCursor: cursor, - NumLimit: limit, + return queryWithLimit(func(offset int) (int, error) { + params := sqlc.FilterInvoicesParams{ + PendingOnly: true, + NumOffset: int32(offset), + NumLimit: int32(i.opts.paginationLimit), + Reverse: false, } - rows, err := db.FetchPendingInvoices(ctx, params) + rows, err := db.FilterInvoices(ctx, params) if err != nil && !errors.Is(err, sql.ErrNoRows) { - return fmt.Errorf("unable to get invoices "+ + return 0, fmt.Errorf("unable to get invoices "+ "from db: %w", err) } @@ -791,17 +740,14 @@ func (i *SQLStore) FetchPendingInvoices(ctx context.Context) ( ctx, db, row, nil, true, ) if err != nil { - return err + return 0, err } invoices[*hash] = *invoice - cursor = row.ID } - if int32(len(rows)) < limit { - return nil - } - } + return len(rows), nil + }, i.opts.paginationLimit) }, func() { invoices = make(map[lntypes.Hash]Invoice) }) @@ -835,20 +781,17 @@ func (i *SQLStore) InvoicesSettledSince(ctx context.Context, idx uint64) ( readTxOpt := sqldb.ReadTxOpt() err := i.db.ExecTx(ctx, readTxOpt, func(db SQLInvoiceQueries) error { - var cursor int64 - limit := int32(i.opts.paginationLimit) - for { - // settle_index is always provided here so the - // invoices_settle_index_idx index can be used. - params := sqlc.FilterInvoicesBySettleIndexParams{ + err := queryWithLimit(func(offset int) (int, error) { + params := sqlc.FilterInvoicesParams{ SettleIndexGet: sqldb.SQLInt64(idx + 1), - IDCursor: cursor, - NumLimit: limit, + NumOffset: int32(offset), + NumLimit: int32(i.opts.paginationLimit), + Reverse: false, } - rows, err := db.FilterInvoicesBySettleIndex(ctx, params) + rows, err := db.FilterInvoices(ctx, params) if err != nil && !errors.Is(err, sql.ErrNoRows) { - return fmt.Errorf("unable to get invoices "+ + return 0, fmt.Errorf("unable to get invoices "+ "from db: %w", err) } @@ -858,13 +801,12 @@ func (i *SQLStore) InvoicesSettledSince(ctx context.Context, idx uint64) ( ctx, db, row, nil, true, ) if err != nil { - return fmt.Errorf("unable to fetch "+ + return 0, fmt.Errorf("unable to fetch "+ "invoice(id=%d) from db: %w", row.ID, err) } invoices = append(invoices, *invoice) - cursor = row.ID processedCount++ if time.Since(lastLogTime) >= @@ -879,9 +821,10 @@ func (i *SQLStore) InvoicesSettledSince(ctx context.Context, idx uint64) ( } } - if int32(len(rows)) < limit { - break - } + return len(rows), nil + }, i.opts.paginationLimit) + if err != nil { + return err } // Now fetch all the AMP sub invoices that were settled since @@ -984,21 +927,17 @@ func (i *SQLStore) InvoicesAddedSince(ctx context.Context, idx uint64) ( readTxOpt := sqldb.ReadTxOpt() err := i.db.ExecTx(ctx, readTxOpt, func(db SQLInvoiceQueries) error { - // id is always provided here so the primary-key index is used - // for this range scan. The cursor starts at idx+1 so the first - // page fetches invoices with id >= idx+1. After each page the - // cursor advances to last_id + 1. - cursor := int64(idx + 1) - limit := int32(i.opts.paginationLimit) - for { - params := sqlc.FilterInvoicesByAddIndexParams{ - AddIndexGet: cursor, - NumLimit: limit, + return queryWithLimit(func(offset int) (int, error) { + params := sqlc.FilterInvoicesParams{ + AddIndexGet: sqldb.SQLInt64(idx + 1), + NumOffset: int32(offset), + NumLimit: int32(i.opts.paginationLimit), + Reverse: false, } - rows, err := db.FilterInvoicesByAddIndex(ctx, params) + rows, err := db.FilterInvoices(ctx, params) if err != nil && !errors.Is(err, sql.ErrNoRows) { - return fmt.Errorf("unable to get invoices "+ + return 0, fmt.Errorf("unable to get invoices "+ "from db: %w", err) } @@ -1008,11 +947,10 @@ func (i *SQLStore) InvoicesAddedSince(ctx context.Context, idx uint64) ( ctx, db, row, nil, true, ) if err != nil { - return err + return 0, err } result = append(result, *invoice) - cursor = row.ID + 1 processedCount++ if time.Since(lastLogTime) >= @@ -1026,10 +964,8 @@ func (i *SQLStore) InvoicesAddedSince(ctx context.Context, idx uint64) ( } } - if int32(len(rows)) < limit { - return nil - } - } + return len(rows), nil + }, i.opts.paginationLimit) }, func() { result = nil }) @@ -1060,78 +996,55 @@ func (i *SQLStore) QueryInvoices(ctx context.Context, "be non-zero") } - // Default date bounds: use the package-level sentinels so that the - // planner always receives a concrete, non-nullable value and can use - // the created_at index without OR-based fallbacks. - createdAfter := invoiceCreatedAfterDefault - if q.CreationDateStart != 0 { - createdAfter = time.Unix(q.CreationDateStart, 0).UTC() - } - - createdBefore := invoiceCreatedBeforeDefault - if q.CreationDateEnd != 0 { - // Add 1 second so the end boundary is inclusive: the SQL - // predicate is strict less-than (created_at < createdBefore). - createdBefore = time.Unix(q.CreationDateEnd+1, 0).UTC() - } - readTxOpt := sqldb.ReadTxOpt() err := i.db.ExecTx(ctx, readTxOpt, func(db SQLInvoiceQueries) error { - limit := int32(i.opts.paginationLimit) - - // For reverse queries the cursor is an inclusive upper bound on - // id (id <= cursor); after each page it advances to - // last_returned_id - 1. Start at IndexOffset, or MaxInt64 to - // begin from the most recent invoice. - // For forward queries the cursor is an inclusive lower bound - // (id >= cursor); after each page it advances to - // last_returned_id + 1. Start at IndexOffset + 1 so the invoice - // at IndexOffset itself is excluded (matching the old - // behaviour). - var cursor int64 - if q.Reversed { - cursor = int64(math.MaxInt64) - if q.IndexOffset != 0 { - cursor = int64(q.IndexOffset) - 1 + return queryWithLimit(func(offset int) (int, error) { + params := sqlc.FilterInvoicesParams{ + NumOffset: int32(offset), + NumLimit: int32(i.opts.paginationLimit), + PendingOnly: q.PendingOnly, + Reverse: q.Reversed, } - } else { - cursor = int64(q.IndexOffset) + 1 - } - - for { - var ( - rows []sqlc.Invoice - err error - ) if q.Reversed { - params := sqlc.FilterInvoicesReverseParams{ - AddIndexLet: cursor, - PendingOnly: q.PendingOnly, - CreatedAfter: createdAfter, - CreatedBefore: createdBefore, - NumLimit: limit, + // If the index offset was not set, we want to + // fetch from the lastest invoice. + if q.IndexOffset == 0 { + params.AddIndexLet = sqldb.SQLInt64( + int64(math.MaxInt64), + ) + } else { + // The invoice with index offset id must + // not be included in the results. + params.AddIndexLet = sqldb.SQLInt64( + q.IndexOffset - 1, + ) } - - rows, err = db.FilterInvoicesReverse( - ctx, params, - ) } else { - params := sqlc.FilterInvoicesForwardParams{ - AddIndexGet: cursor, - PendingOnly: q.PendingOnly, - CreatedAfter: createdAfter, - CreatedBefore: createdBefore, - NumLimit: limit, - } - - rows, err = db.FilterInvoicesForward( - ctx, params, + // The invoice with index offset id must not be + // included in the results. + params.AddIndexGet = sqldb.SQLInt64( + q.IndexOffset + 1, ) } + if q.CreationDateStart != 0 { + params.CreatedAfter = sqldb.SQLTime( + time.Unix(q.CreationDateStart, 0).UTC(), + ) + } + + if q.CreationDateEnd != 0 { + // We need to add 1 to the end date as we're + // checking less than the end date in SQL. + params.CreatedBefore = sqldb.SQLTime( + time.Unix(q.CreationDateEnd+1, 0).UTC(), + ) + } + + rows, err := db.FilterInvoices(ctx, params) if err != nil && !errors.Is(err, sql.ErrNoRows) { - return fmt.Errorf("unable to get invoices "+ + return 0, fmt.Errorf("unable to get invoices "+ "from db: %w", err) } @@ -1141,25 +1054,18 @@ func (i *SQLStore) QueryInvoices(ctx context.Context, ctx, db, row, nil, true, ) if err != nil { - return err + return 0, err } invoices = append(invoices, *invoice) - if q.Reversed { - cursor = row.ID - 1 - } else { - cursor = row.ID + 1 - } if len(invoices) == int(q.NumMaxInvoices) { - return nil + return 0, nil } } - if int32(len(rows)) < limit { - return nil - } - } + return len(rows), nil + }, i.opts.paginationLimit) }, func() { invoices = nil }) @@ -1916,3 +1822,22 @@ func unmarshalInvoiceHTLC(row sqlc.InvoiceHtlc) (CircuitKey, return circuitKey, htlc, nil } + +// queryWithLimit is a helper method that can be used to query the database +// using a limit and offset. The passed query function should return the number +// of rows returned and an error if any. +func queryWithLimit(query func(int) (int, error), limit int) error { + offset := 0 + for { + rows, err := query(offset) + if err != nil { + return err + } + + if rows < limit { + return nil + } + + offset += limit + } +} diff --git a/invoices/test_utils.go b/invoices/test_utils.go index c6322940f..b21804346 100644 --- a/invoices/test_utils.go +++ b/invoices/test_utils.go @@ -10,8 +10,8 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/lntypes" diff --git a/invoices/test_utils_test.go b/invoices/test_utils_test.go index 9698dc72d..6062b3b80 100644 --- a/invoices/test_utils_test.go +++ b/invoices/test_utils_test.go @@ -13,8 +13,8 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/clock" invpkg "github.com/lightningnetwork/lnd/invoices" @@ -100,8 +100,6 @@ const ( var ( testTimeout = 5 * time.Second - testTimeoutLong = time.Minute - testTime = time.Date(2018, time.February, 2, 14, 0, 0, 0, time.UTC) testInvoicePreimage = lntypes.Preimage{1} @@ -257,9 +255,7 @@ func timeout() func() { go func() { select { - // Use a longer timeout to accommodate slow Postgres database - // setup and migrations when running tests in parallel. - case <-time.After(testTimeoutLong): + case <-time.After(10 * time.Second): err := pprof.Lookup("goroutine").WriteTo(os.Stdout, 1) if err != nil { panic(fmt.Sprintf("error writing to std out "+ diff --git a/invoices/update.go b/invoices/update.go index 937bff2d2..6f7a34f4c 100644 --- a/invoices/update.go +++ b/invoices/update.go @@ -5,7 +5,7 @@ import ( "encoding/hex" "errors" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/amp" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" @@ -128,36 +128,16 @@ func resolveReplayedHtlc(ctx *invoiceUpdateCtx, inv *Invoice) (bool, return true, ctx.acceptRes(resultReplayToAccepted), nil case HtlcStateSettled: - var preimage *lntypes.Preimage - switch { - // AMP invoices store a separate preimage on each HTLC. - case inv.IsAMP(): - if htlc.AMP == nil || htlc.AMP.Preimage == nil { - return true, nil, ErrHTLCPreimageMissing - } + pre := inv.Terms.PaymentPreimage - preimage = htlc.AMP.Preimage - if htlc.AMP.Hash != ctx.hash || - !preimage.Matches(htlc.AMP.Hash) { - - return true, nil, ErrHTLCPreimageMismatch - } - - // Regular invoices store their preimage at the invoice level. - case inv.Terms.PaymentPreimage == nil: - return true, nil, errors.New( - "settled invoice missing payment preimage", - ) - - default: - preimage = inv.Terms.PaymentPreimage - if !preimage.Matches(ctx.hash) { - return true, nil, ErrInvoicePreimageMismatch - } + // Terms.PaymentPreimage will be nil for AMP invoices. + // Set it to the HTLCs AMP Preimage instead. + if pre == nil { + pre = htlc.AMP.Preimage } return true, ctx.settleRes( - *preimage, + *pre, ResultReplayToSettled, ), nil @@ -175,12 +155,6 @@ func resolveReplayedHtlc(ctx *invoiceUpdateCtx, inv *Invoice) (bool, func updateInvoice(ctx *invoiceUpdateCtx, inv *Invoice) ( *InvoiceUpdateDesc, HtlcResolution, error) { - // AMP records are processed together with their corresponding MPP - // payload. - if ctx.amp != nil && ctx.mpp == nil { - return nil, ctx.failRes(ResultAmpError), nil - } - // If no MPP payload was provided, then we expect this to be a keysend, // or a payment to an invoice created before we started to require the // MPP payload. @@ -440,12 +414,6 @@ func reconstructAMPPreimages(ctx *invoiceUpdateCtx, func updateLegacy(ctx *invoiceUpdateCtx, inv *Invoice) (*InvoiceUpdateDesc, HtlcResolution, error) { - // AMP invoices use the MPP update path, where each HTLC's AMP data is - // available for processing. - if inv.IsAMP() { - return nil, ctx.failRes(ResultHtlcInvoiceTypeMismatch), nil - } - // If the invoice is already canceled, there is no further // checking to do. if inv.State == ContractCanceled { @@ -464,11 +432,12 @@ func updateLegacy(ctx *invoiceUpdateCtx, // if we're in this method it means that the remote party didn't supply // the expected payload. However if this is a keysend payment, then // we'll permit it to pass. + _, isKeySend := ctx.customRecords[record.KeySendType] invoiceFeatures := inv.Terms.Features paymentAddrRequired := invoiceFeatures.RequiresFeature( lnwire.PaymentAddrRequired, ) - if !isValidKeySend(ctx) && paymentAddrRequired { + if !isKeySend && paymentAddrRequired { log.Warnf("Payment to pay_hash=%v doesn't include MPP "+ "payload, rejecting", ctx.hash) return nil, ctx.failRes(ResultAddressMismatch), nil @@ -520,15 +489,8 @@ func updateLegacy(ctx *invoiceUpdateCtx, return &update, ctx.acceptRes(resultDuplicateToAccepted), nil case ContractSettled: - // Legacy settlement uses the invoice-level payment preimage. - preimage := inv.Terms.PaymentPreimage - if preimage == nil { - return nil, ctx.failRes(ResultHtlcInvoiceTypeMismatch), - nil - } - return &update, ctx.settleRes( - *preimage, ResultDuplicateToSettled, + *inv.Terms.PaymentPreimage, ResultDuplicateToSettled, ), nil } @@ -542,35 +504,12 @@ func updateLegacy(ctx *invoiceUpdateCtx, return &update, ctx.acceptRes(resultAccepted), nil } - // A legacy invoice provides its settlement preimage at the invoice - // level. - preimage := inv.Terms.PaymentPreimage - if preimage == nil { - return nil, ctx.failRes(ResultHtlcInvoiceTypeMismatch), nil - } - update.State = &InvoiceStateUpdateDesc{ NewState: ContractSettled, - Preimage: preimage, + Preimage: inv.Terms.PaymentPreimage, } return &update, ctx.settleRes( - *preimage, ResultSettled, + *inv.Terms.PaymentPreimage, ResultSettled, ), nil } - -// isValidKeySend reports whether the custom records contain a keysend -// preimage whose hash matches the payment hash. -func isValidKeySend(ctx *invoiceUpdateCtx) bool { - preimageBytes, ok := ctx.customRecords[record.KeySendType] - if !ok { - return false - } - - preimage, err := lntypes.MakePreimage(preimageBytes) - if err != nil { - return false - } - - return preimage.Hash() == ctx.hash -} diff --git a/invoices/update_invoice_test.go b/invoices/update_invoice_test.go index 74f4b9a0b..6069fbecd 100644 --- a/invoices/update_invoice_test.go +++ b/invoices/update_invoice_test.go @@ -744,6 +744,7 @@ func TestUpdateHTLC(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { testUpdateHTLC(t, test, testNow) }) @@ -763,363 +764,3 @@ func testUpdateHTLC(t *testing.T, test updateHTLCTest, now time.Time) { require.Equal(t, test.expErr, err) require.Equal(t, test.output, *htlc) } - -// TestResolveReplayedHtlcSettled checks preimage selection for settled HTLC -// replays. -func TestResolveReplayedHtlcSettled(t *testing.T) { - t.Parallel() - - const missingPreimageErr = "settled invoice missing payment preimage" - - validPreimage := lntypes.Preimage{1} - otherPreimage := lntypes.Preimage{2} - validHash := validPreimage.Hash() - otherHash := otherPreimage.Hash() - setID := [32]byte{3} - ampRecord := record.NewAMP([32]byte{4}, setID, 5) - ampFeatures := lnwire.NewFeatureVector( - lnwire.NewRawFeatureVector(lnwire.AMPRequired), - lnwire.Features, - ) - - tests := []struct { - name string - invoicePreimage *lntypes.Preimage - invoiceFeatures *lnwire.FeatureVector - htlcAMP *InvoiceHtlcAMPData - paymentHash lntypes.Hash - expectedPreimage *lntypes.Preimage - expectedErr error - expectedErrText string - }{ - { - name: "regular invoice", - invoicePreimage: &validPreimage, - paymentHash: validHash, - expectedPreimage: &validPreimage, - }, - { - name: "regular invoice missing preimage", - paymentHash: validHash, - expectedErrText: missingPreimageErr, - }, - { - name: "regular invoice preimage mismatch", - invoicePreimage: &otherPreimage, - paymentHash: validHash, - expectedErr: ErrInvoicePreimageMismatch, - }, - { - name: "AMP invoice", - invoiceFeatures: ampFeatures, - htlcAMP: &InvoiceHtlcAMPData{ - Record: *ampRecord, - Hash: validHash, - Preimage: &validPreimage, - }, - paymentHash: validHash, - expectedPreimage: &validPreimage, - }, - { - name: "AMP invoice missing HTLC data", - invoiceFeatures: ampFeatures, - paymentHash: validHash, - expectedErr: ErrHTLCPreimageMissing, - }, - { - name: "AMP invoice missing preimage", - invoiceFeatures: ampFeatures, - htlcAMP: &InvoiceHtlcAMPData{ - Record: *ampRecord, - Hash: validHash, - }, - paymentHash: validHash, - expectedErr: ErrHTLCPreimageMissing, - }, - { - name: "AMP invoice preimage mismatch", - invoiceFeatures: ampFeatures, - htlcAMP: &InvoiceHtlcAMPData{ - Record: *ampRecord, - Hash: validHash, - Preimage: &otherPreimage, - }, - paymentHash: validHash, - expectedErr: ErrHTLCPreimageMismatch, - }, - { - name: "AMP invoice hash mismatch", - invoiceFeatures: ampFeatures, - htlcAMP: &InvoiceHtlcAMPData{ - Record: *ampRecord, - Hash: otherHash, - Preimage: &otherPreimage, - }, - paymentHash: validHash, - expectedErr: ErrHTLCPreimageMismatch, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - circuitKey := CircuitKey{HtlcID: 1} - ctx := &invoiceUpdateCtx{ - hash: test.paymentHash, - circuitKey: circuitKey, - } - invoice := &Invoice{ - Terms: ContractTerm{ - PaymentPreimage: test.invoicePreimage, - Features: test.invoiceFeatures, - }, - Htlcs: map[CircuitKey]*InvoiceHTLC{ - circuitKey: { - State: HtlcStateSettled, - AMP: test.htlcAMP, - }, - }, - } - - replayed, resolution, err := resolveReplayedHtlc( - ctx, invoice, - ) - require.True(t, replayed) - - switch { - case test.expectedErr != nil: - require.ErrorIs(t, err, test.expectedErr) - require.Nil(t, resolution) - - case test.expectedErrText != "": - require.EqualError(t, err, test.expectedErrText) - require.Nil(t, resolution) - - default: - require.NoError(t, err) - requireSettleResolution( - t, resolution, ResultReplayToSettled, - ) - settleResolution, ok := - resolution.(*HtlcSettleResolution) - require.True(t, ok) - require.Equal( - t, *test.expectedPreimage, - settleResolution.Preimage, - ) - } - }) - } -} - -// TestUpdateInvoiceRejectsAmpWithoutMPP checks that AMP records follow the MPP -// update path. -func TestUpdateInvoiceRejectsAmpWithoutMPP(t *testing.T) { - t.Parallel() - - ctx, invoice := newLegacyUpdateTestContext(t, ContractOpen) - ctx.amp = record.NewAMP([32]byte{1}, [32]byte{2}, 3) - - update, resolution, err := updateInvoice(ctx, invoice) - require.NoError(t, err) - require.Nil(t, update) - requireFailResolution(t, resolution, ResultAmpError) -} - -// TestUpdateInvoiceRejectsAmpInvoiceInLegacyPath checks that AMP invoices are -// handled by the MPP update path. -func TestUpdateInvoiceRejectsAmpInvoiceInLegacyPath(t *testing.T) { - t.Parallel() - - ctx, invoice := newLegacyUpdateTestContext(t, ContractOpen) - invoice.Terms.PaymentPreimage = nil - invoice.Terms.Features = lnwire.NewFeatureVector( - lnwire.NewRawFeatureVector( - lnwire.TLVOnionPayloadOptional, - lnwire.PaymentAddrOptional, - lnwire.AMPRequired, - ), - lnwire.Features, - ) - - update, resolution, err := updateInvoice(ctx, invoice) - require.NoError(t, err) - require.Nil(t, update) - requireFailResolution(t, resolution, ResultHtlcInvoiceTypeMismatch) -} - -// TestUpdateLegacyRejectsNilPreimageSettle checks the outcome when a legacy -// settlement has no invoice-level preimage. -func TestUpdateLegacyRejectsNilPreimageSettle(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - state ContractState - }{ - { - name: "new settle", - state: ContractOpen, - }, - { - name: "duplicate settled", - state: ContractSettled, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - ctx, invoice := newLegacyUpdateTestContext( - t, test.state, - ) - invoice.Terms.PaymentPreimage = nil - - update, resolution, err := updateLegacy(ctx, invoice) - require.NoError(t, err) - require.Nil(t, update) - requireFailResolution( - t, resolution, ResultHtlcInvoiceTypeMismatch, - ) - }) - } -} - -// TestUpdateLegacyValidatesKeysendRecord checks that the keysend record is -// well-formed and corresponds to the payment hash. -func TestUpdateLegacyValidatesKeysendRecord(t *testing.T) { - t.Parallel() - - validPreimage := lntypes.Preimage{1} - invalidPreimage := lntypes.Preimage{2} - - tests := []struct { - name string - keysendRecord []byte - expectFail bool - expectedResult FailResolutionResult - }{ - { - name: "missing keysend", - expectFail: true, - expectedResult: ResultAddressMismatch, - }, - { - name: "invalid keysend length", - keysendRecord: []byte{1, 2, 3}, - expectFail: true, - expectedResult: ResultAddressMismatch, - }, - { - name: "wrong keysend preimage", - keysendRecord: invalidPreimage[:], - expectFail: true, - expectedResult: ResultAddressMismatch, - }, - { - name: "valid keysend", - keysendRecord: validPreimage[:], - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - ctx, invoice := newLegacyUpdateTestContext( - t, ContractOpen, - ) - ctx.hash = validPreimage.Hash() - ctx.customRecords = make(record.CustomSet) - invoice.Terms.PaymentPreimage = &validPreimage - invoice.Terms.Features = lnwire.NewFeatureVector( - lnwire.NewRawFeatureVector( - lnwire.TLVOnionPayloadRequired, - lnwire.PaymentAddrRequired, - ), - lnwire.Features, - ) - - if test.keysendRecord != nil { - ctx.customRecords[record.KeySendType] = - test.keysendRecord - } - - update, resolution, err := updateLegacy(ctx, invoice) - require.NoError(t, err) - - if test.expectFail { - require.Nil(t, update) - requireFailResolution( - t, resolution, test.expectedResult, - ) - - return - } - - require.NotNil(t, update) - requireSettleResolution(t, resolution, ResultSettled) - }) - } -} - -// newLegacyUpdateTestContext creates a minimal legacy invoice and update -// context for exercising update selection and settlement outcomes. -func newLegacyUpdateTestContext(t *testing.T, - state ContractState) (*invoiceUpdateCtx, *Invoice) { - - t.Helper() - - preimage := lntypes.Preimage{1} - payHash := preimage.Hash() - - ctx := &invoiceUpdateCtx{ - hash: payHash, - circuitKey: CircuitKey{HtlcID: 1}, - amtPaid: lnwire.MilliSatoshi(1000), - expiry: 40, - currentHeight: 10, - finalCltvRejectDelta: 10, - customRecords: make(record.CustomSet), - wireCustomRecords: make(lnwire.CustomRecords), - } - - invoice := &Invoice{ - State: state, - Terms: ContractTerm{ - FinalCltvDelta: 10, - PaymentPreimage: &preimage, - Value: 1000, - Features: lnwire.NewFeatureVector( - nil, lnwire.Features, - ), - }, - Htlcs: make(map[CircuitKey]*InvoiceHTLC), - } - - return ctx, invoice -} - -// requireFailResolution checks the resolution type and its reported outcome. -func requireFailResolution(t *testing.T, resolution HtlcResolution, - expected FailResolutionResult) { - - t.Helper() - - failResolution, ok := resolution.(*HtlcFailResolution) - require.True(t, ok) - require.Equal(t, expected, failResolution.Outcome) -} - -// requireSettleResolution checks the resolution type and its reported outcome. -func requireSettleResolution(t *testing.T, resolution HtlcResolution, - expected SettleResolutionResult) { - - t.Helper() - - settleResolution, ok := resolution.(*HtlcSettleResolution) - require.True(t, ok) - require.Equal(t, expected, settleResolution.Outcome) -} diff --git a/itest/list_on_test.go b/itest/list_on_test.go index 1301a266e..02fd01218 100644 --- a/itest/list_on_test.go +++ b/itest/list_on_test.go @@ -206,10 +206,6 @@ var allTestCases = []*lntest.TestCase{ Name: "invoice update subscription", TestFunc: testInvoiceSubscriptions, }, - { - Name: "channel update subscription", - TestFunc: testChannelUpdateNotifications, - }, { Name: "streaming channel backup update", TestFunc: testChannelBackupUpdates, @@ -234,10 +230,6 @@ var allTestCases = []*lntest.TestCase{ Name: "neutrino kit", TestFunc: testNeutrino, }, - { - Name: "neutrino headers import", - TestFunc: testNeutrinoHeadersImport, - }, { Name: "etcd failover", TestFunc: testEtcdFailover, @@ -294,10 +286,6 @@ var allTestCases = []*lntest.TestCase{ Name: "open channel reorg test", TestFunc: testOpenChannelAfterReorg, }, - { - Name: "open channel with shutdown address", - TestFunc: testOpenChannelWithShutdownAddr, - }, { Name: "sign psbt", TestFunc: testSignPsbt, @@ -463,10 +451,6 @@ var allTestCases = []*lntest.TestCase{ Name: "forward interceptor on chain settle no restart", TestFunc: testForwardInterceptorOnChainSettleNoRestart, }, - { - Name: "delete forwarding history", - TestFunc: testDeleteForwardingHistory, - }, { Name: "invoice HTLC modifier basic", TestFunc: testInvoiceHtlcModifierBasic, @@ -475,10 +459,6 @@ var allTestCases = []*lntest.TestCase{ Name: "zero conf channel open", TestFunc: testZeroConfChannelOpen, }, - { - Name: "zero conf coop close subscribe events", - TestFunc: testZeroConfCoopCloseSubscribeEvents, - }, { Name: "option scid alias", TestFunc: testOptionScidAlias, @@ -503,10 +483,6 @@ var allTestCases = []*lntest.TestCase{ Name: "sign output raw", TestFunc: testSignOutputRaw, }, - { - Name: "submit package", - TestFunc: testSubmitPackage, - }, { Name: "sign verify message", TestFunc: testSignVerifyMessage, @@ -539,10 +515,6 @@ var allTestCases = []*lntest.TestCase{ Name: "simple taproot channel activation", TestFunc: testSimpleTaprootChannelActivation, }, - { - Name: "simple taproot final channel activation", - TestFunc: testSimpleTaprootFinalChannelActivation, - }, { Name: "wallet import pubkey", TestFunc: testWalletImportPubKey, @@ -567,14 +539,6 @@ var allTestCases = []*lntest.TestCase{ Name: "custom message", TestFunc: testCustomMessage, }, - { - Name: "onion message", - TestFunc: testOnionMessage, - }, - { - Name: "onion message forwarding", - TestFunc: testOnionMessageForwarding, - }, { Name: "sign verify message with addr", TestFunc: testSignVerifyMessageWithAddr, @@ -603,10 +567,6 @@ var allTestCases = []*lntest.TestCase{ Name: "channel fundmax anchor reserve", TestFunc: testChannelFundMaxAnchorReserve, }, - { - Name: "channel fundmax maxchansize", - TestFunc: testChannelFundMaxMaxChanSize, - }, { Name: "htlc timeout resolver extract preimage remote", TestFunc: testHtlcTimeoutResolverExtractPreimageRemote, @@ -631,18 +591,6 @@ var allTestCases = []*lntest.TestCase{ Name: "blinded payment htlc re-forward", TestFunc: testBlindedPaymentHTLCReForward, }, - { - Name: "blinded route next node id", - TestFunc: testBlindedRouteNextNodeID, - }, - { - Name: "blinded route next node id private channel", - TestFunc: testBlindedRouteNextNodeIDPrivateChannel, - }, - { - Name: "blinded route next node id restart", - TestFunc: testBlindedRouteNextNodeIDRestart, - }, { Name: "query blinded route", TestFunc: testQueryBlindedRoutes, @@ -752,8 +700,8 @@ var allTestCases = []*lntest.TestCase{ TestFunc: testDebuglevelShow, }, { - Name: "experimental accountability", - TestFunc: testExperimentalAccountability, + Name: "experimental endorsement", + TestFunc: testExperimentalEndorsement, }, { Name: "quiescence", @@ -767,10 +715,6 @@ var allTestCases = []*lntest.TestCase{ Name: "graph migration", TestFunc: testGraphMigration, }, - { - Name: "payment migration", - TestFunc: testPaymentMigration, - }, { Name: "payment address mismatch", TestFunc: testWrongPaymentAddr, @@ -791,10 +735,6 @@ var allTestCases = []*lntest.TestCase{ Name: "rbf coop close disconnect", TestFunc: testRBFCoopCloseDisconnect, }, - { - Name: "coop close rbf with reorg", - TestFunc: testCoopCloseRBFWithReorg, - }, { Name: "bump fee low budget", TestFunc: testBumpFeeLowBudget, @@ -819,18 +759,6 @@ var allTestCases = []*lntest.TestCase{ Name: "estimate fee", TestFunc: testEstimateFee, }, - { - Name: "estimate on chain fee with selected inputs", - TestFunc: testEstimateOnChainFeeWithSelectedInputs, - }, - { - Name: "estimate on chain fee auto selected inputs", - TestFunc: testEstimateOnChainFeeAutoSelectedInputs, - }, - { - Name: "postgres network separation", - TestFunc: testPostgresNetworkSeparation, - }, } // appendPrefixed is used to add a prefix to each test name in the subtests @@ -898,9 +826,6 @@ func init() { allTestCases = appendPrefixed( "wallet", allTestCases, walletTestCases, ) - allTestCases = appendPrefixed( - "wallet sync", allTestCases, walletSyncTestCases, - ) allTestCases = appendPrefixed( "coop close with external delivery", allTestCases, coopCloseWithExternalTestCases, diff --git a/itest/lnd_amp_test.go b/itest/lnd_amp_test.go index 72c3678e5..525dff0dd 100644 --- a/itest/lnd_amp_test.go +++ b/itest/lnd_amp_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/amp" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" diff --git a/itest/lnd_bump_fee.go b/itest/lnd_bump_fee.go index 3493a585a..3b89e1088 100644 --- a/itest/lnd_bump_fee.go +++ b/itest/lnd_bump_fee.go @@ -3,8 +3,8 @@ package itest import ( "fmt" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lntest" diff --git a/itest/lnd_channel_backup_test.go b/itest/lnd_channel_backup_test.go index ea28b457f..d3daeb1df 100644 --- a/itest/lnd_channel_backup_test.go +++ b/itest/lnd_channel_backup_test.go @@ -11,8 +11,8 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chanbackup" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" @@ -85,30 +85,6 @@ var channelRestoreTestCases = []*lntest.TestCase{ ) }, }, - { - // Restore a channel back up of a confirmed production - // taproot channel. - Name: "restore simple taproot final", - TestFunc: func(ht *lntest.HarnessTest) { - runChanRestoreScenarioCommitTypes( - ht, - lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL, - false, - ) - }, - }, - { - // Restore a channel back up of an unconfirmed production - // taproot channel. - Name: "restore simple taproot final zero conf", - TestFunc: func(ht *lntest.HarnessTest) { - runChanRestoreScenarioCommitTypes( - ht, - lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL, - true, - ) - }, - }, { Name: "restore from rpc", TestFunc: testChannelBackupRestoreFromRPC, @@ -217,9 +193,7 @@ func newChanRestoreScenario(ht *lntest.HarnessTest, ct lnrpc.CommitmentType, // If the commitment type is taproot, then the channel must also be // private. var privateChan bool - if ct == lnrpc.CommitmentType_SIMPLE_TAPROOT || - ct == lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL { - + if ct == lnrpc.CommitmentType_SIMPLE_TAPROOT { privateChan = true } @@ -665,9 +639,7 @@ func runChanRestoreScenarioCommitTypes(ht *lntest.HarnessTest, // If this was a zero conf taproot channel, then since it's private, // we'll need to mine an extra block (framework won't mine extra blocks // otherwise). - if (ct == lnrpc.CommitmentType_SIMPLE_TAPROOT || - ct == lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL) && zeroConf { - + if ct == lnrpc.CommitmentType_SIMPLE_TAPROOT && zeroConf { ht.MineBlocksAndAssertNumTxes(1, 1) } diff --git a/itest/lnd_channel_balance_test.go b/itest/lnd_channel_balance_test.go index 4c5eee354..75e3e8594 100644 --- a/itest/lnd_channel_balance_test.go +++ b/itest/lnd_channel_balance_test.go @@ -3,7 +3,7 @@ package itest import ( "fmt" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" diff --git a/itest/lnd_channel_force_close_test.go b/itest/lnd_channel_force_close_test.go index 2df19f98f..3cb6d30e4 100644 --- a/itest/lnd_channel_force_close_test.go +++ b/itest/lnd_channel_force_close_test.go @@ -5,8 +5,8 @@ import ( "errors" "fmt" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" @@ -26,10 +26,6 @@ var channelForceCloseTestCases = []*lntest.TestCase{ Name: "simple taproot", TestFunc: testChannelForceClosureSimpleTaproot, }, - { - Name: "simple taproot final", - TestFunc: testChannelForceClosureSimpleTaprootFinal, - }, { Name: "anchor restart", TestFunc: testChannelForceClosureAnchorRestart, @@ -38,10 +34,6 @@ var channelForceCloseTestCases = []*lntest.TestCase{ Name: "simple taproot restart", TestFunc: testChannelForceClosureSimpleTaprootRestart, }, - { - Name: "simple taproot final restart", - TestFunc: testChannelForceClosureSimpleTaprootFinalRestart, - }, { Name: "wrong preimage", @@ -95,31 +87,6 @@ func testChannelForceClosureSimpleTaproot(ht *lntest.HarnessTest) { runChannelForceClosureTest(ht, cfgs, openChannelParams) } -// testChannelForceClosureSimpleTaprootFinal runs `runChannelForceClosureTest` -// with production simple taproot channels. -func testChannelForceClosureSimpleTaprootFinal(ht *lntest.HarnessTest) { - // Create a simple network: Alice -> Carol, using production simple - // taproot channels. - // - // Prepare params. - openChannelParams := lntest.OpenChannelParams{ - Amt: chanAmt, - PushAmt: pushAmt, - // If the channel is a taproot channel, then we'll need to - // create a private channel. - // - // TODO(roasbeef): lift after G175 - CommitmentType: lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL, - Private: true, - } - - cfg := node.CfgSimpleTaproot - cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...) - cfgs := [][]string{cfg, cfgCarol} - - runChannelForceClosureTest(ht, cfgs, openChannelParams) -} - // runChannelForceClosureTest performs a test to exercise the behavior of // "force" closing a channel or unilaterally broadcasting the latest local // commitment state on-chain. The test creates a new channel between Alice and @@ -708,31 +675,6 @@ func testChannelForceClosureSimpleTaprootRestart(ht *lntest.HarnessTest) { runChannelForceClosureTestRestart(ht, cfgs, openChannelParams) } -// testChannelForceClosureSimpleTaprootFinalRestart runs -// `runChannelForceClosureTestRestart` with production simple taproot channels. -func testChannelForceClosureSimpleTaprootFinalRestart(ht *lntest.HarnessTest) { - // Create a simple network: Alice -> Carol, using production simple - // taproot channels. - // - // Prepare params. - openChannelParams := lntest.OpenChannelParams{ - Amt: chanAmt, - PushAmt: pushAmt, - // If the channel is a taproot channel, then we'll need to - // create a private channel. - // - // TODO(roasbeef): lift after G175 - CommitmentType: lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL, - Private: true, - } - - cfg := node.CfgSimpleTaproot - cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...) - cfgs := [][]string{cfg, cfgCarol} - - runChannelForceClosureTestRestart(ht, cfgs, openChannelParams) -} - // runChannelForceClosureTestRestart performs a test to exercise the behavior of // "force" closing a channel or unilaterally broadcasting the latest local // commitment state on-chain. The test creates a new channel between Alice and @@ -997,7 +939,7 @@ func runChannelForceClosureTestRestart(ht *lntest.HarnessTest, sweeps = ht.AssertNumPendingSweeps(alice, 2) commitSweep, anchorSweep := sweeps[0], sweeps[1] if commitSweep.AmountSat < anchorSweep.AmountSat { - commitSweep = anchorSweep + commitSweep, anchorSweep = anchorSweep, commitSweep } // Alice's sweeping transaction should now be broadcast. So we fetch the diff --git a/itest/lnd_channel_funding_fund_max_test.go b/itest/lnd_channel_funding_fund_max_test.go index d26215172..f2c73851d 100644 --- a/itest/lnd_channel_funding_fund_max_test.go +++ b/itest/lnd_channel_funding_fund_max_test.go @@ -4,9 +4,8 @@ import ( "errors" "testing" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd" - "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" @@ -409,79 +408,3 @@ func sweepNodeWalletAndAssert(ht *lntest.HarnessTest, node *node.HarnessNode) { // Ensure that the node's balance is 0 checkChannelBalance(ht, node, 0, 0) } - -// testChannelFundMaxMaxChanSize verifies that fundMax uses the protocol-level -// maximum channel size, not the user-configured maxChanSize. The maxChanSize -// config option is intended only for limiting incoming channel requests, not -// outgoing ones. -func testChannelFundMaxMaxChanSize(ht *lntest.HarnessTest) { - testCases := []struct { - name string - wumbo bool - expectedMax btcutil.Amount - }{ - { - name: "non-wumbo", - wumbo: false, - expectedMax: funding.MaxBtcFundingAmount, - }, - { - name: "wumbo", - wumbo: true, - expectedMax: funding.MaxBtcFundingAmountWumbo, - }, - } - - for _, tc := range testCases { - success := ht.Run(tc.name, func(t *testing.T) { - st := ht.Subtest(t) - - // Configure Alice with a restrictive maxChanSize (5M - // sats), which is below both protocol maximums. - aliceArgs := []string{ - "--maxchansize=5000000", - } - if tc.wumbo { - aliceArgs = append( - aliceArgs, "--protocol.wumbo-channels", - ) - } - - alice := st.NewNode("Alice", aliceArgs) - - // Bob needs wumbo enabled to accept large channels. - var bobArgs []string - if tc.wumbo { - bobArgs = []string{"--protocol.wumbo-channels"} - } - bob := st.NewNode("Bob", bobArgs) - - st.EnsureConnected(alice, bob) - - // Fund Alice with more than the protocol maximum. - fundAmt := tc.expectedMax + btcutil.SatoshiPerBitcoin - st.FundCoins(fundAmt, alice) - - // Open channel with fundMax. This should use the - // protocol maximum, not the configured maxChanSize. - chanPoint := st.OpenChannel( - alice, bob, lntest.OpenChannelParams{ - FundMax: true, - }, - ) - - cType := st.GetChannelCommitType(alice, chanPoint) - - // The expected balance is the protocol maximum minus - // the commitment fee. - expectedBalance := tc.expectedMax - - lntest.CalcStaticFee(cType, 0) - - checkChannelBalance(st, alice, expectedBalance, 0) - checkChannelBalance(st, bob, 0, expectedBalance) - }) - if !success { - break - } - } -} diff --git a/itest/lnd_channel_funding_utxo_selection_test.go b/itest/lnd_channel_funding_utxo_selection_test.go index 70765a5fb..32caf2b57 100644 --- a/itest/lnd_channel_funding_utxo_selection_test.go +++ b/itest/lnd_channel_funding_utxo_selection_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lntest" diff --git a/itest/lnd_channel_graph_test.go b/itest/lnd_channel_graph_test.go index 54db51717..f8d63c1d5 100644 --- a/itest/lnd_channel_graph_test.go +++ b/itest/lnd_channel_graph_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" @@ -378,6 +378,7 @@ func testNodeAnnouncement(ht *lntest.HarnessTest) { advertisedAddrs := []string{ "192.168.1.1:8333", "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8337", + "bkb6azqggsaiskzi.onion:9735", "fomvuglh6h6vcag73xo5t5gv56ombih3zr2xvplkpbfd7wrog4swj" + "wid.onion:1234", } @@ -434,6 +435,7 @@ func testUpdateNodeAnnouncement(ht *lntest.HarnessTest) { extraAddrs := []string{ "192.168.1.1:8333", "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8337", + "bkb6azqggsaiskzi.onion:9735", "fomvuglh6h6vcag73xo5t5gv56ombih3zr2xvplkpbfd7wrog4swj" + "wid.onion:1234", } diff --git a/itest/lnd_channel_policy_test.go b/itest/lnd_channel_policy_test.go index 8a38c31d6..7a333f073 100644 --- a/itest/lnd_channel_policy_test.go +++ b/itest/lnd_channel_policy_test.go @@ -5,7 +5,7 @@ import ( "math" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" @@ -170,28 +170,29 @@ func testUpdateChannelPolicy(ht *lntest.HarnessTest) { routes.Routes[0].Hops[1].AmtToForward = amtSat routes.Routes[0].Hops[1].AmtToForwardMsat = amtMSat - // Send the payment with the modified value and expect a failure because - // the amount is below the minimum HTLC size. - sendReq := &routerrpc.SendToRouteRequest{ + // Send the payment with the modified value. + alicePayStream := alice.RPC.SendToRoute() + + sendReq := &lnrpc.SendToRouteRequest{ PaymentHash: resp.RHash, Route: routes.Routes[0], } - sendResp := alice.RPC.SendToRouteV2(sendReq) - require.NotNil(ht, sendResp.Failure, "expected payment failure") - require.Equal( - ht, lnrpc.Failure_AMOUNT_BELOW_MINIMUM, sendResp.Failure.Code, - ) + err := alicePayStream.Send(sendReq) + require.NoError(ht, err, "unable to send payment") - // The failure should carry the advertised min HTLC value so that - // callers can react to the channel policy. - require.NotNil( - ht, sendResp.Failure.ChannelUpdate, - "expected channel update in failure", - ) - require.Equal( - ht, uint64(customMinHtlc), - sendResp.Failure.ChannelUpdate.HtlcMinimumMsat, - ) + // We expect this payment to fail, and that the min_htlc value is + // communicated back to us, since the attempted HTLC value was too low. + sendResp, err := ht.ReceiveSendToRouteUpdate(alicePayStream) + require.NoError(ht, err, "unable to receive payment stream") + + // Expected as part of the error message. + substrs := []string{ + "AmountBelowMinimum", + "HtlcMinimumMsat: (lnwire.MilliSatoshi) 5000 mSAT", + } + for _, s := range substrs { + require.Contains(ht, sendResp.PaymentError, s) + } // Make sure sending using the original value succeeds. payAmt = btcutil.Amount(5) @@ -212,12 +213,17 @@ func testUpdateChannelPolicy(ht *lntest.HarnessTest) { TotalAmtMsat: amtMSat, } - sendReq = &routerrpc.SendToRouteRequest{ + sendReq = &lnrpc.SendToRouteRequest{ PaymentHash: resp.RHash, Route: route, } - sendResp = alice.RPC.SendToRouteV2(sendReq) - require.Nil(ht, sendResp.Failure, "expected payment to succeed") + + err = alicePayStream.Send(sendReq) + require.NoError(ht, err, "unable to send payment") + + sendResp, err = ht.ReceiveSendToRouteUpdate(alicePayStream) + require.NoError(ht, err, "unable to receive payment stream") + require.Empty(ht, sendResp.PaymentError, "expected payment to succeed") // With our little cluster set up, we'll update the outbound fees and // the max htlc size for the Bob side of the Alice->Bob channel, and @@ -289,7 +295,7 @@ func testUpdateChannelPolicy(ht *lntest.HarnessTest) { // propagated. baseFee = int64(800) feeRate = int64(123) - timeLockDelta = uint32(24) + timeLockDelta = uint32(22) maxHtlc *= 2 inboundBaseFee := int32(-400) inboundFeeRatePpm := int32(-60) diff --git a/itest/lnd_coop_close_external_delivery_test.go b/itest/lnd_coop_close_external_delivery_test.go index cc949d0b7..4c2f0a5c4 100644 --- a/itest/lnd_coop_close_external_delivery_test.go +++ b/itest/lnd_coop_close_external_delivery_test.go @@ -3,9 +3,8 @@ package itest import ( "fmt" - "github.com/btcsuite/btcd/address/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/txscript/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/txscript" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lntest" @@ -159,9 +158,9 @@ func testCoopCloseWithExternalDelivery(ht *lntest.HarnessTest, // Use ImportPublicKey. case importPubkey: var ( - deliveryAddr address.Address - pubKey []byte - addressType walletrpc.AddressType + address btcutil.Address + pubKey []byte + addressType walletrpc.AddressType ) switch deliveryAddressType { case lnrpc.AddressType_UNUSED_WITNESS_PUBKEY_HASH: @@ -171,15 +170,15 @@ func testCoopCloseWithExternalDelivery(ht *lntest.HarnessTest, // Make new address for second sub-test. pk[1]++ } - deliveryAddr, err = address.NewAddressWitnessPubKeyHash( - address.Hash160(pk[:]), harnessNetParams, + address, err = btcutil.NewAddressWitnessPubKeyHash( + btcutil.Hash160(pk[:]), harnessNetParams, ) require.NoError(ht, err) pubKey = pk[:] addressType = walletrpc.AddressType_WITNESS_PUBKEY_HASH case lnrpc.AddressType_UNUSED_TAPROOT_PUBKEY: - deliveryAddr = taprootAddress + address = taprootAddress pubKey = taprootPubkey[:] addressType = walletrpc.AddressType_TAPROOT_PUBKEY @@ -188,7 +187,7 @@ func testCoopCloseWithExternalDelivery(ht *lntest.HarnessTest, deliveryAddressType) } - addr = deliveryAddr.String() + addr = address.String() // Import the address to LND. alice.RPC.ImportPublicKey(&walletrpc.ImportPublicKeyRequest{ diff --git a/itest/lnd_coop_close_rbf_test.go b/itest/lnd_coop_close_rbf_test.go index e23f8d351..5f8b15d40 100644 --- a/itest/lnd_coop_close_rbf_test.go +++ b/itest/lnd_coop_close_rbf_test.go @@ -1,29 +1,38 @@ package itest import ( - "fmt" - "testing" - - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/lightningnetwork/lnd/lnrpc" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lntest" - "github.com/lightningnetwork/lnd/lntest/node" - "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/stretchr/testify/require" ) -// runRbfCoopCloseTest encapsulates the parameters and logic for a -// single RBF coop close test run. -func runRbfCoopCloseTest(st *lntest.HarnessTest, - alice, bob *node.HarnessNode, - chanPoint *lnrpc.ChannelPoint, isTaproot bool) { +func testCoopCloseRbf(ht *lntest.HarnessTest) { + rbfCoopFlags := []string{"--protocol.rbf-coop-close"} + // Set the fee estimate to 1sat/vbyte. This ensures that our manually + // initiated RBF attempts will always be successful. + ht.SetFeeEstimate(250) + ht.SetFeeEstimateWithConf(250, 6) + + // To kick things off, we'll create two new nodes, then fund them with + // enough coins to make a 50/50 channel. + cfgs := [][]string{rbfCoopFlags, rbfCoopFlags} + params := lntest.OpenChannelParams{ + Amt: btcutil.Amount(1000000), + PushAmt: btcutil.Amount(1000000 / 2), + } + chanPoints, nodes := ht.CreateSimpleNetwork(cfgs, params) + alice, bob := nodes[0], nodes[1] + chanPoint := chanPoints[0] + + // Now that both sides are active with a funded channel, we can kick + // off the test. + // // To start, we'll have Alice try to close the channel, with a fee rate // of 5 sat/byte. aliceFeeRate := chainfee.SatPerVByte(5) - aliceCloseStream, aliceCloseUpdate := st.CloseChannelAssertPending( + aliceCloseStream, aliceCloseUpdate := ht.CloseChannelAssertPending( alice, chanPoint, false, lntest.WithCoopCloseFeeRate(aliceFeeRate), lntest.WithLocalTxNotify(), @@ -31,79 +40,62 @@ func runRbfCoopCloseTest(st *lntest.HarnessTest, // Confirm that this new update was at 5 sat/vb. alicePendingUpdate := aliceCloseUpdate.GetClosePending() - require.NotNil(st, aliceCloseUpdate) + require.NotNil(ht, aliceCloseUpdate) require.Equal( - st, int64(aliceFeeRate), alicePendingUpdate.FeePerVbyte, + ht, int64(aliceFeeRate), alicePendingUpdate.FeePerVbyte, ) - require.True(st, alicePendingUpdate.LocalCloseTx) + require.True(ht, alicePendingUpdate.LocalCloseTx) // Now, we'll have Bob attempt to RBF the close transaction with a // higher fee rate, double that of Alice's. bobFeeRate := aliceFeeRate * 2 - bobCloseStream, bobCloseUpdate := st.CloseChannelAssertPending( + bobCloseStream, bobCloseUpdate := ht.CloseChannelAssertPending( bob, chanPoint, false, lntest.WithCoopCloseFeeRate(bobFeeRate), lntest.WithLocalTxNotify(), ) // Confirm that this new update was at 10 sat/vb. bobPendingUpdate := bobCloseUpdate.GetClosePending() - require.NotNil(st, bobCloseUpdate) - require.Equal(st, bobPendingUpdate.FeePerVbyte, int64(bobFeeRate)) - require.True(st, bobPendingUpdate.LocalCloseTx) + require.NotNil(ht, bobCloseUpdate) + require.Equal(ht, bobPendingUpdate.FeePerVbyte, int64(bobFeeRate)) + require.True(ht, bobPendingUpdate.LocalCloseTx) var err error // Alice should've also received a similar update that Bob has // increased the closing fee rate to 10 sat/vb with his settled funds. - aliceCloseUpdate, err = st.ReceiveCloseChannelUpdate(aliceCloseStream) - require.NoError(st, err) + aliceCloseUpdate, err = ht.ReceiveCloseChannelUpdate(aliceCloseStream) + require.NoError(ht, err) alicePendingUpdate = aliceCloseUpdate.GetClosePending() - require.NotNil(st, aliceCloseUpdate) - - // For taproot channels, due to different witness sizes, - // the fee per vbyte might be slightly different due to - // rounding when converting between absolute fee and fee - // per vbyte. - if isTaproot { - // Allow for a small difference in fee - // calculation for taproot. - require.InDelta( - st, int64(bobFeeRate), - alicePendingUpdate.FeePerVbyte, 1, - ) - } else { - require.Equal( - st, alicePendingUpdate.FeePerVbyte, - int64(bobFeeRate), - ) - } - require.False(st, alicePendingUpdate.LocalCloseTx) + require.NotNil(ht, aliceCloseUpdate) + require.Equal(ht, alicePendingUpdate.FeePerVbyte, int64(bobFeeRate)) + require.False(ht, alicePendingUpdate.LocalCloseTx) // We'll now attempt to make a fee update that increases Alice's fee // rate by 6 sat/vb, which should be rejected as it is too small of an // increase for the RBF rules. The RPC API however will return the new // fee. We'll skip the mempool check here as it won't make it in. aliceRejectedFeeRate := aliceFeeRate + 1 - _, aliceCloseUpdate = st.CloseChannelAssertPending( + _, aliceCloseUpdate = ht.CloseChannelAssertPending( alice, chanPoint, false, lntest.WithCoopCloseFeeRate(aliceRejectedFeeRate), lntest.WithLocalTxNotify(), lntest.WithSkipMempoolCheck(), ) alicePendingUpdate = aliceCloseUpdate.GetClosePending() - require.NotNil(st, aliceCloseUpdate) + require.NotNil(ht, aliceCloseUpdate) require.Equal( - st, alicePendingUpdate.FeePerVbyte, + ht, alicePendingUpdate.FeePerVbyte, int64(aliceRejectedFeeRate), ) - require.True(st, alicePendingUpdate.LocalCloseTx) + require.True(ht, alicePendingUpdate.LocalCloseTx) - _, err = st.ReceiveCloseChannelUpdate(bobCloseStream) - require.NoError(st, err) + _, err = ht.ReceiveCloseChannelUpdate(bobCloseStream) + require.NoError(ht, err) // We'll now attempt a fee update that we can't actually pay for. This // will actually show up as an error to the remote party. aliceRejectedFeeRate = 100_000 - _, _ = st.CloseChannelAssertPending( + _, _ = ht.CloseChannelAssertPending( alice, chanPoint, false, lntest.WithCoopCloseFeeRate(aliceRejectedFeeRate), lntest.WithLocalTxNotify(), @@ -112,97 +104,32 @@ func runRbfCoopCloseTest(st *lntest.HarnessTest, // At this point, we'll have Alice+Bob reconnect so we can ensure that // we can continue to do RBF bumps even after a reconnection. - st.DisconnectNodes(alice, bob) - st.ConnectNodes(alice, bob) + ht.DisconnectNodes(alice, bob) + ht.ConnectNodes(alice, bob) // Next, we'll have Alice double that fee rate again to 20 sat/vb. aliceFeeRate = bobFeeRate * 2 - aliceCloseStream, aliceCloseUpdate = st.CloseChannelAssertPending( + aliceCloseStream, aliceCloseUpdate = ht.CloseChannelAssertPending( alice, chanPoint, false, lntest.WithCoopCloseFeeRate(aliceFeeRate), lntest.WithLocalTxNotify(), ) alicePendingUpdate = aliceCloseUpdate.GetClosePending() - require.NotNil(st, aliceCloseUpdate) + require.NotNil(ht, aliceCloseUpdate) require.Equal( - st, alicePendingUpdate.FeePerVbyte, int64(aliceFeeRate), + ht, alicePendingUpdate.FeePerVbyte, int64(aliceFeeRate), ) - require.True(st, alicePendingUpdate.LocalCloseTx) + require.True(ht, alicePendingUpdate.LocalCloseTx) // To conclude, we'll mine a block which should now confirm Alice's // version of the coop close transaction. - block := st.MineBlocksAndAssertNumTxes(1, 1)[0] + block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] // Both Alice and Bob should trigger a final close update to signal the // closing transaction has confirmed. - aliceClosingTxid := st.WaitForChannelCloseEvent(aliceCloseStream) - st.AssertTxInBlock(block, aliceClosingTxid) -} - -func testCoopCloseRbf(ht *lntest.HarnessTest) { - // Test with different channel types including taproot - channelTypes := []struct { - name string - commitType lnrpc.CommitmentType - }{ - { - name: "anchors", - commitType: lnrpc.CommitmentType_ANCHORS, - }, - { - name: "taproot", - commitType: lnrpc.CommitmentType_SIMPLE_TAPROOT, - }, - } - - for _, chanType := range channelTypes { - ht.Run(chanType.name, func(t1 *testing.T) { - st := ht.Subtest(t1) - // Set the fee estimate to 1sat/vbyte. This ensures that - // our manually initiated RBF attempts will always be - // successful. - st.SetFeeEstimate(250) - st.SetFeeEstimateWithConf(250, 6) - - // Build node config with commitment type args and RBF - // flag. - baseArgs := lntest.NodeArgsForCommitType( - chanType.commitType, - ) - baseArgs = append( - baseArgs, "--protocol.rbf-coop-close", - ) - nodeArgs := baseArgs - cfgs := [][]string{nodeArgs, nodeArgs} - - // For taproot channels, we need to make them private. - isTaproot := chanType.commitType == - lnrpc.CommitmentType_SIMPLE_TAPROOT - - params := lntest.OpenChannelParams{ - Amt: btcutil.Amount(1000000), - PushAmt: btcutil.Amount(1000000 / 2), - CommitmentType: chanType.commitType, - Private: isTaproot, - } - - // Create network with Alice -> Bob channel, then use - // that to run the RBF coop close test. - chanPoints, nodes := st.CreateSimpleNetwork( - cfgs, params, - ) - alice, bob := nodes[0], nodes[1] - chanPoint := chanPoints[0] - - runRbfCoopCloseTest( - st, alice, bob, chanPoint, isTaproot, - ) - - st.Shutdown(alice) - st.Shutdown(bob) - }) - } + aliceClosingTxid := ht.WaitForChannelCloseEvent(aliceCloseStream) + ht.AssertTxInBlock(block, aliceClosingTxid) } // testRBFCoopCloseDisconnect tests that when a node disconnects that the node @@ -226,174 +153,3 @@ func testRBFCoopCloseDisconnect(ht *lntest.HarnessTest) { // Disconnect Bob from Alice. ht.DisconnectNodes(alice, bob) } - -// testCoopCloseRBFWithReorg tests that the RBF cooperative close flow handles -// chain reorganizations correctly. It verifies that when a close transaction -// is reorged out, the system can still confirm with any valid close tx. -func testCoopCloseRBFWithReorg(ht *lntest.HarnessTest) { - // Skip this test for neutrino backend as we can't trigger reorgs. - if ht.IsNeutrinoBackend() { - ht.Skipf("skipping reorg test for neutrino backend") - } - - // Force cooperative close to require 3 confirmations for predictable - // testing. - const requiredConfs = 3 - rbfCoopFlags := []string{ - "--protocol.rbf-coop-close", - "--dev.force-channel-close-confs=3", - } - - // Set the fee estimate to 1sat/vbyte to ensure our RBF attempts work. - ht.SetFeeEstimate(250) - ht.SetFeeEstimateWithConf(250, 6) - - // Create two nodes with enough coins for a 50/50 channel. - cfgs := [][]string{rbfCoopFlags, rbfCoopFlags} - params := lntest.OpenChannelParams{ - Amt: btcutil.Amount(10_000_000), - PushAmt: btcutil.Amount(5_000_000), - } - chanPoints, nodes := ht.CreateSimpleNetwork(cfgs, params) - alice, bob := nodes[0], nodes[1] - chanPoint := chanPoints[0] - - // Initiate cooperative close with initial fee rate of 5 sat/vb. - initialFeeRate := chainfee.SatPerVByte(5) - _, aliceCloseUpdate := ht.CloseChannelAssertPending( - alice, chanPoint, false, - lntest.WithCoopCloseFeeRate(initialFeeRate), - lntest.WithLocalTxNotify(), - ) - - // Verify the initial close transaction is at the expected fee rate. - alicePendingUpdate := aliceCloseUpdate.GetClosePending() - require.NotNil(ht, aliceCloseUpdate) - require.Equal( - ht, int64(initialFeeRate), alicePendingUpdate.FeePerVbyte, - ) - - // Capture the initial close transaction from the mempool. - initialCloseTxid, err := chainhash.NewHash(alicePendingUpdate.Txid) - require.NoError(ht, err) - initialCloseTx := ht.AssertTxInMempool(*initialCloseTxid) - - // Create first RBF replacement before any mining. - firstRbfFeeRate := chainfee.SatPerVByte(10) - _, firstRbfUpdate := ht.CloseChannelAssertPending( - bob, chanPoint, false, - lntest.WithCoopCloseFeeRate(firstRbfFeeRate), - lntest.WithLocalTxNotify(), - ) - - // Capture the first RBF transaction. - closePending := firstRbfUpdate.GetClosePending() - firstRbfTxid, err := chainhash.NewHash(closePending.Txid) - require.NoError(ht, err) - firstRbfTx := ht.AssertTxInMempool(*firstRbfTxid) - - _, bestHeight := ht.GetBestBlock() - ht.Logf("Current block height: %d", bestHeight) - - // Mine n-1 blocks (2 blocks when requiring 3 confirmations) with the - // first RBF transaction. This is just shy of full confirmation. - block1 := ht.Miner().MineBlockWithTxes( - []*btcutil.Tx{btcutil.NewTx(firstRbfTx)}, - ) - - ht.Logf("Mined block %d with first RBF tx", bestHeight+1) - - block2 := ht.MineEmptyBlocks(1)[0] - - ht.Logf("Mined block %d", bestHeight+2) - - ht.Logf("Re-orging two blocks to remove first RBF tx") - - // Trigger a reorganization that removes the last 2 blocks. This is - // safe because we haven't reached full confirmation yet. - bestBlockHash := block2.Header.BlockHash() - require.NoError( - ht, ht.Miner().InvalidateBlock(&bestBlockHash), - ) - bestBlockHash = block1.Header.BlockHash() - require.NoError( - ht, ht.Miner().InvalidateBlock(&bestBlockHash), - ) - - _, bestHeight = ht.GetBestBlock() - ht.Logf("Re-orged to block height: %d", bestHeight) - - ht.Log("Mining blocks to surpass previous chain") - - // Mine 2 empty blocks to trigger the reorg on the nodes. - ht.MineEmptyBlocks(2) - - _, bestHeight = ht.GetBestBlock() - ht.Logf("Mined blocks to reach height: %d", bestHeight) - - // Now, instead of mining the second RBF, mine the INITIAL transaction - // to test that the system can handle any valid spend of the funding - // output. - block := ht.Miner().MineBlockWithTxes( - []*btcutil.Tx{btcutil.NewTx(initialCloseTx)}, - ) - ht.AssertTxInBlock(block, *initialCloseTxid) - - // Mine additional blocks to reach the required confirmations. - ht.MineEmptyBlocks(requiredConfs - 1) - - // Both parties should see that the channel is now fully closed on - // chain with the expected closing txid. - expectedClosingTxid := initialCloseTxid.String() - err = wait.NoError(func() error { - req := &lnrpc.ClosedChannelsRequest{} - aliceClosedChans := alice.RPC.ClosedChannels(req) - bobClosedChans := bob.RPC.ClosedChannels(req) - if len(aliceClosedChans.Channels) != 1 { - return fmt.Errorf("alice: expected 1 closed "+ - "chan, got %d", - len(aliceClosedChans.Channels)) - } - if len(bobClosedChans.Channels) != 1 { - return fmt.Errorf("bob: expected 1 closed "+ - "chan, got %d", - len(bobClosedChans.Channels)) - } - - aliceClosedChan := aliceClosedChans.Channels[0] - if aliceClosedChan.ClosingTxHash != expectedClosingTxid { - return fmt.Errorf("alice: expected closing "+ - "txid %s, got %s", - expectedClosingTxid, - aliceClosedChan.ClosingTxHash) - } - if aliceClosedChan.CloseType != - lnrpc.ChannelCloseSummary_COOPERATIVE_CLOSE { - - return fmt.Errorf("alice: expected cooperative "+ - "close, got %v", - aliceClosedChan.CloseType) - } - - bobClosedChan := bobClosedChans.Channels[0] - if bobClosedChan.ClosingTxHash != expectedClosingTxid { - return fmt.Errorf("bob: expected closing "+ - "txid %s, got %s", - expectedClosingTxid, - bobClosedChan.ClosingTxHash) - } - if bobClosedChan.CloseType != - lnrpc.ChannelCloseSummary_COOPERATIVE_CLOSE { - - return fmt.Errorf("bob: expected cooperative "+ - "close, got %v", - bobClosedChan.CloseType) - } - - return nil - }, defaultTimeout) - require.NoError(ht, err) - - ht.Logf("Successfully verified closing txid: %s", - expectedClosingTxid) -} diff --git a/itest/lnd_coop_close_with_htlcs_test.go b/itest/lnd_coop_close_with_htlcs_test.go index f4654288a..06d4dc9ab 100644 --- a/itest/lnd_coop_close_with_htlcs_test.go +++ b/itest/lnd_coop_close_with_htlcs_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" @@ -72,6 +72,7 @@ func testCoopCloseWithHtlcs(ht *lntest.HarnessTest) { testCases := createFlagCombos() for _, testCase := range testCases { + testCase := testCase // Capture range variable. ht.Run(testCase.testName, func(t *testing.T) { tt := ht.Subtest(t) @@ -93,6 +94,7 @@ func testCoopCloseWithHtlcsWithRestart(ht *lntest.HarnessTest) { testCases := createFlagCombos() for _, testCase := range testCases { + testCase := testCase // Capture range variable. ht.Run(testCase.testName, func(t *testing.T) { tt := ht.Subtest(t) diff --git a/itest/lnd_estimate_on_chain_fee_test.go b/itest/lnd_estimate_on_chain_fee_test.go deleted file mode 100644 index 93130b974..000000000 --- a/itest/lnd_estimate_on_chain_fee_test.go +++ /dev/null @@ -1,232 +0,0 @@ -package itest - -import ( - "fmt" - - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/lnrpc/walletrpc" - "github.com/lightningnetwork/lnd/lntest" - "github.com/stretchr/testify/require" -) - -// testEstimateOnChainFeeWithSelectedInputs tests that the EstimateFee RPC -// with specific input selection produces an accurate fee estimate that matches -// the actual fee when sending coins with the same inputs. -func testEstimateOnChainFeeWithSelectedInputs(ht *lntest.HarnessTest) { - // Create a new node for this test. - alice := ht.NewNode("Alice", nil) - - // Fund Alice with multiple UTXOs of different amounts to give us - // several inputs to choose from. - const ( - utxo1Amount = btcutil.Amount(500_000) - utxo2Amount = btcutil.Amount(300_000) - utxo3Amount = btcutil.Amount(200_000) - targetConf = 2 - ) - - ht.FundCoins(utxo1Amount, alice) - ht.FundCoins(utxo2Amount, alice) - ht.FundCoins(utxo3Amount, alice) - - // List Alice's UTXOs to get their outpoints. - utxosResp := alice.RPC.ListUnspent(&walletrpc.ListUnspentRequest{ - MinConfs: 1, - MaxConfs: 1000, - }) - require.GreaterOrEqual(ht, len(utxosResp.Utxos), 3, - "expected at least 3 UTXOs") - - // Create a lookup map to find outpoints by amount. - utxoLookup := make(map[int64]*lnrpc.OutPoint) - for _, utxo := range utxosResp.Utxos { - utxoLookup[utxo.AmountSat] = utxo.Outpoint - } - - // Select the first two UTXOs for our test. - selectedInputs := []*lnrpc.OutPoint{ - utxoLookup[int64(utxo1Amount)], - utxoLookup[int64(utxo2Amount)], - } - require.NotNil(ht, selectedInputs[0], "first UTXO not found") - require.NotNil(ht, selectedInputs[1], "second UTXO not found") - - // Generate a destination address for the transaction. - destAddrResp := alice.RPC.NewAddress(&lnrpc.NewAddressRequest{ - Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, - }) - - // Amount to send (less than the sum of selected UTXOs to allow for - // fees and change). - const sendAmount = int64(400_000) - - // Create an address-to-amount map for the transaction. - addrToAmount := map[string]int64{ - destAddrResp.Address: sendAmount, - } - - // Call EstimateFee with the selected inputs. - estimateReq := &lnrpc.EstimateFeeRequest{ - AddrToAmount: addrToAmount, - TargetConf: targetConf, - Inputs: selectedInputs, - } - estimateResp, err := alice.RPC.LN.EstimateFee(ht.Context(), estimateReq) - require.NoError(ht, err, "EstimateFee failed") - - ht.Logf("Fee estimate: %d sats, fee rate: %d sat/vbyte", - estimateResp.FeeSat, estimateResp.SatPerVbyte) - - // Verify that the estimate response includes the inputs we specified. - require.Len(ht, estimateResp.Inputs, 2, - "expected 2 inputs in estimate response") - - // Create a map of input outpoints from the estimate for an easy lookup. - estimateInputs := make(map[string]bool) - for _, input := range estimateResp.Inputs { - key := fmt.Sprintf("%s:%d", input.TxidStr, input.OutputIndex) - estimateInputs[key] = true - } - - // Verify each selected outpoint is in the estimate. - for _, outpoint := range selectedInputs { - key := fmt.Sprintf("%s:%d", outpoint.TxidStr, - outpoint.OutputIndex) - - require.True(ht, estimateInputs[key], - "outpoint %s not found in estimate inputs", key) - } - - // Now actually send coins using the same parameters. - sendReq := &lnrpc.SendCoinsRequest{ - Addr: destAddrResp.Address, - Amount: sendAmount, - TargetConf: targetConf, - Outpoints: selectedInputs, - } - sendResp := alice.RPC.SendCoins(sendReq) - txid := sendResp.Txid - - ht.Logf("Transaction sent with txid: %s", txid) - - // Get the transaction details to extract the actual fee paid. - txDetails := alice.RPC.GetTransactions( - &lnrpc.GetTransactionsRequest{ - StartHeight: 0, - EndHeight: -1, - }, - ) - - // Find our transaction in the list. - var actualFee int64 - var found bool - for _, tx := range txDetails.Transactions { - if tx.TxHash == txid { - actualFee = tx.TotalFees - found = true - ht.Logf("Actual fee paid: %d sats", actualFee) - - break - } - } - require.True(ht, found, "sent transaction not found") - - require.EqualValues( - ht, estimateResp.FeeSat, actualFee, "fee estimate does not "+ - "match actual fee", - ) - - // Mine the SendCoinsRequest. - ht.MineBlocksAndAssertNumTxes(1, 1) -} - -// testEstimateOnChainFeeAutoSelectedInputs tests that the EstimateFee RPC -// without input selection allows the wallet to auto-select inputs, and that -// using those selected inputs in SendCoins produces a matching fee. -func testEstimateOnChainFeeAutoSelectedInputs(ht *lntest.HarnessTest) { - // Create a new node for this test. - alice := ht.NewNode("Alice", nil) - - // Fund Alice with multiple UTXOs. - const ( - utxo1Amount = btcutil.Amount(500_000) - utxo2Amount = btcutil.Amount(300_000) - utxo3Amount = btcutil.Amount(200_000) - targetConf = 2 - ) - - ht.FundCoins(utxo1Amount, alice) - ht.FundCoins(utxo2Amount, alice) - ht.FundCoins(utxo3Amount, alice) - - // Generate a destination address. - destAddrResp := alice.RPC.NewAddress( - &lnrpc.NewAddressRequest{ - Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, - }, - ) - - const sendAmount = int64(100_000) - addrToAmount := map[string]int64{ - destAddrResp.Address: sendAmount, - } - - // Estimate fee without specifying inputs (wallet auto-selects). - estimateReq := &lnrpc.EstimateFeeRequest{ - AddrToAmount: addrToAmount, - TargetConf: targetConf, - } - estimateResp, err := alice.RPC.LN.EstimateFee(ht.Context(), estimateReq) - require.NoError(ht, err, "EstimateFee failed") - - ht.Logf("Fee estimate (auto-select): %d sats, "+ - "fee rate: %d sat/vbyte, inputs selected: %d", - estimateResp.FeeSat, estimateResp.SatPerVbyte, - len(estimateResp.Inputs)) - - // The estimate should have selected some inputs. - require.NotEmpty(ht, estimateResp.Inputs, - "estimate should have selected inputs") - - // Send using the inputs that were selected by the estimate. - sendReq := &lnrpc.SendCoinsRequest{ - Addr: destAddrResp.Address, - Amount: sendAmount, - TargetConf: targetConf, - Outpoints: estimateResp.Inputs, - } - sendResp := alice.RPC.SendCoins(sendReq) - txid := sendResp.Txid - - ht.Logf("Transaction sent with txid: %s", txid) - - // Get the actual fee. - txDetails := alice.RPC.GetTransactions( - &lnrpc.GetTransactionsRequest{ - StartHeight: 0, - EndHeight: -1, - }, - ) - - var actualFee int64 - var found bool - for _, tx := range txDetails.Transactions { - if tx.TxHash == txid { - actualFee = tx.TotalFees - found = true - ht.Logf("Actual fee paid: %d sats", actualFee) - - break - } - } - require.True(ht, found, "sent transaction not found") - - require.EqualValues( - ht, estimateResp.FeeSat, actualFee, "fee estimate does not "+ - "match actual fee", - ) - - // Mine the SendCoinsRequest. - ht.MineBlocksAndAssertNumTxes(1, 1) -} diff --git a/itest/lnd_estimate_route_fee_test.go b/itest/lnd_estimate_route_fee_test.go index c9f40eac7..07329d969 100644 --- a/itest/lnd_estimate_route_fee_test.go +++ b/itest/lnd_estimate_route_fee_test.go @@ -3,7 +3,7 @@ package itest import ( "testing" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" @@ -19,14 +19,12 @@ var ( probeAmount = btcutil.Amount(100_000) probeAmt = int64(probeAmount) * 1_000 - failureReasonNone = lnrpc.PaymentFailureReason_FAILURE_REASON_NONE //nolint:ll - failureReasonNoRoute = lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE //nolint:ll - failureInsufficientBalance = lnrpc.PaymentFailureReason_FAILURE_REASON_INSUFFICIENT_BALANCE //nolint:ll + failureReasonNone = lnrpc.PaymentFailureReason_FAILURE_REASON_NONE + failureReasonNoRoute = lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE //nolint:ll ) const ( - ErrNoRouteInGraph = "unable to find a path to destination" - ErrInsufficientBalance = "insufficient local balance" + ErrNoRouteInGraph = "unable to find a path to destination" ) type estimateRouteFeeTestCase struct { @@ -43,10 +41,6 @@ type estimateRouteFeeTestCase struct { // routeHints are the route hints that will be used for the probe. routeHints []*lnrpc.RouteHint - // outgoingChanIds is the list of channel IDs allowed for the first - // hop. If empty, any channel may be used. - outgoingChanIds []uint64 - // expectedRoutingFeesMsat are the expected routing fees that will be // returned by the probe. expectedRoutingFeesMsat int64 @@ -160,11 +154,6 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) { require.Len(ht, davesPrivChannels.Channels, 1) daveFrankChanID := davesPrivChannels.Channels[0].ChanId - channelAliceCarol := ht.QueryChannelByChanPoint( - mts.alice, mts.channelPoints[0], - ) - aliceCarolChanID := channelAliceCarol.ChanId - // Let's disable the paths from Alice to Bob through Dave and Eve with // high fees. This ensures that the path estimates are based on Carol's // channel to Bob for the first set of tests. @@ -452,58 +441,6 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) { highestFeeRouteDelta, expectedFailureReason: failureReasonNone, }, - // Test probe-based estimation with a specific outgoing channel. - // We specify the Alice-Carol channel, so the route should go - // through Carol to Bob. - { - name: "probe based estimate with " + - "outgoing channel", - probing: true, - destination: mts.bob, - routeHints: []*lnrpc.RouteHint{}, - outgoingChanIds: []uint64{aliceCarolChanID}, - expectedRoutingFeesMsat: feeStandardSingleHop, - expectedCltvDelta: locktime + deltaCB, - expectedFailureReason: failureReasonNone, - }, - // Test probe-based estimation with a non-existent outgoing - // channel. This should fail because the specified channel - // doesn't exist and has no balance. - { - name: "probe based estimate with " + - "invalid outgoing channel", - probing: true, - destination: mts.bob, - routeHints: []*lnrpc.RouteHint{}, - outgoingChanIds: []uint64{999999999}, - expectedRoutingFeesMsat: 0, - expectedCltvDelta: 0, - expectedFailureReason: failureInsufficientBalance, - }, - // Test graph-based estimation with a specific outgoing channel. - // We specify the Alice-Carol channel, so the route should go - // through Carol to Bob. - { - name: "graph based estimate with " + - "outgoing channel", - probing: false, - destination: mts.bob, - outgoingChanIds: []uint64{aliceCarolChanID}, - expectedRoutingFeesMsat: feeStandardSingleHop, - expectedCltvDelta: locktime + deltaCB, - expectedFailureReason: failureReasonNone, - }, - // Test graph-based estimation with a non-existent outgoing - // channel. This should fail because the specified channel - // doesn't exist and has no balance. - { - name: "graph based estimate with " + - "invalid outgoing channel", - probing: false, - destination: mts.bob, - outgoingChanIds: []uint64{999999999}, - expectedError: ErrInsufficientBalance, - }, } for _, testCase := range testCases { @@ -530,15 +467,13 @@ func runFeeEstimationTestCase(ht *lntest.HarnessTest, tc.destination, probeAmount, 1, tc.routeHints..., ) feeReq = &routerrpc.RouteFeeRequest{ - PaymentRequest: payReqs[0], - Timeout: uint32(wait.PaymentTimeout.Seconds()), - OutgoingChanIds: tc.outgoingChanIds, + PaymentRequest: payReqs[0], + Timeout: uint32(wait.PaymentTimeout.Seconds()), } } else { feeReq = &routerrpc.RouteFeeRequest{ - Dest: tc.destination.PubKey[:], - AmtSat: int64(probeAmount), - OutgoingChanIds: tc.outgoingChanIds, + Dest: tc.destination.PubKey[:], + AmtSat: int64(probeAmount), } } diff --git a/itest/lnd_etcd_failover_test.go b/itest/lnd_etcd_failover_test.go index ef361230e..53b1dd6b3 100644 --- a/itest/lnd_etcd_failover_test.go +++ b/itest/lnd_etcd_failover_test.go @@ -12,7 +12,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/cluster" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lncfg" @@ -47,6 +47,7 @@ func testEtcdFailover(ht *lntest.HarnessTest) { }} for _, test := range testCases { + test := test success := ht.Run(test.name, func(t1 *testing.T) { st := ht.Subtest(t1) diff --git a/itest/lnd_experimental_accountability.go b/itest/lnd_experimental_endorsement.go similarity index 60% rename from itest/lnd_experimental_accountability.go rename to itest/lnd_experimental_endorsement.go index 56b5d0b04..67f5e30c0 100644 --- a/itest/lnd_experimental_accountability.go +++ b/itest/lnd_experimental_endorsement.go @@ -3,7 +3,7 @@ package itest import ( "math" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lntest" @@ -15,19 +15,19 @@ import ( "github.com/stretchr/testify/require" ) -// testExperimentalAccountability tests setting of positive and negative -// experimental accountable signals. -func testExperimentalAccountability(ht *lntest.HarnessTest) { - testAccountability(ht, true) - testAccountability(ht, false) +// testExperimentalEndorsement tests setting of positive and negative +// experimental endorsement signals. +func testExperimentalEndorsement(ht *lntest.HarnessTest) { + testEndorsement(ht, true) + testEndorsement(ht, false) } -// testAccountability sets up a 5 hop network and tests propagation of -// experimental accountable signals. -func testAccountability(ht *lntest.HarnessTest, aliceAccountable bool) { +// testEndorsement sets up a 5 hop network and tests propagation of +// experimental endorsement signals. +func testEndorsement(ht *lntest.HarnessTest, aliceEndorse bool) { cfg := node.CfgAnchor carolCfg := append( - []string{"--protocol.no-experimental-accountability"}, cfg..., + []string{"--protocol.no-experimental-endorsement"}, cfg..., ) cfgs := [][]string{cfg, cfg, carolCfg, cfg, cfg} @@ -57,12 +57,18 @@ func testAccountability(ht *lntest.HarnessTest, aliceAccountable bool) { FeeLimitMsat: math.MaxInt64, } - expectedValue := []byte{lnwire.ExperimentalUnaccountable} - if aliceAccountable { - expectedValue = []byte{lnwire.ExperimentalAccountable} - t := uint64(lnwire.ExperimentalAccountableType) - sendReq.FirstHopCustomRecords = map[uint64][]byte{ - t: expectedValue, + var expectedValue []byte + hasEndorsement := lntest.ExperimentalEndorsementActive() + + if hasEndorsement { + if aliceEndorse { + expectedValue = []byte{lnwire.ExperimentalEndorsed} + t := uint64(lnwire.ExperimentalEndorsementType) + sendReq.FirstHopCustomRecords = map[uint64][]byte{ + t: expectedValue, + } + } else { + expectedValue = []byte{lnwire.ExperimentalUnendorsed} } } @@ -70,28 +76,29 @@ func testAccountability(ht *lntest.HarnessTest, aliceAccountable bool) { // Validate that our signal (positive or zero) propagates until carol // and then is dropped because she has disabled the feature. - validateAccountableAndResume( - ht, bobIntercept, true, expectedValue, + // When the endorsement experiment is not active, no signal is sent. + validateEndorsedAndResume( + ht, bobIntercept, hasEndorsement, expectedValue, ) - validateAccountableAndResume( - ht, carolIntercept, true, expectedValue, + validateEndorsedAndResume( + ht, carolIntercept, hasEndorsement, expectedValue, ) - validateAccountableAndResume(ht, daveIntercept, false, nil) + validateEndorsedAndResume(ht, daveIntercept, false, nil) var preimage lntypes.Preimage copy(preimage[:], invoice.RPreimage) ht.AssertPaymentStatus(alice, preimage.Hash(), lnrpc.Payment_SUCCEEDED) } -func validateAccountableAndResume(ht *lntest.HarnessTest, - interceptor rpc.InterceptorClient, hasAccountable bool, +func validateEndorsedAndResume(ht *lntest.HarnessTest, + interceptor rpc.InterceptorClient, hasEndorsement bool, expectedValue []byte) { packet := ht.ReceiveHtlcInterceptor(interceptor) var expectedRecords map[uint64][]byte - if hasAccountable { - u64Type := uint64(lnwire.ExperimentalAccountableType) + if hasEndorsement { + u64Type := uint64(lnwire.ExperimentalEndorsementType) expectedRecords = map[uint64][]byte{ u64Type: expectedValue, } diff --git a/itest/lnd_forward_delete_test.go b/itest/lnd_forward_delete_test.go deleted file mode 100644 index 0361d7e23..000000000 --- a/itest/lnd_forward_delete_test.go +++ /dev/null @@ -1,475 +0,0 @@ -package itest - -import ( - "fmt" - "testing" - "time" - - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/lnrpc/routerrpc" - "github.com/lightningnetwork/lnd/lntest" - "github.com/lightningnetwork/lnd/lntest/wait" - "github.com/stretchr/testify/require" -) - -// testDeleteForwardingHistory tests the deletion of forwarding history events. -func testDeleteForwardingHistory(ht *lntest.HarnessTest) { - // Run subtests for different deletion scenarios. - testCases := []struct { - name string - test func(ht *lntest.HarnessTest) - }{ - { - name: "basic deletion", - test: testBasicDeletion, - }, - { - name: "partial deletion", - test: testPartialDeletion, - }, - { - name: "empty database", - test: testEmptyDatabaseDeletion, - }, - { - name: "idempotency", - test: testDeletionIdempotency, - }, - { - name: "time formats", - test: testTimeFormats, - }, - } - - for _, tc := range testCases { - success := ht.Run(tc.name, func(t *testing.T) { - st := ht.Subtest(t) - tc.test(st) - }) - - if !success { - return - } - } -} - -// testBasicDeletion tests basic forwarding history deletion functionality. -func testBasicDeletion(ht *lntest.HarnessTest) { - // Create a three-hop network: Alice -> Bob -> Carol. - const chanAmt = btcutil.Amount(300000) - p := lntest.OpenChannelParams{Amt: chanAmt} - - cfgs := [][]string{nil, {"--dev.min-fwd-history-age=2s"}, nil} - chanPoints, nodes := ht.CreateSimpleNetwork(cfgs, p) - alice, bob, carol := nodes[0], nodes[1], nodes[2] - _ = chanPoints - - const numPayments = 10 - const paymentAmt = 1000 - - // Send multiple payments from Alice to Carol through Bob. Sleep after - // each payment to ensure minimum age validation. - for i := 0; i < numPayments; i++ { - invoice := carol.RPC.AddInvoice(&lnrpc.Invoice{ - ValueMsat: paymentAmt, - Memo: fmt.Sprintf("test payment %d", i), - }) - - payReq := &routerrpc.SendPaymentRequest{ - PaymentRequest: invoice.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: 100000, - } - - ht.SendPaymentAssertSettled(alice, payReq) - - // Sleep to ensure events are old enough. - time.Sleep(time.Second) - } - - // Sleep an additional 2 seconds to ensure all events are old enough. - time.Sleep(2 * time.Second) - - // Query Bob's forwarding history to verify events exist. The switch - // flushes forwarding events to the DB asynchronously on a 15-second - // ticker, so poll until the expected count appears. - var fwdHistory *lnrpc.ForwardingHistoryResponse - err := wait.NoError(func() error { - fwdHistory = bob.RPC.ForwardingHistory(nil) - if len(fwdHistory.ForwardingEvents) != numPayments { - return fmt.Errorf("expected %d forwarding events, "+ - "got %d", numPayments, - len(fwdHistory.ForwardingEvents)) - } - - return nil - }, wait.DefaultTimeout) - require.NoError(ht, err, "timed out waiting for forwarding events") - - // Calculate expected total fees. - var expectedFees int64 - for _, event := range fwdHistory.ForwardingEvents { - expectedFees += int64(event.FeeMsat) - } - - // Record the timestamp of the last event for testing. - // - //nolint:ll - lastTimestamp := fwdHistory.ForwardingEvents[len(fwdHistory.ForwardingEvents)-1].TimestampNs - - // Delete all forwarding events using a timestamp that's 2 seconds in - // the past to satisfy the minimum age validation. - // - //nolint:ll - delResp := bob.RPC.DeleteForwardingHistory( - &routerrpc.DeleteForwardingHistoryRequest{ - TimeSpec: &routerrpc.DeleteForwardingHistoryRequest_DeleteBeforeTime{ - DeleteBeforeTime: uint64(time.Now().Add( - -2 * time.Second).Unix(), - ), - }, - }, - ) - - // Verify deletion statistics. - require.Equal( - ht, uint64(numPayments), delResp.EventsDeleted, - "wrong number of events deleted", - ) - require.Equal( - ht, expectedFees, delResp.TotalFeeMsat, - "wrong total fees", - ) - require.Contains( - ht, delResp.Status, "Successfully deleted", - "unexpected status message", - ) - - // Query forwarding history again to verify events are deleted. - fwdHistoryAfter := bob.RPC.ForwardingHistory(nil) - require.Empty( - ht, fwdHistoryAfter.ForwardingEvents, - "forwarding events should be deleted", - ) - - // Verify that the last event timestamp is no longer in the history. - fwdHistorySpecific := bob.RPC.ForwardingHistory( - &lnrpc.ForwardingHistoryRequest{ - StartTime: 0, - EndTime: lastTimestamp, - }, - ) - require.Empty( - ht, fwdHistorySpecific.ForwardingEvents, - "specific time range query should return no events", - ) -} - -// testPartialDeletion tests deleting only a subset of forwarding events. -func testPartialDeletion(ht *lntest.HarnessTest) { - // Create a three-hop network: Alice -> Bob -> Carol. - const chanAmt = btcutil.Amount(300000) - p := lntest.OpenChannelParams{Amt: chanAmt} - - cfgs := [][]string{nil, {"--dev.min-fwd-history-age=2s"}, nil} - chanPoints, nodes := ht.CreateSimpleNetwork(cfgs, p) - alice, bob, carol := nodes[0], nodes[1], nodes[2] - _ = chanPoints - - const firstBatch = 5 - const paymentAmt = 1000 - - // Send first batch of payments. - for i := 0; i < firstBatch; i++ { - invoice := carol.RPC.AddInvoice(&lnrpc.Invoice{ - ValueMsat: paymentAmt, - Memo: fmt.Sprintf("batch 1 payment %d", i), - }) - - payReq := &routerrpc.SendPaymentRequest{ - PaymentRequest: invoice.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: 100000, - } - - ht.SendPaymentAssertSettled(alice, payReq) - - // Sleep to ensure events are old enough. - time.Sleep(time.Second) - } - - // Record the timestamp after first batch. - cutoffTime := time.Now() - - // Send a second batch of payments. - const secondBatch = 5 - for i := 0; i < secondBatch; i++ { - invoice := carol.RPC.AddInvoice(&lnrpc.Invoice{ - ValueMsat: paymentAmt, - Memo: fmt.Sprintf("batch 2 payment %d", i), - }) - - payReq := &routerrpc.SendPaymentRequest{ - PaymentRequest: invoice.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: 100000, - } - - ht.SendPaymentAssertSettled(alice, payReq) - } - - // Query Bob's forwarding history to verify all events exist. The switch - // flushes forwarding events to the DB asynchronously on a 15-second - // ticker, so poll until the expected count appears. - totalExpected := firstBatch + secondBatch - var fwdHistory *lnrpc.ForwardingHistoryResponse - err := wait.NoError(func() error { - fwdHistory = bob.RPC.ForwardingHistory(nil) - if len(fwdHistory.ForwardingEvents) != totalExpected { - return fmt.Errorf("expected %d forwarding events, "+ - "got %d", totalExpected, - len(fwdHistory.ForwardingEvents)) - } - - return nil - }, wait.DefaultTimeout) - require.NoError(ht, err, "timed out waiting for forwarding events") - - // Delete only the first batch of events using the cutoff time. - // - //nolint:ll - delResp := bob.RPC.DeleteForwardingHistory( - &routerrpc.DeleteForwardingHistoryRequest{ - TimeSpec: &routerrpc.DeleteForwardingHistoryRequest_DeleteBeforeTime{ - DeleteBeforeTime: uint64(cutoffTime.Unix()), - }, - }, - ) - - // Should have deleted approximately the first batch. - require.LessOrEqual( - ht, delResp.EventsDeleted, uint64(firstBatch), - "deleted more events than expected", - ) - require.Greater( - ht, delResp.EventsDeleted, uint64(0), - "should have deleted some events", - ) - - // Query forwarding history to verify second batch remains. - fwdHistoryAfter := bob.RPC.ForwardingHistory(nil) - require.NotEmpty( - ht, fwdHistoryAfter.ForwardingEvents, - "some forwarding events should remain", - ) - require.GreaterOrEqual( - ht, len(fwdHistoryAfter.ForwardingEvents), secondBatch-1, - "at least most of second batch should remain", - ) -} - -// testEmptyDatabaseDeletion tests deletion on an empty forwarding log. -func testEmptyDatabaseDeletion(ht *lntest.HarnessTest) { - // Create a standalone node (no channels, no forwards). - bob := ht.NewNode("Bob", nil) - - // Try to delete from empty database using custom duration format. - // - //nolint:ll - delResp := bob.RPC.DeleteForwardingHistory( - &routerrpc.DeleteForwardingHistoryRequest{ - TimeSpec: &routerrpc.DeleteForwardingHistoryRequest_DeleteBeforeDuration{ - DeleteBeforeDuration: "-1d", - }, - }, - ) - - // Should successfully handle empty database. - require.Equal( - ht, uint64(0), delResp.EventsDeleted, - "should delete 0 events from empty database", - ) - require.Equal( - ht, int64(0), delResp.TotalFeeMsat, - "should have 0 fees from empty database", - ) -} - -// testDeletionIdempotency tests that deletion is idempotent. -func testDeletionIdempotency(ht *lntest.HarnessTest) { - // Create a three-hop network: Alice -> Bob -> Carol. - const chanAmt = btcutil.Amount(300000) - p := lntest.OpenChannelParams{Amt: chanAmt} - - cfgs := [][]string{nil, {"--dev.min-fwd-history-age=2s"}, nil} - chanPoints, nodes := ht.CreateSimpleNetwork(cfgs, p) - alice, bob, carol := nodes[0], nodes[1], nodes[2] - _ = chanPoints - - // Send a few payments to create forwarding events. - const numPayments = 5 - const paymentAmt = 1000 - - for i := 0; i < numPayments; i++ { - invoice := carol.RPC.AddInvoice(&lnrpc.Invoice{ - ValueMsat: paymentAmt, - Memo: fmt.Sprintf("payment %d", i), - }) - - payReq := &routerrpc.SendPaymentRequest{ - PaymentRequest: invoice.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: 100000, - } - - ht.SendPaymentAssertSettled(alice, payReq) - - // Sleep to ensure events are old enough. - time.Sleep(time.Second) - } - - // Sleep an additional 2 seconds to ensure all events are old enough. - time.Sleep(2 * time.Second) - - // Verify events exist. Poll until the async flush has persisted them. - var fwdHistory *lnrpc.ForwardingHistoryResponse - err := wait.NoError(func() error { - fwdHistory = bob.RPC.ForwardingHistory(nil) - if len(fwdHistory.ForwardingEvents) != numPayments { - return fmt.Errorf("expected %d forwarding events, "+ - "got %d", numPayments, - len(fwdHistory.ForwardingEvents)) - } - - return nil - }, wait.DefaultTimeout) - require.NoError(ht, err, "timed out waiting for forwarding events") - - // Delete all events using a timestamp 2 seconds in the past. - deleteTime := uint64(time.Now().Add(-2 * time.Second).Unix()) - - //nolint:ll - delResp1 := bob.RPC.DeleteForwardingHistory( - &routerrpc.DeleteForwardingHistoryRequest{ - TimeSpec: &routerrpc.DeleteForwardingHistoryRequest_DeleteBeforeTime{ - DeleteBeforeTime: deleteTime, - }, - }, - ) - - require.Equal( - ht, uint64(numPayments), delResp1.EventsDeleted, - "first deletion should delete all events", - ) - - // Delete again with same parameters. - // - //nolint:ll - delResp2 := bob.RPC.DeleteForwardingHistory( - &routerrpc.DeleteForwardingHistoryRequest{ - TimeSpec: &routerrpc.DeleteForwardingHistoryRequest_DeleteBeforeTime{ - DeleteBeforeTime: deleteTime, - }, - }, - ) - - // Second deletion should delete nothing (idempotent). - require.Equal( - ht, uint64(0), delResp2.EventsDeleted, - "second deletion should delete 0 events (idempotent)", - ) - require.Equal( - ht, int64(0), delResp2.TotalFeeMsat, - "second deletion should have 0 fees", - ) -} - -// testTimeFormats tests different time specification formats. -func testTimeFormats(ht *lntest.HarnessTest) { - // Create a three-hop network: Alice -> Bob -> Carol. - const chanAmt = btcutil.Amount(300000) - p := lntest.OpenChannelParams{Amt: chanAmt} - - cfgs := [][]string{nil, {"--dev.min-fwd-history-age=2s"}, nil} - chanPoints, nodes := ht.CreateSimpleNetwork(cfgs, p) - alice, bob, carol := nodes[0], nodes[1], nodes[2] - _ = chanPoints - - // Helper function to create forwarding events. - createForwards := func(count int) { - for i := 0; i < count; i++ { - invoice := carol.RPC.AddInvoice(&lnrpc.Invoice{ - ValueMsat: 1000, - Memo: fmt.Sprintf("payment %d", i), - }) - - payReq := &routerrpc.SendPaymentRequest{ - PaymentRequest: invoice.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: 100000, - } - - ht.SendPaymentAssertSettled(alice, payReq) - - // Sleep to ensure events are old enough. - time.Sleep(time.Second) - } - - // Sleep an additional 2 seconds to ensure all events are old - // enough. - time.Sleep(2 * time.Second) - } - - // Test relative duration format. - createForwards(3) - - // Use duration format. Events are just created, so "-1d" (1 day ago) - // will not delete them. - // - //nolint:ll - delResp := bob.RPC.DeleteForwardingHistory( - &routerrpc.DeleteForwardingHistoryRequest{ - TimeSpec: &routerrpc.DeleteForwardingHistoryRequest_DeleteBeforeDuration{ - DeleteBeforeDuration: "-1d", - }, - }, - ) - - // Should delete nothing since events are recent. - require.Equal( - ht, uint64(0), delResp.EventsDeleted, - "recent events should not be deleted with -1d duration", - ) - - // Test absolute timestamp format. Query current events and use a - // timestamp 2 seconds in the past. Poll until the async flush has - // persisted all 3 events. - var fwdHistory2 *lnrpc.ForwardingHistoryResponse - err2 := wait.NoError(func() error { - fwdHistory2 = bob.RPC.ForwardingHistory(nil) - if len(fwdHistory2.ForwardingEvents) != 3 { - return fmt.Errorf("expected 3 forwarding events, "+ - "got %d", len(fwdHistory2.ForwardingEvents)) - } - - return nil - }, wait.DefaultTimeout) - require.NoError(ht, err2, "timed out waiting for forwarding events") - - //nolint:ll - delResp2 := bob.RPC.DeleteForwardingHistory( - &routerrpc.DeleteForwardingHistoryRequest{ - TimeSpec: &routerrpc.DeleteForwardingHistoryRequest_DeleteBeforeTime{ - DeleteBeforeTime: uint64( - time.Now().Add(-2 * time.Second).Unix(), - ), - }, - }, - ) - - require.Equal( - ht, uint64(3), delResp2.EventsDeleted, - "absolute timestamp should delete all events", - ) -} diff --git a/itest/lnd_forward_interceptor_test.go b/itest/lnd_forward_interceptor_test.go index 7391d4178..d1b7b3d48 100644 --- a/itest/lnd_forward_interceptor_test.go +++ b/itest/lnd_forward_interceptor_test.go @@ -1,12 +1,13 @@ package itest import ( + "bytes" "fmt" "reflect" "strings" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" @@ -14,6 +15,7 @@ import ( "github.com/lightningnetwork/lnd/lntest/node" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" @@ -387,7 +389,7 @@ func testForwardInterceptorRestart(ht *lntest.HarnessTest) { // all intercepted packets. These packets are held to simulate a // pending payment. packet := ht.ReceiveHtlcInterceptor(bobInterceptor) - require.Equal(ht, lntest.CustomRecordsWithUnaccountable( + require.Equal(ht, lntest.CustomRecordsWithUnendorsed( customRecords, ), packet.InWireCustomRecords) @@ -432,14 +434,25 @@ func testForwardInterceptorRestart(ht *lntest.HarnessTest) { // We should get another notification about the held HTLC. packet = ht.ReceiveHtlcInterceptor(bobInterceptor) - require.Len(ht, packet.InWireCustomRecords, 2) - require.Equal(ht, lntest.CustomRecordsWithUnaccountable(customRecords), + // Check the expected number of custom records based on whether the + // endorsement experiment is still active. + expectedLen := 1 + if lntest.ExperimentalEndorsementActive() { + expectedLen = 2 + } + require.Len(ht, packet.InWireCustomRecords, expectedLen) + require.Equal(ht, lntest.CustomRecordsWithUnendorsed(customRecords), packet.InWireCustomRecords) // And now we forward the payment at Carol, expecting only an - // accountability signal in our incoming custom records. + // endorsement signal in our incoming custom records (if the experiment + // is still active). packet = ht.ReceiveHtlcInterceptor(carolInterceptor) - require.Len(ht, packet.InWireCustomRecords, 1) + expectedCarolLen := 0 + if lntest.ExperimentalEndorsementActive() { + expectedCarolLen = 1 + } + require.Len(ht, packet.InWireCustomRecords, expectedCarolLen) err = carolInterceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{ IncomingCircuitKey: packet.IncomingCircuitKey, Action: actionResume, @@ -451,7 +464,7 @@ func testForwardInterceptorRestart(ht *lntest.HarnessTest) { alice, preimage.Hash(), lnrpc.Payment_SUCCEEDED, func(p *lnrpc.Payment) error { recordsEqual := reflect.DeepEqual( - lntest.CustomRecordsWithUnaccountable( + lntest.CustomRecordsWithUnendorsed( sendReq.FirstHopCustomRecords, ), p.FirstHopCustomRecords, ) @@ -475,9 +488,17 @@ func testForwardInterceptorRestart(ht *lntest.HarnessTest) { rt.FirstHopAmountMsat) } - // Make sure the custom channel data is nil because - // this is not a custom channel payment. - require.Nil(ht, rt.CustomChannelData) + cr := lnwire.CustomRecords(p.FirstHopCustomRecords) + recordData, err := cr.Serialize() + if err != nil { + return err + } + + if !bytes.Equal(rt.CustomChannelData, recordData) { + return fmt.Errorf("expected custom records to "+ + "be equal, got %x expected %x", + rt.CustomChannelData, recordData) + } return nil }, diff --git a/itest/lnd_funding_test.go b/itest/lnd_funding_test.go index cf7dad225..b6734e032 100644 --- a/itest/lnd_funding_test.go +++ b/itest/lnd_funding_test.go @@ -4,10 +4,10 @@ import ( "fmt" "testing" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/labels" @@ -33,10 +33,6 @@ var basicFundingTestCases = []*lntest.TestCase{ Name: "basic flow simple taproot", TestFunc: testBasicChannelFundingSimpleTaproot, }, - { - Name: "basic flow simple taproot final", - TestFunc: testBasicChannelFundingSimpleTaprootFinal, - }, } // allFundingTypes defines the channel types to test for the basic funding @@ -45,7 +41,6 @@ var allFundingTypes = []lnrpc.CommitmentType{ lnrpc.CommitmentType_STATIC_REMOTE_KEY, lnrpc.CommitmentType_ANCHORS, lnrpc.CommitmentType_SIMPLE_TAPROOT, - lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL, } // testBasicChannelFundingStaticRemote performs a test exercising expected @@ -135,35 +130,6 @@ func testBasicChannelFundingSimpleTaproot(ht *lntest.HarnessTest) { } } -// testBasicChannelFundingSimpleTaprootFinal performs a test exercising expected -// behavior from a basic funding workflow. The test creates a new channel -// between Carol and Dave, with Carol using the production simple taproot -// commitment type, and Dave using allFundingTypes. -func testBasicChannelFundingSimpleTaprootFinal(ht *lntest.HarnessTest) { - carolCommitType := lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - // We'll test all possible combinations of the feature bit presence - // that both nodes can signal for this new channel type. We'll make a - // new Carol+Dave for each test instance as well. - for _, daveCommitType := range allFundingTypes { - cc := carolCommitType - dc := daveCommitType - - testName := fmt.Sprintf( - "carol_commit=%v,dave_commit=%v", cc, dc, - ) - - success := ht.Run(testName, func(t *testing.T) { - st := ht.Subtest(t) - runBasicFundingTest(st, cc, dc) - }) - - if !success { - break - } - } -} - // runBasicFundingTest is a helper function that takes Carol and Dave's // commitment types and test the funding flow. func runBasicFundingTest(ht *lntest.HarnessTest, carolCommitType, @@ -192,24 +158,15 @@ func runBasicFundingTest(ht *lntest.HarnessTest, carolCommitType, // private, otherwise it'll be rejected by Dave. // // TODO(roasbeef): lift after gossip 1.75 - if carolCommitType == lnrpc.CommitmentType_SIMPLE_TAPROOT || - carolCommitType == lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL { - + if carolCommitType == lnrpc.CommitmentType_SIMPLE_TAPROOT { privateChan = true } - // If carol wants taproot (staging or final), but dave wants something - // that doesn't enable taproot support, then we'll assert that the - // channel negotiation attempt fails. Cross-type negotiation between - // SIMPLE_TAPROOT and SIMPLE_TAPROOT_FINAL succeeds because both - // staging and final feature bits are advertised when taproot is - // enabled. - carolWantsTaproot := carolCommitType == lnrpc.CommitmentType_SIMPLE_TAPROOT || //nolint:ll - carolCommitType == lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - daveHasTaproot := daveCommitType == lnrpc.CommitmentType_SIMPLE_TAPROOT || //nolint:ll - daveCommitType == lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL + // If carol wants taproot, but dave wants something else, then we'll + // assert that the channel negotiation attempt fails. + if carolCommitType == lnrpc.CommitmentType_SIMPLE_TAPROOT && + daveCommitType != lnrpc.CommitmentType_SIMPLE_TAPROOT { - if carolWantsTaproot && !daveHasTaproot { expectedErr := fmt.Errorf("requested channel type " + "not supported") amt := funding.MaxBtcFundingAmount @@ -224,12 +181,6 @@ func runBasicFundingTest(ht *lntest.HarnessTest, carolCommitType, return } - // NOTE: With both staging and final feature bits advertised by default, - // cross-type negotiation (e.g., Carol wants FINAL, Dave prefers - // STAGING) will succeed because explicit channel_type takes precedence. - // The channel will be created with Carol's requested type (FINAL) since - // Dave advertises support for it. - carolChan, daveChan := basicChannelFundingTest( ht, carol, dave, nil, privateChan, &carolCommitType, ) @@ -245,9 +196,6 @@ func runBasicFundingTest(ht *lntest.HarnessTest, carolCommitType, expType := carolCommitType switch daveCommitType { - // Dave supports production taproot, type will be what Carol supports. - case lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL: - // Dave supports taproot, type will be what Carol supports. case lnrpc.CommitmentType_SIMPLE_TAPROOT: @@ -258,9 +206,6 @@ func runBasicFundingTest(ht *lntest.HarnessTest, carolCommitType, if expType == lnrpc.CommitmentType_SIMPLE_TAPROOT { expType = lnrpc.CommitmentType_ANCHORS } - if expType == lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL { - expType = lnrpc.CommitmentType_ANCHORS - } // Dave only supports tweakless, channel will be downgraded to this // type if Carol supports anchors. @@ -270,8 +215,6 @@ func runBasicFundingTest(ht *lntest.HarnessTest, carolCommitType, expType = lnrpc.CommitmentType_STATIC_REMOTE_KEY case lnrpc.CommitmentType_SIMPLE_TAPROOT: expType = lnrpc.CommitmentType_STATIC_REMOTE_KEY - case lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL: - expType = lnrpc.CommitmentType_STATIC_REMOTE_KEY } // Dave only supports legacy type, channel will be downgraded to this @@ -297,9 +240,6 @@ func runBasicFundingTest(ht *lntest.HarnessTest, carolCommitType, case expType == lnrpc.CommitmentType_SIMPLE_TAPROOT && chansCommitType == lnrpc.CommitmentType_SIMPLE_TAPROOT: - case expType == lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL && - chansCommitType == lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL: - default: ht.Fatalf("expected nodes to signal commit type %v, instead "+ "got %v", expType, chansCommitType) @@ -349,13 +289,10 @@ func basicChannelFundingTest(ht *lntest.HarnessTest, // explicit commitment type. This allows us to continue supporting the // existing min version comparison for implicit negotiation. var commitTypeParam lnrpc.CommitmentType - if commitType != nil { - switch *commitType { - case lnrpc.CommitmentType_SIMPLE_TAPROOT, - lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL: + if commitType != nil && + *commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT { - commitTypeParam = *commitType - } + commitTypeParam = *commitType } // First establish a channel with a capacity of 0.5 BTC between Alice @@ -897,38 +834,9 @@ func runExternalFundingTaproot(ht *lntest.HarnessTest) { // HTLCs. ht.AssertInvoiceSettled(dave, resp.PaymentAddr) - // Mine past the thaw height so the channel can be cooperatively - // closed. With RBF cooperative close (auto-enabled for taproot - // channels), both sides enforce the thaw height check during - // shutdown negotiation. - ht.MineBlocks(int(thawHeight)) - // Next we'll try but this time with Dave (the responder) as the - // initiator. With RBF close auto-enabled for taproot channels, - // both sides create competing closing txs. Only one makes it - // to the mempool (the other is rejected as a same-fee RBF). - // We skip the mempool check since the local tx may not be the - // one that made it in. - closeStream, _ := ht.CloseChannelAssertPending( - dave, chanPoint2, false, - lntest.WithSkipMempoolCheck(), - ) - - // Mine a block to confirm whichever closing tx is in the mempool. - ht.MineBlocksAndAssertNumTxes(1, 1) - - // Consume updates until we get the final ChanClose event. The - // RBF close protocol may send multiple ClosePending updates - // (one per side's closing tx proposal) before the final close. - for { - event, err := ht.ReceiveCloseChannelUpdate(closeStream) - require.NoError(ht, err) - - //nolint:ll - if _, ok := event.Update.(*lnrpc.CloseStatusUpdate_ChanClose); ok { - break - } - } + // initiator. This time the channel should be closed as normal. + ht.CloseChannel(dave, chanPoint2) // Let's make sure we can abandon it. carol.RPC.AbandonChannel(&lnrpc.AbandonChannelRequest{ @@ -1364,17 +1272,8 @@ func testChannelFundingWithUnstableUtxos(ht *lntest.HarnessTest) { // Make sure Carol sees her to_remote output from the force close tx. ht.AssertNumPendingSweeps(carol, 1) - // Wait for Carol's sweep transaction to appear in the mempool. Due to - // async confirmation notifications, there's a race between when the - // sweep is registered and when the sweeper processes the next block. - // The sweeper uses immediate=false, so it broadcasts on the next block - // after registration. Mine an empty block to trigger the broadcast. - ht.MineEmptyBlocks(1) - - // Now the sweep should be in the mempool. - ht.AssertNumTxsInMempool(1) - - // Now we should see the unconfirmed UTXO from the sweep. + // We need to wait for carol initiating the sweep of the to_remote + // output of chanPoint2. utxo := ht.AssertNumUTXOsUnconfirmed(carol, 1)[0] // We now try to open channel using the unconfirmed utxo. @@ -1430,11 +1329,6 @@ func testChannelFundingWithUnstableUtxos(ht *lntest.HarnessTest) { // Make sure Carol sees her to_remote output from the force close tx. ht.AssertNumPendingSweeps(carol, 1) - // Mine an empty block to trigger the sweep broadcast (same fix as - // above). - ht.MineEmptyBlocks(1) - ht.AssertNumTxsInMempool(1) - // Wait for the to_remote sweep tx to show up in carol's wallet. ht.AssertNumUTXOsUnconfirmed(carol, 1) diff --git a/itest/lnd_graph.go b/itest/lnd_graph.go index 25c08d2f8..206948227 100644 --- a/itest/lnd_graph.go +++ b/itest/lnd_graph.go @@ -3,7 +3,7 @@ package itest import ( "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/stretchr/testify/require" diff --git a/itest/lnd_graph_migration_test.go b/itest/lnd_graph_migration_test.go index 5dc74913d..81fe65e87 100644 --- a/itest/lnd_graph_migration_test.go +++ b/itest/lnd_graph_migration_test.go @@ -3,11 +3,11 @@ package itest import ( "context" "database/sql" + "net" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/node" - "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/sqldb" "github.com/stretchr/testify/require" @@ -60,29 +60,27 @@ func testGraphMigration(ht *lntest.HarnessTest) { // assertDBState is a helper function that asserts the state of the // graph DB. - assertDBState := func(db graphdb.Store) { + assertDBState := func(db graphdb.V1Store) { var ( numNodes int edges = make(map[uint64]bool) ) - err := db.ForEachNodeCached(ctx, lnwire.GossipVersion1, - func(_ context.Context, - _ route.Vertex, - chans map[uint64]*graphdb.DirectedChannel, - ) error { + err := db.ForEachNodeCached(ctx, false, func(_ context.Context, + _ route.Vertex, _ []net.Addr, + chans map[uint64]*graphdb.DirectedChannel) error { - numNodes++ + numNodes++ - // For each node, count the number of edges. - for _, ch := range chans { - edges[ch.ChannelID] = true - } + // For each node, also count the number of edges. + for _, ch := range chans { + edges[ch.ChannelID] = true + } - return nil - }, func() { - clear(edges) - numNodes = 0 - }) + return nil + }, func() { + clear(edges) + numNodes = 0 + }) require.NoError(ht, err) require.Equal(ht, expNumNodes, numNodes) require.Equal(ht, expNumChans, len(edges)) @@ -129,7 +127,7 @@ func testGraphMigration(ht *lntest.HarnessTest) { } func openNativeSQLGraphDB(ht *lntest.HarnessTest, - hn *node.HarnessNode) graphdb.Store { + hn *node.HarnessNode) graphdb.V1Store { db := openNativeSQLDB(ht, hn) diff --git a/itest/lnd_hold_persistence_test.go b/itest/lnd_hold_persistence_test.go index 73c096d6e..f024453eb 100644 --- a/itest/lnd_hold_persistence_test.go +++ b/itest/lnd_hold_persistence_test.go @@ -3,7 +3,7 @@ package itest import ( "fmt" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" diff --git a/itest/lnd_htlc_test.go b/itest/lnd_htlc_test.go index 27169a95b..e5825993f 100644 --- a/itest/lnd_htlc_test.go +++ b/itest/lnd_htlc_test.go @@ -1,7 +1,7 @@ package itest import ( - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/stretchr/testify/require" diff --git a/itest/lnd_htlc_timeout_resolver_test.go b/itest/lnd_htlc_timeout_resolver_test.go index d4107e988..25aa0afcc 100644 --- a/itest/lnd_htlc_timeout_resolver_test.go +++ b/itest/lnd_htlc_timeout_resolver_test.go @@ -1,7 +1,7 @@ package itest import ( - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" @@ -14,8 +14,8 @@ import ( ) const ( - finalCltvDelta = routing.MinCLTVDelta // 24. - thawHeightDelta = finalCltvDelta * 2 // 48. + finalCltvDelta = routing.MinCLTVDelta // 18. + thawHeightDelta = finalCltvDelta * 2 // 36. ) // makeRouteHints creates a route hints that will allow Carol to be reached diff --git a/itest/lnd_invoice_acceptor_test.go b/itest/lnd_invoice_acceptor_test.go index 23bb30ee7..f7c617b25 100644 --- a/itest/lnd_invoice_acceptor_test.go +++ b/itest/lnd_invoice_acceptor_test.go @@ -3,7 +3,7 @@ package itest import ( "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/lnrpc" @@ -102,9 +102,9 @@ func testInvoiceHtlcModifierBasic(ht *lntest.HarnessTest) { ht, tc.sendAmountMsat, modifierRequest.ExitHtlcAmt, ) - // Expect custom records plus accountable signal. + // Expect custom records plus endorsement signal. require.Equal( - ht, lntest.CustomRecordsWithUnaccountable( + ht, lntest.CustomRecordsWithUnendorsed( tc.lastHopCustomRecords, ), modifierRequest.ExitHtlcWireCustomRecords, ) @@ -152,7 +152,7 @@ func testInvoiceHtlcModifierBasic(ht *lntest.HarnessTest) { require.Len(ht, updatedInvoice.Htlcs, 1) require.Equal( - ht, lntest.CustomRecordsWithUnaccountable( + ht, lntest.CustomRecordsWithUnendorsed( tc.lastHopCustomRecords, ), updatedInvoice.Htlcs[0].CustomRecords, ) diff --git a/itest/lnd_macaroons_test.go b/itest/lnd_macaroons_test.go index ddd48b1df..70c50c3b3 100644 --- a/itest/lnd_macaroons_test.go +++ b/itest/lnd_macaroons_test.go @@ -391,6 +391,7 @@ func testMacaroonAuthentication(ht *lntest.HarnessTest) { }} for _, tc := range testCases { + tc := tc ht.Run(tc.name, func(tt *testing.T) { ctxt, cancel := context.WithTimeout( ht.Context(), defaultTimeout, @@ -606,6 +607,7 @@ func testBakeMacaroon(ht *lntest.HarnessTest) { }} for _, tc := range testCases { + tc := tc ht.Run(tc.name, func(tt *testing.T) { ctxt, cancel := context.WithTimeout( ht.Context(), defaultTimeout, diff --git a/itest/lnd_max_channel_size_test.go b/itest/lnd_max_channel_size_test.go index 9f565f3bd..4e19f8653 100644 --- a/itest/lnd_max_channel_size_test.go +++ b/itest/lnd_max_channel_size_test.go @@ -3,7 +3,7 @@ package itest import ( "fmt" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lnwallet" diff --git a/itest/lnd_misc_test.go b/itest/lnd_misc_test.go index 99d8de6b7..f6252132f 100644 --- a/itest/lnd_misc_test.go +++ b/itest/lnd_misc_test.go @@ -7,10 +7,10 @@ import ( "os" "testing" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/wallet" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/input" @@ -1580,7 +1580,7 @@ func testReorgNotifications(ht *lntest.HarnessTest) { // Reorg block1. blockHash1 := block1.Header.BlockHash() - require.NoError(ht, ht.Miner().InvalidateBlock(&blockHash1)) + require.NoError(ht, ht.Miner().Client.InvalidateBlock(&blockHash1)) // Mine empty blocks to evict block1 in bitcoin backend (e.g. bitcoind). ht.Miner().MineEmptyBlocks(2) diff --git a/itest/lnd_mpp_test.go b/itest/lnd_mpp_test.go index f9c38fb70..abc61e298 100644 --- a/itest/lnd_mpp_test.go +++ b/itest/lnd_mpp_test.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" diff --git a/itest/lnd_multi-hop-payments_test.go b/itest/lnd_multi-hop-payments_test.go index dea04bd25..718fdca87 100644 --- a/itest/lnd_multi-hop-payments_test.go +++ b/itest/lnd_multi-hop-payments_test.go @@ -1,7 +1,7 @@ package itest import ( - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" diff --git a/itest/lnd_multi-hop_force_close_test.go b/itest/lnd_multi-hop_force_close_test.go index fbd82ec16..034a5641b 100644 --- a/itest/lnd_multi-hop_force_close_test.go +++ b/itest/lnd_multi-hop_force_close_test.go @@ -3,7 +3,7 @@ package itest import ( "fmt" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" @@ -47,14 +47,6 @@ var multiHopForceCloseTestCases = []*lntest.TestCase{ Name: "local claim outgoing htlc simple taproot zero conf", TestFunc: testLocalClaimOutgoingHTLCSimpleTaprootZeroConf, }, - { - Name: "local claim outgoing htlc simple taproot final", - TestFunc: testLocalClaimOutgoingHTLCSimpleTaprootFinal, - }, - { - Name: "local claim outgoing htlc simple taproot final zero conf", - TestFunc: testLocalClaimOutgoingHTLCSimpleTaprootFinalZeroConf, - }, { Name: "local claim outgoing htlc leased", TestFunc: testLocalClaimOutgoingHTLCLeased, @@ -79,14 +71,6 @@ var multiHopForceCloseTestCases = []*lntest.TestCase{ Name: "receiver preimage claim simple taproot zero conf", TestFunc: testMultiHopReceiverPreimageClaimSimpleTaprootZeroConf, }, - { - Name: "receiver preimage claim simple taproot final", - TestFunc: testMultiHopReceiverPreimageClaimSimpleTaprootFinal, - }, - { - Name: "receiver preimage claim simple taproot final zero conf", - TestFunc: testMultiHopReceiverPreimageClaimSimpleTaprootFinalZeroConf, - }, { Name: "receiver preimage claim leased", TestFunc: testMultiHopReceiverPreimageClaimLeased, @@ -111,14 +95,6 @@ var multiHopForceCloseTestCases = []*lntest.TestCase{ Name: "local force close before timeout simple taproot zero conf", TestFunc: testLocalForceCloseBeforeTimeoutSimpleTaprootZeroConf, }, - { - Name: "local force close before timeout simple taproot final", - TestFunc: testLocalForceCloseBeforeTimeoutSimpleTaprootFinal, - }, - { - Name: "local force close before timeout simple taproot final zero conf", - TestFunc: testLocalForceCloseBeforeTimeoutSimpleTaprootFinalZeroConf, - }, { Name: "local force close before timeout leased", TestFunc: testLocalForceCloseBeforeTimeoutLeased, @@ -143,14 +119,6 @@ var multiHopForceCloseTestCases = []*lntest.TestCase{ Name: "remote force close before timeout simple taproot zero conf", TestFunc: testRemoteForceCloseBeforeTimeoutSimpleTaprootZeroConf, }, - { - Name: "remote force close before timeout simple taproot final", - TestFunc: testRemoteForceCloseBeforeTimeoutSimpleTaprootFinal, - }, - { - Name: "remote force close before timeout simple taproot final zero conf", - TestFunc: testRemoteForceCloseBeforeTimeoutSimpleTaprootFinalZeroConf, - }, { Name: "remote force close before timeout leased", TestFunc: testRemoteForceCloseBeforeTimeoutLeased, @@ -175,14 +143,6 @@ var multiHopForceCloseTestCases = []*lntest.TestCase{ Name: "local claim incoming htlc simple taproot zero conf", TestFunc: testLocalClaimIncomingHTLCSimpleTaprootZeroConf, }, - { - Name: "local claim incoming htlc simple taproot final", - TestFunc: testLocalClaimIncomingHTLCSimpleTaprootFinal, - }, - { - Name: "local claim incoming htlc simple taproot final zero conf", - TestFunc: testLocalClaimIncomingHTLCSimpleTaprootFinalZeroConf, - }, { Name: "local claim incoming htlc leased", TestFunc: testLocalClaimIncomingHTLCLeased, @@ -207,14 +167,6 @@ var multiHopForceCloseTestCases = []*lntest.TestCase{ Name: "local preimage claim simple taproot zero conf", TestFunc: testLocalPreimageClaimSimpleTaprootZeroConf, }, - { - Name: "local preimage claim simple taproot final", - TestFunc: testLocalPreimageClaimSimpleTaprootFinal, - }, - { - Name: "local preimage claim simple taproot final zero conf", - TestFunc: testLocalPreimageClaimSimpleTaprootFinalZeroConf, - }, { Name: "local preimage claim leased", TestFunc: testLocalPreimageClaimLeased, @@ -239,14 +191,6 @@ var multiHopForceCloseTestCases = []*lntest.TestCase{ Name: "htlc aggregation simple taproot zero conf", TestFunc: testHtlcAggregaitonSimpleTaprootZeroConf, }, - { - Name: "htlc aggregation simple taproot final", - TestFunc: testHtlcAggregationSimpleTaprootFinal, - }, - { - Name: "htlc aggregation simple taproot final zero conf", - TestFunc: testHtlcAggregationSimpleTaprootFinalZeroConf, - }, { Name: "htlc aggregation leased", TestFunc: testHtlcAggregaitonLeased, @@ -341,53 +285,6 @@ func testLocalClaimOutgoingHTLCSimpleTaprootZeroConf(ht *lntest.HarnessTest) { runLocalClaimOutgoingHTLC(ht, cfgs, openChannelParams) } -// testLocalClaimOutgoingHTLCSimpleTaprootFinal tests -// `runLocalClaimOutgoingHTLC` with production simple taproot channel. -func testLocalClaimOutgoingHTLCSimpleTaprootFinal(ht *lntest.HarnessTest) { - c := lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - // Create a three hop network: Alice -> Bob -> Carol, using production - // simple taproot channels. - // - // Prepare params. - openChannelParams := lntest.OpenChannelParams{ - Amt: chanAmt, - CommitmentType: c, - Private: true, - } - - cfg := node.CfgSimpleTaproot - cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...) - cfgs := [][]string{cfg, cfg, cfgCarol} - - runLocalClaimOutgoingHTLC(ht, cfgs, openChannelParams) -} - -// testLocalClaimOutgoingHTLCSimpleTaprootFinalZeroConf tests -// `runLocalClaimOutgoingHTLC` with zero-conf production simple taproot channel. -func testLocalClaimOutgoingHTLCSimpleTaprootFinalZeroConf(ht *lntest.HarnessTest) { //nolint:ll - c := lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - // Create a three hop network: Alice -> Bob -> Carol, using zero-conf - // production simple taproot channels. - // - // Prepare params. - openChannelParams := lntest.OpenChannelParams{ - Amt: chanAmt, - ZeroConf: true, - CommitmentType: c, - Private: true, - } - - // Prepare Carol's node config to enable zero-conf and leased channel. - cfg := node.CfgSimpleTaproot - cfg = append(cfg, node.CfgZeroConf...) - cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...) - cfgs := [][]string{cfg, cfg, cfgCarol} - - runLocalClaimOutgoingHTLC(ht, cfgs, openChannelParams) -} - // testLocalClaimOutgoingHTLCLeased tests `runLocalClaimOutgoingHTLC` with // script enforced lease channel. func testLocalClaimOutgoingHTLCLeased(ht *lntest.HarnessTest) { @@ -464,12 +361,7 @@ func runLocalClaimOutgoingHTLC(ht *lntest.HarnessTest, // If this is a taproot channel, then we'll need to make some manual // route hints so Alice can actually find a route. var routeHints []*lnrpc.RouteHint - isTaproot := params.CommitmentType == - lnrpc.CommitmentType_SIMPLE_TAPROOT || - params.CommitmentType == - lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - if isTaproot { + if params.CommitmentType == lnrpc.CommitmentType_SIMPLE_TAPROOT { routeHints = makeRouteHints(bob, carol, params.ZeroConf) } @@ -625,25 +517,19 @@ func runLocalClaimOutgoingHTLC(ht *lntest.HarnessTest, // Now that Bob has claimed his HTLCs, Alice should mark the two // payments as failed. // - // Alice's payment can fail with either NO_ROUTE or TIMEOUT depending - // on timing. There's a race between: - // 1. The channel closure propagating to Alice's graph (-> NO_ROUTE) - // 2. The payment attempt timeout firing (-> TIMEOUT) - // Both failure reasons are correct. - p := ht.AssertPaymentFailureReasonAny(alice, preimage, - lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE, - lnrpc.PaymentFailureReason_FAILURE_REASON_TIMEOUT, - ) - - // The HTLC-level failure code should be PERMANENT_CHANNEL_FAILURE - // regardless of which payment-level failure reason we got. + // Alice will mark this payment as failed with no route as the only + // route she has is Alice->Bob->Carol. This won't be the case if she + // has a second route, as another attempt will be tried. + // + // TODO(yy): we should instead mark this payment as timed out if she has + // a second route to try this payment, which is the timeout set by Alice + // when sending the payment. + expectedReason := lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE + p := ht.AssertPaymentFailureReason(alice, preimage, expectedReason) require.Equal(ht, lnrpc.Failure_PERMANENT_CHANNEL_FAILURE, p.Htlcs[0].Failure.Code) - p = ht.AssertPaymentFailureReasonAny(alice, preimageDust, - lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE, - lnrpc.PaymentFailureReason_FAILURE_REASON_TIMEOUT, - ) + p = ht.AssertPaymentFailureReason(alice, preimageDust, expectedReason) require.Equal(ht, lnrpc.Failure_PERMANENT_CHANNEL_FAILURE, p.Htlcs[0].Failure.Code) } @@ -731,55 +617,6 @@ func testMultiHopReceiverPreimageClaimSimpleTaprootZeroConf( runMultiHopReceiverPreimageClaim(ht, cfgs, openChannelParams) } -// testMultiHopReceiverPreimageClaimSimpleTaprootFinal tests -// `runMultiHopReceiverPreimageClaim` with production simple taproot channels. -func testMultiHopReceiverPreimageClaimSimpleTaprootFinal(ht *lntest.HarnessTest) { //nolint:ll - c := lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - // Create a three hop network: Alice -> Bob -> Carol, using production - // simple taproot channels. - // - // Prepare params. - openChannelParams := lntest.OpenChannelParams{ - Amt: chanAmt, - CommitmentType: c, - Private: true, - } - - cfg := node.CfgSimpleTaproot - cfgs := [][]string{cfg, cfg, cfg} - - runMultiHopReceiverPreimageClaim(ht, cfgs, openChannelParams) -} - -// testMultiHopReceiverPreimageClaimSimpleTaprootFinalZeroConf tests -// `runMultiHopReceiverPreimageClaim` with zero-conf production simple taproot -// channels. -func testMultiHopReceiverPreimageClaimSimpleTaprootFinalZeroConf( - ht *lntest.HarnessTest) { - - c := lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - // Create a three hop network: Alice -> Bob -> Carol, using zero-conf - // production simple taproot channels. - // - // Prepare params. - openChannelParams := lntest.OpenChannelParams{ - Amt: chanAmt, - ZeroConf: true, - CommitmentType: c, - Private: true, - } - - // Prepare Carol's node config to enable zero-conf and leased - // channel. - cfg := node.CfgSimpleTaproot - cfg = append(cfg, node.CfgZeroConf...) - cfgs := [][]string{cfg, cfg, cfg} - - runMultiHopReceiverPreimageClaim(ht, cfgs, openChannelParams) -} - // testMultiHopReceiverPreimageClaimLeased tests // `runMultiHopReceiverPreimageClaim` with script enforce lease channels. func testMultiHopReceiverPreimageClaimLeased(ht *lntest.HarnessTest) { @@ -854,12 +691,7 @@ func runMultiHopReceiverPreimageClaim(ht *lntest.HarnessTest, // If this is a taproot channel, then we'll need to make some manual // route hints so Alice can actually find a route. var routeHints []*lnrpc.RouteHint - isTaproot := params.CommitmentType == - lnrpc.CommitmentType_SIMPLE_TAPROOT || - params.CommitmentType == - lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - if isTaproot { + if params.CommitmentType == lnrpc.CommitmentType_SIMPLE_TAPROOT { routeHints = makeRouteHints(bob, carol, params.ZeroConf) } @@ -1140,56 +972,6 @@ func testLocalForceCloseBeforeTimeoutSimpleTaprootZeroConf( runLocalForceCloseBeforeHtlcTimeout(ht, cfgs, params) } -// testLocalForceCloseBeforeTimeoutSimpleTaprootFinal tests -// `runLocalForceCloseBeforeHtlcTimeout` with production simple taproot channel. -func testLocalForceCloseBeforeTimeoutSimpleTaprootFinal(ht *lntest.HarnessTest) { //nolint:ll - c := lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - // Create a three hop network: Alice -> Bob -> Carol, using production - // simple taproot channels. - // - // Prepare params. - params := lntest.OpenChannelParams{ - Amt: chanAmt, - CommitmentType: c, - Private: true, - } - - cfg := node.CfgSimpleTaproot - cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...) - cfgs := [][]string{cfg, cfg, cfgCarol} - - runLocalForceCloseBeforeHtlcTimeout(ht, cfgs, params) -} - -// testLocalForceCloseBeforeTimeoutSimpleTaprootFinalZeroConf tests -// `runLocalForceCloseBeforeHtlcTimeout` with zero-conf production simple -// taproot channel. -func testLocalForceCloseBeforeTimeoutSimpleTaprootFinalZeroConf( - ht *lntest.HarnessTest) { - - c := lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - // Create a three hop network: Alice -> Bob -> Carol, using zero-conf - // production simple taproot channels. - // - // Prepare params. - params := lntest.OpenChannelParams{ - Amt: chanAmt, - ZeroConf: true, - CommitmentType: c, - Private: true, - } - - // Prepare Carol's node config to enable zero-conf and leased channel. - cfg := node.CfgSimpleTaproot - cfg = append(cfg, node.CfgZeroConf...) - cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...) - cfgs := [][]string{cfg, cfg, cfgCarol} - - runLocalForceCloseBeforeHtlcTimeout(ht, cfgs, params) -} - // testLocalForceCloseBeforeTimeoutLeased tests // `runLocalForceCloseBeforeHtlcTimeout` with script enforced lease channel. func testLocalForceCloseBeforeTimeoutLeased(ht *lntest.HarnessTest) { @@ -1259,12 +1041,7 @@ func runLocalForceCloseBeforeHtlcTimeout(ht *lntest.HarnessTest, // If this is a taproot channel, then we'll need to make some manual // route hints so Alice can actually find a route. var routeHints []*lnrpc.RouteHint - isTaproot := params.CommitmentType == - lnrpc.CommitmentType_SIMPLE_TAPROOT || - params.CommitmentType == - lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - if isTaproot { + if params.CommitmentType == lnrpc.CommitmentType_SIMPLE_TAPROOT { routeHints = makeRouteHints(bob, carol, params.ZeroConf) } @@ -1534,57 +1311,6 @@ func testRemoteForceCloseBeforeTimeoutSimpleTaproot(ht *lntest.HarnessTest) { runRemoteForceCloseBeforeHtlcTimeout(ht, cfgs, params) } -// testRemoteForceCloseBeforeTimeoutSimpleTaprootFinal tests -// `runRemoteForceCloseBeforeHtlcTimeout` with production simple taproot -// channel. -func testRemoteForceCloseBeforeTimeoutSimpleTaprootFinal(ht *lntest.HarnessTest) { //nolint:ll - c := lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - // Create a three hop network: Alice -> Bob -> Carol, using production - // simple taproot channels. - // - // Prepare params. - params := lntest.OpenChannelParams{ - Amt: chanAmt, - CommitmentType: c, - Private: true, - } - - cfg := node.CfgSimpleTaproot - cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...) - cfgs := [][]string{cfg, cfg, cfgCarol} - - runRemoteForceCloseBeforeHtlcTimeout(ht, cfgs, params) -} - -// testRemoteForceCloseBeforeTimeoutSimpleTaprootFinalZeroConf tests -// `runRemoteForceCloseBeforeHtlcTimeout` with zero-conf production simple -// taproot channel. -func testRemoteForceCloseBeforeTimeoutSimpleTaprootFinalZeroConf( - ht *lntest.HarnessTest) { - - c := lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - // Create a three hop network: Alice -> Bob -> Carol, using zero-conf - // production simple taproot channels. - // - // Prepare params. - params := lntest.OpenChannelParams{ - Amt: chanAmt, - ZeroConf: true, - CommitmentType: c, - Private: true, - } - - // Prepare Carol's node config to enable zero-conf and leased channel. - cfg := node.CfgSimpleTaproot - cfg = append(cfg, node.CfgZeroConf...) - cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...) - cfgs := [][]string{cfg, cfg, cfgCarol} - - runRemoteForceCloseBeforeHtlcTimeout(ht, cfgs, params) -} - // testRemoteForceCloseBeforeTimeoutLeasedZeroConf tests // `runRemoteForceCloseBeforeHtlcTimeout` with zero-conf script enforced lease // channel. @@ -1651,12 +1377,7 @@ func runRemoteForceCloseBeforeHtlcTimeout(ht *lntest.HarnessTest, // If this is a taproot channel, then we'll need to make some manual // route hints so Alice can actually find a route. var routeHints []*lnrpc.RouteHint - isTaproot := params.CommitmentType == - lnrpc.CommitmentType_SIMPLE_TAPROOT || - params.CommitmentType == - lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - if isTaproot { + if params.CommitmentType == lnrpc.CommitmentType_SIMPLE_TAPROOT { routeHints = makeRouteHints(bob, carol, params.ZeroConf) } @@ -1887,52 +1608,6 @@ func testLocalClaimIncomingHTLCSimpleTaproot(ht *lntest.HarnessTest) { runLocalClaimIncomingHTLC(ht, cfgs, params) } -// testLocalClaimIncomingHTLCSimpleTaprootFinal tests -// `runLocalClaimIncomingHTLC` with production simple taproot channel. -func testLocalClaimIncomingHTLCSimpleTaprootFinal(ht *lntest.HarnessTest) { - c := lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - // Create a three hop network: Alice -> Bob -> Carol, using production - // simple taproot channels. - // - // Prepare params. - params := lntest.OpenChannelParams{ - Amt: chanAmt, - CommitmentType: c, - Private: true, - } - - cfg := node.CfgSimpleTaproot - cfgs := [][]string{cfg, cfg, cfg} - - runLocalClaimIncomingHTLC(ht, cfgs, params) -} - -// testLocalClaimIncomingHTLCSimpleTaprootFinalZeroConf tests -// `runLocalClaimIncomingHTLC` with zero-conf production simple taproot channel. -func testLocalClaimIncomingHTLCSimpleTaprootFinalZeroConf(ht *lntest.HarnessTest) { //nolint:ll - c := lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - // Create a three hop network: Alice -> Bob -> Carol, using zero-conf - // production simple taproot channels. - // - // Prepare params. - params := lntest.OpenChannelParams{ - Amt: chanAmt, - ZeroConf: true, - CommitmentType: c, - Private: true, - } - - // Prepare Carol's node config to enable zero-conf and simple taproot - // channel. - cfg := node.CfgSimpleTaproot - cfg = append(cfg, node.CfgZeroConf...) - cfgs := [][]string{cfg, cfg, cfg} - - runLocalClaimIncomingHTLC(ht, cfgs, params) -} - // runLocalClaimIncomingHTLC tests that in a multi-hop HTLC scenario, if we // force close a channel with an incoming HTLC, and later find out the preimage // via the witness beacon, we properly settle the HTLC on-chain using the HTLC @@ -1958,12 +1633,7 @@ func runLocalClaimIncomingHTLC(ht *lntest.HarnessTest, // If this is a taproot channel, then we'll need to make some manual // route hints so Alice can actually find a route. var routeHints []*lnrpc.RouteHint - isTaproot := params.CommitmentType == - lnrpc.CommitmentType_SIMPLE_TAPROOT || - params.CommitmentType == - lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - if isTaproot { + if params.CommitmentType == lnrpc.CommitmentType_SIMPLE_TAPROOT { routeHints = makeRouteHints(bob, carol, params.ZeroConf) } @@ -2566,51 +2236,6 @@ func testLocalPreimageClaimSimpleTaproot(ht *lntest.HarnessTest) { runLocalPreimageClaim(ht, cfgs, params) } -// testLocalPreimageClaimSimpleTaprootFinal tests `runLocalPreimageClaim` with -// production simple taproot channel. -func testLocalPreimageClaimSimpleTaprootFinal(ht *lntest.HarnessTest) { - c := lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - // Create a three hop network: Alice -> Bob -> Carol, using production - // simple taproot channels. - // - // Prepare params. - params := lntest.OpenChannelParams{ - Amt: chanAmt, - CommitmentType: c, - Private: true, - } - - cfg := node.CfgSimpleTaproot - cfgs := [][]string{cfg, cfg, cfg} - - runLocalPreimageClaim(ht, cfgs, params) -} - -// testLocalPreimageClaimSimpleTaprootFinalZeroConf tests -// `runLocalPreimageClaim` with zero-conf production simple taproot channel. -func testLocalPreimageClaimSimpleTaprootFinalZeroConf(ht *lntest.HarnessTest) { - c := lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - // Create a three hop network: Alice -> Bob -> Carol, using zero-conf - // production simple taproot channels. - // - // Prepare params. - params := lntest.OpenChannelParams{ - Amt: chanAmt, - ZeroConf: true, - CommitmentType: c, - Private: true, - } - - // Prepare Carol's node config to enable zero-conf and leased channel. - cfg := node.CfgSimpleTaproot - cfg = append(cfg, node.CfgZeroConf...) - cfgs := [][]string{cfg, cfg, cfg} - - runLocalPreimageClaim(ht, cfgs, params) -} - // runLocalPreimageClaim tests that in the multi-hop HTLC scenario, if the // remote party goes to chain while we have an incoming HTLC, then when we // found out the preimage via the witness beacon, we properly settle the HTLC @@ -2637,12 +2262,7 @@ func runLocalPreimageClaim(ht *lntest.HarnessTest, // If this is a taproot channel, then we'll need to make some manual // route hints so Alice can actually find a route. var routeHints []*lnrpc.RouteHint - isTaproot := params.CommitmentType == - lnrpc.CommitmentType_SIMPLE_TAPROOT || - params.CommitmentType == - lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - if isTaproot { + if params.CommitmentType == lnrpc.CommitmentType_SIMPLE_TAPROOT { routeHints = makeRouteHints(bob, carol, params.ZeroConf) } @@ -3188,51 +2808,6 @@ func testHtlcAggregaitonSimpleTaproot(ht *lntest.HarnessTest) { runHtlcAggregation(ht, cfgs, params) } -// testHtlcAggregationSimpleTaprootFinal tests `runHtlcAggregation` with -// production simple taproot channel. -func testHtlcAggregationSimpleTaprootFinal(ht *lntest.HarnessTest) { - c := lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - // Create a three hop network: Alice -> Bob -> Carol, using production - // simple taproot channels. - // - // Prepare params. - params := lntest.OpenChannelParams{ - Amt: chanAmt, - CommitmentType: c, - Private: true, - } - - cfg := node.CfgSimpleTaproot - cfgs := [][]string{cfg, cfg, cfg} - - runHtlcAggregation(ht, cfgs, params) -} - -// testHtlcAggregationSimpleTaprootFinalZeroConf tests `runHtlcAggregation` -// with zero-conf production simple taproot channel. -func testHtlcAggregationSimpleTaprootFinalZeroConf(ht *lntest.HarnessTest) { - c := lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - // Create a three hop network: Alice -> Bob -> Carol, using zero-conf - // production simple taproot channels. - // - // Prepare params. - params := lntest.OpenChannelParams{ - Amt: chanAmt, - ZeroConf: true, - CommitmentType: c, - Private: true, - } - - // Prepare Carol's node config to enable zero-conf and leased channel. - cfg := node.CfgSimpleTaproot - cfg = append(cfg, node.CfgZeroConf...) - cfgs := [][]string{cfg, cfg, cfg} - - runHtlcAggregation(ht, cfgs, params) -} - // testHtlcAggregaitonLeasedZeroConf tests `runHtlcAggregation` with zero-conf // script enforced lease channel. func testHtlcAggregaitonLeasedZeroConf(ht *lntest.HarnessTest) { @@ -3303,12 +2878,7 @@ func runHtlcAggregation(ht *lntest.HarnessTest, aliceRouteHints []*lnrpc.RouteHint ) - isTaproot := params.CommitmentType == - lnrpc.CommitmentType_SIMPLE_TAPROOT || - params.CommitmentType == - lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - if isTaproot { + if params.CommitmentType == lnrpc.CommitmentType_SIMPLE_TAPROOT { carolRouteHints = makeRouteHints(bob, carol, params.ZeroConf) aliceRouteHints = makeRouteHints(bob, alice, params.ZeroConf) } diff --git a/itest/lnd_network_test.go b/itest/lnd_network_test.go index fb5c17588..c5df649ed 100644 --- a/itest/lnd_network_test.go +++ b/itest/lnd_network_test.go @@ -5,7 +5,7 @@ import ( "net" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" diff --git a/itest/lnd_neutrino_headers_import_test.go b/itest/lnd_neutrino_headers_import_test.go deleted file mode 100644 index 48fdcc62c..000000000 --- a/itest/lnd_neutrino_headers_import_test.go +++ /dev/null @@ -1,133 +0,0 @@ -//go:build integration - -package itest - -import ( - "io" - "os" - "path/filepath" - - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/lightninglabs/neutrino/chainimport" - "github.com/lightninglabs/neutrino/headerfs" - "github.com/lightningnetwork/lnd/lntest" - "github.com/stretchr/testify/require" -) - -// testNeutrinoHeadersImport verifies that a neutrino node can import block -// and filter headers from pre-built files, allowing it to sync faster than -// downloading headers one-by-one via P2P. -func testNeutrinoHeadersImport(ht *lntest.HarnessTest) { - // This test only applies to the neutrino backend. - if !ht.IsNeutrinoBackend() { - ht.Skipf("skipping neutrino headers import test " + - "for non-neutrino backend") - } - - // Mine blocks so there is a meaningful chain to sync. - const numBlocks = 50 - ht.MineBlocks(numBlocks) - - // Start a reference node that syncs normally via P2P. NewNode waits - // for blockchain sync automatically. - refNode := ht.NewNode("Reference", nil) - refInfo := refNode.RPC.GetInfo() - require.True(ht, refInfo.SyncedToChain, - "reference node should be synced") - - bestHeight := refInfo.BlockHeight - - // Locate the header files created by the reference node's neutrino - // backend. These are stored as flat binary files in the chain data - // directory. - netName := chaincfg.RegressionNetParams.Name - chainDir := filepath.Join( - refNode.Cfg.DataDir, "chain", "bitcoin", netName, - ) - blockHeadersPath := filepath.Join(chainDir, "block_headers.bin") - filterHeadersPath := filepath.Join( - chainDir, "reg_filter_headers.bin", - ) - - // Verify the header files exist. - _, err := os.Stat(blockHeadersPath) - require.NoError(ht, err, "block_headers.bin not found") - _, err = os.Stat(filterHeadersPath) - require.NoError(ht, err, "reg_filter_headers.bin not found") - - // Copy the header files to a temporary directory for import. - importDir := ht.T.TempDir() - blockImportPath := filepath.Join(importDir, "block_headers.bin") - filterImportPath := filepath.Join( - importDir, "reg_filter_headers.bin", - ) - - copyFile(ht, blockHeadersPath, blockImportPath) - copyFile(ht, filterHeadersPath, filterImportPath) - - // Add import metadata to the copied files. The metadata prepend - // includes network magic, version, header type, and start height. - // This is required by neutrino's chainimport package. - err = chainimport.AddHeadersImportMetadata( - blockImportPath, chaincfg.RegressionNetParams.Net, - 0, headerfs.Block, 0, - ) - require.NoError(ht, err, "failed to add block header metadata") - - err = chainimport.AddHeadersImportMetadata( - filterImportPath, chaincfg.RegressionNetParams.Net, - 0, headerfs.RegularFilter, 0, - ) - require.NoError(ht, err, "failed to add filter header metadata") - - // Shut down the reference node to free resources. - ht.Shutdown(refNode) - - // Start a new node configured to import headers from the prepared - // files. The node imports the headers from file before falling back - // to P2P sync for any remaining blocks. - importArgs := []string{ - "--neutrino.blockheaderssource=" + blockImportPath, - "--neutrino.filterheaderssource=" + filterImportPath, - } - importNode := ht.NewNode("Import", importArgs) - - // Verify the import node synced to the chain. - importInfo := importNode.RPC.GetInfo() - require.True(ht, importInfo.SyncedToChain, - "import node should be synced to chain") - require.GreaterOrEqual( - ht, importInfo.BlockHeight, bestHeight, - "import node should have at least the same height as "+ - "the reference node", - ) - - // Mine additional blocks using the miner directly and verify the - // import node picks them up via P2P sync (hybrid import + P2P - // sync). We use the miner directly because MineBlocks asserts all - // active nodes are synced after each block, which can race with - // neutrino's P2P sync. - const additionalBlocks = 10 - ht.Miner().MineBlocks(additionalBlocks) - - // Wait for the import node to see the new blocks. - expectedHeight := int32(bestHeight) + additionalBlocks - ht.WaitForNodeBlockHeight(importNode, expectedHeight) -} - -// copyFile copies a file from src to dst. -func copyFile(ht *lntest.HarnessTest, src, dst string) { - srcFile, err := os.Open(src) - require.NoError(ht, err, "failed to open source file: %s", src) - defer srcFile.Close() - - dstFile, err := os.Create(dst) - require.NoError(ht, err, "failed to create dest file: %s", dst) - defer dstFile.Close() - - _, err = io.Copy(dstFile, srcFile) - require.NoError(ht, err, "failed to copy file") - - err = dstFile.Sync() - require.NoError(ht, err, "failed to sync dest file") -} diff --git a/itest/lnd_nonstd_sweep_test.go b/itest/lnd_nonstd_sweep_test.go index b26193870..1e20e2bfe 100644 --- a/itest/lnd_nonstd_sweep_test.go +++ b/itest/lnd_nonstd_sweep_test.go @@ -3,35 +3,34 @@ package itest import ( "testing" - "github.com/btcsuite/btcd/address/v2" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/stretchr/testify/require" ) func testNonstdSweep(ht *lntest.HarnessTest) { - p2shAddr, err := address.NewAddressScriptHash( + p2shAddr, err := btcutil.NewAddressScriptHash( make([]byte, 1), harnessNetParams, ) require.NoError(ht, err) - p2pkhAddr, err := address.NewAddressPubKeyHash( + p2pkhAddr, err := btcutil.NewAddressPubKeyHash( make([]byte, 20), harnessNetParams, ) require.NoError(ht, err) - p2wshAddr, err := address.NewAddressWitnessScriptHash( + p2wshAddr, err := btcutil.NewAddressWitnessScriptHash( make([]byte, 32), harnessNetParams, ) require.NoError(ht, err) - p2wkhAddr, err := address.NewAddressWitnessPubKeyHash( + p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash( make([]byte, 20), harnessNetParams, ) require.NoError(ht, err) - p2trAddr, err := address.NewAddressTaproot( + p2trAddr, err := btcutil.NewAddressTaproot( make([]byte, 32), harnessNetParams, ) require.NoError(ht, err) @@ -63,6 +62,7 @@ func testNonstdSweep(ht *lntest.HarnessTest) { } for _, test := range tests { + test := test success := ht.Run(test.name, func(t *testing.T) { st := ht.Subtest(t) @@ -123,13 +123,12 @@ func testNonStdSweepInner(ht *lntest.HarnessTest, address string) { fee = inputVal - outputVal - // Calculate the vsize of the transaction so we can determine if the + // Fetch the vsize of the transaction so we can determine if the // transaction pays >= 1 sat/vbyte. - weight := ht.CalculateTxWeight(msgTx) - vbytes := (int64(weight) + 3) / 4 + rawTx := ht.Miner().GetRawTransactionVerbose(txid) // Require fee >= vbytes. - require.True(ht, int64(fee) >= vbytes) + require.True(ht, fee >= int(rawTx.Vsize)) // Mine a block to keep the mempool clean. ht.MineBlocksAndAssertNumTxes(1, 1) diff --git a/itest/lnd_onchain_test.go b/itest/lnd_onchain_test.go index 08860c12f..d5d0a19f2 100644 --- a/itest/lnd_onchain_test.go +++ b/itest/lnd_onchain_test.go @@ -4,10 +4,10 @@ import ( "bytes" "fmt" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/chainrpc" "github.com/lightningnetwork/lnd/lnrpc/signrpc" diff --git a/itest/lnd_onion_message_forward_test.go b/itest/lnd_onion_message_forward_test.go deleted file mode 100644 index 192dbd8a8..000000000 --- a/itest/lnd_onion_message_forward_test.go +++ /dev/null @@ -1,346 +0,0 @@ -package itest - -import ( - "testing" - "time" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - sphinx "github.com/lightningnetwork/lightning-onion" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/lntest" - "github.com/lightningnetwork/lnd/lntest/node" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/onionmessage" - "github.com/lightningnetwork/lnd/record" - "github.com/stretchr/testify/require" -) - -// onionMessageTestCase defines a test case for onion message forwarding. -type onionMessageTestCase struct { - name string - - // setup is called before building the blinded path to perform any - // additional setup (e.g., opening channels for SCID tests). - setup func(ht *lntest.HarnessTest, alice, bob, carol *node.HarnessNode) - - // buildPath builds the blinded path for the test. It returns the - // blinded path info, the final hop payloads, the first hop node, - // and the expected receiving peer pubkey for validation. - buildPath func(ht *lntest.HarnessTest, alice, bob, - carol *node.HarnessNode) ( - blindedPath *sphinx.BlindedPathInfo, - finalHopTLVs []*lnwire.FinalHopTLV, - firstHop *node.HarnessNode, - expectedPeer []byte, - ) -} - -// testOnionMessageForwarding tests forwarding of onion messages across -// multiple scenarios including forwarding by node ID, by SCID, and with -// concatenated blinded paths. -func testOnionMessageForwarding(ht *lntest.HarnessTest) { - // Spin up a three-node chain Alice -> Bob -> Carol, with both - // channels opened up front via CreateSimpleNetwork. Opening the - // channels before any forwarding run matters because onion message - // ingress is gated on having at least one fully open channel with - // the sending peer, so without these channels every hop would - // silently drop the message. The Bob -> Carol channel also doubles - // as the SCID source for the "forward via scid" test case, which - // keeps the per-test setup minimal. - chanPoints, nodes := ht.CreateSimpleNetwork( - [][]string{nil, nil, nil}, - lntest.OpenChannelParams{ - Amt: btcutil.Amount(100_000), - }, - ) - alice, bob, carol := nodes[0], nodes[1], nodes[2] - bobCarolChan := chanPoints[1] - - testCases := []onionMessageTestCase{ - { - name: "forward via next node id", - buildPath: func(ht *lntest.HarnessTest, alice, bob, - carol *node.HarnessNode) ( - *sphinx.BlindedPathInfo, - []*lnwire.FinalHopTLV, - *node.HarnessNode, []byte, - ) { - - return buildForwardNextNodePath( - ht, bob, carol, - ) - }, - }, - { - name: "forward via scid", - setup: func(ht *lntest.HarnessTest, alice, bob, - carol *node.HarnessNode) { - - // The Bob -> Carol channel was opened up - // front; just wait for it to be in the graph - // so the SCID can be resolved. - ht.AssertChannelInGraph(bob, bobCarolChan) - }, - buildPath: func(ht *lntest.HarnessTest, alice, bob, - carol *node.HarnessNode) ( - *sphinx.BlindedPathInfo, - []*lnwire.FinalHopTLV, - *node.HarnessNode, []byte, - ) { - - return buildForwardSCIDPath(ht, bob, carol) - }, - }, - { - name: "forward concatenated path", - buildPath: buildConcatenatedPath, - }, - } - - for _, tc := range testCases { - success := ht.Run(tc.name, func(t *testing.T) { - // Run optional setup. - if tc.setup != nil { - tc.setup(ht, alice, bob, carol) - } - - // Build the blinded path for this test case. - blindedPath, finalPayloads, firstHop, expectedPeer := - tc.buildPath(ht, alice, bob, carol) - - // Build the onion message. - onionMsg, _ := onionmessage.BuildOnionMessage( - ht.T, blindedPath, finalPayloads, - ) - - // Subscribe to onion messages on Carol before sending. - msgClient, cancel := carol.RPC.SubscribeOnionMessages() - defer cancel() - - messages := make(chan *lnrpc.OnionMessageUpdate) - go func() { - for { - msg, err := msgClient.Recv() - if err != nil { - return - } - select { - case messages <- msg: - case <-ht.Context().Done(): - return - } - } - }() - - // Send the message from Alice to the first hop. - pathKey := blindedPath.SessionKey.PubKey(). - SerializeCompressed() - aliceMsg := &lnrpc.SendOnionMessageRequest{ - Peer: firstHop.PubKey[:], - PathKey: pathKey, - Onion: onionMsg.OnionBlob, - } - alice.RPC.SendOnionMessage(aliceMsg) - - // Wait for Carol to receive the message. - select { - case msg := <-messages: - require.Equal( - ht, expectedPeer, msg.Peer, - "unexpected peer", - ) - - // Verify final payload if provided. - for _, fp := range finalPayloads { - tlvType := uint64(fp.TLVType) - require.Equal( - ht, fp.Value, - msg.CustomRecords[tlvType], - ) - } - - case <-time.After(lntest.DefaultTimeout): - ht.Fatalf("carol did not receive onion message") - } - }) - if !success { - break - } - } -} - -// buildForwardNextNodePath builds a blinded path for forwarding via explicit -// next node ID. Path: Alice -> Bob -> Carol. -func buildForwardNextNodePath(ht *lntest.HarnessTest, bob, - carol *node.HarnessNode) ( - *sphinx.BlindedPathInfo, []*lnwire.FinalHopTLV, - *node.HarnessNode, []byte, -) { - - bobPubKey, err := btcec.ParsePubKey(bob.PubKey[:]) - require.NoError(ht.T, err) - - carolPubKey, err := btcec.ParsePubKey(carol.PubKey[:]) - require.NoError(ht.T, err) - - // Bob's payload: forward to Carol via node ID. - nextNode := fn.NewLeft[*btcec.PublicKey, lnwire.ShortChannelID]( - carolPubKey, - ) - bobData := record.NewNonFinalBlindedRouteDataOnionMessage( - nextNode, nil, nil, - ) - - // Carol's payload: final hop (empty route data). - carolData := &record.BlindedRouteData{} - - hops := []*sphinx.HopInfo{ - { - NodePub: bobPubKey, - PlainText: onionmessage.EncodeBlindedRouteData( - ht.T, bobData, - ), - }, - { - NodePub: carolPubKey, - PlainText: onionmessage.EncodeBlindedRouteData( - ht.T, carolData, - ), - }, - } - - blindedPath := onionmessage.BuildBlindedPath(ht.T, hops) - - finalHopTLVs := []*lnwire.FinalHopTLV{ - { - TLVType: lnwire.InvoiceRequestNamespaceType, - Value: []byte{1, 2, 3}, - }, - } - - return blindedPath, finalHopTLVs, bob, bob.PubKey[:] -} - -// buildForwardSCIDPath builds a blinded path for forwarding via SCID. -// Requires a channel between Bob and Carol to exist. -// Path: Alice -> Bob -> Carol (Bob uses SCID to identify Carol). -func buildForwardSCIDPath(ht *lntest.HarnessTest, bob, - carol *node.HarnessNode) ( - *sphinx.BlindedPathInfo, []*lnwire.FinalHopTLV, - *node.HarnessNode, []byte, -) { - - bobPubKey, err := btcec.ParsePubKey(bob.PubKey[:]) - require.NoError(ht.T, err) - - carolPubKey, err := btcec.ParsePubKey(carol.PubKey[:]) - require.NoError(ht.T, err) - - // Get the SCID of the Bob-Carol channel from Bob's perspective. - channels := bob.RPC.ListChannels(&lnrpc.ListChannelsRequest{ - Peer: carol.PubKey[:], - }) - require.Len(ht.T, channels.Channels, 1, "expected one channel") - scid := lnwire.NewShortChanIDFromInt(channels.Channels[0].ChanId) - - // Bob's payload: forward to Carol via SCID. - nextNode := fn.NewRight[*btcec.PublicKey](scid) - bobData := record.NewNonFinalBlindedRouteDataOnionMessage( - nextNode, nil, nil, - ) - - // Carol's payload: final hop (empty route data). - carolData := &record.BlindedRouteData{} - - hops := []*sphinx.HopInfo{ - { - NodePub: bobPubKey, - PlainText: onionmessage.EncodeBlindedRouteData( - ht.T, bobData, - ), - }, - { - NodePub: carolPubKey, - PlainText: onionmessage.EncodeBlindedRouteData( - ht.T, carolData, - ), - }, - } - - blindedPath := onionmessage.BuildBlindedPath(ht.T, hops) - - finalHopTLVs := []*lnwire.FinalHopTLV{ - { - TLVType: lnwire.InvoiceRequestNamespaceType, - Value: []byte{4, 5, 6}, - }, - } - - return blindedPath, finalHopTLVs, bob, bob.PubKey[:] -} - -// buildConcatenatedPath builds a concatenated blinded path scenario. -// Alice builds a path to Bob, Carol provides a blinded path starting at Bob. -// Bob's payload includes NextBlindingOverride to switch to Carol's path. -// Path: Alice -> Bob (intro) -> Carol. -func buildConcatenatedPath(ht *lntest.HarnessTest, alice, bob, - carol *node.HarnessNode) ( - *sphinx.BlindedPathInfo, []*lnwire.FinalHopTLV, - *node.HarnessNode, []byte, -) { - - bobPubKey, err := btcec.ParsePubKey(bob.PubKey[:]) - require.NoError(ht.T, err) - - carolPubKey, err := btcec.ParsePubKey(carol.PubKey[:]) - require.NoError(ht.T, err) - - // Carol creates a blinded path starting at Bob (introduction node). - // Carol's route data: final hop. - carolData := &record.BlindedRouteData{} - - receiverHops := []*sphinx.HopInfo{ - { - NodePub: carolPubKey, - PlainText: onionmessage.EncodeBlindedRouteData( - ht.T, carolData, - ), - }, - } - receiverPath := onionmessage.BuildBlindedPath(ht.T, receiverHops) - - // Alice creates a path to Bob with NextBlindingOverride pointing to - // Carol's blinding point. - nextNode := fn.NewLeft[*btcec.PublicKey, lnwire.ShortChannelID]( - carolPubKey, - ) - bobData := record.NewNonFinalBlindedRouteDataOnionMessage( - nextNode, receiverPath.Path.BlindingPoint, nil, - ) - - senderHops := []*sphinx.HopInfo{ - { - NodePub: bobPubKey, - PlainText: onionmessage.EncodeBlindedRouteData( - ht.T, bobData, - ), - }, - } - senderPath := onionmessage.BuildBlindedPath(ht.T, senderHops) - - // Concatenate the paths. - concatenatedPath := onionmessage.ConcatBlindedPaths( - ht.T, senderPath, receiverPath, - ) - - finalHopTLVs := []*lnwire.FinalHopTLV{ - { - TLVType: lnwire.InvoiceRequestNamespaceType, - Value: []byte{7, 8, 9}, - }, - } - - return concatenatedPath, finalHopTLVs, bob, bob.PubKey[:] -} diff --git a/itest/lnd_onion_message_test.go b/itest/lnd_onion_message_test.go deleted file mode 100644 index c74423e3a..000000000 --- a/itest/lnd_onion_message_test.go +++ /dev/null @@ -1,110 +0,0 @@ -package itest - -import ( - "time" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - sphinx "github.com/lightningnetwork/lightning-onion" - "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/lntest" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/onionmessage" - "github.com/lightningnetwork/lnd/record" - "github.com/stretchr/testify/require" -) - -// testOnionMessage tests sending and receiving of the onion message type. -func testOnionMessage(ht *lntest.HarnessTest) { - // Alice needs coins to fund a channel with Bob: onion message ingress - // is gated on the sender and receiver sharing at least one fully open - // channel as the Sybil-resistance layer on top of the byte-granular - // rate limiter. - alice := ht.NewNodeWithCoins("Alice", nil) - bob := ht.NewNode("Bob", nil) - - // Subscribe Alice to onion messages before we send any, so that we - // don't miss any. - msgClient, cancel := alice.RPC.SubscribeOnionMessages() - defer cancel() - - // Create a channel to receive onion messages on. - messages := make(chan *lnrpc.OnionMessageUpdate) - go func() { - for { - // If we fail to receive, just exit. The test should - // fail elsewhere if it doesn't get a message that it - // was expecting. - msg, err := msgClient.Recv() - if err != nil { - return - } - - // Deliver the message into our channel or exit if the - // test is shutting down. - select { - case messages <- msg: - case <-ht.Context().Done(): - return - } - } - }() - - // Connect alice and bob and open a channel between them. Onion message - // ingress is gated on having at least one fully open channel with the - // sending peer, so without a channel Alice would silently drop Bob's - // message and the test would time out. - ht.EnsureConnected(alice, bob) - ht.OpenChannel(alice, bob, lntest.OpenChannelParams{ - Amt: btcutil.Amount(100_000), - }) - - // Build a valid onion message destined for Alice. - alicePubKey, err := btcec.ParsePubKey(alice.PubKey[:]) - require.NoError(ht.T, err) - - // Alice is the final destination, so her route data is empty. - aliceData := &record.BlindedRouteData{} - - hops := []*sphinx.HopInfo{ - { - NodePub: alicePubKey, - PlainText: onionmessage.EncodeBlindedRouteData( - ht.T, aliceData, - ), - }, - } - - blindedPath := onionmessage.BuildBlindedPath(ht.T, hops) - - // Add a custom payload to verify it's received correctly. - finalHopTLVs := []*lnwire.FinalHopTLV{ - { - TLVType: lnwire.InvoiceRequestNamespaceType, - Value: []byte{1, 2, 3}, - }, - } - - onionMsg, _ := onionmessage.BuildOnionMessage( - ht.T, blindedPath, finalHopTLVs, - ) - - // Send it from Bob to Alice. - pathKey := blindedPath.SessionKey.PubKey().SerializeCompressed() - bobMsg := &lnrpc.SendOnionMessageRequest{ - Peer: alice.PubKey[:], - PathKey: pathKey, - Onion: onionMsg.OnionBlob, - } - bob.RPC.SendOnionMessage(bobMsg) - - // Wait for Alice to receive the message. - select { - case msg := <-messages: - // Check we received the message from Bob. - require.Equal(ht, bob.PubKey[:], msg.Peer, "msg peer wrong") - - case <-time.After(lntest.DefaultTimeout): - ht.Fatalf("alice did not receive onion message: %v", bobMsg) - } -} diff --git a/itest/lnd_open_channel_test.go b/itest/lnd_open_channel_test.go index 5e92ebe1a..e1959b8b0 100644 --- a/itest/lnd_open_channel_test.go +++ b/itest/lnd_open_channel_test.go @@ -3,10 +3,9 @@ package itest import ( "fmt" "strings" - "time" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lncfg" @@ -91,7 +90,8 @@ func testOpenChannelAfterReorg(ht *lntest.HarnessTest) { // open. block := ht.MineBlocksAndAssertNumTxes(10, 1)[0] ht.AssertTxInBlock(block, *fundingTxID) - tempMiner.GenerateBlocks(15) + _, err = tempMiner.Client.Generate(15) + require.NoError(ht, err, "unable to generate blocks") // Ensure the chain lengths are what we expect, with the temp miner // being 5 blocks ahead. @@ -135,7 +135,8 @@ func testOpenChannelAfterReorg(ht *lntest.HarnessTest) { // This should have caused a reorg, and Alice should sync to the longer // chain, where the funding transaction is not confirmed. - _, tempMinerHeight := tempMiner.GetBestBlock() + _, tempMinerHeight, err := tempMiner.Client.GetBestBlock() + require.NoError(ht, err, "unable to get current blockheight") ht.WaitForNodeBlockHeight(alice, tempMinerHeight) // Since the fundingtx was reorged out, Alice should now have no edges @@ -1045,7 +1046,8 @@ func testPendingChannelAfterReorg(ht *lntest.HarnessTest) { // We now cause a fork, by letting our original miner mine 1 blocks, // and our new miner mine 3. - tempMiner.GenerateBlocks(3) + _, err := tempMiner.Client.Generate(3) + require.NoError(ht, err, "unable to generate blocks on temp miner") // Ensure the chain lengths are what we expect, with the temp miner // being 2 blocks ahead. @@ -1070,7 +1072,8 @@ func testPendingChannelAfterReorg(ht *lntest.HarnessTest) { // This should have caused a reorg, and Alice should sync to the longer // chain, where the funding transaction is not confirmed. - _, tempMinerHeight := tempMiner.GetBestBlock() + _, tempMinerHeight, err := tempMiner.Client.GetBestBlock() + require.NoError(ht, err, "unable to get current blockheight") ht.WaitForNodeBlockHeight(alice, tempMinerHeight) // After the reorg, the funding transaction's confirmation is removed, @@ -1153,66 +1156,6 @@ func testSimpleTaprootChannelActivation(ht *lntest.HarnessTest) { ht.AssertChannelActive(alice, chanPoint) } -// testSimpleTaprootFinalChannelActivation ensures that a simple taproot final -// channel (using production scripts) is active if the initiator disconnects -// and reconnects in between channel opening and channel confirmation. -func testSimpleTaprootFinalChannelActivation(ht *lntest.HarnessTest) { - simpleTaprootFinalChanArgs := lntest.NodeArgsForCommitType( - lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL, - ) - - // Make the new set of participants. - alice := ht.NewNode("alice", simpleTaprootFinalChanArgs) - bob := ht.NewNode("bob", simpleTaprootFinalChanArgs) - - ht.FundCoins(btcutil.SatoshiPerBitcoin, alice) - - // Make sure Alice and Bob are connected. - ht.EnsureConnected(alice, bob) - - // Create simple taproot final channel opening parameters. - params := lntest.OpenChannelParams{ - FundMax: true, - CommitmentType: lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL, - Private: true, - } - - // Alice opens the channel to Bob. - pendingChan := ht.OpenChannelAssertPending(alice, bob, params) - - // We'll create the channel point to be able to close the channel once - // our test is done. - chanPoint := &lnrpc.ChannelPoint{ - FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{ - FundingTxidBytes: pendingChan.Txid, - }, - OutputIndex: pendingChan.OutputIndex, - } - - // We disconnect and reconnect Alice and Bob before the channel is - // confirmed. Our expectation is that the channel is active once the - // channel is confirmed. - ht.DisconnectNodes(alice, bob) - ht.EnsureConnected(alice, bob) - - // Mine six blocks to confirm the channel funding transaction. - ht.MineBlocksAndAssertNumTxes(6, 1) - - // Verify that Alice sees an active channel to Bob. - ht.AssertChannelActive(alice, chanPoint) - - // Verify that the channel uses the final taproot commitment type. - aliceChannels := alice.RPC.ListChannels(&lnrpc.ListChannelsRequest{}) - require.Len(ht, aliceChannels.Channels, 1) - require.Equal(ht, lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL, - aliceChannels.Channels[0].CommitmentType) - - bobChannels := bob.RPC.ListChannels(&lnrpc.ListChannelsRequest{}) - require.Len(ht, bobChannels.Channels, 1) - require.Equal(ht, lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL, - bobChannels.Channels[0].CommitmentType) -} - // testOpenChannelLockedBalance tests that when a funding reservation is // made for opening a channel, the balance of the required outputs shows // up as locked balance in the WalletBalance response. @@ -1326,174 +1269,3 @@ func testFundingManagerFundingTimeout(ht *lntest.HarnessTest) { // Cleanup the mempool by mining blocks. ht.MineBlocksAndAssertNumTxes(6, 1) } - -// testOpenChannelWithShutdownAddr verifies that if the funder or fundee -// specifies an upfront shutdown address in the config, the funds are correctly -// transferred to the specified address during channel closure. -func testOpenChannelWithShutdownAddr(ht *lntest.HarnessTest) { - const ( - // Channel funding amount in sat. - channelAmount int64 = 100000 - - // Payment amount in sat. - paymentAmount int64 = 50000 - ) - - // Create nodes for testing, ensuring Alice has sufficient initial - // funds. - alice := ht.NewNodeWithCoins("Alice", nil) - bob := ht.NewNode("Bob", nil) - - // Generate upfront shutdown addresses for both nodes. - aliceShutdownAddr := alice.RPC.NewAddress(&lnrpc.NewAddressRequest{ - Type: lnrpc.AddressType_UNUSED_WITNESS_PUBKEY_HASH, - }) - bobShutdownAddr := bob.RPC.NewAddress(&lnrpc.NewAddressRequest{ - Type: lnrpc.AddressType_UNUSED_WITNESS_PUBKEY_HASH, - }) - - // Update nodes with upfront shutdown addresses and restart them. - aliceNodeArgs := []string{ - fmt.Sprintf( - "--upfront-shutdown-address=%s", - aliceShutdownAddr.Address, - ), - } - ht.RestartNodeWithExtraArgs(alice, aliceNodeArgs) - - bobNodeArgs := []string{ - fmt.Sprintf( - "--upfront-shutdown-address=%s", - bobShutdownAddr.Address, - ), - } - ht.RestartNodeWithExtraArgs(bob, bobNodeArgs) - - // Connect Alice and Bob. - ht.ConnectNodes(alice, bob) - - // Open a channel between Alice and Bob. - openChannelParams := lntest.OpenChannelParams{ - Amt: btcutil.Amount(channelAmount), - PushAmt: btcutil.Amount(paymentAmount), - } - channelPoint := ht.OpenChannel(alice, bob, openChannelParams) - - // Now close out the channel and obtain the raw closing TX. - closingTxid := ht.CloseChannel(alice, channelPoint) - closingTx := ht.GetRawTransaction(closingTxid).MsgTx() - - // Calculate Alice's updated balance. - aliceFee := ht.CalculateTxFee(closingTx) - aliceExpectedBalance := channelAmount - paymentAmount - int64(aliceFee) - - // Ensure Alice sees the change output in the list of unspent outputs. - // We expect 6 confirmed UTXOs, as 5 UTXOs of 1 BTC each were sent to - // the node during NewNodeWithCoins. - aliceUTXOConfirmed := ht.AssertNumUTXOsConfirmed(alice, 6)[0] - require.Equal(ht, aliceShutdownAddr.Address, aliceUTXOConfirmed.Address) - require.Equal(ht, aliceExpectedBalance, aliceUTXOConfirmed.AmountSat) - - // Ensure Bob see the change output in the list of unspent outputs. - bobUTXOConfirmed := ht.AssertNumUTXOsConfirmed(bob, 1)[0] - require.Equal(ht, bobShutdownAddr.Address, bobUTXOConfirmed.Address) - require.Equal(ht, paymentAmount, bobUTXOConfirmed.AmountSat) -} - -// testChannelUpdateNotifications checks that clients subscribed to channel -// events receive real-time updates when the channel state changes. -func testChannelUpdateNotifications(ht *lntest.HarnessTest) { - // We'll start by creating two nodes, Alice and Bob, and a channel - // between them. - alice := ht.NewNodeWithCoins("Alice", nil) - bob := ht.NewNode("Bob", nil) - - ht.EnsureConnected(alice, bob) - - // We'll subscribe to channel events for both nodes. - aliceSub := alice.RPC.SubscribeChannelEvents() - bobSub := bob.RPC.SubscribeChannelEvents() - - // We'll then open a channel between Alice and Bob. - chanPoint := ht.OpenChannel( - alice, bob, lntest.OpenChannelParams{ - Amt: 1000000, - PushAmt: 500000, - }, - ) - - // We'll wait for the channel to be active. We expect to receive one - // pending, one open, and one active notification. - ht.AssertChannelActive(alice, chanPoint) - ht.AssertChannelActive(bob, chanPoint) - - ht.AssertChannelEventType( - aliceSub, lnrpc.ChannelEventUpdate_PENDING_OPEN_CHANNEL, - ) - ht.AssertChannelEventType( - aliceSub, lnrpc.ChannelEventUpdate_OPEN_CHANNEL, - ) - ht.AssertChannelEventType( - aliceSub, lnrpc.ChannelEventUpdate_ACTIVE_CHANNEL, - ) - - ht.AssertChannelEventType( - bobSub, lnrpc.ChannelEventUpdate_PENDING_OPEN_CHANNEL, - ) - ht.AssertChannelEventType( - bobSub, lnrpc.ChannelEventUpdate_OPEN_CHANNEL, - ) - ht.AssertChannelEventType( - bobSub, lnrpc.ChannelEventUpdate_ACTIVE_CHANNEL, - ) - - // We'll now make a payment from Alice to Bob to trigger a channel - // update. - payReqs, _, _ := ht.CreatePayReqs(bob, btcutil.Amount(1000), 1) - ht.CompletePaymentRequests(alice, payReqs) - - // assertUpdates is a helper function to assert the number of commitment - // updates received by a node. - assertUpdates := func(sub rpc.ChannelEventsClient, numUpdates int) { - event := ht.AssertChannelEventType( - sub, lnrpc.ChannelEventUpdate_CHANNEL_UPDATE, - ) - require.IsType( - ht, &lnrpc.ChannelEventUpdate_UpdatedChannel{}, - event.Channel, - ) - channel := event.GetUpdatedChannel().Channel - require.EqualValues(ht, numUpdates, channel.NumUpdates) - } - - // expectNoMoreUpdates is a helper function to assert that no more - // channel updates are received by a node. - expectNoMoreUpdates := func(sub rpc.ChannelEventsClient) { - updates := make(chan struct{}) - go func() { - _, err := sub.Recv() - // Only signal if we successfully received an update. - // If Recv fails (e.g., context canceled during test - // cleanup), that's fine - it means no update arrived. - if err == nil { - close(updates) - } - }() - - select { - case <-updates: - ht.Fatalf("expected no more updates") - case <-time.After(defaultTimeout): - } - } - - // We expect to see two updates from each node's point of view. One for - // the addition of the HTLC, and a second for the settlement. - assertUpdates(aliceSub, 1) - assertUpdates(aliceSub, 2) - expectNoMoreUpdates(aliceSub) - - assertUpdates(bobSub, 1) - assertUpdates(bobSub, 2) - expectNoMoreUpdates(bobSub) -} diff --git a/itest/lnd_payment_migration_test.go b/itest/lnd_payment_migration_test.go deleted file mode 100644 index 6a26870ff..000000000 --- a/itest/lnd_payment_migration_test.go +++ /dev/null @@ -1,193 +0,0 @@ -package itest - -import ( - "database/sql" - - "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/lnrpc/routerrpc" - "github.com/lightningnetwork/lnd/lntest" - "github.com/lightningnetwork/lnd/lntest/node" - paymentsdb "github.com/lightningnetwork/lnd/payments/db" - "github.com/lightningnetwork/lnd/sqldb" - "github.com/stretchr/testify/require" -) - -// openNativeSQLPaymentsDB opens the native SQL payments store for the given -// node, using the already-migrated SQL database on disk. -func openNativeSQLPaymentsDB(ht *lntest.HarnessTest, - hn *node.HarnessNode) paymentsdb.DB { - - db := openNativeSQLDB(ht, hn) - - executor := sqldb.NewTransactionExecutor( - db, func(tx *sql.Tx) paymentsdb.SQLQueries { - return db.WithTx(tx) - }, - ) - - queryCfg := sqldb.DefaultSQLiteConfig() - if hn.Cfg.DBBackend != node.BackendSqlite { - queryCfg = sqldb.DefaultPostgresConfig() - } - - store, err := paymentsdb.NewSQLStore( - &paymentsdb.SQLStoreConfig{ - QueryCfg: queryCfg, - }, - executor, - ) - require.NoError(ht, err) - - return store -} - -// testPaymentMigration tests that the payment migration from the old KV store -// to the new native SQL store works correctly. -// -// Crucially, it also verifies that payments sent *after* the migration land in -// the SQL backend. This catches the regression where the schema migration was -// promoted to mainline but the store was still wired up to the KV backend — -// in that case pre-migration data is copied to SQL by the migration, but all -// new payments continue to be written to KV and are invisible to the SQL store. -func testPaymentMigration(ht *lntest.HarnessTest) { - alice := ht.NewNodeWithCoins("Alice", nil) - bob := ht.NewNodeWithCoins("Bob", nil) - - // Make sure we run the test with SQLite or Postgres. - if alice.Cfg.DBBackend != node.BackendSqlite && - alice.Cfg.DBBackend != node.BackendPostgres { - - ht.Skip("node not running with SQLite or Postgres") - } - - // Skip the test if the node is already running with native SQL. - if alice.Cfg.NativeSQL { - ht.Skip("node already running with native SQL") - } - - ht.EnsureConnected(alice, bob) - cp := ht.OpenChannel( - alice, bob, lntest.OpenChannelParams{ - Amt: 1_000_000, - PushAmt: 500_000, - }, - ) - - const numPreMigrationPayments = 5 - - // Step 1: Send payments from Alice to Bob before the migration. These - // will be written to Alice's KV payment store. - for i := range numPreMigrationPayments { - invoice := bob.RPC.AddInvoice(&lnrpc.Invoice{ - Value: int64(1_000 + i*100), - }) - - ht.SendPaymentAssertSettled( - alice, &routerrpc.SendPaymentRequest{ - PaymentRequest: invoice.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, - ) - } - - ht.CloseChannel(alice, cp) - - // Stop Alice so we can safely examine the database. - require.NoError(ht, alice.Stop()) - - // Open the KV payments store and confirm the pre-migration payment - // count. - kvPaymentsDB, err := paymentsdb.NewKVStore(openKVBackend(ht, alice)) - require.NoError(ht, err) - - allPaymentsQuery := paymentsdb.Query{ - MaxPayments: 9999, - IncludeIncomplete: true, - } - - kvResult, err := kvPaymentsDB.QueryPayments( - ht.Context(), allPaymentsQuery, - ) - require.NoError(ht, err) - require.Len(ht, kvResult.Payments, numPreMigrationPayments) - - // Step 2: Start Alice with --db.use-native-sql to trigger the - // migration. Run it three times to verify the migration is idempotent. - alice.SetExtraArgs([]string{"--db.use-native-sql"}) - - for range 3 { - require.NoError(ht, alice.Start(ht.Context())) - require.NoError(ht, alice.Stop()) - - sqlPaymentsDB := openNativeSQLPaymentsDB(ht, alice) - sqlResult, err := sqlPaymentsDB.QueryPayments( - ht.Context(), allPaymentsQuery, - ) - require.NoError(ht, err) - require.Len(ht, sqlResult.Payments, numPreMigrationPayments) - } - - // Step 3: Send payments after the migration. These must land in the - // SQL backend. If the payments store was accidentally left pointing at - // the KV backend, these payments would be missing from the SQL store - // and the final assertion below would fail. - require.NoError(ht, alice.Start(ht.Context())) - - ht.EnsureConnected(alice, bob) - - cp2 := ht.OpenChannel( - alice, bob, lntest.OpenChannelParams{ - Amt: 1_000_000, - PushAmt: 500_000, - }, - ) - - const numPostMigrationPayments = 5 - - for i := range numPostMigrationPayments { - invoice := bob.RPC.AddInvoice(&lnrpc.Invoice{ - Value: int64(2_000 + i*100), - }) - - ht.SendPaymentAssertSettled( - alice, &routerrpc.SendPaymentRequest{ - PaymentRequest: invoice.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, - ) - } - - ht.CloseChannel(alice, cp2) - require.NoError(ht, alice.Stop()) - - // Open the SQL store directly and verify it contains both the - // pre-migration payments (migrated from KV) and the post-migration - // payments (written directly to SQL). - sqlPaymentsDB := openNativeSQLPaymentsDB(ht, alice) - sqlResult, err := sqlPaymentsDB.QueryPayments( - ht.Context(), allPaymentsQuery, - ) - require.NoError(ht, err) - - totalExpected := numPreMigrationPayments + numPostMigrationPayments - require.Len( - ht, sqlResult.Payments, totalExpected, - "expected %d payments in SQL store (pre + post migration), "+ - "got %d; if post-migration payments are missing the "+ - "payments store may be wired to the KV backend "+ - "instead of SQL in BuildDatabase", totalExpected, - len(sqlResult.Payments), - ) - - // Verify that Alice can no longer start without --db.use-native-sql - // now that the tombstone has been set. - alice.SetExtraArgs(nil) - require.NoError(ht, alice.StartLndCmd(ht.Context())) - require.Error(ht, alice.WaitForProcessExit()) - - // Start Alice again so the harness can clean up properly. - alice.SetExtraArgs([]string{"--db.use-native-sql"}) - require.NoError(ht, alice.Start(ht.Context())) -} diff --git a/itest/lnd_payment_test.go b/itest/lnd_payment_test.go index 707e6d581..37aff0522 100644 --- a/itest/lnd_payment_test.go +++ b/itest/lnd_payment_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" @@ -504,86 +504,61 @@ func testListPayments(ht *lntest.HarnessTest) { expected bool } - // Create test cases with proper rounding for start and end dates. - createCases := func(startTimeSeconds, - endTimeSeconds uint64) []testCase { - + // Create test cases to check the timestamp filters. + createCases := func(createTimeSeconds uint64) []testCase { return []testCase{ { // Use a start date same as the creation date - // (truncated) should return us the item. + // should return us the item. name: "exact start date", - startDate: startTimeSeconds, + startDate: createTimeSeconds, expected: true, }, { // Use an earlier start date should return us // the item. name: "earlier start date", - startDate: startTimeSeconds - 1, + startDate: createTimeSeconds - 1, expected: true, }, { // Use a future start date should return us // nothing. name: "future start date", - startDate: startTimeSeconds + 1, + startDate: createTimeSeconds + 1, expected: false, }, { // Use an end date same as the creation date - // (ceiling) should return us the item. + // should return us the item. name: "exact end date", - endDate: endTimeSeconds, + endDate: createTimeSeconds, expected: true, }, { // Use an end date in the future should return // us the item. name: "future end date", - endDate: endTimeSeconds + 1, + endDate: createTimeSeconds + 1, expected: true, }, { // Use an earlier end date should return us // nothing. - name: "earlier end date", - // The native sql backend has a higher - // precision than the kv backend, the native sql - // backend uses microseconds, the kv backend - // when filtering uses seconds so we need to - // subtract 2 seconds to ensure the payment is - // not included. - // We could also truncate before inserting - // into the sql db but I rather relax this test - // here. - endDate: endTimeSeconds - 2, + name: "earlier end date", + endDate: createTimeSeconds - 1, expected: false, }, } } - // Get the payment creation time in seconds, using different approaches - // for start and end date comparisons to avoid rounding issues. - creationTime := time.Unix(0, p.CreationTimeNs) - - // For start date comparisons: use truncation (floor) to include - // payments from the beginning of that second. - paymentCreateSecondsStart := uint64( - creationTime.Truncate(time.Second).Unix(), - ) - - // For end date comparisons: use ceiling to include payments up to the - // end of that second. - paymentCreateSecondsEnd := uint64( - (p.CreationTimeNs + time.Second.Nanoseconds() - 1) / - time.Second.Nanoseconds(), + // Get the payment creation time in seconds. + paymentCreateSeconds := uint64( + p.CreationTimeNs / time.Second.Nanoseconds(), ) // Create test cases from the payment creation time. - testCases := createCases( - paymentCreateSecondsStart, paymentCreateSecondsEnd, - ) + testCases := createCases(paymentCreateSeconds) // We now check the timestamp filters in `ListPayments`. for _, tc := range testCases { @@ -603,9 +578,7 @@ func testListPayments(ht *lntest.HarnessTest) { } // Create test cases from the invoice creation time. - testCases = createCases( - uint64(invoice.CreationDate), uint64(invoice.CreationDate), - ) + testCases = createCases(uint64(invoice.CreationDate)) // We now do the same check for `ListInvoices`. for _, tc := range testCases { @@ -709,9 +682,7 @@ func runAsyncPayments(ht *lntest.HarnessTest, alice, bob *node.HarnessNode, if commitType != nil { chanArgs.CommitmentType = *commitType - if *commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT || - *commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL { //nolint:ll - + if *commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT { chanArgs.Private = true } } diff --git a/itest/lnd_postgres_network_separation_test.go b/itest/lnd_postgres_network_separation_test.go deleted file mode 100644 index 5fd6db9fe..000000000 --- a/itest/lnd_postgres_network_separation_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package itest - -import ( - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/lightningnetwork/lnd/chainparams" - "github.com/lightningnetwork/lnd/lntest" - "github.com/lightningnetwork/lnd/sqldb" - "github.com/stretchr/testify/require" -) - -// testPostgresNetworkSeparation verifies that lnd refuses to start when the -// active Bitcoin network does not match the network stored in the postgres -// chain_params table. This prevents silent data corruption that would occur if -// a user accidentally reused the same postgres DSN across different networks. -// -// Note: the equivalent SQLite scenario (reusing the same .db file across -// networks) is not covered here because the itest harness does not provide a -// direct path to inject an existing SQLite file into a new node. The feature -// is exercised for SQLite at the unit-test level in chainparams/store_test.go. -// -// The test flow is: -// 1. Start lnd on regtest with native SQL enabled → first startup writes -// "regtest" into the chain_params table. -// 2. Restart with the same postgres DSN and regtest → must succeed, proving -// ValidateNetwork passes when the active network matches the stored one. -// 3. Stop the node. -// 4. Restart lnd with the same postgres DSN but switch to simnet → lnd must -// detect the network mismatch and exit with an error. -func testPostgresNetworkSeparation(ht *lntest.HarnessTest) { - // This test is only relevant for the postgres backend with native SQL. - // The SQLite equivalent is covered at the unit-test level. - if !ht.IsPostgresBackend() { - ht.Skip("node not running with postgres backend") - } - - // First startup: native SQL applies migrations and persists regtest in - // chain_params. - alice := ht.NewNodeWithCoins("Alice", []string{"--db.use-native-sql"}) - - // Second startup: same DSN and network — ValidateNetwork must succeed; - // proves the matching path against a real DB, not only unit tests. - ht.RestartNode(alice) - - require.NoError(ht, alice.Stop()) - - // Direct store check: simnet vs stored regtest must be - // ErrNetworkMismatch, independent of whether lnd's process failed for - // the right reason. - store, err := sqldb.NewPostgresStore(&sqldb.PostgresConfig{ - Dsn: alice.Cfg.PostgresDsn, - Timeout: defaultTimeout, - }) - require.NoError(ht, err) - defer store.Close() - - chainParamsStore := chainparams.NewStore(store.GetBaseDB()) - err = chainParamsStore.ValidateNetwork( - ht.Context(), &chaincfg.SimNetParams, - ) - require.ErrorIs(ht, err, chainparams.ErrNetworkMismatch) - - // Process-level check: restart lnd with simnet while the DB still says - // regtest — must exit early (ValidateNetwork during startup). - // - // Now restart alice but override the network to simnet. The DSN still - // points at the same postgres database, so lnd should detect the - // mismatch and refuse to start. - // - // ExtraArgs are appended last when building the lnd command line and - // therefore take precedence over the generated --bitcoin.regtest flag, - // effectively switching the node to simnet. - alice.Cfg.NetParams = &chaincfg.SimNetParams - alice.SetExtraArgs([]string{ - "--db.use-native-sql", - "--bitcoin.simnet", - "--bitcoin.node=neutrino", - }) - - // StartLndCmd launches the process without waiting for it to become - // ready, which is what we want since we expect it to exit early. - require.NoError(ht, alice.StartLndCmd(ht.Context())) - - // The process should exit with a non-zero status due to the network - // mismatch error returned by ValidateNetwork. We only assert that the - // error is non-nil rather than matching the exact OS exit-code string, - // because WaitForProcessExit may return a harness-level shutdown error - // when the node exits before writing "Shutdown complete" to its log. - require.Error(ht, alice.WaitForProcessExit()) -} diff --git a/itest/lnd_psbt_test.go b/itest/lnd_psbt_test.go index d0ac35c90..758aec23b 100644 --- a/itest/lnd_psbt_test.go +++ b/itest/lnd_psbt_test.go @@ -6,16 +6,15 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/btcutil/v2/hdkeychain" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/psbt/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -146,9 +145,7 @@ func runPsbtChanFundingWithNodes(ht *lntest.HarnessTest, carol, // If this is a taproot channel, then we'll decode the PSBT to assert // that an internal key is included. - if commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT || - commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL { - + if commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT { decodedPSBT, err := psbt.NewFromRawBytes( bytes.NewReader(tempPsbt), false, ) @@ -502,7 +499,7 @@ func runPsbtChanFundingSingleStep(ht *lntest.HarnessTest, private bool, Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, } addrResp := carol.RPC.NewAddress(req) - reserveAddr, err := address.DecodeAddress( + reserveAddr, err := btcutil.DecodeAddress( addrResp.Address, harnessNetParams, ) require.NoError(ht, err) @@ -721,8 +718,8 @@ func runSignPsbtSegWitV0P2WKH(ht *lntest.HarnessTest, alice *node.HarnessNode) { addrPubKey, err := addrKey.ECPubKey() require.NoError(ht, err) - pubKeyHash := address.Hash160(addrPubKey.SerializeCompressed()) - witnessAddr, err := address.NewAddressWitnessPubKeyHash( + pubKeyHash := btcutil.Hash160(addrPubKey.SerializeCompressed()) + witnessAddr, err := btcutil.NewAddressWitnessPubKeyHash( pubKeyHash, harnessNetParams, ) require.NoError(ht, err) @@ -802,15 +799,15 @@ func runSignPsbtSegWitV0NP2WKH(ht *lntest.HarnessTest, addrPubKey, err := addrKey.ECPubKey() require.NoError(ht, err) - pubKeyHash := address.Hash160(addrPubKey.SerializeCompressed()) - witnessAddr, err := address.NewAddressWitnessPubKeyHash( + pubKeyHash := btcutil.Hash160(addrPubKey.SerializeCompressed()) + witnessAddr, err := btcutil.NewAddressWitnessPubKeyHash( pubKeyHash, harnessNetParams, ) require.NoError(ht, err) witnessProgram, err := txscript.PayToAddrScript(witnessAddr) require.NoError(ht, err) - np2wkhAddr, err := address.NewAddressScriptHash( + np2wkhAddr, err := btcutil.NewAddressScriptHash( witnessProgram, harnessNetParams, ) require.NoError(ht, err) @@ -858,7 +855,7 @@ func runSignPsbtSegWitV1KeySpendBip86(ht *lntest.HarnessTest, // Our taproot key is a BIP0086 key spend only construction that just // commits to the internal key and no root hash. taprootKey := txscript.ComputeTaprootKeyNoScript(internalKey) - tapScriptAddr, err := address.NewAddressTaproot( + tapScriptAddr, err := btcutil.NewAddressTaproot( schnorr.SerializePubKey(taprootKey), harnessNetParams, ) require.NoError(ht, err) @@ -906,7 +903,7 @@ func runSignPsbtSegWitV1KeySpendRootHash(ht *lntest.HarnessTest, rootHash := leaf1.TapHash() taprootKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash[:]) - tapScriptAddr, err := address.NewAddressTaproot( + tapScriptAddr, err := btcutil.NewAddressTaproot( schnorr.SerializePubKey(taprootKey), harnessNetParams, ) require.NoError(ht, err) @@ -954,7 +951,7 @@ func runSignPsbtSegWitV1ScriptSpend(ht *lntest.HarnessTest, rootHash := leaf1.TapHash() taprootKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash[:]) - tapScriptAddr, err := address.NewAddressTaproot( + tapScriptAddr, err := btcutil.NewAddressTaproot( schnorr.SerializePubKey(taprootKey), harnessNetParams, ) require.NoError(ht, err) @@ -1179,7 +1176,7 @@ func runFundPsbt(ht *lntest.HarnessTest, alice, bob *node.HarnessNode) { // addressToPkScript parses the given address string and returns the pkScript // for the regtest environment. func addressToPkScript(t testing.TB, addr string) []byte { - parsed, err := address.DecodeAddress(addr, harnessNetParams) + parsed, err := btcutil.DecodeAddress(addr, harnessNetParams) require.NoError(t, err) pkScript, err := txscript.PayToAddrScript(parsed) @@ -1921,7 +1918,7 @@ func testPsbtChanFundingWithUnstableUtxos(ht *lntest.HarnessTest) { // Consume the "channel pending" update. This waits until the funding // transaction was fully compiled. updateResp = ht.ReceiveOpenChannelUpdate(chanUpdates) - _, ok = updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending) + upd, ok = updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending) require.True(ht, ok) err = finalTx.Deserialize(bytes.NewReader(finalizeRes.RawFinalTx)) diff --git a/itest/lnd_quiescence_test.go b/itest/lnd_quiescence_test.go index 10a01fed8..c60f93842 100644 --- a/itest/lnd_quiescence_test.go +++ b/itest/lnd_quiescence_test.go @@ -1,7 +1,7 @@ package itest import ( - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/devrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" diff --git a/itest/lnd_recovery_test.go b/itest/lnd_recovery_test.go index dee802794..ea93f373e 100644 --- a/itest/lnd_recovery_test.go +++ b/itest/lnd_recovery_test.go @@ -5,12 +5,11 @@ import ( "fmt" "math" - "github.com/btcsuite/btcd/address/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/btcutil/v2/hdkeychain" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/aezeed" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnrpc" @@ -336,8 +335,8 @@ func testRescanAddressDetection(ht *lntest.HarnessTest) { // Create an address generated from internal keys. keyDesc := carol.RPC.DeriveNextKey(&walletrpc.KeyReq{KeyFamily: 123}) - pubKeyHash := address.Hash160(keyDesc.RawKeyBytes) - ghostUtxoAddr, err := address.NewAddressWitnessPubKeyHash( + pubKeyHash := btcutil.Hash160(keyDesc.RawKeyBytes) + ghostUtxoAddr, err := btcutil.NewAddressWitnessPubKeyHash( pubKeyHash, harnessNetParams, ) require.NoError(ht, err) diff --git a/itest/lnd_remote_signer_test.go b/itest/lnd_remote_signer_test.go index 1c1ffa592..eac22828b 100644 --- a/itest/lnd_remote_signer_test.go +++ b/itest/lnd_remote_signer_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/btcutil/v2/hdkeychain" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnrpc" @@ -47,10 +47,6 @@ var remoteSignerTestCases = []*lntest.TestCase{ Name: "funding async payments taproot", TestFunc: testRemoteSignerAsyncPaymentsTaproot, }, - { - Name: "funding async payments taproot final", - TestFunc: testRemoteSignerAsyncPaymentsTaprootFinal, - }, { Name: "shared key", TestFunc: testRemoteSignerSharedKey, @@ -158,9 +154,7 @@ func prepareRemoteSignerTest(ht *lntest.HarnessTest, tc remoteSignerTestCase) ( } var commitArgs []string - if tc.commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT || - tc.commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL { - + if tc.commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT { commitArgs = lntest.NodeArgsForCommitType( tc.commitType, ) @@ -313,24 +307,6 @@ func testRemoteSignerAsyncPaymentsTaproot(ht *lntest.HarnessTest) { tc.fn(ht, watchOnly, carol) } -func testRemoteSignerAsyncPaymentsTaprootFinal(ht *lntest.HarnessTest) { - tc := remoteSignerTestCase{ - name: "async payments taproot final", - sendCoins: true, - fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) { - commitType := lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - - runAsyncPayments( - tt, wo, carol, &commitType, - ) - }, - commitType: lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL, - } - - _, watchOnly, carol := prepareRemoteSignerTest(ht, tc) - tc.fn(ht, watchOnly, carol) -} - func testRemoteSignerSharedKey(ht *lntest.HarnessTest) { tc := remoteSignerTestCase{ name: "shared key", diff --git a/itest/lnd_res_handoff_test.go b/itest/lnd_res_handoff_test.go index 626a2183a..d7bf49990 100644 --- a/itest/lnd_res_handoff_test.go +++ b/itest/lnd_res_handoff_test.go @@ -3,7 +3,7 @@ package itest import ( "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/stretchr/testify/require" diff --git a/itest/lnd_rest_api_test.go b/itest/lnd_rest_api_test.go index 5ec4823f3..70e103208 100644 --- a/itest/lnd_rest_api_test.go +++ b/itest/lnd_rest_api_test.go @@ -221,12 +221,14 @@ func testRestAPI(ht *lntest.HarnessTest) { alice := ht.NewNodeWithCoins("Alice", args) for _, tc := range testCases { + tc := tc ht.Run(tc.name, func(t *testing.T) { tc.run(t, alice, bob) }) } for _, tc := range wsTestCases { + tc := tc ht.Run(tc.name, func(t *testing.T) { st := ht.Subtest(t) tc.run(st) diff --git a/itest/lnd_revocation_test.go b/itest/lnd_revocation_test.go index 0cc7d6de2..b975438f1 100644 --- a/itest/lnd_revocation_test.go +++ b/itest/lnd_revocation_test.go @@ -6,9 +6,9 @@ import ( "fmt" "testing" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntest" @@ -52,8 +52,7 @@ func breachRetributionTestCase(ht *lntest.HarnessTest, // In order to test Carol's response to an uncooperative channel // closure by Bob, we'll first open up a channel between them with a // 0.5 BTC value. - privateChan := commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT || - commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL + privateChan := commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT chanPoint := ht.OpenChannel( carol, bob, lntest.OpenChannelParams{ CommitmentType: commitType, @@ -199,7 +198,6 @@ func breachRetributionTestCase(ht *lntest.HarnessTest, func testRevokedCloseRetribution(ht *lntest.HarnessTest) { for _, commitType := range []lnrpc.CommitmentType{ lnrpc.CommitmentType_SIMPLE_TAPROOT, - lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL, } { testName := fmt.Sprintf("%v", commitType.String()) ht.Run(testName, func(t *testing.T) { @@ -250,8 +248,7 @@ func revokedCloseRetributionZeroValueRemoteOutputCase(ht *lntest.HarnessTest, // In order to test Dave's response to an uncooperative channel // closure by Carol, we'll first open up a channel between them with a // 0.5 BTC value. - privateChan := commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT || - commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL + privateChan := commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT chanPoint := ht.OpenChannel( dave, carol, lntest.OpenChannelParams{ CommitmentType: commitType, @@ -282,10 +279,8 @@ func revokedCloseRetributionZeroValueRemoteOutputCase(ht *lntest.HarnessTest, // backup. ht.EnsureConnected(dave, carol) - // Once connected, wait for both channel links to be active again. + // Once connected, give Dave some time to enable the channel again. ht.AssertChannelInGraph(dave, chanPoint) - ht.AssertChannelActive(dave, chanPoint) - ht.AssertChannelActive(carol, chanPoint) // Finally, send payments from Dave to Carol, consuming Carol's // remaining payment hashes. @@ -386,7 +381,6 @@ func revokedCloseRetributionZeroValueRemoteOutputCase(ht *lntest.HarnessTest, func testRevokedCloseRetributionZeroValueRemoteOutput(ht *lntest.HarnessTest) { for _, commitType := range []lnrpc.CommitmentType{ lnrpc.CommitmentType_SIMPLE_TAPROOT, - lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL, } { testName := fmt.Sprintf("%v", commitType.String()) ht.Run(testName, func(t *testing.T) { @@ -441,8 +435,7 @@ func revokedCloseRetributionRemoteHodlCase(ht *lntest.HarnessTest, // In order to test Dave's response to an uncooperative channel closure // by Carol, we'll first open up a channel between them with a // funding.MaxBtcFundingAmount (2^24) satoshis value. - privateChan := commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT || - commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL + privateChan := commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT chanPoint := ht.OpenChannel( dave, carol, lntest.OpenChannelParams{ Amt: chanAmt, @@ -514,10 +507,8 @@ func revokedCloseRetributionRemoteHodlCase(ht *lntest.HarnessTest, // backup. ht.EnsureConnected(dave, carol) - // Once connected, wait for both channel links to be active again. + // Once connected, give Dave some time to enable the channel again. ht.AssertChannelInGraph(dave, chanPoint) - ht.AssertChannelActive(dave, chanPoint) - ht.AssertChannelActive(carol, chanPoint) // Finally, send payments from Dave to Carol, consuming Carol's // remaining payment hashes. @@ -598,7 +589,7 @@ func revokedCloseRetributionRemoteHodlCase(ht *lntest.HarnessTest, // NOTE: We don't use `ht.GetRawTransaction` // which asserts a txid must be found as the HTLC // spending txes might be aggregated. - tx, err := ht.Miner().GetRawTransactionNoAssert(txid) + tx, err := ht.Miner().Client.GetRawTransaction(&txid) if err != nil { return nil, err } @@ -713,7 +704,6 @@ func revokedCloseRetributionRemoteHodlCase(ht *lntest.HarnessTest, func testRevokedCloseRetributionRemoteHodl(ht *lntest.HarnessTest) { for _, commitType := range []lnrpc.CommitmentType{ lnrpc.CommitmentType_SIMPLE_TAPROOT, - lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL, } { testName := fmt.Sprintf("%v", commitType.String()) ht.Run(testName, func(t *testing.T) { diff --git a/itest/lnd_route_blinding_test.go b/itest/lnd_route_blinding_test.go index ea3b2c685..af2612d24 100644 --- a/itest/lnd_route_blinding_test.go +++ b/itest/lnd_route_blinding_test.go @@ -1,7 +1,6 @@ package itest import ( - "bytes" "context" "crypto/sha256" "encoding/hex" @@ -10,17 +9,13 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - sphinx "github.com/lightningnetwork/lightning-onion" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/chainreg" - "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/node" "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/record" - "github.com/lightningnetwork/lnd/tlv" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -357,7 +352,7 @@ func (b *blindedForwardTest) setupNetwork(ctx context.Context, withInterceptor bool) { carolArgs := []string{ - "--bitcoin.timelockdelta=24", + "--bitcoin.timelockdelta=18", fmt.Sprintf("--bitcoin.defaultremotedelay=%v", toLocalCSV), } if withInterceptor { @@ -365,7 +360,7 @@ func (b *blindedForwardTest) setupNetwork(ctx context.Context, } daveArgs := []string{ - "--bitcoin.timelockdelta=24", + "--bitcoin.timelockdelta=18", fmt.Sprintf("--bitcoin.defaultremotedelay=%v", toLocalCSV), } cfgs := [][]string{nil, nil, carolArgs, daveArgs} @@ -388,78 +383,6 @@ func (b *blindedForwardTest) setupNetwork(ctx context.Context, } } -// setupNetworkPrivateMiddle sets up the same Alice -> Bob -> Carol -> Dave -// network as setupNetwork (with an interceptor on Carol), except that the -// Bob -> Carol channel is private. This is the channel the introduction node -// (Bob) must resolve to from Carol's node ID, exercising resolution to an SCID -// alias of an unadvertised channel. -func (b *blindedForwardTest) setupNetworkPrivateMiddle(ctx context.Context) { - carolArgs := []string{ - "--bitcoin.timelockdelta=24", - fmt.Sprintf("--bitcoin.defaultremotedelay=%v", toLocalCSV), - "--requireinterceptor", - } - daveArgs := []string{ - "--bitcoin.timelockdelta=24", - fmt.Sprintf("--bitcoin.defaultremotedelay=%v", toLocalCSV), - } - - alice := b.ht.NewNode("Alice", nil) - bob := b.ht.NewNode("Bob", nil) - carol := b.ht.NewNode("Carol", carolArgs) - dave := b.ht.NewNode("Dave", daveArgs) - b.alice, b.bob, b.carol, b.dave = alice, bob, carol, dave - - b.ht.EnsureConnected(alice, bob) - b.ht.EnsureConnected(bob, carol) - b.ht.EnsureConnected(carol, dave) - - // Fund every node that opens a channel. - const chanAmt = btcutil.Amount(100_000) - b.ht.FundCoins(btcutil.SatoshiPerBitcoin, alice) - b.ht.FundCoins(btcutil.SatoshiPerBitcoin, bob) - b.ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) - - // Open Alice -> Bob and Carol -> Dave as public channels, but Bob -> - // Carol (the hop the introduction node must resolve by node ID) as a - // private channel, so it is only reachable via an SCID alias. - reqs := []*lntest.OpenChannelRequest{ - { - Local: alice, - Remote: bob, - Param: lntest.OpenChannelParams{Amt: chanAmt}, - }, - { - Local: bob, - Remote: carol, - Param: lntest.OpenChannelParams{ - Amt: chanAmt, - Private: true, - }, - }, - { - Local: carol, - Remote: dave, - Param: lntest.OpenChannelParams{Amt: chanAmt}, - }, - } - b.channels = b.ht.OpenMultiChannelsAsync(reqs) - - // Alice must know the public Alice -> Bob channel to build a route to - // the introduction node, and Bob and Carol must both know the private - // Bob -> Carol channel used for forwarding. - b.ht.AssertChannelInGraph(alice, b.channels[0]) - b.ht.AssertChannelInGraph(bob, b.channels[0]) - b.ht.AssertChannelInGraph(bob, b.channels[1]) - b.ht.AssertChannelInGraph(carol, b.channels[1]) - b.ht.AssertChannelInGraph(carol, b.channels[2]) - b.ht.AssertChannelInGraph(dave, b.channels[2]) - - var err error - b.carolInterceptor, err = b.carol.RPC.Router.HtlcInterceptor(ctx) - require.NoError(b.ht, err, "interceptor") -} - // buildBlindedPath returns a blinded route from Bob -> Carol -> Dave, with Bob // acting as the introduction point. func (b *blindedForwardTest) buildBlindedPath() *lnrpc.BlindedPaymentPath { @@ -500,6 +423,8 @@ func (b *blindedForwardTest) cleanup() { // createRouteToBlinded queries for a route from alice to the blinded path // provided. +// +//nolint:gomnd func (b *blindedForwardTest) createRouteToBlinded(paymentAmt int64, blindedPath *lnrpc.BlindedPaymentPath) *lnrpc.Route { @@ -781,13 +706,6 @@ func testIntroductionNodeError(ht *lntest.HarnessTest) { // at the introduction node. testCase.drainCarolLiquidity(true) - // NOTE: The drain above causes Bob to originate a payment, producing - // SEND-type HTLC events that may still be in-flight when we subscribe. - // Wait for the commitment dance to finish so those events are flushed - // before we subscribe, preventing them from corrupting the assertion - // below. - flakePaymentStreamReturnEarly() - // Subscribe to Bob's HTLC events so that we can observe the payment // coming in. bobEvents := bob.RPC.SubscribeHtlcEvents() @@ -1503,349 +1421,6 @@ func testBlindedPaymentHTLCReForward(ht *lntest.HarnessTest) { } } -// nextNodeIDRouteData builds the recipient data for a non-final blinded hop -// that identifies the next hop by its node ID (next_node_id) rather than a -// short channel ID. This is the form of recipient data that a non-lnd -// implementation may produce and that the forwarding node must resolve to one -// of its active channels. -func nextNodeIDRouteData(nextNode *btcec.PublicKey, - relayInfo record.PaymentRelayInfo, - constraints *record.PaymentConstraints) *record.BlindedRouteData { - - return &record.BlindedRouteData{ - NextNodeID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType4](nextNode), - ), - RelayInfo: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType10](relayInfo), - ), - Constraints: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType12](*constraints), - ), - } -} - -// buildBlindedPathWithNextNodeID constructs a Bob -> Carol -> Dave blinded path -// in which the non-final hops (Bob and Carol) identify their next hop by node -// ID instead of a short channel ID. Bob is the introduction node. The returned -// path can be used to exercise an lnd forwarding node's ability to resolve a -// next_node_id to one of its active channels. -func (b *blindedForwardTest) buildBlindedPathWithNextNodeID( - paymentAmt int64) *lnrpc.BlindedPaymentPath { - - bobPub, err := btcec.ParsePubKey(b.bob.PubKey[:]) - require.NoError(b.ht, err) - - carolPub, err := btcec.ParsePubKey(b.carol.PubKey[:]) - require.NoError(b.ht, err) - - davePub, err := btcec.ParsePubKey(b.dave.PubKey[:]) - require.NoError(b.ht, err) - - // Use zero fees so that the forwarded amount remains constant along the - // path, keeping the route math trivial. - const ( - hopCltvDelta uint16 = 144 - finalCltvDelta uint32 = 24 - ) - - // Set a generous max CLTV constraint so that the incoming expiry at - // each hop never trips the payment constraints check. - info := b.alice.RPC.GetInfo() - constraints := &record.PaymentConstraints{ - MaxCltvExpiry: info.BlockHeight + 10_000, - HtlcMinimumMsat: 0, - } - relayInfo := record.PaymentRelayInfo{ - CltvExpiryDelta: hopCltvDelta, - FeeRate: 0, - BaseFee: 0, - } - - // Bob (the introduction node) forwards to Carol and Carol forwards to - // Dave, each identified purely by node ID. Dave is the final hop; its - // path ID is arbitrary because the payment is settled at Carol via the - // interceptor before it ever reaches Dave. - hopData := []struct { - pub *btcec.PublicKey - data *record.BlindedRouteData - }{ - { - pub: bobPub, - data: nextNodeIDRouteData( - carolPub, relayInfo, constraints, - ), - }, - { - pub: carolPub, - data: nextNodeIDRouteData( - davePub, relayInfo, constraints, - ), - }, - { - pub: davePub, - data: record.NewFinalHopBlindedRouteData( - constraints, bytes.Repeat([]byte{1}, 32), - ), - }, - } - - paymentPath := make([]*sphinx.HopInfo, len(hopData)) - for i, hop := range hopData { - plainText, err := record.EncodeBlindedRouteData(hop.data) - require.NoError(b.ht, err) - - paymentPath[i] = &sphinx.HopInfo{ - NodePub: hop.pub, - PlainText: plainText, - } - } - - // Encrypt the per-hop data into a blinded path using a fresh session - // key. - sessionKey, err := btcec.NewPrivateKey() - require.NoError(b.ht, err) - - blindedPathInfo, err := sphinx.BuildBlindedPath(sessionKey, paymentPath) - require.NoError(b.ht, err) - blindedPath := blindedPathInfo.Path - - // The introduction node is communicated in plaintext, so overwrite the - // first hop's blinded pub key with the real introduction point. - blindedPath.BlindedHops[0].BlindedNodePub = - blindedPath.IntroductionPoint - - blindedHops := make( - []*lnrpc.BlindedHop, len(blindedPath.BlindedHops), - ) - for i, hop := range blindedPath.BlindedHops { - blindedHops[i] = &lnrpc.BlindedHop{ - BlindedNode: hop.BlindedNodePub.SerializeCompressed(), - EncryptedData: hop.CipherText, - } - } - - return &lnrpc.BlindedPaymentPath{ - BlindedPath: &lnrpc.BlindedPath{ - IntroductionNode: b.bob.PubKey[:], - BlindingPoint: blindedPath.BlindingPoint. - SerializeCompressed(), - BlindedHops: blindedHops, - }, - BaseFeeMsat: 0, - TotalCltvDelta: 2*uint32(hopCltvDelta) + finalCltvDelta, - HtlcMinMsat: 0, - HtlcMaxMsat: uint64(paymentAmt) * 2, - } -} - -// testBlindedRouteNextNodeID tests that an lnd node acting as the introduction -// node of a blinded path can forward a payment when the recipient identifies -// the next hop by its node ID (next_node_id) rather than a short channel ID. -// The introduction node must resolve the node ID to one of its active channels -// with that peer. -func testBlindedRouteNextNodeID(ht *lntest.HarnessTest) { - ctx, testCase := newBlindedForwardTest(ht) - defer testCase.cleanup() - - // Set up the Alice -> Bob -> Carol -> Dave network with an interceptor - // on Carol. Bob is the introduction node whose node ID resolution we - // want to exercise, and Carol's interceptor lets us deterministically - // observe that Bob successfully resolved and forwarded the HTLC. - testCase.setupNetwork(ctx, true) - - testCase.runNextNodeIDForward(ctx, nil) -} - -// testBlindedRouteNextNodeIDPrivateChannel is like testBlindedRouteNextNodeID, -// but the Bob -> Carol channel that the introduction node must resolve by node -// ID is private. This exercises the introduction node's ability to resolve the -// next node's ID to an SCID alias of an unadvertised channel (option-scid-alias -// channels are not forwardable by their confirmed SCID). -func testBlindedRouteNextNodeIDPrivateChannel(ht *lntest.HarnessTest) { - ctx, testCase := newBlindedForwardTest(ht) - defer testCase.cleanup() - - // Set up Alice -> Bob -> Carol -> Dave where the Bob -> Carol channel - // is private, so Bob must resolve Carol's node ID to that channel's - // alias. - testCase.setupNetworkPrivateMiddle(ctx) - - testCase.runNextNodeIDForward(ctx, nil) -} - -// testBlindedRouteNextNodeIDRestart tests that a blinded payment forwarded by -// node ID survives a restart of the introduction node. The HTLC is held at the -// receiver's interceptor after the introduction node (Bob) has resolved the -// next node's ID and forwarded it. Bob is then restarted, forcing it to replay -// its forwarding package and re-decode the node-ID blinded hop, after which the -// in-flight HTLC must remain intact and the payment must still settle. -func testBlindedRouteNextNodeIDRestart(ht *lntest.HarnessTest) { - ctx, testCase := newBlindedForwardTest(ht) - defer testCase.cleanup() - - testCase.setupNetwork(ctx, true) - - // Open a second, parallel Bob -> Carol channel with zero fees, matching - // the zero-fee policy runNextNodeIDForward sets on channels[1]. The - // blinded path identifies the hop by Carol's node ID, so both Bob -> - // Carol channels are valid candidates and Bob's non-strict forwarding - // picks one at random. We use this to prove that replaying the - // forwarding package after a restart re-pins the same randomly selected - // channel and does not duplicate the HTLC onto the other one. - ht.FundCoins(btcutil.SatoshiPerBitcoin, testCase.bob) - parallel := ht.OpenChannel( - testCase.bob, testCase.carol, - lntest.OpenChannelParams{Amt: chanAmt}, - ) - testCase.bob.RPC.UpdateChannelPolicy(&lnrpc.PolicyUpdateRequest{ - Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ - ChanPoint: parallel, - }, - BaseFeeMsat: 0, - FeeRatePpm: 0, - TimeLockDelta: 80, - }) - - testCase.runNextNodeIDForward(ctx, func() { - hash := sha256.Sum256(testCase.preimage[:]) - - // Non-strict forwarding picked one of the two Bob -> Carol - // channels at random. Find which one currently carries the - // outgoing HTLC so we can assert it stays there across the - // restart. - chosen, other := testCase.channels[1], parallel - if channelHasHTLC(ht, testCase.bob, parallel, hash[:]) { - chosen, other = parallel, testCase.channels[1] - } - - // Restart the introduction node while the HTLC is held at - // Carol's interceptor. On startup Bob replays its forwarding - // package and must re-decode the node-ID blinded hop without - // disturbing the already forwarded HTLC. - ht.RestartNode(testCase.bob) - ht.EnsureConnected(testCase.alice, testCase.bob) - ht.EnsureConnected(testCase.bob, testCase.carol) - - // After replaying its forwarding package, the in-flight HTLC - // must still be on the originally selected channel and must not - // have been duplicated onto the other Bob -> Carol channel. Bob - // therefore holds exactly two active HTLCs: the incoming one - // from Alice and the single outgoing one to Carol. - ht.AssertOutgoingHTLCActive(testCase.bob, chosen, hash[:]) - ht.AssertHTLCNotActive(testCase.bob, other, hash[:]) - ht.AssertNumActiveHtlcs(testCase.bob, 2) - }) -} - -// channelHasHTLC reports whether the given channel currently has a pending -// HTLC locked in for the provided payment hash. -func channelHasHTLC(ht *lntest.HarnessTest, hn *node.HarnessNode, - cp *lnrpc.ChannelPoint, hash []byte) bool { - - channel := ht.GetChannelByChanPoint(hn, cp) - for _, htlc := range channel.PendingHtlcs { - if bytes.Equal(htlc.HashLock, hash) { - return true - } - } - - return false -} - -// runNextNodeIDForward drives a payment along a blinded path whose non-final -// hops identify the next hop by node ID, asserting that the lnd introduction -// node (Bob) resolves the node ID to one of its channels and forwards the HTLC -// to Carol, who settles it via her interceptor. If midFlight is non-nil it is -// invoked while the HTLC is held at Carol's interceptor, before it is settled, -// letting callers exercise behaviour such as restarting the introduction node. -func (b *blindedForwardTest) runNextNodeIDForward(ctx context.Context, - midFlight func()) { - - ht := b.ht - - // Since buildBlindedPathWithNextNodeID constructs a path with zero - // fees to keep routing math trivial, we must update Bob's outgoing - // channel policy to have zero fees so that forwarding is not rejected - // with FeeInsufficient. - bobUpdateReq := &lnrpc.PolicyUpdateRequest{ - Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ - ChanPoint: b.channels[1], - }, - BaseFeeMsat: 0, - FeeRatePpm: 0, - TimeLockDelta: 80, - } - b.bob.RPC.UpdateChannelPolicy(bobUpdateReq) - - const paymentAmt = 10_000_000 - blindedPath := b.buildBlindedPathWithNextNodeID(paymentAmt) - route := b.createRouteToBlinded(paymentAmt, blindedPath) - - hash := sha256.Sum256(b.preimage[:]) - sendReq := &routerrpc.SendToRouteRequest{ - PaymentHash: hash[:], - Route: route, - } - - // Dispatch the payment in the background since the HTLC will be held by - // Carol's interceptor until we resolve it. - done := make(chan struct{}) - go func() { - defer close(done) - - htlcAttempt, err := b.alice.RPC.Router.SendToRouteV2( - ctx, sendReq, - ) - require.NoError(ht, err) - require.Equal( - ht, lnrpc.HTLCAttempt_SUCCEEDED, htlcAttempt.Status, - ) - }() - - // Bob holding two active HTLCs (one incoming from Alice, one outgoing - // to Carol) demonstrates that Bob (the lnd introduction node) resolved - // Carol's node ID and forwarded the HTLC onwards. We assert on the - // count rather than a specific Bob -> Carol channel because non-strict - // forwarding may pick any of Bob's channels to Carol. - ht.AssertOutgoingHTLCActive(b.alice, b.channels[0], hash[:]) - ht.AssertNumActiveHtlcs(b.bob, 2) - - // Carol intercepts the forwarded HTLC, confirming that the introduction - // node's resolution and forwarding succeeded. Settle it with the - // preimage so that Alice's payment completes successfully. - interceptor := b.carolInterceptor - carolHTLC, err := interceptor.Recv() - require.NoError(ht, err) - - // Carol's own onward hop to Dave is also identified by node ID, so her - // intercept request must expose Dave's pubkey and flag the node-ID - // forward with the sentinel outgoing channel rather than a zero SCID. - require.Equal( - ht, htlcswitch.NodeIDForwardSCID, - carolHTLC.OutgoingRequestedChanId, - ) - require.Equal(ht, b.dave.PubKey[:], carolHTLC.OutgoingRequestedNodeId) - - // Run any caller-supplied step while the HTLC is held mid-flight. - if midFlight != nil { - midFlight() - } - - err = interceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{ - IncomingCircuitKey: carolHTLC.IncomingCircuitKey, - Action: routerrpc.ResolveHoldForwardAction_SETTLE, - Preimage: b.preimage[:], - }) - require.NoError(ht, err) - - select { - case <-done: - case <-time.After(defaultTimeout): - require.Fail(ht, "timeout waiting for payment to complete") - } -} - // testPartiallySpecifiedBlindedPath tests lnd's ability to: // - Assert the error when attempting to create a blinded payment with an // invalid partially specified path. diff --git a/itest/lnd_routing_test.go b/itest/lnd_routing_test.go index 9d3494055..9679f887e 100644 --- a/itest/lnd_routing_test.go +++ b/itest/lnd_routing_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" @@ -22,20 +22,41 @@ import ( var sendToRouteTestCases = []*lntest.TestCase{ { - Name: "single hop", - TestFunc: testSingleHopSendToRoute, + Name: "single hop with sync", + TestFunc: func(ht *lntest.HarnessTest) { + // useStream: false, routerrpc: false. + testSingleHopSendToRouteCase(ht, false, false) + }, + }, + { + Name: "single hop with stream", + TestFunc: func(ht *lntest.HarnessTest) { + // useStream: true, routerrpc: false. + testSingleHopSendToRouteCase(ht, true, false) + }, + }, + { + Name: "single hop with v2", + TestFunc: func(ht *lntest.HarnessTest) { + // useStream: false, routerrpc: true. + testSingleHopSendToRouteCase(ht, false, true) + }, }, } -// testSingleHopSendToRoute tests that payments are properly processed through -// a provided route with a single hop. We'll create the following network -// topology: +// testSingleHopSendToRouteCase tests that payments are properly processed +// through a provided route with a single hop. We'll create the following +// network topology: // // Carol --100k--> Dave // // We'll query the daemon for routes from Carol to Dave and then send payments -// by feeding the route back into SendToRouteV2. -func testSingleHopSendToRoute(ht *lntest.HarnessTest) { +// by feeding the route back into the various SendToRoute RPC methods. Here we +// test all three SendToRoute endpoints, forcing each to perform both a regular +// payment and an MPP payment. +func testSingleHopSendToRouteCase(ht *lntest.HarnessTest, + useStream, useRPC bool) { + const chanAmt = btcutil.Amount(100000) const paymentAmtSat = 1000 const numPayments = 5 @@ -76,6 +97,8 @@ func testSingleHopSendToRoute(ht *lntest.HarnessTest) { ht.WaitForNodeBlockHeight(carol, minerHeight) ht.WaitForNodeBlockHeight(dave, minerHeight) + // Query for routes to pay from Carol to Dave using the default CLTV + // config. routesReq := &lnrpc.QueryRoutesRequest{ PubKey: dave.PubKeyStr, Amt: paymentAmtSat, @@ -85,28 +108,82 @@ func testSingleHopSendToRoute(ht *lntest.HarnessTest) { // There should only be one route to try, so take the first item. r := routes.Routes[0] - for i, rHash := range rHashes { - // Set the MPP record on the last hop with the payment addr from - // the corresponding invoice so the receiver can accept the - // HTLC. + // Construct a closure that will set MPP fields on the route, which + // allows us to test MPP payments. + setMPPFields := func(i int) { hop := r.Hops[len(r.Hops)-1] hop.TlvPayload = true hop.MppRecord = &lnrpc.MPPRecord{ PaymentAddr: payAddrs[i], TotalAmtMsat: paymentAmtSat * 1000, } + } - // Dispatch the payment along the prepared route and assert that - // no failure was returned. - sendReq := &routerrpc.SendToRouteRequest{ - PaymentHash: rHash, - Route: r, + // Construct closures for each of the payment types covered: + // - main rpc server sync + // - main rpc server streaming + // - routerrpc server sync + sendToRouteSync := func() { + for i, rHash := range rHashes { + setMPPFields(i) + + sendReq := &lnrpc.SendToRouteRequest{ + PaymentHash: rHash, + Route: r, + } + resp := carol.RPC.SendToRouteSync(sendReq) + require.Emptyf(ht, resp.PaymentError, + "received payment error from %s: %v", + carol.Name(), resp.PaymentError) } - resp := carol.RPC.SendToRouteV2(sendReq) - require.Nilf( - ht, resp.Failure, "received payment error from %s", - carol.Name(), - ) + } + sendToRouteStream := func() { + alicePayStream := carol.RPC.SendToRoute() + + for i, rHash := range rHashes { + setMPPFields(i) + + sendReq := &lnrpc.SendToRouteRequest{ + PaymentHash: rHash, + Route: routes.Routes[0], + } + err := alicePayStream.Send(sendReq) + require.NoError(ht, err, "unable to send payment") + + resp, err := ht.ReceiveSendToRouteUpdate(alicePayStream) + require.NoError(ht, err, "unable to receive stream") + require.Emptyf(ht, resp.PaymentError, + "received payment error from %s: %v", + carol.Name(), resp.PaymentError) + } + } + sendToRouteRouterRPC := func() { + for i, rHash := range rHashes { + setMPPFields(i) + + sendReq := &routerrpc.SendToRouteRequest{ + PaymentHash: rHash, + Route: r, + } + resp := carol.RPC.SendToRouteV2(sendReq) + require.Nilf(ht, resp.Failure, "received payment "+ + "error from %s", carol.Name()) + } + } + + // Using Carol as the node as the source, send the payments + // synchronously via the routerrpc's SendToRoute, or via the main RPC + // server's SendToRoute streaming or sync calls. + switch { + case !useRPC && useStream: + sendToRouteStream() + case !useRPC && !useStream: + sendToRouteSync() + case useRPC && !useStream: + sendToRouteRouterRPC() + default: + require.Fail(ht, "routerrpc does not support "+ + "streaming send_to_route") } // Verify that the payment's from Carol's PoV have the correct payment @@ -354,15 +431,22 @@ func testSendToRouteErrorPropagation(ht *lntest.HarnessTest) { resp := bob.RPC.AddInvoice(invoice) rHash := resp.RHash - // Using Alice as the source, send to the invoice from Bob via a fake - // route - we expect this to fail with UnknownNextPeer. - sendReq := &routerrpc.SendToRouteRequest{ + // Using Alice as the source, pay to the invoice from Bob. + alicePayStream := alice.RPC.SendToRoute() + + sendReq := &lnrpc.SendToRouteRequest{ PaymentHash: rHash, Route: fakeRoute.Routes[0], } - event := alice.RPC.SendToRouteV2(sendReq) - require.NotNil(ht, event.Failure, "expected payment failure") - require.Equal(ht, lnrpc.Failure_UNKNOWN_NEXT_PEER, event.Failure.Code) + err := alicePayStream.Send(sendReq) + require.NoError(ht, err, "unable to send payment") + + // At this place we should get an rpc error with notification + // that edge is not found on hop(0) + event, err := ht.ReceiveSendToRouteUpdate(alicePayStream) + require.NoError(ht, err, "payment stream has been closed but fake "+ + "route has consumed") + require.Contains(ht, event.PaymentError, "UnknownNextPeer") } // testPrivateChannels tests that a private channel can be used for diff --git a/itest/lnd_rpc_middleware_interceptor_test.go b/itest/lnd_rpc_middleware_interceptor_test.go index ab4c96dd4..5b16da015 100644 --- a/itest/lnd_rpc_middleware_interceptor_test.go +++ b/itest/lnd_rpc_middleware_interceptor_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/node" @@ -78,19 +78,6 @@ func testRPCMiddlewareInterceptor(ht *lntest.HarnessTest) { ) }) - // Test that multiple read-only middlewares can be registered at the - // same time and that both receive intercept messages. - // - // NOTE: we restart the node here to make sure the old interceptor is - // removed from registration. - ht.RestartNode(alice) - ht.EnsureConnected(alice, bob) - ht.Run("multiple read-only middlewares", func(tt *testing.T) { - multipleReadOnlyMiddlewareTest( - tt, alice, readonlyMac, - ) - }) - // We've manually disconnected Bob from Alice in the previous test, make // sure they're connected again. // @@ -205,6 +192,7 @@ func middlewareRegistrationRestrictionTests(t *testing.T, }} for idx, tc := range testCases { + tc := tc t.Run(fmt.Sprintf("%d", idx), func(tt *testing.T) { invalidName := registerMiddleware( @@ -219,62 +207,6 @@ func middlewareRegistrationRestrictionTests(t *testing.T, } } -// multipleReadOnlyMiddlewareTest verifies that multiple read-only middlewares -// can be registered simultaneously and that both receive intercept messages for -// the same RPC call. -func multipleReadOnlyMiddlewareTest(t *testing.T, - node *node.HarnessNode, userMac *macaroon.Macaroon) { - - t.Helper() - - ctxb := t.Context() - ctxc, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - - // Register two read-only middlewares with different names. - reg1 := registerMiddleware( - t, node, &lnrpc.MiddlewareRegistration{ - MiddlewareName: "itest-readonly-one", - ReadOnlyMode: true, - }, true, - ) - defer reg1.cancel() - - reg2 := registerMiddleware( - t, node, &lnrpc.MiddlewareRegistration{ - MiddlewareName: "itest-readonly-two", - ReadOnlyMode: true, - }, true, - ) - defer reg2.cancel() - - // Create a client connection to simulate a user request. - cleanup, client := macaroonClient(t, node, userMac) - defer cleanup() - - // Send a simple RPC request listing all channels to trigger the rpc - // interceptors. We need to invoke the intercept logic in a goroutine - // because we'd block the execution of the main task otherwise. - req := &lnrpc.ListChannelsRequest{ActiveOnly: true} - go reg1.interceptUnary( - "/lnrpc.Lightning/ListChannels", req, nil, true, false, - nil, - ) - go reg2.interceptUnary( - "/lnrpc.Lightning/ListChannels", req, nil, true, false, - nil, - ) - - // Do the actual call now and wait for both interceptors to process. - resp, err := client.ListChannels(ctxc, req) - require.NoError(t, err) - - // Since both middlewares are read-only, they cannot replace the - // response. Verify that both received the same response as the client. - assertInterceptedType(t, resp, <-reg1.responsesChan) - assertInterceptedType(t, resp, <-reg2.responsesChan) -} - // middlewareInterceptionTest tests that unary and streaming requests can be // intercepted. It also makes sure that depending on the mode (read-only or // custom macaroon caveat) a middleware only gets access to the requests it diff --git a/itest/lnd_signer_test.go b/itest/lnd_signer_test.go index 9ef7ddeb7..8c408aa1e 100644 --- a/itest/lnd_signer_test.go +++ b/itest/lnd_signer_test.go @@ -4,12 +4,12 @@ import ( "bytes" "crypto/sha256" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/signrpc" @@ -282,8 +282,8 @@ func assertSignOutputRaw(ht *lntest.HarnessTest, keyDesc *signrpc.KeyDescriptor, sigHash txscript.SigHashType) { - pubKeyHash := address.Hash160(targetPubKey.SerializeCompressed()) - targetAddr, err := address.NewAddressWitnessPubKeyHash( + pubKeyHash := btcutil.Hash160(targetPubKey.SerializeCompressed()) + targetAddr, err := btcutil.NewAddressWitnessPubKeyHash( pubKeyHash, harnessNetParams, ) require.NoError(ht, err) @@ -310,7 +310,7 @@ func assertSignOutputRaw(ht *lntest.HarnessTest, addrReq := &lnrpc.NewAddressRequest{Type: AddrTypeWitnessPubkeyHash} p2wkhResp := alice.RPC.NewAddress(addrReq) - p2wkhAdrr, err := address.DecodeAddress( + p2wkhAdrr, err := btcutil.DecodeAddress( p2wkhResp.Address, harnessNetParams, ) require.NoError(ht, err) diff --git a/itest/lnd_single_hop_invoice_test.go b/itest/lnd_single_hop_invoice_test.go index 7d753715d..cc29438d6 100644 --- a/itest/lnd_single_hop_invoice_test.go +++ b/itest/lnd_single_hop_invoice_test.go @@ -4,7 +4,7 @@ import ( "bytes" "encoding/hex" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lntest" diff --git a/itest/lnd_submit_package_test.go b/itest/lnd_submit_package_test.go deleted file mode 100644 index c995220dc..000000000 --- a/itest/lnd_submit_package_test.go +++ /dev/null @@ -1,209 +0,0 @@ -package itest - -import ( - "bytes" - - btcaddr "github.com/btcsuite/btcd/address/v2" - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/lnrpc/signrpc" - "github.com/lightningnetwork/lnd/lnrpc/walletrpc" - "github.com/lightningnetwork/lnd/lntest" - "github.com/stretchr/testify/require" -) - -// testSubmitPackage tests that the WalletKit.SubmitPackage RPC relays a v3 -// (TRUC) transaction package: a zero-fee parent that would be rejected by a -// standalone broadcast (below the minimum relay fee) is accepted together with -// a fee-paying CPFP child whose combined package feerate clears policy. -// -// This requires a bitcoind chain backend, as btcd has no submitpackage RPC and -// cannot relay zero-fee v3 transactions; run with backend=bitcoind. The -// zero-fee parent can only enter the mempool via package evaluation (a -// standalone submission is rejected for the min relay fee), so a successful -// SubmitPackage proves the CPFP package path worked end to end. -func testSubmitPackage(ht *lntest.HarnessTest) { - // submitpackage is a bitcoind RPC: btcd has no equivalent and neutrino - // has no mempool, so this test only applies to the bitcoind backend. - if ht.ChainBackendName() != "bitcoind" { - ht.Skipf("submitpackage requires the bitcoind backend, got %v", - ht.ChainBackendName()) - } - - // The zero-fee v3 parent only propagates to (and is observable in) the - // mempool of a package-relay-capable node, so the miner must also be - // bitcoind. With the default btcd miner the package is submitted to - // Alice's bitcoind successfully but never relays to the miner, so the - // mempool assertions below would time out. - if ht.Miner().BackendName() != "bitcoind" { - ht.Skipf("submitpackage requires a bitcoind miner for the "+ - "zero-fee v3 package to relay, got %v miner", - ht.Miner().BackendName()) - } - - alice := ht.NewNodeWithCoins("Alice", nil) - - const ( - fundAmt = int64(btcutil.SatoshiPerBitcoin) - - // childFee is paid by the child for the whole package. It - // must cover both transactions' weight at >= the min relay - // fee; a few thousand sats is comfortably above that. - childFee = int64(20_000) - - // p2wkhKeyFamily is a custom key family so the derived keys - // (and thus the addresses we control via SignOutputRaw) are - // independent of the node's normal key usage. - p2wkhKeyFamily = 44 - ) - - // p2wkhKey derives a fresh key and returns it together with the p2wkh - // address/pkScript it controls, which we can later spend via the - // SignOutputRaw RPC. - p2wkhKey := func() (*signrpc.KeyDescriptor, *btcec.PublicKey, - btcaddr.Address, []byte) { - - keyDesc := alice.RPC.DeriveNextKey(&walletrpc.KeyReq{ - KeyFamily: p2wkhKeyFamily, - }) - - pubKey, err := btcec.ParsePubKey(keyDesc.RawKeyBytes) - require.NoError(ht, err) - - addr, err := btcaddr.NewAddressWitnessPubKeyHash( - btcaddr.Hash160(pubKey.SerializeCompressed()), - harnessNetParams, - ) - require.NoError(ht, err) - - pkScript, err := txscript.PayToAddrScript(addr) - require.NoError(ht, err) - - return keyDesc, pubKey, addr, pkScript - } - - // signP2WKHInput signs input idx of tx (spending a p2wkh output - // with the given pkScript and value) via SignOutputRaw and attaches - // the witness. - signP2WKHInput := func(tx *wire.MsgTx, idx int, pkScript []byte, - value int64, keyDesc *signrpc.KeyDescriptor, - pubKey *btcec.PublicKey) { - - var buf bytes.Buffer - require.NoError(ht, tx.Serialize(&buf)) - - signResp := alice.RPC.SignOutputRaw(&signrpc.SignReq{ - RawTxBytes: buf.Bytes(), - SignDescs: []*signrpc.SignDescriptor{{ - Output: &signrpc.TxOut{ - PkScript: pkScript, - Value: value, - }, - InputIndex: int32(idx), - KeyDesc: keyDesc, - Sighash: uint32(txscript.SigHashAll), - WitnessScript: pkScript, - }}, - }) - - tx.TxIn[idx].Witness = wire.TxWitness{ - append(signResp.RawSigs[0], byte(txscript.SigHashAll)), - pubKey.SerializeCompressed(), - } - } - - serialize := func(tx *wire.MsgTx) []byte { - var buf bytes.Buffer - require.NoError(ht, tx.Serialize(&buf)) - - return buf.Bytes() - } - - // Fund a p2wkh output we control: send coins to a key-derived - // address and confirm it, so the parent has a confirmed input to spend. - parentInKey, parentInPub, parentInAddr, parentInScript := p2wkhKey() - alice.RPC.SendCoins(&lnrpc.SendCoinsRequest{ - Addr: parentInAddr.String(), - Amount: fundAmt, - TargetConf: 6, - }) - fundTxid := ht.AssertNumTxsInMempool(1)[0] - fundOutIdx := ht.GetOutputIndex(fundTxid, parentInAddr.String()) - ht.MineBlocksAndAssertNumTxes(1, 1) - - // The child will spend the parent's output, so derive a key we control - // for it and use its script as the parent's output. - childInKey, childInPub, _, childInScript := p2wkhKey() - - // Build the zero-fee v3 parent: spend the confirmed input and pay the - // full value to the child-input script, leaving no fee. - parent := wire.NewMsgTx(3) - parent.AddTxIn(&wire.TxIn{ - PreviousOutPoint: wire.OutPoint{ - Hash: fundTxid, - Index: uint32(fundOutIdx), - }, - }) - parent.AddTxOut(wire.NewTxOut(fundAmt, childInScript)) - signP2WKHInput( - parent, 0, parentInScript, fundAmt, parentInKey, parentInPub, - ) - - // Build the v3 CPFP child: spend the parent's unconfirmed output - // and pay childFee, which covers the whole package. - childOut := alice.RPC.NewAddress(&lnrpc.NewAddressRequest{ - Type: AddrTypeWitnessPubkeyHash, - }) - childOutAddr, err := btcaddr.DecodeAddress( - childOut.Address, harnessNetParams, - ) - require.NoError(ht, err) - childOutScript, err := txscript.PayToAddrScript(childOutAddr) - require.NoError(ht, err) - - child := wire.NewMsgTx(3) - child.AddTxIn(&wire.TxIn{ - PreviousOutPoint: wire.OutPoint{ - Hash: parent.TxHash(), - Index: 0, - }, - }) - child.AddTxOut(wire.NewTxOut(fundAmt-childFee, childOutScript)) - signP2WKHInput(child, 0, childInScript, fundAmt, childInKey, childInPub) - - // Submit the two transactions as a package. A max fee rate of 0 - // disables the fee-rate ceiling so a high-feerate CPFP child is - // never rejected. - noFeeLimit := uint64(0) - resp := alice.RPC.SubmitPackage(&walletrpc.SubmitPackageRequest{ - RawTxs: [][]byte{serialize(parent), serialize(child)}, - SatPerVbyte: &noFeeLimit, - }) - - // The whole package must be accepted, with a per-tx result (keyed by - // wtxid) for each transaction and no per-tx error. - require.Equal(ht, "success", resp.PackageMsg) - require.Len(ht, resp.TxResults, 2) - for _, txResult := range resp.TxResults { - require.Emptyf( - ht, txResult.Error, "tx %s rejected", txResult.Txid, - ) - } - - // The accepted package must now be in the mempool: both the zero-fee - // parent and its fee-paying CPFP child. This proves the package - // actually relayed, not merely that the RPC returned success. - ht.AssertTxInMempool(parent.TxHash()) - ht.AssertTxInMempool(child.TxHash()) - - // Mine the package so it confirms (the strongest end-to-end proof the - // CPFP package relayed) and the mempool is clean for the harness's - // end-of-test teardown check. Both the parent and child must land in - // the mined block. - block := ht.MineBlocksAndAssertNumTxes(1, 2) - ht.AssertTxInBlock(block[0], parent.TxHash()) - ht.AssertTxInBlock(block[0], child.TxHash()) -} diff --git a/itest/lnd_sweep_test.go b/itest/lnd_sweep_test.go index 07bae5f01..c5bcd3b15 100644 --- a/itest/lnd_sweep_test.go +++ b/itest/lnd_sweep_test.go @@ -4,9 +4,9 @@ import ( "fmt" "time" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lncfg" @@ -689,20 +689,8 @@ func testSweepCPFPAnchorIncomingTimeout(ht *lntest.HarnessTest) { // contractcourt will offer the HTLC to his sweeper. We are not testing // the HTLC sweeping behaviors so we just perform a simple check and // exit the test. - htlcSweep := ht.AssertNumPendingSweeps(bob, 1)[0] - htlcSweepOutpointHash, err := chainhash.NewHashFromStr( - htlcSweep.Outpoint.TxidStr, - ) - require.NoError(ht, err) - htlcSweepOutpoint := wire.OutPoint{ - Hash: *htlcSweepOutpointHash, - Index: htlcSweep.Outpoint.OutputIndex, - } - - // The final sweep may be RBFed between the mempool check and block - // generation, so assert that the mined tx spends the pending sweep's - // outpoint instead of asserting the txid observed before mining. - ht.MineBlockAndAssertOutpointSpent(1, htlcSweepOutpoint) + ht.AssertNumPendingSweeps(bob, 1) + ht.MineBlocksAndAssertNumTxes(1, 1) // Finally, clean the mempool for the next test. ht.CleanShutDown() @@ -891,7 +879,7 @@ func testSweepHTLCs(ht *lntest.HarnessTest) { // Before we mine empty blocks to check the RBF behavior, we need to be // aware that Bob's incoming HTLC will expire before his outgoing HTLC // deadline is reached. This happens because the incoming HTLC is sent - // onchain at CLTVDelta-BroadcastDelta=24-16=8, which means after 8 + // onchain at CLTVDelta-BroadcastDelta=18-10=8, which means after 8 // blocks are mined, we expect Bob force closes the channel Alice->Bob. blocksTillIncomingSweep := cltvDelta - lncfg.DefaultIncomingBroadcastDelta diff --git a/itest/lnd_switch_test.go b/itest/lnd_switch_test.go index 42e1511e2..82900c9d3 100644 --- a/itest/lnd_switch_test.go +++ b/itest/lnd_switch_test.go @@ -1,7 +1,7 @@ package itest import ( - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/node" diff --git a/itest/lnd_taproot_test.go b/itest/lnd_taproot_test.go index d26bbe7ec..842102965 100644 --- a/itest/lnd_taproot_test.go +++ b/itest/lnd_taproot_test.go @@ -6,16 +6,14 @@ import ( "encoding/hex" "testing" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/psbt/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnrpc" @@ -81,7 +79,6 @@ func testTaprootMuSig2(ht *lntest.HarnessTest) { testTaprootMuSig2ScriptSpend(ht, alice, version) testTaprootMuSig2CombinedLeafKeySpend(ht, alice, version) testMuSig2CombineKey(ht, alice, version) - testTaprootMuSig2CombinedNonceCoordinator(ht, alice, version) } } @@ -1153,7 +1150,7 @@ func testTaprootImportTapscriptFullTree(ht *lntest.HarnessTest, } importResp := alice.RPC.ImportTapscript(req) - calculatedAddr, err := address.NewAddressTaproot( + calculatedAddr, err := btcutil.NewAddressTaproot( schnorr.SerializePubKey(taprootKey), harnessNetParams, ) require.NoError(ht, err) @@ -1221,7 +1218,7 @@ func testTaprootImportTapscriptPartialReveal(ht *lntest.HarnessTest, } importResp := alice.RPC.ImportTapscript(req) - calculatedAddr, err := address.NewAddressTaproot( + calculatedAddr, err := btcutil.NewAddressTaproot( schnorr.SerializePubKey(taprootKey), harnessNetParams, ) require.NoError(ht, err) @@ -1278,7 +1275,7 @@ func testTaprootImportTapscriptRootHashOnly(ht *lntest.HarnessTest, } importResp := alice.RPC.ImportTapscript(req) - calculatedAddr, err := address.NewAddressTaproot( + calculatedAddr, err := btcutil.NewAddressTaproot( schnorr.SerializePubKey(taprootKey), harnessNetParams, ) require.NoError(ht, err) @@ -1335,7 +1332,7 @@ func testTaprootImportTapscriptFullKey(ht *lntest.HarnessTest, } importResp := alice.RPC.ImportTapscript(req) - calculatedAddr, err := address.NewAddressTaproot( + calculatedAddr, err := btcutil.NewAddressTaproot( schnorr.SerializePubKey(taprootKey), harnessNetParams, ) require.NoError(ht, err) @@ -1393,7 +1390,7 @@ func testTaprootImportTapscriptFullKeyFundPsbt(ht *lntest.HarnessTest, } importResp := alice.RPC.ImportTapscript(req) - calculatedAddr, err := address.NewAddressTaproot( + calculatedAddr, err := btcutil.NewAddressTaproot( schnorr.SerializePubKey(taprootKey), harnessNetParams, ) require.NoError(ht, err) @@ -1568,7 +1565,7 @@ func testScriptHashLock(t *testing.T, preimage []byte) txscript.TapLeaf { builder := txscript.NewScriptBuilder() builder.AddOp(txscript.OP_DUP) builder.AddOp(txscript.OP_HASH160) - builder.AddData(address.Hash160(preimage)) + builder.AddData(btcutil.Hash160(preimage)) builder.AddOp(txscript.OP_EQUALVERIFY) script1, err := builder.Script() require.NoError(t, err) @@ -1590,12 +1587,12 @@ func testScriptSchnorrSig(t *testing.T, // newAddrWithScript returns a new address and its pkScript. func newAddrWithScript(ht *lntest.HarnessTest, node *node.HarnessNode, - addrType lnrpc.AddressType) (address.Address, []byte) { + addrType lnrpc.AddressType) (btcutil.Address, []byte) { p2wkhResp := node.RPC.NewAddress(&lnrpc.NewAddressRequest{ Type: addrType, }) - p2wkhAddr, err := address.DecodeAddress( + p2wkhAddr, err := btcutil.DecodeAddress( p2wkhResp.Address, harnessNetParams, ) require.NoError(ht, err) @@ -1611,7 +1608,7 @@ func newAddrWithScript(ht *lntest.HarnessTest, node *node.HarnessNode, func sendToTaprootOutput(ht *lntest.HarnessTest, hn *node.HarnessNode, taprootKey *btcec.PublicKey) (wire.OutPoint, []byte) { - tapScriptAddr, err := address.NewAddressTaproot( + tapScriptAddr, err := btcutil.NewAddressTaproot( schnorr.SerializePubKey(taprootKey), harnessNetParams, ) require.NoError(ht, err) @@ -1750,7 +1747,7 @@ func confirmAddress(ht *lntest.HarnessTest, hn *node.HarnessNode, // Before we confirm the transaction, let's register a confirmation // listener for it, which we expect to fire after mining a block. - parsedAddr, err := address.DecodeAddress(addrString, harnessNetParams) + parsedAddr, err := btcutil.DecodeAddress(addrString, harnessNetParams) require.NoError(ht, err) addrPkScript, err := txscript.PayToAddrScript(parsedAddr) require.NoError(ht, err) @@ -2116,189 +2113,3 @@ func testMuSig2CombineKey(ht *lntest.HarnessTest, alice *node.HarnessNode, ) } } - -// testTaprootMuSig2CombinedNonceCoordinator tests the coordinator pattern where -// a single party aggregates all nonces and distributes the combined nonce to -// participants using MuSig2RegisterCombinedNonce. -func testTaprootMuSig2CombinedNonceCoordinator(ht *lntest.HarnessTest, - alice *node.HarnessNode, version signrpc.MuSig2Version) { - - // We're using a simple BIP-86 key spend only setup. - taprootTweak := &signrpc.TaprootTweakDesc{ - KeySpendOnly: true, - } - - // Derive signing keys for our three participants. - keyDesc1, keyDesc2, keyDesc3, allPubKeys := deriveSigningKeys( - ht, alice, version, - ) - - // Create three sessions WITHOUT exchanging nonces initially. This - // simulates the coordinator pattern where the coordinator collects - // nonces first, then aggregates them externally. - sessResp1 := alice.RPC.MuSig2CreateSession( - &signrpc.MuSig2SessionRequest{ - KeyLoc: keyDesc1.KeyLoc, - AllSignerPubkeys: allPubKeys, - TaprootTweak: taprootTweak, - Version: version, - }, - ) - require.Equal(ht, version, sessResp1.Version) - require.False(ht, sessResp1.HaveAllNonces) - - sessResp2 := alice.RPC.MuSig2CreateSession( - &signrpc.MuSig2SessionRequest{ - KeyLoc: keyDesc2.KeyLoc, - AllSignerPubkeys: allPubKeys, - TaprootTweak: taprootTweak, - Version: version, - }, - ) - require.False(ht, sessResp2.HaveAllNonces) - - sessResp3 := alice.RPC.MuSig2CreateSession( - &signrpc.MuSig2SessionRequest{ - KeyLoc: keyDesc3.KeyLoc, - AllSignerPubkeys: allPubKeys, - TaprootTweak: taprootTweak, - Version: version, - }, - ) - require.False(ht, sessResp3.HaveAllNonces) - - // The coordinator collects all individual nonces. - allNonces := [][]byte{ - sessResp1.LocalPublicNonces, - sessResp2.LocalPublicNonces, - sessResp3.LocalPublicNonces, - } - - // For v0.4.0, both RegisterCombinedNonce and GetCombinedNonce should - // return unsupported errors. - if version == signrpc.MuSig2Version_MUSIG2_VERSION_V040 { - // Try to register a combined nonce - should fail with - // unsupported error. - var dummyNonce [66]byte - err := alice.RPC.MuSig2RegisterCombinedNonceErr( - &signrpc.MuSig2RegisterCombinedNonceRequest{ - SessionId: sessResp1.SessionId, - CombinedPublicNonce: dummyNonce[:], - }, - ) - require.ErrorContains(ht, err, "not supported") - - // Try to get combined nonce - should also fail. - err = alice.RPC.MuSig2GetCombinedNonceErr( - &signrpc.MuSig2GetCombinedNonceRequest{ - SessionId: sessResp1.SessionId, - }, - ) - require.ErrorContains(ht, err, "not supported") - - // For v0.4.0, we can't proceed with the coordinator pattern, - // so we're done with this version. - return - } - - // Copy the nonces over to slice of fixed byte arrays and then use the - // musig2 library to aggregate them. - var nonces [][musig2.PubNonceSize]byte - for _, nonce := range allNonces { - var n [musig2.PubNonceSize]byte - copy(n[:], nonce) - nonces = append(nonces, n) - } - - combinedNonce, err := musig2.AggregateNonces(nonces) - require.NoError(ht, err) - - // The coordinator now distributes the combined nonce to all - // participants. - alice.RPC.MuSig2RegisterCombinedNonce( - &signrpc.MuSig2RegisterCombinedNonceRequest{ - SessionId: sessResp1.SessionId, - CombinedPublicNonce: combinedNonce[:], - }, - ) - - alice.RPC.MuSig2RegisterCombinedNonce( - &signrpc.MuSig2RegisterCombinedNonceRequest{ - SessionId: sessResp2.SessionId, - CombinedPublicNonce: combinedNonce[:], - }, - ) - - alice.RPC.MuSig2RegisterCombinedNonce( - &signrpc.MuSig2RegisterCombinedNonceRequest{ - SessionId: sessResp3.SessionId, - CombinedPublicNonce: combinedNonce[:], - }, - ) - - // Verify we can retrieve the combined nonce. - getNonceResp := alice.RPC.MuSig2GetCombinedNonce( - &signrpc.MuSig2GetCombinedNonceRequest{ - SessionId: sessResp1.SessionId, - }, - ) - require.Equal(ht, combinedNonce[:], getNonceResp.CombinedPublicNonce) - - // Test mutual exclusivity: trying to register individual nonces after - // combined nonce should fail. - err = alice.RPC.MuSig2RegisterNoncesErr( - &signrpc.MuSig2RegisterNoncesRequest{ - SessionId: sessResp1.SessionId, - OtherSignerPublicNonces: [][]byte{ - sessResp2.LocalPublicNonces, - }, - }, - ) - require.ErrorContains(ht, err, "already have all nonces") - - // Now complete a full signing flow to verify everything works. - combinedKey, err := schnorr.ParsePubKey(sessResp1.CombinedKey) - require.NoError(ht, err) - - // Create a simple message to sign. - var msg [32]byte - copy(msg[:], []byte("test message for combined nonce")) - - // All three participants sign the message. - signReq := &signrpc.MuSig2SignRequest{ - SessionId: sessResp1.SessionId, - MessageDigest: msg[:], - } - alice.RPC.MuSig2Sign(signReq) - - signReq = &signrpc.MuSig2SignRequest{ - SessionId: sessResp2.SessionId, - MessageDigest: msg[:], - Cleanup: true, - } - signResp2 := alice.RPC.MuSig2Sign(signReq) - - signReq = &signrpc.MuSig2SignRequest{ - SessionId: sessResp3.SessionId, - MessageDigest: msg[:], - Cleanup: true, - } - signResp3 := alice.RPC.MuSig2Sign(signReq) - - // Combine the signatures. - combineReq := &signrpc.MuSig2CombineSigRequest{ - SessionId: sessResp1.SessionId, - OtherPartialSignatures: [][]byte{ - signResp2.LocalPartialSignature, - signResp3.LocalPartialSignature, - }, - } - combineResp := alice.RPC.MuSig2CombineSig(combineReq) - require.True(ht, combineResp.HaveAllSignatures) - require.NotEmpty(ht, combineResp.FinalSignature) - - // Verify the final signature is valid. - sig, err := schnorr.ParseSignature(combineResp.FinalSignature) - require.NoError(ht, err) - require.True(ht, sig.Verify(msg[:], combinedKey)) -} diff --git a/itest/lnd_test.go b/itest/lnd_test.go index 0580bb12b..32c0f1c02 100644 --- a/itest/lnd_test.go +++ b/itest/lnd_test.go @@ -11,11 +11,10 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/integration/rpctest" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntest" - "github.com/lightningnetwork/lnd/lntest/miner" "github.com/lightningnetwork/lnd/lntest/node" "github.com/lightningnetwork/lnd/lntest/port" "github.com/lightningnetwork/lnd/lntest/wait" @@ -87,12 +86,6 @@ var ( lndExecutable = flag.String( "lndexec", itestLndBinary, "full path to lnd binary", ) - - // minerBackendFlag selects which miner backend to use. If not set, the - // default miner is btcd. - minerBackendFlag = flag.String( - "minerbackend", "", "miner backend (btcd, bitcoind)", - ) ) // TestLightningNetworkDaemon performs a series of integration tests amongst a @@ -111,15 +104,8 @@ func TestLightningNetworkDaemon(t *testing.T) { // Get the binary path and setup the harness test. binary := getLndBinary(t) - var minerCfg *miner.MinerConfig - if minerBackendFlag != nil && *minerBackendFlag != "" { - minerCfg = &miner.MinerConfig{ - Backend: *minerBackendFlag, - } - } - - harnessTest := lntest.SetupHarnessWithMinerConfig( - t, binary, *dbBackendFlag, *nativeSQLFlag, feeService, minerCfg, + harnessTest := lntest.SetupHarness( + t, binary, *dbBackendFlag, *nativeSQLFlag, feeService, ) defer harnessTest.Stop() @@ -128,6 +114,7 @@ func TestLightningNetworkDaemon(t *testing.T) { // Run the subset of the test cases selected in this tranche. for idx, testCase := range testCases { + testCase := testCase name := fmt.Sprintf("tranche%02d/%02d-of-%d/%s/%s", trancheIndex, trancheOffset+uint(idx)+1, len(allTestCases), harnessTest.ChainBackendName(), diff --git a/itest/lnd_trackpayments_test.go b/itest/lnd_trackpayments_test.go index d92a55d8a..dc8655a26 100644 --- a/itest/lnd_trackpayments_test.go +++ b/itest/lnd_trackpayments_test.go @@ -4,7 +4,7 @@ import ( "encoding/hex" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" diff --git a/itest/lnd_wallet.go b/itest/lnd_wallet.go index 4b40abbc6..1c07b087b 100644 --- a/itest/lnd_wallet.go +++ b/itest/lnd_wallet.go @@ -1,8 +1,8 @@ package itest import ( - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/node" "github.com/lightningnetwork/lnd/lnwallet" diff --git a/itest/lnd_wallet_import_test.go b/itest/lnd_wallet_import_test.go index 78602ff8e..4a08fb2e6 100644 --- a/itest/lnd_wallet_import_test.go +++ b/itest/lnd_wallet_import_test.go @@ -6,13 +6,12 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/btcutil/v2/hdkeychain" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/funding" @@ -124,20 +123,20 @@ func newExternalAddr(ht *lntest.HarnessTest, funder, signer *node.HarnessNode, func assertExternalAddrType(t *testing.T, addrStr string, accountAddrType walletrpc.AddressType) { - addr, err := address.DecodeAddress(addrStr, harnessNetParams) + addr, err := btcutil.DecodeAddress(addrStr, harnessNetParams) require.NoError(t, err) switch accountAddrType { case walletrpc.AddressType_WITNESS_PUBKEY_HASH: - require.IsType(t, addr, &address.AddressWitnessPubKeyHash{}) + require.IsType(t, addr, &btcutil.AddressWitnessPubKeyHash{}) case walletrpc.AddressType_NESTED_WITNESS_PUBKEY_HASH, walletrpc.AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH: - require.IsType(t, addr, &address.AddressScriptHash{}) + require.IsType(t, addr, &btcutil.AddressScriptHash{}) case walletrpc.AddressType_TAPROOT_PUBKEY: - require.IsType(t, addr, &address.AddressTaproot{}) + require.IsType(t, addr, &btcutil.AddressTaproot{}) default: t.Fatalf("unsupported account addr type %v", accountAddrType) @@ -647,6 +646,7 @@ func testWalletImportPubKey(ht *lntest.HarnessTest) { } for _, tc := range testCases { + tc := tc success := ht.Run(tc.name, func(tt *testing.T) { testFunc := func(ht *lntest.HarnessTest) { testWalletImportPubKeyScenario( diff --git a/itest/lnd_wallet_sync_test.go b/itest/lnd_wallet_sync_test.go deleted file mode 100644 index 7b6de9a67..000000000 --- a/itest/lnd_wallet_sync_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package itest - -import ( - "time" - - "github.com/lightningnetwork/lnd/lntest" - "github.com/stretchr/testify/require" -) - -// walletSyncTestCases defines a set of tests for the wallet_synced field -// in GetInfoResponse. -var walletSyncTestCases = []*lntest.TestCase{ - { - Name: "wallet synced", - TestFunc: runTestWalletSynced, - }, -} - -// runTestWalletSynced tests that the wallet_synced field in GetInfoResponse -// correctly reflects the wallet's sync state. It verifies that wallet_synced -// is false while the wallet is catching up to new blocks, and becomes true -// once fully synced. -func runTestWalletSynced(ht *lntest.HarnessTest) { - // Create a test node. - alice := ht.NewNodeWithCoins("Alice", nil) - - // Verify wallet starts synced. - resp := alice.RPC.GetInfo() - require.True(ht, resp.WalletSynced) - ht.Logf("Alice wallet_synced=%v", resp.WalletSynced) - - // Stop Alice to create a clear sync gap while we mine blocks. - require.NoError(ht, alice.Stop(), "failed to stop Alice") - - // Mine blocks while Alice is offline. - const numBlocks = 40 - ht.Miner().MineBlocks(numBlocks) - _, minerHeight := ht.Miner().GetBestBlock() - - // Restart Alice without waiting for full chain sync. - require.NoError( - ht, alice.Start(ht.Context()), "failed to restart Alice", - ) - - // While Alice is behind the miner height, wallet_synced must be false. - deadline := time.Now().Add(lntest.DefaultTimeout) - for { - resp := alice.RPC.GetInfo() - if int32(resp.BlockHeight) >= minerHeight { - break - } - - require.Falsef(ht, resp.WalletSynced, - "wallet_synced=true while behind "+ - "(nodeHeight=%v, minerHeight=%v)", - resp.BlockHeight, minerHeight) - - if time.Now().After(deadline) { - require.Fail(ht, "timed out waiting for "+ - "node to catch up") - } - - time.Sleep(50 * time.Millisecond) - } - - // Final verification that wallet_synced is true. - require.Eventually(ht, func() bool { - return alice.RPC.GetInfo().WalletSynced - }, lntest.DefaultTimeout, 200*time.Millisecond, - "wallet should be synced after waiting") -} diff --git a/itest/lnd_watchtower_test.go b/itest/lnd_watchtower_test.go index 303ded46b..77781ced0 100644 --- a/itest/lnd_watchtower_test.go +++ b/itest/lnd_watchtower_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" @@ -97,10 +97,9 @@ func testTowerClientTowerAndSessionManagement(ht *lntest.HarnessTest) { } // Assert that there are a few sessions between Dave and Wallis. There - // should be one per client. There are currently 4 types of clients - // (legacy, anchor, taproot-staging, taproot-final), so we expect 4 - // sessions. - assertNumSessions(wallisPk, 4, false) + // should be one per client. There are currently 3 types of clients, so + // we expect 3 sessions. + assertNumSessions(wallisPk, 3, false) // Before we make a channel, we'll load up Dave with some coins sent // directly from the miner. @@ -154,7 +153,7 @@ func testTowerClientTowerAndSessionManagement(ht *lntest.HarnessTest) { Pubkey: wilmaPk, Address: wilmaListener, }) - assertNumSessions(wilmaPk, 4, false) + assertNumSessions(wilmaPk, 3, false) // The updates from before should now appear on the new watchtower. assertNumBackups(ht, dave.RPC, wilmaPk, 4, false) @@ -172,7 +171,7 @@ func testTowerClientTowerAndSessionManagement(ht *lntest.HarnessTest) { // number of sessions with Wallis has not changed - in other words, the // previously used session was re-used. assertNumBackups(ht, dave.RPC, wallisPk, 8, false) - assertNumSessions(wallisPk, 4, false) + assertNumSessions(wallisPk, 3, false) findSession := func(towerPk []byte, numBackups uint32) []byte { info := dave.RPC.GetTowerInfo(&wtclientrpc.GetTowerInfoRequest{ @@ -205,7 +204,7 @@ func testTowerClientTowerAndSessionManagement(ht *lntest.HarnessTest) { // This should force the client to negotiate a new session. The old // session still remains in our session list since the channel for which // it has updates for is still open. - assertNumSessions(wallisPk, 5, false) + assertNumSessions(wallisPk, 4, false) // Any new back-ups should now be backed up on a different session. generateBackups(ht, dave, alice, 2) @@ -226,7 +225,7 @@ func testTowerClientTowerAndSessionManagement(ht *lntest.HarnessTest) { // be checked on each new block. It could have been the case that all // checks with the above mined blocks were completed before the // closable session was queued. - assertNumSessions(wallisPk, 4, true) + assertNumSessions(wallisPk, 3, true) // For the sake of completion, we call RemoveTower here for both towers // to show that this should never error. @@ -339,7 +338,6 @@ func testRevokedCloseRetributionAltruistWatchtower(ht *lntest.HarnessTest) { lnrpc.CommitmentType_LEGACY, lnrpc.CommitmentType_ANCHORS, lnrpc.CommitmentType_SIMPLE_TAPROOT, - lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL, } { testName := fmt.Sprintf("%v", commitType.String()) ct := commitType @@ -450,10 +448,8 @@ func testRevokedCloseRetributionAltruistWatchtowerCase(ht *lntest.HarnessTest, // backup. ht.EnsureConnected(dave, carol) - // Once connected, wait for both channel links to be active again. + // Once connected, give Dave some time to enable the channel again. ht.AssertChannelInGraph(dave, chanPoint) - ht.AssertChannelActive(dave, chanPoint) - ht.AssertChannelActive(carol, chanPoint) // Finally, send payments from Dave to Carol, consuming Carol's // remaining payment hashes. diff --git a/itest/lnd_wipe_fwdpkgs_test.go b/itest/lnd_wipe_fwdpkgs_test.go index d0c669573..337331fd5 100644 --- a/itest/lnd_wipe_fwdpkgs_test.go +++ b/itest/lnd_wipe_fwdpkgs_test.go @@ -85,28 +85,13 @@ func testWipeForwardingPackages(ht *lntest.HarnessTest) { // close channel should now become pending force closed channel. pendingAB = ht.AssertChannelPendingForceClose(bob, chanPointAB).Channel - // On backends that close channels via tombstone markers (sqlite, - // postgres), the per-channel forwarding-package bucket is left on - // disk by design — the synchronous close path's nested-bucket - // delete is exactly what tombstoning avoids. The bytes are reclaimed - // by the upcoming native-SQL channel-state migration. The unit-test - // suite in channeldb covers the tombstone semantics directly, so - // here we just skip the post-close fwd-pkg assertions on those - // backends while still exercising the rest of the close flow for - // backend symmetry. - if !ht.UsesClosedChanTombstones() { - require.Zero(ht, pendingAB.NumForwardingPackages) + // Check the forwarding pacakges are deleted. + require.Zero(ht, pendingAB.NumForwardingPackages) - // For Alice, the forwarding packages should have been wiped - // too. - pending := ht.AssertChannelPendingForceClose(alice, chanPointAB) - pendingAB = pending.Channel - require.Zero(ht, pendingAB.NumForwardingPackages) - } else { - // Still drive Alice's pending-force-close lookup so the rest - // of the test stays backend-symmetric. - ht.AssertChannelPendingForceClose(alice, chanPointAB) - } + // For Alice, the forwarding packages should have been wiped too. + pending := ht.AssertChannelPendingForceClose(alice, chanPointAB) + pendingAB = pending.Channel + require.Zero(ht, pendingAB.NumForwardingPackages) // Alice should one pending sweep. ht.AssertNumPendingSweeps(alice, 1) diff --git a/itest/lnd_wumbo_channels_test.go b/itest/lnd_wumbo_channels_test.go index 74c15691b..8bf18106e 100644 --- a/itest/lnd_wumbo_channels_test.go +++ b/itest/lnd_wumbo_channels_test.go @@ -1,7 +1,7 @@ package itest import ( - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lnwallet" diff --git a/itest/lnd_zero_conf_close_event_test.go b/itest/lnd_zero_conf_close_event_test.go deleted file mode 100644 index bfd6dc70e..000000000 --- a/itest/lnd_zero_conf_close_event_test.go +++ /dev/null @@ -1,310 +0,0 @@ -package itest - -import ( - "time" - - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/lntest" - "github.com/lightningnetwork/lnd/lntest/rpc" - "github.com/lightningnetwork/lnd/lntest/wait" - "github.com/stretchr/testify/require" -) - -// testZeroConfCoopCloseSubscribeEvents exercises the regression that was -// reported when production builds switched to a multi-confirmation -// reorg-aware close dispatch: SubscribeChannelEvents stopped emitting -// CLOSED_CHANNEL on cooperative closes for zero-conf channels until the -// close had reached the full confirmation depth, instead of firing at first -// detection like v0.20.1. -// -// The fix wires an early-dispatch callback into the chain watcher that fires -// a preliminary CLOSED_CHANNEL event over the channel notifier as soon as a -// coop close spend lands on chain. This test asserts: -// -// 1. CLOSED_CHANNEL fires on the SubscribeChannelEvents stream after only -// the first confirmation of the close tx (not after the full N=3 -// depth required by --dev.force-channel-close-confs=3). -// 2. FULLY_RESOLVED_CHANNEL fires once the close has reached N confs and -// the channel arbitrator has finished its resolution flow. -// 3. Exactly one CLOSED_CHANNEL is delivered — the suppression logic in -// the channel arbitrator drops the duplicate that would otherwise fire -// from MarkChannelClosed at N confs. -func testZeroConfCoopCloseSubscribeEvents(ht *lntest.HarnessTest) { - // Force coop close to require 3 confs so we exercise the async path - // in the chain watcher (the same path production hits via - // CloseConfsForCapacity). - const requiredConfs = 3 - - // Zero-conf channels need option-scid-alias and anchors; force-confs - // is what flips us out of the numConfs==1 fast-path so we can verify - // the early dispatch is what surfaces the CLOSED_CHANNEL event. - nodeArgs := []string{ - "--protocol.option-scid-alias", - "--protocol.zero-conf", - "--protocol.anchors", - "--dev.force-channel-close-confs=3", - } - - alice := ht.NewNode("Alice", nodeArgs) - bob := ht.NewNode("Bob", nodeArgs) - - ht.FundCoins(btcutil.SatoshiPerBitcoin, alice) - ht.EnsureConnected(alice, bob) - - // A channel acceptor on Bob is needed to allow the zero-conf - // negotiation to succeed. - acceptStream, cancelAcceptor := bob.RPC.ChannelAcceptor() - go acceptChannel(ht.T, true, acceptStream) - - const chanAmt = btcutil.Amount(1_000_000) - openParams := lntest.OpenChannelParams{ - Amt: chanAmt, - Private: true, - CommitmentType: lnrpc.CommitmentType_ANCHORS, - ZeroConf: true, - } - stream := ht.OpenChannelAssertStream(alice, bob, openParams) - cancelAcceptor() - - // Wait for the channel-open update — for zero-conf this arrives - // without any blocks needing to be mined. - chanPoint := ht.WaitForChannelOpenEvent(stream) - ht.AssertChannelInGraph(alice, chanPoint) - ht.AssertChannelInGraph(bob, chanPoint) - - // Subscribe Alice to channel events BEFORE we initiate the close so - // we capture the full close lifecycle on the wire. - chanSub := alice.RPC.SubscribeChannelEvents() - - // Alice initiates the cooperative close; NoWait so the closing tx - // just lands in the mempool. - closeStream, _ := ht.CloseChannelAssertPending(alice, chanPoint, false) - - // Mine a single block so the close tx confirms once. For a zero-conf - // channel the funding tx is still unconfirmed when we initiate the - // close, so the mempool holds both the funding tx and the close tx - // when we mine the first block. With the fix in place, the chain - // watcher's processDetectedSpend should insta-dispatch - // CLOSED_CHANNEL over the notifier as soon as the close spend lands, - // even though the async path is still waiting on two more confs - // before driving MarkChannelClosed. - ht.MineBlocksAndAssertNumTxes(1, 2) - - closedSeen := waitForChannelEventOfType( - ht, chanSub, - lnrpc.ChannelEventUpdate_CLOSED_CHANNEL, - ) - require.NotNil( - ht, closedSeen, - "CLOSED_CHANNEL must fire after the first conf of the "+ - "close tx (regression: production was waiting for "+ - "the full 3-conf depth)", - ) - - // The CLOSED_CHANNEL summary must reflect a cooperative close - // initiated by the local node. - closedSummary := closedSeen.GetClosedChannel() - require.NotNil(ht, closedSummary, - "CLOSED_CHANNEL update must carry a close summary") - require.Equal(ht, - lnrpc.ChannelCloseSummary_COOPERATIVE_CLOSE, - closedSummary.CloseType, - ) - require.Equal(ht, - lnrpc.Initiator_INITIATOR_LOCAL, - closedSummary.CloseInitiator, - ) - - // Mine the remaining confs needed to take the close to its final - // resolution state. - ht.MineBlocksAndAssertNumTxes(requiredConfs-1, 0) - - // Drain the close-channel client stream so the test cleanup path - // doesn't hang on it. - go func() { - for { - if _, err := closeStream.Recv(); err != nil { - return - } - } - }() - - // FULLY_RESOLVED_CHANNEL must arrive after the close advances - // through the channel arbitrator at full N confs. Crucially, no - // second CLOSED_CHANNEL event must arrive between the early one and - // the FULLY_RESOLVED_CHANNEL — the channel-arbitrator-side - // suppression drops the duplicate that MarkChannelClosed would - // otherwise emit. We use a stream walker here (rather than - // waitForChannelEventOfType, which silently discards every event - // that isn't the one being waited for) so that a regression which - // re-fires CLOSED_CHANNEL while we wait for FULLY_RESOLVED_CHANNEL - // is surfaced as a test failure instead of being swallowed. - resolvedSeen := waitForChannelEventForbidClosed(ht, chanSub) - require.NotNil(ht, resolvedSeen, - "FULLY_RESOLVED_CHANNEL must fire after the close reaches "+ - "the required confirmation depth") - - // Belt-and-suspenders: also drain a small quiet window after - // FULLY_RESOLVED to catch any late-arriving duplicate. - assertNoMoreClosedEvents(ht, chanSub, 500*time.Millisecond) -} - -// waitForChannelEventOfType drains the channel events subscription until -// one of the supplied type lands or the harness's default timeout elapses. -// Other event types (PENDING_OPEN, OPEN, ACTIVE, INACTIVE, CHANNEL_UPDATE) -// are expected during a normal close flow and must be tolerated rather than -// fail the test. -func waitForChannelEventOfType(ht *lntest.HarnessTest, - sub rpc.ChannelEventsClient, - want lnrpc.ChannelEventUpdate_UpdateType) *lnrpc.ChannelEventUpdate { - - type result struct { - event *lnrpc.ChannelEventUpdate - err error - } - - results := make(chan result, 1) - deadline := time.After(wait.DefaultTimeout) - - go func() { - for { - ev, err := sub.Recv() - if err != nil { - results <- result{err: err} - return - } - if ev.Type == want { - results <- result{event: ev} - return - } - } - }() - - select { - case r := <-results: - require.NoErrorf(ht, r.err, - "error from channel event stream while waiting "+ - "for %v", want) - - return r.event - - case <-deadline: - ht.Fatalf("timed out waiting for channel event %v", want) - return nil - } -} - -// waitForChannelEventForbidClosed drains the channel events subscription -// until FULLY_RESOLVED_CHANNEL lands, failing the test immediately if a -// second CLOSED_CHANNEL is observed along the way. This is the strict -// variant of waitForChannelEventOfType for the gap between the early -// CLOSED_CHANNEL (fired by the chain watcher at first conf) and the -// FULLY_RESOLVED_CHANNEL (fired by the channel arbitrator at N confs): -// any CLOSED_CHANNEL in that window would be the duplicate that the -// MarkChannelClosed suppression in the arbitrator is meant to drop. -func waitForChannelEventForbidClosed(ht *lntest.HarnessTest, - sub rpc.ChannelEventsClient) *lnrpc.ChannelEventUpdate { - - type result struct { - event *lnrpc.ChannelEventUpdate - err error - } - - results := make(chan result, 1) - deadline := time.After(wait.DefaultTimeout) - - go func() { - for { - ev, err := sub.Recv() - if err != nil { - results <- result{err: err} - return - } - - switch ev.Type { - case lnrpc.ChannelEventUpdate_FULLY_RESOLVED_CHANNEL: - results <- result{event: ev} - return - - case lnrpc.ChannelEventUpdate_CLOSED_CHANNEL: - results <- result{event: ev} - return - } - } - }() - - select { - case r := <-results: - require.NoErrorf(ht, r.err, - "error from channel event stream while waiting "+ - "for FULLY_RESOLVED_CHANNEL") - - require.Equal(ht, - lnrpc.ChannelEventUpdate_FULLY_RESOLVED_CHANNEL, - r.event.Type, - "unexpected duplicate CLOSED_CHANNEL event observed "+ - "between the early dispatch and "+ - "FULLY_RESOLVED_CHANNEL: %v", r.event, - ) - - return r.event - - case <-deadline: - ht.Fatalf("timed out waiting for FULLY_RESOLVED_CHANNEL") - return nil - } -} - -// assertNoMoreClosedEvents reads from the subscription for the supplied -// quiet window and fails the test if a CLOSED_CHANNEL event is observed. -// FULLY_RESOLVED_CHANNEL has already been observed by this point so a -// second CLOSED_CHANNEL would represent the duplicate-notify regression -// that the suppression logic is meant to prevent. -func assertNoMoreClosedEvents(ht *lntest.HarnessTest, - sub rpc.ChannelEventsClient, window time.Duration) { - - done := make(chan struct{}) - defer close(done) - - errs := make(chan error, 1) - dups := make(chan *lnrpc.ChannelEventUpdate, 1) - - go func() { - for { - ev, err := sub.Recv() - if err != nil { - select { - case errs <- err: - case <-done: - } - - return - } - if ev.Type == - lnrpc.ChannelEventUpdate_CLOSED_CHANNEL { - - select { - case dups <- ev: - case <-done: - } - - return - } - } - }() - - select { - case dup := <-dups: - ht.Fatalf("unexpected duplicate CLOSED_CHANNEL event: %v", - dup) - - case err := <-errs: - // Stream EOF or context cancel is fine; we just want the - // quiet window to elapse without a duplicate. - _ = err - - case <-time.After(window): - // Quiet window elapsed without a duplicate. Pass. - } -} diff --git a/itest/lnd_zero_conf_test.go b/itest/lnd_zero_conf_test.go index ba5eddad4..ac82ba0d5 100644 --- a/itest/lnd_zero_conf_test.go +++ b/itest/lnd_zero_conf_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/aliasmgr" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" @@ -281,6 +281,7 @@ func testOptionScidAlias(ht *lntest.HarnessTest) { } for _, testCase := range testCases { + testCase := testCase success := ht.Run(testCase.name, func(t *testing.T) { st := ht.Subtest(t) optionScidAliasScenario( diff --git a/keychain/btcwallet.go b/keychain/btcwallet.go index 41167f3c4..cdacef53b 100644 --- a/keychain/btcwallet.go +++ b/keychain/btcwallet.go @@ -7,8 +7,8 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/walletdb" diff --git a/keychain/interface_test.go b/keychain/interface_test.go index 5d66b9751..8d27aa94f 100644 --- a/keychain/interface_test.go +++ b/keychain/interface_test.go @@ -7,8 +7,8 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcwallet/snacl" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wallet" @@ -345,7 +345,7 @@ func TestSecretKeyRingDerivation(t *testing.T) { // If we attempt to query for this key, then we // should get ErrCannotDerivePrivKey. - _, err = secretKeyRing.DerivePrivKey( + privKey, err = secretKeyRing.DerivePrivKey( keyDesc, ) if err != ErrCannotDerivePrivKey { diff --git a/keychain/signer.go b/keychain/signer.go index f0fe98f0a..fa2b70762 100644 --- a/keychain/signer.go +++ b/keychain/signer.go @@ -3,7 +3,7 @@ package keychain import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" ) func NewPubKeyMessageSigner(pubKey *btcec.PublicKey, keyLoc KeyLocator, diff --git a/kvdb/go.mod b/kvdb/go.mod index d932a0dd1..4c2dc49a2 100644 --- a/kvdb/go.mod +++ b/kvdb/go.mod @@ -2,14 +2,14 @@ module github.com/lightningnetwork/lnd/kvdb require ( github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084 - github.com/btcsuite/btcwallet/walletdb v1.6.0 + github.com/btcsuite/btcwallet/walletdb v1.5.1 github.com/davecgh/go-spew v1.1.1 github.com/fergusstrange/embedded-postgres v1.25.0 github.com/google/btree v1.0.1 - github.com/jackc/pgx/v5 v5.9.2 + github.com/jackc/pgx/v4 v4.18.3 github.com/lightningnetwork/lnd/healthcheck v1.2.4 github.com/lightningnetwork/lnd/sqldb v1.0.6 - github.com/stretchr/testify v1.11.1 + github.com/stretchr/testify v1.10.0 go.etcd.io/bbolt v1.4.3 go.etcd.io/etcd/api/v3 v3.5.12 go.etcd.io/etcd/client/pkg/v3 v3.5.12 @@ -21,36 +21,33 @@ require ( require ( dario.cat/mergo v1.0.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/BurntSushi/toml v1.3.2 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/btcsuite/btcd v0.26.0 // indirect - github.com/btcsuite/btcd/chaincfg/v2 v2.0.0 // indirect - github.com/btcsuite/btcd/chainhash/v2 v2.0.0 // indirect - github.com/btcsuite/btcd/wire/v2 v2.0.0 // indirect - github.com/btcsuite/btclog v1.0.0 // indirect + github.com/btcsuite/btcd v0.24.2 // indirect + github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect + github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/containerd/continuity v0.3.0 // indirect github.com/coreos/go-semver v0.3.0 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/coreos/go-systemd/v22 v22.3.2 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/cli v28.1.1+incompatible // indirect github.com/docker/docker v28.1.1+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/go-viper/mapstructure/v2 v2.3.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang-migrate/migrate/v4 v4.17.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.3 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect @@ -65,33 +62,34 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgproto3/v2 v2.3.3 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgtype v1.14.4 // indirect + github.com/jackc/pgx/v5 v5.7.4 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jonboulle/clockwork v0.2.2 // indirect - github.com/json-iterator/go v1.1.12 // indirect + github.com/json-iterator/go v1.1.11 // indirect github.com/lib/pq v1.10.9 // indirect github.com/lightningnetwork/lnd/ticker v1.1.0 // indirect github.com/lightningnetwork/lnd/tor v1.0.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/miekg/dns v1.1.43 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/sys/user v0.3.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/opencontainers/runc v1.2.8 // indirect + github.com/opencontainers/runc v1.1.14 // indirect github.com/ory/dockertest/v3 v3.10.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.23.2 // indirect - github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/client_golang v1.11.1 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.26.0 // indirect + github.com/prometheus/procfs v0.6.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sirupsen/logrus v1.9.2 // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect @@ -103,34 +101,33 @@ require ( go.etcd.io/etcd/client/v2 v2.305.12 // indirect go.etcd.io/etcd/pkg/v3 v3.5.12 // indirect go.etcd.io/etcd/raft/v3 v3.5.12 // indirect - go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect - go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 // indirect - go.opentelemetry.io/otel/metric v1.44.0 // indirect - go.opentelemetry.io/otel/sdk v1.44.0 // indirect - go.opentelemetry.io/otel/trace v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.35.0 // indirect + go.opentelemetry.io/otel/sdk v1.35.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.17.0 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/crypto v0.46.0 // indirect + golang.org/x/crypto v0.37.0 // indirect golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect - golang.org/x/mod v0.30.0 // indirect - golang.org/x/net v0.48.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.45.0 // indirect - golang.org/x/text v0.32.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.39.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.39.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect - google.golang.org/grpc v1.79.3 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect + google.golang.org/grpc v1.59.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect @@ -143,4 +140,11 @@ require ( sigs.k8s.io/yaml v1.2.0 // indirect ) -go 1.25.11 +// This replace is for https://github.com/advisories/GHSA-25xm-hr59-7c27 +replace github.com/ulikunitz/xz => github.com/ulikunitz/xz v0.5.11 + +// This replace is for +// https://deps.dev/advisory/OSV/GO-2021-0053?from=%2Fgo%2Fgithub.com%252Fgogo%252Fprotobuf%2Fv1.3.1 +replace github.com/gogo/protobuf => github.com/gogo/protobuf v1.3.2 + +go 1.24.11 diff --git a/kvdb/go.sum b/kvdb/go.sum index d977fce26..4ad9aaa76 100644 --- a/kvdb/go.sum +++ b/kvdb/go.sum @@ -2,45 +2,50 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= -cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= -cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.22.0-beta.0.20220207191057-4dc4ff7963b4/go.mod h1:7alexyj/lHlOtr2PJK7L/+HDJZpcGDn/pAU98r7DY08= -github.com/btcsuite/btcd v0.26.0 h1:yntnSshlG3+H7dTwIOR4LTFXDPojVBsFORBNN5y5c/c= -github.com/btcsuite/btcd v0.26.0/go.mod h1:7ft7+a/MoJHFouFopCb1zyiR9IWPlrcPVn6K/lJ1dcA= +github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= +github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= -github.com/btcsuite/btcd/chaincfg/v2 v2.0.0 h1:M/RTtXfXA9odC1RUEOyZFXj/NXKVHPYZXVjb60xTOok= -github.com/btcsuite/btcd/chaincfg/v2 v2.0.0/go.mod h1:rHgHIXYYfn70m25a+BJ9f9z7VZAsTiDQGB2XYaippGQ= -github.com/btcsuite/btcd/chainhash/v2 v2.0.0 h1:PMLlSloHJuEeB80XG9EjpXWNEKAZAMLl6YHZ6YsEuoA= -github.com/btcsuite/btcd/chainhash/v2 v2.0.0/go.mod h1:mKxcZ7oGTXE7IRV+sS9hP4EVBwc/SzfNR+52IsOP9j8= -github.com/btcsuite/btcd/wire/v2 v2.0.0 h1:mYSKzZZ0a1sK+aMhXzfDSVsSzRkWkU3x2U04TFRS2z8= -github.com/btcsuite/btcd/wire/v2 v2.0.0/go.mod h1:bGxkPkk8IiDvUo1D96wE03llBIk7p2MdWYRyAQwLmqM= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/btcsuite/btclog v1.0.0 h1:sEkpKJMmfGiyZjADwEIgB1NSwMyfdD1FB8v6+w1T0Ns= -github.com/btcsuite/btclog v1.0.0/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ= +github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c h1:4HxD1lBUGUddhzgaNgrCPsFWd7cGYNpeFUgd9ZIgyM0= +github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ= github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084 h1:y3bvkt8ki0KX35eUEU8XShRHusz1S+55QwXUTmxn888= github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcwallet/walletdb v1.6.0 h1:Yund5XbdqFxNW7+R2Sxs02bMC5fMrmORj4GN8MV55no= -github.com/btcsuite/btcwallet/walletdb v1.6.0/go.mod h1:q9xif0Csp52GVb3l252BbHCuyiCnuEbrPWu/HAsvaYc= +github.com/btcsuite/btcwallet/walletdb v1.5.1 h1:HgMhDNCrtEFPC+8q0ei5DQ5U9Tl4RCspA22DEKXlopI= +github.com/btcsuite/btcwallet/walletdb v1.5.1/go.mod h1:jk/hvpLFINF0C1kfTn0bfx2GbnFT+Nvnj6eblZALfjs= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= @@ -51,20 +56,26 @@ github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46f github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= -github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= +github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY= +github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -92,8 +103,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= -github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= +github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fergusstrange/embedded-postgres v1.25.0 h1:sa+k2Ycrtz40eCRPOzI7Ry7TtkWXXJ+YRsxpKMDhxK0= @@ -101,19 +112,25 @@ github.com/fergusstrange/embedded-postgres v1.25.0/go.mod h1:t/MLs0h9ukYM6FSt99R github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= +github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= @@ -121,10 +138,11 @@ github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w github.com/golang-migrate/migrate/v4 v4.17.0 h1:rd40H3QXU0AA4IoLllFcEAEo9dYKRHYND2gB4p7xcaU= github.com/golang-migrate/migrate/v4 v4.17.0/go.mod h1:+Cp2mtLP4/aXDTKb9wmXYitdrNx2HGs45rbWAo6OsKM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I= -github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= @@ -133,6 +151,7 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= @@ -141,18 +160,21 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= @@ -169,47 +191,94 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9 github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= -github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.14.4 h1:fKuNiCumbKTAIxQwXfB/nsrnkEI6bPJrrSiMKgbJ2j8= +github.com/jackc/pgtype v1.14.4/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg= +github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightningnetwork/lnd/healthcheck v1.2.4 h1:lLPLac+p/TllByxGSlkCwkJlkddqMP5UCoawCj3mgFQ= @@ -220,25 +289,31 @@ github.com/lightningnetwork/lnd/ticker v1.1.0 h1:ShoBiRP3pIxZHaETndfQ5kEe+S4NdAY github.com/lightningnetwork/lnd/ticker v1.1.0/go.mod h1:ubqbSVCn6RlE0LazXuBr7/Zi6QT0uQo++OgIRBxQUrk= github.com/lightningnetwork/lnd/tor v1.0.0 h1:wvEc7I+Y7IOtPglVP3cVBbYhiVhc7uTd7cMF9gQRzwA= github.com/lightningnetwork/lnd/tor v1.0.0/go.mod h1:RDtaAdwfAm+ONuPYwUhNIH1RAvKPv+75lHPOegUcz64= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= -github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -254,47 +329,76 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v1.2.8 h1:RnEICeDReapbZ5lZEgHvj7E9Q3Eex9toYmaGBsbvU5Q= -github.com/opencontainers/runc v1.2.8/go.mod h1:cC0YkmZcuvr+rtBZ6T7NBoVbMGNAdLa/21vIElJDOzI= +github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w= +github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= -github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= -github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= +github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= @@ -309,6 +413,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= go.etcd.io/etcd/api/v3 v3.5.12 h1:W4sw5ZoU2Juc9gBWuLk5U6fHfNVyY1WC5g9uiXZio/c= @@ -325,167 +431,243 @@ go.etcd.io/etcd/raft/v3 v3.5.12 h1:7r22RufdDsq2z3STjoR7Msz6fYH8tmbkdheGfwJNRmU= go.etcd.io/etcd/raft/v3 v3.5.12/go.mod h1:ERQuZVe79PI6vcC3DlKBukDCLja/L7YMu29B74Iwj4U= go.etcd.io/etcd/server/v3 v3.5.12 h1:EtMjsbfyfkwZuA2JlKOiBfuGkFCekv5H178qjXypbG8= go.etcd.io/etcd/server/v3 v3.5.12/go.mod h1:axB0oCjMy+cemo5290/CutIjoxlfA6KVYKD1w0uue10= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 h1:PzIubN4/sjByhDRHLviCjJuweBXWFZWhghjg7cS28+M= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= -go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= -go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 h1:DeFD0VgTZ+Cj6hxravYYZE2W4GlneVH81iAOPjZkzk8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0/go.mod h1:GijYcYmNpX1KazD5JmWGsi4P7dDTTTnfv1UbGn84MnU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 h1:gvmNvqrPYovvyRmCSygkUDyL8lC5Tl845MLEwqpxhEU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0/go.mod h1:vNUq47TGFioo+ffTSnKNdob241vePmtNZnAODKapKd0= -go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= -go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= -go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= -go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= -go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0= +golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= +google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= -google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -493,6 +675,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -505,6 +688,7 @@ gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk= modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA= diff --git a/kvdb/postgres/db.go b/kvdb/postgres/db.go index 6aca0276d..5d2b482a2 100644 --- a/kvdb/postgres/db.go +++ b/kvdb/postgres/db.go @@ -16,10 +16,12 @@ var sqliteCmdReplacements = sqlbase.SQLiteCmdReplacements{ "INTEGER PRIMARY KEY": "BIGSERIAL PRIMARY KEY", } -// newSQLBaseConfig builds the shared sqlbase config used by both the regular -// and migration Postgres backends from the passed backend config and prefix. -func newSQLBaseConfig(config *Config, prefix string) *sqlbase.Config { - return &sqlbase.Config{ +// newPostgresBackend returns a db object initialized with the passed backend +// config. If postgres connection cannot be established, then returns error. +func newPostgresBackend(ctx context.Context, config *Config, prefix string) ( + walletdb.DB, error) { + + cfg := &sqlbase.Config{ DriverName: "pgx", Dsn: config.Dsn, Timeout: config.Timeout, @@ -28,22 +30,6 @@ func newSQLBaseConfig(config *Config, prefix string) *sqlbase.Config { SQLiteCmdReplacements: sqliteCmdReplacements, WithTxLevelLock: config.WithGlobalLock, } -} - -// newPostgresBackend returns a db object initialized with the passed backend -// config. If postgres connection cannot be established, then returns error. -func newPostgresBackend(ctx context.Context, config *Config, prefix string) ( - walletdb.DB, error) { - - return sqlbase.NewSqlBackend(ctx, newSQLBaseConfig(config, prefix)) -} - -// NewMigrationBackend returns a Postgres backend that explicitly exposes the -// migration-only bulk KV interface. -func NewMigrationBackend(ctx context.Context, config *Config, prefix string) ( - sqlbase.MigrationBackend, error) { - - return sqlbase.NewPostgresBackend( - ctx, newSQLBaseConfig(config, prefix), - ) + + return sqlbase.NewSqlBackend(ctx, cfg) } diff --git a/kvdb/postgres/db_test.go b/kvdb/postgres/db_test.go index 11f7b4020..1f660087c 100644 --- a/kvdb/postgres/db_test.go +++ b/kvdb/postgres/db_test.go @@ -8,7 +8,6 @@ import ( "github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/walletdb/walletdbtest" - "github.com/lightningnetwork/lnd/kvdb/sqlbase" "github.com/stretchr/testify/require" ) @@ -21,11 +20,6 @@ func TestInterface(t *testing.T) { f, err := NewFixture("") require.NoError(t, err) - // The regular Postgres backend must not expose migration-only - // capabilities. Callers must opt in through NewMigrationBackend. - _, ok := f.Db.(sqlbase.MigrationBulkKVStore) - require.False(t, ok) - // dbType is the database type name for this driver. const dbType = "postgres" diff --git a/kvdb/postgres/fixture.go b/kvdb/postgres/fixture.go index 0ebbe5d08..449ba8de6 100644 --- a/kvdb/postgres/fixture.go +++ b/kvdb/postgres/fixture.go @@ -59,33 +59,7 @@ func StartEmbeddedPostgres() (func() error, error) { // NewFixture returns a new postgres test database. The database name is // randomly generated. -func NewFixture(dbName string) (*fixture[walletdb.DB], error) { - return newFixture(dbName, prefix, false, newPostgresBackend) -} - -// NewMigrationFixture returns a new postgres test database that explicitly -// exposes the migration-only bulk KV interface. -func NewMigrationFixture(dbName string) ( - *fixture[sqlbase.MigrationBackend], error) { - - return newFixture(dbName, prefix, false, NewMigrationBackend) -} - -// NewMigrationFixtureWithLock is like NewMigrationFixture but enables the -// global tx-level lock so the lock-guarded migration paths are exercised. -func NewMigrationFixtureWithLock(dbName string) ( - *fixture[sqlbase.MigrationBackend], error) { - - return newFixture(dbName, prefix, true, NewMigrationBackend) -} - -// newFixture creates a new postgres test database using the passed backend -// constructor, allowing callers to select the regular or migration backend and -// whether the global tx-level lock is enabled. -func newFixture[T walletdb.DB](dbName, tablePrefix string, - withGlobalLock bool, openBackend func(context.Context, *Config, - string) (T, error)) (*fixture[T], error) { - +func NewFixture(dbName string) (*fixture, error) { if dbName == "" { // Create random database name. randBytes := make([]byte, 8) @@ -113,36 +87,35 @@ func newFixture[T walletdb.DB](dbName, tablePrefix string, // Open database dsn := getTestDsn(dbName) - db, err := openBackend( + db, err := newPostgresBackend( context.Background(), &Config{ - Dsn: dsn, - Timeout: time.Minute, - WithGlobalLock: withGlobalLock, + Dsn: dsn, + Timeout: time.Minute, }, - tablePrefix, + prefix, ) if err != nil { return nil, err } - return &fixture[T]{ + return &fixture{ Dsn: dsn, Db: db, }, nil } -type fixture[T walletdb.DB] struct { +type fixture struct { Dsn string - Db T + Db walletdb.DB } -func (b *fixture[T]) DB() walletdb.DB { +func (b *fixture) DB() walletdb.DB { return b.Db } // Dump returns the raw contents of the database. -func (b *fixture[T]) Dump() (map[string]interface{}, error) { +func (b *fixture) Dump() (map[string]interface{}, error) { dbConn, err := sql.Open("pgx", b.Dsn) if err != nil { return nil, err diff --git a/kvdb/postgres/migration_bulk_test.go b/kvdb/postgres/migration_bulk_test.go deleted file mode 100644 index ecd41a1b8..000000000 --- a/kvdb/postgres/migration_bulk_test.go +++ /dev/null @@ -1,302 +0,0 @@ -//go:build kvdb_postgres - -package postgres - -import ( - "math" - "testing" - - "github.com/btcsuite/btcwallet/walletdb" - "github.com/lightningnetwork/lnd/kvdb/sqlbase" - "github.com/stretchr/testify/require" -) - -// TestMigrationBulkKVStorePostgres verifies explicit migration capability -// opt-in, bucket sequence preservation, leaf COPY semantics, verification, -// transaction closure, and target truncation. -func TestMigrationBulkKVStorePostgres(t *testing.T) { - stop, err := StartEmbeddedPostgres() - require.NoError(t, err) - defer func() { - require.NoError(t, stop()) - }() - - f, err := NewMigrationFixture("") - require.NoError(t, err) - defer func() { - require.NoError(t, f.Db.Close()) - }() - - ctx := t.Context() - store := f.Db - - empty, err := store.CheckEmpty(ctx) - require.NoError(t, err) - require.True(t, empty) - - tx, err := store.BeginBulk(ctx) - require.NoError(t, err) - defer func() { - require.NoError(t, tx.Rollback()) - }() - - // Both nil and non-nil empty bucket keys must follow walletdb's key - // semantics without poisoning the bulk transaction. - for _, key := range [][]byte{nil, {}} { - _, err := tx.InsertBucket(ctx, nil, key, 0) - require.ErrorIs(t, err, walletdb.ErrBucketNameRequired) - } - - rootID, err := tx.InsertBucket( - ctx, nil, []byte("root"), math.MaxUint64, - ) - require.NoError(t, err) - - maxIntPlusOne := uint64(math.MaxInt64) + 1 - nestedID, err := tx.InsertBucket( - ctx, &rootID, []byte("nested"), maxIntPlusOne, - ) - require.NoError(t, err) - - // Leaves must belong to a previously inserted bucket. In particular, - // the zero-value parent must not be treated as a top-level leaf. - err = tx.InsertLeaves(ctx, []sqlbase.MigrationBulkLeaf{{ - Key: []byte("top-level"), - Value: []byte("unsupported"), - }}) - require.EqualError(t, err, "bulk leaf 0 has invalid parent id 0") - - // As with regular walletdb writes, nil and non-nil empty leaf keys are - // invalid. The indexed error identifies the bad entry in a batch. - for _, key := range [][]byte{nil, {}} { - err = tx.InsertLeaves(ctx, []sqlbase.MigrationBulkLeaf{{ - ParentID: rootID, - Key: key, - Value: []byte("value"), - }}) - require.ErrorIs(t, err, walletdb.ErrKeyRequired) - require.ErrorContains(t, err, "bulk leaf 0") - } - - require.NoError(t, tx.InsertLeaves(ctx, []sqlbase.MigrationBulkLeaf{ - { - ParentID: rootID, - Key: []byte("a"), - Value: []byte("value"), - }, - { - ParentID: nestedID, - Key: []byte("empty"), - Value: []byte{}, - }, - { - ParentID: nestedID, - Key: []byte("nil"), - Value: nil, - }, - })) - - require.NoError(t, tx.Commit()) - - _, err = tx.InsertBucket(ctx, nil, []byte("closed"), 0) - require.ErrorIs(t, err, walletdb.ErrTxClosed) - require.ErrorIs(t, tx.InsertLeaves(ctx, nil), walletdb.ErrTxClosed) - - empty, err = store.CheckEmpty(ctx) - require.NoError(t, err) - require.False(t, empty) - - verifier, err := store.BeginBulkVerify(ctx) - require.NoError(t, err) - defer func() { - require.NoError(t, verifier.Rollback()) - }() - - top, err := verifier.FetchTopLevel(ctx) - require.NoError(t, err) - require.Len(t, top, 1) - require.Equal(t, rootID, top[0].ID) - require.Nil(t, top[0].ParentID) - require.Equal(t, []byte("root"), top[0].Key) - require.True(t, top[0].IsBucket) - require.Equal(t, uint64(math.MaxUint64), top[0].Sequence) - - rootChildren, err := verifier.FetchChildren(ctx, []int64{rootID}) - require.NoError(t, err) - require.Len(t, rootChildren, 2) - - require.Equal(t, []byte("a"), rootChildren[0].Key) - require.False(t, rootChildren[0].IsBucket) - require.Equal(t, []byte("value"), rootChildren[0].Value) - require.NotNil(t, rootChildren[0].ParentID) - require.Equal(t, rootID, *rootChildren[0].ParentID) - - require.Equal(t, []byte("nested"), rootChildren[1].Key) - require.True(t, rootChildren[1].IsBucket) - require.Equal(t, nestedID, rootChildren[1].ID) - require.Equal(t, maxIntPlusOne, rootChildren[1].Sequence) - - nestedChildren, err := verifier.FetchChildren(ctx, []int64{nestedID}) - require.NoError(t, err) - require.Len(t, nestedChildren, 2) - require.Equal(t, []byte("empty"), nestedChildren[0].Key) - require.False(t, nestedChildren[0].IsBucket) - require.NotNil(t, nestedChildren[0].Value) - require.Empty(t, nestedChildren[0].Value) - - require.Equal(t, []byte("nil"), nestedChildren[1].Key) - require.False(t, nestedChildren[1].IsBucket) - require.NotNil(t, nestedChildren[1].Value) - require.Empty(t, nestedChildren[1].Value) - - noChildren, err := verifier.FetchChildren(ctx, nil) - require.NoError(t, err) - require.Nil(t, noChildren) - - require.NoError(t, verifier.Rollback()) - - _, err = verifier.FetchTopLevel(ctx) - require.ErrorIs(t, err, walletdb.ErrTxClosed) - _, err = verifier.FetchChildren(ctx, nil) - require.ErrorIs(t, err, walletdb.ErrTxClosed) - - require.NoError(t, store.TruncateTargetTable(ctx)) - empty, err = store.CheckEmpty(ctx) - require.NoError(t, err) - require.True(t, empty) -} - -// TestMigrationBulkKVStoreRollbackPostgres verifies that rollback discards a -// bulk load, remains idempotent, and closes the transaction to further writes. -func TestMigrationBulkKVStoreRollbackPostgres(t *testing.T) { - stop, err := StartEmbeddedPostgres() - require.NoError(t, err) - defer func() { - require.NoError(t, stop()) - }() - - f, err := NewMigrationFixture("") - require.NoError(t, err) - defer func() { - require.NoError(t, f.Db.Close()) - }() - - ctx := t.Context() - store := f.Db - - tx, err := store.BeginBulk(ctx) - require.NoError(t, err) - - rootID, err := tx.InsertBucket(ctx, nil, []byte("root"), 0) - require.NoError(t, err) - require.NoError(t, tx.InsertLeaves(ctx, []sqlbase.MigrationBulkLeaf{{ - ParentID: rootID, - Key: []byte("leaf"), - Value: []byte("value"), - }})) - - require.NoError(t, tx.Rollback()) - require.NoError(t, tx.Rollback()) - - _, err = tx.InsertBucket(ctx, nil, []byte("closed"), 0) - require.ErrorIs(t, err, walletdb.ErrTxClosed) - require.ErrorIs(t, tx.InsertLeaves(ctx, nil), walletdb.ErrTxClosed) - - empty, err := store.CheckEmpty(ctx) - require.NoError(t, err) - require.True(t, empty) -} - -// TestMigrationBulkKVStoreMixedCasePrefixPostgres verifies that Postgres's -// unquoted SQL paths and the quoted COPY identifier resolve the same table -// when the configured prefix contains uppercase characters. -func TestMigrationBulkKVStoreMixedCasePrefixPostgres(t *testing.T) { - stop, err := StartEmbeddedPostgres() - require.NoError(t, err) - defer func() { - require.NoError(t, stop()) - }() - - f, err := newFixture( - "", "MixedCase", false, NewMigrationBackend, - ) - require.NoError(t, err) - defer func() { - require.NoError(t, f.Db.Close()) - }() - - ctx := t.Context() - tx, err := f.Db.BeginBulk(ctx) - require.NoError(t, err) - defer func() { - require.NoError(t, tx.Rollback()) - }() - - rootID, err := tx.InsertBucket(ctx, nil, []byte("root"), 0) - require.NoError(t, err) - require.NoError(t, tx.InsertLeaves(ctx, []sqlbase.MigrationBulkLeaf{{ - ParentID: rootID, - Key: []byte("leaf"), - Value: []byte("value"), - }})) - require.NoError(t, tx.Commit()) - - empty, err := f.Db.CheckEmpty(ctx) - require.NoError(t, err) - require.False(t, empty) -} - -// TestMigrationBulkKVStoreGlobalLockPostgres runs a full bulk load and -// verification cycle with the global tx-level lock enabled to exercise the -// lock-guarded write and read paths without deadlocking. -func TestMigrationBulkKVStoreGlobalLockPostgres(t *testing.T) { - stop, err := StartEmbeddedPostgres() - require.NoError(t, err) - defer func() { - require.NoError(t, stop()) - }() - - f, err := NewMigrationFixtureWithLock("") - require.NoError(t, err) - defer func() { - require.NoError(t, f.Db.Close()) - }() - - ctx := t.Context() - store := f.Db - - // Write path: BeginBulk takes the exclusive lock and Commit releases - // it. - tx, err := store.BeginBulk(ctx) - require.NoError(t, err) - - rootID, err := tx.InsertBucket(ctx, nil, []byte("root"), 0) - require.NoError(t, err) - require.NoError(t, tx.InsertLeaves(ctx, []sqlbase.MigrationBulkLeaf{{ - ParentID: rootID, - Key: []byte("leaf"), - Value: []byte("value"), - }})) - require.NoError(t, tx.Commit()) - - // Read path: CheckEmpty and the verifier take the shared read lock. - empty, err := store.CheckEmpty(ctx) - require.NoError(t, err) - require.False(t, empty) - - verifier, err := store.BeginBulkVerify(ctx) - require.NoError(t, err) - defer func() { - require.NoError(t, verifier.Rollback()) - }() - - top, err := verifier.FetchTopLevel(ctx) - require.NoError(t, err) - require.Len(t, top, 1) - require.Equal(t, rootID, top[0].ID) - - children, err := verifier.FetchChildren(ctx, []int64{rootID}) - require.NoError(t, err) - require.Len(t, children, 1) - require.Equal(t, []byte("value"), children[0].Value) -} diff --git a/kvdb/sqlbase/db_conn_set.go b/kvdb/sqlbase/db_conn_set.go index fa7d93af6..ee360a973 100644 --- a/kvdb/sqlbase/db_conn_set.go +++ b/kvdb/sqlbase/db_conn_set.go @@ -5,7 +5,7 @@ import ( "fmt" "sync" - _ "github.com/jackc/pgx/v5/stdlib" + _ "github.com/jackc/pgx/v4/stdlib" ) // dbConn stores the actual connection and a user count. diff --git a/kvdb/sqlbase/migration_bulk.go b/kvdb/sqlbase/migration_bulk.go deleted file mode 100644 index 3b4f1dca4..000000000 --- a/kvdb/sqlbase/migration_bulk.go +++ /dev/null @@ -1,109 +0,0 @@ -//go:build kvdb_postgres || (kvdb_sqlite && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64))) - -package sqlbase - -import ( - "context" - - "github.com/btcsuite/btcwallet/walletdb" -) - -// MigrationBackend combines the regular walletdb database operations with the -// migration-only bulk capabilities guaranteed by a migration backend. -type MigrationBackend interface { - walletdb.DB - MigrationBulkKVStore -} - -// MigrationBulkKVStore exposes migration-only helpers for loading and verifying -// the SQL KV schema directly. Normal application code should continue to use -// the walletdb/kvdb bucket APIs. -type MigrationBulkKVStore interface { - // CheckEmpty returns whether the underlying KV table has no rows. - CheckEmpty(ctx context.Context) (bool, error) - - // TruncateTargetTable unconditionally and irreversibly removes every - // row from the underlying KV table. It is only intended for fresh-only - // migration recovery when the caller owns the whole target table. - TruncateTargetTable(ctx context.Context) error - - // BeginBulk opens a destination write transaction for bulk loading. - // Callers must defer Rollback immediately after a successful open; the - // rollback is a no-op after Commit and releases locks/connections on - // all other exits. - BeginBulk(ctx context.Context) (MigrationBulkKVTx, error) - - // BeginBulkVerify opens a read transaction for batched verification. - // Callers must defer Rollback immediately after a successful open so - // the read transaction lock is always released. - BeginBulkVerify(ctx context.Context) (MigrationBulkKVVerifier, error) -} - -// MigrationBulkLeaf is a leaf key/value row to be inserted under ParentID. -// ParentID must be a positive row ID returned by InsertBucket. Top-level leaves -// are not supported by the migration bulk API. -type MigrationBulkLeaf struct { - ParentID int64 - - // Key must be non-empty. - Key []byte - Value []byte -} - -// MigrationBulkKVTx is a migration-only transaction for bulk-loading SQL KV -// data. -type MigrationBulkKVTx interface { - // InsertBucket inserts a bucket row with a non-empty key. It returns - // the generated id. A nil parentID creates a top-level bucket. - InsertBucket(ctx context.Context, parentID *int64, key []byte, - seq uint64) (int64, error) - - // InsertLeaves inserts leaf rows with non-empty keys. Implementations - // may choose COPY, multi-row INSERT, or another backend-specific - // strategy. - InsertLeaves(ctx context.Context, leaves []MigrationBulkLeaf) error - - // Commit atomically commits the bulk load transaction. - Commit() error - - // Rollback aborts the bulk load transaction. - Rollback() error -} - -// MigrationBulkChild is a single SQL KV row returned by the verifier helpers. -type MigrationBulkChild struct { - // ID is the SQL row id. - ID int64 - - // ParentID is nil for top-level rows. - ParentID *int64 - - // Key is the bucket key or leaf key. - Key []byte - - // Value is the leaf value. It is nil for buckets. - Value []byte - - // IsBucket identifies bucket rows explicitly. This avoids ambiguity - // between SQL NULL bucket markers and empty leaf values decoded as nil. - IsBucket bool - - // Sequence is the bucket sequence number. It is zero for unset - // sequences and for leaf rows. - Sequence uint64 -} - -// MigrationBulkKVVerifier reads SQL KV rows in batches for migration -// verification. -type MigrationBulkKVVerifier interface { - // FetchTopLevel returns all top-level rows ordered by key. - FetchTopLevel(ctx context.Context) ([]MigrationBulkChild, error) - - // FetchChildren returns direct children for the given bucket ids, - // ordered by parent id and key. - FetchChildren(ctx context.Context, - parentIDs []int64) ([]MigrationBulkChild, error) - - // Rollback closes the verifier transaction. - Rollback() error -} diff --git a/kvdb/sqlbase/migration_bulk_postgres.go b/kvdb/sqlbase/migration_bulk_postgres.go deleted file mode 100644 index c3c8b5c86..000000000 --- a/kvdb/sqlbase/migration_bulk_postgres.go +++ /dev/null @@ -1,433 +0,0 @@ -//go:build kvdb_postgres - -package sqlbase - -import ( - "context" - "database/sql" - "errors" - "fmt" - "strings" - "sync" - - "github.com/btcsuite/btcwallet/walletdb" - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/stdlib" -) - -// postgresDB adds Postgres-only capabilities to the shared SQL backend. -type postgresDB struct { - *db -} - -var ( - _ walletdb.DB = (*postgresDB)(nil) - _ MigrationBulkKVStore = (*postgresDB)(nil) -) - -// bulkLeafCols is the leaf-row projection of the shared KV table schema -// defined in schema.go. The id column is database-generated. Sequence is -// walletdb bucket metadata copied separately by InsertBucket, so leaf rows do -// not include either column in the COPY operation. -var bulkLeafCols = []string{"parent_id", "key", "value"} - -// NewPostgresBackend returns a shared SQL backend with Postgres-only -// capabilities, including migration bulk loading. -func NewPostgresBackend(ctx context.Context, cfg *Config) ( - MigrationBackend, error) { - - db, err := NewSqlBackend(ctx, cfg) - if err != nil { - return nil, err - } - - return &postgresDB{db: db}, nil -} - -// CheckEmpty returns whether the underlying KV table has no rows. -func (p *postgresDB) CheckEmpty(ctx context.Context) (bool, error) { - locker := p.bulkLocker(true) - locker.Lock() - defer locker.Unlock() - - var count int64 - err := p.db.db.QueryRowContext( - ctx, "SELECT COUNT(*) FROM "+p.table, - ).Scan(&count) - if err != nil { - return false, err - } - - return count == 0, nil -} - -// TruncateTargetTable unconditionally and irreversibly removes every row from -// the underlying KV table. It is only intended for fresh migration recovery -// where the caller owns the whole target table. -func (p *postgresDB) TruncateTargetTable(ctx context.Context) error { - locker := p.bulkLocker(false) - locker.Lock() - defer locker.Unlock() - - _, err := p.db.db.ExecContext(ctx, "TRUNCATE TABLE "+p.table) - - return err -} - -// BeginBulk opens a write transaction for bulk loading. It uses a dedicated -// *sql.Conn so InsertLeaves can reach the underlying pgx connection and COPY -// into the same transaction. Callers must defer Rollback immediately after a -// successful open so the lock and connection are released on all exits. -func (p *postgresDB) BeginBulk(ctx context.Context) (MigrationBulkKVTx, error) { - locker := p.bulkLocker(false) - locker.Lock() - - conn, err := p.db.db.Conn(ctx) - if err != nil { - locker.Unlock() - return nil, err - } - - // A bulk migration can touch millions of rows in a single transaction. - // PostgreSQL retains predicate locks until a serializable transaction - // ends, which can make its predicate lock table consume excessive memory. - // Read committed is sufficient because the migration owns the empty - // destination database while loading it. - tx, err := conn.BeginTx(ctx, &sql.TxOptions{ - Isolation: sql.LevelReadCommitted, - }) - if err != nil { - locker.Unlock() - _ = conn.Close() - return nil, err - } - - return &postgresBulkKVTx{ - db: p.db, - conn: conn, - tx: tx, - locker: locker, - active: true, - }, nil -} - -// BeginBulkVerify opens a read-only transaction for batched verification. -// Callers must defer Rollback immediately after a successful open so the read -// transaction lock is always released. -func (p *postgresDB) BeginBulkVerify( - ctx context.Context) (MigrationBulkKVVerifier, error) { - - locker := p.bulkLocker(true) - locker.Lock() - - tx, err := p.db.db.BeginTx(ctx, &sql.TxOptions{ - ReadOnly: true, - Isolation: sql.LevelRepeatableRead, - }) - if err != nil { - locker.Unlock() - return nil, err - } - - return &postgresBulkKVVerifier{ - db: p.db, - tx: tx, - locker: locker, - active: true, - }, nil -} - -// bulkLocker returns the same optional global lock used by regular sqlbase -// transactions so migration-only transactions respect WithTxLevelLock. -func (p *postgresDB) bulkLocker(readOnly bool) sync.Locker { - if !p.cfg.WithTxLevelLock { - return newNoopLocker() - } - if readOnly { - return p.lock.RLocker() - } - - return &p.lock -} - -// postgresBulkKVTx is a migration-only Postgres transaction for loading the SQL -// KV table directly. -type postgresBulkKVTx struct { - db *db - conn *sql.Conn - tx *sql.Tx - locker sync.Locker - active bool -} - -// InsertBucket inserts a bucket row and returns its generated id. -func (p *postgresBulkKVTx) InsertBucket(ctx context.Context, - parentID *int64, key []byte, seq uint64) (int64, error) { - - if !p.active { - return 0, walletdb.ErrTxClosed - } - if len(key) == 0 { - return 0, walletdb.ErrBucketNameRequired - } - - keyCopy := cloneBulkBytes(key) - - var id int64 - if seq != 0 { - err := p.tx.QueryRowContext( - ctx, "INSERT INTO "+p.db.table+ - " (parent_id, key, sequence) "+ - "VALUES ($1,$2,$3) RETURNING id", - parentID, keyCopy, int64(seq), - ).Scan(&id) - - return id, err - } - - err := p.tx.QueryRowContext( - ctx, "INSERT INTO "+p.db.table+" (parent_id, key) "+ - "VALUES ($1,$2) RETURNING id", - parentID, keyCopy, - ).Scan(&id) - - return id, err -} - -// InsertLeaves inserts leaf rows with Postgres COPY. -func (p *postgresBulkKVTx) InsertLeaves(ctx context.Context, - leaves []MigrationBulkLeaf) error { - - if !p.active { - return walletdb.ErrTxClosed - } - if len(leaves) == 0 { - return nil - } - - rows := make([][]any, len(leaves)) - for i := range leaves { - if leaves[i].ParentID <= 0 { - return fmt.Errorf( - "bulk leaf %d has invalid parent id %d", i, - leaves[i].ParentID, - ) - } - if len(leaves[i].Key) == 0 { - return fmt.Errorf( - "bulk leaf %d: %w", i, walletdb.ErrKeyRequired, - ) - } - - value := cloneBulkBytes(leaves[i].Value) - if value == nil { - value = []byte{} - } - - rows[i] = []any{ - leaves[i].ParentID, - cloneBulkBytes(leaves[i].Key), - value, - } - } - - var copied int64 - err := p.conn.Raw(func(driverConn any) error { - pgxConn, ok := driverConn.(*stdlib.Conn) - if !ok { - return fmt.Errorf("driver conn is %T, not "+ - "pgx/v5/stdlib.Conn", driverConn) - } - - var copyErr error - // The shared schema and normal SQL paths use unquoted - // identifiers, which Postgres folds to lowercase. CopyFrom - // quotes its identifier, so fold it explicitly to resolve the - // same physical table. - copied, copyErr = pgxConn.Conn().CopyFrom( - ctx, pgx.Identifier{strings.ToLower(p.db.table)}, - bulkLeafCols, - pgx.CopyFromRows(rows), - ) - - return copyErr - }) - if err != nil { - return err - } - if copied != int64(len(leaves)) { - return fmt.Errorf("bulk leaf copy count mismatch: got=%d "+ - "want=%d", copied, len(leaves)) - } - - return nil -} - -// Commit commits the bulk transaction and releases its dedicated connection. -func (p *postgresBulkKVTx) Commit() error { - if !p.active { - return walletdb.ErrTxClosed - } - - err := p.tx.Commit() - p.active = false - p.locker.Unlock() - closeErr := p.conn.Close() - if err != nil { - if closeErr != nil { - log.Warnf( - "Could not close bulk migration connection: %v", - closeErr, - ) - } - - return err - } - if closeErr != nil { - log.Warnf("Could not close bulk migration connection after "+ - "commit: %v", closeErr) - } - - return nil -} - -// Rollback rolls back the bulk transaction and releases its dedicated -// connection. It is idempotent for already-closed transactions. -func (p *postgresBulkKVTx) Rollback() error { - if !p.active { - return nil - } - - err := p.tx.Rollback() - p.active = false - p.locker.Unlock() - closeErr := p.conn.Close() - if err != nil && !errors.Is(err, sql.ErrTxDone) { - return err - } - - return closeErr -} - -// postgresBulkKVVerifier is a read-only Postgres transaction for batched SQL KV -// verification. -type postgresBulkKVVerifier struct { - db *db - tx *sql.Tx - locker sync.Locker - active bool -} - -// FetchTopLevel returns all top-level rows ordered by key. -func (p *postgresBulkKVVerifier) FetchTopLevel( - ctx context.Context) ([]MigrationBulkChild, error) { - - if !p.active { - return nil, walletdb.ErrTxClosed - } - - // The table name is constructed internally from the configured prefix. - //nolint:gosec - rows, err := p.tx.QueryContext(ctx, "SELECT id, parent_id, key, "+ - "value, sequence, CASE WHEN value IS NULL THEN 1 ELSE 0 END "+ - "FROM "+p.db.table+" WHERE parent_id IS NULL ORDER BY key") - if err != nil { - return nil, err - } - defer rows.Close() - - return scanBulkChildren(rows) -} - -// FetchChildren returns direct children for parentIDs ordered by parent id and -// key. -func (p *postgresBulkKVVerifier) FetchChildren(ctx context.Context, - parentIDs []int64) ([]MigrationBulkChild, error) { - - if !p.active { - return nil, walletdb.ErrTxClosed - } - if len(parentIDs) == 0 { - return nil, nil - } - - // parentIDs is passed as a native []int64; the pgx stdlib driver - // encodes it as a Postgres bigint array for the ANY($1) match. - // - // The table name is constructed internally from the configured prefix. - //nolint:gosec - rows, err := p.tx.QueryContext(ctx, "SELECT id, parent_id, key, "+ - "value, sequence, CASE WHEN value IS NULL THEN 1 ELSE 0 END "+ - "FROM "+p.db.table+" WHERE parent_id = ANY($1) "+ - "ORDER BY parent_id, key", parentIDs) - if err != nil { - return nil, err - } - defer rows.Close() - - return scanBulkChildren(rows) -} - -// Rollback closes the verifier read transaction. -func (p *postgresBulkKVVerifier) Rollback() error { - if !p.active { - return nil - } - - err := p.tx.Rollback() - p.active = false - p.locker.Unlock() - if err != nil && !errors.Is(err, sql.ErrTxDone) { - return err - } - - return nil -} - -// scanBulkChildren scans verifier rows and preserves an explicit IsBucket flag -// so empty leaf values are not confused with SQL NULL bucket markers. -func scanBulkChildren(rows *sql.Rows) ([]MigrationBulkChild, error) { - var children []MigrationBulkChild - for rows.Next() { - var ( - child MigrationBulkChild - parentID sql.NullInt64 - sequence sql.NullInt64 - bucketFlag int - ) - if err := rows.Scan( - &child.ID, &parentID, &child.Key, &child.Value, - &sequence, &bucketFlag, - ); err != nil { - return nil, err - } - if parentID.Valid { - id := parentID.Int64 - child.ParentID = &id - } - if sequence.Valid { - child.Sequence = uint64(sequence.Int64) - } - child.IsBucket = bucketFlag == 1 - if !child.IsBucket && child.Value == nil { - child.Value = []byte{} - } - - children = append(children, child) - } - - return children, rows.Err() -} - -// cloneBulkBytes copies driver-owned byte slices before they are buffered or -// returned to callers. -func cloneBulkBytes(b []byte) []byte { - if b == nil { - return nil - } - - out := make([]byte, len(b)) - copy(out, b) - - return out -} diff --git a/kvdb/sqlbase/schema.go b/kvdb/sqlbase/schema.go index e8694469e..1ff3aefb9 100644 --- a/kvdb/sqlbase/schema.go +++ b/kvdb/sqlbase/schema.go @@ -21,7 +21,7 @@ func newKVSchemaCreationCmd(table, schema string, ) if schema != "" { finalCmd = fmt.Sprintf( - "%s", `CREATE SCHEMA IF NOT EXISTS `+schema+`;`, + `CREATE SCHEMA IF NOT EXISTS ` + schema + `;`, ) tableInSchema = fmt.Sprintf("%s.%s", schema, table) @@ -44,26 +44,26 @@ func newKVSchemaCreationCmd(table, schema string, // // The replacements map can be used to replace any sqlite keywords. // Callers should note that the sqlite keywords are case-sensitive. - finalCmd += fmt.Sprintf("%s", ` -CREATE TABLE IF NOT EXISTS `+tableInSchema+` + finalCmd += fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS ` + tableInSchema + ` ( key BLOB NOT NULL, value BLOB, parent_id BIGINT, id INTEGER PRIMARY KEY, sequence BIGINT, - CONSTRAINT `+table+`_parent FOREIGN KEY (parent_id) - REFERENCES `+tableInSchema+` (id) + CONSTRAINT ` + table + `_parent FOREIGN KEY (parent_id) + REFERENCES ` + tableInSchema + ` (id) ON UPDATE NO ACTION ON DELETE CASCADE ); -CREATE INDEX IF NOT EXISTS `+table+`_p - ON `+tableInSchema+` (parent_id); -CREATE UNIQUE INDEX IF NOT EXISTS `+table+`_up - ON `+tableInSchema+` +CREATE INDEX IF NOT EXISTS ` + table + `_p + ON ` + tableInSchema + ` (parent_id); +CREATE UNIQUE INDEX IF NOT EXISTS ` + table + `_up + ON ` + tableInSchema + ` (parent_id, key) WHERE parent_id IS NOT NULL; -CREATE UNIQUE INDEX IF NOT EXISTS `+table+`_unp - ON `+tableInSchema+` (key) WHERE parent_id IS NULL; +CREATE UNIQUE INDEX IF NOT EXISTS ` + table + `_unp + ON ` + tableInSchema + ` (key) WHERE parent_id IS NULL; `) for from, to := range replacements { diff --git a/kvdb/sqlite/db_test.go b/kvdb/sqlite/db_test.go index 74c191361..e444803f2 100644 --- a/kvdb/sqlite/db_test.go +++ b/kvdb/sqlite/db_test.go @@ -26,11 +26,6 @@ func TestInterface(t *testing.T) { sqlDB, err := NewSqliteBackend(ctx, cfg, dir, "tmp.db", "table") require.NoError(t, err) - // The regular SQLite backend must not expose migration-only - // capabilities. Migration backends must be explicitly selected. - _, ok := sqlDB.(sqlbase.MigrationBulkKVStore) - require.False(t, ok) - t.Cleanup(func() { require.NoError(t, sqlDB.Close()) }) diff --git a/lncfg/address.go b/lncfg/address.go index f96e4be22..381ff2a54 100644 --- a/lncfg/address.go +++ b/lncfg/address.go @@ -229,17 +229,6 @@ func ParseAddressString(strAddress string, defaultPort string, // an onion addresses, if so, we can directly pass the raw // address and port to create the proper address. if tor.IsOnionHost(rawHost) { - // Reject v2 at the operator-input boundary; the wire - // codec still round-trips v2 from peer-signed - // announcements. - if len(rawHost) == tor.V2Len { - return nil, fmt.Errorf("tor v2 onion "+ - "services were retired in October "+ - "2021 and are no longer supported; "+ - "use a v3 .onion address instead: %s", - rawHost) - } - portNum, err := strconv.Atoi(rawPort) if err != nil { return nil, err diff --git a/lncfg/address_test.go b/lncfg/address_test.go index d256b65a3..2066aecc3 100644 --- a/lncfg/address_test.go +++ b/lncfg/address_test.go @@ -55,15 +55,25 @@ var ( false, false, }, + { + "3g2upl4pq6kufc4m.onion", + "tcp", + "3g2upl4pq6kufc4m.onion:1234", + false, + false, + }, + { + "3g2upl4pq6kufc4m.onion:9735", + "tcp", + "3g2upl4pq6kufc4m.onion:9735", + false, + false, + }, } invalidTestVectors = []string{ "some string", "://", "12.12.12.12.12", - // v2 onion services were retired by Tor in October 2021 and - // must be rejected at the input boundary. - "3g2upl4pq6kufc4m.onion", - "3g2upl4pq6kufc4m.onion:9735", } ) diff --git a/lncfg/config.go b/lncfg/config.go index c0ab51f26..178ef203b 100644 --- a/lncfg/config.go +++ b/lncfg/config.go @@ -20,16 +20,11 @@ const ( // DefaultIncomingBroadcastDelta defines the number of blocks before the // expiry of an incoming htlc at which we force close the channel. We // only go to chain if we also have the preimage to actually pull in the - // htlc. BOLT #2 suggests 7 blocks. We use more for extra safety. - // - // The value accounts for: - // - Up to 6 blocks waiting for close tx confirmation (reorg safety) - // - Time to broadcast and confirm our sweep/2nd level success tx - // - // Within this window we need to get our sweep confirmed, because after - // that the remote party is also able to claim the htlc using the - // timeout path. - DefaultIncomingBroadcastDelta = 16 + // htlc. BOLT #2 suggests 7 blocks. We use a few more for extra safety. + // Within this window we need to get our sweep or 2nd level success tx + // confirmed, because after that the remote party is also able to claim + // the htlc using the timeout path. + DefaultIncomingBroadcastDelta = 10 // DefaultFinalCltvRejectDelta defines the number of blocks before the // expiry of an incoming exit hop htlc at which we cancel it back diff --git a/lncfg/db.go b/lncfg/db.go index 4a8680b38..5bd4d2e19 100644 --- a/lncfg/db.go +++ b/lncfg/db.go @@ -40,6 +40,9 @@ const ( DefaultBatchCommitInterval = 500 * time.Millisecond defaultPostgresMaxConnections = 50 + defaultSqliteMaxConnections = 2 + + defaultSqliteBusyTimeout = 5 * time.Second // NSChannelDB is the namespace name that we use for the combined graph // and channel state DB. @@ -89,8 +92,6 @@ type DB struct { NoGraphCache bool `long:"no-graph-cache" description:"Don't use the in-memory graph cache for path finding. Much slower but uses less RAM. Can only be used with a bolt database backend."` - SyncGraphCacheLoad bool `long:"sync-graph-cache-load" description:"Force synchronous loading of the graph cache. This will block the startup until the graph cache is fully loaded into memory. This is useful if any bugs appear with the new async loading feature of the graph cache."` - PruneRevocation bool `long:"prune-revocation" description:"Run the optional migration that prunes the revocation logs to save disk space."` NoRevLogAmtData bool `long:"no-rev-log-amt-data" description:"If set, the to-local and to-remote output amounts of revoked commitment transactions will not be stored in the revocation log. Note that once this data is lost, a watchtower client will not be able to back up the revoked state."` @@ -125,8 +126,8 @@ func DefaultDB() *DB { QueryConfig: *sqldb.DefaultPostgresConfig(), }, Sqlite: &sqldb.SqliteConfig{ - MaxConnections: sqldb.DefaultSqliteMaxConns, - BusyTimeout: sqldb.DefaultSqliteBusyTimeout, + MaxConnections: defaultSqliteMaxConnections, + BusyTimeout: defaultSqliteBusyTimeout, QueryConfig: *sqldb.DefaultSQLiteConfig(), }, UseNativeSQL: false, @@ -206,7 +207,7 @@ func (db *DB) Init(ctx context.Context, dbPath string) error { sqlbase.Init(db.Postgres.MaxConnections) case db.Backend == SqliteBackend: - sqlbase.Init(db.Sqlite.MaxConns()) + sqlbase.Init(db.Sqlite.MaxConnections) } return nil diff --git a/lncfg/dev.go b/lncfg/dev.go index 15c9367cb..f048d69b7 100644 --- a/lncfg/dev.go +++ b/lncfg/dev.go @@ -5,7 +5,6 @@ package lncfg import ( "time" - "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwallet/chanfunding" ) @@ -59,15 +58,3 @@ func (d *DevConfig) GetMaxWaitNumBlocksFundingConf() uint32 { func (d *DevConfig) GetUnsafeConnect() bool { return false } - -// GetMinFwdHistoryAge returns 0 for production builds, causing the caller to -// use the hardcoded default of 1h. -func (d *DevConfig) GetMinFwdHistoryAge() time.Duration { - return 0 -} - -// ChannelCloseConfs returns the config value for channel close confirmations -// override, which is always None for production build. -func (d *DevConfig) ChannelCloseConfs() fn.Option[uint32] { - return fn.None[uint32]() -} diff --git a/lncfg/dev_integration.go b/lncfg/dev_integration.go index 3793e7c46..8ac85f5d9 100644 --- a/lncfg/dev_integration.go +++ b/lncfg/dev_integration.go @@ -5,7 +5,6 @@ package lncfg import ( "time" - "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwallet/chanfunding" ) @@ -28,8 +27,6 @@ type DevConfig struct { UnsafeDisconnect bool `long:"unsafedisconnect" description:"Allows the rpcserver to intentionally disconnect from peers with open channels."` MaxWaitNumBlocksFundingConf uint32 `long:"maxwaitnumblocksfundingconf" description:"Maximum blocks to wait for funding confirmation before discarding non-initiated channels."` UnsafeConnect bool `long:"unsafeconnect" description:"Allow the rpcserver to connect to a peer even if there's already a connection."` - ForceChannelCloseConfs uint32 `long:"force-channel-close-confs" description:"Force a specific number of confirmations for channel closes (dev/test only)"` - MinFwdHistoryAge time.Duration `long:"min-fwd-history-age" description:"Minimum age of forwarding events before they can be deleted via DeleteForwardingHistory (dev/test only, default: 1h)"` } // ChannelReadyWait returns the config value `ProcessChannelReadyWait`. @@ -74,19 +71,3 @@ func (d *DevConfig) GetMaxWaitNumBlocksFundingConf() uint32 { func (d *DevConfig) GetUnsafeConnect() bool { return d.UnsafeConnect } - -// GetMinFwdHistoryAge returns the minimum age for forwarding history deletion. -// Returns 0 if unset, which causes the caller to use the default (1h). -func (d *DevConfig) GetMinFwdHistoryAge() time.Duration { - return d.MinFwdHistoryAge -} - -// ChannelCloseConfs returns the forced confirmation count if set, or None if -// the default behavior should be used. -func (d *DevConfig) ChannelCloseConfs() fn.Option[uint32] { - if d.ForceChannelCloseConfs == 0 { - return fn.None[uint32]() - } - - return fn.Some(d.ForceChannelCloseConfs) -} diff --git a/lncfg/neutrino.go b/lncfg/neutrino.go index 62c8b247c..d0f508ecf 100644 --- a/lncfg/neutrino.go +++ b/lncfg/neutrino.go @@ -1,9 +1,6 @@ package lncfg -import ( - "fmt" - "time" -) +import "time" // Neutrino holds the configuration options for the daemon's connection to // neutrino. @@ -21,21 +18,4 @@ type Neutrino struct { ValidateChannels bool `long:"validatechannels" description:"Validate every channel in the graph during sync by downloading the containing block. This is the inverse of routing.assumechanvalid, meaning that for Neutrino the validation is turned off by default for massively increased graph sync performance. This speedup comes at the risk of using an unvalidated view of the network for routing. Overwrites the value of routing.assumechanvalid if Neutrino is used. (default: false)"` BroadcastTimeout time.Duration `long:"broadcasttimeout" description:"The amount of time to wait before giving up on a transaction broadcast attempt."` PersistFilters bool `long:"persistfilters" description:"Whether compact filters fetched from the P2P network should be persisted to disk."` - - BlockHeadersSource string `long:"blockheaderssource" description:"Source for importing block headers on startup for fast initial sync. Can be a local file path or HTTP(S) URL (e.g., https://block-dn.org/headers/import/800000). When set, neutrino imports headers from this source before P2P sync."` - FilterHeadersSource string `long:"filterheaderssource" description:"Source for importing filter headers on startup for fast initial sync. Can be a local file path or HTTP(S) URL (e.g., https://block-dn.org/filter-headers/import/800000). Must be set together with blockheaderssource."` -} - -// Validate checks the neutrino configuration for consistency. -func (n *Neutrino) Validate() error { - blockSet := n.BlockHeadersSource != "" - filterSet := n.FilterHeadersSource != "" - - if blockSet != filterSet { - return fmt.Errorf("both neutrino.blockheaderssource and " + - "neutrino.filterheaderssource must be specified " + - "together for headers import") - } - - return nil } diff --git a/lncfg/protocol.go b/lncfg/protocol.go index e95e3cf4c..4d348b215 100644 --- a/lncfg/protocol.go +++ b/lncfg/protocol.go @@ -37,7 +37,7 @@ type ProtocolOptions struct { // RbfCoopClose should be set if we want to signal that we support for // the new experimental RBF coop close feature. - RbfCoopClose bool `long:"rbf-coop-close" description:"if set, then lnd will signal that it supports the new RBF based coop close protocol"` + RbfCoopClose bool `long:"rbf-coop-close" description:"if set, then lnd will signal that it supports the new RBF based coop close protocol, taproot channels are not supported"` // NoAnchors should be set if we don't want to support opening or accepting // channels having the anchor commitment type. @@ -71,49 +71,8 @@ type ProtocolOptions struct { // NoRouteBlindingOption disables forwarding of payments in blinded routes. NoRouteBlindingOption bool `long:"no-route-blinding" description:"do not forward payments that are a part of a blinded route"` - // NoOnionMessagesOption disables onion message forwarding. - NoOnionMessagesOption bool `long:"no-onion-messages" description:"disable support for onion messaging"` - - // OnionMsgPeerKbps is the maximum sustained onion message ingress - // bandwidth, in decimal kilobits per second (1 Kbps = 1000 bits/s), - // that will be accepted from any single peer. Setting this to zero, - // together with a zero burst, disables the per-peer onion message - // rate limiter. - OnionMsgPeerKbps uint64 `long:"onion-msg-peer-kbps" description:"max onion message ingress rate from a single peer, in decimal kilobits per second; set both this and onion-msg-peer-burst-bytes to 0 to disable the per-peer limiter"` - - // OnionMsgPeerBurstBytes is the token bucket depth, in bytes, used - // by the per-peer onion message rate limiter. A value of zero, - // paired with a zero rate, disables the per-peer limiter. - OnionMsgPeerBurstBytes uint64 `long:"onion-msg-peer-burst-bytes" description:"token bucket burst for the per-peer onion message limiter, in bytes; set both this and onion-msg-peer-kbps to 0 to disable the per-peer limiter"` - - // OnionMsgGlobalKbps is the maximum sustained onion message ingress - // bandwidth, in decimal kilobits per second, that will be accepted - // across all peers combined. Setting this to zero, together with a - // zero burst, disables the global onion message rate limiter. - OnionMsgGlobalKbps uint64 `long:"onion-msg-global-kbps" description:"max onion message ingress rate across all peers combined, in decimal kilobits per second; set both this and onion-msg-global-burst-bytes to 0 to disable the global limiter"` - - // OnionMsgGlobalBurstBytes is the token bucket depth, in bytes, used - // by the global onion message rate limiter. A value of zero, paired - // with a zero rate, disables the global limiter. - OnionMsgGlobalBurstBytes uint64 `long:"onion-msg-global-burst-bytes" description:"token bucket burst for the global onion message limiter, in bytes; set both this and onion-msg-global-kbps to 0 to disable the global limiter"` - - // OnionMsgRelayAll disables the channel-presence gate on the onion - // message ingress path. When false (the default), incoming onion - // messages from peers that do not have at least one fully open - // channel with us are dropped before the rate limiters are - // consulted: without a funded channel, a new peer identity is free - // and the global rate limiter alone is easy to saturate. Setting - // this to true admits onion messages from any peer into the - // limiter pipeline, at the cost of that Sybil-resistance property. - OnionMsgRelayAll bool `long:"onion-msg-relay-all" description:"accept incoming onion messages from peers with no fully open channel; by default only peers with at least one active channel are admitted to the onion message ingress path"` - - // NoExperimentalAccountabilityOption disables experimental accountability. - NoExperimentalAccountabilityOption bool `long:"no-experimental-accountability" description:"do not forward experimental accountability signals"` - - // NoExperimentalEndorsementOption is the deprecated name for - // NoExperimentalAccountabilityOption. It is hidden and will be removed - // in a future release. - NoExperimentalEndorsementOption bool `long:"no-experimental-endorsement" hidden:"true" description:"deprecated: use no-experimental-accountability instead"` + // NoExperimentalEndorsementOption disables experimental endorsement. + NoExperimentalEndorsementOption bool `long:"no-experimental-endorsement" description:"do not forward experimental endorsement signals"` // CustomMessage allows the custom message APIs to handle messages with // the provided protocol numbers, which fall outside the custom message @@ -180,17 +139,10 @@ func (l *ProtocolOptions) NoRouteBlinding() bool { return l.NoRouteBlindingOption } -// NoOnionMessages returns true if onion messaging is disabled. -func (l *ProtocolOptions) NoOnionMessages() bool { - return l.NoOnionMessagesOption -} - -// NoExpAccountability returns true if experimental accountability should be -// disabled. It also checks the deprecated NoExperimentalEndorsementOption for -// backwards compatibility. -func (l *ProtocolOptions) NoExpAccountability() bool { - return l.NoExperimentalAccountabilityOption || - l.NoExperimentalEndorsementOption +// NoExperimentalEndorsement returns true if experimental endorsement should +// be disabled. +func (l *ProtocolOptions) NoExperimentalEndorsement() bool { + return l.NoExperimentalEndorsementOption } // NoQuiescence returns true if quiescence is disabled. diff --git a/lncfg/protocol_integration.go b/lncfg/protocol_integration.go index 4fcaa642c..c68d6ffe6 100644 --- a/lncfg/protocol_integration.go +++ b/lncfg/protocol_integration.go @@ -74,49 +74,8 @@ type ProtocolOptions struct { // NoRouteBlindingOption disables forwarding of payments in blinded routes. NoRouteBlindingOption bool `long:"no-route-blinding" description:"do not forward payments that are a part of a blinded route"` - // NoOnionMessagesOption disables onion message forwarding. - NoOnionMessagesOption bool `long:"no-onion-messages" description:"disable support for onion messaging"` - - // OnionMsgPeerKbps is the maximum sustained onion message ingress - // bandwidth, in decimal kilobits per second (1 Kbps = 1000 bits/s), - // that will be accepted from any single peer. Setting this to zero, - // together with a zero burst, disables the per-peer onion message - // rate limiter. - OnionMsgPeerKbps uint64 `long:"onion-msg-peer-kbps" description:"max onion message ingress rate from a single peer, in decimal kilobits per second; set both this and onion-msg-peer-burst-bytes to 0 to disable the per-peer limiter"` - - // OnionMsgPeerBurstBytes is the token bucket depth, in bytes, used - // by the per-peer onion message rate limiter. A value of zero, - // paired with a zero rate, disables the per-peer limiter. - OnionMsgPeerBurstBytes uint64 `long:"onion-msg-peer-burst-bytes" description:"token bucket burst for the per-peer onion message limiter, in bytes; set both this and onion-msg-peer-kbps to 0 to disable the per-peer limiter"` - - // OnionMsgGlobalKbps is the maximum sustained onion message ingress - // bandwidth, in decimal kilobits per second, that will be accepted - // across all peers combined. Setting this to zero, together with a - // zero burst, disables the global onion message rate limiter. - OnionMsgGlobalKbps uint64 `long:"onion-msg-global-kbps" description:"max onion message ingress rate across all peers combined, in decimal kilobits per second; set both this and onion-msg-global-burst-bytes to 0 to disable the global limiter"` - - // OnionMsgGlobalBurstBytes is the token bucket depth, in bytes, used - // by the global onion message rate limiter. A value of zero, paired - // with a zero rate, disables the global limiter. - OnionMsgGlobalBurstBytes uint64 `long:"onion-msg-global-burst-bytes" description:"token bucket burst for the global onion message limiter, in bytes; set both this and onion-msg-global-kbps to 0 to disable the global limiter"` - - // OnionMsgRelayAll disables the channel-presence gate on the onion - // message ingress path. When false (the default), incoming onion - // messages from peers that do not have at least one fully open - // channel with us are dropped before the rate limiters are - // consulted: without a funded channel, a new peer identity is free - // and the global rate limiter alone is easy to saturate. Setting - // this to true admits onion messages from any peer into the - // limiter pipeline, at the cost of that Sybil-resistance property. - OnionMsgRelayAll bool `long:"onion-msg-relay-all" description:"accept incoming onion messages from peers with no fully open channel; by default only peers with at least one active channel are admitted to the onion message ingress path"` - - // NoExperimentalAccountabilityOption disables experimental accountability. - NoExperimentalAccountabilityOption bool `long:"no-experimental-accountability" description:"do not forward experimental accountability signals"` - - // NoExperimentalEndorsementOption is the deprecated name for - // NoExperimentalAccountabilityOption. It is hidden and will be removed - // in a future release. - NoExperimentalEndorsementOption bool `long:"no-experimental-endorsement" hidden:"true" description:"deprecated: use no-experimental-accountability instead"` + // NoExperimentalEndorsementOption disables experimental endorsement. + NoExperimentalEndorsementOption bool `long:"no-experimental-endorsement" description:"do not forward experimental endorsement signals"` // NoQuiescenceOption disables quiescence for all channels. NoQuiescenceOption bool `long:"no-quiescence" description:"do not allow or advertise quiescence for any channel"` @@ -178,17 +137,10 @@ func (l *ProtocolOptions) NoRouteBlinding() bool { return l.NoRouteBlindingOption } -// NoOnionMessages returns true if onion messaging is disabled. -func (l *ProtocolOptions) NoOnionMessages() bool { - return l.NoOnionMessagesOption -} - -// NoExpAccountability returns true if experimental accountability should be -// disabled. It also checks the deprecated NoExperimentalEndorsementOption for -// backwards compatibility. -func (l *ProtocolOptions) NoExpAccountability() bool { - return l.NoExperimentalAccountabilityOption || - l.NoExperimentalEndorsementOption +// NoExperimentalEndorsement returns true if experimental endorsement should +// be disabled. +func (l *ProtocolOptions) NoExperimentalEndorsement() bool { + return l.NoExperimentalEndorsementOption } // NoQuiescence returns true if quiescence is disabled. diff --git a/lncfg/tor.go b/lncfg/tor.go index 81a24cd63..932d5dfc9 100644 --- a/lncfg/tor.go +++ b/lncfg/tor.go @@ -12,6 +12,7 @@ type Tor struct { Control string `long:"control" description:"The host:port that Tor is listening on for Tor control connections"` TargetIPAddress string `long:"targetipaddress" description:"IP address that Tor should use as the target of the hidden service"` Password string `long:"password" description:"The password used to arrive at the HashedControlPassword for the control port. If provided, the HASHEDPASSWORD authentication method will be used instead of the SAFECOOKIE one."` + V2 bool `long:"v2" description:"DEPRECATED: Tor v2 onion services are obsolete and support will be removed in v0.21.0. Use v3 instead." hidden:"true"` V3 bool `long:"v3" description:"Automatically set up a v3 onion service to listen for inbound connections"` PrivateKeyPath string `long:"privatekeypath" description:"The path to the private key of the onion service being created"` EncryptKey bool `long:"encryptkey" description:"Encrypts the Tor private key file on disk"` diff --git a/lnd.go b/lnd.go index ac42e7cc2..76b08a114 100644 --- a/lnd.go +++ b/lnd.go @@ -19,7 +19,7 @@ import ( "sync" "time" - "github.com/btcsuite/btcd/address/v2" + "github.com/btcsuite/btcd/btcutil" proxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/lightningnetwork/lnd/autopilot" "github.com/lightningnetwork/lnd/build" @@ -530,11 +530,11 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, } } - // If tor is active and a v3 onion service has been specified, make a - // tor controller and pass it into both the watchtower server and the - // regular lnd server. + // If tor is active and either v2 or v3 onion services have been + // specified, make a tor controller and pass it into both the watchtower + // server and the regular lnd server. var torController *tor.Controller - if cfg.Tor.Active && cfg.Tor.V3 { + if cfg.Tor.Active && (cfg.Tor.V2 || cfg.Tor.V3) { torController = tor.NewController( cfg.Tor.Control, cfg.Tor.TargetIPAddress, cfg.Tor.Password, @@ -571,7 +571,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, DB: dbs.TowerServerDB, EpochRegistrar: activeChainControl.ChainNotifier, Net: cfg.net, - NewAddress: func() (address.Address, error) { + NewAddress: func() (btcutil.Address, error) { return activeChainControl.Wallet.NewAddress( lnwallet.TaprootPubkey, false, lnwallet.DefaultAccountName, @@ -591,6 +591,13 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, wtCfg.WatchtowerKeyPath = cfg.Tor.WatchtowerKeyPath wtCfg.EncryptKey = cfg.Tor.EncryptKey wtCfg.KeyRing = activeChainControl.KeyRing + + switch { + case cfg.Tor.V2: + wtCfg.Type = tor.V2 + case cfg.Tor.V3: + wtCfg.Type = tor.V3 + } } wtConfig, err := cfg.Watchtower.Apply( diff --git a/lnencrypt/crypto_test.go b/lnencrypt/crypto_test.go index dfe224f63..42ebe1cc2 100644 --- a/lnencrypt/crypto_test.go +++ b/lnencrypt/crypto_test.go @@ -66,7 +66,9 @@ func TestEncryptDecryptPayload(t *testing.T) { require.NoError(t, err) for _, payloadCase := range payloadCases { + payloadCase := payloadCase for _, enc := range []*Encrypter{keyRingEnc, privKeyEnc} { + enc := enc // First, we'll encrypt the passed payload with our // scheme. diff --git a/lnmock/chain.go b/lnmock/chain.go index 84fda0030..1470513b6 100644 --- a/lnmock/chain.go +++ b/lnmock/chain.go @@ -1,12 +1,10 @@ package lnmock import ( - "context" - - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/stretchr/testify/mock" @@ -20,7 +18,7 @@ type MockChain struct { // Compile-time constraint to ensure MockChain implements the Chain interface. var _ chain.Interface = (*MockChain)(nil) -func (m *MockChain) Start(_ context.Context) error { +func (m *MockChain) Start() error { args := m.Called() return args.Error(0) @@ -128,15 +126,15 @@ func (m *MockChain) SendRawTransaction(tx *wire.MsgTx, allowHighFees bool) ( return args.Get(0).(*chainhash.Hash), args.Error(1) } -func (m *MockChain) Rescan(startHash *chainhash.Hash, addrs []address.Address, - outPoints map[wire.OutPoint]address.Address) error { +func (m *MockChain) Rescan(startHash *chainhash.Hash, addrs []btcutil.Address, + outPoints map[wire.OutPoint]btcutil.Address) error { args := m.Called(startHash, addrs, outPoints) return args.Error(0) } -func (m *MockChain) NotifyReceived(addrs []address.Address) error { +func (m *MockChain) NotifyReceived(addrs []btcutil.Address) error { args := m.Called(addrs) return args.Error(0) @@ -172,19 +170,6 @@ func (m *MockChain) TestMempoolAccept(txns []*wire.MsgTx, maxFeeRate float64) ( return args.Get(0).([]*btcjson.TestMempoolAcceptResult), args.Error(1) } -// SubmitPackage is a mock implementation of the chain.Interface method. -func (m *MockChain) SubmitPackage(txns []*wire.MsgTx, - maxFeeRate *float64) (*btcjson.SubmitPackageResult, error) { - - args := m.Called(txns, maxFeeRate) - - if args.Get(0) == nil { - return nil, args.Error(1) - } - - return args.Get(0).(*btcjson.SubmitPackageResult), args.Error(1) -} - func (m *MockChain) MapRPCErr(err error) error { args := m.Called(err) diff --git a/lnpeer/mock_peer.go b/lnpeer/mock_peer.go index 35ba8041e..7fed3e4a0 100644 --- a/lnpeer/mock_peer.go +++ b/lnpeer/mock_peer.go @@ -4,7 +4,7 @@ import ( "net" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/mock" ) diff --git a/lnpeer/peer.go b/lnpeer/peer.go index ef18ddab7..cb6bc9867 100644 --- a/lnpeer/peer.go +++ b/lnpeer/peer.go @@ -4,8 +4,8 @@ import ( "net" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" ) @@ -14,7 +14,7 @@ import ( // with the set of channel options that may change how the channel is created. // This can be used to pass along the nonce state needed for taproot channels. type NewChannel struct { - *chanstate.OpenChannel + *channeldb.OpenChannel // ChanOpts can be used to change how the channel is created. ChanOpts []lnwallet.ChannelOpt diff --git a/lnrpc/Dockerfile b/lnrpc/Dockerfile index 8d24e37f1..680a774e8 100644 --- a/lnrpc/Dockerfile +++ b/lnrpc/Dockerfile @@ -1,6 +1,6 @@ # If you change this please also update GO_VERSION in Makefile (then run # `make lint` to see where else it needs to be updated as well). -FROM golang:1.26.4-bookworm +FROM golang:1.25.5-bookworm RUN apt-get update && apt-get install -y \ git \ diff --git a/lnrpc/README.md b/lnrpc/README.md index 50a6e2361..b504525f1 100644 --- a/lnrpc/README.md +++ b/lnrpc/README.md @@ -69,6 +69,15 @@ description): * Attempts to close a target channel. A channel can either be closed cooperatively if the channel peer is online, or using a "force" close to broadcast the latest channel state. + * SendPayment + * Send a payment over Lightning to a target peer. + * SendPaymentSync + * SendPaymentSync is the synchronous non-streaming version of SendPayment. + * SendToRoute + * Send a payment over Lightning to a target peer through a route explicitly + defined by the user. + * SendToRouteSync + * SendToRouteSync is the synchronous non-streaming version of SendToRoute. * AddInvoice * Adds an invoice to the daemon. Invoices are automatically settled once seen as an incoming HTLC. diff --git a/lnrpc/autopilotrpc/autopilot.pb.go b/lnrpc/autopilotrpc/autopilot.pb.go index 71792cee9..7de3be54f 100644 --- a/lnrpc/autopilotrpc/autopilot.pb.go +++ b/lnrpc/autopilotrpc/autopilot.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.33.0 // protoc v3.21.12 // source: autopilotrpc/autopilot.proto @@ -11,7 +11,6 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" - unsafe "unsafe" ) const ( @@ -22,16 +21,18 @@ const ( ) type StatusRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *StatusRequest) Reset() { *x = StatusRequest{} - mi := &file_autopilotrpc_autopilot_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_autopilotrpc_autopilot_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *StatusRequest) String() string { @@ -42,7 +43,7 @@ func (*StatusRequest) ProtoMessage() {} func (x *StatusRequest) ProtoReflect() protoreflect.Message { mi := &file_autopilotrpc_autopilot_proto_msgTypes[0] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -58,18 +59,21 @@ func (*StatusRequest) Descriptor() ([]byte, []int) { } type StatusResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Indicates whether the autopilot is active or not. - Active bool `protobuf:"varint,1,opt,name=active,proto3" json:"active,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Indicates whether the autopilot is active or not. + Active bool `protobuf:"varint,1,opt,name=active,proto3" json:"active,omitempty"` } func (x *StatusResponse) Reset() { *x = StatusResponse{} - mi := &file_autopilotrpc_autopilot_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_autopilotrpc_autopilot_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *StatusResponse) String() string { @@ -80,7 +84,7 @@ func (*StatusResponse) ProtoMessage() {} func (x *StatusResponse) ProtoReflect() protoreflect.Message { mi := &file_autopilotrpc_autopilot_proto_msgTypes[1] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -103,18 +107,21 @@ func (x *StatusResponse) GetActive() bool { } type ModifyStatusRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Whether the autopilot agent should be enabled or not. - Enable bool `protobuf:"varint,1,opt,name=enable,proto3" json:"enable,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Whether the autopilot agent should be enabled or not. + Enable bool `protobuf:"varint,1,opt,name=enable,proto3" json:"enable,omitempty"` } func (x *ModifyStatusRequest) Reset() { *x = ModifyStatusRequest{} - mi := &file_autopilotrpc_autopilot_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_autopilotrpc_autopilot_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ModifyStatusRequest) String() string { @@ -125,7 +132,7 @@ func (*ModifyStatusRequest) ProtoMessage() {} func (x *ModifyStatusRequest) ProtoReflect() protoreflect.Message { mi := &file_autopilotrpc_autopilot_proto_msgTypes[2] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -148,16 +155,18 @@ func (x *ModifyStatusRequest) GetEnable() bool { } type ModifyStatusResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *ModifyStatusResponse) Reset() { *x = ModifyStatusResponse{} - mi := &file_autopilotrpc_autopilot_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_autopilotrpc_autopilot_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ModifyStatusResponse) String() string { @@ -168,7 +177,7 @@ func (*ModifyStatusResponse) ProtoMessage() {} func (x *ModifyStatusResponse) ProtoReflect() protoreflect.Message { mi := &file_autopilotrpc_autopilot_proto_msgTypes[3] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -184,19 +193,22 @@ func (*ModifyStatusResponse) Descriptor() ([]byte, []int) { } type QueryScoresRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Pubkeys []string `protobuf:"bytes,1,rep,name=pubkeys,proto3" json:"pubkeys,omitempty"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Pubkeys []string `protobuf:"bytes,1,rep,name=pubkeys,proto3" json:"pubkeys,omitempty"` // If set, we will ignore the local channel state when calculating scores. IgnoreLocalState bool `protobuf:"varint,2,opt,name=ignore_local_state,json=ignoreLocalState,proto3" json:"ignore_local_state,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *QueryScoresRequest) Reset() { *x = QueryScoresRequest{} - mi := &file_autopilotrpc_autopilot_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_autopilotrpc_autopilot_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *QueryScoresRequest) String() string { @@ -207,7 +219,7 @@ func (*QueryScoresRequest) ProtoMessage() {} func (x *QueryScoresRequest) ProtoReflect() protoreflect.Message { mi := &file_autopilotrpc_autopilot_proto_msgTypes[4] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -237,17 +249,20 @@ func (x *QueryScoresRequest) GetIgnoreLocalState() bool { } type QueryScoresResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Results []*QueryScoresResponse_HeuristicResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Results []*QueryScoresResponse_HeuristicResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` } func (x *QueryScoresResponse) Reset() { *x = QueryScoresResponse{} - mi := &file_autopilotrpc_autopilot_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_autopilotrpc_autopilot_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *QueryScoresResponse) String() string { @@ -258,7 +273,7 @@ func (*QueryScoresResponse) ProtoMessage() {} func (x *QueryScoresResponse) ProtoReflect() protoreflect.Message { mi := &file_autopilotrpc_autopilot_proto_msgTypes[5] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -281,21 +296,24 @@ func (x *QueryScoresResponse) GetResults() []*QueryScoresResponse_HeuristicResul } type SetScoresRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The name of the heuristic to provide scores to. Heuristic string `protobuf:"bytes,1,opt,name=heuristic,proto3" json:"heuristic,omitempty"` // A map from hex-encoded public keys to scores. Scores must be in the range // [0.0, 1.0]. - Scores map[string]float64 `protobuf:"bytes,2,rep,name=scores,proto3" json:"scores,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"fixed64,2,opt,name=value"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Scores map[string]float64 `protobuf:"bytes,2,rep,name=scores,proto3" json:"scores,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"fixed64,2,opt,name=value,proto3"` } func (x *SetScoresRequest) Reset() { *x = SetScoresRequest{} - mi := &file_autopilotrpc_autopilot_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_autopilotrpc_autopilot_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SetScoresRequest) String() string { @@ -306,7 +324,7 @@ func (*SetScoresRequest) ProtoMessage() {} func (x *SetScoresRequest) ProtoReflect() protoreflect.Message { mi := &file_autopilotrpc_autopilot_proto_msgTypes[6] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -336,16 +354,18 @@ func (x *SetScoresRequest) GetScores() map[string]float64 { } type SetScoresResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *SetScoresResponse) Reset() { *x = SetScoresResponse{} - mi := &file_autopilotrpc_autopilot_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_autopilotrpc_autopilot_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SetScoresResponse) String() string { @@ -356,7 +376,7 @@ func (*SetScoresResponse) ProtoMessage() {} func (x *SetScoresResponse) ProtoReflect() protoreflect.Message { mi := &file_autopilotrpc_autopilot_proto_msgTypes[7] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -372,18 +392,21 @@ func (*SetScoresResponse) Descriptor() ([]byte, []int) { } type QueryScoresResponse_HeuristicResult struct { - state protoimpl.MessageState `protogen:"open.v1"` - Heuristic string `protobuf:"bytes,1,opt,name=heuristic,proto3" json:"heuristic,omitempty"` - Scores map[string]float64 `protobuf:"bytes,2,rep,name=scores,proto3" json:"scores,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"fixed64,2,opt,name=value"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Heuristic string `protobuf:"bytes,1,opt,name=heuristic,proto3" json:"heuristic,omitempty"` + Scores map[string]float64 `protobuf:"bytes,2,rep,name=scores,proto3" json:"scores,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"fixed64,2,opt,name=value,proto3"` } func (x *QueryScoresResponse_HeuristicResult) Reset() { *x = QueryScoresResponse_HeuristicResult{} - mi := &file_autopilotrpc_autopilot_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_autopilotrpc_autopilot_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *QueryScoresResponse_HeuristicResult) String() string { @@ -394,7 +417,7 @@ func (*QueryScoresResponse_HeuristicResult) ProtoMessage() {} func (x *QueryScoresResponse_HeuristicResult) ProtoReflect() protoreflect.Message { mi := &file_autopilotrpc_autopilot_proto_msgTypes[8] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -425,53 +448,96 @@ func (x *QueryScoresResponse_HeuristicResult) GetScores() map[string]float64 { var File_autopilotrpc_autopilot_proto protoreflect.FileDescriptor -const file_autopilotrpc_autopilot_proto_rawDesc = "" + - "\n" + - "\x1cautopilotrpc/autopilot.proto\x12\fautopilotrpc\"\x0f\n" + - "\rStatusRequest\"(\n" + - "\x0eStatusResponse\x12\x16\n" + - "\x06active\x18\x01 \x01(\bR\x06active\"-\n" + - "\x13ModifyStatusRequest\x12\x16\n" + - "\x06enable\x18\x01 \x01(\bR\x06enable\"\x16\n" + - "\x14ModifyStatusResponse\"\\\n" + - "\x12QueryScoresRequest\x12\x18\n" + - "\apubkeys\x18\x01 \x03(\tR\apubkeys\x12,\n" + - "\x12ignore_local_state\x18\x02 \x01(\bR\x10ignoreLocalState\"\xa6\x02\n" + - "\x13QueryScoresResponse\x12K\n" + - "\aresults\x18\x01 \x03(\v21.autopilotrpc.QueryScoresResponse.HeuristicResultR\aresults\x1a\xc1\x01\n" + - "\x0fHeuristicResult\x12\x1c\n" + - "\theuristic\x18\x01 \x01(\tR\theuristic\x12U\n" + - "\x06scores\x18\x02 \x03(\v2=.autopilotrpc.QueryScoresResponse.HeuristicResult.ScoresEntryR\x06scores\x1a9\n" + - "\vScoresEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\x01R\x05value:\x028\x01\"\xaf\x01\n" + - "\x10SetScoresRequest\x12\x1c\n" + - "\theuristic\x18\x01 \x01(\tR\theuristic\x12B\n" + - "\x06scores\x18\x02 \x03(\v2*.autopilotrpc.SetScoresRequest.ScoresEntryR\x06scores\x1a9\n" + - "\vScoresEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\x01R\x05value:\x028\x01\"\x13\n" + - "\x11SetScoresResponse2\xc9\x02\n" + - "\tAutopilot\x12C\n" + - "\x06Status\x12\x1b.autopilotrpc.StatusRequest\x1a\x1c.autopilotrpc.StatusResponse\x12U\n" + - "\fModifyStatus\x12!.autopilotrpc.ModifyStatusRequest\x1a\".autopilotrpc.ModifyStatusResponse\x12R\n" + - "\vQueryScores\x12 .autopilotrpc.QueryScoresRequest\x1a!.autopilotrpc.QueryScoresResponse\x12L\n" + - "\tSetScores\x12\x1e.autopilotrpc.SetScoresRequest\x1a\x1f.autopilotrpc.SetScoresResponseB4Z2github.com/lightningnetwork/lnd/lnrpc/autopilotrpcb\x06proto3" +var file_autopilotrpc_autopilot_proto_rawDesc = []byte{ + 0x0a, 0x1c, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x61, + 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, + 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x72, 0x70, 0x63, 0x22, 0x0f, 0x0a, 0x0d, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x28, 0x0a, + 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x22, 0x2d, 0x0a, 0x13, 0x4d, 0x6f, 0x64, 0x69, 0x66, + 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, + 0x0a, 0x06, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, + 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5c, + 0x0a, 0x12, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x12, 0x2c, + 0x0a, 0x12, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x69, 0x67, 0x6e, 0x6f, + 0x72, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x22, 0xa6, 0x02, 0x0a, + 0x13, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x65, 0x75, 0x72, 0x69, 0x73, 0x74, + 0x69, 0x63, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x73, 0x1a, 0xc1, 0x01, 0x0a, 0x0f, 0x48, 0x65, 0x75, 0x72, 0x69, 0x73, 0x74, 0x69, 0x63, 0x52, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x68, 0x65, 0x75, 0x72, 0x69, 0x73, 0x74, + 0x69, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x68, 0x65, 0x75, 0x72, 0x69, 0x73, + 0x74, 0x69, 0x63, 0x12, 0x55, 0x0a, 0x06, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x65, 0x75, 0x72, 0x69, 0x73, 0x74, 0x69, 0x63, + 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x06, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x53, 0x63, + 0x6f, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xaf, 0x01, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x53, 0x63, 0x6f, + 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x68, 0x65, + 0x75, 0x72, 0x69, 0x73, 0x74, 0x69, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x68, + 0x65, 0x75, 0x72, 0x69, 0x73, 0x74, 0x69, 0x63, 0x12, 0x42, 0x0a, 0x06, 0x73, 0x63, 0x6f, 0x72, + 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, + 0x69, 0x6c, 0x6f, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x53, 0x63, 0x6f, 0x72, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x1a, 0x39, 0x0a, 0x0b, + 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x13, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x53, 0x63, + 0x6f, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xc9, 0x02, 0x0a, + 0x09, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x12, 0x43, 0x0a, 0x06, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1c, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x55, 0x0a, 0x0c, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x21, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4d, + 0x6f, 0x64, 0x69, 0x66, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, + 0x63, 0x6f, 0x72, 0x65, 0x73, 0x12, 0x20, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, + 0x6c, 0x6f, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x63, 0x6f, 0x72, + 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x09, 0x53, 0x65, + 0x74, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x12, 0x1e, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, + 0x6c, 0x6f, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, + 0x6c, 0x6f, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, + 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2f, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} var ( file_autopilotrpc_autopilot_proto_rawDescOnce sync.Once - file_autopilotrpc_autopilot_proto_rawDescData []byte + file_autopilotrpc_autopilot_proto_rawDescData = file_autopilotrpc_autopilot_proto_rawDesc ) func file_autopilotrpc_autopilot_proto_rawDescGZIP() []byte { file_autopilotrpc_autopilot_proto_rawDescOnce.Do(func() { - file_autopilotrpc_autopilot_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_autopilotrpc_autopilot_proto_rawDesc), len(file_autopilotrpc_autopilot_proto_rawDesc))) + file_autopilotrpc_autopilot_proto_rawDescData = protoimpl.X.CompressGZIP(file_autopilotrpc_autopilot_proto_rawDescData) }) return file_autopilotrpc_autopilot_proto_rawDescData } var file_autopilotrpc_autopilot_proto_msgTypes = make([]protoimpl.MessageInfo, 11) -var file_autopilotrpc_autopilot_proto_goTypes = []any{ +var file_autopilotrpc_autopilot_proto_goTypes = []interface{}{ (*StatusRequest)(nil), // 0: autopilotrpc.StatusRequest (*StatusResponse)(nil), // 1: autopilotrpc.StatusResponse (*ModifyStatusRequest)(nil), // 2: autopilotrpc.ModifyStatusRequest @@ -508,11 +574,121 @@ func file_autopilotrpc_autopilot_proto_init() { if File_autopilotrpc_autopilot_proto != nil { return } + if !protoimpl.UnsafeEnabled { + file_autopilotrpc_autopilot_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatusRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotrpc_autopilot_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatusResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotrpc_autopilot_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModifyStatusRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotrpc_autopilot_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModifyStatusResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotrpc_autopilot_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*QueryScoresRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotrpc_autopilot_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*QueryScoresResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotrpc_autopilot_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetScoresRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotrpc_autopilot_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetScoresResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotrpc_autopilot_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*QueryScoresResponse_HeuristicResult); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_autopilotrpc_autopilot_proto_rawDesc), len(file_autopilotrpc_autopilot_proto_rawDesc)), + RawDescriptor: file_autopilotrpc_autopilot_proto_rawDesc, NumEnums: 0, NumMessages: 11, NumExtensions: 0, @@ -523,6 +699,7 @@ func file_autopilotrpc_autopilot_proto_init() { MessageInfos: file_autopilotrpc_autopilot_proto_msgTypes, }.Build() File_autopilotrpc_autopilot_proto = out.File + file_autopilotrpc_autopilot_proto_rawDesc = nil file_autopilotrpc_autopilot_proto_goTypes = nil file_autopilotrpc_autopilot_proto_depIdxs = nil } diff --git a/lnrpc/chainrpc/chain_server.go b/lnrpc/chainrpc/chain_server.go index 690cd8177..da68e034b 100644 --- a/lnrpc/chainrpc/chain_server.go +++ b/lnrpc/chainrpc/chain_server.go @@ -11,15 +11,13 @@ import ( "path/filepath" "sync" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/macaroons" "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" "gopkg.in/macaroon-bakery.v2/bakery" ) @@ -86,8 +84,8 @@ var ( // ErrChainNotifierServerNotActive indicates that the chain notifier hasn't // finished the startup process. - ErrChainNotifierServerNotActive = status.Error(codes.Unavailable, - "chain notifier RPC is still in the process of starting") + ErrChainNotifierServerNotActive = errors.New("chain notifier RPC is " + + "still in the process of starting") ) // ServerShell is a shell struct holding a reference to the actual sub-server. diff --git a/lnrpc/chainrpc/chainkit.pb.go b/lnrpc/chainrpc/chainkit.pb.go index 4cb5f2a76..fe350879c 100644 --- a/lnrpc/chainrpc/chainkit.pb.go +++ b/lnrpc/chainrpc/chainkit.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.33.0 // protoc v3.21.12 // source: chainrpc/chainkit.proto @@ -11,7 +11,6 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" - unsafe "unsafe" ) const ( @@ -22,18 +21,21 @@ const ( ) type GetBlockRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The hash of the requested block. - BlockHash []byte `protobuf:"bytes,1,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The hash of the requested block. + BlockHash []byte `protobuf:"bytes,1,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"` } func (x *GetBlockRequest) Reset() { *x = GetBlockRequest{} - mi := &file_chainrpc_chainkit_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_chainrpc_chainkit_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetBlockRequest) String() string { @@ -44,7 +46,7 @@ func (*GetBlockRequest) ProtoMessage() {} func (x *GetBlockRequest) ProtoReflect() protoreflect.Message { mi := &file_chainrpc_chainkit_proto_msgTypes[0] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -69,18 +71,21 @@ func (x *GetBlockRequest) GetBlockHash() []byte { // TODO(ffranr): The neutrino GetBlock response includes many // additional helpful fields. Consider adding them here also. type GetBlockResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The raw bytes of the requested block. - RawBlock []byte `protobuf:"bytes,1,opt,name=raw_block,json=rawBlock,proto3" json:"raw_block,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The raw bytes of the requested block. + RawBlock []byte `protobuf:"bytes,1,opt,name=raw_block,json=rawBlock,proto3" json:"raw_block,omitempty"` } func (x *GetBlockResponse) Reset() { *x = GetBlockResponse{} - mi := &file_chainrpc_chainkit_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_chainrpc_chainkit_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetBlockResponse) String() string { @@ -91,7 +96,7 @@ func (*GetBlockResponse) ProtoMessage() {} func (x *GetBlockResponse) ProtoReflect() protoreflect.Message { mi := &file_chainrpc_chainkit_proto_msgTypes[1] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -114,18 +119,21 @@ func (x *GetBlockResponse) GetRawBlock() []byte { } type GetBlockHeaderRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The hash of the block with the requested header. - BlockHash []byte `protobuf:"bytes,1,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The hash of the block with the requested header. + BlockHash []byte `protobuf:"bytes,1,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"` } func (x *GetBlockHeaderRequest) Reset() { *x = GetBlockHeaderRequest{} - mi := &file_chainrpc_chainkit_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_chainrpc_chainkit_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetBlockHeaderRequest) String() string { @@ -136,7 +144,7 @@ func (*GetBlockHeaderRequest) ProtoMessage() {} func (x *GetBlockHeaderRequest) ProtoReflect() protoreflect.Message { mi := &file_chainrpc_chainkit_proto_msgTypes[2] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -159,18 +167,21 @@ func (x *GetBlockHeaderRequest) GetBlockHash() []byte { } type GetBlockHeaderResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The header of the block with the requested hash. RawBlockHeader []byte `protobuf:"bytes,1,opt,name=raw_block_header,json=rawBlockHeader,proto3" json:"raw_block_header,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *GetBlockHeaderResponse) Reset() { *x = GetBlockHeaderResponse{} - mi := &file_chainrpc_chainkit_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_chainrpc_chainkit_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetBlockHeaderResponse) String() string { @@ -181,7 +192,7 @@ func (*GetBlockHeaderResponse) ProtoMessage() {} func (x *GetBlockHeaderResponse) ProtoReflect() protoreflect.Message { mi := &file_chainrpc_chainkit_proto_msgTypes[3] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -204,16 +215,18 @@ func (x *GetBlockHeaderResponse) GetRawBlockHeader() []byte { } type GetBestBlockRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *GetBestBlockRequest) Reset() { *x = GetBestBlockRequest{} - mi := &file_chainrpc_chainkit_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_chainrpc_chainkit_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetBestBlockRequest) String() string { @@ -224,7 +237,7 @@ func (*GetBestBlockRequest) ProtoMessage() {} func (x *GetBestBlockRequest) ProtoReflect() protoreflect.Message { mi := &file_chainrpc_chainkit_proto_msgTypes[4] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -240,20 +253,23 @@ func (*GetBestBlockRequest) Descriptor() ([]byte, []int) { } type GetBestBlockResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The hash of the best block. BlockHash []byte `protobuf:"bytes,1,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"` // The height of the best block. - BlockHeight int32 `protobuf:"varint,2,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + BlockHeight int32 `protobuf:"varint,2,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` } func (x *GetBestBlockResponse) Reset() { *x = GetBestBlockResponse{} - mi := &file_chainrpc_chainkit_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_chainrpc_chainkit_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetBestBlockResponse) String() string { @@ -264,7 +280,7 @@ func (*GetBestBlockResponse) ProtoMessage() {} func (x *GetBestBlockResponse) ProtoReflect() protoreflect.Message { mi := &file_chainrpc_chainkit_proto_msgTypes[5] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -294,18 +310,21 @@ func (x *GetBestBlockResponse) GetBlockHeight() int32 { } type GetBlockHashRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Block height of the target best chain block. - BlockHeight int64 `protobuf:"varint,1,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Block height of the target best chain block. + BlockHeight int64 `protobuf:"varint,1,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` } func (x *GetBlockHashRequest) Reset() { *x = GetBlockHashRequest{} - mi := &file_chainrpc_chainkit_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_chainrpc_chainkit_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetBlockHashRequest) String() string { @@ -316,7 +335,7 @@ func (*GetBlockHashRequest) ProtoMessage() {} func (x *GetBlockHashRequest) ProtoReflect() protoreflect.Message { mi := &file_chainrpc_chainkit_proto_msgTypes[6] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -339,18 +358,21 @@ func (x *GetBlockHashRequest) GetBlockHeight() int64 { } type GetBlockHashResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The hash of the best block at the specified height. - BlockHash []byte `protobuf:"bytes,1,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The hash of the best block at the specified height. + BlockHash []byte `protobuf:"bytes,1,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"` } func (x *GetBlockHashResponse) Reset() { *x = GetBlockHashResponse{} - mi := &file_chainrpc_chainkit_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_chainrpc_chainkit_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetBlockHashResponse) String() string { @@ -361,7 +383,7 @@ func (*GetBlockHashResponse) ProtoMessage() {} func (x *GetBlockHashResponse) ProtoReflect() protoreflect.Message { mi := &file_chainrpc_chainkit_proto_msgTypes[7] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -385,49 +407,78 @@ func (x *GetBlockHashResponse) GetBlockHash() []byte { var File_chainrpc_chainkit_proto protoreflect.FileDescriptor -const file_chainrpc_chainkit_proto_rawDesc = "" + - "\n" + - "\x17chainrpc/chainkit.proto\x12\bchainrpc\"0\n" + - "\x0fGetBlockRequest\x12\x1d\n" + - "\n" + - "block_hash\x18\x01 \x01(\fR\tblockHash\"/\n" + - "\x10GetBlockResponse\x12\x1b\n" + - "\traw_block\x18\x01 \x01(\fR\brawBlock\"6\n" + - "\x15GetBlockHeaderRequest\x12\x1d\n" + - "\n" + - "block_hash\x18\x01 \x01(\fR\tblockHash\"B\n" + - "\x16GetBlockHeaderResponse\x12(\n" + - "\x10raw_block_header\x18\x01 \x01(\fR\x0erawBlockHeader\"\x15\n" + - "\x13GetBestBlockRequest\"X\n" + - "\x14GetBestBlockResponse\x12\x1d\n" + - "\n" + - "block_hash\x18\x01 \x01(\fR\tblockHash\x12!\n" + - "\fblock_height\x18\x02 \x01(\x05R\vblockHeight\"8\n" + - "\x13GetBlockHashRequest\x12!\n" + - "\fblock_height\x18\x01 \x01(\x03R\vblockHeight\"5\n" + - "\x14GetBlockHashResponse\x12\x1d\n" + - "\n" + - "block_hash\x18\x01 \x01(\fR\tblockHash2\xc0\x02\n" + - "\bChainKit\x12A\n" + - "\bGetBlock\x12\x19.chainrpc.GetBlockRequest\x1a\x1a.chainrpc.GetBlockResponse\x12S\n" + - "\x0eGetBlockHeader\x12\x1f.chainrpc.GetBlockHeaderRequest\x1a .chainrpc.GetBlockHeaderResponse\x12M\n" + - "\fGetBestBlock\x12\x1d.chainrpc.GetBestBlockRequest\x1a\x1e.chainrpc.GetBestBlockResponse\x12M\n" + - "\fGetBlockHash\x12\x1d.chainrpc.GetBlockHashRequest\x1a\x1e.chainrpc.GetBlockHashResponseB0Z.github.com/lightningnetwork/lnd/lnrpc/chainrpcb\x06proto3" +var file_chainrpc_chainkit_proto_rawDesc = []byte{ + 0x0a, 0x17, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x6b, 0x69, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x72, 0x70, 0x63, 0x22, 0x30, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, + 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x48, 0x61, 0x73, 0x68, 0x22, 0x2f, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x61, 0x77, + 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x72, 0x61, + 0x77, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x36, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x22, 0x42, + 0x0a, 0x16, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x61, 0x77, 0x5f, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0e, 0x72, 0x61, 0x77, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x22, 0x15, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x42, 0x65, 0x73, 0x74, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x58, 0x0a, 0x14, 0x47, 0x65, 0x74, + 0x42, 0x65, 0x73, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, + 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x22, 0x38, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, + 0x61, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x35, 0x0a, + 0x14, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, + 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x48, 0x61, 0x73, 0x68, 0x32, 0xc0, 0x02, 0x0a, 0x08, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4b, 0x69, + 0x74, 0x12, 0x41, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x19, 0x2e, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x1f, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0c, 0x47, 0x65, 0x74, + 0x42, 0x65, 0x73, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x1d, 0x2e, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x65, 0x73, 0x74, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x65, 0x73, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1d, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, + 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} var ( file_chainrpc_chainkit_proto_rawDescOnce sync.Once - file_chainrpc_chainkit_proto_rawDescData []byte + file_chainrpc_chainkit_proto_rawDescData = file_chainrpc_chainkit_proto_rawDesc ) func file_chainrpc_chainkit_proto_rawDescGZIP() []byte { file_chainrpc_chainkit_proto_rawDescOnce.Do(func() { - file_chainrpc_chainkit_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_chainrpc_chainkit_proto_rawDesc), len(file_chainrpc_chainkit_proto_rawDesc))) + file_chainrpc_chainkit_proto_rawDescData = protoimpl.X.CompressGZIP(file_chainrpc_chainkit_proto_rawDescData) }) return file_chainrpc_chainkit_proto_rawDescData } var file_chainrpc_chainkit_proto_msgTypes = make([]protoimpl.MessageInfo, 8) -var file_chainrpc_chainkit_proto_goTypes = []any{ +var file_chainrpc_chainkit_proto_goTypes = []interface{}{ (*GetBlockRequest)(nil), // 0: chainrpc.GetBlockRequest (*GetBlockResponse)(nil), // 1: chainrpc.GetBlockResponse (*GetBlockHeaderRequest)(nil), // 2: chainrpc.GetBlockHeaderRequest @@ -458,11 +509,109 @@ func file_chainrpc_chainkit_proto_init() { if File_chainrpc_chainkit_proto != nil { return } + if !protoimpl.UnsafeEnabled { + file_chainrpc_chainkit_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlockRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chainrpc_chainkit_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlockResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chainrpc_chainkit_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlockHeaderRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chainrpc_chainkit_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlockHeaderResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chainrpc_chainkit_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBestBlockRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chainrpc_chainkit_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBestBlockResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chainrpc_chainkit_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlockHashRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chainrpc_chainkit_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlockHashResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_chainrpc_chainkit_proto_rawDesc), len(file_chainrpc_chainkit_proto_rawDesc)), + RawDescriptor: file_chainrpc_chainkit_proto_rawDesc, NumEnums: 0, NumMessages: 8, NumExtensions: 0, @@ -473,6 +622,7 @@ func file_chainrpc_chainkit_proto_init() { MessageInfos: file_chainrpc_chainkit_proto_msgTypes, }.Build() File_chainrpc_chainkit_proto = out.File + file_chainrpc_chainkit_proto_rawDesc = nil file_chainrpc_chainkit_proto_goTypes = nil file_chainrpc_chainkit_proto_depIdxs = nil } diff --git a/lnrpc/chainrpc/chainnotifier.pb.go b/lnrpc/chainrpc/chainnotifier.pb.go index 41785b605..1c2ec26d4 100644 --- a/lnrpc/chainrpc/chainnotifier.pb.go +++ b/lnrpc/chainrpc/chainnotifier.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.33.0 // protoc v3.21.12 // source: chainrpc/chainnotifier.proto @@ -11,7 +11,6 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" - unsafe "unsafe" ) const ( @@ -22,7 +21,10 @@ const ( ) type ConfRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The transaction hash for which we should request a confirmation notification // for. If set to a hash of all zeros, then the confirmation notification will // be requested for the script instead. @@ -41,16 +43,16 @@ type ConfRequest struct { HeightHint uint32 `protobuf:"varint,4,opt,name=height_hint,json=heightHint,proto3" json:"height_hint,omitempty"` // If true, then the block that mines the specified txid/script will be // included in eventual the notification event. - IncludeBlock bool `protobuf:"varint,5,opt,name=include_block,json=includeBlock,proto3" json:"include_block,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + IncludeBlock bool `protobuf:"varint,5,opt,name=include_block,json=includeBlock,proto3" json:"include_block,omitempty"` } func (x *ConfRequest) Reset() { *x = ConfRequest{} - mi := &file_chainrpc_chainnotifier_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_chainrpc_chainnotifier_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ConfRequest) String() string { @@ -61,7 +63,7 @@ func (*ConfRequest) ProtoMessage() {} func (x *ConfRequest) ProtoReflect() protoreflect.Message { mi := &file_chainrpc_chainnotifier_proto_msgTypes[0] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -112,7 +114,10 @@ func (x *ConfRequest) GetIncludeBlock() bool { } type ConfDetails struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The raw bytes of the confirmed transaction. RawTx []byte `protobuf:"bytes,1,opt,name=raw_tx,json=rawTx,proto3" json:"raw_tx,omitempty"` // The hash of the block in which the confirmed transaction was included in. @@ -124,16 +129,16 @@ type ConfDetails struct { TxIndex uint32 `protobuf:"varint,4,opt,name=tx_index,json=txIndex,proto3" json:"tx_index,omitempty"` // The raw bytes of the block that mined the transaction. Only included if // include_block was set in the request. - RawBlock []byte `protobuf:"bytes,5,opt,name=raw_block,json=rawBlock,proto3" json:"raw_block,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + RawBlock []byte `protobuf:"bytes,5,opt,name=raw_block,json=rawBlock,proto3" json:"raw_block,omitempty"` } func (x *ConfDetails) Reset() { *x = ConfDetails{} - mi := &file_chainrpc_chainnotifier_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_chainrpc_chainnotifier_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ConfDetails) String() string { @@ -144,7 +149,7 @@ func (*ConfDetails) ProtoMessage() {} func (x *ConfDetails) ProtoReflect() protoreflect.Message { mi := &file_chainrpc_chainnotifier_proto_msgTypes[1] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -195,16 +200,18 @@ func (x *ConfDetails) GetRawBlock() []byte { } type Reorg struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *Reorg) Reset() { *x = Reorg{} - mi := &file_chainrpc_chainnotifier_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_chainrpc_chainnotifier_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *Reorg) String() string { @@ -215,7 +222,7 @@ func (*Reorg) ProtoMessage() {} func (x *Reorg) ProtoReflect() protoreflect.Message { mi := &file_chainrpc_chainnotifier_proto_msgTypes[2] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -231,21 +238,24 @@ func (*Reorg) Descriptor() ([]byte, []int) { } type ConfEvent struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to Event: + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Event: // // *ConfEvent_Conf // *ConfEvent_Reorg - Event isConfEvent_Event `protobuf_oneof:"event"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Event isConfEvent_Event `protobuf_oneof:"event"` } func (x *ConfEvent) Reset() { *x = ConfEvent{} - mi := &file_chainrpc_chainnotifier_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_chainrpc_chainnotifier_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ConfEvent) String() string { @@ -256,7 +266,7 @@ func (*ConfEvent) ProtoMessage() {} func (x *ConfEvent) ProtoReflect() protoreflect.Message { mi := &file_chainrpc_chainnotifier_proto_msgTypes[3] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -271,27 +281,23 @@ func (*ConfEvent) Descriptor() ([]byte, []int) { return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{3} } -func (x *ConfEvent) GetEvent() isConfEvent_Event { - if x != nil { - return x.Event +func (m *ConfEvent) GetEvent() isConfEvent_Event { + if m != nil { + return m.Event } return nil } func (x *ConfEvent) GetConf() *ConfDetails { - if x != nil { - if x, ok := x.Event.(*ConfEvent_Conf); ok { - return x.Conf - } + if x, ok := x.GetEvent().(*ConfEvent_Conf); ok { + return x.Conf } return nil } func (x *ConfEvent) GetReorg() *Reorg { - if x != nil { - if x, ok := x.Event.(*ConfEvent_Reorg); ok { - return x.Reorg - } + if x, ok := x.GetEvent().(*ConfEvent_Reorg); ok { + return x.Reorg } return nil } @@ -317,20 +323,23 @@ func (*ConfEvent_Conf) isConfEvent_Event() {} func (*ConfEvent_Reorg) isConfEvent_Event() {} type Outpoint struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The hash of the transaction. Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` // The index of the output within the transaction. - Index uint32 `protobuf:"varint,2,opt,name=index,proto3" json:"index,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Index uint32 `protobuf:"varint,2,opt,name=index,proto3" json:"index,omitempty"` } func (x *Outpoint) Reset() { *x = Outpoint{} - mi := &file_chainrpc_chainnotifier_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_chainrpc_chainnotifier_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *Outpoint) String() string { @@ -341,7 +350,7 @@ func (*Outpoint) ProtoMessage() {} func (x *Outpoint) ProtoReflect() protoreflect.Message { mi := &file_chainrpc_chainnotifier_proto_msgTypes[4] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -371,7 +380,10 @@ func (x *Outpoint) GetIndex() uint32 { } type SpendRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The outpoint for which we should request a spend notification for. If set to // a zero outpoint, then the spend notification will be requested for the // script instead. A zero or nil outpoint is not supported for Taproot spends @@ -387,16 +399,16 @@ type SpendRequest struct { // The earliest height in the chain for which the outpoint/output script could // have been spent. This should in most cases be set to the broadcast height of // the outpoint/output script. - HeightHint uint32 `protobuf:"varint,3,opt,name=height_hint,json=heightHint,proto3" json:"height_hint,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + HeightHint uint32 `protobuf:"varint,3,opt,name=height_hint,json=heightHint,proto3" json:"height_hint,omitempty"` } func (x *SpendRequest) Reset() { *x = SpendRequest{} - mi := &file_chainrpc_chainnotifier_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_chainrpc_chainnotifier_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SpendRequest) String() string { @@ -407,7 +419,7 @@ func (*SpendRequest) ProtoMessage() {} func (x *SpendRequest) ProtoReflect() protoreflect.Message { mi := &file_chainrpc_chainnotifier_proto_msgTypes[5] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -444,7 +456,10 @@ func (x *SpendRequest) GetHeightHint() uint32 { } type SpendDetails struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The outpoint was that spent. SpendingOutpoint *Outpoint `protobuf:"bytes,1,opt,name=spending_outpoint,json=spendingOutpoint,proto3" json:"spending_outpoint,omitempty"` // The raw bytes of the spending transaction. @@ -455,15 +470,15 @@ type SpendDetails struct { SpendingInputIndex uint32 `protobuf:"varint,4,opt,name=spending_input_index,json=spendingInputIndex,proto3" json:"spending_input_index,omitempty"` // The height at which the spending transaction was included in a block. SpendingHeight uint32 `protobuf:"varint,5,opt,name=spending_height,json=spendingHeight,proto3" json:"spending_height,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *SpendDetails) Reset() { *x = SpendDetails{} - mi := &file_chainrpc_chainnotifier_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_chainrpc_chainnotifier_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SpendDetails) String() string { @@ -474,7 +489,7 @@ func (*SpendDetails) ProtoMessage() {} func (x *SpendDetails) ProtoReflect() protoreflect.Message { mi := &file_chainrpc_chainnotifier_proto_msgTypes[6] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -525,21 +540,24 @@ func (x *SpendDetails) GetSpendingHeight() uint32 { } type SpendEvent struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to Event: + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Event: // // *SpendEvent_Spend // *SpendEvent_Reorg - Event isSpendEvent_Event `protobuf_oneof:"event"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Event isSpendEvent_Event `protobuf_oneof:"event"` } func (x *SpendEvent) Reset() { *x = SpendEvent{} - mi := &file_chainrpc_chainnotifier_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_chainrpc_chainnotifier_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SpendEvent) String() string { @@ -550,7 +568,7 @@ func (*SpendEvent) ProtoMessage() {} func (x *SpendEvent) ProtoReflect() protoreflect.Message { mi := &file_chainrpc_chainnotifier_proto_msgTypes[7] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -565,27 +583,23 @@ func (*SpendEvent) Descriptor() ([]byte, []int) { return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{7} } -func (x *SpendEvent) GetEvent() isSpendEvent_Event { - if x != nil { - return x.Event +func (m *SpendEvent) GetEvent() isSpendEvent_Event { + if m != nil { + return m.Event } return nil } func (x *SpendEvent) GetSpend() *SpendDetails { - if x != nil { - if x, ok := x.Event.(*SpendEvent_Spend); ok { - return x.Spend - } + if x, ok := x.GetEvent().(*SpendEvent_Spend); ok { + return x.Spend } return nil } func (x *SpendEvent) GetReorg() *Reorg { - if x != nil { - if x, ok := x.Event.(*SpendEvent_Reorg); ok { - return x.Reorg - } + if x, ok := x.GetEvent().(*SpendEvent_Reorg); ok { + return x.Reorg } return nil } @@ -611,20 +625,23 @@ func (*SpendEvent_Spend) isSpendEvent_Event() {} func (*SpendEvent_Reorg) isSpendEvent_Event() {} type BlockEpoch struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The hash of the block. Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` // The height of the block. - Height uint32 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Height uint32 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` } func (x *BlockEpoch) Reset() { *x = BlockEpoch{} - mi := &file_chainrpc_chainnotifier_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_chainrpc_chainnotifier_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *BlockEpoch) String() string { @@ -635,7 +652,7 @@ func (*BlockEpoch) ProtoMessage() {} func (x *BlockEpoch) ProtoReflect() protoreflect.Message { mi := &file_chainrpc_chainnotifier_proto_msgTypes[8] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -666,70 +683,109 @@ func (x *BlockEpoch) GetHeight() uint32 { var File_chainrpc_chainnotifier_proto protoreflect.FileDescriptor -const file_chainrpc_chainnotifier_proto_rawDesc = "" + - "\n" + - "\x1cchainrpc/chainnotifier.proto\x12\bchainrpc\"\x9c\x01\n" + - "\vConfRequest\x12\x12\n" + - "\x04txid\x18\x01 \x01(\fR\x04txid\x12\x16\n" + - "\x06script\x18\x02 \x01(\fR\x06script\x12\x1b\n" + - "\tnum_confs\x18\x03 \x01(\rR\bnumConfs\x12\x1f\n" + - "\vheight_hint\x18\x04 \x01(\rR\n" + - "heightHint\x12#\n" + - "\rinclude_block\x18\x05 \x01(\bR\fincludeBlock\"\x9e\x01\n" + - "\vConfDetails\x12\x15\n" + - "\x06raw_tx\x18\x01 \x01(\fR\x05rawTx\x12\x1d\n" + - "\n" + - "block_hash\x18\x02 \x01(\fR\tblockHash\x12!\n" + - "\fblock_height\x18\x03 \x01(\rR\vblockHeight\x12\x19\n" + - "\btx_index\x18\x04 \x01(\rR\atxIndex\x12\x1b\n" + - "\traw_block\x18\x05 \x01(\fR\brawBlock\"\a\n" + - "\x05Reorg\"j\n" + - "\tConfEvent\x12+\n" + - "\x04conf\x18\x01 \x01(\v2\x15.chainrpc.ConfDetailsH\x00R\x04conf\x12'\n" + - "\x05reorg\x18\x02 \x01(\v2\x0f.chainrpc.ReorgH\x00R\x05reorgB\a\n" + - "\x05event\"4\n" + - "\bOutpoint\x12\x12\n" + - "\x04hash\x18\x01 \x01(\fR\x04hash\x12\x14\n" + - "\x05index\x18\x02 \x01(\rR\x05index\"w\n" + - "\fSpendRequest\x12.\n" + - "\boutpoint\x18\x01 \x01(\v2\x12.chainrpc.OutpointR\boutpoint\x12\x16\n" + - "\x06script\x18\x02 \x01(\fR\x06script\x12\x1f\n" + - "\vheight_hint\x18\x03 \x01(\rR\n" + - "heightHint\"\xfc\x01\n" + - "\fSpendDetails\x12?\n" + - "\x11spending_outpoint\x18\x01 \x01(\v2\x12.chainrpc.OutpointR\x10spendingOutpoint\x12&\n" + - "\x0fraw_spending_tx\x18\x02 \x01(\fR\rrawSpendingTx\x12(\n" + - "\x10spending_tx_hash\x18\x03 \x01(\fR\x0espendingTxHash\x120\n" + - "\x14spending_input_index\x18\x04 \x01(\rR\x12spendingInputIndex\x12'\n" + - "\x0fspending_height\x18\x05 \x01(\rR\x0espendingHeight\"n\n" + - "\n" + - "SpendEvent\x12.\n" + - "\x05spend\x18\x01 \x01(\v2\x16.chainrpc.SpendDetailsH\x00R\x05spend\x12'\n" + - "\x05reorg\x18\x02 \x01(\v2\x0f.chainrpc.ReorgH\x00R\x05reorgB\a\n" + - "\x05event\"8\n" + - "\n" + - "BlockEpoch\x12\x12\n" + - "\x04hash\x18\x01 \x01(\fR\x04hash\x12\x16\n" + - "\x06height\x18\x02 \x01(\rR\x06height2\xe7\x01\n" + - "\rChainNotifier\x12I\n" + - "\x19RegisterConfirmationsNtfn\x12\x15.chainrpc.ConfRequest\x1a\x13.chainrpc.ConfEvent0\x01\x12C\n" + - "\x11RegisterSpendNtfn\x12\x16.chainrpc.SpendRequest\x1a\x14.chainrpc.SpendEvent0\x01\x12F\n" + - "\x16RegisterBlockEpochNtfn\x12\x14.chainrpc.BlockEpoch\x1a\x14.chainrpc.BlockEpoch0\x01B0Z.github.com/lightningnetwork/lnd/lnrpc/chainrpcb\x06proto3" +var file_chainrpc_chainnotifier_proto_rawDesc = []byte{ + 0x0a, 0x1c, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x22, 0x9c, 0x01, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, + 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x6f, 0x6e, 0x66, + 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6e, 0x75, 0x6d, 0x43, 0x6f, 0x6e, 0x66, + 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x68, 0x69, 0x6e, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x48, 0x69, + 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x6e, 0x63, 0x6c, 0x75, + 0x64, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x9e, 0x01, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x66, + 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x61, 0x77, 0x5f, 0x74, + 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x61, 0x77, 0x54, 0x78, 0x12, 0x1d, + 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x21, 0x0a, + 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x07, 0x74, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1b, 0x0a, 0x09, 0x72, + 0x61, 0x77, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, + 0x72, 0x61, 0x77, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x07, 0x0a, 0x05, 0x52, 0x65, 0x6f, 0x72, + 0x67, 0x22, 0x6a, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x66, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2b, + 0x0a, 0x04, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, + 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x44, 0x65, 0x74, 0x61, + 0x69, 0x6c, 0x73, 0x48, 0x00, 0x52, 0x04, 0x63, 0x6f, 0x6e, 0x66, 0x12, 0x27, 0x0a, 0x05, 0x72, + 0x65, 0x6f, 0x72, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x68, 0x61, + 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6f, 0x72, 0x67, 0x48, 0x00, 0x52, 0x05, 0x72, + 0x65, 0x6f, 0x72, 0x67, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x34, 0x0a, + 0x08, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, + 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x22, 0x77, 0x0a, 0x0c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0a, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x48, 0x69, 0x6e, 0x74, 0x22, 0xfc, 0x01, 0x0a, + 0x0c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x3f, 0x0a, + 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x10, 0x73, 0x70, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x26, + 0x0a, 0x0f, 0x72, 0x61, 0x77, 0x5f, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, + 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x72, 0x61, 0x77, 0x53, 0x70, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0e, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x48, 0x61, 0x73, 0x68, + 0x12, 0x30, 0x0a, 0x14, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x6e, 0x70, + 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, + 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x73, 0x70, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x6e, 0x0a, 0x0a, 0x53, + 0x70, 0x65, 0x6e, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x05, 0x73, 0x70, 0x65, + 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, + 0x48, 0x00, 0x52, 0x05, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x12, 0x27, 0x0a, 0x05, 0x72, 0x65, 0x6f, + 0x72, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6f, 0x72, 0x67, 0x48, 0x00, 0x52, 0x05, 0x72, 0x65, 0x6f, + 0x72, 0x67, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x38, 0x0a, 0x0a, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, + 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x32, 0xe7, 0x01, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4e, + 0x6f, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x49, 0x0a, 0x19, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x4e, 0x74, 0x66, 0x6e, 0x12, 0x15, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x30, 0x01, 0x12, 0x43, 0x0a, 0x11, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, 0x70, + 0x65, 0x6e, 0x64, 0x4e, 0x74, 0x66, 0x6e, 0x12, 0x16, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x14, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x70, 0x65, 0x6e, 0x64, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x46, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x4e, 0x74, 0x66, + 0x6e, 0x12, 0x14, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x1a, 0x14, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x30, 0x01, 0x42, + 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, + 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, + 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, + 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} var ( file_chainrpc_chainnotifier_proto_rawDescOnce sync.Once - file_chainrpc_chainnotifier_proto_rawDescData []byte + file_chainrpc_chainnotifier_proto_rawDescData = file_chainrpc_chainnotifier_proto_rawDesc ) func file_chainrpc_chainnotifier_proto_rawDescGZIP() []byte { file_chainrpc_chainnotifier_proto_rawDescOnce.Do(func() { - file_chainrpc_chainnotifier_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_chainrpc_chainnotifier_proto_rawDesc), len(file_chainrpc_chainnotifier_proto_rawDesc))) + file_chainrpc_chainnotifier_proto_rawDescData = protoimpl.X.CompressGZIP(file_chainrpc_chainnotifier_proto_rawDescData) }) return file_chainrpc_chainnotifier_proto_rawDescData } var file_chainrpc_chainnotifier_proto_msgTypes = make([]protoimpl.MessageInfo, 9) -var file_chainrpc_chainnotifier_proto_goTypes = []any{ +var file_chainrpc_chainnotifier_proto_goTypes = []interface{}{ (*ConfRequest)(nil), // 0: chainrpc.ConfRequest (*ConfDetails)(nil), // 1: chainrpc.ConfDetails (*Reorg)(nil), // 2: chainrpc.Reorg @@ -765,11 +821,121 @@ func file_chainrpc_chainnotifier_proto_init() { if File_chainrpc_chainnotifier_proto != nil { return } - file_chainrpc_chainnotifier_proto_msgTypes[3].OneofWrappers = []any{ + if !protoimpl.UnsafeEnabled { + file_chainrpc_chainnotifier_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConfRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chainrpc_chainnotifier_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConfDetails); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chainrpc_chainnotifier_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Reorg); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chainrpc_chainnotifier_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConfEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chainrpc_chainnotifier_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Outpoint); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chainrpc_chainnotifier_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SpendRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chainrpc_chainnotifier_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SpendDetails); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chainrpc_chainnotifier_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SpendEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_chainrpc_chainnotifier_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BlockEpoch); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_chainrpc_chainnotifier_proto_msgTypes[3].OneofWrappers = []interface{}{ (*ConfEvent_Conf)(nil), (*ConfEvent_Reorg)(nil), } - file_chainrpc_chainnotifier_proto_msgTypes[7].OneofWrappers = []any{ + file_chainrpc_chainnotifier_proto_msgTypes[7].OneofWrappers = []interface{}{ (*SpendEvent_Spend)(nil), (*SpendEvent_Reorg)(nil), } @@ -777,7 +943,7 @@ func file_chainrpc_chainnotifier_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_chainrpc_chainnotifier_proto_rawDesc), len(file_chainrpc_chainnotifier_proto_rawDesc)), + RawDescriptor: file_chainrpc_chainnotifier_proto_rawDesc, NumEnums: 0, NumMessages: 9, NumExtensions: 0, @@ -788,6 +954,7 @@ func file_chainrpc_chainnotifier_proto_init() { MessageInfos: file_chainrpc_chainnotifier_proto_msgTypes, }.Build() File_chainrpc_chainnotifier_proto = out.File + file_chainrpc_chainnotifier_proto_rawDesc = nil file_chainrpc_chainnotifier_proto_goTypes = nil file_chainrpc_chainnotifier_proto_depIdxs = nil } diff --git a/lnrpc/devrpc/config_active.go b/lnrpc/devrpc/config_active.go index 432117659..c5d43c194 100644 --- a/lnrpc/devrpc/config_active.go +++ b/lnrpc/devrpc/config_active.go @@ -4,7 +4,7 @@ package devrpc import ( - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/htlcswitch" ) diff --git a/lnrpc/devrpc/dev.pb.go b/lnrpc/devrpc/dev.pb.go index ec055dd39..d8de47fc8 100644 --- a/lnrpc/devrpc/dev.pb.go +++ b/lnrpc/devrpc/dev.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.33.0 // protoc v3.21.12 // source: devrpc/dev.proto @@ -12,7 +12,6 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" - unsafe "unsafe" ) const ( @@ -23,16 +22,18 @@ const ( ) type ImportGraphResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *ImportGraphResponse) Reset() { *x = ImportGraphResponse{} - mi := &file_devrpc_dev_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_devrpc_dev_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ImportGraphResponse) String() string { @@ -43,7 +44,7 @@ func (*ImportGraphResponse) ProtoMessage() {} func (x *ImportGraphResponse) ProtoReflect() protoreflect.Message { mi := &file_devrpc_dev_proto_msgTypes[0] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -59,18 +60,21 @@ func (*ImportGraphResponse) Descriptor() ([]byte, []int) { } type QuiescenceRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The channel point of the channel we wish to quiesce - ChanId *lnrpc.ChannelPoint `protobuf:"bytes,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The channel point of the channel we wish to quiesce + ChanId *lnrpc.ChannelPoint `protobuf:"bytes,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"` } func (x *QuiescenceRequest) Reset() { *x = QuiescenceRequest{} - mi := &file_devrpc_dev_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_devrpc_dev_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *QuiescenceRequest) String() string { @@ -81,7 +85,7 @@ func (*QuiescenceRequest) ProtoMessage() {} func (x *QuiescenceRequest) ProtoReflect() protoreflect.Message { mi := &file_devrpc_dev_proto_msgTypes[1] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -104,19 +108,22 @@ func (x *QuiescenceRequest) GetChanId() *lnrpc.ChannelPoint { } type QuiescenceResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Indicates whether or not we hold the initiator role or not once the // negotiation completes - Initiator bool `protobuf:"varint,1,opt,name=initiator,proto3" json:"initiator,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Initiator bool `protobuf:"varint,1,opt,name=initiator,proto3" json:"initiator,omitempty"` } func (x *QuiescenceResponse) Reset() { *x = QuiescenceResponse{} - mi := &file_devrpc_dev_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_devrpc_dev_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *QuiescenceResponse) String() string { @@ -127,7 +134,7 @@ func (*QuiescenceResponse) ProtoMessage() {} func (x *QuiescenceResponse) ProtoReflect() protoreflect.Message { mi := &file_devrpc_dev_proto_msgTypes[2] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -151,32 +158,47 @@ func (x *QuiescenceResponse) GetInitiator() bool { var File_devrpc_dev_proto protoreflect.FileDescriptor -const file_devrpc_dev_proto_rawDesc = "" + - "\n" + - "\x10devrpc/dev.proto\x12\x06devrpc\x1a\x0flightning.proto\"\x15\n" + - "\x13ImportGraphResponse\"A\n" + - "\x11QuiescenceRequest\x12,\n" + - "\achan_id\x18\x01 \x01(\v2\x13.lnrpc.ChannelPointR\x06chanId\"2\n" + - "\x12QuiescenceResponse\x12\x1c\n" + - "\tinitiator\x18\x01 \x01(\bR\tinitiator2\x88\x01\n" + - "\x03Dev\x12?\n" + - "\vImportGraph\x12\x13.lnrpc.ChannelGraph\x1a\x1b.devrpc.ImportGraphResponse\x12@\n" + - "\aQuiesce\x12\x19.devrpc.QuiescenceRequest\x1a\x1a.devrpc.QuiescenceResponseB.Z,github.com/lightningnetwork/lnd/lnrpc/devrpcb\x06proto3" +var file_devrpc_dev_proto_rawDesc = []byte{ + 0x0a, 0x10, 0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x2f, 0x64, 0x65, 0x76, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x06, 0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x1a, 0x0f, 0x6c, 0x69, 0x67, 0x68, + 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x15, 0x0a, 0x13, 0x49, + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x41, 0x0a, 0x11, 0x51, 0x75, 0x69, 0x65, 0x73, 0x63, 0x65, 0x6e, 0x63, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x63, + 0x68, 0x61, 0x6e, 0x49, 0x64, 0x22, 0x32, 0x0a, 0x12, 0x51, 0x75, 0x69, 0x65, 0x73, 0x63, 0x65, + 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x69, + 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, + 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x32, 0x88, 0x01, 0x0a, 0x03, 0x44, 0x65, + 0x76, 0x12, 0x3f, 0x0a, 0x0b, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, + 0x12, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x47, 0x72, 0x61, 0x70, 0x68, 0x1a, 0x1b, 0x2e, 0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x2e, 0x49, + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x40, 0x0a, 0x07, 0x51, 0x75, 0x69, 0x65, 0x73, 0x63, 0x65, 0x12, 0x19, 0x2e, + 0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x69, 0x65, 0x73, 0x63, 0x65, 0x6e, 0x63, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x64, 0x65, 0x76, 0x72, 0x70, + 0x63, 0x2e, 0x51, 0x75, 0x69, 0x65, 0x73, 0x63, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, + 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x64, 0x65, + 0x76, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} var ( file_devrpc_dev_proto_rawDescOnce sync.Once - file_devrpc_dev_proto_rawDescData []byte + file_devrpc_dev_proto_rawDescData = file_devrpc_dev_proto_rawDesc ) func file_devrpc_dev_proto_rawDescGZIP() []byte { file_devrpc_dev_proto_rawDescOnce.Do(func() { - file_devrpc_dev_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_devrpc_dev_proto_rawDesc), len(file_devrpc_dev_proto_rawDesc))) + file_devrpc_dev_proto_rawDescData = protoimpl.X.CompressGZIP(file_devrpc_dev_proto_rawDescData) }) return file_devrpc_dev_proto_rawDescData } var file_devrpc_dev_proto_msgTypes = make([]protoimpl.MessageInfo, 3) -var file_devrpc_dev_proto_goTypes = []any{ +var file_devrpc_dev_proto_goTypes = []interface{}{ (*ImportGraphResponse)(nil), // 0: devrpc.ImportGraphResponse (*QuiescenceRequest)(nil), // 1: devrpc.QuiescenceRequest (*QuiescenceResponse)(nil), // 2: devrpc.QuiescenceResponse @@ -201,11 +223,49 @@ func file_devrpc_dev_proto_init() { if File_devrpc_dev_proto != nil { return } + if !protoimpl.UnsafeEnabled { + file_devrpc_dev_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ImportGraphResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_devrpc_dev_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*QuiescenceRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_devrpc_dev_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*QuiescenceResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_devrpc_dev_proto_rawDesc), len(file_devrpc_dev_proto_rawDesc)), + RawDescriptor: file_devrpc_dev_proto_rawDesc, NumEnums: 0, NumMessages: 3, NumExtensions: 0, @@ -216,6 +276,7 @@ func file_devrpc_dev_proto_init() { MessageInfos: file_devrpc_dev_proto_msgTypes, }.Build() File_devrpc_dev_proto = out.File + file_devrpc_dev_proto_rawDesc = nil file_devrpc_dev_proto_goTypes = nil file_devrpc_dev_proto_depIdxs = nil } diff --git a/lnrpc/devrpc/dev_server.go b/lnrpc/devrpc/dev_server.go index c0f11f749..6cc4347f0 100644 --- a/lnrpc/devrpc/dev_server.go +++ b/lnrpc/devrpc/dev_server.go @@ -12,9 +12,9 @@ import ( "sync/atomic" "time" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" @@ -224,8 +224,17 @@ func (s *Server) ImportGraph(ctx context.Context, // Obtain the pointer to the global singleton channel graph. graphDB := s.cfg.GraphDB + var err error for _, rpcNode := range graph.Nodes { - pubKeyBytes, err := parsePubKey(rpcNode.PubKey) + node := &models.Node{ + HaveNodeAnnouncement: true, + LastUpdate: time.Unix( + int64(rpcNode.LastUpdate), 0, + ), + Alias: rpcNode.Alias, + } + + node.PubKeyBytes, err = parsePubKey(rpcNode.PubKey) if err != nil { return nil, err } @@ -242,25 +251,15 @@ func (s *Server) ImportGraph(ctx context.Context, } featureVector := lnwire.NewRawFeatureVector(featureBits...) + node.Features = lnwire.NewFeatureVector( + featureVector, featureNames, + ) - nodeColor, err := lncfg.ParseHexColor(rpcNode.Color) + node.Color, err = lncfg.ParseHexColor(rpcNode.Color) if err != nil { return nil, err } - node := models.NewV1Node(pubKeyBytes, &models.NodeV1Fields{ - LastUpdate: time.Unix( - int64(rpcNode.LastUpdate), 0, - ), - Alias: rpcNode.Alias, - Features: featureVector, - Color: nodeColor, - // NOTE: this is a workaround to ensure that - // HaveAnnouncement() returns true so that the other - // fields are properly persisted. - AuthSigBytes: []byte{0}, - }) - if err := graphDB.AddNode(ctx, node); err != nil { return nil, fmt.Errorf("unable to add node %v: %w", rpcNode.PubKey, err) @@ -270,13 +269,20 @@ func (s *Server) ImportGraph(ctx context.Context, } for _, rpcEdge := range graph.Edges { + rpcEdge := rpcEdge - node1, err := parsePubKey(rpcEdge.Node1Pub) + edge := &models.ChannelEdgeInfo{ + ChannelID: rpcEdge.ChannelId, + ChainHash: *s.cfg.ActiveNetParams.GenesisHash, + Capacity: btcutil.Amount(rpcEdge.Capacity), + } + + edge.NodeKey1Bytes, err = parsePubKey(rpcEdge.Node1Pub) if err != nil { return nil, err } - node2, err := parsePubKey(rpcEdge.Node2Pub) + edge.NodeKey2Bytes, err = parsePubKey(rpcEdge.Node2Pub) if err != nil { return nil, err } @@ -285,16 +291,7 @@ func (s *Server) ImportGraph(ctx context.Context, if err != nil { return nil, err } - - edge, err := models.NewV1Channel( - rpcEdge.ChannelId, *s.cfg.ActiveNetParams.GenesisHash, - node1, node2, &models.ChannelV1Fields{}, - models.WithCapacity(btcutil.Amount(rpcEdge.Capacity)), - models.WithChannelPoint(*channelPoint), - ) - if err != nil { - return nil, err - } + edge.ChannelPoint = *channelPoint if err := graphDB.AddChannelEdge(ctx, edge); err != nil { return nil, fmt.Errorf("unable to add edge %v: %w", @@ -303,7 +300,6 @@ func (s *Server) ImportGraph(ctx context.Context, makePolicy := func(rpcPolicy *lnrpc.RoutingPolicy) *models.ChannelEdgePolicy { //nolint:ll policy := &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, ChannelID: rpcEdge.ChannelId, LastUpdate: time.Unix( int64(rpcPolicy.LastUpdate), 0, diff --git a/lnrpc/gen_protos_docker.sh b/lnrpc/gen_protos_docker.sh index a79ce5f14..68c65581a 100755 --- a/lnrpc/gen_protos_docker.sh +++ b/lnrpc/gen_protos_docker.sh @@ -6,7 +6,7 @@ set -e DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # golang docker image version used in this script. -GO_IMAGE=docker.io/library/golang:1.26.4-alpine +GO_IMAGE=docker.io/library/golang:1.25.5-alpine PROTOBUF_VERSION=$(docker run --rm -v $DIR/../:/lnd -w /lnd $GO_IMAGE \ go list -f '{{.Version}}' -m google.golang.org/protobuf) diff --git a/lnrpc/invoicesrpc/addinvoice.go b/lnrpc/invoicesrpc/addinvoice.go index 42839d851..a7d8af655 100644 --- a/lnrpc/invoicesrpc/addinvoice.go +++ b/lnrpc/invoicesrpc/addinvoice.go @@ -10,14 +10,13 @@ import ( "sort" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/lntypes" @@ -70,8 +69,9 @@ type AddInvoiceConfig struct { // specified. DefaultCLTVExpiry uint32 - // ChanDB is used to access open channel state. - ChanDB chanstate.OpenChannelStore + // ChanDB is a global boltdb instance which is needed to access the + // channel graph. + ChanDB *channeldb.ChannelStateDB // Graph gives the invoice server access to various graph related // queries. @@ -345,7 +345,7 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig, // If specified, add a fallback address to the payment request. if len(invoice.FallbackAddr) > 0 { - addr, err := address.DecodeAddress( + addr, err := btcutil.DecodeAddress( invoice.FallbackAddr, cfg.ChainParams, ) if err != nil { @@ -523,16 +523,8 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig, //nolint:ll paths, err := blindedpath.BuildBlindedPaymentPaths( &blindedpath.BuildBlindedPathCfg{ - FindRoutes: cfg.QueryBlindedRoutes, - FetchChannelEdgesByID: func(chanID uint64) ( - *models.ChannelEdgeInfo, - *models.ChannelEdgePolicy, - *models.ChannelEdgePolicy, error) { - - return cfg.Graph.FetchChannelEdgesByID( - context.TODO(), chanID, - ) - }, + FindRoutes: cfg.QueryBlindedRoutes, + FetchChannelEdgesByID: cfg.Graph.FetchChannelEdgesByID, FetchOurOpenChannels: cfg.ChanDB.FetchAllOpenChannels, PathID: paymentAddr[:], ValueMsat: invoice.Value, @@ -731,7 +723,7 @@ type HopHintInfo struct { ScidAliasFeature bool } -func newHopHintInfo(c *chanstate.OpenChannel, isActive bool) *HopHintInfo { +func newHopHintInfo(c *channeldb.OpenChannel, isActive bool) *HopHintInfo { isPublic := c.ChannelFlags&lnwire.FFAnnounceChannel != 0 return &HopHintInfo{ @@ -782,7 +774,7 @@ type SelectHopHintsCfg struct { // FetchAllChannels retrieves all open channels currently stored // within the database. - FetchAllChannels func() ([]*chanstate.OpenChannel, error) + FetchAllChannels func() ([]*channeldb.OpenChannel, error) // IsChannelActive checks whether the channel identified by the provided // ChannelID is considered active. @@ -796,23 +788,12 @@ func newSelectHopHintsCfg(invoicesCfg *AddInvoiceConfig, maxHopHints int) *SelectHopHintsCfg { return &SelectHopHintsCfg{ - FetchAllChannels: invoicesCfg.ChanDB.FetchAllChannels, - IsChannelActive: invoicesCfg.IsChannelActive, - IsPublicNode: func(pubKey [33]byte) (bool, error) { - return invoicesCfg.Graph.IsPublicNode( - context.TODO(), pubKey, - ) - }, - FetchChannelEdgesByID: func(chanID uint64) ( - *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, - *models.ChannelEdgePolicy, error) { - - return invoicesCfg.Graph.FetchChannelEdgesByID( - context.TODO(), chanID, - ) - }, - GetAlias: invoicesCfg.GetAlias, - MaxHopHints: maxHopHints, + FetchAllChannels: invoicesCfg.ChanDB.FetchAllChannels, + IsChannelActive: invoicesCfg.IsChannelActive, + IsPublicNode: invoicesCfg.Graph.IsPublicNode, + FetchChannelEdgesByID: invoicesCfg.Graph.FetchChannelEdgesByID, + GetAlias: invoicesCfg.GetAlias, + MaxHopHints: maxHopHints, } } @@ -845,7 +826,7 @@ func sufficientHints(nHintsLeft int, currentAmount, // getPotentialHints returns a slice of open channels that should be considered // for the hopHint list in an invoice. The slice is sorted in descending order // based on the remote balance. -func getPotentialHints(cfg *SelectHopHintsCfg) ([]*chanstate.OpenChannel, +func getPotentialHints(cfg *SelectHopHintsCfg) ([]*channeldb.OpenChannel, error) { // TODO(positiveblue): get the channels slice already filtered by @@ -855,7 +836,7 @@ func getPotentialHints(cfg *SelectHopHintsCfg) ([]*chanstate.OpenChannel, return nil, err } - privateChannels := make([]*chanstate.OpenChannel, 0, len(openChannels)) + privateChannels := make([]*channeldb.OpenChannel, 0, len(openChannels)) for _, oc := range openChannels { isPublic := oc.ChannelFlags&lnwire.FFAnnounceChannel != 0 if !isPublic { @@ -877,7 +858,7 @@ func getPotentialHints(cfg *SelectHopHintsCfg) ([]*chanstate.OpenChannel, // shouldIncludeChannel returns true if the channel passes all the checks to // be a hopHint in a given invoice. func shouldIncludeChannel(cfg *SelectHopHintsCfg, - channel *chanstate.OpenChannel, + channel *channeldb.OpenChannel, alreadyIncluded map[uint64]bool) (zpay32.HopHint, lnwire.MilliSatoshi, bool) { @@ -923,7 +904,7 @@ func shouldIncludeChannel(cfg *SelectHopHintsCfg, // descending priority. func selectHopHints(cfg *SelectHopHintsCfg, nHintsLeft int, targetBandwidth lnwire.MilliSatoshi, - potentialHints []*chanstate.OpenChannel, + potentialHints []*channeldb.OpenChannel, alreadyIncluded map[uint64]bool) [][]zpay32.HopHint { currentBandwidth := lnwire.MilliSatoshi(0) diff --git a/lnrpc/invoicesrpc/addinvoice_test.go b/lnrpc/invoicesrpc/addinvoice_test.go index ac8408f52..1a6b8997e 100644 --- a/lnrpc/invoicesrpc/addinvoice_test.go +++ b/lnrpc/invoicesrpc/addinvoice_test.go @@ -6,13 +6,11 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/zpay32" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -78,15 +76,11 @@ func (h *hopHintsConfigMock) GetAlias( // FetchAllChannels retrieves all open channels currently stored // within the database. -func (h *hopHintsConfigMock) FetchAllChannels() ([]*chanstate.OpenChannel, +func (h *hopHintsConfigMock) FetchAllChannels() ([]*channeldb.OpenChannel, error) { args := h.Mock.Called() - - channels, ok := args.Get(0).([]*chanstate.OpenChannel) - require.True(h.t, ok) - - return channels, args.Error(1) + return args.Get(0).([]*channeldb.OpenChannel), args.Error(1) } // FetchChannelEdgesByID attempts to lookup the two directed edges for @@ -125,7 +119,7 @@ func getTestPubKey() *btcec.PublicKey { var shouldIncludeChannelTestCases = []struct { name string setupMock func(*hopHintsConfigMock) - channel *chanstate.OpenChannel + channel *channeldb.OpenChannel alreadyIncluded map[uint64]bool cfg *SelectHopHintsCfg hopHint zpay32.HopHint @@ -135,7 +129,7 @@ var shouldIncludeChannelTestCases = []struct { name: "already included channels should not be included " + "again", alreadyIncluded: map[uint64]bool{1: true}, - channel: &chanstate.OpenChannel{ + channel: &channeldb.OpenChannel{ ShortChannelID: lnwire.NewShortChanIDFromInt(1), }, include: false, @@ -150,7 +144,7 @@ var shouldIncludeChannelTestCases = []struct { "IsChannelActive", chanID, ).Once().Return(true) }, - channel: &chanstate.OpenChannel{ + channel: &channeldb.OpenChannel{ FundingOutpoint: wire.OutPoint{ Index: 0, }, @@ -167,7 +161,7 @@ var shouldIncludeChannelTestCases = []struct { "IsChannelActive", chanID, ).Once().Return(false) }, - channel: &chanstate.OpenChannel{ + channel: &channeldb.OpenChannel{ FundingOutpoint: wire.OutPoint{ Index: 0, }, @@ -189,7 +183,7 @@ var shouldIncludeChannelTestCases = []struct { "IsPublicNode", mock.Anything, ).Once().Return(false, nil) }, - channel: &chanstate.OpenChannel{ + channel: &channeldb.OpenChannel{ FundingOutpoint: wire.OutPoint{ Index: 0, }, @@ -224,7 +218,7 @@ var shouldIncludeChannelTestCases = []struct { "FetchChannelEdgesByID", mock.Anything, ).Once().Return(nil, nil, nil, fmt.Errorf("no edge")) }, - channel: &chanstate.OpenChannel{ + channel: &channeldb.OpenChannel{ FundingOutpoint: wire.OutPoint{ Index: 0, }, @@ -260,12 +254,12 @@ var shouldIncludeChannelTestCases = []struct { "GetAlias", mock.Anything, ).Once().Return(lnwire.ShortChannelID{}, nil) }, - channel: &chanstate.OpenChannel{ + channel: &channeldb.OpenChannel{ FundingOutpoint: wire.OutPoint{ Index: 0, }, IdentityPub: getTestPubKey(), - ChanType: chanstate.ScidAliasFeatureBit, + ChanType: channeldb.ScidAliasFeatureBit, }, include: false, }, { @@ -298,12 +292,12 @@ var shouldIncludeChannelTestCases = []struct { "GetAlias", mock.Anything, ).Once().Return(alias, nil) }, - channel: &chanstate.OpenChannel{ + channel: &channeldb.OpenChannel{ FundingOutpoint: wire.OutPoint{ Index: 0, }, IdentityPub: getTestPubKey(), - ChanType: chanstate.ScidAliasFeatureBit, + ChanType: channeldb.ScidAliasFeatureBit, }, include: false, }, { @@ -330,19 +324,10 @@ var shouldIncludeChannelTestCases = []struct { h.Mock.On( "FetchChannelEdgesByID", mock.Anything, ).Once().Return( - func() *models.ChannelEdgeInfo { - edge, err := models.NewV1Channel( - 0, chainhash.Hash{}, selectedPolicy, - route.Vertex{}, - &models.ChannelV1Fields{}, - ) - require.NoError(h.t, err) - - return edge - }(), - //nolint:ll + &models.ChannelEdgeInfo{ + NodeKey1Bytes: selectedPolicy, + }, &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, FeeBaseMSat: 1000, FeeProportionalMillionths: 20, TimeLockDelta: 13, @@ -351,7 +336,7 @@ var shouldIncludeChannelTestCases = []struct { nil, ) }, - channel: &chanstate.OpenChannel{ + channel: &channeldb.OpenChannel{ FundingOutpoint: wire.OutPoint{ Index: 1, }, @@ -389,16 +374,14 @@ var shouldIncludeChannelTestCases = []struct { ).Once().Return( &models.ChannelEdgeInfo{}, &models.ChannelEdgePolicy{}, - //nolint:ll &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, FeeBaseMSat: 1000, FeeProportionalMillionths: 20, TimeLockDelta: 13, }, nil, ) }, - channel: &chanstate.OpenChannel{ + channel: &channeldb.OpenChannel{ FundingOutpoint: wire.OutPoint{ Index: 1, }, @@ -436,9 +419,7 @@ var shouldIncludeChannelTestCases = []struct { ).Once().Return( &models.ChannelEdgeInfo{}, &models.ChannelEdgePolicy{}, - //nolint:ll &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, FeeBaseMSat: 1000, FeeProportionalMillionths: 20, TimeLockDelta: 13, @@ -451,13 +432,13 @@ var shouldIncludeChannelTestCases = []struct { "GetAlias", mock.Anything, ).Once().Return(aliasSCID, nil) }, - channel: &chanstate.OpenChannel{ + channel: &channeldb.OpenChannel{ FundingOutpoint: wire.OutPoint{ Index: 1, }, IdentityPub: getTestPubKey(), ShortChannelID: lnwire.NewShortChanIDFromInt(12), - ChanType: chanstate.ScidAliasFeatureBit, + ChanType: channeldb.ScidAliasFeatureBit, }, hopHint: zpay32.HopHint{ NodeID: getTestPubKey(), @@ -471,6 +452,7 @@ var shouldIncludeChannelTestCases = []struct { func TestShouldIncludeChannel(t *testing.T) { for _, tc := range shouldIncludeChannelTestCases { + tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() @@ -536,6 +518,7 @@ var sufficientHintsTestCases = []struct { func TestSufficientHints(t *testing.T) { for _, tc := range sufficientHintsTestCases { + tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() @@ -575,7 +558,7 @@ var populateHopHintsTestCases = []struct { setupMock: func(h *hopHintsConfigMock) { fundingOutpoint := wire.OutPoint{Index: 9} chanID := lnwire.NewChanIDFromOutPoint(fundingOutpoint) - allChannels := []*chanstate.OpenChannel{ + allChannels := []*channeldb.OpenChannel{ { FundingOutpoint: fundingOutpoint, ShortChannelID: lnwire.NewShortChanIDFromInt(9), @@ -622,9 +605,9 @@ var populateHopHintsTestCases = []struct { fundingOutpoint := wire.OutPoint{Index: 9} chanID := lnwire.NewChanIDFromOutPoint(fundingOutpoint) remoteBalance := lnwire.MilliSatoshi(10_000_000) - allChannels := []*chanstate.OpenChannel{ + allChannels := []*channeldb.OpenChannel{ { - LocalCommitment: chanstate.ChannelCommitment{ + LocalCommitment: channeldb.ChannelCommitment{ RemoteBalance: remoteBalance, }, FundingOutpoint: fundingOutpoint, @@ -673,12 +656,12 @@ var populateHopHintsTestCases = []struct { fundingOutpoint := wire.OutPoint{Index: 9} chanID := lnwire.NewChanIDFromOutPoint(fundingOutpoint) remoteBalance := lnwire.MilliSatoshi(10_000_000) - allChannels := []*chanstate.OpenChannel{ + allChannels := []*channeldb.OpenChannel{ // Because the channels with higher remote balance have // enough bandwidth we should never use this one. {}, { - LocalCommitment: chanstate.ChannelCommitment{ + LocalCommitment: channeldb.ChannelCommitment{ RemoteBalance: remoteBalance, }, FundingOutpoint: fundingOutpoint, @@ -872,11 +855,11 @@ func setupMockTwoChannels(h *hopHintsConfigMock) (lnwire.ChannelID, chanID2 := lnwire.NewChanIDFromOutPoint(fundingOutpoint2) remoteBalance2 := lnwire.MilliSatoshi(1_000_000) - allChannels := []*chanstate.OpenChannel{ + allChannels := []*channeldb.OpenChannel{ // After sorting we will first process chanID1 and then // chanID2. { - LocalCommitment: chanstate.ChannelCommitment{ + LocalCommitment: channeldb.ChannelCommitment{ RemoteBalance: remoteBalance2, }, FundingOutpoint: fundingOutpoint2, @@ -884,7 +867,7 @@ func setupMockTwoChannels(h *hopHintsConfigMock) (lnwire.ChannelID, IdentityPub: getTestPubKey(), }, { - LocalCommitment: chanstate.ChannelCommitment{ + LocalCommitment: channeldb.ChannelCommitment{ RemoteBalance: remoteBalance1, }, FundingOutpoint: fundingOutpoint1, @@ -902,6 +885,7 @@ func setupMockTwoChannels(h *hopHintsConfigMock) (lnwire.ChannelID, func TestPopulateHopHints(t *testing.T) { for _, tc := range populateHopHintsTestCases { + tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() diff --git a/lnrpc/invoicesrpc/config_active.go b/lnrpc/invoicesrpc/config_active.go index 3a162bf72..f2d2b04bb 100644 --- a/lnrpc/invoicesrpc/config_active.go +++ b/lnrpc/invoicesrpc/config_active.go @@ -4,8 +4,8 @@ package invoicesrpc import ( - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/btcsuite/btcd/chaincfg" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/macaroons" @@ -55,9 +55,9 @@ type Config struct { // graph. Graph GraphSource - // ChanStateDB is a possibly replicated db instance which contains open - // channel state. - ChanStateDB chanstate.OpenChannelStore + // ChanStateDB is a possibly replicated db instance which contains the + // channel state + ChanStateDB *channeldb.ChannelStateDB // GenInvoiceFeatures returns a feature containing feature bits that // should be advertised on freshly generated invoices. diff --git a/lnrpc/invoicesrpc/htlc_modifier.go b/lnrpc/invoicesrpc/htlc_modifier.go index 64c39995b..00259962c 100644 --- a/lnrpc/invoicesrpc/htlc_modifier.go +++ b/lnrpc/invoicesrpc/htlc_modifier.go @@ -3,7 +3,7 @@ package invoicesrpc import ( "fmt" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" "github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/lnwire" ) diff --git a/lnrpc/invoicesrpc/interfaces.go b/lnrpc/invoicesrpc/interfaces.go index 99a8bef23..df47d2f24 100644 --- a/lnrpc/invoicesrpc/interfaces.go +++ b/lnrpc/invoicesrpc/interfaces.go @@ -1,8 +1,6 @@ package invoicesrpc import ( - "context" - "github.com/lightningnetwork/lnd/graph/db/models" ) @@ -11,12 +9,11 @@ type GraphSource interface { // FetchChannelEdgesByID attempts to look up the two directed edges for // the channel identified by the channel ID. If the channel can't be // found, then graphdb.ErrEdgeNotFound is returned. - FetchChannelEdgesByID(ctx context.Context, chanID uint64) ( - *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, - *models.ChannelEdgePolicy, error) + FetchChannelEdgesByID(chanID uint64) (*models.ChannelEdgeInfo, + *models.ChannelEdgePolicy, *models.ChannelEdgePolicy, error) // IsPublicNode is a helper method that determines whether the node with // the given public key is seen as a public node in the graph from the // graph's source node's point of view. - IsPublicNode(ctx context.Context, pubKey [33]byte) (bool, error) + IsPublicNode(pubKey [33]byte) (bool, error) } diff --git a/lnrpc/invoicesrpc/invoices.pb.go b/lnrpc/invoicesrpc/invoices.pb.go index 1a551bded..ca2b54e7d 100644 --- a/lnrpc/invoicesrpc/invoices.pb.go +++ b/lnrpc/invoicesrpc/invoices.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.33.0 // protoc v3.21.12 // source: invoicesrpc/invoices.proto @@ -12,7 +12,6 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" - unsafe "unsafe" ) const ( @@ -79,19 +78,22 @@ func (LookupModifier) EnumDescriptor() ([]byte, []int) { } type CancelInvoiceMsg struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Hash corresponding to the (hold) invoice to cancel. When using // REST, this field must be encoded as base64. - PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"` } func (x *CancelInvoiceMsg) Reset() { *x = CancelInvoiceMsg{} - mi := &file_invoicesrpc_invoices_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_invoicesrpc_invoices_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *CancelInvoiceMsg) String() string { @@ -102,7 +104,7 @@ func (*CancelInvoiceMsg) ProtoMessage() {} func (x *CancelInvoiceMsg) ProtoReflect() protoreflect.Message { mi := &file_invoicesrpc_invoices_proto_msgTypes[0] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -125,16 +127,18 @@ func (x *CancelInvoiceMsg) GetPaymentHash() []byte { } type CancelInvoiceResp struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *CancelInvoiceResp) Reset() { *x = CancelInvoiceResp{} - mi := &file_invoicesrpc_invoices_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_invoicesrpc_invoices_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *CancelInvoiceResp) String() string { @@ -145,7 +149,7 @@ func (*CancelInvoiceResp) ProtoMessage() {} func (x *CancelInvoiceResp) ProtoReflect() protoreflect.Message { mi := &file_invoicesrpc_invoices_proto_msgTypes[1] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -161,7 +165,10 @@ func (*CancelInvoiceResp) Descriptor() ([]byte, []int) { } type AddHoldInvoiceRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // An optional memo to attach along with the invoice. Used for record keeping // purposes for the invoice's creator, and will also be set in the description // field of the encoded payment request if the description_hash field is not @@ -191,16 +198,16 @@ type AddHoldInvoiceRequest struct { // invoice's destination. RouteHints []*lnrpc.RouteHint `protobuf:"bytes,8,rep,name=route_hints,json=routeHints,proto3" json:"route_hints,omitempty"` // Whether this invoice should include routing hints for private channels. - Private bool `protobuf:"varint,9,opt,name=private,proto3" json:"private,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Private bool `protobuf:"varint,9,opt,name=private,proto3" json:"private,omitempty"` } func (x *AddHoldInvoiceRequest) Reset() { *x = AddHoldInvoiceRequest{} - mi := &file_invoicesrpc_invoices_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_invoicesrpc_invoices_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *AddHoldInvoiceRequest) String() string { @@ -211,7 +218,7 @@ func (*AddHoldInvoiceRequest) ProtoMessage() {} func (x *AddHoldInvoiceRequest) ProtoReflect() protoreflect.Message { mi := &file_invoicesrpc_invoices_proto_msgTypes[2] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -297,7 +304,10 @@ func (x *AddHoldInvoiceRequest) GetPrivate() bool { } type AddHoldInvoiceResp struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // A bare-bones invoice for a payment within the Lightning Network. With the // details of the invoice, the sender has all the data necessary to send a // payment to the recipient. @@ -311,16 +321,16 @@ type AddHoldInvoiceResp struct { // the payment secret in specifications (e.g. BOLT 11). This value should // be used in all payments for this invoice as we require it for end to end // security. - PaymentAddr []byte `protobuf:"bytes,3,opt,name=payment_addr,json=paymentAddr,proto3" json:"payment_addr,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + PaymentAddr []byte `protobuf:"bytes,3,opt,name=payment_addr,json=paymentAddr,proto3" json:"payment_addr,omitempty"` } func (x *AddHoldInvoiceResp) Reset() { *x = AddHoldInvoiceResp{} - mi := &file_invoicesrpc_invoices_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_invoicesrpc_invoices_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *AddHoldInvoiceResp) String() string { @@ -331,7 +341,7 @@ func (*AddHoldInvoiceResp) ProtoMessage() {} func (x *AddHoldInvoiceResp) ProtoReflect() protoreflect.Message { mi := &file_invoicesrpc_invoices_proto_msgTypes[3] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -368,19 +378,22 @@ func (x *AddHoldInvoiceResp) GetPaymentAddr() []byte { } type SettleInvoiceMsg struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Externally discovered pre-image that should be used to settle the hold // invoice. - Preimage []byte `protobuf:"bytes,1,opt,name=preimage,proto3" json:"preimage,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Preimage []byte `protobuf:"bytes,1,opt,name=preimage,proto3" json:"preimage,omitempty"` } func (x *SettleInvoiceMsg) Reset() { *x = SettleInvoiceMsg{} - mi := &file_invoicesrpc_invoices_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_invoicesrpc_invoices_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SettleInvoiceMsg) String() string { @@ -391,7 +404,7 @@ func (*SettleInvoiceMsg) ProtoMessage() {} func (x *SettleInvoiceMsg) ProtoReflect() protoreflect.Message { mi := &file_invoicesrpc_invoices_proto_msgTypes[4] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -414,16 +427,18 @@ func (x *SettleInvoiceMsg) GetPreimage() []byte { } type SettleInvoiceResp struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *SettleInvoiceResp) Reset() { *x = SettleInvoiceResp{} - mi := &file_invoicesrpc_invoices_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_invoicesrpc_invoices_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SettleInvoiceResp) String() string { @@ -434,7 +449,7 @@ func (*SettleInvoiceResp) ProtoMessage() {} func (x *SettleInvoiceResp) ProtoReflect() protoreflect.Message { mi := &file_invoicesrpc_invoices_proto_msgTypes[5] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -450,19 +465,22 @@ func (*SettleInvoiceResp) Descriptor() ([]byte, []int) { } type SubscribeSingleInvoiceRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Hash corresponding to the (hold) invoice to subscribe to. When using // REST, this field must be encoded as base64url. - RHash []byte `protobuf:"bytes,2,opt,name=r_hash,json=rHash,proto3" json:"r_hash,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + RHash []byte `protobuf:"bytes,2,opt,name=r_hash,json=rHash,proto3" json:"r_hash,omitempty"` } func (x *SubscribeSingleInvoiceRequest) Reset() { *x = SubscribeSingleInvoiceRequest{} - mi := &file_invoicesrpc_invoices_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_invoicesrpc_invoices_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SubscribeSingleInvoiceRequest) String() string { @@ -473,7 +491,7 @@ func (*SubscribeSingleInvoiceRequest) ProtoMessage() {} func (x *SubscribeSingleInvoiceRequest) ProtoReflect() protoreflect.Message { mi := &file_invoicesrpc_invoices_proto_msgTypes[6] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -496,23 +514,26 @@ func (x *SubscribeSingleInvoiceRequest) GetRHash() []byte { } type LookupInvoiceMsg struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to InvoiceRef: + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to InvoiceRef: // // *LookupInvoiceMsg_PaymentHash // *LookupInvoiceMsg_PaymentAddr // *LookupInvoiceMsg_SetId InvoiceRef isLookupInvoiceMsg_InvoiceRef `protobuf_oneof:"invoice_ref"` LookupModifier LookupModifier `protobuf:"varint,4,opt,name=lookup_modifier,json=lookupModifier,proto3,enum=invoicesrpc.LookupModifier" json:"lookup_modifier,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *LookupInvoiceMsg) Reset() { *x = LookupInvoiceMsg{} - mi := &file_invoicesrpc_invoices_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_invoicesrpc_invoices_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *LookupInvoiceMsg) String() string { @@ -523,7 +544,7 @@ func (*LookupInvoiceMsg) ProtoMessage() {} func (x *LookupInvoiceMsg) ProtoReflect() protoreflect.Message { mi := &file_invoicesrpc_invoices_proto_msgTypes[7] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -538,36 +559,30 @@ func (*LookupInvoiceMsg) Descriptor() ([]byte, []int) { return file_invoicesrpc_invoices_proto_rawDescGZIP(), []int{7} } -func (x *LookupInvoiceMsg) GetInvoiceRef() isLookupInvoiceMsg_InvoiceRef { - if x != nil { - return x.InvoiceRef +func (m *LookupInvoiceMsg) GetInvoiceRef() isLookupInvoiceMsg_InvoiceRef { + if m != nil { + return m.InvoiceRef } return nil } func (x *LookupInvoiceMsg) GetPaymentHash() []byte { - if x != nil { - if x, ok := x.InvoiceRef.(*LookupInvoiceMsg_PaymentHash); ok { - return x.PaymentHash - } + if x, ok := x.GetInvoiceRef().(*LookupInvoiceMsg_PaymentHash); ok { + return x.PaymentHash } return nil } func (x *LookupInvoiceMsg) GetPaymentAddr() []byte { - if x != nil { - if x, ok := x.InvoiceRef.(*LookupInvoiceMsg_PaymentAddr); ok { - return x.PaymentAddr - } + if x, ok := x.GetInvoiceRef().(*LookupInvoiceMsg_PaymentAddr); ok { + return x.PaymentAddr } return nil } func (x *LookupInvoiceMsg) GetSetId() []byte { - if x != nil { - if x, ok := x.InvoiceRef.(*LookupInvoiceMsg_SetId); ok { - return x.SetId - } + if x, ok := x.GetInvoiceRef().(*LookupInvoiceMsg_SetId); ok { + return x.SetId } return nil } @@ -604,20 +619,23 @@ func (*LookupInvoiceMsg_SetId) isLookupInvoiceMsg_InvoiceRef() {} // CircuitKey is a unique identifier for an HTLC. type CircuitKey struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The id of the channel that the is part of this circuit. ChanId uint64 `protobuf:"varint,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"` // The index of the incoming htlc in the incoming channel. - HtlcId uint64 `protobuf:"varint,2,opt,name=htlc_id,json=htlcId,proto3" json:"htlc_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + HtlcId uint64 `protobuf:"varint,2,opt,name=htlc_id,json=htlcId,proto3" json:"htlc_id,omitempty"` } func (x *CircuitKey) Reset() { *x = CircuitKey{} - mi := &file_invoicesrpc_invoices_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_invoicesrpc_invoices_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *CircuitKey) String() string { @@ -628,7 +646,7 @@ func (*CircuitKey) ProtoMessage() {} func (x *CircuitKey) ProtoReflect() protoreflect.Message { mi := &file_invoicesrpc_invoices_proto_msgTypes[8] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -658,7 +676,10 @@ func (x *CircuitKey) GetHtlcId() uint64 { } type HtlcModifyRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The invoice the intercepted HTLC is attempting to settle. The HTLCs in // the invoice are only HTLCs that have already been accepted or settled, // not including the current intercepted HTLC. @@ -672,16 +693,16 @@ type HtlcModifyRequest struct { // The current block height. CurrentHeight uint32 `protobuf:"varint,5,opt,name=current_height,json=currentHeight,proto3" json:"current_height,omitempty"` // The wire message custom records of the exit HTLC. - ExitHtlcWireCustomRecords map[uint64][]byte `protobuf:"bytes,6,rep,name=exit_htlc_wire_custom_records,json=exitHtlcWireCustomRecords,proto3" json:"exit_htlc_wire_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + ExitHtlcWireCustomRecords map[uint64][]byte `protobuf:"bytes,6,rep,name=exit_htlc_wire_custom_records,json=exitHtlcWireCustomRecords,proto3" json:"exit_htlc_wire_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *HtlcModifyRequest) Reset() { *x = HtlcModifyRequest{} - mi := &file_invoicesrpc_invoices_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_invoicesrpc_invoices_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *HtlcModifyRequest) String() string { @@ -692,7 +713,7 @@ func (*HtlcModifyRequest) ProtoMessage() {} func (x *HtlcModifyRequest) ProtoReflect() protoreflect.Message { mi := &file_invoicesrpc_invoices_proto_msgTypes[9] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -750,7 +771,10 @@ func (x *HtlcModifyRequest) GetExitHtlcWireCustomRecords() map[uint64][]byte { } type HtlcModifyResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The circuit key of the HTLC that the client wants to modify. CircuitKey *CircuitKey `protobuf:"bytes,1,opt,name=circuit_key,json=circuitKey,proto3" json:"circuit_key,omitempty"` // The modified amount in milli-satoshi that the exit HTLC is paying. This @@ -762,16 +786,16 @@ type HtlcModifyResponse struct { // be cancelled. The interceptor client may set this field if some // unexpected behavior is encountered. Setting this will ignore the amt_paid // field. - CancelSet bool `protobuf:"varint,3,opt,name=cancel_set,json=cancelSet,proto3" json:"cancel_set,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + CancelSet bool `protobuf:"varint,3,opt,name=cancel_set,json=cancelSet,proto3" json:"cancel_set,omitempty"` } func (x *HtlcModifyResponse) Reset() { *x = HtlcModifyResponse{} - mi := &file_invoicesrpc_invoices_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_invoicesrpc_invoices_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *HtlcModifyResponse) String() string { @@ -782,7 +806,7 @@ func (*HtlcModifyResponse) ProtoMessage() {} func (x *HtlcModifyResponse) ProtoReflect() protoreflect.Message { mi := &file_invoicesrpc_invoices_proto_msgTypes[10] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -820,90 +844,165 @@ func (x *HtlcModifyResponse) GetCancelSet() bool { var File_invoicesrpc_invoices_proto protoreflect.FileDescriptor -const file_invoicesrpc_invoices_proto_rawDesc = "" + - "\n" + - "\x1ainvoicesrpc/invoices.proto\x12\vinvoicesrpc\x1a\x0flightning.proto\"5\n" + - "\x10CancelInvoiceMsg\x12!\n" + - "\fpayment_hash\x18\x01 \x01(\fR\vpaymentHash\"\x13\n" + - "\x11CancelInvoiceResp\"\xca\x02\n" + - "\x15AddHoldInvoiceRequest\x12\x12\n" + - "\x04memo\x18\x01 \x01(\tR\x04memo\x12\x12\n" + - "\x04hash\x18\x02 \x01(\fR\x04hash\x12\x14\n" + - "\x05value\x18\x03 \x01(\x03R\x05value\x12\x1d\n" + - "\n" + - "value_msat\x18\n" + - " \x01(\x03R\tvalueMsat\x12)\n" + - "\x10description_hash\x18\x04 \x01(\fR\x0fdescriptionHash\x12\x16\n" + - "\x06expiry\x18\x05 \x01(\x03R\x06expiry\x12#\n" + - "\rfallback_addr\x18\x06 \x01(\tR\ffallbackAddr\x12\x1f\n" + - "\vcltv_expiry\x18\a \x01(\x04R\n" + - "cltvExpiry\x121\n" + - "\vroute_hints\x18\b \x03(\v2\x10.lnrpc.RouteHintR\n" + - "routeHints\x12\x18\n" + - "\aprivate\x18\t \x01(\bR\aprivate\"}\n" + - "\x12AddHoldInvoiceResp\x12'\n" + - "\x0fpayment_request\x18\x01 \x01(\tR\x0epaymentRequest\x12\x1b\n" + - "\tadd_index\x18\x02 \x01(\x04R\baddIndex\x12!\n" + - "\fpayment_addr\x18\x03 \x01(\fR\vpaymentAddr\".\n" + - "\x10SettleInvoiceMsg\x12\x1a\n" + - "\bpreimage\x18\x01 \x01(\fR\bpreimage\"\x13\n" + - "\x11SettleInvoiceResp\"<\n" + - "\x1dSubscribeSingleInvoiceRequest\x12\x15\n" + - "\x06r_hash\x18\x02 \x01(\fR\x05rHashJ\x04\b\x01\x10\x02\"\xca\x01\n" + - "\x10LookupInvoiceMsg\x12#\n" + - "\fpayment_hash\x18\x01 \x01(\fH\x00R\vpaymentHash\x12#\n" + - "\fpayment_addr\x18\x02 \x01(\fH\x00R\vpaymentAddr\x12\x17\n" + - "\x06set_id\x18\x03 \x01(\fH\x00R\x05setId\x12D\n" + - "\x0flookup_modifier\x18\x04 \x01(\x0e2\x1b.invoicesrpc.LookupModifierR\x0elookupModifierB\r\n" + - "\vinvoice_ref\">\n" + - "\n" + - "CircuitKey\x12\x17\n" + - "\achan_id\x18\x01 \x01(\x04R\x06chanId\x12\x17\n" + - "\ahtlc_id\x18\x02 \x01(\x04R\x06htlcId\"\xcd\x03\n" + - "\x11HtlcModifyRequest\x12(\n" + - "\ainvoice\x18\x01 \x01(\v2\x0e.lnrpc.InvoiceR\ainvoice\x12J\n" + - "\x15exit_htlc_circuit_key\x18\x02 \x01(\v2\x17.invoicesrpc.CircuitKeyR\x12exitHtlcCircuitKey\x12\"\n" + - "\rexit_htlc_amt\x18\x03 \x01(\x04R\vexitHtlcAmt\x12(\n" + - "\x10exit_htlc_expiry\x18\x04 \x01(\rR\x0eexitHtlcExpiry\x12%\n" + - "\x0ecurrent_height\x18\x05 \x01(\rR\rcurrentHeight\x12\x7f\n" + - "\x1dexit_htlc_wire_custom_records\x18\x06 \x03(\v2=.invoicesrpc.HtlcModifyRequest.ExitHtlcWireCustomRecordsEntryR\x19exitHtlcWireCustomRecords\x1aL\n" + - "\x1eExitHtlcWireCustomRecordsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\x04R\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\fR\x05value:\x028\x01\"\x9a\x01\n" + - "\x12HtlcModifyResponse\x128\n" + - "\vcircuit_key\x18\x01 \x01(\v2\x17.invoicesrpc.CircuitKeyR\n" + - "circuitKey\x12\x1e\n" + - "\bamt_paid\x18\x02 \x01(\x04H\x00R\aamtPaid\x88\x01\x01\x12\x1d\n" + - "\n" + - "cancel_set\x18\x03 \x01(\bR\tcancelSetB\v\n" + - "\t_amt_paid*D\n" + - "\x0eLookupModifier\x12\v\n" + - "\aDEFAULT\x10\x00\x12\x11\n" + - "\rHTLC_SET_ONLY\x10\x01\x12\x12\n" + - "\x0eHTLC_SET_BLANK\x10\x022\xf0\x03\n" + - "\bInvoices\x12V\n" + - "\x16SubscribeSingleInvoice\x12*.invoicesrpc.SubscribeSingleInvoiceRequest\x1a\x0e.lnrpc.Invoice0\x01\x12N\n" + - "\rCancelInvoice\x12\x1d.invoicesrpc.CancelInvoiceMsg\x1a\x1e.invoicesrpc.CancelInvoiceResp\x12U\n" + - "\x0eAddHoldInvoice\x12\".invoicesrpc.AddHoldInvoiceRequest\x1a\x1f.invoicesrpc.AddHoldInvoiceResp\x12N\n" + - "\rSettleInvoice\x12\x1d.invoicesrpc.SettleInvoiceMsg\x1a\x1e.invoicesrpc.SettleInvoiceResp\x12@\n" + - "\x0fLookupInvoiceV2\x12\x1d.invoicesrpc.LookupInvoiceMsg\x1a\x0e.lnrpc.Invoice\x12S\n" + - "\fHtlcModifier\x12\x1f.invoicesrpc.HtlcModifyResponse\x1a\x1e.invoicesrpc.HtlcModifyRequest(\x010\x01B3Z1github.com/lightningnetwork/lnd/lnrpc/invoicesrpcb\x06proto3" +var file_invoicesrpc_invoices_proto_rawDesc = []byte{ + 0x0a, 0x1a, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2f, 0x69, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x69, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x1a, 0x0f, 0x6c, 0x69, 0x67, 0x68, 0x74, + 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x35, 0x0a, 0x10, 0x43, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x12, 0x21, + 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, + 0x68, 0x22, 0x13, 0x0a, 0x11, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x22, 0xca, 0x02, 0x0a, 0x15, 0x41, 0x64, 0x64, 0x48, 0x6f, + 0x6c, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6d, 0x65, 0x6d, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x29, 0x0a, + 0x10, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, + 0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, + 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x61, 0x64, 0x64, + 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, + 0x6b, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78, + 0x70, 0x69, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x63, 0x6c, 0x74, 0x76, + 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x31, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, + 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x72, + 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, + 0x76, 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, + 0x61, 0x74, 0x65, 0x22, 0x7d, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x48, 0x6f, 0x6c, 0x64, 0x49, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x61, 0x64, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, + 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64, + 0x64, 0x72, 0x22, 0x2e, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, + 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, + 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, + 0x67, 0x65, 0x22, 0x13, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, + 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x22, 0x3c, 0x0a, 0x1d, 0x53, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x62, 0x65, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x5f, 0x68, 0x61, + 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x48, 0x61, 0x73, 0x68, 0x4a, + 0x04, 0x08, 0x01, 0x10, 0x02, 0x22, 0xca, 0x01, 0x0a, 0x10, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, + 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x12, 0x23, 0x0a, 0x0c, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x48, 0x00, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, + 0x23, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x41, 0x64, 0x64, 0x72, 0x12, 0x17, 0x0a, 0x06, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x44, 0x0a, + 0x0f, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x5f, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x73, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x4d, 0x6f, 0x64, 0x69, 0x66, + 0x69, 0x65, 0x72, 0x52, 0x0e, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x4d, 0x6f, 0x64, 0x69, 0x66, + 0x69, 0x65, 0x72, 0x42, 0x0d, 0x0a, 0x0b, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x72, + 0x65, 0x66, 0x22, 0x3e, 0x0a, 0x0a, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, + 0x12, 0x17, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x74, 0x6c, + 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x74, 0x6c, 0x63, + 0x49, 0x64, 0x22, 0xcd, 0x03, 0x0a, 0x11, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x6f, 0x64, 0x69, 0x66, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x07, 0x69, 0x6e, 0x76, 0x6f, + 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x07, 0x69, 0x6e, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x15, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, + 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x17, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x12, 0x65, 0x78, 0x69, 0x74, + 0x48, 0x74, 0x6c, 0x63, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x22, + 0x0a, 0x0d, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x61, 0x6d, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x65, 0x78, 0x69, 0x74, 0x48, 0x74, 0x6c, 0x63, 0x41, + 0x6d, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, + 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x65, 0x78, + 0x69, 0x74, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x25, 0x0a, 0x0e, + 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x12, 0x7f, 0x0a, 0x1d, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x68, 0x74, 0x6c, 0x63, + 0x5f, 0x77, 0x69, 0x72, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x69, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x6f, 0x64, + 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x45, 0x78, 0x69, 0x74, 0x48, + 0x74, 0x6c, 0x63, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x19, 0x65, 0x78, 0x69, 0x74, 0x48, + 0x74, 0x6c, 0x63, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x4c, 0x0a, 0x1e, 0x45, 0x78, 0x69, 0x74, 0x48, 0x74, 0x6c, 0x63, + 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x22, 0x9a, 0x01, 0x0a, 0x12, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x6f, 0x64, 0x69, 0x66, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x0b, 0x63, 0x69, 0x72, + 0x63, 0x75, 0x69, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, + 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x69, 0x72, + 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x0a, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, + 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x07, 0x61, 0x6d, 0x74, 0x50, 0x61, 0x69, 0x64, + 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x5f, 0x73, 0x65, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x53, + 0x65, 0x74, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61, 0x69, 0x64, 0x2a, + 0x44, 0x0a, 0x0e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, + 0x72, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x11, + 0x0a, 0x0d, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x54, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, + 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x54, 0x5f, 0x42, 0x4c, + 0x41, 0x4e, 0x4b, 0x10, 0x02, 0x32, 0xf0, 0x03, 0x0a, 0x08, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x73, 0x12, 0x56, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, + 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x2a, 0x2e, 0x69, + 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x62, 0x65, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x30, 0x01, 0x12, 0x4e, 0x0a, 0x0d, 0x43, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x1d, 0x2e, 0x69, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, + 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x1a, 0x1e, 0x2e, 0x69, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x49, + 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x55, 0x0a, 0x0e, 0x41, 0x64, + 0x64, 0x48, 0x6f, 0x6c, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x22, 0x2e, 0x69, + 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x48, 0x6f, + 0x6c, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1f, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x64, 0x64, 0x48, 0x6f, 0x6c, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x12, 0x4e, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x12, 0x1d, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x4d, 0x73, + 0x67, 0x1a, 0x1e, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x12, 0x40, 0x0a, 0x0f, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x56, 0x32, 0x12, 0x1d, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, + 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x4d, 0x73, 0x67, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, + 0x69, 0x63, 0x65, 0x12, 0x53, 0x0a, 0x0c, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x6f, 0x64, 0x69, 0x66, + 0x69, 0x65, 0x72, 0x12, 0x1f, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, + 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x1e, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, + 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, + 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2f, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} var ( file_invoicesrpc_invoices_proto_rawDescOnce sync.Once - file_invoicesrpc_invoices_proto_rawDescData []byte + file_invoicesrpc_invoices_proto_rawDescData = file_invoicesrpc_invoices_proto_rawDesc ) func file_invoicesrpc_invoices_proto_rawDescGZIP() []byte { file_invoicesrpc_invoices_proto_rawDescOnce.Do(func() { - file_invoicesrpc_invoices_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_invoicesrpc_invoices_proto_rawDesc), len(file_invoicesrpc_invoices_proto_rawDesc))) + file_invoicesrpc_invoices_proto_rawDescData = protoimpl.X.CompressGZIP(file_invoicesrpc_invoices_proto_rawDescData) }) return file_invoicesrpc_invoices_proto_rawDescData } var file_invoicesrpc_invoices_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_invoicesrpc_invoices_proto_msgTypes = make([]protoimpl.MessageInfo, 12) -var file_invoicesrpc_invoices_proto_goTypes = []any{ +var file_invoicesrpc_invoices_proto_goTypes = []interface{}{ (LookupModifier)(0), // 0: invoicesrpc.LookupModifier (*CancelInvoiceMsg)(nil), // 1: invoicesrpc.CancelInvoiceMsg (*CancelInvoiceResp)(nil), // 2: invoicesrpc.CancelInvoiceResp @@ -951,17 +1050,151 @@ func file_invoicesrpc_invoices_proto_init() { if File_invoicesrpc_invoices_proto != nil { return } - file_invoicesrpc_invoices_proto_msgTypes[7].OneofWrappers = []any{ + if !protoimpl.UnsafeEnabled { + file_invoicesrpc_invoices_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CancelInvoiceMsg); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_invoicesrpc_invoices_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CancelInvoiceResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_invoicesrpc_invoices_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddHoldInvoiceRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_invoicesrpc_invoices_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddHoldInvoiceResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_invoicesrpc_invoices_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SettleInvoiceMsg); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_invoicesrpc_invoices_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SettleInvoiceResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_invoicesrpc_invoices_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SubscribeSingleInvoiceRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_invoicesrpc_invoices_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LookupInvoiceMsg); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_invoicesrpc_invoices_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CircuitKey); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_invoicesrpc_invoices_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HtlcModifyRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_invoicesrpc_invoices_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HtlcModifyResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_invoicesrpc_invoices_proto_msgTypes[7].OneofWrappers = []interface{}{ (*LookupInvoiceMsg_PaymentHash)(nil), (*LookupInvoiceMsg_PaymentAddr)(nil), (*LookupInvoiceMsg_SetId)(nil), } - file_invoicesrpc_invoices_proto_msgTypes[10].OneofWrappers = []any{} + file_invoicesrpc_invoices_proto_msgTypes[10].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_invoicesrpc_invoices_proto_rawDesc), len(file_invoicesrpc_invoices_proto_rawDesc)), + RawDescriptor: file_invoicesrpc_invoices_proto_rawDesc, NumEnums: 1, NumMessages: 12, NumExtensions: 0, @@ -973,6 +1206,7 @@ func file_invoicesrpc_invoices_proto_init() { MessageInfos: file_invoicesrpc_invoices_proto_msgTypes, }.Build() File_invoicesrpc_invoices_proto = out.File + file_invoicesrpc_invoices_proto_rawDesc = nil file_invoicesrpc_invoices_proto_goTypes = nil file_invoicesrpc_invoices_proto_depIdxs = nil } diff --git a/lnrpc/invoicesrpc/utils.go b/lnrpc/invoicesrpc/utils.go index f59c6dfbb..096c8305d 100644 --- a/lnrpc/invoicesrpc/utils.go +++ b/lnrpc/invoicesrpc/utils.go @@ -7,7 +7,7 @@ import ( "slices" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" "github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwire" diff --git a/lnrpc/lightning.pb.go b/lnrpc/lightning.pb.go index 72352bdf1..d08cf3a6a 100644 --- a/lnrpc/lightning.pb.go +++ b/lnrpc/lightning.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.33.0 // protoc v3.21.12 // source: lightning.proto @@ -11,7 +11,6 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" - unsafe "unsafe" ) const ( @@ -230,19 +229,8 @@ const ( // to guarantee that the channel initiator has no incentives to close a leased // channel before its maturity date. CommitmentType_SCRIPT_ENFORCED_LEASE CommitmentType = 4 - // The production taproot channel type that uses musig2 for the funding - // output and the new tapscript features, with final scripts and feature - // bits 80/81. This is the recommended taproot variant; new integrations - // should select this enum value. - CommitmentType_TAPROOT CommitmentType = 7 - // Deprecated alias for TAPROOT, preserved so existing clients that select - // the production taproot channel type by its historic name continue to - // compile and serialize against the same wire value. - CommitmentType_SIMPLE_TAPROOT_FINAL CommitmentType = 7 - // A legacy taproot channel type that uses musig2 for the funding output and - // the new tapscript features, but with development scripts and the staging - // feature bits. Retained for compatibility with peers that have not upgraded - // to TAPROOT; new integrations should prefer TAPROOT. + // A channel that uses musig2 for the funding output, and the new tapscript + // features where relevant. CommitmentType_SIMPLE_TAPROOT CommitmentType = 5 // Identical to the SIMPLE_TAPROOT channel type, but with extra functionality. // This channel type also commits to additional meta data in the tapscript @@ -258,8 +246,6 @@ var ( 2: "STATIC_REMOTE_KEY", 3: "ANCHORS", 4: "SCRIPT_ENFORCED_LEASE", - 7: "TAPROOT", - // Duplicate value: 7: "SIMPLE_TAPROOT_FINAL", 5: "SIMPLE_TAPROOT", 6: "SIMPLE_TAPROOT_OVERLAY", } @@ -269,8 +255,6 @@ var ( "STATIC_REMOTE_KEY": 2, "ANCHORS": 3, "SCRIPT_ENFORCED_LEASE": 4, - "TAPROOT": 7, - "SIMPLE_TAPROOT_FINAL": 7, "SIMPLE_TAPROOT": 5, "SIMPLE_TAPROOT_OVERLAY": 6, } @@ -487,58 +471,6 @@ func (ResolutionOutcome) EnumDescriptor() ([]byte, []int) { return file_lightning_proto_rawDescGZIP(), []int{6} } -type GraphCacheStatus int32 - -const ( - GraphCacheStatus_GRAPH_CACHE_STATUS_DISABLED GraphCacheStatus = 0 - GraphCacheStatus_GRAPH_CACHE_STATUS_LOADING GraphCacheStatus = 1 - GraphCacheStatus_GRAPH_CACHE_STATUS_LOADED GraphCacheStatus = 2 - GraphCacheStatus_GRAPH_CACHE_STATUS_FAILED GraphCacheStatus = 3 -) - -// Enum value maps for GraphCacheStatus. -var ( - GraphCacheStatus_name = map[int32]string{ - 0: "GRAPH_CACHE_STATUS_DISABLED", - 1: "GRAPH_CACHE_STATUS_LOADING", - 2: "GRAPH_CACHE_STATUS_LOADED", - 3: "GRAPH_CACHE_STATUS_FAILED", - } - GraphCacheStatus_value = map[string]int32{ - "GRAPH_CACHE_STATUS_DISABLED": 0, - "GRAPH_CACHE_STATUS_LOADING": 1, - "GRAPH_CACHE_STATUS_LOADED": 2, - "GRAPH_CACHE_STATUS_FAILED": 3, - } -) - -func (x GraphCacheStatus) Enum() *GraphCacheStatus { - p := new(GraphCacheStatus) - *p = x - return p -} - -func (x GraphCacheStatus) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (GraphCacheStatus) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[7].Descriptor() -} - -func (GraphCacheStatus) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[7] -} - -func (x GraphCacheStatus) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use GraphCacheStatus.Descriptor instead. -func (GraphCacheStatus) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{7} -} - type NodeMetricType int32 const ( @@ -569,11 +501,11 @@ func (x NodeMetricType) String() string { } func (NodeMetricType) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[8].Descriptor() + return file_lightning_proto_enumTypes[7].Descriptor() } func (NodeMetricType) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[8] + return &file_lightning_proto_enumTypes[7] } func (x NodeMetricType) Number() protoreflect.EnumNumber { @@ -582,7 +514,7 @@ func (x NodeMetricType) Number() protoreflect.EnumNumber { // Deprecated: Use NodeMetricType.Descriptor instead. func (NodeMetricType) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{8} + return file_lightning_proto_rawDescGZIP(), []int{7} } type InvoiceHTLCState int32 @@ -618,11 +550,11 @@ func (x InvoiceHTLCState) String() string { } func (InvoiceHTLCState) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[9].Descriptor() + return file_lightning_proto_enumTypes[8].Descriptor() } func (InvoiceHTLCState) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[9] + return &file_lightning_proto_enumTypes[8] } func (x InvoiceHTLCState) Number() protoreflect.EnumNumber { @@ -631,7 +563,7 @@ func (x InvoiceHTLCState) Number() protoreflect.EnumNumber { // Deprecated: Use InvoiceHTLCState.Descriptor instead. func (InvoiceHTLCState) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{9} + return file_lightning_proto_rawDescGZIP(), []int{8} } type PaymentFailureReason int32 @@ -688,11 +620,11 @@ func (x PaymentFailureReason) String() string { } func (PaymentFailureReason) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[10].Descriptor() + return file_lightning_proto_enumTypes[9].Descriptor() } func (PaymentFailureReason) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[10] + return &file_lightning_proto_enumTypes[9] } func (x PaymentFailureReason) Number() protoreflect.EnumNumber { @@ -701,7 +633,7 @@ func (x PaymentFailureReason) Number() protoreflect.EnumNumber { // Deprecated: Use PaymentFailureReason.Descriptor instead. func (PaymentFailureReason) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{10} + return file_lightning_proto_rawDescGZIP(), []int{9} } type FeatureBit int32 @@ -809,11 +741,11 @@ func (x FeatureBit) String() string { } func (FeatureBit) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[11].Descriptor() + return file_lightning_proto_enumTypes[10].Descriptor() } func (FeatureBit) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[11] + return &file_lightning_proto_enumTypes[10] } func (x FeatureBit) Number() protoreflect.EnumNumber { @@ -822,7 +754,7 @@ func (x FeatureBit) Number() protoreflect.EnumNumber { // Deprecated: Use FeatureBit.Descriptor instead. func (FeatureBit) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{11} + return file_lightning_proto_rawDescGZIP(), []int{10} } type UpdateFailure int32 @@ -864,11 +796,11 @@ func (x UpdateFailure) String() string { } func (UpdateFailure) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[12].Descriptor() + return file_lightning_proto_enumTypes[11].Descriptor() } func (UpdateFailure) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[12] + return &file_lightning_proto_enumTypes[11] } func (x UpdateFailure) Number() protoreflect.EnumNumber { @@ -877,7 +809,7 @@ func (x UpdateFailure) Number() protoreflect.EnumNumber { // Deprecated: Use UpdateFailure.Descriptor instead. func (UpdateFailure) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{12} + return file_lightning_proto_rawDescGZIP(), []int{11} } type ChannelCloseSummary_ClosureType int32 @@ -922,11 +854,11 @@ func (x ChannelCloseSummary_ClosureType) String() string { } func (ChannelCloseSummary_ClosureType) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[13].Descriptor() + return file_lightning_proto_enumTypes[12].Descriptor() } func (ChannelCloseSummary_ClosureType) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[13] + return &file_lightning_proto_enumTypes[12] } func (x ChannelCloseSummary_ClosureType) Number() protoreflect.EnumNumber { @@ -935,7 +867,7 @@ func (x ChannelCloseSummary_ClosureType) Number() protoreflect.EnumNumber { // Deprecated: Use ChannelCloseSummary_ClosureType.Descriptor instead. func (ChannelCloseSummary_ClosureType) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{48, 0} + return file_lightning_proto_rawDescGZIP(), []int{47, 0} } type Peer_SyncType int32 @@ -978,11 +910,11 @@ func (x Peer_SyncType) String() string { } func (Peer_SyncType) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[14].Descriptor() + return file_lightning_proto_enumTypes[13].Descriptor() } func (Peer_SyncType) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[14] + return &file_lightning_proto_enumTypes[13] } func (x Peer_SyncType) Number() protoreflect.EnumNumber { @@ -991,7 +923,7 @@ func (x Peer_SyncType) Number() protoreflect.EnumNumber { // Deprecated: Use Peer_SyncType.Descriptor instead. func (Peer_SyncType) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{52, 0} + return file_lightning_proto_rawDescGZIP(), []int{51, 0} } type PeerEvent_EventType int32 @@ -1024,11 +956,11 @@ func (x PeerEvent_EventType) String() string { } func (PeerEvent_EventType) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[15].Descriptor() + return file_lightning_proto_enumTypes[14].Descriptor() } func (PeerEvent_EventType) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[15] + return &file_lightning_proto_enumTypes[14] } func (x PeerEvent_EventType) Number() protoreflect.EnumNumber { @@ -1037,7 +969,7 @@ func (x PeerEvent_EventType) Number() protoreflect.EnumNumber { // Deprecated: Use PeerEvent_EventType.Descriptor instead. func (PeerEvent_EventType) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{57, 0} + return file_lightning_proto_rawDescGZIP(), []int{56, 0} } // There are three resolution states for the anchor: @@ -1079,11 +1011,11 @@ func (x PendingChannelsResponse_ForceClosedChannel_AnchorState) String() string } func (PendingChannelsResponse_ForceClosedChannel_AnchorState) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[16].Descriptor() + return file_lightning_proto_enumTypes[15].Descriptor() } func (PendingChannelsResponse_ForceClosedChannel_AnchorState) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[16] + return &file_lightning_proto_enumTypes[15] } func (x PendingChannelsResponse_ForceClosedChannel_AnchorState) Number() protoreflect.EnumNumber { @@ -1092,7 +1024,7 @@ func (x PendingChannelsResponse_ForceClosedChannel_AnchorState) Number() protore // Deprecated: Use PendingChannelsResponse_ForceClosedChannel_AnchorState.Descriptor instead. func (PendingChannelsResponse_ForceClosedChannel_AnchorState) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{90, 5, 0} + return file_lightning_proto_rawDescGZIP(), []int{89, 5, 0} } type ChannelEventUpdate_UpdateType int32 @@ -1105,7 +1037,6 @@ const ( ChannelEventUpdate_PENDING_OPEN_CHANNEL ChannelEventUpdate_UpdateType = 4 ChannelEventUpdate_FULLY_RESOLVED_CHANNEL ChannelEventUpdate_UpdateType = 5 ChannelEventUpdate_CHANNEL_FUNDING_TIMEOUT ChannelEventUpdate_UpdateType = 6 - ChannelEventUpdate_CHANNEL_UPDATE ChannelEventUpdate_UpdateType = 7 ) // Enum value maps for ChannelEventUpdate_UpdateType. @@ -1118,7 +1049,6 @@ var ( 4: "PENDING_OPEN_CHANNEL", 5: "FULLY_RESOLVED_CHANNEL", 6: "CHANNEL_FUNDING_TIMEOUT", - 7: "CHANNEL_UPDATE", } ChannelEventUpdate_UpdateType_value = map[string]int32{ "OPEN_CHANNEL": 0, @@ -1128,7 +1058,6 @@ var ( "PENDING_OPEN_CHANNEL": 4, "FULLY_RESOLVED_CHANNEL": 5, "CHANNEL_FUNDING_TIMEOUT": 6, - "CHANNEL_UPDATE": 7, } ) @@ -1143,11 +1072,11 @@ func (x ChannelEventUpdate_UpdateType) String() string { } func (ChannelEventUpdate_UpdateType) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[17].Descriptor() + return file_lightning_proto_enumTypes[16].Descriptor() } func (ChannelEventUpdate_UpdateType) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[17] + return &file_lightning_proto_enumTypes[16] } func (x ChannelEventUpdate_UpdateType) Number() protoreflect.EnumNumber { @@ -1156,7 +1085,7 @@ func (x ChannelEventUpdate_UpdateType) Number() protoreflect.EnumNumber { // Deprecated: Use ChannelEventUpdate_UpdateType.Descriptor instead. func (ChannelEventUpdate_UpdateType) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{93, 0} + return file_lightning_proto_rawDescGZIP(), []int{91, 0} } type Invoice_InvoiceState int32 @@ -1195,11 +1124,11 @@ func (x Invoice_InvoiceState) String() string { } func (Invoice_InvoiceState) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[18].Descriptor() + return file_lightning_proto_enumTypes[17].Descriptor() } func (Invoice_InvoiceState) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[18] + return &file_lightning_proto_enumTypes[17] } func (x Invoice_InvoiceState) Number() protoreflect.EnumNumber { @@ -1208,7 +1137,7 @@ func (x Invoice_InvoiceState) Number() protoreflect.EnumNumber { // Deprecated: Use Invoice_InvoiceState.Descriptor instead. func (Invoice_InvoiceState) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{137, 0} + return file_lightning_proto_rawDescGZIP(), []int{135, 0} } type Payment_PaymentStatus int32 @@ -1257,11 +1186,11 @@ func (x Payment_PaymentStatus) String() string { } func (Payment_PaymentStatus) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[19].Descriptor() + return file_lightning_proto_enumTypes[18].Descriptor() } func (Payment_PaymentStatus) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[19] + return &file_lightning_proto_enumTypes[18] } func (x Payment_PaymentStatus) Number() protoreflect.EnumNumber { @@ -1270,7 +1199,7 @@ func (x Payment_PaymentStatus) Number() protoreflect.EnumNumber { // Deprecated: Use Payment_PaymentStatus.Descriptor instead. func (Payment_PaymentStatus) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{148, 0} + return file_lightning_proto_rawDescGZIP(), []int{146, 0} } type HTLCAttempt_HTLCStatus int32 @@ -1306,11 +1235,11 @@ func (x HTLCAttempt_HTLCStatus) String() string { } func (HTLCAttempt_HTLCStatus) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[20].Descriptor() + return file_lightning_proto_enumTypes[19].Descriptor() } func (HTLCAttempt_HTLCStatus) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[20] + return &file_lightning_proto_enumTypes[19] } func (x HTLCAttempt_HTLCStatus) Number() protoreflect.EnumNumber { @@ -1319,7 +1248,7 @@ func (x HTLCAttempt_HTLCStatus) Number() protoreflect.EnumNumber { // Deprecated: Use HTLCAttempt_HTLCStatus.Descriptor instead. func (HTLCAttempt_HTLCStatus) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{149, 0} + return file_lightning_proto_rawDescGZIP(), []int{147, 0} } type Failure_FailureCode int32 @@ -1440,11 +1369,11 @@ func (x Failure_FailureCode) String() string { } func (Failure_FailureCode) Descriptor() protoreflect.EnumDescriptor { - return file_lightning_proto_enumTypes[21].Descriptor() + return file_lightning_proto_enumTypes[20].Descriptor() } func (Failure_FailureCode) Type() protoreflect.EnumType { - return &file_lightning_proto_enumTypes[21] + return &file_lightning_proto_enumTypes[20] } func (x Failure_FailureCode) Number() protoreflect.EnumNumber { @@ -1453,22 +1382,25 @@ func (x Failure_FailureCode) Number() protoreflect.EnumNumber { // Deprecated: Use Failure_FailureCode.Descriptor instead. func (Failure_FailureCode) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{193, 0} + return file_lightning_proto_rawDescGZIP(), []int{191, 0} } type LookupHtlcResolutionRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ChanId uint64 `protobuf:"varint,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"` - HtlcIndex uint64 `protobuf:"varint,2,opt,name=htlc_index,json=htlcIndex,proto3" json:"htlc_index,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ChanId uint64 `protobuf:"varint,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"` + HtlcIndex uint64 `protobuf:"varint,2,opt,name=htlc_index,json=htlcIndex,proto3" json:"htlc_index,omitempty"` } func (x *LookupHtlcResolutionRequest) Reset() { *x = LookupHtlcResolutionRequest{} - mi := &file_lightning_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *LookupHtlcResolutionRequest) String() string { @@ -1479,7 +1411,7 @@ func (*LookupHtlcResolutionRequest) ProtoMessage() {} func (x *LookupHtlcResolutionRequest) ProtoReflect() protoreflect.Message { mi := &file_lightning_proto_msgTypes[0] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1509,20 +1441,23 @@ func (x *LookupHtlcResolutionRequest) GetHtlcIndex() uint64 { } type LookupHtlcResolutionResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Settled is true is the htlc was settled. If false, the htlc was failed. Settled bool `protobuf:"varint,1,opt,name=settled,proto3" json:"settled,omitempty"` // Offchain indicates whether the htlc was resolved off-chain or on-chain. - Offchain bool `protobuf:"varint,2,opt,name=offchain,proto3" json:"offchain,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Offchain bool `protobuf:"varint,2,opt,name=offchain,proto3" json:"offchain,omitempty"` } func (x *LookupHtlcResolutionResponse) Reset() { *x = LookupHtlcResolutionResponse{} - mi := &file_lightning_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *LookupHtlcResolutionResponse) String() string { @@ -1533,7 +1468,7 @@ func (*LookupHtlcResolutionResponse) ProtoMessage() {} func (x *LookupHtlcResolutionResponse) ProtoReflect() protoreflect.Message { mi := &file_lightning_proto_msgTypes[1] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1563,16 +1498,18 @@ func (x *LookupHtlcResolutionResponse) GetOffchain() bool { } type SubscribeCustomMessagesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *SubscribeCustomMessagesRequest) Reset() { *x = SubscribeCustomMessagesRequest{} - mi := &file_lightning_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SubscribeCustomMessagesRequest) String() string { @@ -1583,7 +1520,7 @@ func (*SubscribeCustomMessagesRequest) ProtoMessage() {} func (x *SubscribeCustomMessagesRequest) ProtoReflect() protoreflect.Message { mi := &file_lightning_proto_msgTypes[2] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1599,22 +1536,25 @@ func (*SubscribeCustomMessagesRequest) Descriptor() ([]byte, []int) { } type CustomMessage struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Peer from which the message originates Peer []byte `protobuf:"bytes,1,opt,name=peer,proto3" json:"peer,omitempty"` // Message type. This value will be in the custom range (>= 32768). Type uint32 `protobuf:"varint,2,opt,name=type,proto3" json:"type,omitempty"` // Raw message data - Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` } func (x *CustomMessage) Reset() { *x = CustomMessage{} - mi := &file_lightning_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *CustomMessage) String() string { @@ -1625,7 +1565,7 @@ func (*CustomMessage) ProtoMessage() {} func (x *CustomMessage) ProtoReflect() protoreflect.Message { mi := &file_lightning_proto_msgTypes[3] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1662,9 +1602,11 @@ func (x *CustomMessage) GetData() []byte { } type SendCustomMessageRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Peer to which the message will be sent. Represented as a byte-encoded - // public key + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Peer to send the message to Peer []byte `protobuf:"bytes,1,opt,name=peer,proto3" json:"peer,omitempty"` // Message type. This value needs to be in the custom range (>= 32768). // To send a type < custom range, lnd needs to be compiled with the `dev` @@ -1672,16 +1614,16 @@ type SendCustomMessageRequest struct { // experimental protocol configuration. Type uint32 `protobuf:"varint,2,opt,name=type,proto3" json:"type,omitempty"` // Raw message data. - Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` } func (x *SendCustomMessageRequest) Reset() { *x = SendCustomMessageRequest{} - mi := &file_lightning_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SendCustomMessageRequest) String() string { @@ -1692,7 +1634,7 @@ func (*SendCustomMessageRequest) ProtoMessage() {} func (x *SendCustomMessageRequest) ProtoReflect() protoreflect.Message { mi := &file_lightning_proto_msgTypes[4] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1729,18 +1671,21 @@ func (x *SendCustomMessageRequest) GetData() []byte { } type SendCustomMessageResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The status of the send operation. - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The status of the send operation. + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` } func (x *SendCustomMessageResponse) Reset() { *x = SendCustomMessageResponse{} - mi := &file_lightning_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SendCustomMessageResponse) String() string { @@ -1751,7 +1696,7 @@ func (*SendCustomMessageResponse) ProtoMessage() {} func (x *SendCustomMessageResponse) ProtoReflect() protoreflect.Message { mi := &file_lightning_proto_msgTypes[5] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1773,269 +1718,11 @@ func (x *SendCustomMessageResponse) GetStatus() string { return "" } -type SubscribeOnionMessagesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *SubscribeOnionMessagesRequest) Reset() { - *x = SubscribeOnionMessagesRequest{} - mi := &file_lightning_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *SubscribeOnionMessagesRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SubscribeOnionMessagesRequest) ProtoMessage() {} - -func (x *SubscribeOnionMessagesRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[6] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SubscribeOnionMessagesRequest.ProtoReflect.Descriptor instead. -func (*SubscribeOnionMessagesRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{6} -} - -type OnionMessageUpdate struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Peer from which this message originates. Represented as a byte-encoded - // public key. - Peer []byte `protobuf:"bytes,1,opt,name=peer,proto3" json:"peer,omitempty"` - // PathKey is used to derive the blinded node id by tweaking the hop's - // static public key. The hop uses the corresponding blinded private key - // together with the sender's ephemeral key to perform ECDH and obtain the - // shared secret for decrypting the onion payload. Separately, for - // decrypting `encrypted_recipient_data`, the recipient performs ECDH - // between its static node private key and the path_key to derive the - // decryption key. - PathKey []byte `protobuf:"bytes,2,opt,name=path_key,json=pathKey,proto3" json:"path_key,omitempty"` - // Serialized Sphinx onion packet (BOLT 4) containing the layered, per-hop - // encrypted payloads and routing instructions used to forward this message - // along its designated path. - Onion []byte `protobuf:"bytes,3,opt,name=onion,proto3" json:"onion,omitempty"` - // reply_path is the blinded path that should be used when replying to a - // received message. The introduction_node field is passed through verbatim - // from the wire. It may carry either the 33-byte SEC1 compressed pubkey - // form or the 9-byte sciddir form. The sciddir form consists of a 1-byte - // direction selector (0x00 or 0x01) followed by an 8-byte short channel ID. - // Subscribers that intend to reply resolve the sciddir form against their - // local channel graph. - ReplyPath *BlindedPath `protobuf:"bytes,4,opt,name=reply_path,json=replyPath,proto3" json:"reply_path,omitempty"` - // encrypted_recipient_data is the encrypted data that contains the - // forwarding information for an onion message. It contains either - // next_node_id or short_channel_id for each non-final node. It MAY contain - // the path_id for the final node. - EncryptedRecipientData []byte `protobuf:"bytes,5,opt,name=encrypted_recipient_data,json=encryptedRecipientData,proto3" json:"encrypted_recipient_data,omitempty"` - // Custom onion message tlv records. These are customized fields that are - // not defined by LND and cannot be extracted. - CustomRecords map[uint64][]byte `protobuf:"bytes,6,rep,name=custom_records,json=customRecords,proto3" json:"custom_records,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *OnionMessageUpdate) Reset() { - *x = OnionMessageUpdate{} - mi := &file_lightning_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *OnionMessageUpdate) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*OnionMessageUpdate) ProtoMessage() {} - -func (x *OnionMessageUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[7] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use OnionMessageUpdate.ProtoReflect.Descriptor instead. -func (*OnionMessageUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{7} -} - -func (x *OnionMessageUpdate) GetPeer() []byte { - if x != nil { - return x.Peer - } - return nil -} - -func (x *OnionMessageUpdate) GetPathKey() []byte { - if x != nil { - return x.PathKey - } - return nil -} - -func (x *OnionMessageUpdate) GetOnion() []byte { - if x != nil { - return x.Onion - } - return nil -} - -func (x *OnionMessageUpdate) GetReplyPath() *BlindedPath { - if x != nil { - return x.ReplyPath - } - return nil -} - -func (x *OnionMessageUpdate) GetEncryptedRecipientData() []byte { - if x != nil { - return x.EncryptedRecipientData - } - return nil -} - -func (x *OnionMessageUpdate) GetCustomRecords() map[uint64][]byte { - if x != nil { - return x.CustomRecords - } - return nil -} - -type SendOnionMessageRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Peer to send the message to - Peer []byte `protobuf:"bytes,1,opt,name=peer,proto3" json:"peer,omitempty"` - // PathKey is used to derive the blinded node id by tweaking the hop's - // static public key. The hop uses the corresponding blinded private key - // together with the sender's ephemeral key to perform ECDH and obtain the - // shared secret for decrypting the onion payload. Separately, for - // decrypting `encrypted_recipient_data`, the recipient performs ECDH - // between its static node private key and the path_key to derive the - // decryption key. - PathKey []byte `protobuf:"bytes,2,opt,name=path_key,json=pathKey,proto3" json:"path_key,omitempty"` - // Serialized Sphinx onion packet (BOLT 4) containing the layered, per-hop - // encrypted payloads and routing instructions used to forward this message - // along its designated path. - Onion []byte `protobuf:"bytes,3,opt,name=onion,proto3" json:"onion,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *SendOnionMessageRequest) Reset() { - *x = SendOnionMessageRequest{} - mi := &file_lightning_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *SendOnionMessageRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SendOnionMessageRequest) ProtoMessage() {} - -func (x *SendOnionMessageRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[8] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SendOnionMessageRequest.ProtoReflect.Descriptor instead. -func (*SendOnionMessageRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{8} -} - -func (x *SendOnionMessageRequest) GetPeer() []byte { - if x != nil { - return x.Peer - } - return nil -} - -func (x *SendOnionMessageRequest) GetPathKey() []byte { - if x != nil { - return x.PathKey - } - return nil -} - -func (x *SendOnionMessageRequest) GetOnion() []byte { - if x != nil { - return x.Onion - } - return nil -} - -type SendOnionMessageResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The status of the onion message send operation. - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *SendOnionMessageResponse) Reset() { - *x = SendOnionMessageResponse{} - mi := &file_lightning_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *SendOnionMessageResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SendOnionMessageResponse) ProtoMessage() {} - -func (x *SendOnionMessageResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[9] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SendOnionMessageResponse.ProtoReflect.Descriptor instead. -func (*SendOnionMessageResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{9} -} - -func (x *SendOnionMessageResponse) GetStatus() string { - if x != nil { - return x.Status - } - return "" -} - type Utxo struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The type of address AddressType AddressType `protobuf:"varint,1,opt,name=address_type,json=addressType,proto3,enum=lnrpc.AddressType" json:"address_type,omitempty"` // The address @@ -2048,15 +1735,15 @@ type Utxo struct { Outpoint *OutPoint `protobuf:"bytes,5,opt,name=outpoint,proto3" json:"outpoint,omitempty"` // The number of confirmations for the Utxo Confirmations int64 `protobuf:"varint,6,opt,name=confirmations,proto3" json:"confirmations,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *Utxo) Reset() { *x = Utxo{} - mi := &file_lightning_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *Utxo) String() string { @@ -2066,8 +1753,8 @@ func (x *Utxo) String() string { func (*Utxo) ProtoMessage() {} func (x *Utxo) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[10] - if x != nil { + mi := &file_lightning_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2079,7 +1766,7 @@ func (x *Utxo) ProtoReflect() protoreflect.Message { // Deprecated: Use Utxo.ProtoReflect.Descriptor instead. func (*Utxo) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{10} + return file_lightning_proto_rawDescGZIP(), []int{6} } func (x *Utxo) GetAddressType() AddressType { @@ -2125,7 +1812,10 @@ func (x *Utxo) GetConfirmations() int64 { } type OutputDetail struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The type of the output OutputType OutputScriptType `protobuf:"varint,1,opt,name=output_type,json=outputType,proto3,enum=lnrpc.OutputScriptType" json:"output_type,omitempty"` // The address @@ -2137,16 +1827,16 @@ type OutputDetail struct { // The value of the output coin in satoshis Amount int64 `protobuf:"varint,5,opt,name=amount,proto3" json:"amount,omitempty"` // Denotes if the output is controlled by the internal wallet - IsOurAddress bool `protobuf:"varint,6,opt,name=is_our_address,json=isOurAddress,proto3" json:"is_our_address,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + IsOurAddress bool `protobuf:"varint,6,opt,name=is_our_address,json=isOurAddress,proto3" json:"is_our_address,omitempty"` } func (x *OutputDetail) Reset() { *x = OutputDetail{} - mi := &file_lightning_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *OutputDetail) String() string { @@ -2156,8 +1846,8 @@ func (x *OutputDetail) String() string { func (*OutputDetail) ProtoMessage() {} func (x *OutputDetail) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[11] - if x != nil { + mi := &file_lightning_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2169,7 +1859,7 @@ func (x *OutputDetail) ProtoReflect() protoreflect.Message { // Deprecated: Use OutputDetail.ProtoReflect.Descriptor instead. func (*OutputDetail) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{11} + return file_lightning_proto_rawDescGZIP(), []int{7} } func (x *OutputDetail) GetOutputType() OutputScriptType { @@ -2215,7 +1905,10 @@ func (x *OutputDetail) GetIsOurAddress() bool { } type Transaction struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The transaction hash TxHash string `protobuf:"bytes,1,opt,name=tx_hash,json=txHash,proto3" json:"tx_hash,omitempty"` // The transaction amount, denominated in satoshis @@ -2243,15 +1936,15 @@ type Transaction struct { Label string `protobuf:"bytes,10,opt,name=label,proto3" json:"label,omitempty"` // PreviousOutpoints/Inputs of this transaction. PreviousOutpoints []*PreviousOutPoint `protobuf:"bytes,12,rep,name=previous_outpoints,json=previousOutpoints,proto3" json:"previous_outpoints,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *Transaction) Reset() { *x = Transaction{} - mi := &file_lightning_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *Transaction) String() string { @@ -2261,8 +1954,8 @@ func (x *Transaction) String() string { func (*Transaction) ProtoMessage() {} func (x *Transaction) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[12] - if x != nil { + mi := &file_lightning_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2274,7 +1967,7 @@ func (x *Transaction) ProtoReflect() protoreflect.Message { // Deprecated: Use Transaction.ProtoReflect.Descriptor instead. func (*Transaction) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{12} + return file_lightning_proto_rawDescGZIP(), []int{8} } func (x *Transaction) GetTxHash() string { @@ -2363,7 +2056,10 @@ func (x *Transaction) GetPreviousOutpoints() []*PreviousOutPoint { } type GetTransactionsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The height from which to list transactions, inclusive. If this value is // greater than end_height, transactions will be read in reverse. StartHeight int32 `protobuf:"varint,1,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty"` @@ -2381,15 +2077,15 @@ type GetTransactionsRequest struct { // The maximal number of transactions returned in the response to this query. // This value should be set to 0 to return all transactions. MaxTransactions uint32 `protobuf:"varint,5,opt,name=max_transactions,json=maxTransactions,proto3" json:"max_transactions,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *GetTransactionsRequest) Reset() { *x = GetTransactionsRequest{} - mi := &file_lightning_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetTransactionsRequest) String() string { @@ -2399,8 +2095,8 @@ func (x *GetTransactionsRequest) String() string { func (*GetTransactionsRequest) ProtoMessage() {} func (x *GetTransactionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[13] - if x != nil { + mi := &file_lightning_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2412,7 +2108,7 @@ func (x *GetTransactionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetTransactionsRequest.ProtoReflect.Descriptor instead. func (*GetTransactionsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{13} + return file_lightning_proto_rawDescGZIP(), []int{9} } func (x *GetTransactionsRequest) GetStartHeight() int32 { @@ -2451,7 +2147,10 @@ func (x *GetTransactionsRequest) GetMaxTransactions() uint32 { } type TransactionDetails struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The list of transactions relevant to the wallet. Transactions []*Transaction `protobuf:"bytes,1,rep,name=transactions,proto3" json:"transactions,omitempty"` // The index of the last item in the set of returned transactions. This can be @@ -2459,16 +2158,16 @@ type TransactionDetails struct { LastIndex uint64 `protobuf:"varint,2,opt,name=last_index,json=lastIndex,proto3" json:"last_index,omitempty"` // The index of the last item in the set of returned transactions. This can be // used to seek backwards, pagination style. - FirstIndex uint64 `protobuf:"varint,3,opt,name=first_index,json=firstIndex,proto3" json:"first_index,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + FirstIndex uint64 `protobuf:"varint,3,opt,name=first_index,json=firstIndex,proto3" json:"first_index,omitempty"` } func (x *TransactionDetails) Reset() { *x = TransactionDetails{} - mi := &file_lightning_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *TransactionDetails) String() string { @@ -2478,8 +2177,8 @@ func (x *TransactionDetails) String() string { func (*TransactionDetails) ProtoMessage() {} func (x *TransactionDetails) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[14] - if x != nil { + mi := &file_lightning_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2491,7 +2190,7 @@ func (x *TransactionDetails) ProtoReflect() protoreflect.Message { // Deprecated: Use TransactionDetails.ProtoReflect.Descriptor instead. func (*TransactionDetails) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{14} + return file_lightning_proto_rawDescGZIP(), []int{10} } func (x *TransactionDetails) GetTransactions() []*Transaction { @@ -2516,22 +2215,25 @@ func (x *TransactionDetails) GetFirstIndex() uint64 { } type FeeLimit struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to Limit: + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Limit: // // *FeeLimit_Fixed // *FeeLimit_FixedMsat // *FeeLimit_Percent - Limit isFeeLimit_Limit `protobuf_oneof:"limit"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Limit isFeeLimit_Limit `protobuf_oneof:"limit"` } func (x *FeeLimit) Reset() { *x = FeeLimit{} - mi := &file_lightning_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *FeeLimit) String() string { @@ -2541,8 +2243,8 @@ func (x *FeeLimit) String() string { func (*FeeLimit) ProtoMessage() {} func (x *FeeLimit) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[15] - if x != nil { + mi := &file_lightning_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2554,39 +2256,33 @@ func (x *FeeLimit) ProtoReflect() protoreflect.Message { // Deprecated: Use FeeLimit.ProtoReflect.Descriptor instead. func (*FeeLimit) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{15} + return file_lightning_proto_rawDescGZIP(), []int{11} } -func (x *FeeLimit) GetLimit() isFeeLimit_Limit { - if x != nil { - return x.Limit +func (m *FeeLimit) GetLimit() isFeeLimit_Limit { + if m != nil { + return m.Limit } return nil } func (x *FeeLimit) GetFixed() int64 { - if x != nil { - if x, ok := x.Limit.(*FeeLimit_Fixed); ok { - return x.Fixed - } + if x, ok := x.GetLimit().(*FeeLimit_Fixed); ok { + return x.Fixed } return 0 } func (x *FeeLimit) GetFixedMsat() int64 { - if x != nil { - if x, ok := x.Limit.(*FeeLimit_FixedMsat); ok { - return x.FixedMsat - } + if x, ok := x.GetLimit().(*FeeLimit_FixedMsat); ok { + return x.FixedMsat } return 0 } func (x *FeeLimit) GetPercent() int64 { - if x != nil { - if x, ok := x.Limit.(*FeeLimit_Percent); ok { - return x.Percent - } + if x, ok := x.GetLimit().(*FeeLimit_Percent); ok { + return x.Percent } return 0 } @@ -2620,8 +2316,369 @@ func (*FeeLimit_FixedMsat) isFeeLimit_Limit() {} func (*FeeLimit_Percent) isFeeLimit_Limit() {} +type SendRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The identity pubkey of the payment recipient. When using REST, this field + // must be encoded as base64. + Dest []byte `protobuf:"bytes,1,opt,name=dest,proto3" json:"dest,omitempty"` + // The hex-encoded identity pubkey of the payment recipient. Deprecated now + // that the REST gateway supports base64 encoding of bytes fields. + // + // Deprecated: Marked as deprecated in lightning.proto. + DestString string `protobuf:"bytes,2,opt,name=dest_string,json=destString,proto3" json:"dest_string,omitempty"` + // The amount to send expressed in satoshis. + // + // The fields amt and amt_msat are mutually exclusive. + Amt int64 `protobuf:"varint,3,opt,name=amt,proto3" json:"amt,omitempty"` + // The amount to send expressed in millisatoshis. + // + // The fields amt and amt_msat are mutually exclusive. + AmtMsat int64 `protobuf:"varint,12,opt,name=amt_msat,json=amtMsat,proto3" json:"amt_msat,omitempty"` + // The hash to use within the payment's HTLC. When using REST, this field + // must be encoded as base64. + PaymentHash []byte `protobuf:"bytes,4,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"` + // The hex-encoded hash to use within the payment's HTLC. Deprecated now + // that the REST gateway supports base64 encoding of bytes fields. + // + // Deprecated: Marked as deprecated in lightning.proto. + PaymentHashString string `protobuf:"bytes,5,opt,name=payment_hash_string,json=paymentHashString,proto3" json:"payment_hash_string,omitempty"` + // A bare-bones invoice for a payment within the Lightning Network. With the + // details of the invoice, the sender has all the data necessary to send a + // payment to the recipient. + PaymentRequest string `protobuf:"bytes,6,opt,name=payment_request,json=paymentRequest,proto3" json:"payment_request,omitempty"` + // The CLTV delta from the current height that should be used to set the + // timelock for the final hop. + FinalCltvDelta int32 `protobuf:"varint,7,opt,name=final_cltv_delta,json=finalCltvDelta,proto3" json:"final_cltv_delta,omitempty"` + // The maximum number of satoshis that will be paid as a fee of the payment. + // This value can be represented either as a percentage of the amount being + // sent, or as a fixed amount of the maximum fee the user is willing the pay to + // send the payment. If not specified, lnd will use a default value of 100% + // fees for small amounts (<=1k sat) or 5% fees for larger amounts. + FeeLimit *FeeLimit `protobuf:"bytes,8,opt,name=fee_limit,json=feeLimit,proto3" json:"fee_limit,omitempty"` + // The channel id of the channel that must be taken to the first hop. If zero, + // any channel may be used. + OutgoingChanId uint64 `protobuf:"varint,9,opt,name=outgoing_chan_id,json=outgoingChanId,proto3" json:"outgoing_chan_id,omitempty"` + // The pubkey of the last hop of the route. If empty, any hop may be used. + LastHopPubkey []byte `protobuf:"bytes,13,opt,name=last_hop_pubkey,json=lastHopPubkey,proto3" json:"last_hop_pubkey,omitempty"` + // An optional maximum total time lock for the route. This should not exceed + // lnd's `--max-cltv-expiry` setting. If zero, then the value of + // `--max-cltv-expiry` is enforced. + CltvLimit uint32 `protobuf:"varint,10,opt,name=cltv_limit,json=cltvLimit,proto3" json:"cltv_limit,omitempty"` + // An optional field that can be used to pass an arbitrary set of TLV records + // to a peer which understands the new records. This can be used to pass + // application specific data during the payment attempt. Record types are + // required to be in the custom range >= 65536. When using REST, the values + // must be encoded as base64. + DestCustomRecords map[uint64][]byte `protobuf:"bytes,11,rep,name=dest_custom_records,json=destCustomRecords,proto3" json:"dest_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // If set, circular payments to self are permitted. + AllowSelfPayment bool `protobuf:"varint,14,opt,name=allow_self_payment,json=allowSelfPayment,proto3" json:"allow_self_payment,omitempty"` + // Features assumed to be supported by the final node. All transitive feature + // dependencies must also be set properly. For a given feature bit pair, either + // optional or remote may be set, but not both. If this field is nil or empty, + // the router will try to load destination features from the graph as a + // fallback. + DestFeatures []FeatureBit `protobuf:"varint,15,rep,packed,name=dest_features,json=destFeatures,proto3,enum=lnrpc.FeatureBit" json:"dest_features,omitempty"` + // The payment address of the generated invoice. This is also called + // payment secret in specifications (e.g. BOLT 11). + PaymentAddr []byte `protobuf:"bytes,16,opt,name=payment_addr,json=paymentAddr,proto3" json:"payment_addr,omitempty"` +} + +func (x *SendRequest) Reset() { + *x = SendRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SendRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendRequest) ProtoMessage() {} + +func (x *SendRequest) ProtoReflect() protoreflect.Message { + mi := &file_lightning_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendRequest.ProtoReflect.Descriptor instead. +func (*SendRequest) Descriptor() ([]byte, []int) { + return file_lightning_proto_rawDescGZIP(), []int{12} +} + +func (x *SendRequest) GetDest() []byte { + if x != nil { + return x.Dest + } + return nil +} + +// Deprecated: Marked as deprecated in lightning.proto. +func (x *SendRequest) GetDestString() string { + if x != nil { + return x.DestString + } + return "" +} + +func (x *SendRequest) GetAmt() int64 { + if x != nil { + return x.Amt + } + return 0 +} + +func (x *SendRequest) GetAmtMsat() int64 { + if x != nil { + return x.AmtMsat + } + return 0 +} + +func (x *SendRequest) GetPaymentHash() []byte { + if x != nil { + return x.PaymentHash + } + return nil +} + +// Deprecated: Marked as deprecated in lightning.proto. +func (x *SendRequest) GetPaymentHashString() string { + if x != nil { + return x.PaymentHashString + } + return "" +} + +func (x *SendRequest) GetPaymentRequest() string { + if x != nil { + return x.PaymentRequest + } + return "" +} + +func (x *SendRequest) GetFinalCltvDelta() int32 { + if x != nil { + return x.FinalCltvDelta + } + return 0 +} + +func (x *SendRequest) GetFeeLimit() *FeeLimit { + if x != nil { + return x.FeeLimit + } + return nil +} + +func (x *SendRequest) GetOutgoingChanId() uint64 { + if x != nil { + return x.OutgoingChanId + } + return 0 +} + +func (x *SendRequest) GetLastHopPubkey() []byte { + if x != nil { + return x.LastHopPubkey + } + return nil +} + +func (x *SendRequest) GetCltvLimit() uint32 { + if x != nil { + return x.CltvLimit + } + return 0 +} + +func (x *SendRequest) GetDestCustomRecords() map[uint64][]byte { + if x != nil { + return x.DestCustomRecords + } + return nil +} + +func (x *SendRequest) GetAllowSelfPayment() bool { + if x != nil { + return x.AllowSelfPayment + } + return false +} + +func (x *SendRequest) GetDestFeatures() []FeatureBit { + if x != nil { + return x.DestFeatures + } + return nil +} + +func (x *SendRequest) GetPaymentAddr() []byte { + if x != nil { + return x.PaymentAddr + } + return nil +} + +type SendResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PaymentError string `protobuf:"bytes,1,opt,name=payment_error,json=paymentError,proto3" json:"payment_error,omitempty"` + PaymentPreimage []byte `protobuf:"bytes,2,opt,name=payment_preimage,json=paymentPreimage,proto3" json:"payment_preimage,omitempty"` + PaymentRoute *Route `protobuf:"bytes,3,opt,name=payment_route,json=paymentRoute,proto3" json:"payment_route,omitempty"` + PaymentHash []byte `protobuf:"bytes,4,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"` +} + +func (x *SendResponse) Reset() { + *x = SendResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SendResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendResponse) ProtoMessage() {} + +func (x *SendResponse) ProtoReflect() protoreflect.Message { + mi := &file_lightning_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendResponse.ProtoReflect.Descriptor instead. +func (*SendResponse) Descriptor() ([]byte, []int) { + return file_lightning_proto_rawDescGZIP(), []int{13} +} + +func (x *SendResponse) GetPaymentError() string { + if x != nil { + return x.PaymentError + } + return "" +} + +func (x *SendResponse) GetPaymentPreimage() []byte { + if x != nil { + return x.PaymentPreimage + } + return nil +} + +func (x *SendResponse) GetPaymentRoute() *Route { + if x != nil { + return x.PaymentRoute + } + return nil +} + +func (x *SendResponse) GetPaymentHash() []byte { + if x != nil { + return x.PaymentHash + } + return nil +} + +type SendToRouteRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The payment hash to use for the HTLC. When using REST, this field must be + // encoded as base64. + PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"` + // An optional hex-encoded payment hash to be used for the HTLC. Deprecated now + // that the REST gateway supports base64 encoding of bytes fields. + // + // Deprecated: Marked as deprecated in lightning.proto. + PaymentHashString string `protobuf:"bytes,2,opt,name=payment_hash_string,json=paymentHashString,proto3" json:"payment_hash_string,omitempty"` + // Route that should be used to attempt to complete the payment. + Route *Route `protobuf:"bytes,4,opt,name=route,proto3" json:"route,omitempty"` +} + +func (x *SendToRouteRequest) Reset() { + *x = SendToRouteRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SendToRouteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendToRouteRequest) ProtoMessage() {} + +func (x *SendToRouteRequest) ProtoReflect() protoreflect.Message { + mi := &file_lightning_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendToRouteRequest.ProtoReflect.Descriptor instead. +func (*SendToRouteRequest) Descriptor() ([]byte, []int) { + return file_lightning_proto_rawDescGZIP(), []int{14} +} + +func (x *SendToRouteRequest) GetPaymentHash() []byte { + if x != nil { + return x.PaymentHash + } + return nil +} + +// Deprecated: Marked as deprecated in lightning.proto. +func (x *SendToRouteRequest) GetPaymentHashString() string { + if x != nil { + return x.PaymentHashString + } + return "" +} + +func (x *SendToRouteRequest) GetRoute() *Route { + if x != nil { + return x.Route + } + return nil +} + type ChannelAcceptRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The pubkey of the node that wishes to open an inbound channel. NodePubkey []byte `protobuf:"bytes,1,opt,name=node_pubkey,json=nodePubkey,proto3" json:"node_pubkey,omitempty"` // The hash of the genesis block that the proposed channel resides in. @@ -2662,15 +2719,15 @@ type ChannelAcceptRequest struct { // Whether the initiator wants to use the scid-alias channel type. This is // separate from the feature bit. WantsScidAlias bool `protobuf:"varint,16,opt,name=wants_scid_alias,json=wantsScidAlias,proto3" json:"wants_scid_alias,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ChannelAcceptRequest) Reset() { *x = ChannelAcceptRequest{} - mi := &file_lightning_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChannelAcceptRequest) String() string { @@ -2680,8 +2737,8 @@ func (x *ChannelAcceptRequest) String() string { func (*ChannelAcceptRequest) ProtoMessage() {} func (x *ChannelAcceptRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[16] - if x != nil { + mi := &file_lightning_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2693,7 +2750,7 @@ func (x *ChannelAcceptRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelAcceptRequest.ProtoReflect.Descriptor instead. func (*ChannelAcceptRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{16} + return file_lightning_proto_rawDescGZIP(), []int{15} } func (x *ChannelAcceptRequest) GetNodePubkey() []byte { @@ -2809,7 +2866,10 @@ func (x *ChannelAcceptRequest) GetWantsScidAlias() bool { } type ChannelAcceptResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Whether or not the client accepts the channel. Accept bool `protobuf:"varint,1,opt,name=accept,proto3" json:"accept,omitempty"` // The pending channel id to which this response applies. @@ -2845,16 +2905,16 @@ type ChannelAcceptResponse struct { // Whether the responder wants this to be a zero-conf channel. This will fail // if either side does not have the scid-alias feature bit set. The minimum // depth field must be zero if this is true. - ZeroConf bool `protobuf:"varint,11,opt,name=zero_conf,json=zeroConf,proto3" json:"zero_conf,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + ZeroConf bool `protobuf:"varint,11,opt,name=zero_conf,json=zeroConf,proto3" json:"zero_conf,omitempty"` } func (x *ChannelAcceptResponse) Reset() { *x = ChannelAcceptResponse{} - mi := &file_lightning_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChannelAcceptResponse) String() string { @@ -2864,8 +2924,8 @@ func (x *ChannelAcceptResponse) String() string { func (*ChannelAcceptResponse) ProtoMessage() {} func (x *ChannelAcceptResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[17] - if x != nil { + mi := &file_lightning_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2877,7 +2937,7 @@ func (x *ChannelAcceptResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelAcceptResponse.ProtoReflect.Descriptor instead. func (*ChannelAcceptResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{17} + return file_lightning_proto_rawDescGZIP(), []int{16} } func (x *ChannelAcceptResponse) GetAccept() bool { @@ -2958,23 +3018,26 @@ func (x *ChannelAcceptResponse) GetZeroConf() bool { } type ChannelPoint struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to FundingTxid: + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to FundingTxid: // // *ChannelPoint_FundingTxidBytes // *ChannelPoint_FundingTxidStr FundingTxid isChannelPoint_FundingTxid `protobuf_oneof:"funding_txid"` // The index of the output of the funding transaction - OutputIndex uint32 `protobuf:"varint,3,opt,name=output_index,json=outputIndex,proto3" json:"output_index,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + OutputIndex uint32 `protobuf:"varint,3,opt,name=output_index,json=outputIndex,proto3" json:"output_index,omitempty"` } func (x *ChannelPoint) Reset() { *x = ChannelPoint{} - mi := &file_lightning_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChannelPoint) String() string { @@ -2984,8 +3047,8 @@ func (x *ChannelPoint) String() string { func (*ChannelPoint) ProtoMessage() {} func (x *ChannelPoint) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[18] - if x != nil { + mi := &file_lightning_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2997,30 +3060,26 @@ func (x *ChannelPoint) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelPoint.ProtoReflect.Descriptor instead. func (*ChannelPoint) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{18} + return file_lightning_proto_rawDescGZIP(), []int{17} } -func (x *ChannelPoint) GetFundingTxid() isChannelPoint_FundingTxid { - if x != nil { - return x.FundingTxid +func (m *ChannelPoint) GetFundingTxid() isChannelPoint_FundingTxid { + if m != nil { + return m.FundingTxid } return nil } func (x *ChannelPoint) GetFundingTxidBytes() []byte { - if x != nil { - if x, ok := x.FundingTxid.(*ChannelPoint_FundingTxidBytes); ok { - return x.FundingTxidBytes - } + if x, ok := x.GetFundingTxid().(*ChannelPoint_FundingTxidBytes); ok { + return x.FundingTxidBytes } return nil } func (x *ChannelPoint) GetFundingTxidStr() string { - if x != nil { - if x, ok := x.FundingTxid.(*ChannelPoint_FundingTxidStr); ok { - return x.FundingTxidStr - } + if x, ok := x.GetFundingTxid().(*ChannelPoint_FundingTxidStr); ok { + return x.FundingTxidStr } return "" } @@ -3053,22 +3112,25 @@ func (*ChannelPoint_FundingTxidBytes) isChannelPoint_FundingTxid() {} func (*ChannelPoint_FundingTxidStr) isChannelPoint_FundingTxid() {} type OutPoint struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Raw bytes representing the transaction id. TxidBytes []byte `protobuf:"bytes,1,opt,name=txid_bytes,json=txidBytes,proto3" json:"txid_bytes,omitempty"` // Reversed, hex-encoded string representing the transaction id. TxidStr string `protobuf:"bytes,2,opt,name=txid_str,json=txidStr,proto3" json:"txid_str,omitempty"` // The index of the output on the transaction. - OutputIndex uint32 `protobuf:"varint,3,opt,name=output_index,json=outputIndex,proto3" json:"output_index,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + OutputIndex uint32 `protobuf:"varint,3,opt,name=output_index,json=outputIndex,proto3" json:"output_index,omitempty"` } func (x *OutPoint) Reset() { *x = OutPoint{} - mi := &file_lightning_proto_msgTypes[19] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *OutPoint) String() string { @@ -3078,8 +3140,8 @@ func (x *OutPoint) String() string { func (*OutPoint) ProtoMessage() {} func (x *OutPoint) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[19] - if x != nil { + mi := &file_lightning_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3091,7 +3153,7 @@ func (x *OutPoint) ProtoReflect() protoreflect.Message { // Deprecated: Use OutPoint.ProtoReflect.Descriptor instead. func (*OutPoint) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{19} + return file_lightning_proto_rawDescGZIP(), []int{18} } func (x *OutPoint) GetTxidBytes() []byte { @@ -3116,21 +3178,24 @@ func (x *OutPoint) GetOutputIndex() uint32 { } type PreviousOutPoint struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The outpoint in format txid:n. Outpoint string `protobuf:"bytes,1,opt,name=outpoint,proto3" json:"outpoint,omitempty"` // Denotes if the outpoint is controlled by the internal wallet. // The flag will only detect p2wkh, np2wkh and p2tr inputs as its own. - IsOurOutput bool `protobuf:"varint,2,opt,name=is_our_output,json=isOurOutput,proto3" json:"is_our_output,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + IsOurOutput bool `protobuf:"varint,2,opt,name=is_our_output,json=isOurOutput,proto3" json:"is_our_output,omitempty"` } func (x *PreviousOutPoint) Reset() { *x = PreviousOutPoint{} - mi := &file_lightning_proto_msgTypes[20] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PreviousOutPoint) String() string { @@ -3140,8 +3205,8 @@ func (x *PreviousOutPoint) String() string { func (*PreviousOutPoint) ProtoMessage() {} func (x *PreviousOutPoint) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[20] - if x != nil { + mi := &file_lightning_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3153,7 +3218,7 @@ func (x *PreviousOutPoint) ProtoReflect() protoreflect.Message { // Deprecated: Use PreviousOutPoint.ProtoReflect.Descriptor instead. func (*PreviousOutPoint) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{20} + return file_lightning_proto_rawDescGZIP(), []int{19} } func (x *PreviousOutPoint) GetOutpoint() string { @@ -3171,21 +3236,24 @@ func (x *PreviousOutPoint) GetIsOurOutput() bool { } type LightningAddress struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The identity pubkey of the Lightning node. Pubkey string `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` // The network location of the lightning node, e.g. `69.69.69.69:1337` or // `localhost:10011`. - Host string `protobuf:"bytes,2,opt,name=host,proto3" json:"host,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Host string `protobuf:"bytes,2,opt,name=host,proto3" json:"host,omitempty"` } func (x *LightningAddress) Reset() { *x = LightningAddress{} - mi := &file_lightning_proto_msgTypes[21] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *LightningAddress) String() string { @@ -3195,8 +3263,8 @@ func (x *LightningAddress) String() string { func (*LightningAddress) ProtoMessage() {} func (x *LightningAddress) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[21] - if x != nil { + mi := &file_lightning_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3208,7 +3276,7 @@ func (x *LightningAddress) ProtoReflect() protoreflect.Message { // Deprecated: Use LightningAddress.ProtoReflect.Descriptor instead. func (*LightningAddress) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{21} + return file_lightning_proto_rawDescGZIP(), []int{20} } func (x *LightningAddress) GetPubkey() string { @@ -3226,9 +3294,12 @@ func (x *LightningAddress) GetHost() string { } type EstimateFeeRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The map from addresses to amounts for the transaction. - AddrToAmount map[string]int64 `protobuf:"bytes,1,rep,name=AddrToAmount,proto3" json:"AddrToAmount,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + AddrToAmount map[string]int64 `protobuf:"bytes,1,rep,name=AddrToAmount,proto3" json:"AddrToAmount,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` // The target number of blocks that this transaction should be confirmed // by. TargetConf int32 `protobuf:"varint,2,opt,name=target_conf,json=targetConf,proto3" json:"target_conf,omitempty"` @@ -3239,17 +3310,15 @@ type EstimateFeeRequest struct { SpendUnconfirmed bool `protobuf:"varint,4,opt,name=spend_unconfirmed,json=spendUnconfirmed,proto3" json:"spend_unconfirmed,omitempty"` // The strategy to use for selecting coins during fees estimation. CoinSelectionStrategy CoinSelectionStrategy `protobuf:"varint,5,opt,name=coin_selection_strategy,json=coinSelectionStrategy,proto3,enum=lnrpc.CoinSelectionStrategy" json:"coin_selection_strategy,omitempty"` - // A list of selected inputs for the transaction. - Inputs []*OutPoint `protobuf:"bytes,6,rep,name=inputs,proto3" json:"inputs,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *EstimateFeeRequest) Reset() { *x = EstimateFeeRequest{} - mi := &file_lightning_proto_msgTypes[22] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *EstimateFeeRequest) String() string { @@ -3259,8 +3328,8 @@ func (x *EstimateFeeRequest) String() string { func (*EstimateFeeRequest) ProtoMessage() {} func (x *EstimateFeeRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[22] - if x != nil { + mi := &file_lightning_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3272,7 +3341,7 @@ func (x *EstimateFeeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EstimateFeeRequest.ProtoReflect.Descriptor instead. func (*EstimateFeeRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{22} + return file_lightning_proto_rawDescGZIP(), []int{21} } func (x *EstimateFeeRequest) GetAddrToAmount() map[string]int64 { @@ -3310,15 +3379,11 @@ func (x *EstimateFeeRequest) GetCoinSelectionStrategy() CoinSelectionStrategy { return CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG } -func (x *EstimateFeeRequest) GetInputs() []*OutPoint { - if x != nil { - return x.Inputs - } - return nil -} - type EstimateFeeResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The total fee in satoshis. FeeSat int64 `protobuf:"varint,1,opt,name=fee_sat,json=feeSat,proto3" json:"fee_sat,omitempty"` // Deprecated, use sat_per_vbyte. @@ -3328,17 +3393,15 @@ type EstimateFeeResponse struct { FeerateSatPerByte int64 `protobuf:"varint,2,opt,name=feerate_sat_per_byte,json=feerateSatPerByte,proto3" json:"feerate_sat_per_byte,omitempty"` // The fee rate in satoshi/vbyte. SatPerVbyte uint64 `protobuf:"varint,3,opt,name=sat_per_vbyte,json=satPerVbyte,proto3" json:"sat_per_vbyte,omitempty"` - // A list of selected inputs for the transaction the estimate is for. - Inputs []*OutPoint `protobuf:"bytes,4,rep,name=inputs,proto3" json:"inputs,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *EstimateFeeResponse) Reset() { *x = EstimateFeeResponse{} - mi := &file_lightning_proto_msgTypes[23] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *EstimateFeeResponse) String() string { @@ -3348,8 +3411,8 @@ func (x *EstimateFeeResponse) String() string { func (*EstimateFeeResponse) ProtoMessage() {} func (x *EstimateFeeResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[23] - if x != nil { + mi := &file_lightning_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3361,7 +3424,7 @@ func (x *EstimateFeeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EstimateFeeResponse.ProtoReflect.Descriptor instead. func (*EstimateFeeResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{23} + return file_lightning_proto_rawDescGZIP(), []int{22} } func (x *EstimateFeeResponse) GetFeeSat() int64 { @@ -3386,17 +3449,13 @@ func (x *EstimateFeeResponse) GetSatPerVbyte() uint64 { return 0 } -func (x *EstimateFeeResponse) GetInputs() []*OutPoint { - if x != nil { - return x.Inputs - } - return nil -} - type SendManyRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The map from addresses to amounts - AddrToAmount map[string]int64 `protobuf:"bytes,1,rep,name=AddrToAmount,proto3" json:"AddrToAmount,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + AddrToAmount map[string]int64 `protobuf:"bytes,1,rep,name=AddrToAmount,proto3" json:"AddrToAmount,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` // The target number of blocks that this transaction should be confirmed // by. TargetConf int32 `protobuf:"varint,3,opt,name=target_conf,json=targetConf,proto3" json:"target_conf,omitempty"` @@ -3418,15 +3477,15 @@ type SendManyRequest struct { SpendUnconfirmed bool `protobuf:"varint,8,opt,name=spend_unconfirmed,json=spendUnconfirmed,proto3" json:"spend_unconfirmed,omitempty"` // The strategy to use for selecting coins during sending many requests. CoinSelectionStrategy CoinSelectionStrategy `protobuf:"varint,9,opt,name=coin_selection_strategy,json=coinSelectionStrategy,proto3,enum=lnrpc.CoinSelectionStrategy" json:"coin_selection_strategy,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *SendManyRequest) Reset() { *x = SendManyRequest{} - mi := &file_lightning_proto_msgTypes[24] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SendManyRequest) String() string { @@ -3436,8 +3495,8 @@ func (x *SendManyRequest) String() string { func (*SendManyRequest) ProtoMessage() {} func (x *SendManyRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[24] - if x != nil { + mi := &file_lightning_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3449,7 +3508,7 @@ func (x *SendManyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SendManyRequest.ProtoReflect.Descriptor instead. func (*SendManyRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{24} + return file_lightning_proto_rawDescGZIP(), []int{23} } func (x *SendManyRequest) GetAddrToAmount() map[string]int64 { @@ -3510,18 +3569,21 @@ func (x *SendManyRequest) GetCoinSelectionStrategy() CoinSelectionStrategy { } type SendManyResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The id of the transaction - Txid string `protobuf:"bytes,1,opt,name=txid,proto3" json:"txid,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The id of the transaction + Txid string `protobuf:"bytes,1,opt,name=txid,proto3" json:"txid,omitempty"` } func (x *SendManyResponse) Reset() { *x = SendManyResponse{} - mi := &file_lightning_proto_msgTypes[25] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SendManyResponse) String() string { @@ -3531,8 +3593,8 @@ func (x *SendManyResponse) String() string { func (*SendManyResponse) ProtoMessage() {} func (x *SendManyResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[25] - if x != nil { + mi := &file_lightning_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3544,7 +3606,7 @@ func (x *SendManyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SendManyResponse.ProtoReflect.Descriptor instead. func (*SendManyResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{25} + return file_lightning_proto_rawDescGZIP(), []int{24} } func (x *SendManyResponse) GetTxid() string { @@ -3555,7 +3617,10 @@ func (x *SendManyResponse) GetTxid() string { } type SendCoinsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The address to send coins to Addr string `protobuf:"bytes,1,opt,name=addr,proto3" json:"addr,omitempty"` // The amount in satoshis to send @@ -3585,16 +3650,16 @@ type SendCoinsRequest struct { // The strategy to use for selecting coins. CoinSelectionStrategy CoinSelectionStrategy `protobuf:"varint,10,opt,name=coin_selection_strategy,json=coinSelectionStrategy,proto3,enum=lnrpc.CoinSelectionStrategy" json:"coin_selection_strategy,omitempty"` // A list of selected outpoints as inputs for the transaction. - Outpoints []*OutPoint `protobuf:"bytes,11,rep,name=outpoints,proto3" json:"outpoints,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Outpoints []*OutPoint `protobuf:"bytes,11,rep,name=outpoints,proto3" json:"outpoints,omitempty"` } func (x *SendCoinsRequest) Reset() { *x = SendCoinsRequest{} - mi := &file_lightning_proto_msgTypes[26] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SendCoinsRequest) String() string { @@ -3604,8 +3669,8 @@ func (x *SendCoinsRequest) String() string { func (*SendCoinsRequest) ProtoMessage() {} func (x *SendCoinsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[26] - if x != nil { + mi := &file_lightning_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3617,7 +3682,7 @@ func (x *SendCoinsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SendCoinsRequest.ProtoReflect.Descriptor instead. func (*SendCoinsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{26} + return file_lightning_proto_rawDescGZIP(), []int{25} } func (x *SendCoinsRequest) GetAddr() string { @@ -3699,18 +3764,21 @@ func (x *SendCoinsRequest) GetOutpoints() []*OutPoint { } type SendCoinsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The transaction ID of the transaction - Txid string `protobuf:"bytes,1,opt,name=txid,proto3" json:"txid,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The transaction ID of the transaction + Txid string `protobuf:"bytes,1,opt,name=txid,proto3" json:"txid,omitempty"` } func (x *SendCoinsResponse) Reset() { *x = SendCoinsResponse{} - mi := &file_lightning_proto_msgTypes[27] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SendCoinsResponse) String() string { @@ -3720,8 +3788,8 @@ func (x *SendCoinsResponse) String() string { func (*SendCoinsResponse) ProtoMessage() {} func (x *SendCoinsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[27] - if x != nil { + mi := &file_lightning_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3733,7 +3801,7 @@ func (x *SendCoinsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SendCoinsResponse.ProtoReflect.Descriptor instead. func (*SendCoinsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{27} + return file_lightning_proto_rawDescGZIP(), []int{26} } func (x *SendCoinsResponse) GetTxid() string { @@ -3744,22 +3812,25 @@ func (x *SendCoinsResponse) GetTxid() string { } type ListUnspentRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The minimum number of confirmations to be included. MinConfs int32 `protobuf:"varint,1,opt,name=min_confs,json=minConfs,proto3" json:"min_confs,omitempty"` // The maximum number of confirmations to be included. MaxConfs int32 `protobuf:"varint,2,opt,name=max_confs,json=maxConfs,proto3" json:"max_confs,omitempty"` // An optional filter to only include outputs belonging to an account. - Account string `protobuf:"bytes,3,opt,name=account,proto3" json:"account,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Account string `protobuf:"bytes,3,opt,name=account,proto3" json:"account,omitempty"` } func (x *ListUnspentRequest) Reset() { *x = ListUnspentRequest{} - mi := &file_lightning_proto_msgTypes[28] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListUnspentRequest) String() string { @@ -3769,8 +3840,8 @@ func (x *ListUnspentRequest) String() string { func (*ListUnspentRequest) ProtoMessage() {} func (x *ListUnspentRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[28] - if x != nil { + mi := &file_lightning_proto_msgTypes[27] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3782,7 +3853,7 @@ func (x *ListUnspentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUnspentRequest.ProtoReflect.Descriptor instead. func (*ListUnspentRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{28} + return file_lightning_proto_rawDescGZIP(), []int{27} } func (x *ListUnspentRequest) GetMinConfs() int32 { @@ -3807,18 +3878,21 @@ func (x *ListUnspentRequest) GetAccount() string { } type ListUnspentResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // A list of utxos - Utxos []*Utxo `protobuf:"bytes,1,rep,name=utxos,proto3" json:"utxos,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // A list of utxos + Utxos []*Utxo `protobuf:"bytes,1,rep,name=utxos,proto3" json:"utxos,omitempty"` } func (x *ListUnspentResponse) Reset() { *x = ListUnspentResponse{} - mi := &file_lightning_proto_msgTypes[29] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListUnspentResponse) String() string { @@ -3828,8 +3902,8 @@ func (x *ListUnspentResponse) String() string { func (*ListUnspentResponse) ProtoMessage() {} func (x *ListUnspentResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[29] - if x != nil { + mi := &file_lightning_proto_msgTypes[28] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3841,7 +3915,7 @@ func (x *ListUnspentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUnspentResponse.ProtoReflect.Descriptor instead. func (*ListUnspentResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{29} + return file_lightning_proto_rawDescGZIP(), []int{28} } func (x *ListUnspentResponse) GetUtxos() []*Utxo { @@ -3852,21 +3926,24 @@ func (x *ListUnspentResponse) GetUtxos() []*Utxo { } type NewAddressRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The type of address to generate. Type AddressType `protobuf:"varint,1,opt,name=type,proto3,enum=lnrpc.AddressType" json:"type,omitempty"` // The name of the account to generate a new address for. If empty, the // default wallet account is used. - Account string `protobuf:"bytes,2,opt,name=account,proto3" json:"account,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Account string `protobuf:"bytes,2,opt,name=account,proto3" json:"account,omitempty"` } func (x *NewAddressRequest) Reset() { *x = NewAddressRequest{} - mi := &file_lightning_proto_msgTypes[30] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *NewAddressRequest) String() string { @@ -3876,8 +3953,8 @@ func (x *NewAddressRequest) String() string { func (*NewAddressRequest) ProtoMessage() {} func (x *NewAddressRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[30] - if x != nil { + mi := &file_lightning_proto_msgTypes[29] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3889,7 +3966,7 @@ func (x *NewAddressRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use NewAddressRequest.ProtoReflect.Descriptor instead. func (*NewAddressRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{30} + return file_lightning_proto_rawDescGZIP(), []int{29} } func (x *NewAddressRequest) GetType() AddressType { @@ -3907,18 +3984,21 @@ func (x *NewAddressRequest) GetAccount() string { } type NewAddressResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The newly generated wallet address - Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The newly generated wallet address + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` } func (x *NewAddressResponse) Reset() { *x = NewAddressResponse{} - mi := &file_lightning_proto_msgTypes[31] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *NewAddressResponse) String() string { @@ -3928,8 +4008,8 @@ func (x *NewAddressResponse) String() string { func (*NewAddressResponse) ProtoMessage() {} func (x *NewAddressResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[31] - if x != nil { + mi := &file_lightning_proto_msgTypes[30] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3941,7 +4021,7 @@ func (x *NewAddressResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use NewAddressResponse.ProtoReflect.Descriptor instead. func (*NewAddressResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{31} + return file_lightning_proto_rawDescGZIP(), []int{30} } func (x *NewAddressResponse) GetAddress() string { @@ -3952,22 +4032,25 @@ func (x *NewAddressResponse) GetAddress() string { } type SignMessageRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The message to be signed. When using REST, this field must be encoded as // base64. Msg []byte `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` // Instead of the default double-SHA256 hashing of the message before signing, // only use one round of hashing instead. - SingleHash bool `protobuf:"varint,2,opt,name=single_hash,json=singleHash,proto3" json:"single_hash,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + SingleHash bool `protobuf:"varint,2,opt,name=single_hash,json=singleHash,proto3" json:"single_hash,omitempty"` } func (x *SignMessageRequest) Reset() { *x = SignMessageRequest{} - mi := &file_lightning_proto_msgTypes[32] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SignMessageRequest) String() string { @@ -3977,8 +4060,8 @@ func (x *SignMessageRequest) String() string { func (*SignMessageRequest) ProtoMessage() {} func (x *SignMessageRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[32] - if x != nil { + mi := &file_lightning_proto_msgTypes[31] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3990,7 +4073,7 @@ func (x *SignMessageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SignMessageRequest.ProtoReflect.Descriptor instead. func (*SignMessageRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{32} + return file_lightning_proto_rawDescGZIP(), []int{31} } func (x *SignMessageRequest) GetMsg() []byte { @@ -4008,18 +4091,21 @@ func (x *SignMessageRequest) GetSingleHash() bool { } type SignMessageResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The signature for the given message - Signature string `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The signature for the given message + Signature string `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` } func (x *SignMessageResponse) Reset() { *x = SignMessageResponse{} - mi := &file_lightning_proto_msgTypes[33] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SignMessageResponse) String() string { @@ -4029,8 +4115,8 @@ func (x *SignMessageResponse) String() string { func (*SignMessageResponse) ProtoMessage() {} func (x *SignMessageResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[33] - if x != nil { + mi := &file_lightning_proto_msgTypes[32] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4042,7 +4128,7 @@ func (x *SignMessageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SignMessageResponse.ProtoReflect.Descriptor instead. func (*SignMessageResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{33} + return file_lightning_proto_rawDescGZIP(), []int{32} } func (x *SignMessageResponse) GetSignature() string { @@ -4053,21 +4139,24 @@ func (x *SignMessageResponse) GetSignature() string { } type VerifyMessageRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The message over which the signature is to be verified. When using REST, // this field must be encoded as base64. Msg []byte `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` // The signature to be verified over the given message - Signature string `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Signature string `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` } func (x *VerifyMessageRequest) Reset() { *x = VerifyMessageRequest{} - mi := &file_lightning_proto_msgTypes[34] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *VerifyMessageRequest) String() string { @@ -4077,8 +4166,8 @@ func (x *VerifyMessageRequest) String() string { func (*VerifyMessageRequest) ProtoMessage() {} func (x *VerifyMessageRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[34] - if x != nil { + mi := &file_lightning_proto_msgTypes[33] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4090,7 +4179,7 @@ func (x *VerifyMessageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyMessageRequest.ProtoReflect.Descriptor instead. func (*VerifyMessageRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{34} + return file_lightning_proto_rawDescGZIP(), []int{33} } func (x *VerifyMessageRequest) GetMsg() []byte { @@ -4108,20 +4197,23 @@ func (x *VerifyMessageRequest) GetSignature() string { } type VerifyMessageResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Whether the signature was valid over the given message Valid bool `protobuf:"varint,1,opt,name=valid,proto3" json:"valid,omitempty"` // The pubkey recovered from the signature - Pubkey string `protobuf:"bytes,2,opt,name=pubkey,proto3" json:"pubkey,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Pubkey string `protobuf:"bytes,2,opt,name=pubkey,proto3" json:"pubkey,omitempty"` } func (x *VerifyMessageResponse) Reset() { *x = VerifyMessageResponse{} - mi := &file_lightning_proto_msgTypes[35] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *VerifyMessageResponse) String() string { @@ -4131,8 +4223,8 @@ func (x *VerifyMessageResponse) String() string { func (*VerifyMessageResponse) ProtoMessage() {} func (x *VerifyMessageResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[35] - if x != nil { + mi := &file_lightning_proto_msgTypes[34] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4144,7 +4236,7 @@ func (x *VerifyMessageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyMessageResponse.ProtoReflect.Descriptor instead. func (*VerifyMessageResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{35} + return file_lightning_proto_rawDescGZIP(), []int{34} } func (x *VerifyMessageResponse) GetValid() bool { @@ -4162,7 +4254,10 @@ func (x *VerifyMessageResponse) GetPubkey() string { } type ConnectPeerRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Lightning address of the peer to connect to. Addr *LightningAddress `protobuf:"bytes,1,opt,name=addr,proto3" json:"addr,omitempty"` // If set, the daemon will attempt to persistently connect to the target @@ -4170,16 +4265,16 @@ type ConnectPeerRequest struct { Perm bool `protobuf:"varint,2,opt,name=perm,proto3" json:"perm,omitempty"` // The connection timeout value (in seconds) for this request. It won't affect // other requests. - Timeout uint64 `protobuf:"varint,3,opt,name=timeout,proto3" json:"timeout,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Timeout uint64 `protobuf:"varint,3,opt,name=timeout,proto3" json:"timeout,omitempty"` } func (x *ConnectPeerRequest) Reset() { *x = ConnectPeerRequest{} - mi := &file_lightning_proto_msgTypes[36] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ConnectPeerRequest) String() string { @@ -4189,8 +4284,8 @@ func (x *ConnectPeerRequest) String() string { func (*ConnectPeerRequest) ProtoMessage() {} func (x *ConnectPeerRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[36] - if x != nil { + mi := &file_lightning_proto_msgTypes[35] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4202,7 +4297,7 @@ func (x *ConnectPeerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConnectPeerRequest.ProtoReflect.Descriptor instead. func (*ConnectPeerRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{36} + return file_lightning_proto_rawDescGZIP(), []int{35} } func (x *ConnectPeerRequest) GetAddr() *LightningAddress { @@ -4227,18 +4322,21 @@ func (x *ConnectPeerRequest) GetTimeout() uint64 { } type ConnectPeerResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The status of the connect operation. - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The status of the connect operation. + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` } func (x *ConnectPeerResponse) Reset() { *x = ConnectPeerResponse{} - mi := &file_lightning_proto_msgTypes[37] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ConnectPeerResponse) String() string { @@ -4248,8 +4346,8 @@ func (x *ConnectPeerResponse) String() string { func (*ConnectPeerResponse) ProtoMessage() {} func (x *ConnectPeerResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[37] - if x != nil { + mi := &file_lightning_proto_msgTypes[36] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4261,7 +4359,7 @@ func (x *ConnectPeerResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConnectPeerResponse.ProtoReflect.Descriptor instead. func (*ConnectPeerResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{37} + return file_lightning_proto_rawDescGZIP(), []int{36} } func (x *ConnectPeerResponse) GetStatus() string { @@ -4272,18 +4370,21 @@ func (x *ConnectPeerResponse) GetStatus() string { } type DisconnectPeerRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The pubkey of the node to disconnect from - PubKey string `protobuf:"bytes,1,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The pubkey of the node to disconnect from + PubKey string `protobuf:"bytes,1,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty"` } func (x *DisconnectPeerRequest) Reset() { *x = DisconnectPeerRequest{} - mi := &file_lightning_proto_msgTypes[38] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *DisconnectPeerRequest) String() string { @@ -4293,8 +4394,8 @@ func (x *DisconnectPeerRequest) String() string { func (*DisconnectPeerRequest) ProtoMessage() {} func (x *DisconnectPeerRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[38] - if x != nil { + mi := &file_lightning_proto_msgTypes[37] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4306,7 +4407,7 @@ func (x *DisconnectPeerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DisconnectPeerRequest.ProtoReflect.Descriptor instead. func (*DisconnectPeerRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{38} + return file_lightning_proto_rawDescGZIP(), []int{37} } func (x *DisconnectPeerRequest) GetPubKey() string { @@ -4317,18 +4418,21 @@ func (x *DisconnectPeerRequest) GetPubKey() string { } type DisconnectPeerResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The status of the disconnect operation. - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The status of the disconnect operation. + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` } func (x *DisconnectPeerResponse) Reset() { *x = DisconnectPeerResponse{} - mi := &file_lightning_proto_msgTypes[39] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *DisconnectPeerResponse) String() string { @@ -4338,8 +4442,8 @@ func (x *DisconnectPeerResponse) String() string { func (*DisconnectPeerResponse) ProtoMessage() {} func (x *DisconnectPeerResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[39] - if x != nil { + mi := &file_lightning_proto_msgTypes[38] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4351,7 +4455,7 @@ func (x *DisconnectPeerResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DisconnectPeerResponse.ProtoReflect.Descriptor instead. func (*DisconnectPeerResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{39} + return file_lightning_proto_rawDescGZIP(), []int{38} } func (x *DisconnectPeerResponse) GetStatus() string { @@ -4362,11 +4466,14 @@ func (x *DisconnectPeerResponse) GetStatus() string { } type HTLC struct { - state protoimpl.MessageState `protogen:"open.v1"` - Incoming bool `protobuf:"varint,1,opt,name=incoming,proto3" json:"incoming,omitempty"` - Amount int64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` - HashLock []byte `protobuf:"bytes,3,opt,name=hash_lock,json=hashLock,proto3" json:"hash_lock,omitempty"` - ExpirationHeight uint32 `protobuf:"varint,4,opt,name=expiration_height,json=expirationHeight,proto3" json:"expiration_height,omitempty"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Incoming bool `protobuf:"varint,1,opt,name=incoming,proto3" json:"incoming,omitempty"` + Amount int64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` + HashLock []byte `protobuf:"bytes,3,opt,name=hash_lock,json=hashLock,proto3" json:"hash_lock,omitempty"` + ExpirationHeight uint32 `protobuf:"varint,4,opt,name=expiration_height,json=expirationHeight,proto3" json:"expiration_height,omitempty"` // Index identifying the htlc on the channel. HtlcIndex uint64 `protobuf:"varint,5,opt,name=htlc_index,json=htlcIndex,proto3" json:"htlc_index,omitempty"` // If this HTLC is involved in a forwarding operation, this field indicates @@ -4382,16 +4489,16 @@ type HTLC struct { // Whether the HTLC is locked in. An HTLC is considered locked in when the // remote party has sent us the `revoke_and_ack` to irrevocably commit this // HTLC. - LockedIn bool `protobuf:"varint,8,opt,name=locked_in,json=lockedIn,proto3" json:"locked_in,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + LockedIn bool `protobuf:"varint,8,opt,name=locked_in,json=lockedIn,proto3" json:"locked_in,omitempty"` } func (x *HTLC) Reset() { *x = HTLC{} - mi := &file_lightning_proto_msgTypes[40] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[39] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *HTLC) String() string { @@ -4401,8 +4508,8 @@ func (x *HTLC) String() string { func (*HTLC) ProtoMessage() {} func (x *HTLC) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[40] - if x != nil { + mi := &file_lightning_proto_msgTypes[39] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4414,7 +4521,7 @@ func (x *HTLC) ProtoReflect() protoreflect.Message { // Deprecated: Use HTLC.ProtoReflect.Descriptor instead. func (*HTLC) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{40} + return file_lightning_proto_rawDescGZIP(), []int{39} } func (x *HTLC) GetIncoming() bool { @@ -4474,7 +4581,10 @@ func (x *HTLC) GetLockedIn() bool { } type ChannelConstraints struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The CSV delay expressed in relative blocks. If the channel is force closed, // we will need to wait for this many blocks before we can regain our funds. CsvDelay uint32 `protobuf:"varint,1,opt,name=csv_delay,json=csvDelay,proto3" json:"csv_delay,omitempty"` @@ -4489,15 +4599,15 @@ type ChannelConstraints struct { MinHtlcMsat uint64 `protobuf:"varint,5,opt,name=min_htlc_msat,json=minHtlcMsat,proto3" json:"min_htlc_msat,omitempty"` // The total number of incoming HTLC's that the initiator will accept. MaxAcceptedHtlcs uint32 `protobuf:"varint,6,opt,name=max_accepted_htlcs,json=maxAcceptedHtlcs,proto3" json:"max_accepted_htlcs,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ChannelConstraints) Reset() { *x = ChannelConstraints{} - mi := &file_lightning_proto_msgTypes[41] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChannelConstraints) String() string { @@ -4507,8 +4617,8 @@ func (x *ChannelConstraints) String() string { func (*ChannelConstraints) ProtoMessage() {} func (x *ChannelConstraints) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[41] - if x != nil { + mi := &file_lightning_proto_msgTypes[40] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4520,7 +4630,7 @@ func (x *ChannelConstraints) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelConstraints.ProtoReflect.Descriptor instead. func (*ChannelConstraints) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{41} + return file_lightning_proto_rawDescGZIP(), []int{40} } func (x *ChannelConstraints) GetCsvDelay() uint32 { @@ -4566,7 +4676,10 @@ func (x *ChannelConstraints) GetMaxAcceptedHtlcs() uint32 { } type Channel struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Whether this channel is active or not Active bool `protobuf:"varint,1,opt,name=active,proto3" json:"active,omitempty"` // The identity pubkey of the remote node @@ -4683,15 +4796,15 @@ type Channel struct { Memo string `protobuf:"bytes,36,opt,name=memo,proto3" json:"memo,omitempty"` // Custom channel data that might be populated in custom channels. CustomChannelData []byte `protobuf:"bytes,37,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *Channel) Reset() { *x = Channel{} - mi := &file_lightning_proto_msgTypes[42] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *Channel) String() string { @@ -4701,8 +4814,8 @@ func (x *Channel) String() string { func (*Channel) ProtoMessage() {} func (x *Channel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[42] - if x != nil { + mi := &file_lightning_proto_msgTypes[41] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4714,7 +4827,7 @@ func (x *Channel) ProtoReflect() protoreflect.Message { // Deprecated: Use Channel.ProtoReflect.Descriptor instead. func (*Channel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{42} + return file_lightning_proto_rawDescGZIP(), []int{41} } func (x *Channel) GetActive() bool { @@ -4981,11 +5094,14 @@ func (x *Channel) GetCustomChannelData() []byte { } type ListChannelsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ActiveOnly bool `protobuf:"varint,1,opt,name=active_only,json=activeOnly,proto3" json:"active_only,omitempty"` - InactiveOnly bool `protobuf:"varint,2,opt,name=inactive_only,json=inactiveOnly,proto3" json:"inactive_only,omitempty"` - PublicOnly bool `protobuf:"varint,3,opt,name=public_only,json=publicOnly,proto3" json:"public_only,omitempty"` - PrivateOnly bool `protobuf:"varint,4,opt,name=private_only,json=privateOnly,proto3" json:"private_only,omitempty"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ActiveOnly bool `protobuf:"varint,1,opt,name=active_only,json=activeOnly,proto3" json:"active_only,omitempty"` + InactiveOnly bool `protobuf:"varint,2,opt,name=inactive_only,json=inactiveOnly,proto3" json:"inactive_only,omitempty"` + PublicOnly bool `protobuf:"varint,3,opt,name=public_only,json=publicOnly,proto3" json:"public_only,omitempty"` + PrivateOnly bool `protobuf:"varint,4,opt,name=private_only,json=privateOnly,proto3" json:"private_only,omitempty"` // Filters the response for channels with a target peer's pubkey. If peer is // empty, all channels will be returned. Peer []byte `protobuf:"bytes,5,opt,name=peer,proto3" json:"peer,omitempty"` @@ -4993,15 +5109,15 @@ type ListChannelsRequest struct { // enabled. It is turned off by default in order to avoid degradation of // performance for existing clients. PeerAliasLookup bool `protobuf:"varint,6,opt,name=peer_alias_lookup,json=peerAliasLookup,proto3" json:"peer_alias_lookup,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ListChannelsRequest) Reset() { *x = ListChannelsRequest{} - mi := &file_lightning_proto_msgTypes[43] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[42] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListChannelsRequest) String() string { @@ -5011,8 +5127,8 @@ func (x *ListChannelsRequest) String() string { func (*ListChannelsRequest) ProtoMessage() {} func (x *ListChannelsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[43] - if x != nil { + mi := &file_lightning_proto_msgTypes[42] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5024,7 +5140,7 @@ func (x *ListChannelsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListChannelsRequest.ProtoReflect.Descriptor instead. func (*ListChannelsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{43} + return file_lightning_proto_rawDescGZIP(), []int{42} } func (x *ListChannelsRequest) GetActiveOnly() bool { @@ -5070,18 +5186,21 @@ func (x *ListChannelsRequest) GetPeerAliasLookup() bool { } type ListChannelsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The list of active channels - Channels []*Channel `protobuf:"bytes,11,rep,name=channels,proto3" json:"channels,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The list of active channels + Channels []*Channel `protobuf:"bytes,11,rep,name=channels,proto3" json:"channels,omitempty"` } func (x *ListChannelsResponse) Reset() { *x = ListChannelsResponse{} - mi := &file_lightning_proto_msgTypes[44] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[43] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListChannelsResponse) String() string { @@ -5091,8 +5210,8 @@ func (x *ListChannelsResponse) String() string { func (*ListChannelsResponse) ProtoMessage() {} func (x *ListChannelsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[44] - if x != nil { + mi := &file_lightning_proto_msgTypes[43] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5104,7 +5223,7 @@ func (x *ListChannelsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListChannelsResponse.ProtoReflect.Descriptor instead. func (*ListChannelsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{44} + return file_lightning_proto_rawDescGZIP(), []int{43} } func (x *ListChannelsResponse) GetChannels() []*Channel { @@ -5115,21 +5234,24 @@ func (x *ListChannelsResponse) GetChannels() []*Channel { } type AliasMap struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // For non-zero-conf channels, this is the confirmed SCID. Otherwise, this is // the first assigned "base" alias. BaseScid uint64 `protobuf:"varint,1,opt,name=base_scid,json=baseScid,proto3" json:"base_scid,omitempty"` // The set of all aliases stored for the base SCID. - Aliases []uint64 `protobuf:"varint,2,rep,packed,name=aliases,proto3" json:"aliases,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Aliases []uint64 `protobuf:"varint,2,rep,packed,name=aliases,proto3" json:"aliases,omitempty"` } func (x *AliasMap) Reset() { *x = AliasMap{} - mi := &file_lightning_proto_msgTypes[45] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[44] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *AliasMap) String() string { @@ -5139,8 +5261,8 @@ func (x *AliasMap) String() string { func (*AliasMap) ProtoMessage() {} func (x *AliasMap) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[45] - if x != nil { + mi := &file_lightning_proto_msgTypes[44] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5152,7 +5274,7 @@ func (x *AliasMap) ProtoReflect() protoreflect.Message { // Deprecated: Use AliasMap.ProtoReflect.Descriptor instead. func (*AliasMap) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{45} + return file_lightning_proto_rawDescGZIP(), []int{44} } func (x *AliasMap) GetBaseScid() uint64 { @@ -5170,16 +5292,18 @@ func (x *AliasMap) GetAliases() []uint64 { } type ListAliasesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *ListAliasesRequest) Reset() { *x = ListAliasesRequest{} - mi := &file_lightning_proto_msgTypes[46] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[45] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListAliasesRequest) String() string { @@ -5189,8 +5313,8 @@ func (x *ListAliasesRequest) String() string { func (*ListAliasesRequest) ProtoMessage() {} func (x *ListAliasesRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[46] - if x != nil { + mi := &file_lightning_proto_msgTypes[45] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5202,21 +5326,24 @@ func (x *ListAliasesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAliasesRequest.ProtoReflect.Descriptor instead. func (*ListAliasesRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{46} + return file_lightning_proto_rawDescGZIP(), []int{45} } type ListAliasesResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - AliasMaps []*AliasMap `protobuf:"bytes,1,rep,name=alias_maps,json=aliasMaps,proto3" json:"alias_maps,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AliasMaps []*AliasMap `protobuf:"bytes,1,rep,name=alias_maps,json=aliasMaps,proto3" json:"alias_maps,omitempty"` } func (x *ListAliasesResponse) Reset() { *x = ListAliasesResponse{} - mi := &file_lightning_proto_msgTypes[47] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[46] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListAliasesResponse) String() string { @@ -5226,8 +5353,8 @@ func (x *ListAliasesResponse) String() string { func (*ListAliasesResponse) ProtoMessage() {} func (x *ListAliasesResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[47] - if x != nil { + mi := &file_lightning_proto_msgTypes[46] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5239,7 +5366,7 @@ func (x *ListAliasesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAliasesResponse.ProtoReflect.Descriptor instead. func (*ListAliasesResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{47} + return file_lightning_proto_rawDescGZIP(), []int{46} } func (x *ListAliasesResponse) GetAliasMaps() []*AliasMap { @@ -5250,7 +5377,10 @@ func (x *ListAliasesResponse) GetAliasMaps() []*AliasMap { } type ChannelCloseSummary struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The outpoint (txid:index) of the funding transaction. ChannelPoint string `protobuf:"bytes,1,opt,name=channel_point,json=channelPoint,proto3" json:"channel_point,omitempty"` // The unique channel ID for the channel. @@ -5290,15 +5420,15 @@ type ChannelCloseSummary struct { // The TLV encoded custom channel data records for this output, which might // be set for custom channels. CustomChannelData []byte `protobuf:"bytes,16,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ChannelCloseSummary) Reset() { *x = ChannelCloseSummary{} - mi := &file_lightning_proto_msgTypes[48] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[47] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChannelCloseSummary) String() string { @@ -5308,8 +5438,8 @@ func (x *ChannelCloseSummary) String() string { func (*ChannelCloseSummary) ProtoMessage() {} func (x *ChannelCloseSummary) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[48] - if x != nil { + mi := &file_lightning_proto_msgTypes[47] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5321,7 +5451,7 @@ func (x *ChannelCloseSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelCloseSummary.ProtoReflect.Descriptor instead. func (*ChannelCloseSummary) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{48} + return file_lightning_proto_rawDescGZIP(), []int{47} } func (x *ChannelCloseSummary) GetChannelPoint() string { @@ -5437,7 +5567,10 @@ func (x *ChannelCloseSummary) GetCustomChannelData() []byte { } type Resolution struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The type of output we are resolving. ResolutionType ResolutionType `protobuf:"varint,1,opt,name=resolution_type,json=resolutionType,proto3,enum=lnrpc.ResolutionType" json:"resolution_type,omitempty"` // The outcome of our on chain action that resolved the outpoint. @@ -5448,16 +5581,16 @@ type Resolution struct { AmountSat uint64 `protobuf:"varint,4,opt,name=amount_sat,json=amountSat,proto3" json:"amount_sat,omitempty"` // The hex-encoded transaction ID of the sweep transaction that spent the // output. - SweepTxid string `protobuf:"bytes,5,opt,name=sweep_txid,json=sweepTxid,proto3" json:"sweep_txid,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + SweepTxid string `protobuf:"bytes,5,opt,name=sweep_txid,json=sweepTxid,proto3" json:"sweep_txid,omitempty"` } func (x *Resolution) Reset() { *x = Resolution{} - mi := &file_lightning_proto_msgTypes[49] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[48] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *Resolution) String() string { @@ -5467,8 +5600,8 @@ func (x *Resolution) String() string { func (*Resolution) ProtoMessage() {} func (x *Resolution) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[49] - if x != nil { + mi := &file_lightning_proto_msgTypes[48] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5480,7 +5613,7 @@ func (x *Resolution) ProtoReflect() protoreflect.Message { // Deprecated: Use Resolution.ProtoReflect.Descriptor instead. func (*Resolution) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{49} + return file_lightning_proto_rawDescGZIP(), []int{48} } func (x *Resolution) GetResolutionType() ResolutionType { @@ -5519,22 +5652,25 @@ func (x *Resolution) GetSweepTxid() string { } type ClosedChannelsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Cooperative bool `protobuf:"varint,1,opt,name=cooperative,proto3" json:"cooperative,omitempty"` - LocalForce bool `protobuf:"varint,2,opt,name=local_force,json=localForce,proto3" json:"local_force,omitempty"` - RemoteForce bool `protobuf:"varint,3,opt,name=remote_force,json=remoteForce,proto3" json:"remote_force,omitempty"` - Breach bool `protobuf:"varint,4,opt,name=breach,proto3" json:"breach,omitempty"` - FundingCanceled bool `protobuf:"varint,5,opt,name=funding_canceled,json=fundingCanceled,proto3" json:"funding_canceled,omitempty"` - Abandoned bool `protobuf:"varint,6,opt,name=abandoned,proto3" json:"abandoned,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Cooperative bool `protobuf:"varint,1,opt,name=cooperative,proto3" json:"cooperative,omitempty"` + LocalForce bool `protobuf:"varint,2,opt,name=local_force,json=localForce,proto3" json:"local_force,omitempty"` + RemoteForce bool `protobuf:"varint,3,opt,name=remote_force,json=remoteForce,proto3" json:"remote_force,omitempty"` + Breach bool `protobuf:"varint,4,opt,name=breach,proto3" json:"breach,omitempty"` + FundingCanceled bool `protobuf:"varint,5,opt,name=funding_canceled,json=fundingCanceled,proto3" json:"funding_canceled,omitempty"` + Abandoned bool `protobuf:"varint,6,opt,name=abandoned,proto3" json:"abandoned,omitempty"` } func (x *ClosedChannelsRequest) Reset() { *x = ClosedChannelsRequest{} - mi := &file_lightning_proto_msgTypes[50] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[49] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ClosedChannelsRequest) String() string { @@ -5544,8 +5680,8 @@ func (x *ClosedChannelsRequest) String() string { func (*ClosedChannelsRequest) ProtoMessage() {} func (x *ClosedChannelsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[50] - if x != nil { + mi := &file_lightning_proto_msgTypes[49] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5557,7 +5693,7 @@ func (x *ClosedChannelsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ClosedChannelsRequest.ProtoReflect.Descriptor instead. func (*ClosedChannelsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{50} + return file_lightning_proto_rawDescGZIP(), []int{49} } func (x *ClosedChannelsRequest) GetCooperative() bool { @@ -5603,17 +5739,20 @@ func (x *ClosedChannelsRequest) GetAbandoned() bool { } type ClosedChannelsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Channels []*ChannelCloseSummary `protobuf:"bytes,1,rep,name=channels,proto3" json:"channels,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Channels []*ChannelCloseSummary `protobuf:"bytes,1,rep,name=channels,proto3" json:"channels,omitempty"` } func (x *ClosedChannelsResponse) Reset() { *x = ClosedChannelsResponse{} - mi := &file_lightning_proto_msgTypes[51] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[50] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ClosedChannelsResponse) String() string { @@ -5623,8 +5762,8 @@ func (x *ClosedChannelsResponse) String() string { func (*ClosedChannelsResponse) ProtoMessage() {} func (x *ClosedChannelsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[51] - if x != nil { + mi := &file_lightning_proto_msgTypes[50] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5636,7 +5775,7 @@ func (x *ClosedChannelsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ClosedChannelsResponse.ProtoReflect.Descriptor instead. func (*ClosedChannelsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{51} + return file_lightning_proto_rawDescGZIP(), []int{50} } func (x *ClosedChannelsResponse) GetChannels() []*ChannelCloseSummary { @@ -5647,7 +5786,10 @@ func (x *ClosedChannelsResponse) GetChannels() []*ChannelCloseSummary { } type Peer struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The identity pubkey of the peer PubKey string `protobuf:"bytes,1,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty"` // Network address of the peer; eg `127.0.0.1:10011` @@ -5667,7 +5809,7 @@ type Peer struct { // The type of sync we are currently performing with this peer. SyncType Peer_SyncType `protobuf:"varint,10,opt,name=sync_type,json=syncType,proto3,enum=lnrpc.Peer_SyncType" json:"sync_type,omitempty"` // Features advertised by the remote peer in their init message. - Features map[uint32]*Feature `protobuf:"bytes,11,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Features map[uint32]*Feature `protobuf:"bytes,11,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // The latest errors received from our peer with timestamps, limited to the 10 // most recent errors. These errors are tracked across peer connections, but // are not persisted across lnd restarts. Note that these errors are only @@ -5686,15 +5828,15 @@ type Peer struct { LastFlapNs int64 `protobuf:"varint,14,opt,name=last_flap_ns,json=lastFlapNs,proto3" json:"last_flap_ns,omitempty"` // The last ping payload the peer has sent to us. LastPingPayload []byte `protobuf:"bytes,15,opt,name=last_ping_payload,json=lastPingPayload,proto3" json:"last_ping_payload,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *Peer) Reset() { *x = Peer{} - mi := &file_lightning_proto_msgTypes[52] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[51] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *Peer) String() string { @@ -5704,8 +5846,8 @@ func (x *Peer) String() string { func (*Peer) ProtoMessage() {} func (x *Peer) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[52] - if x != nil { + mi := &file_lightning_proto_msgTypes[51] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5717,7 +5859,7 @@ func (x *Peer) ProtoReflect() protoreflect.Message { // Deprecated: Use Peer.ProtoReflect.Descriptor instead. func (*Peer) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{52} + return file_lightning_proto_rawDescGZIP(), []int{51} } func (x *Peer) GetPubKey() string { @@ -5819,20 +5961,23 @@ func (x *Peer) GetLastPingPayload() []byte { } type TimestampedError struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The unix timestamp in seconds when the error occurred. Timestamp uint64 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` // The string representation of the error sent by our peer. - Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` } func (x *TimestampedError) Reset() { *x = TimestampedError{} - mi := &file_lightning_proto_msgTypes[53] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[52] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *TimestampedError) String() string { @@ -5842,8 +5987,8 @@ func (x *TimestampedError) String() string { func (*TimestampedError) ProtoMessage() {} func (x *TimestampedError) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[53] - if x != nil { + mi := &file_lightning_proto_msgTypes[52] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5855,7 +6000,7 @@ func (x *TimestampedError) ProtoReflect() protoreflect.Message { // Deprecated: Use TimestampedError.ProtoReflect.Descriptor instead. func (*TimestampedError) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{53} + return file_lightning_proto_rawDescGZIP(), []int{52} } func (x *TimestampedError) GetTimestamp() uint64 { @@ -5873,20 +6018,23 @@ func (x *TimestampedError) GetError() string { } type ListPeersRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // If true, only the last error that our peer sent us will be returned with // the peer's information, rather than the full set of historic errors we have // stored. - LatestError bool `protobuf:"varint,1,opt,name=latest_error,json=latestError,proto3" json:"latest_error,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + LatestError bool `protobuf:"varint,1,opt,name=latest_error,json=latestError,proto3" json:"latest_error,omitempty"` } func (x *ListPeersRequest) Reset() { *x = ListPeersRequest{} - mi := &file_lightning_proto_msgTypes[54] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[53] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListPeersRequest) String() string { @@ -5896,8 +6044,8 @@ func (x *ListPeersRequest) String() string { func (*ListPeersRequest) ProtoMessage() {} func (x *ListPeersRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[54] - if x != nil { + mi := &file_lightning_proto_msgTypes[53] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5909,7 +6057,7 @@ func (x *ListPeersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPeersRequest.ProtoReflect.Descriptor instead. func (*ListPeersRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{54} + return file_lightning_proto_rawDescGZIP(), []int{53} } func (x *ListPeersRequest) GetLatestError() bool { @@ -5920,18 +6068,21 @@ func (x *ListPeersRequest) GetLatestError() bool { } type ListPeersResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The list of currently connected peers - Peers []*Peer `protobuf:"bytes,1,rep,name=peers,proto3" json:"peers,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The list of currently connected peers + Peers []*Peer `protobuf:"bytes,1,rep,name=peers,proto3" json:"peers,omitempty"` } func (x *ListPeersResponse) Reset() { *x = ListPeersResponse{} - mi := &file_lightning_proto_msgTypes[55] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[54] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListPeersResponse) String() string { @@ -5941,8 +6092,8 @@ func (x *ListPeersResponse) String() string { func (*ListPeersResponse) ProtoMessage() {} func (x *ListPeersResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[55] - if x != nil { + mi := &file_lightning_proto_msgTypes[54] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5954,7 +6105,7 @@ func (x *ListPeersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPeersResponse.ProtoReflect.Descriptor instead. func (*ListPeersResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{55} + return file_lightning_proto_rawDescGZIP(), []int{54} } func (x *ListPeersResponse) GetPeers() []*Peer { @@ -5965,16 +6116,18 @@ func (x *ListPeersResponse) GetPeers() []*Peer { } type PeerEventSubscription struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *PeerEventSubscription) Reset() { *x = PeerEventSubscription{} - mi := &file_lightning_proto_msgTypes[56] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[55] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PeerEventSubscription) String() string { @@ -5984,8 +6137,8 @@ func (x *PeerEventSubscription) String() string { func (*PeerEventSubscription) ProtoMessage() {} func (x *PeerEventSubscription) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[56] - if x != nil { + mi := &file_lightning_proto_msgTypes[55] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -5997,23 +6150,26 @@ func (x *PeerEventSubscription) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerEventSubscription.ProtoReflect.Descriptor instead. func (*PeerEventSubscription) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{56} + return file_lightning_proto_rawDescGZIP(), []int{55} } type PeerEvent struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The identity pubkey of the peer. - PubKey string `protobuf:"bytes,1,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty"` - Type PeerEvent_EventType `protobuf:"varint,2,opt,name=type,proto3,enum=lnrpc.PeerEvent_EventType" json:"type,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The identity pubkey of the peer. + PubKey string `protobuf:"bytes,1,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty"` + Type PeerEvent_EventType `protobuf:"varint,2,opt,name=type,proto3,enum=lnrpc.PeerEvent_EventType" json:"type,omitempty"` } func (x *PeerEvent) Reset() { *x = PeerEvent{} - mi := &file_lightning_proto_msgTypes[57] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[56] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PeerEvent) String() string { @@ -6023,8 +6179,8 @@ func (x *PeerEvent) String() string { func (*PeerEvent) ProtoMessage() {} func (x *PeerEvent) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[57] - if x != nil { + mi := &file_lightning_proto_msgTypes[56] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -6036,7 +6192,7 @@ func (x *PeerEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerEvent.ProtoReflect.Descriptor instead. func (*PeerEvent) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{57} + return file_lightning_proto_rawDescGZIP(), []int{56} } func (x *PeerEvent) GetPubKey() string { @@ -6054,16 +6210,18 @@ func (x *PeerEvent) GetType() PeerEvent_EventType { } type GetInfoRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *GetInfoRequest) Reset() { *x = GetInfoRequest{} - mi := &file_lightning_proto_msgTypes[58] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[57] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetInfoRequest) String() string { @@ -6073,8 +6231,8 @@ func (x *GetInfoRequest) String() string { func (*GetInfoRequest) ProtoMessage() {} func (x *GetInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[58] - if x != nil { + mi := &file_lightning_proto_msgTypes[57] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -6086,11 +6244,14 @@ func (x *GetInfoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetInfoRequest.ProtoReflect.Descriptor instead. func (*GetInfoRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{58} + return file_lightning_proto_rawDescGZIP(), []int{57} } type GetInfoResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The version of the LND software that the node is running. Version string `protobuf:"bytes,14,opt,name=version,proto3" json:"version,omitempty"` // The SHA1 commit hash that the daemon is compiled with. @@ -6132,25 +6293,20 @@ type GetInfoResponse struct { Uris []string `protobuf:"bytes,12,rep,name=uris,proto3" json:"uris,omitempty"` // Features that our node has advertised in our init message, node // announcements and invoices. - Features map[uint32]*Feature `protobuf:"bytes,19,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Features map[uint32]*Feature `protobuf:"bytes,19,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // Indicates whether the HTLC interceptor API is in always-on mode. RequireHtlcInterceptor bool `protobuf:"varint,21,opt,name=require_htlc_interceptor,json=requireHtlcInterceptor,proto3" json:"require_htlc_interceptor,omitempty"` // Indicates whether final htlc resolutions are stored on disk. StoreFinalHtlcResolutions bool `protobuf:"varint,22,opt,name=store_final_htlc_resolutions,json=storeFinalHtlcResolutions,proto3" json:"store_final_htlc_resolutions,omitempty"` - // Whether the wallet is fully synced to the best chain. This indicates the - // wallet's internal sync state with the backing chain source. - WalletSynced bool `protobuf:"varint,23,opt,name=wallet_synced,json=walletSynced,proto3" json:"wallet_synced,omitempty"` - // The current status of the in-memory graph cache. - GraphCacheStatus GraphCacheStatus `protobuf:"varint,24,opt,name=graph_cache_status,json=graphCacheStatus,proto3,enum=lnrpc.GraphCacheStatus" json:"graph_cache_status,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *GetInfoResponse) Reset() { *x = GetInfoResponse{} - mi := &file_lightning_proto_msgTypes[59] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[58] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetInfoResponse) String() string { @@ -6160,8 +6316,8 @@ func (x *GetInfoResponse) String() string { func (*GetInfoResponse) ProtoMessage() {} func (x *GetInfoResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[59] - if x != nil { + mi := &file_lightning_proto_msgTypes[58] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -6173,7 +6329,7 @@ func (x *GetInfoResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetInfoResponse.ProtoReflect.Descriptor instead. func (*GetInfoResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{59} + return file_lightning_proto_rawDescGZIP(), []int{58} } func (x *GetInfoResponse) GetVersion() string { @@ -6317,34 +6473,19 @@ func (x *GetInfoResponse) GetStoreFinalHtlcResolutions() bool { return false } -func (x *GetInfoResponse) GetWalletSynced() bool { - if x != nil { - return x.WalletSynced - } - return false -} - -func (x *GetInfoResponse) GetGraphCacheStatus() GraphCacheStatus { - if x != nil { - return x.GraphCacheStatus - } - return GraphCacheStatus_GRAPH_CACHE_STATUS_DISABLED -} - type GetDebugInfoRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // If set to true, the log file content will be included in the response. - // By default, only the config information is returned. - IncludeLog bool `protobuf:"varint,1,opt,name=include_log,json=includeLog,proto3" json:"include_log,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *GetDebugInfoRequest) Reset() { *x = GetDebugInfoRequest{} - mi := &file_lightning_proto_msgTypes[60] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[59] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetDebugInfoRequest) String() string { @@ -6354,8 +6495,8 @@ func (x *GetDebugInfoRequest) String() string { func (*GetDebugInfoRequest) ProtoMessage() {} func (x *GetDebugInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[60] - if x != nil { + mi := &file_lightning_proto_msgTypes[59] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -6367,29 +6508,25 @@ func (x *GetDebugInfoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDebugInfoRequest.ProtoReflect.Descriptor instead. func (*GetDebugInfoRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{60} -} - -func (x *GetDebugInfoRequest) GetIncludeLog() bool { - if x != nil { - return x.IncludeLog - } - return false + return file_lightning_proto_rawDescGZIP(), []int{59} } type GetDebugInfoResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Config map[string]string `protobuf:"bytes,1,rep,name=config,proto3" json:"config,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - Log []string `protobuf:"bytes,2,rep,name=log,proto3" json:"log,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Config map[string]string `protobuf:"bytes,1,rep,name=config,proto3" json:"config,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Log []string `protobuf:"bytes,2,rep,name=log,proto3" json:"log,omitempty"` } func (x *GetDebugInfoResponse) Reset() { *x = GetDebugInfoResponse{} - mi := &file_lightning_proto_msgTypes[61] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[60] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetDebugInfoResponse) String() string { @@ -6399,8 +6536,8 @@ func (x *GetDebugInfoResponse) String() string { func (*GetDebugInfoResponse) ProtoMessage() {} func (x *GetDebugInfoResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[61] - if x != nil { + mi := &file_lightning_proto_msgTypes[60] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -6412,7 +6549,7 @@ func (x *GetDebugInfoResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDebugInfoResponse.ProtoReflect.Descriptor instead. func (*GetDebugInfoResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{61} + return file_lightning_proto_rawDescGZIP(), []int{60} } func (x *GetDebugInfoResponse) GetConfig() map[string]string { @@ -6430,16 +6567,18 @@ func (x *GetDebugInfoResponse) GetLog() []string { } type GetRecoveryInfoRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *GetRecoveryInfoRequest) Reset() { *x = GetRecoveryInfoRequest{} - mi := &file_lightning_proto_msgTypes[62] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[61] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetRecoveryInfoRequest) String() string { @@ -6449,8 +6588,8 @@ func (x *GetRecoveryInfoRequest) String() string { func (*GetRecoveryInfoRequest) ProtoMessage() {} func (x *GetRecoveryInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[62] - if x != nil { + mi := &file_lightning_proto_msgTypes[61] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -6462,26 +6601,29 @@ func (x *GetRecoveryInfoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRecoveryInfoRequest.ProtoReflect.Descriptor instead. func (*GetRecoveryInfoRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{62} + return file_lightning_proto_rawDescGZIP(), []int{61} } type GetRecoveryInfoResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Whether the wallet is in recovery mode RecoveryMode bool `protobuf:"varint,1,opt,name=recovery_mode,json=recoveryMode,proto3" json:"recovery_mode,omitempty"` // Whether the wallet recovery progress is finished RecoveryFinished bool `protobuf:"varint,2,opt,name=recovery_finished,json=recoveryFinished,proto3" json:"recovery_finished,omitempty"` // The recovery progress, ranging from 0 to 1. - Progress float64 `protobuf:"fixed64,3,opt,name=progress,proto3" json:"progress,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Progress float64 `protobuf:"fixed64,3,opt,name=progress,proto3" json:"progress,omitempty"` } func (x *GetRecoveryInfoResponse) Reset() { *x = GetRecoveryInfoResponse{} - mi := &file_lightning_proto_msgTypes[63] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[62] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetRecoveryInfoResponse) String() string { @@ -6491,8 +6633,8 @@ func (x *GetRecoveryInfoResponse) String() string { func (*GetRecoveryInfoResponse) ProtoMessage() {} func (x *GetRecoveryInfoResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[63] - if x != nil { + mi := &file_lightning_proto_msgTypes[62] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -6504,7 +6646,7 @@ func (x *GetRecoveryInfoResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRecoveryInfoResponse.ProtoReflect.Descriptor instead. func (*GetRecoveryInfoResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{63} + return file_lightning_proto_rawDescGZIP(), []int{62} } func (x *GetRecoveryInfoResponse) GetRecoveryMode() bool { @@ -6529,23 +6671,26 @@ func (x *GetRecoveryInfoResponse) GetProgress() float64 { } type Chain struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Deprecated. The chain is now always assumed to be bitcoin. // The blockchain the node is on (must be bitcoin) // // Deprecated: Marked as deprecated in lightning.proto. Chain string `protobuf:"bytes,1,opt,name=chain,proto3" json:"chain,omitempty"` // The network the node is on (eg regtest, testnet, mainnet) - Network string `protobuf:"bytes,2,opt,name=network,proto3" json:"network,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Network string `protobuf:"bytes,2,opt,name=network,proto3" json:"network,omitempty"` } func (x *Chain) Reset() { *x = Chain{} - mi := &file_lightning_proto_msgTypes[64] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[63] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *Chain) String() string { @@ -6555,8 +6700,8 @@ func (x *Chain) String() string { func (*Chain) ProtoMessage() {} func (x *Chain) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[64] - if x != nil { + mi := &file_lightning_proto_msgTypes[63] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -6568,7 +6713,7 @@ func (x *Chain) ProtoReflect() protoreflect.Message { // Deprecated: Use Chain.ProtoReflect.Descriptor instead. func (*Chain) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{64} + return file_lightning_proto_rawDescGZIP(), []int{63} } // Deprecated: Marked as deprecated in lightning.proto. @@ -6587,17 +6732,20 @@ func (x *Chain) GetNetwork() string { } type ChannelOpenUpdate struct { - state protoimpl.MessageState `protogen:"open.v1"` - ChannelPoint *ChannelPoint `protobuf:"bytes,1,opt,name=channel_point,json=channelPoint,proto3" json:"channel_point,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ChannelPoint *ChannelPoint `protobuf:"bytes,1,opt,name=channel_point,json=channelPoint,proto3" json:"channel_point,omitempty"` } func (x *ChannelOpenUpdate) Reset() { *x = ChannelOpenUpdate{} - mi := &file_lightning_proto_msgTypes[65] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[64] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChannelOpenUpdate) String() string { @@ -6607,8 +6755,8 @@ func (x *ChannelOpenUpdate) String() string { func (*ChannelOpenUpdate) ProtoMessage() {} func (x *ChannelOpenUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[65] - if x != nil { + mi := &file_lightning_proto_msgTypes[64] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -6620,7 +6768,7 @@ func (x *ChannelOpenUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelOpenUpdate.ProtoReflect.Descriptor instead. func (*ChannelOpenUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{65} + return file_lightning_proto_rawDescGZIP(), []int{64} } func (x *ChannelOpenUpdate) GetChannelPoint() *ChannelPoint { @@ -6631,7 +6779,10 @@ func (x *ChannelOpenUpdate) GetChannelPoint() *ChannelPoint { } type CloseOutput struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The amount in satoshi of this close output. This amount is the final // commitment balance of the channel and the actual amount paid out on chain // might be smaller due to subtracted fees. @@ -6643,15 +6794,15 @@ type CloseOutput struct { // The TLV encoded custom channel data records for this output, which might // be set for custom channels. CustomChannelData []byte `protobuf:"bytes,4,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *CloseOutput) Reset() { *x = CloseOutput{} - mi := &file_lightning_proto_msgTypes[66] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[65] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *CloseOutput) String() string { @@ -6661,8 +6812,8 @@ func (x *CloseOutput) String() string { func (*CloseOutput) ProtoMessage() {} func (x *CloseOutput) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[66] - if x != nil { + mi := &file_lightning_proto_msgTypes[65] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -6674,7 +6825,7 @@ func (x *CloseOutput) ProtoReflect() protoreflect.Message { // Deprecated: Use CloseOutput.ProtoReflect.Descriptor instead. func (*CloseOutput) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{66} + return file_lightning_proto_rawDescGZIP(), []int{65} } func (x *CloseOutput) GetAmountSat() int64 { @@ -6706,9 +6857,12 @@ func (x *CloseOutput) GetCustomChannelData() []byte { } type ChannelCloseUpdate struct { - state protoimpl.MessageState `protogen:"open.v1"` - ClosingTxid []byte `protobuf:"bytes,1,opt,name=closing_txid,json=closingTxid,proto3" json:"closing_txid,omitempty"` - Success bool `protobuf:"varint,2,opt,name=success,proto3" json:"success,omitempty"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClosingTxid []byte `protobuf:"bytes,1,opt,name=closing_txid,json=closingTxid,proto3" json:"closing_txid,omitempty"` + Success bool `protobuf:"varint,2,opt,name=success,proto3" json:"success,omitempty"` // The local channel close output. If the local channel balance was dust to // begin with, this output will not be set. LocalCloseOutput *CloseOutput `protobuf:"bytes,3,opt,name=local_close_output,json=localCloseOutput,proto3" json:"local_close_output,omitempty"` @@ -6717,15 +6871,15 @@ type ChannelCloseUpdate struct { RemoteCloseOutput *CloseOutput `protobuf:"bytes,4,opt,name=remote_close_output,json=remoteCloseOutput,proto3" json:"remote_close_output,omitempty"` // Any additional outputs that might be added for custom channel types. AdditionalOutputs []*CloseOutput `protobuf:"bytes,5,rep,name=additional_outputs,json=additionalOutputs,proto3" json:"additional_outputs,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ChannelCloseUpdate) Reset() { *x = ChannelCloseUpdate{} - mi := &file_lightning_proto_msgTypes[67] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[66] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChannelCloseUpdate) String() string { @@ -6735,8 +6889,8 @@ func (x *ChannelCloseUpdate) String() string { func (*ChannelCloseUpdate) ProtoMessage() {} func (x *ChannelCloseUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[67] - if x != nil { + mi := &file_lightning_proto_msgTypes[66] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -6748,7 +6902,7 @@ func (x *ChannelCloseUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelCloseUpdate.ProtoReflect.Descriptor instead. func (*ChannelCloseUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{67} + return file_lightning_proto_rawDescGZIP(), []int{66} } func (x *ChannelCloseUpdate) GetClosingTxid() []byte { @@ -6787,7 +6941,10 @@ func (x *ChannelCloseUpdate) GetAdditionalOutputs() []*CloseOutput { } type CloseChannelRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The outpoint (txid:index) of the funding transaction. With this value, Bob // will be able to generate a signature for Alice's version of the commitment // transaction. @@ -6823,16 +6980,16 @@ type CloseChannelRequest struct { // initiated even if HTLCs are active on the channel. The channel will wait // until all HTLCs are resolved and then start the coop closing process. The // channel will be disabled in the meantime and will disallow any new HTLCs. - NoWait bool `protobuf:"varint,8,opt,name=no_wait,json=noWait,proto3" json:"no_wait,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + NoWait bool `protobuf:"varint,8,opt,name=no_wait,json=noWait,proto3" json:"no_wait,omitempty"` } func (x *CloseChannelRequest) Reset() { *x = CloseChannelRequest{} - mi := &file_lightning_proto_msgTypes[68] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[67] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *CloseChannelRequest) String() string { @@ -6842,8 +6999,8 @@ func (x *CloseChannelRequest) String() string { func (*CloseChannelRequest) ProtoMessage() {} func (x *CloseChannelRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[68] - if x != nil { + mi := &file_lightning_proto_msgTypes[67] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -6855,7 +7012,7 @@ func (x *CloseChannelRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CloseChannelRequest.ProtoReflect.Descriptor instead. func (*CloseChannelRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{68} + return file_lightning_proto_rawDescGZIP(), []int{67} } func (x *CloseChannelRequest) GetChannelPoint() *ChannelPoint { @@ -6916,22 +7073,25 @@ func (x *CloseChannelRequest) GetNoWait() bool { } type CloseStatusUpdate struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to Update: + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Update: // // *CloseStatusUpdate_ClosePending // *CloseStatusUpdate_ChanClose // *CloseStatusUpdate_CloseInstant - Update isCloseStatusUpdate_Update `protobuf_oneof:"update"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Update isCloseStatusUpdate_Update `protobuf_oneof:"update"` } func (x *CloseStatusUpdate) Reset() { *x = CloseStatusUpdate{} - mi := &file_lightning_proto_msgTypes[69] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[68] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *CloseStatusUpdate) String() string { @@ -6941,8 +7101,8 @@ func (x *CloseStatusUpdate) String() string { func (*CloseStatusUpdate) ProtoMessage() {} func (x *CloseStatusUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[69] - if x != nil { + mi := &file_lightning_proto_msgTypes[68] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -6954,39 +7114,33 @@ func (x *CloseStatusUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use CloseStatusUpdate.ProtoReflect.Descriptor instead. func (*CloseStatusUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{69} + return file_lightning_proto_rawDescGZIP(), []int{68} } -func (x *CloseStatusUpdate) GetUpdate() isCloseStatusUpdate_Update { - if x != nil { - return x.Update +func (m *CloseStatusUpdate) GetUpdate() isCloseStatusUpdate_Update { + if m != nil { + return m.Update } return nil } func (x *CloseStatusUpdate) GetClosePending() *PendingUpdate { - if x != nil { - if x, ok := x.Update.(*CloseStatusUpdate_ClosePending); ok { - return x.ClosePending - } + if x, ok := x.GetUpdate().(*CloseStatusUpdate_ClosePending); ok { + return x.ClosePending } return nil } func (x *CloseStatusUpdate) GetChanClose() *ChannelCloseUpdate { - if x != nil { - if x, ok := x.Update.(*CloseStatusUpdate_ChanClose); ok { - return x.ChanClose - } + if x, ok := x.GetUpdate().(*CloseStatusUpdate_ChanClose); ok { + return x.ChanClose } return nil } func (x *CloseStatusUpdate) GetCloseInstant() *InstantUpdate { - if x != nil { - if x, ok := x.Update.(*CloseStatusUpdate_CloseInstant); ok { - return x.CloseInstant - } + if x, ok := x.GetUpdate().(*CloseStatusUpdate_CloseInstant); ok { + return x.CloseInstant } return nil } @@ -7014,20 +7168,23 @@ func (*CloseStatusUpdate_ChanClose) isCloseStatusUpdate_Update() {} func (*CloseStatusUpdate_CloseInstant) isCloseStatusUpdate_Update() {} type PendingUpdate struct { - state protoimpl.MessageState `protogen:"open.v1"` - Txid []byte `protobuf:"bytes,1,opt,name=txid,proto3" json:"txid,omitempty"` - OutputIndex uint32 `protobuf:"varint,2,opt,name=output_index,json=outputIndex,proto3" json:"output_index,omitempty"` - FeePerVbyte int64 `protobuf:"varint,3,opt,name=fee_per_vbyte,json=feePerVbyte,proto3" json:"fee_per_vbyte,omitempty"` - LocalCloseTx bool `protobuf:"varint,4,opt,name=local_close_tx,json=localCloseTx,proto3" json:"local_close_tx,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Txid []byte `protobuf:"bytes,1,opt,name=txid,proto3" json:"txid,omitempty"` + OutputIndex uint32 `protobuf:"varint,2,opt,name=output_index,json=outputIndex,proto3" json:"output_index,omitempty"` + FeePerVbyte int64 `protobuf:"varint,3,opt,name=fee_per_vbyte,json=feePerVbyte,proto3" json:"fee_per_vbyte,omitempty"` + LocalCloseTx bool `protobuf:"varint,4,opt,name=local_close_tx,json=localCloseTx,proto3" json:"local_close_tx,omitempty"` } func (x *PendingUpdate) Reset() { *x = PendingUpdate{} - mi := &file_lightning_proto_msgTypes[70] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[69] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PendingUpdate) String() string { @@ -7037,8 +7194,8 @@ func (x *PendingUpdate) String() string { func (*PendingUpdate) ProtoMessage() {} func (x *PendingUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[70] - if x != nil { + mi := &file_lightning_proto_msgTypes[69] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -7050,7 +7207,7 @@ func (x *PendingUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingUpdate.ProtoReflect.Descriptor instead. func (*PendingUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{70} + return file_lightning_proto_rawDescGZIP(), []int{69} } func (x *PendingUpdate) GetTxid() []byte { @@ -7082,20 +7239,23 @@ func (x *PendingUpdate) GetLocalCloseTx() bool { } type InstantUpdate struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The number of pending HTLCs that are currently active on the channel. // These HTLCs need to be resolved before the channel can be closed // cooperatively. NumPendingHtlcs int32 `protobuf:"varint,1,opt,name=num_pending_htlcs,json=numPendingHtlcs,proto3" json:"num_pending_htlcs,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *InstantUpdate) Reset() { *x = InstantUpdate{} - mi := &file_lightning_proto_msgTypes[71] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[70] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *InstantUpdate) String() string { @@ -7105,8 +7265,8 @@ func (x *InstantUpdate) String() string { func (*InstantUpdate) ProtoMessage() {} func (x *InstantUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[71] - if x != nil { + mi := &file_lightning_proto_msgTypes[70] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -7118,7 +7278,7 @@ func (x *InstantUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use InstantUpdate.ProtoReflect.Descriptor instead. func (*InstantUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{71} + return file_lightning_proto_rawDescGZIP(), []int{70} } func (x *InstantUpdate) GetNumPendingHtlcs() int32 { @@ -7129,7 +7289,10 @@ func (x *InstantUpdate) GetNumPendingHtlcs() int32 { } type ReadyForPsbtFunding struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The P2WSH address of the channel funding multisig address that the below // specified amount in satoshis needs to be sent to. FundingAddress string `protobuf:"bytes,1,opt,name=funding_address,json=fundingAddress,proto3" json:"funding_address,omitempty"` @@ -7140,16 +7303,16 @@ type ReadyForPsbtFunding struct { // provided in the PsbtShim, this is the base PSBT with one additional output. // If no base PSBT was specified, this is an otherwise empty PSBT with exactly // one output. - Psbt []byte `protobuf:"bytes,3,opt,name=psbt,proto3" json:"psbt,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Psbt []byte `protobuf:"bytes,3,opt,name=psbt,proto3" json:"psbt,omitempty"` } func (x *ReadyForPsbtFunding) Reset() { *x = ReadyForPsbtFunding{} - mi := &file_lightning_proto_msgTypes[72] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[71] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ReadyForPsbtFunding) String() string { @@ -7159,8 +7322,8 @@ func (x *ReadyForPsbtFunding) String() string { func (*ReadyForPsbtFunding) ProtoMessage() {} func (x *ReadyForPsbtFunding) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[72] - if x != nil { + mi := &file_lightning_proto_msgTypes[71] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -7172,7 +7335,7 @@ func (x *ReadyForPsbtFunding) ProtoReflect() protoreflect.Message { // Deprecated: Use ReadyForPsbtFunding.ProtoReflect.Descriptor instead. func (*ReadyForPsbtFunding) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{72} + return file_lightning_proto_rawDescGZIP(), []int{71} } func (x *ReadyForPsbtFunding) GetFundingAddress() string { @@ -7197,7 +7360,10 @@ func (x *ReadyForPsbtFunding) GetPsbt() []byte { } type BatchOpenChannelRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The list of channels to open. Channels []*BatchOpenChannel `protobuf:"bytes,1,rep,name=channels,proto3" json:"channels,omitempty"` // The target number of blocks that the funding transaction should be @@ -7216,15 +7382,15 @@ type BatchOpenChannelRequest struct { Label string `protobuf:"bytes,6,opt,name=label,proto3" json:"label,omitempty"` // The strategy to use for selecting coins during batch opening channels. CoinSelectionStrategy CoinSelectionStrategy `protobuf:"varint,7,opt,name=coin_selection_strategy,json=coinSelectionStrategy,proto3,enum=lnrpc.CoinSelectionStrategy" json:"coin_selection_strategy,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *BatchOpenChannelRequest) Reset() { *x = BatchOpenChannelRequest{} - mi := &file_lightning_proto_msgTypes[73] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[72] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *BatchOpenChannelRequest) String() string { @@ -7234,8 +7400,8 @@ func (x *BatchOpenChannelRequest) String() string { func (*BatchOpenChannelRequest) ProtoMessage() {} func (x *BatchOpenChannelRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[73] - if x != nil { + mi := &file_lightning_proto_msgTypes[72] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -7247,7 +7413,7 @@ func (x *BatchOpenChannelRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchOpenChannelRequest.ProtoReflect.Descriptor instead. func (*BatchOpenChannelRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{73} + return file_lightning_proto_rawDescGZIP(), []int{72} } func (x *BatchOpenChannelRequest) GetChannels() []*BatchOpenChannel { @@ -7300,7 +7466,10 @@ func (x *BatchOpenChannelRequest) GetCoinSelectionStrategy() CoinSelectionStrate } type BatchOpenChannel struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The pubkey of the node to open a channel with. When using REST, this // field must be encoded as base64. NodePubkey []byte `protobuf:"bytes,1,opt,name=node_pubkey,json=nodePubkey,proto3" json:"node_pubkey,omitempty"` @@ -7370,16 +7539,16 @@ type BatchOpenChannel struct { // An optional note-to-self to go along with the channel containing some // useful information. This is only ever stored locally and in no way impacts // the channel's operation. - Memo string `protobuf:"bytes,20,opt,name=memo,proto3" json:"memo,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Memo string `protobuf:"bytes,20,opt,name=memo,proto3" json:"memo,omitempty"` } func (x *BatchOpenChannel) Reset() { *x = BatchOpenChannel{} - mi := &file_lightning_proto_msgTypes[74] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[73] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *BatchOpenChannel) String() string { @@ -7389,8 +7558,8 @@ func (x *BatchOpenChannel) String() string { func (*BatchOpenChannel) ProtoMessage() {} func (x *BatchOpenChannel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[74] - if x != nil { + mi := &file_lightning_proto_msgTypes[73] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -7402,7 +7571,7 @@ func (x *BatchOpenChannel) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchOpenChannel.ProtoReflect.Descriptor instead. func (*BatchOpenChannel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{74} + return file_lightning_proto_rawDescGZIP(), []int{73} } func (x *BatchOpenChannel) GetNodePubkey() []byte { @@ -7546,17 +7715,20 @@ func (x *BatchOpenChannel) GetMemo() string { } type BatchOpenChannelResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - PendingChannels []*PendingUpdate `protobuf:"bytes,1,rep,name=pending_channels,json=pendingChannels,proto3" json:"pending_channels,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PendingChannels []*PendingUpdate `protobuf:"bytes,1,rep,name=pending_channels,json=pendingChannels,proto3" json:"pending_channels,omitempty"` } func (x *BatchOpenChannelResponse) Reset() { *x = BatchOpenChannelResponse{} - mi := &file_lightning_proto_msgTypes[75] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[74] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *BatchOpenChannelResponse) String() string { @@ -7566,8 +7738,8 @@ func (x *BatchOpenChannelResponse) String() string { func (*BatchOpenChannelResponse) ProtoMessage() {} func (x *BatchOpenChannelResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[75] - if x != nil { + mi := &file_lightning_proto_msgTypes[74] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -7579,7 +7751,7 @@ func (x *BatchOpenChannelResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchOpenChannelResponse.ProtoReflect.Descriptor instead. func (*BatchOpenChannelResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{75} + return file_lightning_proto_rawDescGZIP(), []int{74} } func (x *BatchOpenChannelResponse) GetPendingChannels() []*PendingUpdate { @@ -7590,7 +7762,10 @@ func (x *BatchOpenChannelResponse) GetPendingChannels() []*PendingUpdate { } type OpenChannelRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // A manual fee rate set in sat/vbyte that should be used when crafting the // funding transaction. SatPerVbyte uint64 `protobuf:"varint,1,opt,name=sat_per_vbyte,json=satPerVbyte,proto3" json:"sat_per_vbyte,omitempty"` @@ -7691,16 +7866,16 @@ type OpenChannelRequest struct { // the channel's operation. Memo string `protobuf:"bytes,27,opt,name=memo,proto3" json:"memo,omitempty"` // A list of selected outpoints that are allocated for channel funding. - Outpoints []*OutPoint `protobuf:"bytes,28,rep,name=outpoints,proto3" json:"outpoints,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Outpoints []*OutPoint `protobuf:"bytes,28,rep,name=outpoints,proto3" json:"outpoints,omitempty"` } func (x *OpenChannelRequest) Reset() { *x = OpenChannelRequest{} - mi := &file_lightning_proto_msgTypes[76] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[75] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *OpenChannelRequest) String() string { @@ -7710,8 +7885,8 @@ func (x *OpenChannelRequest) String() string { func (*OpenChannelRequest) ProtoMessage() {} func (x *OpenChannelRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[76] - if x != nil { + mi := &file_lightning_proto_msgTypes[75] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -7723,7 +7898,7 @@ func (x *OpenChannelRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenChannelRequest.ProtoReflect.Descriptor instead. func (*OpenChannelRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{76} + return file_lightning_proto_rawDescGZIP(), []int{75} } func (x *OpenChannelRequest) GetSatPerVbyte() uint64 { @@ -7925,8 +8100,11 @@ func (x *OpenChannelRequest) GetOutpoints() []*OutPoint { } type OpenStatusUpdate struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to Update: + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Update: // // *OpenStatusUpdate_ChanPending // *OpenStatusUpdate_ChanOpen @@ -7935,15 +8113,15 @@ type OpenStatusUpdate struct { // The pending channel ID of the created channel. This value may be used to // further the funding flow manually via the FundingStateStep method. PendingChanId []byte `protobuf:"bytes,4,opt,name=pending_chan_id,json=pendingChanId,proto3" json:"pending_chan_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *OpenStatusUpdate) Reset() { *x = OpenStatusUpdate{} - mi := &file_lightning_proto_msgTypes[77] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[76] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *OpenStatusUpdate) String() string { @@ -7953,8 +8131,8 @@ func (x *OpenStatusUpdate) String() string { func (*OpenStatusUpdate) ProtoMessage() {} func (x *OpenStatusUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[77] - if x != nil { + mi := &file_lightning_proto_msgTypes[76] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -7966,39 +8144,33 @@ func (x *OpenStatusUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenStatusUpdate.ProtoReflect.Descriptor instead. func (*OpenStatusUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{77} + return file_lightning_proto_rawDescGZIP(), []int{76} } -func (x *OpenStatusUpdate) GetUpdate() isOpenStatusUpdate_Update { - if x != nil { - return x.Update +func (m *OpenStatusUpdate) GetUpdate() isOpenStatusUpdate_Update { + if m != nil { + return m.Update } return nil } func (x *OpenStatusUpdate) GetChanPending() *PendingUpdate { - if x != nil { - if x, ok := x.Update.(*OpenStatusUpdate_ChanPending); ok { - return x.ChanPending - } + if x, ok := x.GetUpdate().(*OpenStatusUpdate_ChanPending); ok { + return x.ChanPending } return nil } func (x *OpenStatusUpdate) GetChanOpen() *ChannelOpenUpdate { - if x != nil { - if x, ok := x.Update.(*OpenStatusUpdate_ChanOpen); ok { - return x.ChanOpen - } + if x, ok := x.GetUpdate().(*OpenStatusUpdate_ChanOpen); ok { + return x.ChanOpen } return nil } func (x *OpenStatusUpdate) GetPsbtFund() *ReadyForPsbtFunding { - if x != nil { - if x, ok := x.Update.(*OpenStatusUpdate_PsbtFund); ok { - return x.PsbtFund - } + if x, ok := x.GetUpdate().(*OpenStatusUpdate_PsbtFund); ok { + return x.PsbtFund } return nil } @@ -8039,20 +8211,23 @@ func (*OpenStatusUpdate_ChanOpen) isOpenStatusUpdate_Update() {} func (*OpenStatusUpdate_PsbtFund) isOpenStatusUpdate_Update() {} type KeyLocator struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The family of key being identified. KeyFamily int32 `protobuf:"varint,1,opt,name=key_family,json=keyFamily,proto3" json:"key_family,omitempty"` // The precise index of the key being identified. - KeyIndex int32 `protobuf:"varint,2,opt,name=key_index,json=keyIndex,proto3" json:"key_index,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + KeyIndex int32 `protobuf:"varint,2,opt,name=key_index,json=keyIndex,proto3" json:"key_index,omitempty"` } func (x *KeyLocator) Reset() { *x = KeyLocator{} - mi := &file_lightning_proto_msgTypes[78] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[77] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *KeyLocator) String() string { @@ -8062,8 +8237,8 @@ func (x *KeyLocator) String() string { func (*KeyLocator) ProtoMessage() {} func (x *KeyLocator) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[78] - if x != nil { + mi := &file_lightning_proto_msgTypes[77] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -8075,7 +8250,7 @@ func (x *KeyLocator) ProtoReflect() protoreflect.Message { // Deprecated: Use KeyLocator.ProtoReflect.Descriptor instead. func (*KeyLocator) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{78} + return file_lightning_proto_rawDescGZIP(), []int{77} } func (x *KeyLocator) GetKeyFamily() int32 { @@ -8093,20 +8268,23 @@ func (x *KeyLocator) GetKeyIndex() int32 { } type KeyDescriptor struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The raw bytes of the key being identified. RawKeyBytes []byte `protobuf:"bytes,1,opt,name=raw_key_bytes,json=rawKeyBytes,proto3" json:"raw_key_bytes,omitempty"` // The key locator that identifies which key to use for signing. - KeyLoc *KeyLocator `protobuf:"bytes,2,opt,name=key_loc,json=keyLoc,proto3" json:"key_loc,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + KeyLoc *KeyLocator `protobuf:"bytes,2,opt,name=key_loc,json=keyLoc,proto3" json:"key_loc,omitempty"` } func (x *KeyDescriptor) Reset() { *x = KeyDescriptor{} - mi := &file_lightning_proto_msgTypes[79] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[78] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *KeyDescriptor) String() string { @@ -8116,8 +8294,8 @@ func (x *KeyDescriptor) String() string { func (*KeyDescriptor) ProtoMessage() {} func (x *KeyDescriptor) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[79] - if x != nil { + mi := &file_lightning_proto_msgTypes[78] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -8129,7 +8307,7 @@ func (x *KeyDescriptor) ProtoReflect() protoreflect.Message { // Deprecated: Use KeyDescriptor.ProtoReflect.Descriptor instead. func (*KeyDescriptor) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{79} + return file_lightning_proto_rawDescGZIP(), []int{78} } func (x *KeyDescriptor) GetRawKeyBytes() []byte { @@ -8147,7 +8325,10 @@ func (x *KeyDescriptor) GetKeyLoc() *KeyLocator { } type ChanPointShim struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The size of the pre-crafted output to be used as the channel point for this // channel funding. Amt int64 `protobuf:"varint,1,opt,name=amt,proto3" json:"amt,omitempty"` @@ -8169,16 +8350,16 @@ type ChanPointShim struct { // the value is less than 500,000, or as an absolute height otherwise. ThawHeight uint32 `protobuf:"varint,6,opt,name=thaw_height,json=thawHeight,proto3" json:"thaw_height,omitempty"` // Indicates that the funding output is using a MuSig2 multi-sig output. - Musig2 bool `protobuf:"varint,7,opt,name=musig2,proto3" json:"musig2,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Musig2 bool `protobuf:"varint,7,opt,name=musig2,proto3" json:"musig2,omitempty"` } func (x *ChanPointShim) Reset() { *x = ChanPointShim{} - mi := &file_lightning_proto_msgTypes[80] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[79] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChanPointShim) String() string { @@ -8188,8 +8369,8 @@ func (x *ChanPointShim) String() string { func (*ChanPointShim) ProtoMessage() {} func (x *ChanPointShim) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[80] - if x != nil { + mi := &file_lightning_proto_msgTypes[79] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -8201,7 +8382,7 @@ func (x *ChanPointShim) ProtoReflect() protoreflect.Message { // Deprecated: Use ChanPointShim.ProtoReflect.Descriptor instead. func (*ChanPointShim) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{80} + return file_lightning_proto_rawDescGZIP(), []int{79} } func (x *ChanPointShim) GetAmt() int64 { @@ -8254,7 +8435,10 @@ func (x *ChanPointShim) GetMusig2() bool { } type PsbtShim struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // A unique identifier of 32 random bytes that will be used as the pending // channel ID to identify the PSBT state machine when interacting with it and // on the wire protocol to initiate the funding request. @@ -8268,16 +8452,16 @@ type PsbtShim struct { // This flag prevents this particular channel from broadcasting the transaction // after the negotiation with the remote peer. In a batch of channel openings // this flag should be set to true for every channel but the very last. - NoPublish bool `protobuf:"varint,3,opt,name=no_publish,json=noPublish,proto3" json:"no_publish,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + NoPublish bool `protobuf:"varint,3,opt,name=no_publish,json=noPublish,proto3" json:"no_publish,omitempty"` } func (x *PsbtShim) Reset() { *x = PsbtShim{} - mi := &file_lightning_proto_msgTypes[81] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[80] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PsbtShim) String() string { @@ -8287,8 +8471,8 @@ func (x *PsbtShim) String() string { func (*PsbtShim) ProtoMessage() {} func (x *PsbtShim) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[81] - if x != nil { + mi := &file_lightning_proto_msgTypes[80] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -8300,7 +8484,7 @@ func (x *PsbtShim) ProtoReflect() protoreflect.Message { // Deprecated: Use PsbtShim.ProtoReflect.Descriptor instead. func (*PsbtShim) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{81} + return file_lightning_proto_rawDescGZIP(), []int{80} } func (x *PsbtShim) GetPendingChanId() []byte { @@ -8325,21 +8509,24 @@ func (x *PsbtShim) GetNoPublish() bool { } type FundingShim struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to Shim: + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Shim: // // *FundingShim_ChanPointShim // *FundingShim_PsbtShim - Shim isFundingShim_Shim `protobuf_oneof:"shim"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Shim isFundingShim_Shim `protobuf_oneof:"shim"` } func (x *FundingShim) Reset() { *x = FundingShim{} - mi := &file_lightning_proto_msgTypes[82] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[81] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *FundingShim) String() string { @@ -8349,8 +8536,8 @@ func (x *FundingShim) String() string { func (*FundingShim) ProtoMessage() {} func (x *FundingShim) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[82] - if x != nil { + mi := &file_lightning_proto_msgTypes[81] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -8362,30 +8549,26 @@ func (x *FundingShim) ProtoReflect() protoreflect.Message { // Deprecated: Use FundingShim.ProtoReflect.Descriptor instead. func (*FundingShim) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{82} + return file_lightning_proto_rawDescGZIP(), []int{81} } -func (x *FundingShim) GetShim() isFundingShim_Shim { - if x != nil { - return x.Shim +func (m *FundingShim) GetShim() isFundingShim_Shim { + if m != nil { + return m.Shim } return nil } func (x *FundingShim) GetChanPointShim() *ChanPointShim { - if x != nil { - if x, ok := x.Shim.(*FundingShim_ChanPointShim); ok { - return x.ChanPointShim - } + if x, ok := x.GetShim().(*FundingShim_ChanPointShim); ok { + return x.ChanPointShim } return nil } func (x *FundingShim) GetPsbtShim() *PsbtShim { - if x != nil { - if x, ok := x.Shim.(*FundingShim_PsbtShim); ok { - return x.PsbtShim - } + if x, ok := x.GetShim().(*FundingShim_PsbtShim); ok { + return x.PsbtShim } return nil } @@ -8411,18 +8594,21 @@ func (*FundingShim_ChanPointShim) isFundingShim_Shim() {} func (*FundingShim_PsbtShim) isFundingShim_Shim() {} type FundingShimCancel struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The pending channel ID of the channel to cancel the funding shim for. PendingChanId []byte `protobuf:"bytes,1,opt,name=pending_chan_id,json=pendingChanId,proto3" json:"pending_chan_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *FundingShimCancel) Reset() { *x = FundingShimCancel{} - mi := &file_lightning_proto_msgTypes[83] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[82] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *FundingShimCancel) String() string { @@ -8432,8 +8618,8 @@ func (x *FundingShimCancel) String() string { func (*FundingShimCancel) ProtoMessage() {} func (x *FundingShimCancel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[83] - if x != nil { + mi := &file_lightning_proto_msgTypes[82] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -8445,7 +8631,7 @@ func (x *FundingShimCancel) ProtoReflect() protoreflect.Message { // Deprecated: Use FundingShimCancel.ProtoReflect.Descriptor instead. func (*FundingShimCancel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{83} + return file_lightning_proto_rawDescGZIP(), []int{82} } func (x *FundingShimCancel) GetPendingChanId() []byte { @@ -8456,7 +8642,10 @@ func (x *FundingShimCancel) GetPendingChanId() []byte { } type FundingPsbtVerify struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The funded but not yet signed PSBT that sends the exact channel capacity // amount to the PK script returned in the open channel message in a previous // step. @@ -8473,16 +8662,16 @@ type FundingPsbtVerify struct { // means no inputs or outputs can change, only signatures can be added. If the // TXID changes between this call and the publish step then the channel will // never be created and the funds will be in limbo. - SkipFinalize bool `protobuf:"varint,3,opt,name=skip_finalize,json=skipFinalize,proto3" json:"skip_finalize,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + SkipFinalize bool `protobuf:"varint,3,opt,name=skip_finalize,json=skipFinalize,proto3" json:"skip_finalize,omitempty"` } func (x *FundingPsbtVerify) Reset() { *x = FundingPsbtVerify{} - mi := &file_lightning_proto_msgTypes[84] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[83] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *FundingPsbtVerify) String() string { @@ -8492,8 +8681,8 @@ func (x *FundingPsbtVerify) String() string { func (*FundingPsbtVerify) ProtoMessage() {} func (x *FundingPsbtVerify) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[84] - if x != nil { + mi := &file_lightning_proto_msgTypes[83] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -8505,7 +8694,7 @@ func (x *FundingPsbtVerify) ProtoReflect() protoreflect.Message { // Deprecated: Use FundingPsbtVerify.ProtoReflect.Descriptor instead. func (*FundingPsbtVerify) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{84} + return file_lightning_proto_rawDescGZIP(), []int{83} } func (x *FundingPsbtVerify) GetFundedPsbt() []byte { @@ -8530,7 +8719,10 @@ func (x *FundingPsbtVerify) GetSkipFinalize() bool { } type FundingPsbtFinalize struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The funded PSBT that contains all witness data to send the exact channel // capacity amount to the PK script returned in the open channel message in a // previous step. Cannot be set at the same time as final_raw_tx. @@ -8540,16 +8732,16 @@ type FundingPsbtFinalize struct { // As an alternative to the signed PSBT with all witness data, the final raw // wire format transaction can also be specified directly. Cannot be set at the // same time as signed_psbt. - FinalRawTx []byte `protobuf:"bytes,3,opt,name=final_raw_tx,json=finalRawTx,proto3" json:"final_raw_tx,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + FinalRawTx []byte `protobuf:"bytes,3,opt,name=final_raw_tx,json=finalRawTx,proto3" json:"final_raw_tx,omitempty"` } func (x *FundingPsbtFinalize) Reset() { *x = FundingPsbtFinalize{} - mi := &file_lightning_proto_msgTypes[85] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[84] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *FundingPsbtFinalize) String() string { @@ -8559,8 +8751,8 @@ func (x *FundingPsbtFinalize) String() string { func (*FundingPsbtFinalize) ProtoMessage() {} func (x *FundingPsbtFinalize) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[85] - if x != nil { + mi := &file_lightning_proto_msgTypes[84] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -8572,7 +8764,7 @@ func (x *FundingPsbtFinalize) ProtoReflect() protoreflect.Message { // Deprecated: Use FundingPsbtFinalize.ProtoReflect.Descriptor instead. func (*FundingPsbtFinalize) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{85} + return file_lightning_proto_rawDescGZIP(), []int{84} } func (x *FundingPsbtFinalize) GetSignedPsbt() []byte { @@ -8597,23 +8789,26 @@ func (x *FundingPsbtFinalize) GetFinalRawTx() []byte { } type FundingTransitionMsg struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to Trigger: + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Trigger: // // *FundingTransitionMsg_ShimRegister // *FundingTransitionMsg_ShimCancel // *FundingTransitionMsg_PsbtVerify // *FundingTransitionMsg_PsbtFinalize - Trigger isFundingTransitionMsg_Trigger `protobuf_oneof:"trigger"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Trigger isFundingTransitionMsg_Trigger `protobuf_oneof:"trigger"` } func (x *FundingTransitionMsg) Reset() { *x = FundingTransitionMsg{} - mi := &file_lightning_proto_msgTypes[86] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[85] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *FundingTransitionMsg) String() string { @@ -8623,8 +8818,8 @@ func (x *FundingTransitionMsg) String() string { func (*FundingTransitionMsg) ProtoMessage() {} func (x *FundingTransitionMsg) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[86] - if x != nil { + mi := &file_lightning_proto_msgTypes[85] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -8636,48 +8831,40 @@ func (x *FundingTransitionMsg) ProtoReflect() protoreflect.Message { // Deprecated: Use FundingTransitionMsg.ProtoReflect.Descriptor instead. func (*FundingTransitionMsg) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{86} + return file_lightning_proto_rawDescGZIP(), []int{85} } -func (x *FundingTransitionMsg) GetTrigger() isFundingTransitionMsg_Trigger { - if x != nil { - return x.Trigger +func (m *FundingTransitionMsg) GetTrigger() isFundingTransitionMsg_Trigger { + if m != nil { + return m.Trigger } return nil } func (x *FundingTransitionMsg) GetShimRegister() *FundingShim { - if x != nil { - if x, ok := x.Trigger.(*FundingTransitionMsg_ShimRegister); ok { - return x.ShimRegister - } + if x, ok := x.GetTrigger().(*FundingTransitionMsg_ShimRegister); ok { + return x.ShimRegister } return nil } func (x *FundingTransitionMsg) GetShimCancel() *FundingShimCancel { - if x != nil { - if x, ok := x.Trigger.(*FundingTransitionMsg_ShimCancel); ok { - return x.ShimCancel - } + if x, ok := x.GetTrigger().(*FundingTransitionMsg_ShimCancel); ok { + return x.ShimCancel } return nil } func (x *FundingTransitionMsg) GetPsbtVerify() *FundingPsbtVerify { - if x != nil { - if x, ok := x.Trigger.(*FundingTransitionMsg_PsbtVerify); ok { - return x.PsbtVerify - } + if x, ok := x.GetTrigger().(*FundingTransitionMsg_PsbtVerify); ok { + return x.PsbtVerify } return nil } func (x *FundingTransitionMsg) GetPsbtFinalize() *FundingPsbtFinalize { - if x != nil { - if x, ok := x.Trigger.(*FundingTransitionMsg_PsbtFinalize); ok { - return x.PsbtFinalize - } + if x, ok := x.GetTrigger().(*FundingTransitionMsg_PsbtFinalize); ok { + return x.PsbtFinalize } return nil } @@ -8722,16 +8909,18 @@ func (*FundingTransitionMsg_PsbtVerify) isFundingTransitionMsg_Trigger() {} func (*FundingTransitionMsg_PsbtFinalize) isFundingTransitionMsg_Trigger() {} type FundingStateStepResp struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *FundingStateStepResp) Reset() { *x = FundingStateStepResp{} - mi := &file_lightning_proto_msgTypes[87] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[86] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *FundingStateStepResp) String() string { @@ -8741,8 +8930,8 @@ func (x *FundingStateStepResp) String() string { func (*FundingStateStepResp) ProtoMessage() {} func (x *FundingStateStepResp) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[87] - if x != nil { + mi := &file_lightning_proto_msgTypes[86] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -8754,11 +8943,14 @@ func (x *FundingStateStepResp) ProtoReflect() protoreflect.Message { // Deprecated: Use FundingStateStepResp.ProtoReflect.Descriptor instead. func (*FundingStateStepResp) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{87} + return file_lightning_proto_rawDescGZIP(), []int{86} } type PendingHTLC struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The direction within the channel that the htlc was sent Incoming bool `protobuf:"varint,1,opt,name=incoming,proto3" json:"incoming,omitempty"` // The total value of the htlc @@ -8772,16 +8964,16 @@ type PendingHTLC struct { // mature. BlocksTilMaturity int32 `protobuf:"varint,5,opt,name=blocks_til_maturity,json=blocksTilMaturity,proto3" json:"blocks_til_maturity,omitempty"` // Indicates whether the htlc is in its first or second stage of recovery - Stage uint32 `protobuf:"varint,6,opt,name=stage,proto3" json:"stage,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Stage uint32 `protobuf:"varint,6,opt,name=stage,proto3" json:"stage,omitempty"` } func (x *PendingHTLC) Reset() { *x = PendingHTLC{} - mi := &file_lightning_proto_msgTypes[88] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[87] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PendingHTLC) String() string { @@ -8791,8 +8983,8 @@ func (x *PendingHTLC) String() string { func (*PendingHTLC) ProtoMessage() {} func (x *PendingHTLC) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[88] - if x != nil { + mi := &file_lightning_proto_msgTypes[87] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -8804,7 +8996,7 @@ func (x *PendingHTLC) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingHTLC.ProtoReflect.Descriptor instead. func (*PendingHTLC) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{88} + return file_lightning_proto_rawDescGZIP(), []int{87} } func (x *PendingHTLC) GetIncoming() bool { @@ -8850,19 +9042,22 @@ func (x *PendingHTLC) GetStage() uint32 { } type PendingChannelsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Indicates whether to include the raw transaction hex for // waiting_close_channels. - IncludeRawTx bool `protobuf:"varint,1,opt,name=include_raw_tx,json=includeRawTx,proto3" json:"include_raw_tx,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + IncludeRawTx bool `protobuf:"varint,1,opt,name=include_raw_tx,json=includeRawTx,proto3" json:"include_raw_tx,omitempty"` } func (x *PendingChannelsRequest) Reset() { *x = PendingChannelsRequest{} - mi := &file_lightning_proto_msgTypes[89] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[88] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PendingChannelsRequest) String() string { @@ -8872,8 +9067,8 @@ func (x *PendingChannelsRequest) String() string { func (*PendingChannelsRequest) ProtoMessage() {} func (x *PendingChannelsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[89] - if x != nil { + mi := &file_lightning_proto_msgTypes[88] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -8885,7 +9080,7 @@ func (x *PendingChannelsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingChannelsRequest.ProtoReflect.Descriptor instead. func (*PendingChannelsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{89} + return file_lightning_proto_rawDescGZIP(), []int{88} } func (x *PendingChannelsRequest) GetIncludeRawTx() bool { @@ -8896,7 +9091,10 @@ func (x *PendingChannelsRequest) GetIncludeRawTx() bool { } type PendingChannelsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The balance in satoshis encumbered in pending channels TotalLimboBalance int64 `protobuf:"varint,1,opt,name=total_limbo_balance,json=totalLimboBalance,proto3" json:"total_limbo_balance,omitempty"` // Channels pending opening @@ -8911,15 +9109,15 @@ type PendingChannelsResponse struct { PendingForceClosingChannels []*PendingChannelsResponse_ForceClosedChannel `protobuf:"bytes,4,rep,name=pending_force_closing_channels,json=pendingForceClosingChannels,proto3" json:"pending_force_closing_channels,omitempty"` // Channels waiting for closing tx to confirm WaitingCloseChannels []*PendingChannelsResponse_WaitingCloseChannel `protobuf:"bytes,5,rep,name=waiting_close_channels,json=waitingCloseChannels,proto3" json:"waiting_close_channels,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *PendingChannelsResponse) Reset() { *x = PendingChannelsResponse{} - mi := &file_lightning_proto_msgTypes[90] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[89] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PendingChannelsResponse) String() string { @@ -8929,8 +9127,8 @@ func (x *PendingChannelsResponse) String() string { func (*PendingChannelsResponse) ProtoMessage() {} func (x *PendingChannelsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[90] - if x != nil { + mi := &file_lightning_proto_msgTypes[89] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -8942,7 +9140,7 @@ func (x *PendingChannelsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingChannelsResponse.ProtoReflect.Descriptor instead. func (*PendingChannelsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{90} + return file_lightning_proto_rawDescGZIP(), []int{89} } func (x *PendingChannelsResponse) GetTotalLimboBalance() int64 { @@ -8982,16 +9180,18 @@ func (x *PendingChannelsResponse) GetWaitingCloseChannels() []*PendingChannelsRe } type ChannelEventSubscription struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *ChannelEventSubscription) Reset() { *x = ChannelEventSubscription{} - mi := &file_lightning_proto_msgTypes[91] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[90] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChannelEventSubscription) String() string { @@ -9001,8 +9201,8 @@ func (x *ChannelEventSubscription) String() string { func (*ChannelEventSubscription) ProtoMessage() {} func (x *ChannelEventSubscription) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[91] - if x != nil { + mi := &file_lightning_proto_msgTypes[90] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -9014,56 +9214,15 @@ func (x *ChannelEventSubscription) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelEventSubscription.ProtoReflect.Descriptor instead. func (*ChannelEventSubscription) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{91} -} - -type ChannelCommitUpdate struct { - state protoimpl.MessageState `protogen:"open.v1"` - Channel *Channel `protobuf:"bytes,1,opt,name=channel,proto3" json:"channel,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ChannelCommitUpdate) Reset() { - *x = ChannelCommitUpdate{} - mi := &file_lightning_proto_msgTypes[92] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ChannelCommitUpdate) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ChannelCommitUpdate) ProtoMessage() {} - -func (x *ChannelCommitUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[92] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ChannelCommitUpdate.ProtoReflect.Descriptor instead. -func (*ChannelCommitUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{92} -} - -func (x *ChannelCommitUpdate) GetChannel() *Channel { - if x != nil { - return x.Channel - } - return nil + return file_lightning_proto_rawDescGZIP(), []int{90} } type ChannelEventUpdate struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to Channel: + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Channel: // // *ChannelEventUpdate_OpenChannel // *ChannelEventUpdate_ClosedChannel @@ -9072,18 +9231,17 @@ type ChannelEventUpdate struct { // *ChannelEventUpdate_PendingOpenChannel // *ChannelEventUpdate_FullyResolvedChannel // *ChannelEventUpdate_ChannelFundingTimeout - // *ChannelEventUpdate_UpdatedChannel - Channel isChannelEventUpdate_Channel `protobuf_oneof:"channel"` - Type ChannelEventUpdate_UpdateType `protobuf:"varint,5,opt,name=type,proto3,enum=lnrpc.ChannelEventUpdate_UpdateType" json:"type,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Channel isChannelEventUpdate_Channel `protobuf_oneof:"channel"` + Type ChannelEventUpdate_UpdateType `protobuf:"varint,5,opt,name=type,proto3,enum=lnrpc.ChannelEventUpdate_UpdateType" json:"type,omitempty"` } func (x *ChannelEventUpdate) Reset() { *x = ChannelEventUpdate{} - mi := &file_lightning_proto_msgTypes[93] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[91] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChannelEventUpdate) String() string { @@ -9093,8 +9251,8 @@ func (x *ChannelEventUpdate) String() string { func (*ChannelEventUpdate) ProtoMessage() {} func (x *ChannelEventUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[93] - if x != nil { + mi := &file_lightning_proto_msgTypes[91] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -9106,84 +9264,61 @@ func (x *ChannelEventUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelEventUpdate.ProtoReflect.Descriptor instead. func (*ChannelEventUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{93} + return file_lightning_proto_rawDescGZIP(), []int{91} } -func (x *ChannelEventUpdate) GetChannel() isChannelEventUpdate_Channel { - if x != nil { - return x.Channel +func (m *ChannelEventUpdate) GetChannel() isChannelEventUpdate_Channel { + if m != nil { + return m.Channel } return nil } func (x *ChannelEventUpdate) GetOpenChannel() *Channel { - if x != nil { - if x, ok := x.Channel.(*ChannelEventUpdate_OpenChannel); ok { - return x.OpenChannel - } + if x, ok := x.GetChannel().(*ChannelEventUpdate_OpenChannel); ok { + return x.OpenChannel } return nil } func (x *ChannelEventUpdate) GetClosedChannel() *ChannelCloseSummary { - if x != nil { - if x, ok := x.Channel.(*ChannelEventUpdate_ClosedChannel); ok { - return x.ClosedChannel - } + if x, ok := x.GetChannel().(*ChannelEventUpdate_ClosedChannel); ok { + return x.ClosedChannel } return nil } func (x *ChannelEventUpdate) GetActiveChannel() *ChannelPoint { - if x != nil { - if x, ok := x.Channel.(*ChannelEventUpdate_ActiveChannel); ok { - return x.ActiveChannel - } + if x, ok := x.GetChannel().(*ChannelEventUpdate_ActiveChannel); ok { + return x.ActiveChannel } return nil } func (x *ChannelEventUpdate) GetInactiveChannel() *ChannelPoint { - if x != nil { - if x, ok := x.Channel.(*ChannelEventUpdate_InactiveChannel); ok { - return x.InactiveChannel - } + if x, ok := x.GetChannel().(*ChannelEventUpdate_InactiveChannel); ok { + return x.InactiveChannel } return nil } func (x *ChannelEventUpdate) GetPendingOpenChannel() *PendingUpdate { - if x != nil { - if x, ok := x.Channel.(*ChannelEventUpdate_PendingOpenChannel); ok { - return x.PendingOpenChannel - } + if x, ok := x.GetChannel().(*ChannelEventUpdate_PendingOpenChannel); ok { + return x.PendingOpenChannel } return nil } func (x *ChannelEventUpdate) GetFullyResolvedChannel() *ChannelPoint { - if x != nil { - if x, ok := x.Channel.(*ChannelEventUpdate_FullyResolvedChannel); ok { - return x.FullyResolvedChannel - } + if x, ok := x.GetChannel().(*ChannelEventUpdate_FullyResolvedChannel); ok { + return x.FullyResolvedChannel } return nil } func (x *ChannelEventUpdate) GetChannelFundingTimeout() *ChannelPoint { - if x != nil { - if x, ok := x.Channel.(*ChannelEventUpdate_ChannelFundingTimeout); ok { - return x.ChannelFundingTimeout - } - } - return nil -} - -func (x *ChannelEventUpdate) GetUpdatedChannel() *ChannelCommitUpdate { - if x != nil { - if x, ok := x.Channel.(*ChannelEventUpdate_UpdatedChannel); ok { - return x.UpdatedChannel - } + if x, ok := x.GetChannel().(*ChannelEventUpdate_ChannelFundingTimeout); ok { + return x.ChannelFundingTimeout } return nil } @@ -9227,10 +9362,6 @@ type ChannelEventUpdate_ChannelFundingTimeout struct { ChannelFundingTimeout *ChannelPoint `protobuf:"bytes,8,opt,name=channel_funding_timeout,json=channelFundingTimeout,proto3,oneof"` } -type ChannelEventUpdate_UpdatedChannel struct { - UpdatedChannel *ChannelCommitUpdate `protobuf:"bytes,9,opt,name=updated_channel,json=updatedChannel,proto3,oneof"` -} - func (*ChannelEventUpdate_OpenChannel) isChannelEventUpdate_Channel() {} func (*ChannelEventUpdate_ClosedChannel) isChannelEventUpdate_Channel() {} @@ -9245,23 +9376,24 @@ func (*ChannelEventUpdate_FullyResolvedChannel) isChannelEventUpdate_Channel() { func (*ChannelEventUpdate_ChannelFundingTimeout) isChannelEventUpdate_Channel() {} -func (*ChannelEventUpdate_UpdatedChannel) isChannelEventUpdate_Channel() {} - type WalletAccountBalance struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The confirmed balance of the account (with >= 1 confirmations). ConfirmedBalance int64 `protobuf:"varint,1,opt,name=confirmed_balance,json=confirmedBalance,proto3" json:"confirmed_balance,omitempty"` // The unconfirmed balance of the account (with 0 confirmations). UnconfirmedBalance int64 `protobuf:"varint,2,opt,name=unconfirmed_balance,json=unconfirmedBalance,proto3" json:"unconfirmed_balance,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *WalletAccountBalance) Reset() { *x = WalletAccountBalance{} - mi := &file_lightning_proto_msgTypes[94] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[92] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *WalletAccountBalance) String() string { @@ -9271,8 +9403,8 @@ func (x *WalletAccountBalance) String() string { func (*WalletAccountBalance) ProtoMessage() {} func (x *WalletAccountBalance) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[94] - if x != nil { + mi := &file_lightning_proto_msgTypes[92] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -9284,7 +9416,7 @@ func (x *WalletAccountBalance) ProtoReflect() protoreflect.Message { // Deprecated: Use WalletAccountBalance.ProtoReflect.Descriptor instead. func (*WalletAccountBalance) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{94} + return file_lightning_proto_rawDescGZIP(), []int{92} } func (x *WalletAccountBalance) GetConfirmedBalance() int64 { @@ -9302,23 +9434,26 @@ func (x *WalletAccountBalance) GetUnconfirmedBalance() int64 { } type WalletBalanceRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The wallet account the balance is shown for. // If this is not specified, the balance of the "default" account is shown. Account string `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"` // The minimum number of confirmations each one of your outputs used for the // funding transaction must satisfy. If this is not specified, the default // value of 1 is used. - MinConfs int32 `protobuf:"varint,2,opt,name=min_confs,json=minConfs,proto3" json:"min_confs,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + MinConfs int32 `protobuf:"varint,2,opt,name=min_confs,json=minConfs,proto3" json:"min_confs,omitempty"` } func (x *WalletBalanceRequest) Reset() { *x = WalletBalanceRequest{} - mi := &file_lightning_proto_msgTypes[95] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[93] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *WalletBalanceRequest) String() string { @@ -9328,8 +9463,8 @@ func (x *WalletBalanceRequest) String() string { func (*WalletBalanceRequest) ProtoMessage() {} func (x *WalletBalanceRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[95] - if x != nil { + mi := &file_lightning_proto_msgTypes[93] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -9341,7 +9476,7 @@ func (x *WalletBalanceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use WalletBalanceRequest.ProtoReflect.Descriptor instead. func (*WalletBalanceRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{95} + return file_lightning_proto_rawDescGZIP(), []int{93} } func (x *WalletBalanceRequest) GetAccount() string { @@ -9359,7 +9494,10 @@ func (x *WalletBalanceRequest) GetMinConfs() int32 { } type WalletBalanceResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The balance of the wallet TotalBalance int64 `protobuf:"varint,1,opt,name=total_balance,json=totalBalance,proto3" json:"total_balance,omitempty"` // The confirmed balance of a wallet(with >= 1 confirmations) @@ -9372,16 +9510,16 @@ type WalletBalanceResponse struct { // The amount of reserve required. ReservedBalanceAnchorChan int64 `protobuf:"varint,6,opt,name=reserved_balance_anchor_chan,json=reservedBalanceAnchorChan,proto3" json:"reserved_balance_anchor_chan,omitempty"` // A mapping of each wallet account's name to its balance. - AccountBalance map[string]*WalletAccountBalance `protobuf:"bytes,4,rep,name=account_balance,json=accountBalance,proto3" json:"account_balance,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + AccountBalance map[string]*WalletAccountBalance `protobuf:"bytes,4,rep,name=account_balance,json=accountBalance,proto3" json:"account_balance,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *WalletBalanceResponse) Reset() { *x = WalletBalanceResponse{} - mi := &file_lightning_proto_msgTypes[96] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[94] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *WalletBalanceResponse) String() string { @@ -9391,8 +9529,8 @@ func (x *WalletBalanceResponse) String() string { func (*WalletBalanceResponse) ProtoMessage() {} func (x *WalletBalanceResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[96] - if x != nil { + mi := &file_lightning_proto_msgTypes[94] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -9404,7 +9542,7 @@ func (x *WalletBalanceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use WalletBalanceResponse.ProtoReflect.Descriptor instead. func (*WalletBalanceResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{96} + return file_lightning_proto_rawDescGZIP(), []int{94} } func (x *WalletBalanceResponse) GetTotalBalance() int64 { @@ -9450,20 +9588,23 @@ func (x *WalletBalanceResponse) GetAccountBalance() map[string]*WalletAccountBal } type Amount struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Value denominated in satoshis. Sat uint64 `protobuf:"varint,1,opt,name=sat,proto3" json:"sat,omitempty"` // Value denominated in milli-satoshis. - Msat uint64 `protobuf:"varint,2,opt,name=msat,proto3" json:"msat,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Msat uint64 `protobuf:"varint,2,opt,name=msat,proto3" json:"msat,omitempty"` } func (x *Amount) Reset() { *x = Amount{} - mi := &file_lightning_proto_msgTypes[97] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[95] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *Amount) String() string { @@ -9473,8 +9614,8 @@ func (x *Amount) String() string { func (*Amount) ProtoMessage() {} func (x *Amount) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[97] - if x != nil { + mi := &file_lightning_proto_msgTypes[95] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -9486,7 +9627,7 @@ func (x *Amount) ProtoReflect() protoreflect.Message { // Deprecated: Use Amount.ProtoReflect.Descriptor instead. func (*Amount) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{97} + return file_lightning_proto_rawDescGZIP(), []int{95} } func (x *Amount) GetSat() uint64 { @@ -9504,16 +9645,18 @@ func (x *Amount) GetMsat() uint64 { } type ChannelBalanceRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *ChannelBalanceRequest) Reset() { *x = ChannelBalanceRequest{} - mi := &file_lightning_proto_msgTypes[98] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[96] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChannelBalanceRequest) String() string { @@ -9523,8 +9666,8 @@ func (x *ChannelBalanceRequest) String() string { func (*ChannelBalanceRequest) ProtoMessage() {} func (x *ChannelBalanceRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[98] - if x != nil { + mi := &file_lightning_proto_msgTypes[96] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -9536,11 +9679,14 @@ func (x *ChannelBalanceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelBalanceRequest.ProtoReflect.Descriptor instead. func (*ChannelBalanceRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{98} + return file_lightning_proto_rawDescGZIP(), []int{96} } type ChannelBalanceResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Deprecated. Sum of channels balances denominated in satoshis // // Deprecated: Marked as deprecated in lightning.proto. @@ -9564,15 +9710,15 @@ type ChannelBalanceResponse struct { // Custom channel data that might be populated if there are custom channels // present. CustomChannelData []byte `protobuf:"bytes,9,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ChannelBalanceResponse) Reset() { *x = ChannelBalanceResponse{} - mi := &file_lightning_proto_msgTypes[99] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[97] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChannelBalanceResponse) String() string { @@ -9582,8 +9728,8 @@ func (x *ChannelBalanceResponse) String() string { func (*ChannelBalanceResponse) ProtoMessage() {} func (x *ChannelBalanceResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[99] - if x != nil { + mi := &file_lightning_proto_msgTypes[97] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -9595,7 +9741,7 @@ func (x *ChannelBalanceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelBalanceResponse.ProtoReflect.Descriptor instead. func (*ChannelBalanceResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{99} + return file_lightning_proto_rawDescGZIP(), []int{97} } // Deprecated: Marked as deprecated in lightning.proto. @@ -9664,7 +9810,10 @@ func (x *ChannelBalanceResponse) GetCustomChannelData() []byte { } type QueryRoutesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The 33-byte hex-encoded public key for the payment destination PubKey string `protobuf:"bytes,1,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty"` // The amount to send expressed in satoshis. @@ -9715,7 +9864,12 @@ type QueryRoutesRequest struct { // does not support the specified records, an error will be returned. // Record types are required to be in the custom range >= 65536. When using // REST, the values must be encoded as base64. - DestCustomRecords map[uint64][]byte `protobuf:"bytes,13,rep,name=dest_custom_records,json=destCustomRecords,proto3" json:"dest_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + DestCustomRecords map[uint64][]byte `protobuf:"bytes,13,rep,name=dest_custom_records,json=destCustomRecords,proto3" json:"dest_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // Deprecated, use outgoing_chan_ids. The channel id of the channel that must + // be taken to the first hop. If zero, any channel may be used. + // + // Deprecated: Marked as deprecated in lightning.proto. + OutgoingChanId uint64 `protobuf:"varint,14,opt,name=outgoing_chan_id,json=outgoingChanId,proto3" json:"outgoing_chan_id,omitempty"` // The pubkey of the last hop of the route. If empty, any hop may be used. LastHopPubkey []byte `protobuf:"bytes,15,opt,name=last_hop_pubkey,json=lastHopPubkey,proto3" json:"last_hop_pubkey,omitempty"` // Optional route hints to reach the destination through private channels. @@ -9738,15 +9892,15 @@ type QueryRoutesRequest struct { // The channel ids of the channels allowed for the first hop. If empty, any // channel may be used. OutgoingChanIds []uint64 `protobuf:"varint,20,rep,packed,name=outgoing_chan_ids,json=outgoingChanIds,proto3" json:"outgoing_chan_ids,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *QueryRoutesRequest) Reset() { *x = QueryRoutesRequest{} - mi := &file_lightning_proto_msgTypes[100] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[98] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *QueryRoutesRequest) String() string { @@ -9756,8 +9910,8 @@ func (x *QueryRoutesRequest) String() string { func (*QueryRoutesRequest) ProtoMessage() {} func (x *QueryRoutesRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[100] - if x != nil { + mi := &file_lightning_proto_msgTypes[98] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -9769,7 +9923,7 @@ func (x *QueryRoutesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryRoutesRequest.ProtoReflect.Descriptor instead. func (*QueryRoutesRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{100} + return file_lightning_proto_rawDescGZIP(), []int{98} } func (x *QueryRoutesRequest) GetPubKey() string { @@ -9857,6 +10011,14 @@ func (x *QueryRoutesRequest) GetDestCustomRecords() map[uint64][]byte { return nil } +// Deprecated: Marked as deprecated in lightning.proto. +func (x *QueryRoutesRequest) GetOutgoingChanId() uint64 { + if x != nil { + return x.OutgoingChanId + } + return 0 +} + func (x *QueryRoutesRequest) GetLastHopPubkey() []byte { if x != nil { return x.LastHopPubkey @@ -9900,22 +10062,25 @@ func (x *QueryRoutesRequest) GetOutgoingChanIds() []uint64 { } type NodePair struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The sending node of the pair. When using REST, this field must be encoded as // base64. From []byte `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` // The receiving node of the pair. When using REST, this field must be encoded // as base64. - To []byte `protobuf:"bytes,2,opt,name=to,proto3" json:"to,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + To []byte `protobuf:"bytes,2,opt,name=to,proto3" json:"to,omitempty"` } func (x *NodePair) Reset() { *x = NodePair{} - mi := &file_lightning_proto_msgTypes[101] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[99] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *NodePair) String() string { @@ -9925,8 +10090,8 @@ func (x *NodePair) String() string { func (*NodePair) ProtoMessage() {} func (x *NodePair) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[101] - if x != nil { + mi := &file_lightning_proto_msgTypes[99] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -9938,7 +10103,7 @@ func (x *NodePair) ProtoReflect() protoreflect.Message { // Deprecated: Use NodePair.ProtoReflect.Descriptor instead. func (*NodePair) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{101} + return file_lightning_proto_rawDescGZIP(), []int{99} } func (x *NodePair) GetFrom() []byte { @@ -9956,7 +10121,10 @@ func (x *NodePair) GetTo() []byte { } type EdgeLocator struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The short channel id of this edge. ChannelId uint64 `protobuf:"varint,1,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` // The direction of this edge. If direction_reverse is false, the direction @@ -9964,15 +10132,15 @@ type EdgeLocator struct { // pub key to the endpoint with the larger pub key. If direction_reverse is // is true, the edge goes the other way. DirectionReverse bool `protobuf:"varint,2,opt,name=direction_reverse,json=directionReverse,proto3" json:"direction_reverse,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *EdgeLocator) Reset() { *x = EdgeLocator{} - mi := &file_lightning_proto_msgTypes[102] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[100] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *EdgeLocator) String() string { @@ -9982,8 +10150,8 @@ func (x *EdgeLocator) String() string { func (*EdgeLocator) ProtoMessage() {} func (x *EdgeLocator) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[102] - if x != nil { + mi := &file_lightning_proto_msgTypes[100] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -9995,7 +10163,7 @@ func (x *EdgeLocator) ProtoReflect() protoreflect.Message { // Deprecated: Use EdgeLocator.ProtoReflect.Descriptor instead. func (*EdgeLocator) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{102} + return file_lightning_proto_rawDescGZIP(), []int{100} } func (x *EdgeLocator) GetChannelId() uint64 { @@ -10013,22 +10181,25 @@ func (x *EdgeLocator) GetDirectionReverse() bool { } type QueryRoutesResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The route that results from the path finding operation. This is still a // repeated field to retain backwards compatibility. Routes []*Route `protobuf:"bytes,1,rep,name=routes,proto3" json:"routes,omitempty"` // The success probability of the returned route based on the current mission // control state. [EXPERIMENTAL] - SuccessProb float64 `protobuf:"fixed64,2,opt,name=success_prob,json=successProb,proto3" json:"success_prob,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + SuccessProb float64 `protobuf:"fixed64,2,opt,name=success_prob,json=successProb,proto3" json:"success_prob,omitempty"` } func (x *QueryRoutesResponse) Reset() { *x = QueryRoutesResponse{} - mi := &file_lightning_proto_msgTypes[103] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[101] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *QueryRoutesResponse) String() string { @@ -10038,8 +10209,8 @@ func (x *QueryRoutesResponse) String() string { func (*QueryRoutesResponse) ProtoMessage() {} func (x *QueryRoutesResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[103] - if x != nil { + mi := &file_lightning_proto_msgTypes[101] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -10051,7 +10222,7 @@ func (x *QueryRoutesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryRoutesResponse.ProtoReflect.Descriptor instead. func (*QueryRoutesResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{103} + return file_lightning_proto_rawDescGZIP(), []int{101} } func (x *QueryRoutesResponse) GetRoutes() []*Route { @@ -10069,7 +10240,10 @@ func (x *QueryRoutesResponse) GetSuccessProb() float64 { } type Hop struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The unique channel ID for the channel. The first 3 bytes are the block // height, the next 3 the index within the block, and the last 2 bytes are the // output index for the channel. @@ -10107,7 +10281,7 @@ type Hop struct { // An optional set of key-value TLV records. This is useful within the context // of the SendToRoute call as it allows callers to specify arbitrary K-V pairs // to drop off at each hop within the onion. - CustomRecords map[uint64][]byte `protobuf:"bytes,11,rep,name=custom_records,json=customRecords,proto3" json:"custom_records,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + CustomRecords map[uint64][]byte `protobuf:"bytes,11,rep,name=custom_records,json=customRecords,proto3" json:"custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // The payment metadata to send along with the payment to the payee. Metadata []byte `protobuf:"bytes,13,opt,name=metadata,proto3" json:"metadata,omitempty"` // Blinding point is an optional blinding point included for introduction @@ -10126,16 +10300,16 @@ type Hop struct { // This value is only set in the final hop payload of a blinded payment. This // value is analogous to the MPPRecord that is used for regular (non-blinded) // MPP payments. - TotalAmtMsat uint64 `protobuf:"varint,16,opt,name=total_amt_msat,json=totalAmtMsat,proto3" json:"total_amt_msat,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + TotalAmtMsat uint64 `protobuf:"varint,16,opt,name=total_amt_msat,json=totalAmtMsat,proto3" json:"total_amt_msat,omitempty"` } func (x *Hop) Reset() { *x = Hop{} - mi := &file_lightning_proto_msgTypes[104] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[102] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *Hop) String() string { @@ -10145,8 +10319,8 @@ func (x *Hop) String() string { func (*Hop) ProtoMessage() {} func (x *Hop) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[104] - if x != nil { + mi := &file_lightning_proto_msgTypes[102] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -10158,7 +10332,7 @@ func (x *Hop) ProtoReflect() protoreflect.Message { // Deprecated: Use Hop.ProtoReflect.Descriptor instead. func (*Hop) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{104} + return file_lightning_proto_rawDescGZIP(), []int{102} } func (x *Hop) GetChanId() uint64 { @@ -10278,7 +10452,10 @@ func (x *Hop) GetTotalAmtMsat() uint64 { } type MPPRecord struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // A unique, random identifier used to authenticate the sender as the intended // payer of a multi-path payment. The payment_addr must be the same for all // subpayments, and match the payment_addr provided in the receiver's invoice. @@ -10289,16 +10466,16 @@ type MPPRecord struct { // payment. The caller is responsible for ensuring subpayments to the same node // and payment_hash sum exactly to total_amt_msat. The same // total_amt_msat must be used on all subpayments. - TotalAmtMsat int64 `protobuf:"varint,10,opt,name=total_amt_msat,json=totalAmtMsat,proto3" json:"total_amt_msat,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + TotalAmtMsat int64 `protobuf:"varint,10,opt,name=total_amt_msat,json=totalAmtMsat,proto3" json:"total_amt_msat,omitempty"` } func (x *MPPRecord) Reset() { *x = MPPRecord{} - mi := &file_lightning_proto_msgTypes[105] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[103] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *MPPRecord) String() string { @@ -10308,8 +10485,8 @@ func (x *MPPRecord) String() string { func (*MPPRecord) ProtoMessage() {} func (x *MPPRecord) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[105] - if x != nil { + mi := &file_lightning_proto_msgTypes[103] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -10321,7 +10498,7 @@ func (x *MPPRecord) ProtoReflect() protoreflect.Message { // Deprecated: Use MPPRecord.ProtoReflect.Descriptor instead. func (*MPPRecord) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{105} + return file_lightning_proto_rawDescGZIP(), []int{103} } func (x *MPPRecord) GetPaymentAddr() []byte { @@ -10339,19 +10516,22 @@ func (x *MPPRecord) GetTotalAmtMsat() int64 { } type AMPRecord struct { - state protoimpl.MessageState `protogen:"open.v1"` - RootShare []byte `protobuf:"bytes,1,opt,name=root_share,json=rootShare,proto3" json:"root_share,omitempty"` - SetId []byte `protobuf:"bytes,2,opt,name=set_id,json=setId,proto3" json:"set_id,omitempty"` - ChildIndex uint32 `protobuf:"varint,3,opt,name=child_index,json=childIndex,proto3" json:"child_index,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RootShare []byte `protobuf:"bytes,1,opt,name=root_share,json=rootShare,proto3" json:"root_share,omitempty"` + SetId []byte `protobuf:"bytes,2,opt,name=set_id,json=setId,proto3" json:"set_id,omitempty"` + ChildIndex uint32 `protobuf:"varint,3,opt,name=child_index,json=childIndex,proto3" json:"child_index,omitempty"` } func (x *AMPRecord) Reset() { *x = AMPRecord{} - mi := &file_lightning_proto_msgTypes[106] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[104] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *AMPRecord) String() string { @@ -10361,8 +10541,8 @@ func (x *AMPRecord) String() string { func (*AMPRecord) ProtoMessage() {} func (x *AMPRecord) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[106] - if x != nil { + mi := &file_lightning_proto_msgTypes[104] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -10374,7 +10554,7 @@ func (x *AMPRecord) ProtoReflect() protoreflect.Message { // Deprecated: Use AMPRecord.ProtoReflect.Descriptor instead. func (*AMPRecord) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{106} + return file_lightning_proto_rawDescGZIP(), []int{104} } func (x *AMPRecord) GetRootShare() []byte { @@ -10404,7 +10584,10 @@ func (x *AMPRecord) GetChildIndex() uint32 { // route is only selected as valid if all the channels have sufficient capacity to // carry the initial payment amount after fees are accounted for. type Route struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The cumulative (final) time lock across the entire route. This is the CLTV // value that should be extended to the first hop in the route. All other hops // will decrement the time-lock as advertised, leaving enough time for all @@ -10438,15 +10621,15 @@ type Route struct { FirstHopAmountMsat int64 `protobuf:"varint,7,opt,name=first_hop_amount_msat,json=firstHopAmountMsat,proto3" json:"first_hop_amount_msat,omitempty"` // Custom channel data that might be populated in custom channels. CustomChannelData []byte `protobuf:"bytes,8,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *Route) Reset() { *x = Route{} - mi := &file_lightning_proto_msgTypes[107] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[105] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *Route) String() string { @@ -10456,8 +10639,8 @@ func (x *Route) String() string { func (*Route) ProtoMessage() {} func (x *Route) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[107] - if x != nil { + mi := &file_lightning_proto_msgTypes[105] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -10469,7 +10652,7 @@ func (x *Route) ProtoReflect() protoreflect.Message { // Deprecated: Use Route.ProtoReflect.Descriptor instead. func (*Route) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{107} + return file_lightning_proto_rawDescGZIP(), []int{105} } func (x *Route) GetTotalTimeLock() uint32 { @@ -10531,7 +10714,10 @@ func (x *Route) GetCustomChannelData() []byte { } type NodeInfoRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The 33-byte hex-encoded compressed public of the target node PubKey string `protobuf:"bytes,1,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty"` // If true, will include all known channels associated with the node. @@ -10539,15 +10725,15 @@ type NodeInfoRequest struct { // If true, will include announcements' signatures into ChannelEdge. // Depends on include_channels. IncludeAuthProof bool `protobuf:"varint,3,opt,name=include_auth_proof,json=includeAuthProof,proto3" json:"include_auth_proof,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *NodeInfoRequest) Reset() { *x = NodeInfoRequest{} - mi := &file_lightning_proto_msgTypes[108] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[106] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *NodeInfoRequest) String() string { @@ -10557,8 +10743,8 @@ func (x *NodeInfoRequest) String() string { func (*NodeInfoRequest) ProtoMessage() {} func (x *NodeInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[108] - if x != nil { + mi := &file_lightning_proto_msgTypes[106] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -10570,7 +10756,7 @@ func (x *NodeInfoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeInfoRequest.ProtoReflect.Descriptor instead. func (*NodeInfoRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{108} + return file_lightning_proto_rawDescGZIP(), []int{106} } func (x *NodeInfoRequest) GetPubKey() string { @@ -10595,7 +10781,10 @@ func (x *NodeInfoRequest) GetIncludeAuthProof() bool { } type NodeInfo struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // An individual vertex/node within the channel graph. A node is // connected to other nodes by one or more channel edges emanating from it. As // the graph is directed, a node will also have an incoming edge attached to @@ -10606,16 +10795,16 @@ type NodeInfo struct { // The sum of all channels capacity for the node, denominated in satoshis. TotalCapacity int64 `protobuf:"varint,3,opt,name=total_capacity,json=totalCapacity,proto3" json:"total_capacity,omitempty"` // A list of all public channels for the node. - Channels []*ChannelEdge `protobuf:"bytes,4,rep,name=channels,proto3" json:"channels,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Channels []*ChannelEdge `protobuf:"bytes,4,rep,name=channels,proto3" json:"channels,omitempty"` } func (x *NodeInfo) Reset() { *x = NodeInfo{} - mi := &file_lightning_proto_msgTypes[109] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[107] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *NodeInfo) String() string { @@ -10625,8 +10814,8 @@ func (x *NodeInfo) String() string { func (*NodeInfo) ProtoMessage() {} func (x *NodeInfo) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[109] - if x != nil { + mi := &file_lightning_proto_msgTypes[107] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -10638,7 +10827,7 @@ func (x *NodeInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeInfo.ProtoReflect.Descriptor instead. func (*NodeInfo) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{109} + return file_lightning_proto_rawDescGZIP(), []int{107} } func (x *NodeInfo) GetNode() *LightningNode { @@ -10674,24 +10863,27 @@ func (x *NodeInfo) GetChannels() []*ChannelEdge { // graph is directed, a node will also have an incoming edge attached to it for // each outgoing edge. type LightningNode struct { - state protoimpl.MessageState `protogen:"open.v1"` - LastUpdate uint32 `protobuf:"varint,1,opt,name=last_update,json=lastUpdate,proto3" json:"last_update,omitempty"` - PubKey string `protobuf:"bytes,2,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty"` - Alias string `protobuf:"bytes,3,opt,name=alias,proto3" json:"alias,omitempty"` - Addresses []*NodeAddress `protobuf:"bytes,4,rep,name=addresses,proto3" json:"addresses,omitempty"` - Color string `protobuf:"bytes,5,opt,name=color,proto3" json:"color,omitempty"` - Features map[uint32]*Feature `protobuf:"bytes,6,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - // Custom node announcement tlv records. - CustomRecords map[uint64][]byte `protobuf:"bytes,7,rep,name=custom_records,json=customRecords,proto3" json:"custom_records,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + LastUpdate uint32 `protobuf:"varint,1,opt,name=last_update,json=lastUpdate,proto3" json:"last_update,omitempty"` + PubKey string `protobuf:"bytes,2,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty"` + Alias string `protobuf:"bytes,3,opt,name=alias,proto3" json:"alias,omitempty"` + Addresses []*NodeAddress `protobuf:"bytes,4,rep,name=addresses,proto3" json:"addresses,omitempty"` + Color string `protobuf:"bytes,5,opt,name=color,proto3" json:"color,omitempty"` + Features map[uint32]*Feature `protobuf:"bytes,6,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // Custom node announcement tlv records. + CustomRecords map[uint64][]byte `protobuf:"bytes,7,rep,name=custom_records,json=customRecords,proto3" json:"custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *LightningNode) Reset() { *x = LightningNode{} - mi := &file_lightning_proto_msgTypes[110] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[108] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *LightningNode) String() string { @@ -10701,8 +10893,8 @@ func (x *LightningNode) String() string { func (*LightningNode) ProtoMessage() {} func (x *LightningNode) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[110] - if x != nil { + mi := &file_lightning_proto_msgTypes[108] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -10714,7 +10906,7 @@ func (x *LightningNode) ProtoReflect() protoreflect.Message { // Deprecated: Use LightningNode.ProtoReflect.Descriptor instead. func (*LightningNode) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{110} + return file_lightning_proto_rawDescGZIP(), []int{108} } func (x *LightningNode) GetLastUpdate() uint32 { @@ -10767,18 +10959,21 @@ func (x *LightningNode) GetCustomRecords() map[uint64][]byte { } type NodeAddress struct { - state protoimpl.MessageState `protogen:"open.v1"` - Network string `protobuf:"bytes,1,opt,name=network,proto3" json:"network,omitempty"` - Addr string `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Network string `protobuf:"bytes,1,opt,name=network,proto3" json:"network,omitempty"` + Addr string `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"` } func (x *NodeAddress) Reset() { *x = NodeAddress{} - mi := &file_lightning_proto_msgTypes[111] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[109] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *NodeAddress) String() string { @@ -10788,8 +10983,8 @@ func (x *NodeAddress) String() string { func (*NodeAddress) ProtoMessage() {} func (x *NodeAddress) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[111] - if x != nil { + mi := &file_lightning_proto_msgTypes[109] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -10801,7 +10996,7 @@ func (x *NodeAddress) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeAddress.ProtoReflect.Descriptor instead. func (*NodeAddress) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{111} + return file_lightning_proto_rawDescGZIP(), []int{109} } func (x *NodeAddress) GetNetwork() string { @@ -10819,28 +11014,31 @@ func (x *NodeAddress) GetAddr() string { } type RoutingPolicy struct { - state protoimpl.MessageState `protogen:"open.v1"` - TimeLockDelta uint32 `protobuf:"varint,1,opt,name=time_lock_delta,json=timeLockDelta,proto3" json:"time_lock_delta,omitempty"` - MinHtlc int64 `protobuf:"varint,2,opt,name=min_htlc,json=minHtlc,proto3" json:"min_htlc,omitempty"` - FeeBaseMsat int64 `protobuf:"varint,3,opt,name=fee_base_msat,json=feeBaseMsat,proto3" json:"fee_base_msat,omitempty"` - FeeRateMilliMsat int64 `protobuf:"varint,4,opt,name=fee_rate_milli_msat,json=feeRateMilliMsat,proto3" json:"fee_rate_milli_msat,omitempty"` - Disabled bool `protobuf:"varint,5,opt,name=disabled,proto3" json:"disabled,omitempty"` - MaxHtlcMsat uint64 `protobuf:"varint,6,opt,name=max_htlc_msat,json=maxHtlcMsat,proto3" json:"max_htlc_msat,omitempty"` - LastUpdate uint32 `protobuf:"varint,7,opt,name=last_update,json=lastUpdate,proto3" json:"last_update,omitempty"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TimeLockDelta uint32 `protobuf:"varint,1,opt,name=time_lock_delta,json=timeLockDelta,proto3" json:"time_lock_delta,omitempty"` + MinHtlc int64 `protobuf:"varint,2,opt,name=min_htlc,json=minHtlc,proto3" json:"min_htlc,omitempty"` + FeeBaseMsat int64 `protobuf:"varint,3,opt,name=fee_base_msat,json=feeBaseMsat,proto3" json:"fee_base_msat,omitempty"` + FeeRateMilliMsat int64 `protobuf:"varint,4,opt,name=fee_rate_milli_msat,json=feeRateMilliMsat,proto3" json:"fee_rate_milli_msat,omitempty"` + Disabled bool `protobuf:"varint,5,opt,name=disabled,proto3" json:"disabled,omitempty"` + MaxHtlcMsat uint64 `protobuf:"varint,6,opt,name=max_htlc_msat,json=maxHtlcMsat,proto3" json:"max_htlc_msat,omitempty"` + LastUpdate uint32 `protobuf:"varint,7,opt,name=last_update,json=lastUpdate,proto3" json:"last_update,omitempty"` // Custom channel update tlv records. These are customized fields that are // not defined by LND and cannot be extracted. - CustomRecords map[uint64][]byte `protobuf:"bytes,8,rep,name=custom_records,json=customRecords,proto3" json:"custom_records,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + CustomRecords map[uint64][]byte `protobuf:"bytes,8,rep,name=custom_records,json=customRecords,proto3" json:"custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` InboundFeeBaseMsat int32 `protobuf:"varint,9,opt,name=inbound_fee_base_msat,json=inboundFeeBaseMsat,proto3" json:"inbound_fee_base_msat,omitempty"` InboundFeeRateMilliMsat int32 `protobuf:"varint,10,opt,name=inbound_fee_rate_milli_msat,json=inboundFeeRateMilliMsat,proto3" json:"inbound_fee_rate_milli_msat,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *RoutingPolicy) Reset() { *x = RoutingPolicy{} - mi := &file_lightning_proto_msgTypes[112] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[110] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *RoutingPolicy) String() string { @@ -10850,8 +11048,8 @@ func (x *RoutingPolicy) String() string { func (*RoutingPolicy) ProtoMessage() {} func (x *RoutingPolicy) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[112] - if x != nil { + mi := &file_lightning_proto_msgTypes[110] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -10863,7 +11061,7 @@ func (x *RoutingPolicy) ProtoReflect() protoreflect.Message { // Deprecated: Use RoutingPolicy.ProtoReflect.Descriptor instead. func (*RoutingPolicy) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{112} + return file_lightning_proto_rawDescGZIP(), []int{110} } func (x *RoutingPolicy) GetTimeLockDelta() uint32 { @@ -10942,7 +11140,10 @@ func (x *RoutingPolicy) GetInboundFeeRateMilliMsat() int32 { // on the network are able to validate the authenticity and existence of a // channel. type ChannelAuthProof struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // node_sig1 are the raw bytes of the first node signature encoded // in DER format. NodeSig1 []byte `protobuf:"bytes,1,opt,name=node_sig1,json=nodeSig1,proto3" json:"node_sig1,omitempty"` @@ -10954,16 +11155,16 @@ type ChannelAuthProof struct { NodeSig2 []byte `protobuf:"bytes,3,opt,name=node_sig2,json=nodeSig2,proto3" json:"node_sig2,omitempty"` // bitcoin_sig2 are the raw bytes of the second bitcoin signature of the // MultiSigKey key of the channel encoded in DER format. - BitcoinSig2 []byte `protobuf:"bytes,4,opt,name=bitcoin_sig2,json=bitcoinSig2,proto3" json:"bitcoin_sig2,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + BitcoinSig2 []byte `protobuf:"bytes,4,opt,name=bitcoin_sig2,json=bitcoinSig2,proto3" json:"bitcoin_sig2,omitempty"` } func (x *ChannelAuthProof) Reset() { *x = ChannelAuthProof{} - mi := &file_lightning_proto_msgTypes[113] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[111] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChannelAuthProof) String() string { @@ -10973,8 +11174,8 @@ func (x *ChannelAuthProof) String() string { func (*ChannelAuthProof) ProtoMessage() {} func (x *ChannelAuthProof) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[113] - if x != nil { + mi := &file_lightning_proto_msgTypes[111] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -10986,7 +11187,7 @@ func (x *ChannelAuthProof) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelAuthProof.ProtoReflect.Descriptor instead. func (*ChannelAuthProof) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{113} + return file_lightning_proto_rawDescGZIP(), []int{111} } func (x *ChannelAuthProof) GetNodeSig1() []byte { @@ -11023,7 +11224,10 @@ func (x *ChannelAuthProof) GetBitcoinSig2() []byte { // stored. The other portions relevant to routing policy of a channel are stored // within a ChannelEdgePolicy for each direction of the channel. type ChannelEdge struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The unique channel ID for the channel. The first 3 bytes are the block // height, the next 3 the index within the block, and the last 2 bytes are the // output index for the channel. @@ -11037,22 +11241,22 @@ type ChannelEdge struct { Node1Policy *RoutingPolicy `protobuf:"bytes,7,opt,name=node1_policy,json=node1Policy,proto3" json:"node1_policy,omitempty"` Node2Policy *RoutingPolicy `protobuf:"bytes,8,opt,name=node2_policy,json=node2Policy,proto3" json:"node2_policy,omitempty"` // Custom channel announcement tlv records. - CustomRecords map[uint64][]byte `protobuf:"bytes,9,rep,name=custom_records,json=customRecords,proto3" json:"custom_records,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + CustomRecords map[uint64][]byte `protobuf:"bytes,9,rep,name=custom_records,json=customRecords,proto3" json:"custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // Authentication proof for this channel. This proof contains a set of // signatures binding four identities, which attests to the legitimacy of // the advertised channel. This only is available for advertised channels. // This field is not filled by default. Pass include_auth_proof flag to // DescribeGraph, GetNodeInfo or GetChanInfo to get this data. - AuthProof *ChannelAuthProof `protobuf:"bytes,10,opt,name=auth_proof,json=authProof,proto3" json:"auth_proof,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + AuthProof *ChannelAuthProof `protobuf:"bytes,10,opt,name=auth_proof,json=authProof,proto3" json:"auth_proof,omitempty"` } func (x *ChannelEdge) Reset() { *x = ChannelEdge{} - mi := &file_lightning_proto_msgTypes[114] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[112] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChannelEdge) String() string { @@ -11062,8 +11266,8 @@ func (x *ChannelEdge) String() string { func (*ChannelEdge) ProtoMessage() {} func (x *ChannelEdge) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[114] - if x != nil { + mi := &file_lightning_proto_msgTypes[112] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -11075,7 +11279,7 @@ func (x *ChannelEdge) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelEdge.ProtoReflect.Descriptor instead. func (*ChannelEdge) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{114} + return file_lightning_proto_rawDescGZIP(), []int{112} } func (x *ChannelEdge) GetChannelId() uint64 { @@ -11150,22 +11354,25 @@ func (x *ChannelEdge) GetAuthProof() *ChannelAuthProof { } type ChannelGraphRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Whether unannounced channels are included in the response or not. If set, // unannounced channels are included. Unannounced channels are both private // channels, and public channels that are not yet announced to the network. IncludeUnannounced bool `protobuf:"varint,1,opt,name=include_unannounced,json=includeUnannounced,proto3" json:"include_unannounced,omitempty"` // If true, will include announcements' signatures into ChannelEdge. IncludeAuthProof bool `protobuf:"varint,2,opt,name=include_auth_proof,json=includeAuthProof,proto3" json:"include_auth_proof,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ChannelGraphRequest) Reset() { *x = ChannelGraphRequest{} - mi := &file_lightning_proto_msgTypes[115] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[113] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChannelGraphRequest) String() string { @@ -11175,8 +11382,8 @@ func (x *ChannelGraphRequest) String() string { func (*ChannelGraphRequest) ProtoMessage() {} func (x *ChannelGraphRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[115] - if x != nil { + mi := &file_lightning_proto_msgTypes[113] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -11188,7 +11395,7 @@ func (x *ChannelGraphRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelGraphRequest.ProtoReflect.Descriptor instead. func (*ChannelGraphRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{115} + return file_lightning_proto_rawDescGZIP(), []int{113} } func (x *ChannelGraphRequest) GetIncludeUnannounced() bool { @@ -11207,20 +11414,23 @@ func (x *ChannelGraphRequest) GetIncludeAuthProof() bool { // Returns a new instance of the directed channel graph. type ChannelGraph struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The list of `LightningNode`s in this channel graph Nodes []*LightningNode `protobuf:"bytes,1,rep,name=nodes,proto3" json:"nodes,omitempty"` // The list of `ChannelEdge`s in this channel graph - Edges []*ChannelEdge `protobuf:"bytes,2,rep,name=edges,proto3" json:"edges,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Edges []*ChannelEdge `protobuf:"bytes,2,rep,name=edges,proto3" json:"edges,omitempty"` } func (x *ChannelGraph) Reset() { *x = ChannelGraph{} - mi := &file_lightning_proto_msgTypes[116] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[114] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChannelGraph) String() string { @@ -11230,8 +11440,8 @@ func (x *ChannelGraph) String() string { func (*ChannelGraph) ProtoMessage() {} func (x *ChannelGraph) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[116] - if x != nil { + mi := &file_lightning_proto_msgTypes[114] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -11243,7 +11453,7 @@ func (x *ChannelGraph) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelGraph.ProtoReflect.Descriptor instead. func (*ChannelGraph) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{116} + return file_lightning_proto_rawDescGZIP(), []int{114} } func (x *ChannelGraph) GetNodes() []*LightningNode { @@ -11261,18 +11471,21 @@ func (x *ChannelGraph) GetEdges() []*ChannelEdge { } type NodeMetricsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The requested node metrics. - Types []NodeMetricType `protobuf:"varint,1,rep,packed,name=types,proto3,enum=lnrpc.NodeMetricType" json:"types,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The requested node metrics. + Types []NodeMetricType `protobuf:"varint,1,rep,packed,name=types,proto3,enum=lnrpc.NodeMetricType" json:"types,omitempty"` } func (x *NodeMetricsRequest) Reset() { *x = NodeMetricsRequest{} - mi := &file_lightning_proto_msgTypes[117] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[115] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *NodeMetricsRequest) String() string { @@ -11282,8 +11495,8 @@ func (x *NodeMetricsRequest) String() string { func (*NodeMetricsRequest) ProtoMessage() {} func (x *NodeMetricsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[117] - if x != nil { + mi := &file_lightning_proto_msgTypes[115] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -11295,7 +11508,7 @@ func (x *NodeMetricsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeMetricsRequest.ProtoReflect.Descriptor instead. func (*NodeMetricsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{117} + return file_lightning_proto_rawDescGZIP(), []int{115} } func (x *NodeMetricsRequest) GetTypes() []NodeMetricType { @@ -11306,22 +11519,25 @@ func (x *NodeMetricsRequest) GetTypes() []NodeMetricType { } type NodeMetricsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Betweenness centrality is the sum of the ratio of shortest paths that pass // through the node for each pair of nodes in the graph (not counting paths // starting or ending at this node). // Map of node pubkey to betweenness centrality of the node. Normalized // values are in the [0,1] closed interval. - BetweennessCentrality map[string]*FloatMetric `protobuf:"bytes,1,rep,name=betweenness_centrality,json=betweennessCentrality,proto3" json:"betweenness_centrality,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + BetweennessCentrality map[string]*FloatMetric `protobuf:"bytes,1,rep,name=betweenness_centrality,json=betweennessCentrality,proto3" json:"betweenness_centrality,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *NodeMetricsResponse) Reset() { *x = NodeMetricsResponse{} - mi := &file_lightning_proto_msgTypes[118] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[116] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *NodeMetricsResponse) String() string { @@ -11331,8 +11547,8 @@ func (x *NodeMetricsResponse) String() string { func (*NodeMetricsResponse) ProtoMessage() {} func (x *NodeMetricsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[118] - if x != nil { + mi := &file_lightning_proto_msgTypes[116] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -11344,7 +11560,7 @@ func (x *NodeMetricsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeMetricsResponse.ProtoReflect.Descriptor instead. func (*NodeMetricsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{118} + return file_lightning_proto_rawDescGZIP(), []int{116} } func (x *NodeMetricsResponse) GetBetweennessCentrality() map[string]*FloatMetric { @@ -11355,20 +11571,23 @@ func (x *NodeMetricsResponse) GetBetweennessCentrality() map[string]*FloatMetric } type FloatMetric struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Arbitrary float value. Value float64 `protobuf:"fixed64,1,opt,name=value,proto3" json:"value,omitempty"` // The value normalized to [0,1] or [-1,1]. NormalizedValue float64 `protobuf:"fixed64,2,opt,name=normalized_value,json=normalizedValue,proto3" json:"normalized_value,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *FloatMetric) Reset() { *x = FloatMetric{} - mi := &file_lightning_proto_msgTypes[119] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[117] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *FloatMetric) String() string { @@ -11378,8 +11597,8 @@ func (x *FloatMetric) String() string { func (*FloatMetric) ProtoMessage() {} func (x *FloatMetric) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[119] - if x != nil { + mi := &file_lightning_proto_msgTypes[117] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -11391,7 +11610,7 @@ func (x *FloatMetric) ProtoReflect() protoreflect.Message { // Deprecated: Use FloatMetric.ProtoReflect.Descriptor instead. func (*FloatMetric) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{119} + return file_lightning_proto_rawDescGZIP(), []int{117} } func (x *FloatMetric) GetValue() float64 { @@ -11409,7 +11628,10 @@ func (x *FloatMetric) GetNormalizedValue() float64 { } type ChanInfoRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The unique channel ID for the channel. The first 3 bytes are the block // height, the next 3 the index within the block, and the last 2 bytes are the // output index for the channel. @@ -11419,15 +11641,15 @@ type ChanInfoRequest struct { ChanPoint string `protobuf:"bytes,2,opt,name=chan_point,json=chanPoint,proto3" json:"chan_point,omitempty"` // If true, will include announcements' signatures into ChannelEdge. IncludeAuthProof bool `protobuf:"varint,3,opt,name=include_auth_proof,json=includeAuthProof,proto3" json:"include_auth_proof,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ChanInfoRequest) Reset() { *x = ChanInfoRequest{} - mi := &file_lightning_proto_msgTypes[120] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[118] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChanInfoRequest) String() string { @@ -11437,8 +11659,8 @@ func (x *ChanInfoRequest) String() string { func (*ChanInfoRequest) ProtoMessage() {} func (x *ChanInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[120] - if x != nil { + mi := &file_lightning_proto_msgTypes[118] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -11450,7 +11672,7 @@ func (x *ChanInfoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChanInfoRequest.ProtoReflect.Descriptor instead. func (*ChanInfoRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{120} + return file_lightning_proto_rawDescGZIP(), []int{118} } func (x *ChanInfoRequest) GetChanId() uint64 { @@ -11475,16 +11697,18 @@ func (x *ChanInfoRequest) GetIncludeAuthProof() bool { } type NetworkInfoRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *NetworkInfoRequest) Reset() { *x = NetworkInfoRequest{} - mi := &file_lightning_proto_msgTypes[121] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[119] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *NetworkInfoRequest) String() string { @@ -11494,8 +11718,8 @@ func (x *NetworkInfoRequest) String() string { func (*NetworkInfoRequest) ProtoMessage() {} func (x *NetworkInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[121] - if x != nil { + mi := &file_lightning_proto_msgTypes[119] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -11507,32 +11731,35 @@ func (x *NetworkInfoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use NetworkInfoRequest.ProtoReflect.Descriptor instead. func (*NetworkInfoRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{121} + return file_lightning_proto_rawDescGZIP(), []int{119} } type NetworkInfo struct { - state protoimpl.MessageState `protogen:"open.v1"` - GraphDiameter uint32 `protobuf:"varint,1,opt,name=graph_diameter,json=graphDiameter,proto3" json:"graph_diameter,omitempty"` - AvgOutDegree float64 `protobuf:"fixed64,2,opt,name=avg_out_degree,json=avgOutDegree,proto3" json:"avg_out_degree,omitempty"` - MaxOutDegree uint32 `protobuf:"varint,3,opt,name=max_out_degree,json=maxOutDegree,proto3" json:"max_out_degree,omitempty"` - NumNodes uint32 `protobuf:"varint,4,opt,name=num_nodes,json=numNodes,proto3" json:"num_nodes,omitempty"` - NumChannels uint32 `protobuf:"varint,5,opt,name=num_channels,json=numChannels,proto3" json:"num_channels,omitempty"` - TotalNetworkCapacity int64 `protobuf:"varint,6,opt,name=total_network_capacity,json=totalNetworkCapacity,proto3" json:"total_network_capacity,omitempty"` - AvgChannelSize float64 `protobuf:"fixed64,7,opt,name=avg_channel_size,json=avgChannelSize,proto3" json:"avg_channel_size,omitempty"` - MinChannelSize int64 `protobuf:"varint,8,opt,name=min_channel_size,json=minChannelSize,proto3" json:"min_channel_size,omitempty"` - MaxChannelSize int64 `protobuf:"varint,9,opt,name=max_channel_size,json=maxChannelSize,proto3" json:"max_channel_size,omitempty"` - MedianChannelSizeSat int64 `protobuf:"varint,10,opt,name=median_channel_size_sat,json=medianChannelSizeSat,proto3" json:"median_channel_size_sat,omitempty"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + GraphDiameter uint32 `protobuf:"varint,1,opt,name=graph_diameter,json=graphDiameter,proto3" json:"graph_diameter,omitempty"` + AvgOutDegree float64 `protobuf:"fixed64,2,opt,name=avg_out_degree,json=avgOutDegree,proto3" json:"avg_out_degree,omitempty"` + MaxOutDegree uint32 `protobuf:"varint,3,opt,name=max_out_degree,json=maxOutDegree,proto3" json:"max_out_degree,omitempty"` + NumNodes uint32 `protobuf:"varint,4,opt,name=num_nodes,json=numNodes,proto3" json:"num_nodes,omitempty"` + NumChannels uint32 `protobuf:"varint,5,opt,name=num_channels,json=numChannels,proto3" json:"num_channels,omitempty"` + TotalNetworkCapacity int64 `protobuf:"varint,6,opt,name=total_network_capacity,json=totalNetworkCapacity,proto3" json:"total_network_capacity,omitempty"` + AvgChannelSize float64 `protobuf:"fixed64,7,opt,name=avg_channel_size,json=avgChannelSize,proto3" json:"avg_channel_size,omitempty"` + MinChannelSize int64 `protobuf:"varint,8,opt,name=min_channel_size,json=minChannelSize,proto3" json:"min_channel_size,omitempty"` + MaxChannelSize int64 `protobuf:"varint,9,opt,name=max_channel_size,json=maxChannelSize,proto3" json:"max_channel_size,omitempty"` + MedianChannelSizeSat int64 `protobuf:"varint,10,opt,name=median_channel_size_sat,json=medianChannelSizeSat,proto3" json:"median_channel_size_sat,omitempty"` // The number of edges marked as zombies. NumZombieChans uint64 `protobuf:"varint,11,opt,name=num_zombie_chans,json=numZombieChans,proto3" json:"num_zombie_chans,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *NetworkInfo) Reset() { *x = NetworkInfo{} - mi := &file_lightning_proto_msgTypes[122] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[120] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *NetworkInfo) String() string { @@ -11542,8 +11769,8 @@ func (x *NetworkInfo) String() string { func (*NetworkInfo) ProtoMessage() {} func (x *NetworkInfo) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[122] - if x != nil { + mi := &file_lightning_proto_msgTypes[120] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -11555,7 +11782,7 @@ func (x *NetworkInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use NetworkInfo.ProtoReflect.Descriptor instead. func (*NetworkInfo) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{122} + return file_lightning_proto_rawDescGZIP(), []int{120} } func (x *NetworkInfo) GetGraphDiameter() uint32 { @@ -11636,16 +11863,18 @@ func (x *NetworkInfo) GetNumZombieChans() uint64 { } type StopRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *StopRequest) Reset() { *x = StopRequest{} - mi := &file_lightning_proto_msgTypes[123] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[121] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *StopRequest) String() string { @@ -11655,8 +11884,8 @@ func (x *StopRequest) String() string { func (*StopRequest) ProtoMessage() {} func (x *StopRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[123] - if x != nil { + mi := &file_lightning_proto_msgTypes[121] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -11668,22 +11897,25 @@ func (x *StopRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StopRequest.ProtoReflect.Descriptor instead. func (*StopRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{123} + return file_lightning_proto_rawDescGZIP(), []int{121} } type StopResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The status of the stop operation. - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The status of the stop operation. + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` } func (x *StopResponse) Reset() { *x = StopResponse{} - mi := &file_lightning_proto_msgTypes[124] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[122] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *StopResponse) String() string { @@ -11693,8 +11925,8 @@ func (x *StopResponse) String() string { func (*StopResponse) ProtoMessage() {} func (x *StopResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[124] - if x != nil { + mi := &file_lightning_proto_msgTypes[122] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -11706,7 +11938,7 @@ func (x *StopResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StopResponse.ProtoReflect.Descriptor instead. func (*StopResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{124} + return file_lightning_proto_rawDescGZIP(), []int{122} } func (x *StopResponse) GetStatus() string { @@ -11717,16 +11949,18 @@ func (x *StopResponse) GetStatus() string { } type GraphTopologySubscription struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *GraphTopologySubscription) Reset() { *x = GraphTopologySubscription{} - mi := &file_lightning_proto_msgTypes[125] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[123] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GraphTopologySubscription) String() string { @@ -11736,8 +11970,8 @@ func (x *GraphTopologySubscription) String() string { func (*GraphTopologySubscription) ProtoMessage() {} func (x *GraphTopologySubscription) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[125] - if x != nil { + mi := &file_lightning_proto_msgTypes[123] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -11749,23 +11983,26 @@ func (x *GraphTopologySubscription) ProtoReflect() protoreflect.Message { // Deprecated: Use GraphTopologySubscription.ProtoReflect.Descriptor instead. func (*GraphTopologySubscription) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{125} + return file_lightning_proto_rawDescGZIP(), []int{123} } type GraphTopologyUpdate struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + NodeUpdates []*NodeUpdate `protobuf:"bytes,1,rep,name=node_updates,json=nodeUpdates,proto3" json:"node_updates,omitempty"` ChannelUpdates []*ChannelEdgeUpdate `protobuf:"bytes,2,rep,name=channel_updates,json=channelUpdates,proto3" json:"channel_updates,omitempty"` ClosedChans []*ClosedChannelUpdate `protobuf:"bytes,3,rep,name=closed_chans,json=closedChans,proto3" json:"closed_chans,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *GraphTopologyUpdate) Reset() { *x = GraphTopologyUpdate{} - mi := &file_lightning_proto_msgTypes[126] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[124] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GraphTopologyUpdate) String() string { @@ -11775,8 +12012,8 @@ func (x *GraphTopologyUpdate) String() string { func (*GraphTopologyUpdate) ProtoMessage() {} func (x *GraphTopologyUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[126] - if x != nil { + mi := &file_lightning_proto_msgTypes[124] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -11788,7 +12025,7 @@ func (x *GraphTopologyUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use GraphTopologyUpdate.ProtoReflect.Descriptor instead. func (*GraphTopologyUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{126} + return file_lightning_proto_rawDescGZIP(), []int{124} } func (x *GraphTopologyUpdate) GetNodeUpdates() []*NodeUpdate { @@ -11813,7 +12050,10 @@ func (x *GraphTopologyUpdate) GetClosedChans() []*ClosedChannelUpdate { } type NodeUpdate struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Deprecated, use node_addresses. // // Deprecated: Marked as deprecated in lightning.proto. @@ -11828,16 +12068,16 @@ type NodeUpdate struct { NodeAddresses []*NodeAddress `protobuf:"bytes,7,rep,name=node_addresses,json=nodeAddresses,proto3" json:"node_addresses,omitempty"` // Features that the node has advertised in the init message, node // announcements and invoices. - Features map[uint32]*Feature `protobuf:"bytes,6,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Features map[uint32]*Feature `protobuf:"bytes,6,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *NodeUpdate) Reset() { *x = NodeUpdate{} - mi := &file_lightning_proto_msgTypes[127] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[125] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *NodeUpdate) String() string { @@ -11847,8 +12087,8 @@ func (x *NodeUpdate) String() string { func (*NodeUpdate) ProtoMessage() {} func (x *NodeUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[127] - if x != nil { + mi := &file_lightning_proto_msgTypes[125] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -11860,7 +12100,7 @@ func (x *NodeUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeUpdate.ProtoReflect.Descriptor instead. func (*NodeUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{127} + return file_lightning_proto_rawDescGZIP(), []int{125} } // Deprecated: Marked as deprecated in lightning.proto. @@ -11915,7 +12155,10 @@ func (x *NodeUpdate) GetFeatures() map[uint32]*Feature { } type ChannelEdgeUpdate struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The unique channel ID for the channel. The first 3 bytes are the block // height, the next 3 the index within the block, and the last 2 bytes are the // output index for the channel. @@ -11925,15 +12168,15 @@ type ChannelEdgeUpdate struct { RoutingPolicy *RoutingPolicy `protobuf:"bytes,4,opt,name=routing_policy,json=routingPolicy,proto3" json:"routing_policy,omitempty"` AdvertisingNode string `protobuf:"bytes,5,opt,name=advertising_node,json=advertisingNode,proto3" json:"advertising_node,omitempty"` ConnectingNode string `protobuf:"bytes,6,opt,name=connecting_node,json=connectingNode,proto3" json:"connecting_node,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ChannelEdgeUpdate) Reset() { *x = ChannelEdgeUpdate{} - mi := &file_lightning_proto_msgTypes[128] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[126] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChannelEdgeUpdate) String() string { @@ -11943,8 +12186,8 @@ func (x *ChannelEdgeUpdate) String() string { func (*ChannelEdgeUpdate) ProtoMessage() {} func (x *ChannelEdgeUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[128] - if x != nil { + mi := &file_lightning_proto_msgTypes[126] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -11956,7 +12199,7 @@ func (x *ChannelEdgeUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelEdgeUpdate.ProtoReflect.Descriptor instead. func (*ChannelEdgeUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{128} + return file_lightning_proto_rawDescGZIP(), []int{126} } func (x *ChannelEdgeUpdate) GetChanId() uint64 { @@ -12002,23 +12245,26 @@ func (x *ChannelEdgeUpdate) GetConnectingNode() string { } type ClosedChannelUpdate struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The unique channel ID for the channel. The first 3 bytes are the block // height, the next 3 the index within the block, and the last 2 bytes are the // output index for the channel. - ChanId uint64 `protobuf:"varint,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"` - Capacity int64 `protobuf:"varint,2,opt,name=capacity,proto3" json:"capacity,omitempty"` - ClosedHeight uint32 `protobuf:"varint,3,opt,name=closed_height,json=closedHeight,proto3" json:"closed_height,omitempty"` - ChanPoint *ChannelPoint `protobuf:"bytes,4,opt,name=chan_point,json=chanPoint,proto3" json:"chan_point,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + ChanId uint64 `protobuf:"varint,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"` + Capacity int64 `protobuf:"varint,2,opt,name=capacity,proto3" json:"capacity,omitempty"` + ClosedHeight uint32 `protobuf:"varint,3,opt,name=closed_height,json=closedHeight,proto3" json:"closed_height,omitempty"` + ChanPoint *ChannelPoint `protobuf:"bytes,4,opt,name=chan_point,json=chanPoint,proto3" json:"chan_point,omitempty"` } func (x *ClosedChannelUpdate) Reset() { *x = ClosedChannelUpdate{} - mi := &file_lightning_proto_msgTypes[129] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[127] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ClosedChannelUpdate) String() string { @@ -12028,8 +12274,8 @@ func (x *ClosedChannelUpdate) String() string { func (*ClosedChannelUpdate) ProtoMessage() {} func (x *ClosedChannelUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[129] - if x != nil { + mi := &file_lightning_proto_msgTypes[127] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -12041,7 +12287,7 @@ func (x *ClosedChannelUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use ClosedChannelUpdate.ProtoReflect.Descriptor instead. func (*ClosedChannelUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{129} + return file_lightning_proto_rawDescGZIP(), []int{127} } func (x *ClosedChannelUpdate) GetChanId() uint64 { @@ -12073,7 +12319,10 @@ func (x *ClosedChannelUpdate) GetChanPoint() *ChannelPoint { } type HopHint struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The public key of the node at the start of the channel. NodeId string `protobuf:"bytes,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` // The unique identifier of the channel. @@ -12085,15 +12334,15 @@ type HopHint struct { FeeProportionalMillionths uint32 `protobuf:"varint,4,opt,name=fee_proportional_millionths,json=feeProportionalMillionths,proto3" json:"fee_proportional_millionths,omitempty"` // The time-lock delta of the channel. CltvExpiryDelta uint32 `protobuf:"varint,5,opt,name=cltv_expiry_delta,json=cltvExpiryDelta,proto3" json:"cltv_expiry_delta,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *HopHint) Reset() { *x = HopHint{} - mi := &file_lightning_proto_msgTypes[130] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[128] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *HopHint) String() string { @@ -12103,8 +12352,8 @@ func (x *HopHint) String() string { func (*HopHint) ProtoMessage() {} func (x *HopHint) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[130] - if x != nil { + mi := &file_lightning_proto_msgTypes[128] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -12116,7 +12365,7 @@ func (x *HopHint) ProtoReflect() protoreflect.Message { // Deprecated: Use HopHint.ProtoReflect.Descriptor instead. func (*HopHint) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{130} + return file_lightning_proto_rawDescGZIP(), []int{128} } func (x *HopHint) GetNodeId() string { @@ -12155,17 +12404,20 @@ func (x *HopHint) GetCltvExpiryDelta() uint32 { } type SetID struct { - state protoimpl.MessageState `protogen:"open.v1"` - SetId []byte `protobuf:"bytes,1,opt,name=set_id,json=setId,proto3" json:"set_id,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SetId []byte `protobuf:"bytes,1,opt,name=set_id,json=setId,proto3" json:"set_id,omitempty"` } func (x *SetID) Reset() { *x = SetID{} - mi := &file_lightning_proto_msgTypes[131] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[129] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SetID) String() string { @@ -12175,8 +12427,8 @@ func (x *SetID) String() string { func (*SetID) ProtoMessage() {} func (x *SetID) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[131] - if x != nil { + mi := &file_lightning_proto_msgTypes[129] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -12188,7 +12440,7 @@ func (x *SetID) ProtoReflect() protoreflect.Message { // Deprecated: Use SetID.ProtoReflect.Descriptor instead. func (*SetID) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{131} + return file_lightning_proto_rawDescGZIP(), []int{129} } func (x *SetID) GetSetId() []byte { @@ -12199,19 +12451,22 @@ func (x *SetID) GetSetId() []byte { } type RouteHint struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // A list of hop hints that when chained together can assist in reaching a // specific destination. - HopHints []*HopHint `protobuf:"bytes,1,rep,name=hop_hints,json=hopHints,proto3" json:"hop_hints,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + HopHints []*HopHint `protobuf:"bytes,1,rep,name=hop_hints,json=hopHints,proto3" json:"hop_hints,omitempty"` } func (x *RouteHint) Reset() { *x = RouteHint{} - mi := &file_lightning_proto_msgTypes[132] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[130] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *RouteHint) String() string { @@ -12221,8 +12476,8 @@ func (x *RouteHint) String() string { func (*RouteHint) ProtoMessage() {} func (x *RouteHint) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[132] - if x != nil { + mi := &file_lightning_proto_msgTypes[130] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -12234,7 +12489,7 @@ func (x *RouteHint) ProtoReflect() protoreflect.Message { // Deprecated: Use RouteHint.ProtoReflect.Descriptor instead. func (*RouteHint) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{132} + return file_lightning_proto_rawDescGZIP(), []int{130} } func (x *RouteHint) GetHopHints() []*HopHint { @@ -12245,7 +12500,10 @@ func (x *RouteHint) GetHopHints() []*HopHint { } type BlindedPaymentPath struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The blinded path to send the payment to. BlindedPath *BlindedPath `protobuf:"bytes,1,opt,name=blinded_path,json=blindedPath,proto3" json:"blinded_path,omitempty"` // The base fee for the blinded path provided, expressed in msat. @@ -12263,16 +12521,16 @@ type BlindedPaymentPath struct { // in msat. HtlcMaxMsat uint64 `protobuf:"varint,6,opt,name=htlc_max_msat,json=htlcMaxMsat,proto3" json:"htlc_max_msat,omitempty"` // The feature bits for the route. - Features []FeatureBit `protobuf:"varint,7,rep,packed,name=features,proto3,enum=lnrpc.FeatureBit" json:"features,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Features []FeatureBit `protobuf:"varint,7,rep,packed,name=features,proto3,enum=lnrpc.FeatureBit" json:"features,omitempty"` } func (x *BlindedPaymentPath) Reset() { *x = BlindedPaymentPath{} - mi := &file_lightning_proto_msgTypes[133] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[131] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *BlindedPaymentPath) String() string { @@ -12282,8 +12540,8 @@ func (x *BlindedPaymentPath) String() string { func (*BlindedPaymentPath) ProtoMessage() {} func (x *BlindedPaymentPath) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[133] - if x != nil { + mi := &file_lightning_proto_msgTypes[131] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -12295,7 +12553,7 @@ func (x *BlindedPaymentPath) ProtoReflect() protoreflect.Message { // Deprecated: Use BlindedPaymentPath.ProtoReflect.Descriptor instead. func (*BlindedPaymentPath) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{133} + return file_lightning_proto_rawDescGZIP(), []int{131} } func (x *BlindedPaymentPath) GetBlindedPath() *BlindedPath { @@ -12348,7 +12606,10 @@ func (x *BlindedPaymentPath) GetFeatures() []FeatureBit { } type BlindedPath struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The unblinded pubkey of the introduction node for the route. IntroductionNode []byte `protobuf:"bytes,1,opt,name=introduction_node,json=introductionNode,proto3" json:"introduction_node,omitempty"` // The ephemeral pubkey used by nodes in the blinded route. @@ -12356,16 +12617,16 @@ type BlindedPath struct { // A set of blinded node keys and data blobs for the blinded portion of the // route. Note that the first hop is expected to be the introduction node, // so the route is always expected to have at least one hop. - BlindedHops []*BlindedHop `protobuf:"bytes,3,rep,name=blinded_hops,json=blindedHops,proto3" json:"blinded_hops,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + BlindedHops []*BlindedHop `protobuf:"bytes,3,rep,name=blinded_hops,json=blindedHops,proto3" json:"blinded_hops,omitempty"` } func (x *BlindedPath) Reset() { *x = BlindedPath{} - mi := &file_lightning_proto_msgTypes[134] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[132] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *BlindedPath) String() string { @@ -12375,8 +12636,8 @@ func (x *BlindedPath) String() string { func (*BlindedPath) ProtoMessage() {} func (x *BlindedPath) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[134] - if x != nil { + mi := &file_lightning_proto_msgTypes[132] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -12388,7 +12649,7 @@ func (x *BlindedPath) ProtoReflect() protoreflect.Message { // Deprecated: Use BlindedPath.ProtoReflect.Descriptor instead. func (*BlindedPath) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{134} + return file_lightning_proto_rawDescGZIP(), []int{132} } func (x *BlindedPath) GetIntroductionNode() []byte { @@ -12413,20 +12674,23 @@ func (x *BlindedPath) GetBlindedHops() []*BlindedHop { } type BlindedHop struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The blinded public key of the node. BlindedNode []byte `protobuf:"bytes,1,opt,name=blinded_node,json=blindedNode,proto3" json:"blinded_node,omitempty"` // An encrypted blob of data provided to the blinded node. EncryptedData []byte `protobuf:"bytes,2,opt,name=encrypted_data,json=encryptedData,proto3" json:"encrypted_data,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *BlindedHop) Reset() { *x = BlindedHop{} - mi := &file_lightning_proto_msgTypes[135] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[133] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *BlindedHop) String() string { @@ -12436,8 +12700,8 @@ func (x *BlindedHop) String() string { func (*BlindedHop) ProtoMessage() {} func (x *BlindedHop) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[135] - if x != nil { + mi := &file_lightning_proto_msgTypes[133] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -12449,7 +12713,7 @@ func (x *BlindedHop) ProtoReflect() protoreflect.Message { // Deprecated: Use BlindedHop.ProtoReflect.Descriptor instead. func (*BlindedHop) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{135} + return file_lightning_proto_rawDescGZIP(), []int{133} } func (x *BlindedHop) GetBlindedNode() []byte { @@ -12467,7 +12731,10 @@ func (x *BlindedHop) GetEncryptedData() []byte { } type AMPInvoiceState struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The state the HTLCs associated with this setID are in. State InvoiceHTLCState `protobuf:"varint,1,opt,name=state,proto3,enum=lnrpc.InvoiceHTLCState" json:"state,omitempty"` // The settle index of this HTLC set, if the invoice state is settled. @@ -12475,16 +12742,16 @@ type AMPInvoiceState struct { // The time this HTLC set was settled expressed in unix epoch. SettleTime int64 `protobuf:"varint,3,opt,name=settle_time,json=settleTime,proto3" json:"settle_time,omitempty"` // The total amount paid for the sub-invoice expressed in milli satoshis. - AmtPaidMsat int64 `protobuf:"varint,5,opt,name=amt_paid_msat,json=amtPaidMsat,proto3" json:"amt_paid_msat,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + AmtPaidMsat int64 `protobuf:"varint,5,opt,name=amt_paid_msat,json=amtPaidMsat,proto3" json:"amt_paid_msat,omitempty"` } func (x *AMPInvoiceState) Reset() { *x = AMPInvoiceState{} - mi := &file_lightning_proto_msgTypes[136] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[134] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *AMPInvoiceState) String() string { @@ -12494,8 +12761,8 @@ func (x *AMPInvoiceState) String() string { func (*AMPInvoiceState) ProtoMessage() {} func (x *AMPInvoiceState) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[136] - if x != nil { + mi := &file_lightning_proto_msgTypes[134] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -12507,7 +12774,7 @@ func (x *AMPInvoiceState) ProtoReflect() protoreflect.Message { // Deprecated: Use AMPInvoiceState.ProtoReflect.Descriptor instead. func (*AMPInvoiceState) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{136} + return file_lightning_proto_rawDescGZIP(), []int{134} } func (x *AMPInvoiceState) GetState() InvoiceHTLCState { @@ -12539,7 +12806,10 @@ func (x *AMPInvoiceState) GetAmtPaidMsat() int64 { } type Invoice struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // An optional memo to attach along with the invoice. Used for record keeping // purposes for the invoice's creator, and will also be set in the description // field of the encoded payment request if the description_hash field is not @@ -12638,7 +12908,7 @@ type Invoice struct { Htlcs []*InvoiceHTLC `protobuf:"bytes,22,rep,name=htlcs,proto3" json:"htlcs,omitempty"` // List of features advertised on the invoice. // Note: Output only, don't specify for creating an invoice. - Features map[uint32]*Feature `protobuf:"bytes,24,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Features map[uint32]*Feature `protobuf:"bytes,24,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // Indicates if this invoice was a spontaneous payment that arrived via keysend // [EXPERIMENTAL]. // Note: Output only, don't specify for creating an invoice. @@ -12658,7 +12928,7 @@ type Invoice struct { // used along side LookupInvoice to obtain the HTLC information related to a // given sub-invoice. // Note: Output only, don't specify for creating an invoice. - AmpInvoiceState map[string]*AMPInvoiceState `protobuf:"bytes,28,rep,name=amp_invoice_state,json=ampInvoiceState,proto3" json:"amp_invoice_state,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + AmpInvoiceState map[string]*AMPInvoiceState `protobuf:"bytes,28,rep,name=amp_invoice_state,json=ampInvoiceState,proto3" json:"amp_invoice_state,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // Signals that the invoice should include blinded paths to hide the true // identity of the recipient. IsBlinded bool `protobuf:"varint,29,opt,name=is_blinded,json=isBlinded,proto3" json:"is_blinded,omitempty"` @@ -12666,15 +12936,15 @@ type Invoice struct { // can be used to override the defaults config values provided in by the // global config. This field is only used if is_blinded is true. BlindedPathConfig *BlindedPathConfig `protobuf:"bytes,30,opt,name=blinded_path_config,json=blindedPathConfig,proto3" json:"blinded_path_config,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *Invoice) Reset() { *x = Invoice{} - mi := &file_lightning_proto_msgTypes[137] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[135] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *Invoice) String() string { @@ -12684,8 +12954,8 @@ func (x *Invoice) String() string { func (*Invoice) ProtoMessage() {} func (x *Invoice) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[137] - if x != nil { + mi := &file_lightning_proto_msgTypes[135] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -12697,7 +12967,7 @@ func (x *Invoice) ProtoReflect() protoreflect.Message { // Deprecated: Use Invoice.ProtoReflect.Descriptor instead. func (*Invoice) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{137} + return file_lightning_proto_rawDescGZIP(), []int{135} } func (x *Invoice) GetMemo() string { @@ -12906,7 +13176,10 @@ func (x *Invoice) GetBlindedPathConfig() *BlindedPathConfig { } type BlindedPathConfig struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The minimum number of real hops to include in a blinded path. This doesn't // include our node, so if the minimum is 1, then the path will contain at // minimum our node along with an introduction node hop. If it is zero then @@ -12925,15 +13198,15 @@ type BlindedPathConfig struct { // The chained channels list specified via channel id (separated by commas), // starting from a channel owned by the receiver node. IncomingChannelList []uint64 `protobuf:"varint,5,rep,packed,name=incoming_channel_list,json=incomingChannelList,proto3" json:"incoming_channel_list,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *BlindedPathConfig) Reset() { *x = BlindedPathConfig{} - mi := &file_lightning_proto_msgTypes[138] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[136] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *BlindedPathConfig) String() string { @@ -12943,8 +13216,8 @@ func (x *BlindedPathConfig) String() string { func (*BlindedPathConfig) ProtoMessage() {} func (x *BlindedPathConfig) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[138] - if x != nil { + mi := &file_lightning_proto_msgTypes[136] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -12956,7 +13229,7 @@ func (x *BlindedPathConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use BlindedPathConfig.ProtoReflect.Descriptor instead. func (*BlindedPathConfig) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{138} + return file_lightning_proto_rawDescGZIP(), []int{136} } func (x *BlindedPathConfig) GetMinNumRealHops() uint32 { @@ -12996,7 +13269,10 @@ func (x *BlindedPathConfig) GetIncomingChannelList() []uint64 { // Details of an HTLC that paid to an invoice type InvoiceHTLC struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Short channel id over which the htlc was received. ChanId uint64 `protobuf:"varint,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"` // Index identifying the htlc on the channel. @@ -13014,22 +13290,22 @@ type InvoiceHTLC struct { // Current state the htlc is in. State InvoiceHTLCState `protobuf:"varint,8,opt,name=state,proto3,enum=lnrpc.InvoiceHTLCState" json:"state,omitempty"` // Custom tlv records. - CustomRecords map[uint64][]byte `protobuf:"bytes,9,rep,name=custom_records,json=customRecords,proto3" json:"custom_records,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + CustomRecords map[uint64][]byte `protobuf:"bytes,9,rep,name=custom_records,json=customRecords,proto3" json:"custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // The total amount of the mpp payment in msat. MppTotalAmtMsat uint64 `protobuf:"varint,10,opt,name=mpp_total_amt_msat,json=mppTotalAmtMsat,proto3" json:"mpp_total_amt_msat,omitempty"` // Details relevant to AMP HTLCs, only populated if this is an AMP HTLC. Amp *AMP `protobuf:"bytes,11,opt,name=amp,proto3" json:"amp,omitempty"` // Custom channel data that might be populated in custom channels. CustomChannelData []byte `protobuf:"bytes,12,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *InvoiceHTLC) Reset() { *x = InvoiceHTLC{} - mi := &file_lightning_proto_msgTypes[139] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[137] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *InvoiceHTLC) String() string { @@ -13039,8 +13315,8 @@ func (x *InvoiceHTLC) String() string { func (*InvoiceHTLC) ProtoMessage() {} func (x *InvoiceHTLC) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[139] - if x != nil { + mi := &file_lightning_proto_msgTypes[137] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -13052,7 +13328,7 @@ func (x *InvoiceHTLC) ProtoReflect() protoreflect.Message { // Deprecated: Use InvoiceHTLC.ProtoReflect.Descriptor instead. func (*InvoiceHTLC) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{139} + return file_lightning_proto_rawDescGZIP(), []int{137} } func (x *InvoiceHTLC) GetChanId() uint64 { @@ -13141,7 +13417,10 @@ func (x *InvoiceHTLC) GetCustomChannelData() []byte { // Details specific to AMP HTLCs. type AMP struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // An n-of-n secret share of the root seed from which child payment hashes // and preimages are derived. RootShare []byte `protobuf:"bytes,1,opt,name=root_share,json=rootShare,proto3" json:"root_share,omitempty"` @@ -13155,16 +13434,16 @@ type AMP struct { // The preimage used to settle this AMP htlc. This field will only be // populated if the invoice is in InvoiceState_ACCEPTED or // InvoiceState_SETTLED. - Preimage []byte `protobuf:"bytes,5,opt,name=preimage,proto3" json:"preimage,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Preimage []byte `protobuf:"bytes,5,opt,name=preimage,proto3" json:"preimage,omitempty"` } func (x *AMP) Reset() { *x = AMP{} - mi := &file_lightning_proto_msgTypes[140] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[138] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *AMP) String() string { @@ -13174,8 +13453,8 @@ func (x *AMP) String() string { func (*AMP) ProtoMessage() {} func (x *AMP) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[140] - if x != nil { + mi := &file_lightning_proto_msgTypes[138] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -13187,7 +13466,7 @@ func (x *AMP) ProtoReflect() protoreflect.Message { // Deprecated: Use AMP.ProtoReflect.Descriptor instead. func (*AMP) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{140} + return file_lightning_proto_rawDescGZIP(), []int{138} } func (x *AMP) GetRootShare() []byte { @@ -13226,8 +13505,11 @@ func (x *AMP) GetPreimage() []byte { } type AddInvoiceResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - RHash []byte `protobuf:"bytes,1,opt,name=r_hash,json=rHash,proto3" json:"r_hash,omitempty"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RHash []byte `protobuf:"bytes,1,opt,name=r_hash,json=rHash,proto3" json:"r_hash,omitempty"` // A bare-bones invoice for a payment within the Lightning Network. With the // details of the invoice, the sender has all the data necessary to send a // payment to the recipient. @@ -13240,16 +13522,16 @@ type AddInvoiceResponse struct { // The payment address of the generated invoice. This is also called // payment secret in specifications (e.g. BOLT 11). This value should be used // in all payments for this invoice as we require it for end to end security. - PaymentAddr []byte `protobuf:"bytes,17,opt,name=payment_addr,json=paymentAddr,proto3" json:"payment_addr,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + PaymentAddr []byte `protobuf:"bytes,17,opt,name=payment_addr,json=paymentAddr,proto3" json:"payment_addr,omitempty"` } func (x *AddInvoiceResponse) Reset() { *x = AddInvoiceResponse{} - mi := &file_lightning_proto_msgTypes[141] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[139] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *AddInvoiceResponse) String() string { @@ -13259,8 +13541,8 @@ func (x *AddInvoiceResponse) String() string { func (*AddInvoiceResponse) ProtoMessage() {} func (x *AddInvoiceResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[141] - if x != nil { + mi := &file_lightning_proto_msgTypes[139] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -13272,7 +13554,7 @@ func (x *AddInvoiceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AddInvoiceResponse.ProtoReflect.Descriptor instead. func (*AddInvoiceResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{141} + return file_lightning_proto_rawDescGZIP(), []int{139} } func (x *AddInvoiceResponse) GetRHash() []byte { @@ -13304,7 +13586,10 @@ func (x *AddInvoiceResponse) GetPaymentAddr() []byte { } type PaymentHash struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The hex-encoded payment hash of the invoice to be looked up. The passed // payment hash must be exactly 32 bytes, otherwise an error is returned. // Deprecated now that the REST gateway supports base64 encoding of bytes @@ -13314,16 +13599,16 @@ type PaymentHash struct { RHashStr string `protobuf:"bytes,1,opt,name=r_hash_str,json=rHashStr,proto3" json:"r_hash_str,omitempty"` // The payment hash of the invoice to be looked up. When using REST, this field // must be encoded as base64. - RHash []byte `protobuf:"bytes,2,opt,name=r_hash,json=rHash,proto3" json:"r_hash,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + RHash []byte `protobuf:"bytes,2,opt,name=r_hash,json=rHash,proto3" json:"r_hash,omitempty"` } func (x *PaymentHash) Reset() { *x = PaymentHash{} - mi := &file_lightning_proto_msgTypes[142] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[140] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PaymentHash) String() string { @@ -13333,8 +13618,8 @@ func (x *PaymentHash) String() string { func (*PaymentHash) ProtoMessage() {} func (x *PaymentHash) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[142] - if x != nil { + mi := &file_lightning_proto_msgTypes[140] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -13346,7 +13631,7 @@ func (x *PaymentHash) ProtoReflect() protoreflect.Message { // Deprecated: Use PaymentHash.ProtoReflect.Descriptor instead. func (*PaymentHash) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{142} + return file_lightning_proto_rawDescGZIP(), []int{140} } // Deprecated: Marked as deprecated in lightning.proto. @@ -13365,7 +13650,10 @@ func (x *PaymentHash) GetRHash() []byte { } type ListInvoiceRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // If set, only invoices that are not settled and not canceled will be returned // in the response. PendingOnly bool `protobuf:"varint,1,opt,name=pending_only,json=pendingOnly,proto3" json:"pending_only,omitempty"` @@ -13383,15 +13671,15 @@ type ListInvoiceRequest struct { // If set, returns all invoices with a creation date less than or equal to // it. Measured in seconds since the unix epoch. CreationDateEnd uint64 `protobuf:"varint,8,opt,name=creation_date_end,json=creationDateEnd,proto3" json:"creation_date_end,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ListInvoiceRequest) Reset() { *x = ListInvoiceRequest{} - mi := &file_lightning_proto_msgTypes[143] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[141] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListInvoiceRequest) String() string { @@ -13401,8 +13689,8 @@ func (x *ListInvoiceRequest) String() string { func (*ListInvoiceRequest) ProtoMessage() {} func (x *ListInvoiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[143] - if x != nil { + mi := &file_lightning_proto_msgTypes[141] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -13414,7 +13702,7 @@ func (x *ListInvoiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListInvoiceRequest.ProtoReflect.Descriptor instead. func (*ListInvoiceRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{143} + return file_lightning_proto_rawDescGZIP(), []int{141} } func (x *ListInvoiceRequest) GetPendingOnly() bool { @@ -13460,7 +13748,10 @@ func (x *ListInvoiceRequest) GetCreationDateEnd() uint64 { } type ListInvoiceResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // A list of invoices from the time slice of the time series specified in the // request. Invoices []*Invoice `protobuf:"bytes,1,rep,name=invoices,proto3" json:"invoices,omitempty"` @@ -13470,15 +13761,15 @@ type ListInvoiceResponse struct { // The index of the last item in the set of returned invoices. This can be used // to seek backwards, pagination style. FirstIndexOffset uint64 `protobuf:"varint,3,opt,name=first_index_offset,json=firstIndexOffset,proto3" json:"first_index_offset,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ListInvoiceResponse) Reset() { *x = ListInvoiceResponse{} - mi := &file_lightning_proto_msgTypes[144] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[142] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListInvoiceResponse) String() string { @@ -13488,8 +13779,8 @@ func (x *ListInvoiceResponse) String() string { func (*ListInvoiceResponse) ProtoMessage() {} func (x *ListInvoiceResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[144] - if x != nil { + mi := &file_lightning_proto_msgTypes[142] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -13501,7 +13792,7 @@ func (x *ListInvoiceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListInvoiceResponse.ProtoReflect.Descriptor instead. func (*ListInvoiceResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{144} + return file_lightning_proto_rawDescGZIP(), []int{142} } func (x *ListInvoiceResponse) GetInvoices() []*Invoice { @@ -13526,7 +13817,10 @@ func (x *ListInvoiceResponse) GetFirstIndexOffset() uint64 { } type InvoiceSubscription struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // If specified (non-zero), then we'll first start by sending out // notifications for all added indexes with an add_index greater than this // value. This allows callers to catch up on any events they missed while they @@ -13536,16 +13830,16 @@ type InvoiceSubscription struct { // notifications for all settled indexes with an settle_index greater than // this value. This allows callers to catch up on any events they missed while // they weren't connected to the streaming RPC. - SettleIndex uint64 `protobuf:"varint,2,opt,name=settle_index,json=settleIndex,proto3" json:"settle_index,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + SettleIndex uint64 `protobuf:"varint,2,opt,name=settle_index,json=settleIndex,proto3" json:"settle_index,omitempty"` } func (x *InvoiceSubscription) Reset() { *x = InvoiceSubscription{} - mi := &file_lightning_proto_msgTypes[145] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[143] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *InvoiceSubscription) String() string { @@ -13555,8 +13849,8 @@ func (x *InvoiceSubscription) String() string { func (*InvoiceSubscription) ProtoMessage() {} func (x *InvoiceSubscription) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[145] - if x != nil { + mi := &file_lightning_proto_msgTypes[143] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -13568,7 +13862,7 @@ func (x *InvoiceSubscription) ProtoReflect() protoreflect.Message { // Deprecated: Use InvoiceSubscription.ProtoReflect.Descriptor instead. func (*InvoiceSubscription) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{145} + return file_lightning_proto_rawDescGZIP(), []int{143} } func (x *InvoiceSubscription) GetAddIndex() uint64 { @@ -13586,18 +13880,21 @@ func (x *InvoiceSubscription) GetSettleIndex() uint64 { } type DelCanceledInvoiceReq struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Invoice payment hash to delete. - InvoiceHash string `protobuf:"bytes,1,opt,name=invoice_hash,json=invoiceHash,proto3" json:"invoice_hash,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Invoice payment hash to delete. + InvoiceHash string `protobuf:"bytes,1,opt,name=invoice_hash,json=invoiceHash,proto3" json:"invoice_hash,omitempty"` } func (x *DelCanceledInvoiceReq) Reset() { *x = DelCanceledInvoiceReq{} - mi := &file_lightning_proto_msgTypes[146] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[144] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *DelCanceledInvoiceReq) String() string { @@ -13607,8 +13904,8 @@ func (x *DelCanceledInvoiceReq) String() string { func (*DelCanceledInvoiceReq) ProtoMessage() {} func (x *DelCanceledInvoiceReq) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[146] - if x != nil { + mi := &file_lightning_proto_msgTypes[144] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -13620,7 +13917,7 @@ func (x *DelCanceledInvoiceReq) ProtoReflect() protoreflect.Message { // Deprecated: Use DelCanceledInvoiceReq.ProtoReflect.Descriptor instead. func (*DelCanceledInvoiceReq) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{146} + return file_lightning_proto_rawDescGZIP(), []int{144} } func (x *DelCanceledInvoiceReq) GetInvoiceHash() string { @@ -13631,18 +13928,21 @@ func (x *DelCanceledInvoiceReq) GetInvoiceHash() string { } type DelCanceledInvoiceResp struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The status of the delete operation. - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The status of the delete operation. + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` } func (x *DelCanceledInvoiceResp) Reset() { *x = DelCanceledInvoiceResp{} - mi := &file_lightning_proto_msgTypes[147] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[145] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *DelCanceledInvoiceResp) String() string { @@ -13652,8 +13952,8 @@ func (x *DelCanceledInvoiceResp) String() string { func (*DelCanceledInvoiceResp) ProtoMessage() {} func (x *DelCanceledInvoiceResp) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[147] - if x != nil { + mi := &file_lightning_proto_msgTypes[145] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -13665,7 +13965,7 @@ func (x *DelCanceledInvoiceResp) ProtoReflect() protoreflect.Message { // Deprecated: Use DelCanceledInvoiceResp.ProtoReflect.Descriptor instead. func (*DelCanceledInvoiceResp) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{147} + return file_lightning_proto_rawDescGZIP(), []int{145} } func (x *DelCanceledInvoiceResp) GetStatus() string { @@ -13676,7 +13976,10 @@ func (x *DelCanceledInvoiceResp) GetStatus() string { } type Payment struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The payment hash PaymentHash string `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"` // Deprecated, use value_sat or value_msat. @@ -13716,16 +14019,16 @@ type Payment struct { FailureReason PaymentFailureReason `protobuf:"varint,16,opt,name=failure_reason,json=failureReason,proto3,enum=lnrpc.PaymentFailureReason" json:"failure_reason,omitempty"` // The custom TLV records that were sent to the first hop as part of the HTLC // wire message for this payment. - FirstHopCustomRecords map[uint64][]byte `protobuf:"bytes,17,rep,name=first_hop_custom_records,json=firstHopCustomRecords,proto3" json:"first_hop_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + FirstHopCustomRecords map[uint64][]byte `protobuf:"bytes,17,rep,name=first_hop_custom_records,json=firstHopCustomRecords,proto3" json:"first_hop_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *Payment) Reset() { *x = Payment{} - mi := &file_lightning_proto_msgTypes[148] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[146] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *Payment) String() string { @@ -13735,8 +14038,8 @@ func (x *Payment) String() string { func (*Payment) ProtoMessage() {} func (x *Payment) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[148] - if x != nil { + mi := &file_lightning_proto_msgTypes[146] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -13748,7 +14051,7 @@ func (x *Payment) ProtoReflect() protoreflect.Message { // Deprecated: Use Payment.ProtoReflect.Descriptor instead. func (*Payment) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{148} + return file_lightning_proto_rawDescGZIP(), []int{146} } func (x *Payment) GetPaymentHash() string { @@ -13867,7 +14170,10 @@ func (x *Payment) GetFirstHopCustomRecords() map[uint64][]byte { } type HTLCAttempt struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The unique ID that is used for this attempt. AttemptId uint64 `protobuf:"varint,7,opt,name=attempt_id,json=attemptId,proto3" json:"attempt_id,omitempty"` // The status of the HTLC. @@ -13882,16 +14188,16 @@ type HTLCAttempt struct { // Detailed htlc failure info. Failure *Failure `protobuf:"bytes,5,opt,name=failure,proto3" json:"failure,omitempty"` // The preimage that was used to settle the HTLC. - Preimage []byte `protobuf:"bytes,6,opt,name=preimage,proto3" json:"preimage,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Preimage []byte `protobuf:"bytes,6,opt,name=preimage,proto3" json:"preimage,omitempty"` } func (x *HTLCAttempt) Reset() { *x = HTLCAttempt{} - mi := &file_lightning_proto_msgTypes[149] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[147] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *HTLCAttempt) String() string { @@ -13901,8 +14207,8 @@ func (x *HTLCAttempt) String() string { func (*HTLCAttempt) ProtoMessage() {} func (x *HTLCAttempt) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[149] - if x != nil { + mi := &file_lightning_proto_msgTypes[147] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -13914,7 +14220,7 @@ func (x *HTLCAttempt) ProtoReflect() protoreflect.Message { // Deprecated: Use HTLCAttempt.ProtoReflect.Descriptor instead. func (*HTLCAttempt) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{149} + return file_lightning_proto_rawDescGZIP(), []int{147} } func (x *HTLCAttempt) GetAttemptId() uint64 { @@ -13967,7 +14273,10 @@ func (x *HTLCAttempt) GetPreimage() []byte { } type ListPaymentsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // If true, then return payments that have not yet fully completed. This means // that pending payments, as well as failed payments will show up if this // field is set to true. This flag doesn't change the meaning of the indices, @@ -13996,18 +14305,15 @@ type ListPaymentsRequest struct { // If set, returns all payments with a creation date less than or equal to // it. Measured in seconds since the unix epoch. CreationDateEnd uint64 `protobuf:"varint,7,opt,name=creation_date_end,json=creationDateEnd,proto3" json:"creation_date_end,omitempty"` - // If set, omit hop-level route data for HTLC attempts to reduce query - // cost and response size. - OmitHops bool `protobuf:"varint,8,opt,name=omit_hops,json=omitHops,proto3" json:"omit_hops,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ListPaymentsRequest) Reset() { *x = ListPaymentsRequest{} - mi := &file_lightning_proto_msgTypes[150] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[148] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListPaymentsRequest) String() string { @@ -14017,8 +14323,8 @@ func (x *ListPaymentsRequest) String() string { func (*ListPaymentsRequest) ProtoMessage() {} func (x *ListPaymentsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[150] - if x != nil { + mi := &file_lightning_proto_msgTypes[148] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -14030,7 +14336,7 @@ func (x *ListPaymentsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPaymentsRequest.ProtoReflect.Descriptor instead. func (*ListPaymentsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{150} + return file_lightning_proto_rawDescGZIP(), []int{148} } func (x *ListPaymentsRequest) GetIncludeIncomplete() bool { @@ -14082,15 +14388,11 @@ func (x *ListPaymentsRequest) GetCreationDateEnd() uint64 { return 0 } -func (x *ListPaymentsRequest) GetOmitHops() bool { - if x != nil { - return x.OmitHops - } - return false -} - type ListPaymentsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The list of payments Payments []*Payment `protobuf:"bytes,1,rep,name=payments,proto3" json:"payments,omitempty"` // The index of the first item in the set of returned payments. This can be @@ -14104,15 +14406,15 @@ type ListPaymentsResponse struct { // number of payments requested in the query) currently present in the payments // database. TotalNumPayments uint64 `protobuf:"varint,4,opt,name=total_num_payments,json=totalNumPayments,proto3" json:"total_num_payments,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ListPaymentsResponse) Reset() { *x = ListPaymentsResponse{} - mi := &file_lightning_proto_msgTypes[151] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[149] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListPaymentsResponse) String() string { @@ -14122,8 +14424,8 @@ func (x *ListPaymentsResponse) String() string { func (*ListPaymentsResponse) ProtoMessage() {} func (x *ListPaymentsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[151] - if x != nil { + mi := &file_lightning_proto_msgTypes[149] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -14135,7 +14437,7 @@ func (x *ListPaymentsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPaymentsResponse.ProtoReflect.Descriptor instead. func (*ListPaymentsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{151} + return file_lightning_proto_rawDescGZIP(), []int{149} } func (x *ListPaymentsResponse) GetPayments() []*Payment { @@ -14167,20 +14469,23 @@ func (x *ListPaymentsResponse) GetTotalNumPayments() uint64 { } type DeletePaymentRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Payment hash to delete. PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"` // Only delete failed HTLCs from the payment, not the payment itself. FailedHtlcsOnly bool `protobuf:"varint,2,opt,name=failed_htlcs_only,json=failedHtlcsOnly,proto3" json:"failed_htlcs_only,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *DeletePaymentRequest) Reset() { *x = DeletePaymentRequest{} - mi := &file_lightning_proto_msgTypes[152] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[150] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *DeletePaymentRequest) String() string { @@ -14190,8 +14495,8 @@ func (x *DeletePaymentRequest) String() string { func (*DeletePaymentRequest) ProtoMessage() {} func (x *DeletePaymentRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[152] - if x != nil { + mi := &file_lightning_proto_msgTypes[150] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -14203,7 +14508,7 @@ func (x *DeletePaymentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeletePaymentRequest.ProtoReflect.Descriptor instead. func (*DeletePaymentRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{152} + return file_lightning_proto_rawDescGZIP(), []int{150} } func (x *DeletePaymentRequest) GetPaymentHash() []byte { @@ -14221,23 +14526,26 @@ func (x *DeletePaymentRequest) GetFailedHtlcsOnly() bool { } type DeleteAllPaymentsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Only delete failed payments. FailedPaymentsOnly bool `protobuf:"varint,1,opt,name=failed_payments_only,json=failedPaymentsOnly,proto3" json:"failed_payments_only,omitempty"` // Only delete failed HTLCs from payments, not the payment itself. FailedHtlcsOnly bool `protobuf:"varint,2,opt,name=failed_htlcs_only,json=failedHtlcsOnly,proto3" json:"failed_htlcs_only,omitempty"` // Delete all payments. NOTE: Using this option requires careful // consideration as it is a destructive operation. - AllPayments bool `protobuf:"varint,3,opt,name=all_payments,json=allPayments,proto3" json:"all_payments,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + AllPayments bool `protobuf:"varint,3,opt,name=all_payments,json=allPayments,proto3" json:"all_payments,omitempty"` } func (x *DeleteAllPaymentsRequest) Reset() { *x = DeleteAllPaymentsRequest{} - mi := &file_lightning_proto_msgTypes[153] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[151] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *DeleteAllPaymentsRequest) String() string { @@ -14247,8 +14555,8 @@ func (x *DeleteAllPaymentsRequest) String() string { func (*DeleteAllPaymentsRequest) ProtoMessage() {} func (x *DeleteAllPaymentsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[153] - if x != nil { + mi := &file_lightning_proto_msgTypes[151] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -14260,7 +14568,7 @@ func (x *DeleteAllPaymentsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteAllPaymentsRequest.ProtoReflect.Descriptor instead. func (*DeleteAllPaymentsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{153} + return file_lightning_proto_rawDescGZIP(), []int{151} } func (x *DeleteAllPaymentsRequest) GetFailedPaymentsOnly() bool { @@ -14285,18 +14593,21 @@ func (x *DeleteAllPaymentsRequest) GetAllPayments() bool { } type DeletePaymentResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The status of the delete operation. - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The status of the delete operation. + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` } func (x *DeletePaymentResponse) Reset() { *x = DeletePaymentResponse{} - mi := &file_lightning_proto_msgTypes[154] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[152] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *DeletePaymentResponse) String() string { @@ -14306,8 +14617,8 @@ func (x *DeletePaymentResponse) String() string { func (*DeletePaymentResponse) ProtoMessage() {} func (x *DeletePaymentResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[154] - if x != nil { + mi := &file_lightning_proto_msgTypes[152] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -14319,7 +14630,7 @@ func (x *DeletePaymentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeletePaymentResponse.ProtoReflect.Descriptor instead. func (*DeletePaymentResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{154} + return file_lightning_proto_rawDescGZIP(), []int{152} } func (x *DeletePaymentResponse) GetStatus() string { @@ -14330,18 +14641,21 @@ func (x *DeletePaymentResponse) GetStatus() string { } type DeleteAllPaymentsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The status of the delete operation. - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The status of the delete operation. + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` } func (x *DeleteAllPaymentsResponse) Reset() { *x = DeleteAllPaymentsResponse{} - mi := &file_lightning_proto_msgTypes[155] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[153] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *DeleteAllPaymentsResponse) String() string { @@ -14351,8 +14665,8 @@ func (x *DeleteAllPaymentsResponse) String() string { func (*DeleteAllPaymentsResponse) ProtoMessage() {} func (x *DeleteAllPaymentsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[155] - if x != nil { + mi := &file_lightning_proto_msgTypes[153] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -14364,7 +14678,7 @@ func (x *DeleteAllPaymentsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteAllPaymentsResponse.ProtoReflect.Descriptor instead. func (*DeleteAllPaymentsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{155} + return file_lightning_proto_rawDescGZIP(), []int{153} } func (x *DeleteAllPaymentsResponse) GetStatus() string { @@ -14375,22 +14689,25 @@ func (x *DeleteAllPaymentsResponse) GetStatus() string { } type AbandonChannelRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ChannelPoint *ChannelPoint `protobuf:"bytes,1,opt,name=channel_point,json=channelPoint,proto3" json:"channel_point,omitempty"` - PendingFundingShimOnly bool `protobuf:"varint,2,opt,name=pending_funding_shim_only,json=pendingFundingShimOnly,proto3" json:"pending_funding_shim_only,omitempty"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ChannelPoint *ChannelPoint `protobuf:"bytes,1,opt,name=channel_point,json=channelPoint,proto3" json:"channel_point,omitempty"` + PendingFundingShimOnly bool `protobuf:"varint,2,opt,name=pending_funding_shim_only,json=pendingFundingShimOnly,proto3" json:"pending_funding_shim_only,omitempty"` // Override the requirement for being in dev mode by setting this to true and // confirming the user knows what they are doing and this is a potential foot // gun to lose funds if used on active channels. IKnowWhatIAmDoing bool `protobuf:"varint,3,opt,name=i_know_what_i_am_doing,json=iKnowWhatIAmDoing,proto3" json:"i_know_what_i_am_doing,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *AbandonChannelRequest) Reset() { *x = AbandonChannelRequest{} - mi := &file_lightning_proto_msgTypes[156] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[154] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *AbandonChannelRequest) String() string { @@ -14400,8 +14717,8 @@ func (x *AbandonChannelRequest) String() string { func (*AbandonChannelRequest) ProtoMessage() {} func (x *AbandonChannelRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[156] - if x != nil { + mi := &file_lightning_proto_msgTypes[154] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -14413,7 +14730,7 @@ func (x *AbandonChannelRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AbandonChannelRequest.ProtoReflect.Descriptor instead. func (*AbandonChannelRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{156} + return file_lightning_proto_rawDescGZIP(), []int{154} } func (x *AbandonChannelRequest) GetChannelPoint() *ChannelPoint { @@ -14438,18 +14755,21 @@ func (x *AbandonChannelRequest) GetIKnowWhatIAmDoing() bool { } type AbandonChannelResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The status of the abandon operation. - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The status of the abandon operation. + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` } func (x *AbandonChannelResponse) Reset() { *x = AbandonChannelResponse{} - mi := &file_lightning_proto_msgTypes[157] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[155] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *AbandonChannelResponse) String() string { @@ -14459,8 +14779,8 @@ func (x *AbandonChannelResponse) String() string { func (*AbandonChannelResponse) ProtoMessage() {} func (x *AbandonChannelResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[157] - if x != nil { + mi := &file_lightning_proto_msgTypes[155] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -14472,7 +14792,7 @@ func (x *AbandonChannelResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AbandonChannelResponse.ProtoReflect.Descriptor instead. func (*AbandonChannelResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{157} + return file_lightning_proto_rawDescGZIP(), []int{155} } func (x *AbandonChannelResponse) GetStatus() string { @@ -14483,18 +14803,21 @@ func (x *AbandonChannelResponse) GetStatus() string { } type DebugLevelRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Show bool `protobuf:"varint,1,opt,name=show,proto3" json:"show,omitempty"` - LevelSpec string `protobuf:"bytes,2,opt,name=level_spec,json=levelSpec,proto3" json:"level_spec,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Show bool `protobuf:"varint,1,opt,name=show,proto3" json:"show,omitempty"` + LevelSpec string `protobuf:"bytes,2,opt,name=level_spec,json=levelSpec,proto3" json:"level_spec,omitempty"` } func (x *DebugLevelRequest) Reset() { *x = DebugLevelRequest{} - mi := &file_lightning_proto_msgTypes[158] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[156] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *DebugLevelRequest) String() string { @@ -14504,8 +14827,8 @@ func (x *DebugLevelRequest) String() string { func (*DebugLevelRequest) ProtoMessage() {} func (x *DebugLevelRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[158] - if x != nil { + mi := &file_lightning_proto_msgTypes[156] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -14517,7 +14840,7 @@ func (x *DebugLevelRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DebugLevelRequest.ProtoReflect.Descriptor instead. func (*DebugLevelRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{158} + return file_lightning_proto_rawDescGZIP(), []int{156} } func (x *DebugLevelRequest) GetShow() bool { @@ -14535,17 +14858,20 @@ func (x *DebugLevelRequest) GetLevelSpec() string { } type DebugLevelResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - SubSystems string `protobuf:"bytes,1,opt,name=sub_systems,json=subSystems,proto3" json:"sub_systems,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SubSystems string `protobuf:"bytes,1,opt,name=sub_systems,json=subSystems,proto3" json:"sub_systems,omitempty"` } func (x *DebugLevelResponse) Reset() { *x = DebugLevelResponse{} - mi := &file_lightning_proto_msgTypes[159] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[157] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *DebugLevelResponse) String() string { @@ -14555,8 +14881,8 @@ func (x *DebugLevelResponse) String() string { func (*DebugLevelResponse) ProtoMessage() {} func (x *DebugLevelResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[159] - if x != nil { + mi := &file_lightning_proto_msgTypes[157] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -14568,7 +14894,7 @@ func (x *DebugLevelResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DebugLevelResponse.ProtoReflect.Descriptor instead. func (*DebugLevelResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{159} + return file_lightning_proto_rawDescGZIP(), []int{157} } func (x *DebugLevelResponse) GetSubSystems() string { @@ -14579,18 +14905,21 @@ func (x *DebugLevelResponse) GetSubSystems() string { } type PayReqString struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The payment request string to be decoded - PayReq string `protobuf:"bytes,1,opt,name=pay_req,json=payReq,proto3" json:"pay_req,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The payment request string to be decoded + PayReq string `protobuf:"bytes,1,opt,name=pay_req,json=payReq,proto3" json:"pay_req,omitempty"` } func (x *PayReqString) Reset() { *x = PayReqString{} - mi := &file_lightning_proto_msgTypes[160] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[158] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PayReqString) String() string { @@ -14600,8 +14929,8 @@ func (x *PayReqString) String() string { func (*PayReqString) ProtoMessage() {} func (x *PayReqString) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[160] - if x != nil { + mi := &file_lightning_proto_msgTypes[158] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -14613,7 +14942,7 @@ func (x *PayReqString) ProtoReflect() protoreflect.Message { // Deprecated: Use PayReqString.ProtoReflect.Descriptor instead. func (*PayReqString) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{160} + return file_lightning_proto_rawDescGZIP(), []int{158} } func (x *PayReqString) GetPayReq() string { @@ -14624,30 +14953,33 @@ func (x *PayReqString) GetPayReq() string { } type PayReq struct { - state protoimpl.MessageState `protogen:"open.v1"` - Destination string `protobuf:"bytes,1,opt,name=destination,proto3" json:"destination,omitempty"` - PaymentHash string `protobuf:"bytes,2,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"` - NumSatoshis int64 `protobuf:"varint,3,opt,name=num_satoshis,json=numSatoshis,proto3" json:"num_satoshis,omitempty"` - Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` - Expiry int64 `protobuf:"varint,5,opt,name=expiry,proto3" json:"expiry,omitempty"` - Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty"` - DescriptionHash string `protobuf:"bytes,7,opt,name=description_hash,json=descriptionHash,proto3" json:"description_hash,omitempty"` - FallbackAddr string `protobuf:"bytes,8,opt,name=fallback_addr,json=fallbackAddr,proto3" json:"fallback_addr,omitempty"` - CltvExpiry int64 `protobuf:"varint,9,opt,name=cltv_expiry,json=cltvExpiry,proto3" json:"cltv_expiry,omitempty"` - RouteHints []*RouteHint `protobuf:"bytes,10,rep,name=route_hints,json=routeHints,proto3" json:"route_hints,omitempty"` - PaymentAddr []byte `protobuf:"bytes,11,opt,name=payment_addr,json=paymentAddr,proto3" json:"payment_addr,omitempty"` - NumMsat int64 `protobuf:"varint,12,opt,name=num_msat,json=numMsat,proto3" json:"num_msat,omitempty"` - Features map[uint32]*Feature `protobuf:"bytes,13,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - BlindedPaths []*BlindedPaymentPath `protobuf:"bytes,14,rep,name=blinded_paths,json=blindedPaths,proto3" json:"blinded_paths,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Destination string `protobuf:"bytes,1,opt,name=destination,proto3" json:"destination,omitempty"` + PaymentHash string `protobuf:"bytes,2,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"` + NumSatoshis int64 `protobuf:"varint,3,opt,name=num_satoshis,json=numSatoshis,proto3" json:"num_satoshis,omitempty"` + Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Expiry int64 `protobuf:"varint,5,opt,name=expiry,proto3" json:"expiry,omitempty"` + Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty"` + DescriptionHash string `protobuf:"bytes,7,opt,name=description_hash,json=descriptionHash,proto3" json:"description_hash,omitempty"` + FallbackAddr string `protobuf:"bytes,8,opt,name=fallback_addr,json=fallbackAddr,proto3" json:"fallback_addr,omitempty"` + CltvExpiry int64 `protobuf:"varint,9,opt,name=cltv_expiry,json=cltvExpiry,proto3" json:"cltv_expiry,omitempty"` + RouteHints []*RouteHint `protobuf:"bytes,10,rep,name=route_hints,json=routeHints,proto3" json:"route_hints,omitempty"` + PaymentAddr []byte `protobuf:"bytes,11,opt,name=payment_addr,json=paymentAddr,proto3" json:"payment_addr,omitempty"` + NumMsat int64 `protobuf:"varint,12,opt,name=num_msat,json=numMsat,proto3" json:"num_msat,omitempty"` + Features map[uint32]*Feature `protobuf:"bytes,13,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + BlindedPaths []*BlindedPaymentPath `protobuf:"bytes,14,rep,name=blinded_paths,json=blindedPaths,proto3" json:"blinded_paths,omitempty"` } func (x *PayReq) Reset() { *x = PayReq{} - mi := &file_lightning_proto_msgTypes[161] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[159] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PayReq) String() string { @@ -14657,8 +14989,8 @@ func (x *PayReq) String() string { func (*PayReq) ProtoMessage() {} func (x *PayReq) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[161] - if x != nil { + mi := &file_lightning_proto_msgTypes[159] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -14670,7 +15002,7 @@ func (x *PayReq) ProtoReflect() protoreflect.Message { // Deprecated: Use PayReq.ProtoReflect.Descriptor instead. func (*PayReq) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{161} + return file_lightning_proto_rawDescGZIP(), []int{159} } func (x *PayReq) GetDestination() string { @@ -14772,19 +15104,22 @@ func (x *PayReq) GetBlindedPaths() []*BlindedPaymentPath { } type Feature struct { - state protoimpl.MessageState `protogen:"open.v1"` - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` - IsRequired bool `protobuf:"varint,3,opt,name=is_required,json=isRequired,proto3" json:"is_required,omitempty"` - IsKnown bool `protobuf:"varint,4,opt,name=is_known,json=isKnown,proto3" json:"is_known,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + IsRequired bool `protobuf:"varint,3,opt,name=is_required,json=isRequired,proto3" json:"is_required,omitempty"` + IsKnown bool `protobuf:"varint,4,opt,name=is_known,json=isKnown,proto3" json:"is_known,omitempty"` } func (x *Feature) Reset() { *x = Feature{} - mi := &file_lightning_proto_msgTypes[162] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[160] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *Feature) String() string { @@ -14794,8 +15129,8 @@ func (x *Feature) String() string { func (*Feature) ProtoMessage() {} func (x *Feature) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[162] - if x != nil { + mi := &file_lightning_proto_msgTypes[160] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -14807,7 +15142,7 @@ func (x *Feature) ProtoReflect() protoreflect.Message { // Deprecated: Use Feature.ProtoReflect.Descriptor instead. func (*Feature) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{162} + return file_lightning_proto_rawDescGZIP(), []int{160} } func (x *Feature) GetName() string { @@ -14832,16 +15167,18 @@ func (x *Feature) GetIsKnown() bool { } type FeeReportRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *FeeReportRequest) Reset() { *x = FeeReportRequest{} - mi := &file_lightning_proto_msgTypes[163] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[161] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *FeeReportRequest) String() string { @@ -14851,8 +15188,8 @@ func (x *FeeReportRequest) String() string { func (*FeeReportRequest) ProtoMessage() {} func (x *FeeReportRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[163] - if x != nil { + mi := &file_lightning_proto_msgTypes[161] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -14864,11 +15201,14 @@ func (x *FeeReportRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FeeReportRequest.ProtoReflect.Descriptor instead. func (*FeeReportRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{163} + return file_lightning_proto_rawDescGZIP(), []int{161} } type ChannelFeeReport struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The short channel id that this fee report belongs to. ChanId uint64 `protobuf:"varint,5,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"` // The channel that this fee report belongs to. @@ -14886,15 +15226,15 @@ type ChannelFeeReport struct { // The amount charged per milli-satoshis transferred expressed in // millionths of a satoshi. InboundFeePerMil int32 `protobuf:"varint,7,opt,name=inbound_fee_per_mil,json=inboundFeePerMil,proto3" json:"inbound_fee_per_mil,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ChannelFeeReport) Reset() { *x = ChannelFeeReport{} - mi := &file_lightning_proto_msgTypes[164] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[162] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChannelFeeReport) String() string { @@ -14904,8 +15244,8 @@ func (x *ChannelFeeReport) String() string { func (*ChannelFeeReport) ProtoMessage() {} func (x *ChannelFeeReport) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[164] - if x != nil { + mi := &file_lightning_proto_msgTypes[162] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -14917,7 +15257,7 @@ func (x *ChannelFeeReport) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelFeeReport.ProtoReflect.Descriptor instead. func (*ChannelFeeReport) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{164} + return file_lightning_proto_rawDescGZIP(), []int{162} } func (x *ChannelFeeReport) GetChanId() uint64 { @@ -14970,7 +15310,10 @@ func (x *ChannelFeeReport) GetInboundFeePerMil() int32 { } type FeeReportResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // An array of channel fee reports which describes the current fee schedule // for each channel. ChannelFees []*ChannelFeeReport `protobuf:"bytes,1,rep,name=channel_fees,json=channelFees,proto3" json:"channel_fees,omitempty"` @@ -14982,16 +15325,16 @@ type FeeReportResponse struct { WeekFeeSum uint64 `protobuf:"varint,3,opt,name=week_fee_sum,json=weekFeeSum,proto3" json:"week_fee_sum,omitempty"` // The total amount of fee revenue (in satoshis) the switch has collected // over the past 1 month. - MonthFeeSum uint64 `protobuf:"varint,4,opt,name=month_fee_sum,json=monthFeeSum,proto3" json:"month_fee_sum,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + MonthFeeSum uint64 `protobuf:"varint,4,opt,name=month_fee_sum,json=monthFeeSum,proto3" json:"month_fee_sum,omitempty"` } func (x *FeeReportResponse) Reset() { *x = FeeReportResponse{} - mi := &file_lightning_proto_msgTypes[165] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[163] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *FeeReportResponse) String() string { @@ -15001,8 +15344,8 @@ func (x *FeeReportResponse) String() string { func (*FeeReportResponse) ProtoMessage() {} func (x *FeeReportResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[165] - if x != nil { + mi := &file_lightning_proto_msgTypes[163] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -15014,7 +15357,7 @@ func (x *FeeReportResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FeeReportResponse.ProtoReflect.Descriptor instead. func (*FeeReportResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{165} + return file_lightning_proto_rawDescGZIP(), []int{163} } func (x *FeeReportResponse) GetChannelFees() []*ChannelFeeReport { @@ -15046,22 +15389,25 @@ func (x *FeeReportResponse) GetMonthFeeSum() uint64 { } type InboundFee struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The inbound base fee charged regardless of the number of milli-satoshis // received in the channel. By default, only negative values are accepted. BaseFeeMsat int32 `protobuf:"varint,1,opt,name=base_fee_msat,json=baseFeeMsat,proto3" json:"base_fee_msat,omitempty"` // The effective inbound fee rate in micro-satoshis (parts per million). // By default, only negative values are accepted. - FeeRatePpm int32 `protobuf:"varint,2,opt,name=fee_rate_ppm,json=feeRatePpm,proto3" json:"fee_rate_ppm,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + FeeRatePpm int32 `protobuf:"varint,2,opt,name=fee_rate_ppm,json=feeRatePpm,proto3" json:"fee_rate_ppm,omitempty"` } func (x *InboundFee) Reset() { *x = InboundFee{} - mi := &file_lightning_proto_msgTypes[166] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[164] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *InboundFee) String() string { @@ -15071,8 +15417,8 @@ func (x *InboundFee) String() string { func (*InboundFee) ProtoMessage() {} func (x *InboundFee) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[166] - if x != nil { + mi := &file_lightning_proto_msgTypes[164] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -15084,7 +15430,7 @@ func (x *InboundFee) ProtoReflect() protoreflect.Message { // Deprecated: Use InboundFee.ProtoReflect.Descriptor instead. func (*InboundFee) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{166} + return file_lightning_proto_rawDescGZIP(), []int{164} } func (x *InboundFee) GetBaseFeeMsat() int32 { @@ -15102,8 +15448,11 @@ func (x *InboundFee) GetFeeRatePpm() int32 { } type PolicyUpdateRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to Scope: + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Scope: // // *PolicyUpdateRequest_Global // *PolicyUpdateRequest_ChanPoint @@ -15136,15 +15485,15 @@ type PolicyUpdateRequest struct { // channel permanently. For fields not set in this command, the default // policy will be created. CreateMissingEdge bool `protobuf:"varint,11,opt,name=create_missing_edge,json=createMissingEdge,proto3" json:"create_missing_edge,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *PolicyUpdateRequest) Reset() { *x = PolicyUpdateRequest{} - mi := &file_lightning_proto_msgTypes[167] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[165] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PolicyUpdateRequest) String() string { @@ -15154,8 +15503,8 @@ func (x *PolicyUpdateRequest) String() string { func (*PolicyUpdateRequest) ProtoMessage() {} func (x *PolicyUpdateRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[167] - if x != nil { + mi := &file_lightning_proto_msgTypes[165] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -15167,30 +15516,26 @@ func (x *PolicyUpdateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyUpdateRequest.ProtoReflect.Descriptor instead. func (*PolicyUpdateRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{167} + return file_lightning_proto_rawDescGZIP(), []int{165} } -func (x *PolicyUpdateRequest) GetScope() isPolicyUpdateRequest_Scope { - if x != nil { - return x.Scope +func (m *PolicyUpdateRequest) GetScope() isPolicyUpdateRequest_Scope { + if m != nil { + return m.Scope } return nil } func (x *PolicyUpdateRequest) GetGlobal() bool { - if x != nil { - if x, ok := x.Scope.(*PolicyUpdateRequest_Global); ok { - return x.Global - } + if x, ok := x.GetScope().(*PolicyUpdateRequest_Global); ok { + return x.Global } return false } func (x *PolicyUpdateRequest) GetChanPoint() *ChannelPoint { - if x != nil { - if x, ok := x.Scope.(*PolicyUpdateRequest_ChanPoint); ok { - return x.ChanPoint - } + if x, ok := x.GetScope().(*PolicyUpdateRequest_ChanPoint); ok { + return x.ChanPoint } return nil } @@ -15277,22 +15622,25 @@ func (*PolicyUpdateRequest_Global) isPolicyUpdateRequest_Scope() {} func (*PolicyUpdateRequest_ChanPoint) isPolicyUpdateRequest_Scope() {} type FailedUpdate struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The outpoint in format txid:n Outpoint *OutPoint `protobuf:"bytes,1,opt,name=outpoint,proto3" json:"outpoint,omitempty"` // Reason for the policy update failure. Reason UpdateFailure `protobuf:"varint,2,opt,name=reason,proto3,enum=lnrpc.UpdateFailure" json:"reason,omitempty"` // A string representation of the policy update error. - UpdateError string `protobuf:"bytes,3,opt,name=update_error,json=updateError,proto3" json:"update_error,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + UpdateError string `protobuf:"bytes,3,opt,name=update_error,json=updateError,proto3" json:"update_error,omitempty"` } func (x *FailedUpdate) Reset() { *x = FailedUpdate{} - mi := &file_lightning_proto_msgTypes[168] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[166] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *FailedUpdate) String() string { @@ -15302,8 +15650,8 @@ func (x *FailedUpdate) String() string { func (*FailedUpdate) ProtoMessage() {} func (x *FailedUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[168] - if x != nil { + mi := &file_lightning_proto_msgTypes[166] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -15315,7 +15663,7 @@ func (x *FailedUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use FailedUpdate.ProtoReflect.Descriptor instead. func (*FailedUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{168} + return file_lightning_proto_rawDescGZIP(), []int{166} } func (x *FailedUpdate) GetOutpoint() *OutPoint { @@ -15340,18 +15688,21 @@ func (x *FailedUpdate) GetUpdateError() string { } type PolicyUpdateResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // List of failed policy updates. FailedUpdates []*FailedUpdate `protobuf:"bytes,1,rep,name=failed_updates,json=failedUpdates,proto3" json:"failed_updates,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *PolicyUpdateResponse) Reset() { *x = PolicyUpdateResponse{} - mi := &file_lightning_proto_msgTypes[169] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[167] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PolicyUpdateResponse) String() string { @@ -15361,8 +15712,8 @@ func (x *PolicyUpdateResponse) String() string { func (*PolicyUpdateResponse) ProtoMessage() {} func (x *PolicyUpdateResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[169] - if x != nil { + mi := &file_lightning_proto_msgTypes[167] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -15374,7 +15725,7 @@ func (x *PolicyUpdateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyUpdateResponse.ProtoReflect.Descriptor instead. func (*PolicyUpdateResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{169} + return file_lightning_proto_rawDescGZIP(), []int{167} } func (x *PolicyUpdateResponse) GetFailedUpdates() []*FailedUpdate { @@ -15385,7 +15736,10 @@ func (x *PolicyUpdateResponse) GetFailedUpdates() []*FailedUpdate { } type ForwardingHistoryRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Start time is the starting point of the forwarding history request. All // records beyond this point will be included, respecting the end time, and // the index offset. @@ -15409,15 +15763,15 @@ type ForwardingHistoryRequest struct { // List of outgoing channel ids to filter htlcs being forwarded to a // particular channel OutgoingChanIds []uint64 `protobuf:"varint,7,rep,packed,name=outgoing_chan_ids,json=outgoingChanIds,proto3" json:"outgoing_chan_ids,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ForwardingHistoryRequest) Reset() { *x = ForwardingHistoryRequest{} - mi := &file_lightning_proto_msgTypes[170] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[168] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ForwardingHistoryRequest) String() string { @@ -15427,8 +15781,8 @@ func (x *ForwardingHistoryRequest) String() string { func (*ForwardingHistoryRequest) ProtoMessage() {} func (x *ForwardingHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[170] - if x != nil { + mi := &file_lightning_proto_msgTypes[168] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -15440,7 +15794,7 @@ func (x *ForwardingHistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ForwardingHistoryRequest.ProtoReflect.Descriptor instead. func (*ForwardingHistoryRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{170} + return file_lightning_proto_rawDescGZIP(), []int{168} } func (x *ForwardingHistoryRequest) GetStartTime() uint64 { @@ -15493,7 +15847,10 @@ func (x *ForwardingHistoryRequest) GetOutgoingChanIds() []uint64 { } type ForwardingEvent struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Timestamp is the time (unix epoch offset) that this circuit was // completed. Deprecated by timestamp_ns. // @@ -15533,15 +15890,15 @@ type ForwardingEvent struct { // The ID of the outgoing HTLC in the payment circuit. This field is // optional and may be unset for legacy forwarding events. OutgoingHtlcId *uint64 `protobuf:"varint,15,opt,name=outgoing_htlc_id,json=outgoingHtlcId,proto3,oneof" json:"outgoing_htlc_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ForwardingEvent) Reset() { *x = ForwardingEvent{} - mi := &file_lightning_proto_msgTypes[171] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[169] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ForwardingEvent) String() string { @@ -15551,8 +15908,8 @@ func (x *ForwardingEvent) String() string { func (*ForwardingEvent) ProtoMessage() {} func (x *ForwardingEvent) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[171] - if x != nil { + mi := &file_lightning_proto_msgTypes[169] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -15564,7 +15921,7 @@ func (x *ForwardingEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ForwardingEvent.ProtoReflect.Descriptor instead. func (*ForwardingEvent) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{171} + return file_lightning_proto_rawDescGZIP(), []int{169} } // Deprecated: Marked as deprecated in lightning.proto. @@ -15667,22 +16024,25 @@ func (x *ForwardingEvent) GetOutgoingHtlcId() uint64 { } type ForwardingHistoryResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // A list of forwarding events from the time slice of the time series // specified in the request. ForwardingEvents []*ForwardingEvent `protobuf:"bytes,1,rep,name=forwarding_events,json=forwardingEvents,proto3" json:"forwarding_events,omitempty"` // The index of the last time in the set of returned forwarding events. Can // be used to seek further, pagination style. LastOffsetIndex uint32 `protobuf:"varint,2,opt,name=last_offset_index,json=lastOffsetIndex,proto3" json:"last_offset_index,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ForwardingHistoryResponse) Reset() { *x = ForwardingHistoryResponse{} - mi := &file_lightning_proto_msgTypes[172] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[170] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ForwardingHistoryResponse) String() string { @@ -15692,8 +16052,8 @@ func (x *ForwardingHistoryResponse) String() string { func (*ForwardingHistoryResponse) ProtoMessage() {} func (x *ForwardingHistoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[172] - if x != nil { + mi := &file_lightning_proto_msgTypes[170] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -15705,7 +16065,7 @@ func (x *ForwardingHistoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ForwardingHistoryResponse.ProtoReflect.Descriptor instead. func (*ForwardingHistoryResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{172} + return file_lightning_proto_rawDescGZIP(), []int{170} } func (x *ForwardingHistoryResponse) GetForwardingEvents() []*ForwardingEvent { @@ -15723,18 +16083,21 @@ func (x *ForwardingHistoryResponse) GetLastOffsetIndex() uint32 { } type ExportChannelBackupRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The target channel point to obtain a back up for. - ChanPoint *ChannelPoint `protobuf:"bytes,1,opt,name=chan_point,json=chanPoint,proto3" json:"chan_point,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The target channel point to obtain a back up for. + ChanPoint *ChannelPoint `protobuf:"bytes,1,opt,name=chan_point,json=chanPoint,proto3" json:"chan_point,omitempty"` } func (x *ExportChannelBackupRequest) Reset() { *x = ExportChannelBackupRequest{} - mi := &file_lightning_proto_msgTypes[173] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[171] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ExportChannelBackupRequest) String() string { @@ -15744,8 +16107,8 @@ func (x *ExportChannelBackupRequest) String() string { func (*ExportChannelBackupRequest) ProtoMessage() {} func (x *ExportChannelBackupRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[173] - if x != nil { + mi := &file_lightning_proto_msgTypes[171] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -15757,7 +16120,7 @@ func (x *ExportChannelBackupRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExportChannelBackupRequest.ProtoReflect.Descriptor instead. func (*ExportChannelBackupRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{173} + return file_lightning_proto_rawDescGZIP(), []int{171} } func (x *ExportChannelBackupRequest) GetChanPoint() *ChannelPoint { @@ -15768,23 +16131,26 @@ func (x *ExportChannelBackupRequest) GetChanPoint() *ChannelPoint { } type ChannelBackup struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Identifies the channel that this backup belongs to. ChanPoint *ChannelPoint `protobuf:"bytes,1,opt,name=chan_point,json=chanPoint,proto3" json:"chan_point,omitempty"` // Is an encrypted single-chan backup. this can be passed to // RestoreChannelBackups, or the WalletUnlocker Init and Unlock methods in // order to trigger the recovery protocol. When using REST, this field must be // encoded as base64. - ChanBackup []byte `protobuf:"bytes,2,opt,name=chan_backup,json=chanBackup,proto3" json:"chan_backup,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + ChanBackup []byte `protobuf:"bytes,2,opt,name=chan_backup,json=chanBackup,proto3" json:"chan_backup,omitempty"` } func (x *ChannelBackup) Reset() { *x = ChannelBackup{} - mi := &file_lightning_proto_msgTypes[174] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[172] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChannelBackup) String() string { @@ -15794,8 +16160,8 @@ func (x *ChannelBackup) String() string { func (*ChannelBackup) ProtoMessage() {} func (x *ChannelBackup) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[174] - if x != nil { + mi := &file_lightning_proto_msgTypes[172] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -15807,7 +16173,7 @@ func (x *ChannelBackup) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelBackup.ProtoReflect.Descriptor instead. func (*ChannelBackup) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{174} + return file_lightning_proto_rawDescGZIP(), []int{172} } func (x *ChannelBackup) GetChanPoint() *ChannelPoint { @@ -15825,7 +16191,10 @@ func (x *ChannelBackup) GetChanBackup() []byte { } type MultiChanBackup struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Is the set of all channels that are included in this multi-channel backup. ChanPoints []*ChannelPoint `protobuf:"bytes,1,rep,name=chan_points,json=chanPoints,proto3" json:"chan_points,omitempty"` // A single encrypted blob containing all the static channel backups of the @@ -15833,15 +16202,15 @@ type MultiChanBackup struct { // safely be replaced with any prior/future versions. When using REST, this // field must be encoded as base64. MultiChanBackup []byte `protobuf:"bytes,2,opt,name=multi_chan_backup,json=multiChanBackup,proto3" json:"multi_chan_backup,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *MultiChanBackup) Reset() { *x = MultiChanBackup{} - mi := &file_lightning_proto_msgTypes[175] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[173] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *MultiChanBackup) String() string { @@ -15851,8 +16220,8 @@ func (x *MultiChanBackup) String() string { func (*MultiChanBackup) ProtoMessage() {} func (x *MultiChanBackup) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[175] - if x != nil { + mi := &file_lightning_proto_msgTypes[173] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -15864,7 +16233,7 @@ func (x *MultiChanBackup) ProtoReflect() protoreflect.Message { // Deprecated: Use MultiChanBackup.ProtoReflect.Descriptor instead. func (*MultiChanBackup) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{175} + return file_lightning_proto_rawDescGZIP(), []int{173} } func (x *MultiChanBackup) GetChanPoints() []*ChannelPoint { @@ -15882,16 +16251,18 @@ func (x *MultiChanBackup) GetMultiChanBackup() []byte { } type ChanBackupExportRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *ChanBackupExportRequest) Reset() { *x = ChanBackupExportRequest{} - mi := &file_lightning_proto_msgTypes[176] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[174] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChanBackupExportRequest) String() string { @@ -15901,8 +16272,8 @@ func (x *ChanBackupExportRequest) String() string { func (*ChanBackupExportRequest) ProtoMessage() {} func (x *ChanBackupExportRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[176] - if x != nil { + mi := &file_lightning_proto_msgTypes[174] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -15914,26 +16285,29 @@ func (x *ChanBackupExportRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChanBackupExportRequest.ProtoReflect.Descriptor instead. func (*ChanBackupExportRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{176} + return file_lightning_proto_rawDescGZIP(), []int{174} } type ChanBackupSnapshot struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The set of new channels that have been added since the last channel backup // snapshot was requested. SingleChanBackups *ChannelBackups `protobuf:"bytes,1,opt,name=single_chan_backups,json=singleChanBackups,proto3" json:"single_chan_backups,omitempty"` // A multi-channel backup that covers all open channels currently known to // lnd. MultiChanBackup *MultiChanBackup `protobuf:"bytes,2,opt,name=multi_chan_backup,json=multiChanBackup,proto3" json:"multi_chan_backup,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ChanBackupSnapshot) Reset() { *x = ChanBackupSnapshot{} - mi := &file_lightning_proto_msgTypes[177] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[175] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChanBackupSnapshot) String() string { @@ -15943,8 +16317,8 @@ func (x *ChanBackupSnapshot) String() string { func (*ChanBackupSnapshot) ProtoMessage() {} func (x *ChanBackupSnapshot) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[177] - if x != nil { + mi := &file_lightning_proto_msgTypes[175] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -15956,7 +16330,7 @@ func (x *ChanBackupSnapshot) ProtoReflect() protoreflect.Message { // Deprecated: Use ChanBackupSnapshot.ProtoReflect.Descriptor instead. func (*ChanBackupSnapshot) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{177} + return file_lightning_proto_rawDescGZIP(), []int{175} } func (x *ChanBackupSnapshot) GetSingleChanBackups() *ChannelBackups { @@ -15974,18 +16348,21 @@ func (x *ChanBackupSnapshot) GetMultiChanBackup() *MultiChanBackup { } type ChannelBackups struct { - state protoimpl.MessageState `protogen:"open.v1"` - // A set of single-chan static channel backups. - ChanBackups []*ChannelBackup `protobuf:"bytes,1,rep,name=chan_backups,json=chanBackups,proto3" json:"chan_backups,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // A set of single-chan static channel backups. + ChanBackups []*ChannelBackup `protobuf:"bytes,1,rep,name=chan_backups,json=chanBackups,proto3" json:"chan_backups,omitempty"` } func (x *ChannelBackups) Reset() { *x = ChannelBackups{} - mi := &file_lightning_proto_msgTypes[178] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[176] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChannelBackups) String() string { @@ -15995,8 +16372,8 @@ func (x *ChannelBackups) String() string { func (*ChannelBackups) ProtoMessage() {} func (x *ChannelBackups) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[178] - if x != nil { + mi := &file_lightning_proto_msgTypes[176] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -16008,7 +16385,7 @@ func (x *ChannelBackups) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelBackups.ProtoReflect.Descriptor instead. func (*ChannelBackups) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{178} + return file_lightning_proto_rawDescGZIP(), []int{176} } func (x *ChannelBackups) GetChanBackups() []*ChannelBackup { @@ -16019,21 +16396,24 @@ func (x *ChannelBackups) GetChanBackups() []*ChannelBackup { } type RestoreChanBackupRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to Backup: + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Backup: // // *RestoreChanBackupRequest_ChanBackups // *RestoreChanBackupRequest_MultiChanBackup - Backup isRestoreChanBackupRequest_Backup `protobuf_oneof:"backup"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Backup isRestoreChanBackupRequest_Backup `protobuf_oneof:"backup"` } func (x *RestoreChanBackupRequest) Reset() { *x = RestoreChanBackupRequest{} - mi := &file_lightning_proto_msgTypes[179] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[177] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *RestoreChanBackupRequest) String() string { @@ -16043,8 +16423,8 @@ func (x *RestoreChanBackupRequest) String() string { func (*RestoreChanBackupRequest) ProtoMessage() {} func (x *RestoreChanBackupRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[179] - if x != nil { + mi := &file_lightning_proto_msgTypes[177] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -16056,30 +16436,26 @@ func (x *RestoreChanBackupRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RestoreChanBackupRequest.ProtoReflect.Descriptor instead. func (*RestoreChanBackupRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{179} + return file_lightning_proto_rawDescGZIP(), []int{177} } -func (x *RestoreChanBackupRequest) GetBackup() isRestoreChanBackupRequest_Backup { - if x != nil { - return x.Backup +func (m *RestoreChanBackupRequest) GetBackup() isRestoreChanBackupRequest_Backup { + if m != nil { + return m.Backup } return nil } func (x *RestoreChanBackupRequest) GetChanBackups() *ChannelBackups { - if x != nil { - if x, ok := x.Backup.(*RestoreChanBackupRequest_ChanBackups); ok { - return x.ChanBackups - } + if x, ok := x.GetBackup().(*RestoreChanBackupRequest_ChanBackups); ok { + return x.ChanBackups } return nil } func (x *RestoreChanBackupRequest) GetMultiChanBackup() []byte { - if x != nil { - if x, ok := x.Backup.(*RestoreChanBackupRequest_MultiChanBackup); ok { - return x.MultiChanBackup - } + if x, ok := x.GetBackup().(*RestoreChanBackupRequest_MultiChanBackup); ok { + return x.MultiChanBackup } return nil } @@ -16104,18 +16480,21 @@ func (*RestoreChanBackupRequest_ChanBackups) isRestoreChanBackupRequest_Backup() func (*RestoreChanBackupRequest_MultiChanBackup) isRestoreChanBackupRequest_Backup() {} type RestoreBackupResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The number of channels successfully restored. - NumRestored uint32 `protobuf:"varint,1,opt,name=num_restored,json=numRestored,proto3" json:"num_restored,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The number of channels successfully restored. + NumRestored uint32 `protobuf:"varint,1,opt,name=num_restored,json=numRestored,proto3" json:"num_restored,omitempty"` } func (x *RestoreBackupResponse) Reset() { *x = RestoreBackupResponse{} - mi := &file_lightning_proto_msgTypes[180] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[178] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *RestoreBackupResponse) String() string { @@ -16125,8 +16504,8 @@ func (x *RestoreBackupResponse) String() string { func (*RestoreBackupResponse) ProtoMessage() {} func (x *RestoreBackupResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[180] - if x != nil { + mi := &file_lightning_proto_msgTypes[178] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -16138,7 +16517,7 @@ func (x *RestoreBackupResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RestoreBackupResponse.ProtoReflect.Descriptor instead. func (*RestoreBackupResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{180} + return file_lightning_proto_rawDescGZIP(), []int{178} } func (x *RestoreBackupResponse) GetNumRestored() uint32 { @@ -16149,16 +16528,18 @@ func (x *RestoreBackupResponse) GetNumRestored() uint32 { } type ChannelBackupSubscription struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *ChannelBackupSubscription) Reset() { *x = ChannelBackupSubscription{} - mi := &file_lightning_proto_msgTypes[181] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[179] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChannelBackupSubscription) String() string { @@ -16168,8 +16549,8 @@ func (x *ChannelBackupSubscription) String() string { func (*ChannelBackupSubscription) ProtoMessage() {} func (x *ChannelBackupSubscription) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[181] - if x != nil { + mi := &file_lightning_proto_msgTypes[179] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -16181,21 +16562,24 @@ func (x *ChannelBackupSubscription) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelBackupSubscription.ProtoReflect.Descriptor instead. func (*ChannelBackupSubscription) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{181} + return file_lightning_proto_rawDescGZIP(), []int{179} } type VerifyChanBackupResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - ChanPoints []string `protobuf:"bytes,1,rep,name=chan_points,json=chanPoints,proto3" json:"chan_points,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ChanPoints []string `protobuf:"bytes,1,rep,name=chan_points,json=chanPoints,proto3" json:"chan_points,omitempty"` } func (x *VerifyChanBackupResponse) Reset() { *x = VerifyChanBackupResponse{} - mi := &file_lightning_proto_msgTypes[182] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[180] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *VerifyChanBackupResponse) String() string { @@ -16205,8 +16589,8 @@ func (x *VerifyChanBackupResponse) String() string { func (*VerifyChanBackupResponse) ProtoMessage() {} func (x *VerifyChanBackupResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[182] - if x != nil { + mi := &file_lightning_proto_msgTypes[180] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -16218,7 +16602,7 @@ func (x *VerifyChanBackupResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyChanBackupResponse.ProtoReflect.Descriptor instead. func (*VerifyChanBackupResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{182} + return file_lightning_proto_rawDescGZIP(), []int{180} } func (x *VerifyChanBackupResponse) GetChanPoints() []string { @@ -16229,20 +16613,23 @@ func (x *VerifyChanBackupResponse) GetChanPoints() []string { } type MacaroonPermission struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The entity a permission grants access to. Entity string `protobuf:"bytes,1,opt,name=entity,proto3" json:"entity,omitempty"` // The action that is granted. - Action string `protobuf:"bytes,2,opt,name=action,proto3" json:"action,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Action string `protobuf:"bytes,2,opt,name=action,proto3" json:"action,omitempty"` } func (x *MacaroonPermission) Reset() { *x = MacaroonPermission{} - mi := &file_lightning_proto_msgTypes[183] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[181] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *MacaroonPermission) String() string { @@ -16252,8 +16639,8 @@ func (x *MacaroonPermission) String() string { func (*MacaroonPermission) ProtoMessage() {} func (x *MacaroonPermission) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[183] - if x != nil { + mi := &file_lightning_proto_msgTypes[181] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -16265,7 +16652,7 @@ func (x *MacaroonPermission) ProtoReflect() protoreflect.Message { // Deprecated: Use MacaroonPermission.ProtoReflect.Descriptor instead. func (*MacaroonPermission) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{183} + return file_lightning_proto_rawDescGZIP(), []int{181} } func (x *MacaroonPermission) GetEntity() string { @@ -16283,7 +16670,10 @@ func (x *MacaroonPermission) GetAction() string { } type BakeMacaroonRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The list of permissions the new macaroon should grant. Permissions []*MacaroonPermission `protobuf:"bytes,1,rep,name=permissions,proto3" json:"permissions,omitempty"` // The root key ID used to create the macaroon, must be a positive integer. @@ -16291,15 +16681,15 @@ type BakeMacaroonRequest struct { // Informs the RPC on whether to allow external permissions that LND is not // aware of. AllowExternalPermissions bool `protobuf:"varint,3,opt,name=allow_external_permissions,json=allowExternalPermissions,proto3" json:"allow_external_permissions,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *BakeMacaroonRequest) Reset() { *x = BakeMacaroonRequest{} - mi := &file_lightning_proto_msgTypes[184] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[182] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *BakeMacaroonRequest) String() string { @@ -16309,8 +16699,8 @@ func (x *BakeMacaroonRequest) String() string { func (*BakeMacaroonRequest) ProtoMessage() {} func (x *BakeMacaroonRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[184] - if x != nil { + mi := &file_lightning_proto_msgTypes[182] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -16322,7 +16712,7 @@ func (x *BakeMacaroonRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BakeMacaroonRequest.ProtoReflect.Descriptor instead. func (*BakeMacaroonRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{184} + return file_lightning_proto_rawDescGZIP(), []int{182} } func (x *BakeMacaroonRequest) GetPermissions() []*MacaroonPermission { @@ -16347,18 +16737,21 @@ func (x *BakeMacaroonRequest) GetAllowExternalPermissions() bool { } type BakeMacaroonResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The hex encoded macaroon, serialized in binary format. - Macaroon string `protobuf:"bytes,1,opt,name=macaroon,proto3" json:"macaroon,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The hex encoded macaroon, serialized in binary format. + Macaroon string `protobuf:"bytes,1,opt,name=macaroon,proto3" json:"macaroon,omitempty"` } func (x *BakeMacaroonResponse) Reset() { *x = BakeMacaroonResponse{} - mi := &file_lightning_proto_msgTypes[185] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[183] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *BakeMacaroonResponse) String() string { @@ -16368,8 +16761,8 @@ func (x *BakeMacaroonResponse) String() string { func (*BakeMacaroonResponse) ProtoMessage() {} func (x *BakeMacaroonResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[185] - if x != nil { + mi := &file_lightning_proto_msgTypes[183] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -16381,7 +16774,7 @@ func (x *BakeMacaroonResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BakeMacaroonResponse.ProtoReflect.Descriptor instead. func (*BakeMacaroonResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{185} + return file_lightning_proto_rawDescGZIP(), []int{183} } func (x *BakeMacaroonResponse) GetMacaroon() string { @@ -16392,16 +16785,18 @@ func (x *BakeMacaroonResponse) GetMacaroon() string { } type ListMacaroonIDsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *ListMacaroonIDsRequest) Reset() { *x = ListMacaroonIDsRequest{} - mi := &file_lightning_proto_msgTypes[186] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[184] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListMacaroonIDsRequest) String() string { @@ -16411,8 +16806,8 @@ func (x *ListMacaroonIDsRequest) String() string { func (*ListMacaroonIDsRequest) ProtoMessage() {} func (x *ListMacaroonIDsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[186] - if x != nil { + mi := &file_lightning_proto_msgTypes[184] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -16424,22 +16819,25 @@ func (x *ListMacaroonIDsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListMacaroonIDsRequest.ProtoReflect.Descriptor instead. func (*ListMacaroonIDsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{186} + return file_lightning_proto_rawDescGZIP(), []int{184} } type ListMacaroonIDsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The list of root key IDs that are in use. - RootKeyIds []uint64 `protobuf:"varint,1,rep,packed,name=root_key_ids,json=rootKeyIds,proto3" json:"root_key_ids,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The list of root key IDs that are in use. + RootKeyIds []uint64 `protobuf:"varint,1,rep,packed,name=root_key_ids,json=rootKeyIds,proto3" json:"root_key_ids,omitempty"` } func (x *ListMacaroonIDsResponse) Reset() { *x = ListMacaroonIDsResponse{} - mi := &file_lightning_proto_msgTypes[187] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[185] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListMacaroonIDsResponse) String() string { @@ -16449,8 +16847,8 @@ func (x *ListMacaroonIDsResponse) String() string { func (*ListMacaroonIDsResponse) ProtoMessage() {} func (x *ListMacaroonIDsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[187] - if x != nil { + mi := &file_lightning_proto_msgTypes[185] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -16462,7 +16860,7 @@ func (x *ListMacaroonIDsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListMacaroonIDsResponse.ProtoReflect.Descriptor instead. func (*ListMacaroonIDsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{187} + return file_lightning_proto_rawDescGZIP(), []int{185} } func (x *ListMacaroonIDsResponse) GetRootKeyIds() []uint64 { @@ -16473,18 +16871,21 @@ func (x *ListMacaroonIDsResponse) GetRootKeyIds() []uint64 { } type DeleteMacaroonIDRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The root key ID to be removed. - RootKeyId uint64 `protobuf:"varint,1,opt,name=root_key_id,json=rootKeyId,proto3" json:"root_key_id,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The root key ID to be removed. + RootKeyId uint64 `protobuf:"varint,1,opt,name=root_key_id,json=rootKeyId,proto3" json:"root_key_id,omitempty"` } func (x *DeleteMacaroonIDRequest) Reset() { *x = DeleteMacaroonIDRequest{} - mi := &file_lightning_proto_msgTypes[188] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[186] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *DeleteMacaroonIDRequest) String() string { @@ -16494,8 +16895,8 @@ func (x *DeleteMacaroonIDRequest) String() string { func (*DeleteMacaroonIDRequest) ProtoMessage() {} func (x *DeleteMacaroonIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[188] - if x != nil { + mi := &file_lightning_proto_msgTypes[186] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -16507,7 +16908,7 @@ func (x *DeleteMacaroonIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteMacaroonIDRequest.ProtoReflect.Descriptor instead. func (*DeleteMacaroonIDRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{188} + return file_lightning_proto_rawDescGZIP(), []int{186} } func (x *DeleteMacaroonIDRequest) GetRootKeyId() uint64 { @@ -16518,18 +16919,21 @@ func (x *DeleteMacaroonIDRequest) GetRootKeyId() uint64 { } type DeleteMacaroonIDResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // A boolean indicates that the deletion is successful. - Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // A boolean indicates that the deletion is successful. + Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` } func (x *DeleteMacaroonIDResponse) Reset() { *x = DeleteMacaroonIDResponse{} - mi := &file_lightning_proto_msgTypes[189] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[187] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *DeleteMacaroonIDResponse) String() string { @@ -16539,8 +16943,8 @@ func (x *DeleteMacaroonIDResponse) String() string { func (*DeleteMacaroonIDResponse) ProtoMessage() {} func (x *DeleteMacaroonIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[189] - if x != nil { + mi := &file_lightning_proto_msgTypes[187] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -16552,7 +16956,7 @@ func (x *DeleteMacaroonIDResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteMacaroonIDResponse.ProtoReflect.Descriptor instead. func (*DeleteMacaroonIDResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{189} + return file_lightning_proto_rawDescGZIP(), []int{187} } func (x *DeleteMacaroonIDResponse) GetDeleted() bool { @@ -16563,18 +16967,21 @@ func (x *DeleteMacaroonIDResponse) GetDeleted() bool { } type MacaroonPermissionList struct { - state protoimpl.MessageState `protogen:"open.v1"` - // A list of macaroon permissions. - Permissions []*MacaroonPermission `protobuf:"bytes,1,rep,name=permissions,proto3" json:"permissions,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // A list of macaroon permissions. + Permissions []*MacaroonPermission `protobuf:"bytes,1,rep,name=permissions,proto3" json:"permissions,omitempty"` } func (x *MacaroonPermissionList) Reset() { *x = MacaroonPermissionList{} - mi := &file_lightning_proto_msgTypes[190] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[188] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *MacaroonPermissionList) String() string { @@ -16584,8 +16991,8 @@ func (x *MacaroonPermissionList) String() string { func (*MacaroonPermissionList) ProtoMessage() {} func (x *MacaroonPermissionList) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[190] - if x != nil { + mi := &file_lightning_proto_msgTypes[188] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -16597,7 +17004,7 @@ func (x *MacaroonPermissionList) ProtoReflect() protoreflect.Message { // Deprecated: Use MacaroonPermissionList.ProtoReflect.Descriptor instead. func (*MacaroonPermissionList) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{190} + return file_lightning_proto_rawDescGZIP(), []int{188} } func (x *MacaroonPermissionList) GetPermissions() []*MacaroonPermission { @@ -16608,16 +17015,18 @@ func (x *MacaroonPermissionList) GetPermissions() []*MacaroonPermission { } type ListPermissionsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *ListPermissionsRequest) Reset() { *x = ListPermissionsRequest{} - mi := &file_lightning_proto_msgTypes[191] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[189] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListPermissionsRequest) String() string { @@ -16627,8 +17036,8 @@ func (x *ListPermissionsRequest) String() string { func (*ListPermissionsRequest) ProtoMessage() {} func (x *ListPermissionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[191] - if x != nil { + mi := &file_lightning_proto_msgTypes[189] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -16640,23 +17049,26 @@ func (x *ListPermissionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPermissionsRequest.ProtoReflect.Descriptor instead. func (*ListPermissionsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{191} + return file_lightning_proto_rawDescGZIP(), []int{189} } type ListPermissionsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // A map between all RPC method URIs and their required macaroon permissions to // access them. - MethodPermissions map[string]*MacaroonPermissionList `protobuf:"bytes,1,rep,name=method_permissions,json=methodPermissions,proto3" json:"method_permissions,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + MethodPermissions map[string]*MacaroonPermissionList `protobuf:"bytes,1,rep,name=method_permissions,json=methodPermissions,proto3" json:"method_permissions,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *ListPermissionsResponse) Reset() { *x = ListPermissionsResponse{} - mi := &file_lightning_proto_msgTypes[192] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[190] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListPermissionsResponse) String() string { @@ -16666,8 +17078,8 @@ func (x *ListPermissionsResponse) String() string { func (*ListPermissionsResponse) ProtoMessage() {} func (x *ListPermissionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[192] - if x != nil { + mi := &file_lightning_proto_msgTypes[190] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -16679,7 +17091,7 @@ func (x *ListPermissionsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPermissionsResponse.ProtoReflect.Descriptor instead. func (*ListPermissionsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{192} + return file_lightning_proto_rawDescGZIP(), []int{190} } func (x *ListPermissionsResponse) GetMethodPermissions() map[string]*MacaroonPermissionList { @@ -16690,7 +17102,10 @@ func (x *ListPermissionsResponse) GetMethodPermissions() map[string]*MacaroonPer } type Failure struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Failure code as defined in the Lightning spec Code Failure_FailureCode `protobuf:"varint,1,opt,name=code,proto3,enum=lnrpc.Failure_FailureCode" json:"code,omitempty"` // An optional channel update message. @@ -16707,16 +17122,16 @@ type Failure struct { // the failure message. Position zero is the sender node. FailureSourceIndex uint32 `protobuf:"varint,8,opt,name=failure_source_index,json=failureSourceIndex,proto3" json:"failure_source_index,omitempty"` // A failure type-dependent block height. - Height uint32 `protobuf:"varint,9,opt,name=height,proto3" json:"height,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Height uint32 `protobuf:"varint,9,opt,name=height,proto3" json:"height,omitempty"` } func (x *Failure) Reset() { *x = Failure{} - mi := &file_lightning_proto_msgTypes[193] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[191] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *Failure) String() string { @@ -16726,8 +17141,8 @@ func (x *Failure) String() string { func (*Failure) ProtoMessage() {} func (x *Failure) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[193] - if x != nil { + mi := &file_lightning_proto_msgTypes[191] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -16739,7 +17154,7 @@ func (x *Failure) ProtoReflect() protoreflect.Message { // Deprecated: Use Failure.ProtoReflect.Descriptor instead. func (*Failure) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{193} + return file_lightning_proto_rawDescGZIP(), []int{191} } func (x *Failure) GetCode() Failure_FailureCode { @@ -16799,7 +17214,10 @@ func (x *Failure) GetHeight() uint32 { } type ChannelUpdate struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The signature that validates the announced data and proves the ownership // of node id. Signature []byte `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` @@ -16845,15 +17263,15 @@ type ChannelUpdate struct { // cover these new fields, and ensure we're able to make upgrades to the // network in a forwards compatible manner. ExtraOpaqueData []byte `protobuf:"bytes,12,opt,name=extra_opaque_data,json=extraOpaqueData,proto3" json:"extra_opaque_data,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ChannelUpdate) Reset() { *x = ChannelUpdate{} - mi := &file_lightning_proto_msgTypes[194] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[192] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChannelUpdate) String() string { @@ -16863,8 +17281,8 @@ func (x *ChannelUpdate) String() string { func (*ChannelUpdate) ProtoMessage() {} func (x *ChannelUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[194] - if x != nil { + mi := &file_lightning_proto_msgTypes[192] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -16876,7 +17294,7 @@ func (x *ChannelUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelUpdate.ProtoReflect.Descriptor instead. func (*ChannelUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{194} + return file_lightning_proto_rawDescGZIP(), []int{192} } func (x *ChannelUpdate) GetSignature() []byte { @@ -16964,19 +17382,22 @@ func (x *ChannelUpdate) GetExtraOpaqueData() []byte { } type MacaroonId struct { - state protoimpl.MessageState `protogen:"open.v1"` - Nonce []byte `protobuf:"bytes,1,opt,name=nonce,proto3" json:"nonce,omitempty"` - StorageId []byte `protobuf:"bytes,2,opt,name=storageId,proto3" json:"storageId,omitempty"` - Ops []*Op `protobuf:"bytes,3,rep,name=ops,proto3" json:"ops,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Nonce []byte `protobuf:"bytes,1,opt,name=nonce,proto3" json:"nonce,omitempty"` + StorageId []byte `protobuf:"bytes,2,opt,name=storageId,proto3" json:"storageId,omitempty"` + Ops []*Op `protobuf:"bytes,3,rep,name=ops,proto3" json:"ops,omitempty"` } func (x *MacaroonId) Reset() { *x = MacaroonId{} - mi := &file_lightning_proto_msgTypes[195] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[193] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *MacaroonId) String() string { @@ -16986,8 +17407,8 @@ func (x *MacaroonId) String() string { func (*MacaroonId) ProtoMessage() {} func (x *MacaroonId) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[195] - if x != nil { + mi := &file_lightning_proto_msgTypes[193] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -16999,7 +17420,7 @@ func (x *MacaroonId) ProtoReflect() protoreflect.Message { // Deprecated: Use MacaroonId.ProtoReflect.Descriptor instead. func (*MacaroonId) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{195} + return file_lightning_proto_rawDescGZIP(), []int{193} } func (x *MacaroonId) GetNonce() []byte { @@ -17024,18 +17445,21 @@ func (x *MacaroonId) GetOps() []*Op { } type Op struct { - state protoimpl.MessageState `protogen:"open.v1"` - Entity string `protobuf:"bytes,1,opt,name=entity,proto3" json:"entity,omitempty"` - Actions []string `protobuf:"bytes,2,rep,name=actions,proto3" json:"actions,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Entity string `protobuf:"bytes,1,opt,name=entity,proto3" json:"entity,omitempty"` + Actions []string `protobuf:"bytes,2,rep,name=actions,proto3" json:"actions,omitempty"` } func (x *Op) Reset() { *x = Op{} - mi := &file_lightning_proto_msgTypes[196] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[194] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *Op) String() string { @@ -17045,8 +17469,8 @@ func (x *Op) String() string { func (*Op) ProtoMessage() {} func (x *Op) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[196] - if x != nil { + mi := &file_lightning_proto_msgTypes[194] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -17058,7 +17482,7 @@ func (x *Op) ProtoReflect() protoreflect.Message { // Deprecated: Use Op.ProtoReflect.Descriptor instead. func (*Op) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{196} + return file_lightning_proto_rawDescGZIP(), []int{194} } func (x *Op) GetEntity() string { @@ -17076,7 +17500,10 @@ func (x *Op) GetActions() []string { } type CheckMacPermRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The macaroon to check permissions for, serialized in binary format. For // a macaroon to be valid, it must have been issued by lnd, must succeed all // caveat conditions, and must contain all of the permissions specified in @@ -17105,15 +17532,15 @@ type CheckMacPermRequest struct { // of permissions must be non-empty for the check to actually perform a // permission check. CheckDefaultPermsFromFullMethod bool `protobuf:"varint,4,opt,name=check_default_perms_from_full_method,json=checkDefaultPermsFromFullMethod,proto3" json:"check_default_perms_from_full_method,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *CheckMacPermRequest) Reset() { *x = CheckMacPermRequest{} - mi := &file_lightning_proto_msgTypes[197] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[195] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *CheckMacPermRequest) String() string { @@ -17123,8 +17550,8 @@ func (x *CheckMacPermRequest) String() string { func (*CheckMacPermRequest) ProtoMessage() {} func (x *CheckMacPermRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[197] - if x != nil { + mi := &file_lightning_proto_msgTypes[195] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -17136,7 +17563,7 @@ func (x *CheckMacPermRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckMacPermRequest.ProtoReflect.Descriptor instead. func (*CheckMacPermRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{197} + return file_lightning_proto_rawDescGZIP(), []int{195} } func (x *CheckMacPermRequest) GetMacaroon() []byte { @@ -17168,17 +17595,20 @@ func (x *CheckMacPermRequest) GetCheckDefaultPermsFromFullMethod() bool { } type CheckMacPermResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Valid bool `protobuf:"varint,1,opt,name=valid,proto3" json:"valid,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Valid bool `protobuf:"varint,1,opt,name=valid,proto3" json:"valid,omitempty"` } func (x *CheckMacPermResponse) Reset() { *x = CheckMacPermResponse{} - mi := &file_lightning_proto_msgTypes[198] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[196] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *CheckMacPermResponse) String() string { @@ -17188,8 +17618,8 @@ func (x *CheckMacPermResponse) String() string { func (*CheckMacPermResponse) ProtoMessage() {} func (x *CheckMacPermResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[198] - if x != nil { + mi := &file_lightning_proto_msgTypes[196] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -17201,7 +17631,7 @@ func (x *CheckMacPermResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckMacPermResponse.ProtoReflect.Descriptor instead. func (*CheckMacPermResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{198} + return file_lightning_proto_rawDescGZIP(), []int{196} } func (x *CheckMacPermResponse) GetValid() bool { @@ -17212,7 +17642,10 @@ func (x *CheckMacPermResponse) GetValid() bool { } type RPCMiddlewareRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The unique ID of the intercepted original gRPC request. Useful for mapping // request to response when implementing full duplex message interception. For // streaming requests, this will be the same ID for all incoming and outgoing @@ -17233,7 +17666,7 @@ type RPCMiddlewareRequest struct { // server) or denied (=return error to client). Intercepted responses can also // be replaced/overwritten. // - // Types that are valid to be assigned to InterceptType: + // Types that are assignable to InterceptType: // // *RPCMiddlewareRequest_StreamAuth // *RPCMiddlewareRequest_Request @@ -17252,16 +17685,16 @@ type RPCMiddlewareRequest struct { // metadata](https://grpc.io/docs/guides/metadata/). Context values are not // propagated via gRPC and so we send any pairs along explicitly here so that // the interceptor can access them. - MetadataPairs map[string]*MetadataValues `protobuf:"bytes,9,rep,name=metadata_pairs,json=metadataPairs,proto3" json:"metadata_pairs,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + MetadataPairs map[string]*MetadataValues `protobuf:"bytes,9,rep,name=metadata_pairs,json=metadataPairs,proto3" json:"metadata_pairs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *RPCMiddlewareRequest) Reset() { *x = RPCMiddlewareRequest{} - mi := &file_lightning_proto_msgTypes[199] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[197] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *RPCMiddlewareRequest) String() string { @@ -17271,8 +17704,8 @@ func (x *RPCMiddlewareRequest) String() string { func (*RPCMiddlewareRequest) ProtoMessage() {} func (x *RPCMiddlewareRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[199] - if x != nil { + mi := &file_lightning_proto_msgTypes[197] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -17284,7 +17717,7 @@ func (x *RPCMiddlewareRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RPCMiddlewareRequest.ProtoReflect.Descriptor instead. func (*RPCMiddlewareRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{199} + return file_lightning_proto_rawDescGZIP(), []int{197} } func (x *RPCMiddlewareRequest) GetRequestId() uint64 { @@ -17308,45 +17741,37 @@ func (x *RPCMiddlewareRequest) GetCustomCaveatCondition() string { return "" } -func (x *RPCMiddlewareRequest) GetInterceptType() isRPCMiddlewareRequest_InterceptType { - if x != nil { - return x.InterceptType +func (m *RPCMiddlewareRequest) GetInterceptType() isRPCMiddlewareRequest_InterceptType { + if m != nil { + return m.InterceptType } return nil } func (x *RPCMiddlewareRequest) GetStreamAuth() *StreamAuth { - if x != nil { - if x, ok := x.InterceptType.(*RPCMiddlewareRequest_StreamAuth); ok { - return x.StreamAuth - } + if x, ok := x.GetInterceptType().(*RPCMiddlewareRequest_StreamAuth); ok { + return x.StreamAuth } return nil } func (x *RPCMiddlewareRequest) GetRequest() *RPCMessage { - if x != nil { - if x, ok := x.InterceptType.(*RPCMiddlewareRequest_Request); ok { - return x.Request - } + if x, ok := x.GetInterceptType().(*RPCMiddlewareRequest_Request); ok { + return x.Request } return nil } func (x *RPCMiddlewareRequest) GetResponse() *RPCMessage { - if x != nil { - if x, ok := x.InterceptType.(*RPCMiddlewareRequest_Response); ok { - return x.Response - } + if x, ok := x.GetInterceptType().(*RPCMiddlewareRequest_Response); ok { + return x.Response } return nil } func (x *RPCMiddlewareRequest) GetRegComplete() bool { - if x != nil { - if x, ok := x.InterceptType.(*RPCMiddlewareRequest_RegComplete); ok { - return x.RegComplete - } + if x, ok := x.GetInterceptType().(*RPCMiddlewareRequest_RegComplete); ok { + return x.RegComplete } return false } @@ -17414,18 +17839,21 @@ func (*RPCMiddlewareRequest_Response) isRPCMiddlewareRequest_InterceptType() {} func (*RPCMiddlewareRequest_RegComplete) isRPCMiddlewareRequest_InterceptType() {} type MetadataValues struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The set of metadata values that correspond to the metadata key. - Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The set of metadata values that correspond to the metadata key. + Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` } func (x *MetadataValues) Reset() { *x = MetadataValues{} - mi := &file_lightning_proto_msgTypes[200] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[198] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *MetadataValues) String() string { @@ -17435,8 +17863,8 @@ func (x *MetadataValues) String() string { func (*MetadataValues) ProtoMessage() {} func (x *MetadataValues) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[200] - if x != nil { + mi := &file_lightning_proto_msgTypes[198] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -17448,7 +17876,7 @@ func (x *MetadataValues) ProtoReflect() protoreflect.Message { // Deprecated: Use MetadataValues.ProtoReflect.Descriptor instead. func (*MetadataValues) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{200} + return file_lightning_proto_rawDescGZIP(), []int{198} } func (x *MetadataValues) GetValues() []string { @@ -17459,20 +17887,23 @@ func (x *MetadataValues) GetValues() []string { } type StreamAuth struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The full URI (in the format /./MethodName, for // example /lnrpc.Lightning/GetInfo) of the streaming RPC method that was just // established. MethodFullUri string `protobuf:"bytes,1,opt,name=method_full_uri,json=methodFullUri,proto3" json:"method_full_uri,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *StreamAuth) Reset() { *x = StreamAuth{} - mi := &file_lightning_proto_msgTypes[201] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[199] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *StreamAuth) String() string { @@ -17482,8 +17913,8 @@ func (x *StreamAuth) String() string { func (*StreamAuth) ProtoMessage() {} func (x *StreamAuth) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[201] - if x != nil { + mi := &file_lightning_proto_msgTypes[199] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -17495,7 +17926,7 @@ func (x *StreamAuth) ProtoReflect() protoreflect.Message { // Deprecated: Use StreamAuth.ProtoReflect.Descriptor instead. func (*StreamAuth) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{201} + return file_lightning_proto_rawDescGZIP(), []int{199} } func (x *StreamAuth) GetMethodFullUri() string { @@ -17506,7 +17937,10 @@ func (x *StreamAuth) GetMethodFullUri() string { } type RPCMessage struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The full URI (in the format /./MethodName, for // example /lnrpc.Lightning/GetInfo) of the RPC method the message was sent // to/from. @@ -17523,16 +17957,16 @@ type RPCMessage struct { // Indicates that the response from lnd was an error, not a gRPC response. If // this is set to true then the type_name contains the string "error" and // serialized contains the error string. - IsError bool `protobuf:"varint,5,opt,name=is_error,json=isError,proto3" json:"is_error,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + IsError bool `protobuf:"varint,5,opt,name=is_error,json=isError,proto3" json:"is_error,omitempty"` } func (x *RPCMessage) Reset() { *x = RPCMessage{} - mi := &file_lightning_proto_msgTypes[202] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[200] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *RPCMessage) String() string { @@ -17542,8 +17976,8 @@ func (x *RPCMessage) String() string { func (*RPCMessage) ProtoMessage() {} func (x *RPCMessage) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[202] - if x != nil { + mi := &file_lightning_proto_msgTypes[200] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -17555,7 +17989,7 @@ func (x *RPCMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use RPCMessage.ProtoReflect.Descriptor instead. func (*RPCMessage) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{202} + return file_lightning_proto_rawDescGZIP(), []int{200} } func (x *RPCMessage) GetMethodFullUri() string { @@ -17594,7 +18028,10 @@ func (x *RPCMessage) GetIsError() bool { } type RPCMiddlewareResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The request message ID this response refers to. Must always be set when // giving feedback to an intercept but is ignored for the initial registration // message. @@ -17603,20 +18040,20 @@ type RPCMiddlewareResponse struct { // registration message that identifies the middleware and after that only // feedback messages to requests sent to the middleware. // - // Types that are valid to be assigned to MiddlewareMessage: + // Types that are assignable to MiddlewareMessage: // // *RPCMiddlewareResponse_Register // *RPCMiddlewareResponse_Feedback MiddlewareMessage isRPCMiddlewareResponse_MiddlewareMessage `protobuf_oneof:"middleware_message"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *RPCMiddlewareResponse) Reset() { *x = RPCMiddlewareResponse{} - mi := &file_lightning_proto_msgTypes[203] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[201] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *RPCMiddlewareResponse) String() string { @@ -17626,8 +18063,8 @@ func (x *RPCMiddlewareResponse) String() string { func (*RPCMiddlewareResponse) ProtoMessage() {} func (x *RPCMiddlewareResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[203] - if x != nil { + mi := &file_lightning_proto_msgTypes[201] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -17639,7 +18076,7 @@ func (x *RPCMiddlewareResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RPCMiddlewareResponse.ProtoReflect.Descriptor instead. func (*RPCMiddlewareResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{203} + return file_lightning_proto_rawDescGZIP(), []int{201} } func (x *RPCMiddlewareResponse) GetRefMsgId() uint64 { @@ -17649,27 +18086,23 @@ func (x *RPCMiddlewareResponse) GetRefMsgId() uint64 { return 0 } -func (x *RPCMiddlewareResponse) GetMiddlewareMessage() isRPCMiddlewareResponse_MiddlewareMessage { - if x != nil { - return x.MiddlewareMessage +func (m *RPCMiddlewareResponse) GetMiddlewareMessage() isRPCMiddlewareResponse_MiddlewareMessage { + if m != nil { + return m.MiddlewareMessage } return nil } func (x *RPCMiddlewareResponse) GetRegister() *MiddlewareRegistration { - if x != nil { - if x, ok := x.MiddlewareMessage.(*RPCMiddlewareResponse_Register); ok { - return x.Register - } + if x, ok := x.GetMiddlewareMessage().(*RPCMiddlewareResponse_Register); ok { + return x.Register } return nil } func (x *RPCMiddlewareResponse) GetFeedback() *InterceptFeedback { - if x != nil { - if x, ok := x.MiddlewareMessage.(*RPCMiddlewareResponse_Feedback); ok { - return x.Feedback - } + if x, ok := x.GetMiddlewareMessage().(*RPCMiddlewareResponse_Feedback); ok { + return x.Feedback } return nil } @@ -17702,7 +18135,10 @@ func (*RPCMiddlewareResponse_Register) isRPCMiddlewareResponse_MiddlewareMessage func (*RPCMiddlewareResponse_Feedback) isRPCMiddlewareResponse_MiddlewareMessage() {} type MiddlewareRegistration struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The name of the middleware to register. The name should be as informative // as possible and is logged on registration. MiddlewareName string `protobuf:"bytes,1,opt,name=middleware_name,json=middlewareName,proto3" json:"middleware_name,omitempty"` @@ -17720,16 +18156,16 @@ type MiddlewareRegistration struct { // forwarded to the middleware but the middleware isn't allowed to alter any of // the responses. // NOTE: Cannot be used at the same time as custom_macaroon_caveat_name. - ReadOnlyMode bool `protobuf:"varint,3,opt,name=read_only_mode,json=readOnlyMode,proto3" json:"read_only_mode,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + ReadOnlyMode bool `protobuf:"varint,3,opt,name=read_only_mode,json=readOnlyMode,proto3" json:"read_only_mode,omitempty"` } func (x *MiddlewareRegistration) Reset() { *x = MiddlewareRegistration{} - mi := &file_lightning_proto_msgTypes[204] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[202] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *MiddlewareRegistration) String() string { @@ -17739,8 +18175,8 @@ func (x *MiddlewareRegistration) String() string { func (*MiddlewareRegistration) ProtoMessage() {} func (x *MiddlewareRegistration) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[204] - if x != nil { + mi := &file_lightning_proto_msgTypes[202] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -17752,7 +18188,7 @@ func (x *MiddlewareRegistration) ProtoReflect() protoreflect.Message { // Deprecated: Use MiddlewareRegistration.ProtoReflect.Descriptor instead. func (*MiddlewareRegistration) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{204} + return file_lightning_proto_rawDescGZIP(), []int{202} } func (x *MiddlewareRegistration) GetMiddlewareName() string { @@ -17777,7 +18213,10 @@ func (x *MiddlewareRegistration) GetReadOnlyMode() bool { } type InterceptFeedback struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The error to return to the user. If this is non-empty, the incoming gRPC // stream/request is aborted and the error is returned to the gRPC client. If // this value is empty, it means the middleware accepts the stream/request/ @@ -17791,15 +18230,15 @@ type InterceptFeedback struct { // If the replace_response field is set to true, this field must contain the // binary serialized gRPC message in the protobuf format. ReplacementSerialized []byte `protobuf:"bytes,3,opt,name=replacement_serialized,json=replacementSerialized,proto3" json:"replacement_serialized,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *InterceptFeedback) Reset() { *x = InterceptFeedback{} - mi := &file_lightning_proto_msgTypes[205] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[203] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *InterceptFeedback) String() string { @@ -17809,8 +18248,8 @@ func (x *InterceptFeedback) String() string { func (*InterceptFeedback) ProtoMessage() {} func (x *InterceptFeedback) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[205] - if x != nil { + mi := &file_lightning_proto_msgTypes[203] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -17822,7 +18261,7 @@ func (x *InterceptFeedback) ProtoReflect() protoreflect.Message { // Deprecated: Use InterceptFeedback.ProtoReflect.Descriptor instead. func (*InterceptFeedback) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{205} + return file_lightning_proto_rawDescGZIP(), []int{203} } func (x *InterceptFeedback) GetError() string { @@ -17847,12 +18286,15 @@ func (x *InterceptFeedback) GetReplacementSerialized() []byte { } type PendingChannelsResponse_PendingChannel struct { - state protoimpl.MessageState `protogen:"open.v1"` - RemoteNodePub string `protobuf:"bytes,1,opt,name=remote_node_pub,json=remoteNodePub,proto3" json:"remote_node_pub,omitempty"` - ChannelPoint string `protobuf:"bytes,2,opt,name=channel_point,json=channelPoint,proto3" json:"channel_point,omitempty"` - Capacity int64 `protobuf:"varint,3,opt,name=capacity,proto3" json:"capacity,omitempty"` - LocalBalance int64 `protobuf:"varint,4,opt,name=local_balance,json=localBalance,proto3" json:"local_balance,omitempty"` - RemoteBalance int64 `protobuf:"varint,5,opt,name=remote_balance,json=remoteBalance,proto3" json:"remote_balance,omitempty"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RemoteNodePub string `protobuf:"bytes,1,opt,name=remote_node_pub,json=remoteNodePub,proto3" json:"remote_node_pub,omitempty"` + ChannelPoint string `protobuf:"bytes,2,opt,name=channel_point,json=channelPoint,proto3" json:"channel_point,omitempty"` + Capacity int64 `protobuf:"varint,3,opt,name=capacity,proto3" json:"capacity,omitempty"` + LocalBalance int64 `protobuf:"varint,4,opt,name=local_balance,json=localBalance,proto3" json:"local_balance,omitempty"` + RemoteBalance int64 `protobuf:"varint,5,opt,name=remote_balance,json=remoteBalance,proto3" json:"remote_balance,omitempty"` // The minimum satoshis this node is required to reserve in its // balance. LocalChanReserveSat int64 `protobuf:"varint,6,opt,name=local_chan_reserve_sat,json=localChanReserveSat,proto3" json:"local_chan_reserve_sat,omitempty"` @@ -17875,15 +18317,15 @@ type PendingChannelsResponse_PendingChannel struct { Memo string `protobuf:"bytes,13,opt,name=memo,proto3" json:"memo,omitempty"` // Custom channel data that might be populated in custom channels. CustomChannelData []byte `protobuf:"bytes,34,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *PendingChannelsResponse_PendingChannel) Reset() { *x = PendingChannelsResponse_PendingChannel{} - mi := &file_lightning_proto_msgTypes[212] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[210] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PendingChannelsResponse_PendingChannel) String() string { @@ -17893,8 +18335,8 @@ func (x *PendingChannelsResponse_PendingChannel) String() string { func (*PendingChannelsResponse_PendingChannel) ProtoMessage() {} func (x *PendingChannelsResponse_PendingChannel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[212] - if x != nil { + mi := &file_lightning_proto_msgTypes[210] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -17906,7 +18348,7 @@ func (x *PendingChannelsResponse_PendingChannel) ProtoReflect() protoreflect.Mes // Deprecated: Use PendingChannelsResponse_PendingChannel.ProtoReflect.Descriptor instead. func (*PendingChannelsResponse_PendingChannel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{90, 0} + return file_lightning_proto_rawDescGZIP(), []int{89, 0} } func (x *PendingChannelsResponse_PendingChannel) GetRemoteNodePub() string { @@ -18008,7 +18450,10 @@ func (x *PendingChannelsResponse_PendingChannel) GetCustomChannelData() []byte { } type PendingChannelsResponse_PendingOpenChannel struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The pending channel Channel *PendingChannelsResponse_PendingChannel `protobuf:"bytes,1,opt,name=channel,proto3" json:"channel,omitempty"` // The amount calculated to be paid in fees for the current set of @@ -18050,15 +18495,15 @@ type PendingChannelsResponse_PendingOpenChannel struct { // The confirmation height records the block height at which the funding // transaction was first confirmed. ConfirmationHeight uint32 `protobuf:"varint,8,opt,name=confirmation_height,json=confirmationHeight,proto3" json:"confirmation_height,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *PendingChannelsResponse_PendingOpenChannel) Reset() { *x = PendingChannelsResponse_PendingOpenChannel{} - mi := &file_lightning_proto_msgTypes[213] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[211] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PendingChannelsResponse_PendingOpenChannel) String() string { @@ -18068,8 +18513,8 @@ func (x *PendingChannelsResponse_PendingOpenChannel) String() string { func (*PendingChannelsResponse_PendingOpenChannel) ProtoMessage() {} func (x *PendingChannelsResponse_PendingOpenChannel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[213] - if x != nil { + mi := &file_lightning_proto_msgTypes[211] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -18081,7 +18526,7 @@ func (x *PendingChannelsResponse_PendingOpenChannel) ProtoReflect() protoreflect // Deprecated: Use PendingChannelsResponse_PendingOpenChannel.ProtoReflect.Descriptor instead. func (*PendingChannelsResponse_PendingOpenChannel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{90, 1} + return file_lightning_proto_rawDescGZIP(), []int{89, 1} } func (x *PendingChannelsResponse_PendingOpenChannel) GetChannel() *PendingChannelsResponse_PendingChannel { @@ -18134,7 +18579,10 @@ func (x *PendingChannelsResponse_PendingOpenChannel) GetConfirmationHeight() uin } type PendingChannelsResponse_WaitingCloseChannel struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The pending channel waiting for closing tx to confirm Channel *PendingChannelsResponse_PendingChannel `protobuf:"bytes,1,opt,name=channel,proto3" json:"channel,omitempty"` // The balance in satoshis encumbered in this channel @@ -18147,27 +18595,15 @@ type PendingChannelsResponse_WaitingCloseChannel struct { // The raw hex encoded bytes of the closing transaction. Included if // include_raw_tx in the request is true. ClosingTxHex string `protobuf:"bytes,5,opt,name=closing_tx_hex,json=closingTxHex,proto3" json:"closing_tx_hex,omitempty"` - // Remaining number of confirmations until the channel closure is - // considered final and removed from waiting close. Channel closes - // require multiple confirmations for reorg protection — the exact - // number scales with channel capacity. A closing transaction that - // gets reorganized out of the chain resets this counter. When the - // closing transaction is not yet confirmed, this value equals the - // total number of confirmations required. - BlocksTilCloseConfirmed uint32 `protobuf:"varint,6,opt,name=blocks_til_close_confirmed,json=blocksTilCloseConfirmed,proto3" json:"blocks_til_close_confirmed,omitempty"` - // The block height at which the closing transaction was first confirmed. - // This will be zero if the closing transaction has not yet confirmed, or - // if this information is not available for older channels. - CloseHeight uint32 `protobuf:"varint,7,opt,name=close_height,json=closeHeight,proto3" json:"close_height,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *PendingChannelsResponse_WaitingCloseChannel) Reset() { *x = PendingChannelsResponse_WaitingCloseChannel{} - mi := &file_lightning_proto_msgTypes[214] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[212] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PendingChannelsResponse_WaitingCloseChannel) String() string { @@ -18177,8 +18613,8 @@ func (x *PendingChannelsResponse_WaitingCloseChannel) String() string { func (*PendingChannelsResponse_WaitingCloseChannel) ProtoMessage() {} func (x *PendingChannelsResponse_WaitingCloseChannel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[214] - if x != nil { + mi := &file_lightning_proto_msgTypes[212] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -18190,7 +18626,7 @@ func (x *PendingChannelsResponse_WaitingCloseChannel) ProtoReflect() protoreflec // Deprecated: Use PendingChannelsResponse_WaitingCloseChannel.ProtoReflect.Descriptor instead. func (*PendingChannelsResponse_WaitingCloseChannel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{90, 2} + return file_lightning_proto_rawDescGZIP(), []int{89, 2} } func (x *PendingChannelsResponse_WaitingCloseChannel) GetChannel() *PendingChannelsResponse_PendingChannel { @@ -18228,22 +18664,11 @@ func (x *PendingChannelsResponse_WaitingCloseChannel) GetClosingTxHex() string { return "" } -func (x *PendingChannelsResponse_WaitingCloseChannel) GetBlocksTilCloseConfirmed() uint32 { - if x != nil { - return x.BlocksTilCloseConfirmed - } - return 0 -} - -func (x *PendingChannelsResponse_WaitingCloseChannel) GetCloseHeight() uint32 { - if x != nil { - return x.CloseHeight - } - return 0 -} - type PendingChannelsResponse_Commitments struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Hash of the local version of the commitment tx. LocalTxid string `protobuf:"bytes,1,opt,name=local_txid,json=localTxid,proto3" json:"local_txid,omitempty"` // Hash of the remote version of the commitment tx. @@ -18259,15 +18684,15 @@ type PendingChannelsResponse_Commitments struct { // The amount in satoshis calculated to be paid in fees for the remote // pending commitment. RemotePendingCommitFeeSat uint64 `protobuf:"varint,6,opt,name=remote_pending_commit_fee_sat,json=remotePendingCommitFeeSat,proto3" json:"remote_pending_commit_fee_sat,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *PendingChannelsResponse_Commitments) Reset() { *x = PendingChannelsResponse_Commitments{} - mi := &file_lightning_proto_msgTypes[215] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[213] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PendingChannelsResponse_Commitments) String() string { @@ -18277,8 +18702,8 @@ func (x *PendingChannelsResponse_Commitments) String() string { func (*PendingChannelsResponse_Commitments) ProtoMessage() {} func (x *PendingChannelsResponse_Commitments) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[215] - if x != nil { + mi := &file_lightning_proto_msgTypes[213] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -18290,7 +18715,7 @@ func (x *PendingChannelsResponse_Commitments) ProtoReflect() protoreflect.Messag // Deprecated: Use PendingChannelsResponse_Commitments.ProtoReflect.Descriptor instead. func (*PendingChannelsResponse_Commitments) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{90, 3} + return file_lightning_proto_rawDescGZIP(), []int{89, 3} } func (x *PendingChannelsResponse_Commitments) GetLocalTxid() string { @@ -18336,20 +18761,23 @@ func (x *PendingChannelsResponse_Commitments) GetRemotePendingCommitFeeSat() uin } type PendingChannelsResponse_ClosedChannel struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The pending channel to be closed Channel *PendingChannelsResponse_PendingChannel `protobuf:"bytes,1,opt,name=channel,proto3" json:"channel,omitempty"` // The transaction id of the closing transaction - ClosingTxid string `protobuf:"bytes,2,opt,name=closing_txid,json=closingTxid,proto3" json:"closing_txid,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + ClosingTxid string `protobuf:"bytes,2,opt,name=closing_txid,json=closingTxid,proto3" json:"closing_txid,omitempty"` } func (x *PendingChannelsResponse_ClosedChannel) Reset() { *x = PendingChannelsResponse_ClosedChannel{} - mi := &file_lightning_proto_msgTypes[216] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[214] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PendingChannelsResponse_ClosedChannel) String() string { @@ -18359,8 +18787,8 @@ func (x *PendingChannelsResponse_ClosedChannel) String() string { func (*PendingChannelsResponse_ClosedChannel) ProtoMessage() {} func (x *PendingChannelsResponse_ClosedChannel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[216] - if x != nil { + mi := &file_lightning_proto_msgTypes[214] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -18372,7 +18800,7 @@ func (x *PendingChannelsResponse_ClosedChannel) ProtoReflect() protoreflect.Mess // Deprecated: Use PendingChannelsResponse_ClosedChannel.ProtoReflect.Descriptor instead. func (*PendingChannelsResponse_ClosedChannel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{90, 4} + return file_lightning_proto_rawDescGZIP(), []int{89, 4} } func (x *PendingChannelsResponse_ClosedChannel) GetChannel() *PendingChannelsResponse_PendingChannel { @@ -18390,7 +18818,10 @@ func (x *PendingChannelsResponse_ClosedChannel) GetClosingTxid() string { } type PendingChannelsResponse_ForceClosedChannel struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The pending channel to be force closed Channel *PendingChannelsResponse_PendingChannel `protobuf:"bytes,1,opt,name=channel,proto3" json:"channel,omitempty"` // The transaction id of the closing transaction @@ -18407,15 +18838,15 @@ type PendingChannelsResponse_ForceClosedChannel struct { RecoveredBalance int64 `protobuf:"varint,6,opt,name=recovered_balance,json=recoveredBalance,proto3" json:"recovered_balance,omitempty"` PendingHtlcs []*PendingHTLC `protobuf:"bytes,8,rep,name=pending_htlcs,json=pendingHtlcs,proto3" json:"pending_htlcs,omitempty"` Anchor PendingChannelsResponse_ForceClosedChannel_AnchorState `protobuf:"varint,9,opt,name=anchor,proto3,enum=lnrpc.PendingChannelsResponse_ForceClosedChannel_AnchorState" json:"anchor,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *PendingChannelsResponse_ForceClosedChannel) Reset() { *x = PendingChannelsResponse_ForceClosedChannel{} - mi := &file_lightning_proto_msgTypes[217] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[215] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PendingChannelsResponse_ForceClosedChannel) String() string { @@ -18425,8 +18856,8 @@ func (x *PendingChannelsResponse_ForceClosedChannel) String() string { func (*PendingChannelsResponse_ForceClosedChannel) ProtoMessage() {} func (x *PendingChannelsResponse_ForceClosedChannel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[217] - if x != nil { + mi := &file_lightning_proto_msgTypes[215] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -18438,7 +18869,7 @@ func (x *PendingChannelsResponse_ForceClosedChannel) ProtoReflect() protoreflect // Deprecated: Use PendingChannelsResponse_ForceClosedChannel.ProtoReflect.Descriptor instead. func (*PendingChannelsResponse_ForceClosedChannel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{90, 5} + return file_lightning_proto_rawDescGZIP(), []int{89, 5} } func (x *PendingChannelsResponse_ForceClosedChannel) GetChannel() *PendingChannelsResponse_PendingChannel { @@ -18499,1664 +18930,3276 @@ func (x *PendingChannelsResponse_ForceClosedChannel) GetAnchor() PendingChannels var File_lightning_proto protoreflect.FileDescriptor -const file_lightning_proto_rawDesc = "" + - "\n" + - "\x0flightning.proto\x12\x05lnrpc\"U\n" + - "\x1bLookupHtlcResolutionRequest\x12\x17\n" + - "\achan_id\x18\x01 \x01(\x04R\x06chanId\x12\x1d\n" + - "\n" + - "htlc_index\x18\x02 \x01(\x04R\thtlcIndex\"T\n" + - "\x1cLookupHtlcResolutionResponse\x12\x18\n" + - "\asettled\x18\x01 \x01(\bR\asettled\x12\x1a\n" + - "\boffchain\x18\x02 \x01(\bR\boffchain\" \n" + - "\x1eSubscribeCustomMessagesRequest\"K\n" + - "\rCustomMessage\x12\x12\n" + - "\x04peer\x18\x01 \x01(\fR\x04peer\x12\x12\n" + - "\x04type\x18\x02 \x01(\rR\x04type\x12\x12\n" + - "\x04data\x18\x03 \x01(\fR\x04data\"V\n" + - "\x18SendCustomMessageRequest\x12\x12\n" + - "\x04peer\x18\x01 \x01(\fR\x04peer\x12\x12\n" + - "\x04type\x18\x02 \x01(\rR\x04type\x12\x12\n" + - "\x04data\x18\x03 \x01(\fR\x04data\"3\n" + - "\x19SendCustomMessageResponse\x12\x16\n" + - "\x06status\x18\x01 \x01(\tR\x06status\"\x1f\n" + - "\x1dSubscribeOnionMessagesRequest\"\xdd\x02\n" + - "\x12OnionMessageUpdate\x12\x12\n" + - "\x04peer\x18\x01 \x01(\fR\x04peer\x12\x19\n" + - "\bpath_key\x18\x02 \x01(\fR\apathKey\x12\x14\n" + - "\x05onion\x18\x03 \x01(\fR\x05onion\x121\n" + - "\n" + - "reply_path\x18\x04 \x01(\v2\x12.lnrpc.BlindedPathR\treplyPath\x128\n" + - "\x18encrypted_recipient_data\x18\x05 \x01(\fR\x16encryptedRecipientData\x12S\n" + - "\x0ecustom_records\x18\x06 \x03(\v2,.lnrpc.OnionMessageUpdate.CustomRecordsEntryR\rcustomRecords\x1a@\n" + - "\x12CustomRecordsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\x04R\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\fR\x05value:\x028\x01\"^\n" + - "\x17SendOnionMessageRequest\x12\x12\n" + - "\x04peer\x18\x01 \x01(\fR\x04peer\x12\x19\n" + - "\bpath_key\x18\x02 \x01(\fR\apathKey\x12\x14\n" + - "\x05onion\x18\x03 \x01(\fR\x05onion\"2\n" + - "\x18SendOnionMessageResponse\x12\x16\n" + - "\x06status\x18\x01 \x01(\tR\x06status\"\xe6\x01\n" + - "\x04Utxo\x125\n" + - "\faddress_type\x18\x01 \x01(\x0e2\x12.lnrpc.AddressTypeR\vaddressType\x12\x18\n" + - "\aaddress\x18\x02 \x01(\tR\aaddress\x12\x1d\n" + - "\n" + - "amount_sat\x18\x03 \x01(\x03R\tamountSat\x12\x1b\n" + - "\tpk_script\x18\x04 \x01(\tR\bpkScript\x12+\n" + - "\boutpoint\x18\x05 \x01(\v2\x0f.lnrpc.OutPointR\boutpoint\x12$\n" + - "\rconfirmations\x18\x06 \x01(\x03R\rconfirmations\"\xe0\x01\n" + - "\fOutputDetail\x128\n" + - "\voutput_type\x18\x01 \x01(\x0e2\x17.lnrpc.OutputScriptTypeR\n" + - "outputType\x12\x18\n" + - "\aaddress\x18\x02 \x01(\tR\aaddress\x12\x1b\n" + - "\tpk_script\x18\x03 \x01(\tR\bpkScript\x12!\n" + - "\foutput_index\x18\x04 \x01(\x03R\voutputIndex\x12\x16\n" + - "\x06amount\x18\x05 \x01(\x03R\x06amount\x12$\n" + - "\x0eis_our_address\x18\x06 \x01(\bR\fisOurAddress\"\xce\x03\n" + - "\vTransaction\x12\x17\n" + - "\atx_hash\x18\x01 \x01(\tR\x06txHash\x12\x16\n" + - "\x06amount\x18\x02 \x01(\x03R\x06amount\x12+\n" + - "\x11num_confirmations\x18\x03 \x01(\x05R\x10numConfirmations\x12\x1d\n" + - "\n" + - "block_hash\x18\x04 \x01(\tR\tblockHash\x12!\n" + - "\fblock_height\x18\x05 \x01(\x05R\vblockHeight\x12\x1d\n" + - "\n" + - "time_stamp\x18\x06 \x01(\x03R\ttimeStamp\x12\x1d\n" + - "\n" + - "total_fees\x18\a \x01(\x03R\ttotalFees\x12)\n" + - "\x0edest_addresses\x18\b \x03(\tB\x02\x18\x01R\rdestAddresses\x12:\n" + - "\x0eoutput_details\x18\v \x03(\v2\x13.lnrpc.OutputDetailR\routputDetails\x12\x1c\n" + - "\n" + - "raw_tx_hex\x18\t \x01(\tR\brawTxHex\x12\x14\n" + - "\x05label\x18\n" + - " \x01(\tR\x05label\x12F\n" + - "\x12previous_outpoints\x18\f \x03(\v2\x17.lnrpc.PreviousOutPointR\x11previousOutpoints\"\xc2\x01\n" + - "\x16GetTransactionsRequest\x12!\n" + - "\fstart_height\x18\x01 \x01(\x05R\vstartHeight\x12\x1d\n" + - "\n" + - "end_height\x18\x02 \x01(\x05R\tendHeight\x12\x18\n" + - "\aaccount\x18\x03 \x01(\tR\aaccount\x12!\n" + - "\findex_offset\x18\x04 \x01(\rR\vindexOffset\x12)\n" + - "\x10max_transactions\x18\x05 \x01(\rR\x0fmaxTransactions\"\x8c\x01\n" + - "\x12TransactionDetails\x126\n" + - "\ftransactions\x18\x01 \x03(\v2\x12.lnrpc.TransactionR\ftransactions\x12\x1d\n" + - "\n" + - "last_index\x18\x02 \x01(\x04R\tlastIndex\x12\x1f\n" + - "\vfirst_index\x18\x03 \x01(\x04R\n" + - "firstIndex\"h\n" + - "\bFeeLimit\x12\x16\n" + - "\x05fixed\x18\x01 \x01(\x03H\x00R\x05fixed\x12\x1f\n" + - "\n" + - "fixed_msat\x18\x03 \x01(\x03H\x00R\tfixedMsat\x12\x1a\n" + - "\apercent\x18\x02 \x01(\x03H\x00R\apercentB\a\n" + - "\x05limit\"\xec\x04\n" + - "\x14ChannelAcceptRequest\x12\x1f\n" + - "\vnode_pubkey\x18\x01 \x01(\fR\n" + - "nodePubkey\x12\x1d\n" + - "\n" + - "chain_hash\x18\x02 \x01(\fR\tchainHash\x12&\n" + - "\x0fpending_chan_id\x18\x03 \x01(\fR\rpendingChanId\x12\x1f\n" + - "\vfunding_amt\x18\x04 \x01(\x04R\n" + - "fundingAmt\x12\x19\n" + - "\bpush_amt\x18\x05 \x01(\x04R\apushAmt\x12\x1d\n" + - "\n" + - "dust_limit\x18\x06 \x01(\x04R\tdustLimit\x12-\n" + - "\x13max_value_in_flight\x18\a \x01(\x04R\x10maxValueInFlight\x12'\n" + - "\x0fchannel_reserve\x18\b \x01(\x04R\x0echannelReserve\x12\x19\n" + - "\bmin_htlc\x18\t \x01(\x04R\aminHtlc\x12\x1c\n" + - "\n" + - "fee_per_kw\x18\n" + - " \x01(\x04R\bfeePerKw\x12\x1b\n" + - "\tcsv_delay\x18\v \x01(\rR\bcsvDelay\x12,\n" + - "\x12max_accepted_htlcs\x18\f \x01(\rR\x10maxAcceptedHtlcs\x12#\n" + - "\rchannel_flags\x18\r \x01(\rR\fchannelFlags\x12>\n" + - "\x0fcommitment_type\x18\x0e \x01(\x0e2\x15.lnrpc.CommitmentTypeR\x0ecommitmentType\x12&\n" + - "\x0fwants_zero_conf\x18\x0f \x01(\bR\rwantsZeroConf\x12(\n" + - "\x10wants_scid_alias\x18\x10 \x01(\bR\x0ewantsScidAlias\"\x90\x03\n" + - "\x15ChannelAcceptResponse\x12\x16\n" + - "\x06accept\x18\x01 \x01(\bR\x06accept\x12&\n" + - "\x0fpending_chan_id\x18\x02 \x01(\fR\rpendingChanId\x12\x14\n" + - "\x05error\x18\x03 \x01(\tR\x05error\x12)\n" + - "\x10upfront_shutdown\x18\x04 \x01(\tR\x0fupfrontShutdown\x12\x1b\n" + - "\tcsv_delay\x18\x05 \x01(\rR\bcsvDelay\x12\x1f\n" + - "\vreserve_sat\x18\x06 \x01(\x04R\n" + - "reserveSat\x12+\n" + - "\x12in_flight_max_msat\x18\a \x01(\x04R\x0finFlightMaxMsat\x12$\n" + - "\x0emax_htlc_count\x18\b \x01(\rR\fmaxHtlcCount\x12\x1e\n" + - "\vmin_htlc_in\x18\t \x01(\x04R\tminHtlcIn\x12(\n" + - "\x10min_accept_depth\x18\n" + - " \x01(\rR\x0eminAcceptDepth\x12\x1b\n" + - "\tzero_conf\x18\v \x01(\bR\bzeroConf\"\x9d\x01\n" + - "\fChannelPoint\x12.\n" + - "\x12funding_txid_bytes\x18\x01 \x01(\fH\x00R\x10fundingTxidBytes\x12*\n" + - "\x10funding_txid_str\x18\x02 \x01(\tH\x00R\x0efundingTxidStr\x12!\n" + - "\foutput_index\x18\x03 \x01(\rR\voutputIndexB\x0e\n" + - "\ffunding_txid\"g\n" + - "\bOutPoint\x12\x1d\n" + - "\n" + - "txid_bytes\x18\x01 \x01(\fR\ttxidBytes\x12\x19\n" + - "\btxid_str\x18\x02 \x01(\tR\atxidStr\x12!\n" + - "\foutput_index\x18\x03 \x01(\rR\voutputIndex\"R\n" + - "\x10PreviousOutPoint\x12\x1a\n" + - "\boutpoint\x18\x01 \x01(\tR\boutpoint\x12\"\n" + - "\ris_our_output\x18\x02 \x01(\bR\visOurOutput\">\n" + - "\x10LightningAddress\x12\x16\n" + - "\x06pubkey\x18\x01 \x01(\tR\x06pubkey\x12\x12\n" + - "\x04host\x18\x02 \x01(\tR\x04host\"\x90\x03\n" + - "\x12EstimateFeeRequest\x12O\n" + - "\fAddrToAmount\x18\x01 \x03(\v2+.lnrpc.EstimateFeeRequest.AddrToAmountEntryR\fAddrToAmount\x12\x1f\n" + - "\vtarget_conf\x18\x02 \x01(\x05R\n" + - "targetConf\x12\x1b\n" + - "\tmin_confs\x18\x03 \x01(\x05R\bminConfs\x12+\n" + - "\x11spend_unconfirmed\x18\x04 \x01(\bR\x10spendUnconfirmed\x12T\n" + - "\x17coin_selection_strategy\x18\x05 \x01(\x0e2\x1c.lnrpc.CoinSelectionStrategyR\x15coinSelectionStrategy\x12'\n" + - "\x06inputs\x18\x06 \x03(\v2\x0f.lnrpc.OutPointR\x06inputs\x1a?\n" + - "\x11AddrToAmountEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\x03R\x05value:\x028\x01\"\xb0\x01\n" + - "\x13EstimateFeeResponse\x12\x17\n" + - "\afee_sat\x18\x01 \x01(\x03R\x06feeSat\x123\n" + - "\x14feerate_sat_per_byte\x18\x02 \x01(\x03B\x02\x18\x01R\x11feerateSatPerByte\x12\"\n" + - "\rsat_per_vbyte\x18\x03 \x01(\x04R\vsatPerVbyte\x12'\n" + - "\x06inputs\x18\x04 \x03(\v2\x0f.lnrpc.OutPointR\x06inputs\"\xc1\x03\n" + - "\x0fSendManyRequest\x12L\n" + - "\fAddrToAmount\x18\x01 \x03(\v2(.lnrpc.SendManyRequest.AddrToAmountEntryR\fAddrToAmount\x12\x1f\n" + - "\vtarget_conf\x18\x03 \x01(\x05R\n" + - "targetConf\x12\"\n" + - "\rsat_per_vbyte\x18\x04 \x01(\x04R\vsatPerVbyte\x12$\n" + - "\fsat_per_byte\x18\x05 \x01(\x03B\x02\x18\x01R\n" + - "satPerByte\x12\x14\n" + - "\x05label\x18\x06 \x01(\tR\x05label\x12\x1b\n" + - "\tmin_confs\x18\a \x01(\x05R\bminConfs\x12+\n" + - "\x11spend_unconfirmed\x18\b \x01(\bR\x10spendUnconfirmed\x12T\n" + - "\x17coin_selection_strategy\x18\t \x01(\x0e2\x1c.lnrpc.CoinSelectionStrategyR\x15coinSelectionStrategy\x1a?\n" + - "\x11AddrToAmountEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\x03R\x05value:\x028\x01\"&\n" + - "\x10SendManyResponse\x12\x12\n" + - "\x04txid\x18\x01 \x01(\tR\x04txid\"\xa9\x03\n" + - "\x10SendCoinsRequest\x12\x12\n" + - "\x04addr\x18\x01 \x01(\tR\x04addr\x12\x16\n" + - "\x06amount\x18\x02 \x01(\x03R\x06amount\x12\x1f\n" + - "\vtarget_conf\x18\x03 \x01(\x05R\n" + - "targetConf\x12\"\n" + - "\rsat_per_vbyte\x18\x04 \x01(\x04R\vsatPerVbyte\x12$\n" + - "\fsat_per_byte\x18\x05 \x01(\x03B\x02\x18\x01R\n" + - "satPerByte\x12\x19\n" + - "\bsend_all\x18\x06 \x01(\bR\asendAll\x12\x14\n" + - "\x05label\x18\a \x01(\tR\x05label\x12\x1b\n" + - "\tmin_confs\x18\b \x01(\x05R\bminConfs\x12+\n" + - "\x11spend_unconfirmed\x18\t \x01(\bR\x10spendUnconfirmed\x12T\n" + - "\x17coin_selection_strategy\x18\n" + - " \x01(\x0e2\x1c.lnrpc.CoinSelectionStrategyR\x15coinSelectionStrategy\x12-\n" + - "\toutpoints\x18\v \x03(\v2\x0f.lnrpc.OutPointR\toutpoints\"'\n" + - "\x11SendCoinsResponse\x12\x12\n" + - "\x04txid\x18\x01 \x01(\tR\x04txid\"h\n" + - "\x12ListUnspentRequest\x12\x1b\n" + - "\tmin_confs\x18\x01 \x01(\x05R\bminConfs\x12\x1b\n" + - "\tmax_confs\x18\x02 \x01(\x05R\bmaxConfs\x12\x18\n" + - "\aaccount\x18\x03 \x01(\tR\aaccount\"8\n" + - "\x13ListUnspentResponse\x12!\n" + - "\x05utxos\x18\x01 \x03(\v2\v.lnrpc.UtxoR\x05utxos\"U\n" + - "\x11NewAddressRequest\x12&\n" + - "\x04type\x18\x01 \x01(\x0e2\x12.lnrpc.AddressTypeR\x04type\x12\x18\n" + - "\aaccount\x18\x02 \x01(\tR\aaccount\".\n" + - "\x12NewAddressResponse\x12\x18\n" + - "\aaddress\x18\x01 \x01(\tR\aaddress\"G\n" + - "\x12SignMessageRequest\x12\x10\n" + - "\x03msg\x18\x01 \x01(\fR\x03msg\x12\x1f\n" + - "\vsingle_hash\x18\x02 \x01(\bR\n" + - "singleHash\"3\n" + - "\x13SignMessageResponse\x12\x1c\n" + - "\tsignature\x18\x01 \x01(\tR\tsignature\"F\n" + - "\x14VerifyMessageRequest\x12\x10\n" + - "\x03msg\x18\x01 \x01(\fR\x03msg\x12\x1c\n" + - "\tsignature\x18\x02 \x01(\tR\tsignature\"E\n" + - "\x15VerifyMessageResponse\x12\x14\n" + - "\x05valid\x18\x01 \x01(\bR\x05valid\x12\x16\n" + - "\x06pubkey\x18\x02 \x01(\tR\x06pubkey\"o\n" + - "\x12ConnectPeerRequest\x12+\n" + - "\x04addr\x18\x01 \x01(\v2\x17.lnrpc.LightningAddressR\x04addr\x12\x12\n" + - "\x04perm\x18\x02 \x01(\bR\x04perm\x12\x18\n" + - "\atimeout\x18\x03 \x01(\x04R\atimeout\"-\n" + - "\x13ConnectPeerResponse\x12\x16\n" + - "\x06status\x18\x01 \x01(\tR\x06status\"0\n" + - "\x15DisconnectPeerRequest\x12\x17\n" + - "\apub_key\x18\x01 \x01(\tR\x06pubKey\"0\n" + - "\x16DisconnectPeerResponse\x12\x16\n" + - "\x06status\x18\x01 \x01(\tR\x06status\"\xa3\x02\n" + - "\x04HTLC\x12\x1a\n" + - "\bincoming\x18\x01 \x01(\bR\bincoming\x12\x16\n" + - "\x06amount\x18\x02 \x01(\x03R\x06amount\x12\x1b\n" + - "\thash_lock\x18\x03 \x01(\fR\bhashLock\x12+\n" + - "\x11expiration_height\x18\x04 \x01(\rR\x10expirationHeight\x12\x1d\n" + - "\n" + - "htlc_index\x18\x05 \x01(\x04R\thtlcIndex\x12-\n" + - "\x12forwarding_channel\x18\x06 \x01(\x04R\x11forwardingChannel\x122\n" + - "\x15forwarding_htlc_index\x18\a \x01(\x04R\x13forwardingHtlcIndex\x12\x1b\n" + - "\tlocked_in\x18\b \x01(\bR\blockedIn\"\x84\x02\n" + - "\x12ChannelConstraints\x12\x1b\n" + - "\tcsv_delay\x18\x01 \x01(\rR\bcsvDelay\x12(\n" + - "\x10chan_reserve_sat\x18\x02 \x01(\x04R\x0echanReserveSat\x12$\n" + - "\x0edust_limit_sat\x18\x03 \x01(\x04R\fdustLimitSat\x12/\n" + - "\x14max_pending_amt_msat\x18\x04 \x01(\x04R\x11maxPendingAmtMsat\x12\"\n" + - "\rmin_htlc_msat\x18\x05 \x01(\x04R\vminHtlcMsat\x12,\n" + - "\x12max_accepted_htlcs\x18\x06 \x01(\rR\x10maxAcceptedHtlcs\"\xdd\v\n" + - "\aChannel\x12\x16\n" + - "\x06active\x18\x01 \x01(\bR\x06active\x12#\n" + - "\rremote_pubkey\x18\x02 \x01(\tR\fremotePubkey\x12#\n" + - "\rchannel_point\x18\x03 \x01(\tR\fchannelPoint\x12\x1b\n" + - "\achan_id\x18\x04 \x01(\x04B\x020\x01R\x06chanId\x12\x1a\n" + - "\bcapacity\x18\x05 \x01(\x03R\bcapacity\x12#\n" + - "\rlocal_balance\x18\x06 \x01(\x03R\flocalBalance\x12%\n" + - "\x0eremote_balance\x18\a \x01(\x03R\rremoteBalance\x12\x1d\n" + - "\n" + - "commit_fee\x18\b \x01(\x03R\tcommitFee\x12#\n" + - "\rcommit_weight\x18\t \x01(\x03R\fcommitWeight\x12\x1c\n" + - "\n" + - "fee_per_kw\x18\n" + - " \x01(\x03R\bfeePerKw\x12+\n" + - "\x11unsettled_balance\x18\v \x01(\x03R\x10unsettledBalance\x12.\n" + - "\x13total_satoshis_sent\x18\f \x01(\x03R\x11totalSatoshisSent\x126\n" + - "\x17total_satoshis_received\x18\r \x01(\x03R\x15totalSatoshisReceived\x12\x1f\n" + - "\vnum_updates\x18\x0e \x01(\x04R\n" + - "numUpdates\x120\n" + - "\rpending_htlcs\x18\x0f \x03(\v2\v.lnrpc.HTLCR\fpendingHtlcs\x12\x1f\n" + - "\tcsv_delay\x18\x10 \x01(\rB\x02\x18\x01R\bcsvDelay\x12\x18\n" + - "\aprivate\x18\x11 \x01(\bR\aprivate\x12\x1c\n" + - "\tinitiator\x18\x12 \x01(\bR\tinitiator\x12*\n" + - "\x11chan_status_flags\x18\x13 \x01(\tR\x0fchanStatusFlags\x127\n" + - "\x16local_chan_reserve_sat\x18\x14 \x01(\x03B\x02\x18\x01R\x13localChanReserveSat\x129\n" + - "\x17remote_chan_reserve_sat\x18\x15 \x01(\x03B\x02\x18\x01R\x14remoteChanReserveSat\x12.\n" + - "\x11static_remote_key\x18\x16 \x01(\bB\x02\x18\x01R\x0fstaticRemoteKey\x12>\n" + - "\x0fcommitment_type\x18\x1a \x01(\x0e2\x15.lnrpc.CommitmentTypeR\x0ecommitmentType\x12\x1a\n" + - "\blifetime\x18\x17 \x01(\x03R\blifetime\x12\x16\n" + - "\x06uptime\x18\x18 \x01(\x03R\x06uptime\x12#\n" + - "\rclose_address\x18\x19 \x01(\tR\fcloseAddress\x12&\n" + - "\x0fpush_amount_sat\x18\x1b \x01(\x04R\rpushAmountSat\x12\x1f\n" + - "\vthaw_height\x18\x1c \x01(\rR\n" + - "thawHeight\x12F\n" + - "\x11local_constraints\x18\x1d \x01(\v2\x19.lnrpc.ChannelConstraintsR\x10localConstraints\x12H\n" + - "\x12remote_constraints\x18\x1e \x01(\v2\x19.lnrpc.ChannelConstraintsR\x11remoteConstraints\x12\x1f\n" + - "\valias_scids\x18\x1f \x03(\x04R\n" + - "aliasScids\x12\x1b\n" + - "\tzero_conf\x18 \x01(\bR\bzeroConf\x127\n" + - "\x18zero_conf_confirmed_scid\x18! \x01(\x04R\x15zeroConfConfirmedScid\x12\x1d\n" + - "\n" + - "peer_alias\x18\" \x01(\tR\tpeerAlias\x12*\n" + - "\x0fpeer_scid_alias\x18# \x01(\x04B\x020\x01R\rpeerScidAlias\x12\x12\n" + - "\x04memo\x18$ \x01(\tR\x04memo\x12.\n" + - "\x13custom_channel_data\x18% \x01(\fR\x11customChannelData\"\xdf\x01\n" + - "\x13ListChannelsRequest\x12\x1f\n" + - "\vactive_only\x18\x01 \x01(\bR\n" + - "activeOnly\x12#\n" + - "\rinactive_only\x18\x02 \x01(\bR\finactiveOnly\x12\x1f\n" + - "\vpublic_only\x18\x03 \x01(\bR\n" + - "publicOnly\x12!\n" + - "\fprivate_only\x18\x04 \x01(\bR\vprivateOnly\x12\x12\n" + - "\x04peer\x18\x05 \x01(\fR\x04peer\x12*\n" + - "\x11peer_alias_lookup\x18\x06 \x01(\bR\x0fpeerAliasLookup\"B\n" + - "\x14ListChannelsResponse\x12*\n" + - "\bchannels\x18\v \x03(\v2\x0e.lnrpc.ChannelR\bchannels\"A\n" + - "\bAliasMap\x12\x1b\n" + - "\tbase_scid\x18\x01 \x01(\x04R\bbaseScid\x12\x18\n" + - "\aaliases\x18\x02 \x03(\x04R\aaliases\"\x14\n" + - "\x12ListAliasesRequest\"E\n" + - "\x13ListAliasesResponse\x12.\n" + - "\n" + - "alias_maps\x18\x01 \x03(\v2\x0f.lnrpc.AliasMapR\taliasMaps\"\xe6\x06\n" + - "\x13ChannelCloseSummary\x12#\n" + - "\rchannel_point\x18\x01 \x01(\tR\fchannelPoint\x12\x1b\n" + - "\achan_id\x18\x02 \x01(\x04B\x020\x01R\x06chanId\x12\x1d\n" + - "\n" + - "chain_hash\x18\x03 \x01(\tR\tchainHash\x12&\n" + - "\x0fclosing_tx_hash\x18\x04 \x01(\tR\rclosingTxHash\x12#\n" + - "\rremote_pubkey\x18\x05 \x01(\tR\fremotePubkey\x12\x1a\n" + - "\bcapacity\x18\x06 \x01(\x03R\bcapacity\x12!\n" + - "\fclose_height\x18\a \x01(\rR\vcloseHeight\x12'\n" + - "\x0fsettled_balance\x18\b \x01(\x03R\x0esettledBalance\x12.\n" + - "\x13time_locked_balance\x18\t \x01(\x03R\x11timeLockedBalance\x12E\n" + - "\n" + - "close_type\x18\n" + - " \x01(\x0e2&.lnrpc.ChannelCloseSummary.ClosureTypeR\tcloseType\x127\n" + - "\x0eopen_initiator\x18\v \x01(\x0e2\x10.lnrpc.InitiatorR\ropenInitiator\x129\n" + - "\x0fclose_initiator\x18\f \x01(\x0e2\x10.lnrpc.InitiatorR\x0ecloseInitiator\x123\n" + - "\vresolutions\x18\r \x03(\v2\x11.lnrpc.ResolutionR\vresolutions\x12\x1f\n" + - "\valias_scids\x18\x0e \x03(\x04R\n" + - "aliasScids\x12;\n" + - "\x18zero_conf_confirmed_scid\x18\x0f \x01(\x04B\x020\x01R\x15zeroConfConfirmedScid\x12.\n" + - "\x13custom_channel_data\x18\x10 \x01(\fR\x11customChannelData\"\x8a\x01\n" + - "\vClosureType\x12\x15\n" + - "\x11COOPERATIVE_CLOSE\x10\x00\x12\x15\n" + - "\x11LOCAL_FORCE_CLOSE\x10\x01\x12\x16\n" + - "\x12REMOTE_FORCE_CLOSE\x10\x02\x12\x10\n" + - "\fBREACH_CLOSE\x10\x03\x12\x14\n" + - "\x10FUNDING_CANCELED\x10\x04\x12\r\n" + - "\tABANDONED\x10\x05\"\xeb\x01\n" + - "\n" + - "Resolution\x12>\n" + - "\x0fresolution_type\x18\x01 \x01(\x0e2\x15.lnrpc.ResolutionTypeR\x0eresolutionType\x122\n" + - "\aoutcome\x18\x02 \x01(\x0e2\x18.lnrpc.ResolutionOutcomeR\aoutcome\x12+\n" + - "\boutpoint\x18\x03 \x01(\v2\x0f.lnrpc.OutPointR\boutpoint\x12\x1d\n" + - "\n" + - "amount_sat\x18\x04 \x01(\x04R\tamountSat\x12\x1d\n" + - "\n" + - "sweep_txid\x18\x05 \x01(\tR\tsweepTxid\"\xde\x01\n" + - "\x15ClosedChannelsRequest\x12 \n" + - "\vcooperative\x18\x01 \x01(\bR\vcooperative\x12\x1f\n" + - "\vlocal_force\x18\x02 \x01(\bR\n" + - "localForce\x12!\n" + - "\fremote_force\x18\x03 \x01(\bR\vremoteForce\x12\x16\n" + - "\x06breach\x18\x04 \x01(\bR\x06breach\x12)\n" + - "\x10funding_canceled\x18\x05 \x01(\bR\x0ffundingCanceled\x12\x1c\n" + - "\tabandoned\x18\x06 \x01(\bR\tabandoned\"P\n" + - "\x16ClosedChannelsResponse\x126\n" + - "\bchannels\x18\x01 \x03(\v2\x1a.lnrpc.ChannelCloseSummaryR\bchannels\"\x8b\x05\n" + - "\x04Peer\x12\x17\n" + - "\apub_key\x18\x01 \x01(\tR\x06pubKey\x12\x18\n" + - "\aaddress\x18\x03 \x01(\tR\aaddress\x12\x1d\n" + - "\n" + - "bytes_sent\x18\x04 \x01(\x04R\tbytesSent\x12\x1d\n" + - "\n" + - "bytes_recv\x18\x05 \x01(\x04R\tbytesRecv\x12\x19\n" + - "\bsat_sent\x18\x06 \x01(\x03R\asatSent\x12\x19\n" + - "\bsat_recv\x18\a \x01(\x03R\asatRecv\x12\x18\n" + - "\ainbound\x18\b \x01(\bR\ainbound\x12\x1b\n" + - "\tping_time\x18\t \x01(\x03R\bpingTime\x121\n" + - "\tsync_type\x18\n" + - " \x01(\x0e2\x14.lnrpc.Peer.SyncTypeR\bsyncType\x125\n" + - "\bfeatures\x18\v \x03(\v2\x19.lnrpc.Peer.FeaturesEntryR\bfeatures\x12/\n" + - "\x06errors\x18\f \x03(\v2\x17.lnrpc.TimestampedErrorR\x06errors\x12\x1d\n" + - "\n" + - "flap_count\x18\r \x01(\x05R\tflapCount\x12 \n" + - "\flast_flap_ns\x18\x0e \x01(\x03R\n" + - "lastFlapNs\x12*\n" + - "\x11last_ping_payload\x18\x0f \x01(\fR\x0flastPingPayload\x1aK\n" + - "\rFeaturesEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\rR\x03key\x12$\n" + - "\x05value\x18\x02 \x01(\v2\x0e.lnrpc.FeatureR\x05value:\x028\x01\"P\n" + - "\bSyncType\x12\x10\n" + - "\fUNKNOWN_SYNC\x10\x00\x12\x0f\n" + - "\vACTIVE_SYNC\x10\x01\x12\x10\n" + - "\fPASSIVE_SYNC\x10\x02\x12\x0f\n" + - "\vPINNED_SYNC\x10\x03\"F\n" + - "\x10TimestampedError\x12\x1c\n" + - "\ttimestamp\x18\x01 \x01(\x04R\ttimestamp\x12\x14\n" + - "\x05error\x18\x02 \x01(\tR\x05error\"5\n" + - "\x10ListPeersRequest\x12!\n" + - "\flatest_error\x18\x01 \x01(\bR\vlatestError\"6\n" + - "\x11ListPeersResponse\x12!\n" + - "\x05peers\x18\x01 \x03(\v2\v.lnrpc.PeerR\x05peers\"\x17\n" + - "\x15PeerEventSubscription\"\x84\x01\n" + - "\tPeerEvent\x12\x17\n" + - "\apub_key\x18\x01 \x01(\tR\x06pubKey\x12.\n" + - "\x04type\x18\x02 \x01(\x0e2\x1a.lnrpc.PeerEvent.EventTypeR\x04type\".\n" + - "\tEventType\x12\x0f\n" + - "\vPEER_ONLINE\x10\x00\x12\x10\n" + - "\fPEER_OFFLINE\x10\x01\"\x10\n" + - "\x0eGetInfoRequest\"\xee\a\n" + - "\x0fGetInfoResponse\x12\x18\n" + - "\aversion\x18\x0e \x01(\tR\aversion\x12\x1f\n" + - "\vcommit_hash\x18\x14 \x01(\tR\n" + - "commitHash\x12'\n" + - "\x0fidentity_pubkey\x18\x01 \x01(\tR\x0eidentityPubkey\x12\x14\n" + - "\x05alias\x18\x02 \x01(\tR\x05alias\x12\x14\n" + - "\x05color\x18\x11 \x01(\tR\x05color\x120\n" + - "\x14num_pending_channels\x18\x03 \x01(\rR\x12numPendingChannels\x12.\n" + - "\x13num_active_channels\x18\x04 \x01(\rR\x11numActiveChannels\x122\n" + - "\x15num_inactive_channels\x18\x0f \x01(\rR\x13numInactiveChannels\x12\x1b\n" + - "\tnum_peers\x18\x05 \x01(\rR\bnumPeers\x12!\n" + - "\fblock_height\x18\x06 \x01(\rR\vblockHeight\x12\x1d\n" + - "\n" + - "block_hash\x18\b \x01(\tR\tblockHash\x122\n" + - "\x15best_header_timestamp\x18\r \x01(\x03R\x13bestHeaderTimestamp\x12&\n" + - "\x0fsynced_to_chain\x18\t \x01(\bR\rsyncedToChain\x12&\n" + - "\x0fsynced_to_graph\x18\x12 \x01(\bR\rsyncedToGraph\x12\x1c\n" + - "\atestnet\x18\n" + - " \x01(\bB\x02\x18\x01R\atestnet\x12$\n" + - "\x06chains\x18\x10 \x03(\v2\f.lnrpc.ChainR\x06chains\x12\x12\n" + - "\x04uris\x18\f \x03(\tR\x04uris\x12@\n" + - "\bfeatures\x18\x13 \x03(\v2$.lnrpc.GetInfoResponse.FeaturesEntryR\bfeatures\x128\n" + - "\x18require_htlc_interceptor\x18\x15 \x01(\bR\x16requireHtlcInterceptor\x12?\n" + - "\x1cstore_final_htlc_resolutions\x18\x16 \x01(\bR\x19storeFinalHtlcResolutions\x12#\n" + - "\rwallet_synced\x18\x17 \x01(\bR\fwalletSynced\x12E\n" + - "\x12graph_cache_status\x18\x18 \x01(\x0e2\x17.lnrpc.GraphCacheStatusR\x10graphCacheStatus\x1aK\n" + - "\rFeaturesEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\rR\x03key\x12$\n" + - "\x05value\x18\x02 \x01(\v2\x0e.lnrpc.FeatureR\x05value:\x028\x01J\x04\b\v\x10\f\"6\n" + - "\x13GetDebugInfoRequest\x12\x1f\n" + - "\vinclude_log\x18\x01 \x01(\bR\n" + - "includeLog\"\xa4\x01\n" + - "\x14GetDebugInfoResponse\x12?\n" + - "\x06config\x18\x01 \x03(\v2'.lnrpc.GetDebugInfoResponse.ConfigEntryR\x06config\x12\x10\n" + - "\x03log\x18\x02 \x03(\tR\x03log\x1a9\n" + - "\vConfigEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x18\n" + - "\x16GetRecoveryInfoRequest\"\x87\x01\n" + - "\x17GetRecoveryInfoResponse\x12#\n" + - "\rrecovery_mode\x18\x01 \x01(\bR\frecoveryMode\x12+\n" + - "\x11recovery_finished\x18\x02 \x01(\bR\x10recoveryFinished\x12\x1a\n" + - "\bprogress\x18\x03 \x01(\x01R\bprogress\";\n" + - "\x05Chain\x12\x18\n" + - "\x05chain\x18\x01 \x01(\tB\x02\x18\x01R\x05chain\x12\x18\n" + - "\anetwork\x18\x02 \x01(\tR\anetwork\"M\n" + - "\x11ChannelOpenUpdate\x128\n" + - "\rchannel_point\x18\x01 \x01(\v2\x13.lnrpc.ChannelPointR\fchannelPoint\"\x94\x01\n" + - "\vCloseOutput\x12\x1d\n" + - "\n" + - "amount_sat\x18\x01 \x01(\x03R\tamountSat\x12\x1b\n" + - "\tpk_script\x18\x02 \x01(\fR\bpkScript\x12\x19\n" + - "\bis_local\x18\x03 \x01(\bR\aisLocal\x12.\n" + - "\x13custom_channel_data\x18\x04 \x01(\fR\x11customChannelData\"\x9a\x02\n" + - "\x12ChannelCloseUpdate\x12!\n" + - "\fclosing_txid\x18\x01 \x01(\fR\vclosingTxid\x12\x18\n" + - "\asuccess\x18\x02 \x01(\bR\asuccess\x12@\n" + - "\x12local_close_output\x18\x03 \x01(\v2\x12.lnrpc.CloseOutputR\x10localCloseOutput\x12B\n" + - "\x13remote_close_output\x18\x04 \x01(\v2\x12.lnrpc.CloseOutputR\x11remoteCloseOutput\x12A\n" + - "\x12additional_outputs\x18\x05 \x03(\v2\x12.lnrpc.CloseOutputR\x11additionalOutputs\"\xbf\x02\n" + - "\x13CloseChannelRequest\x128\n" + - "\rchannel_point\x18\x01 \x01(\v2\x13.lnrpc.ChannelPointR\fchannelPoint\x12\x14\n" + - "\x05force\x18\x02 \x01(\bR\x05force\x12\x1f\n" + - "\vtarget_conf\x18\x03 \x01(\x05R\n" + - "targetConf\x12$\n" + - "\fsat_per_byte\x18\x04 \x01(\x03B\x02\x18\x01R\n" + - "satPerByte\x12)\n" + - "\x10delivery_address\x18\x05 \x01(\tR\x0fdeliveryAddress\x12\"\n" + - "\rsat_per_vbyte\x18\x06 \x01(\x04R\vsatPerVbyte\x12)\n" + - "\x11max_fee_per_vbyte\x18\a \x01(\x04R\x0emaxFeePerVbyte\x12\x17\n" + - "\ano_wait\x18\b \x01(\bR\x06noWait\"\xd3\x01\n" + - "\x11CloseStatusUpdate\x12;\n" + - "\rclose_pending\x18\x01 \x01(\v2\x14.lnrpc.PendingUpdateH\x00R\fclosePending\x12:\n" + - "\n" + - "chan_close\x18\x03 \x01(\v2\x19.lnrpc.ChannelCloseUpdateH\x00R\tchanClose\x12;\n" + - "\rclose_instant\x18\x04 \x01(\v2\x14.lnrpc.InstantUpdateH\x00R\fcloseInstantB\b\n" + - "\x06update\"\x90\x01\n" + - "\rPendingUpdate\x12\x12\n" + - "\x04txid\x18\x01 \x01(\fR\x04txid\x12!\n" + - "\foutput_index\x18\x02 \x01(\rR\voutputIndex\x12\"\n" + - "\rfee_per_vbyte\x18\x03 \x01(\x03R\vfeePerVbyte\x12$\n" + - "\x0elocal_close_tx\x18\x04 \x01(\bR\flocalCloseTx\";\n" + - "\rInstantUpdate\x12*\n" + - "\x11num_pending_htlcs\x18\x01 \x01(\x05R\x0fnumPendingHtlcs\"y\n" + - "\x13ReadyForPsbtFunding\x12'\n" + - "\x0ffunding_address\x18\x01 \x01(\tR\x0efundingAddress\x12%\n" + - "\x0efunding_amount\x18\x02 \x01(\x03R\rfundingAmount\x12\x12\n" + - "\x04psbt\x18\x03 \x01(\fR\x04psbt\"\xc9\x02\n" + - "\x17BatchOpenChannelRequest\x123\n" + - "\bchannels\x18\x01 \x03(\v2\x17.lnrpc.BatchOpenChannelR\bchannels\x12\x1f\n" + - "\vtarget_conf\x18\x02 \x01(\x05R\n" + - "targetConf\x12\"\n" + - "\rsat_per_vbyte\x18\x03 \x01(\x03R\vsatPerVbyte\x12\x1b\n" + - "\tmin_confs\x18\x04 \x01(\x05R\bminConfs\x12+\n" + - "\x11spend_unconfirmed\x18\x05 \x01(\bR\x10spendUnconfirmed\x12\x14\n" + - "\x05label\x18\x06 \x01(\tR\x05label\x12T\n" + - "\x17coin_selection_strategy\x18\a \x01(\x0e2\x1c.lnrpc.CoinSelectionStrategyR\x15coinSelectionStrategy\"\x89\x06\n" + - "\x10BatchOpenChannel\x12\x1f\n" + - "\vnode_pubkey\x18\x01 \x01(\fR\n" + - "nodePubkey\x120\n" + - "\x14local_funding_amount\x18\x02 \x01(\x03R\x12localFundingAmount\x12\x19\n" + - "\bpush_sat\x18\x03 \x01(\x03R\apushSat\x12\x18\n" + - "\aprivate\x18\x04 \x01(\bR\aprivate\x12\"\n" + - "\rmin_htlc_msat\x18\x05 \x01(\x03R\vminHtlcMsat\x12(\n" + - "\x10remote_csv_delay\x18\x06 \x01(\rR\x0eremoteCsvDelay\x12#\n" + - "\rclose_address\x18\a \x01(\tR\fcloseAddress\x12&\n" + - "\x0fpending_chan_id\x18\b \x01(\fR\rpendingChanId\x12>\n" + - "\x0fcommitment_type\x18\t \x01(\x0e2\x15.lnrpc.CommitmentTypeR\x0ecommitmentType\x12C\n" + - "\x1fremote_max_value_in_flight_msat\x18\n" + - " \x01(\x04R\x1aremoteMaxValueInFlightMsat\x12(\n" + - "\x10remote_max_htlcs\x18\v \x01(\rR\x0eremoteMaxHtlcs\x12\"\n" + - "\rmax_local_csv\x18\f \x01(\rR\vmaxLocalCsv\x12\x1b\n" + - "\tzero_conf\x18\r \x01(\bR\bzeroConf\x12\x1d\n" + - "\n" + - "scid_alias\x18\x0e \x01(\bR\tscidAlias\x12\x19\n" + - "\bbase_fee\x18\x0f \x01(\x04R\abaseFee\x12\x19\n" + - "\bfee_rate\x18\x10 \x01(\x04R\afeeRate\x12 \n" + - "\fuse_base_fee\x18\x11 \x01(\bR\n" + - "useBaseFee\x12 \n" + - "\fuse_fee_rate\x18\x12 \x01(\bR\n" + - "useFeeRate\x125\n" + - "\x17remote_chan_reserve_sat\x18\x13 \x01(\x04R\x14remoteChanReserveSat\x12\x12\n" + - "\x04memo\x18\x14 \x01(\tR\x04memo\"[\n" + - "\x18BatchOpenChannelResponse\x12?\n" + - "\x10pending_channels\x18\x01 \x03(\v2\x14.lnrpc.PendingUpdateR\x0fpendingChannels\"\xcb\b\n" + - "\x12OpenChannelRequest\x12\"\n" + - "\rsat_per_vbyte\x18\x01 \x01(\x04R\vsatPerVbyte\x12\x1f\n" + - "\vnode_pubkey\x18\x02 \x01(\fR\n" + - "nodePubkey\x120\n" + - "\x12node_pubkey_string\x18\x03 \x01(\tB\x02\x18\x01R\x10nodePubkeyString\x120\n" + - "\x14local_funding_amount\x18\x04 \x01(\x03R\x12localFundingAmount\x12\x19\n" + - "\bpush_sat\x18\x05 \x01(\x03R\apushSat\x12\x1f\n" + - "\vtarget_conf\x18\x06 \x01(\x05R\n" + - "targetConf\x12$\n" + - "\fsat_per_byte\x18\a \x01(\x03B\x02\x18\x01R\n" + - "satPerByte\x12\x18\n" + - "\aprivate\x18\b \x01(\bR\aprivate\x12\"\n" + - "\rmin_htlc_msat\x18\t \x01(\x03R\vminHtlcMsat\x12(\n" + - "\x10remote_csv_delay\x18\n" + - " \x01(\rR\x0eremoteCsvDelay\x12\x1b\n" + - "\tmin_confs\x18\v \x01(\x05R\bminConfs\x12+\n" + - "\x11spend_unconfirmed\x18\f \x01(\bR\x10spendUnconfirmed\x12#\n" + - "\rclose_address\x18\r \x01(\tR\fcloseAddress\x125\n" + - "\ffunding_shim\x18\x0e \x01(\v2\x12.lnrpc.FundingShimR\vfundingShim\x12C\n" + - "\x1fremote_max_value_in_flight_msat\x18\x0f \x01(\x04R\x1aremoteMaxValueInFlightMsat\x12(\n" + - "\x10remote_max_htlcs\x18\x10 \x01(\rR\x0eremoteMaxHtlcs\x12\"\n" + - "\rmax_local_csv\x18\x11 \x01(\rR\vmaxLocalCsv\x12>\n" + - "\x0fcommitment_type\x18\x12 \x01(\x0e2\x15.lnrpc.CommitmentTypeR\x0ecommitmentType\x12\x1b\n" + - "\tzero_conf\x18\x13 \x01(\bR\bzeroConf\x12\x1d\n" + - "\n" + - "scid_alias\x18\x14 \x01(\bR\tscidAlias\x12\x19\n" + - "\bbase_fee\x18\x15 \x01(\x04R\abaseFee\x12\x19\n" + - "\bfee_rate\x18\x16 \x01(\x04R\afeeRate\x12 \n" + - "\fuse_base_fee\x18\x17 \x01(\bR\n" + - "useBaseFee\x12 \n" + - "\fuse_fee_rate\x18\x18 \x01(\bR\n" + - "useFeeRate\x125\n" + - "\x17remote_chan_reserve_sat\x18\x19 \x01(\x04R\x14remoteChanReserveSat\x12\x19\n" + - "\bfund_max\x18\x1a \x01(\bR\afundMax\x12\x12\n" + - "\x04memo\x18\x1b \x01(\tR\x04memo\x12-\n" + - "\toutpoints\x18\x1c \x03(\v2\x0f.lnrpc.OutPointR\toutpoints\"\xf3\x01\n" + - "\x10OpenStatusUpdate\x129\n" + - "\fchan_pending\x18\x01 \x01(\v2\x14.lnrpc.PendingUpdateH\x00R\vchanPending\x127\n" + - "\tchan_open\x18\x03 \x01(\v2\x18.lnrpc.ChannelOpenUpdateH\x00R\bchanOpen\x129\n" + - "\tpsbt_fund\x18\x05 \x01(\v2\x1a.lnrpc.ReadyForPsbtFundingH\x00R\bpsbtFund\x12&\n" + - "\x0fpending_chan_id\x18\x04 \x01(\fR\rpendingChanIdB\b\n" + - "\x06update\"H\n" + - "\n" + - "KeyLocator\x12\x1d\n" + - "\n" + - "key_family\x18\x01 \x01(\x05R\tkeyFamily\x12\x1b\n" + - "\tkey_index\x18\x02 \x01(\x05R\bkeyIndex\"_\n" + - "\rKeyDescriptor\x12\"\n" + - "\rraw_key_bytes\x18\x01 \x01(\fR\vrawKeyBytes\x12*\n" + - "\akey_loc\x18\x02 \x01(\v2\x11.lnrpc.KeyLocatorR\x06keyLoc\"\x88\x02\n" + - "\rChanPointShim\x12\x10\n" + - "\x03amt\x18\x01 \x01(\x03R\x03amt\x122\n" + - "\n" + - "chan_point\x18\x02 \x01(\v2\x13.lnrpc.ChannelPointR\tchanPoint\x121\n" + - "\tlocal_key\x18\x03 \x01(\v2\x14.lnrpc.KeyDescriptorR\blocalKey\x12\x1d\n" + - "\n" + - "remote_key\x18\x04 \x01(\fR\tremoteKey\x12&\n" + - "\x0fpending_chan_id\x18\x05 \x01(\fR\rpendingChanId\x12\x1f\n" + - "\vthaw_height\x18\x06 \x01(\rR\n" + - "thawHeight\x12\x16\n" + - "\x06musig2\x18\a \x01(\bR\x06musig2\"n\n" + - "\bPsbtShim\x12&\n" + - "\x0fpending_chan_id\x18\x01 \x01(\fR\rpendingChanId\x12\x1b\n" + - "\tbase_psbt\x18\x02 \x01(\fR\bbasePsbt\x12\x1d\n" + - "\n" + - "no_publish\x18\x03 \x01(\bR\tnoPublish\"\x85\x01\n" + - "\vFundingShim\x12>\n" + - "\x0fchan_point_shim\x18\x01 \x01(\v2\x14.lnrpc.ChanPointShimH\x00R\rchanPointShim\x12.\n" + - "\tpsbt_shim\x18\x02 \x01(\v2\x0f.lnrpc.PsbtShimH\x00R\bpsbtShimB\x06\n" + - "\x04shim\";\n" + - "\x11FundingShimCancel\x12&\n" + - "\x0fpending_chan_id\x18\x01 \x01(\fR\rpendingChanId\"\x81\x01\n" + - "\x11FundingPsbtVerify\x12\x1f\n" + - "\vfunded_psbt\x18\x01 \x01(\fR\n" + - "fundedPsbt\x12&\n" + - "\x0fpending_chan_id\x18\x02 \x01(\fR\rpendingChanId\x12#\n" + - "\rskip_finalize\x18\x03 \x01(\bR\fskipFinalize\"\x80\x01\n" + - "\x13FundingPsbtFinalize\x12\x1f\n" + - "\vsigned_psbt\x18\x01 \x01(\fR\n" + - "signedPsbt\x12&\n" + - "\x0fpending_chan_id\x18\x02 \x01(\fR\rpendingChanId\x12 \n" + - "\ffinal_raw_tx\x18\x03 \x01(\fR\n" + - "finalRawTx\"\x99\x02\n" + - "\x14FundingTransitionMsg\x129\n" + - "\rshim_register\x18\x01 \x01(\v2\x12.lnrpc.FundingShimH\x00R\fshimRegister\x12;\n" + - "\vshim_cancel\x18\x02 \x01(\v2\x18.lnrpc.FundingShimCancelH\x00R\n" + - "shimCancel\x12;\n" + - "\vpsbt_verify\x18\x03 \x01(\v2\x18.lnrpc.FundingPsbtVerifyH\x00R\n" + - "psbtVerify\x12A\n" + - "\rpsbt_finalize\x18\x04 \x01(\v2\x1a.lnrpc.FundingPsbtFinalizeH\x00R\fpsbtFinalizeB\t\n" + - "\atrigger\"\x16\n" + - "\x14FundingStateStepResp\"\xcc\x01\n" + - "\vPendingHTLC\x12\x1a\n" + - "\bincoming\x18\x01 \x01(\bR\bincoming\x12\x16\n" + - "\x06amount\x18\x02 \x01(\x03R\x06amount\x12\x1a\n" + - "\boutpoint\x18\x03 \x01(\tR\boutpoint\x12'\n" + - "\x0fmaturity_height\x18\x04 \x01(\rR\x0ematurityHeight\x12.\n" + - "\x13blocks_til_maturity\x18\x05 \x01(\x05R\x11blocksTilMaturity\x12\x14\n" + - "\x05stage\x18\x06 \x01(\rR\x05stage\">\n" + - "\x16PendingChannelsRequest\x12$\n" + - "\x0einclude_raw_tx\x18\x01 \x01(\bR\fincludeRawTx\"\xe0\x15\n" + - "\x17PendingChannelsResponse\x12.\n" + - "\x13total_limbo_balance\x18\x01 \x01(\x03R\x11totalLimboBalance\x12e\n" + - "\x15pending_open_channels\x18\x02 \x03(\v21.lnrpc.PendingChannelsResponse.PendingOpenChannelR\x13pendingOpenChannels\x12j\n" + - "\x18pending_closing_channels\x18\x03 \x03(\v2,.lnrpc.PendingChannelsResponse.ClosedChannelB\x02\x18\x01R\x16pendingClosingChannels\x12v\n" + - "\x1epending_force_closing_channels\x18\x04 \x03(\v21.lnrpc.PendingChannelsResponse.ForceClosedChannelR\x1bpendingForceClosingChannels\x12h\n" + - "\x16waiting_close_channels\x18\x05 \x03(\v22.lnrpc.PendingChannelsResponse.WaitingCloseChannelR\x14waitingCloseChannels\x1a\xe3\x04\n" + - "\x0ePendingChannel\x12&\n" + - "\x0fremote_node_pub\x18\x01 \x01(\tR\rremoteNodePub\x12#\n" + - "\rchannel_point\x18\x02 \x01(\tR\fchannelPoint\x12\x1a\n" + - "\bcapacity\x18\x03 \x01(\x03R\bcapacity\x12#\n" + - "\rlocal_balance\x18\x04 \x01(\x03R\flocalBalance\x12%\n" + - "\x0eremote_balance\x18\x05 \x01(\x03R\rremoteBalance\x123\n" + - "\x16local_chan_reserve_sat\x18\x06 \x01(\x03R\x13localChanReserveSat\x125\n" + - "\x17remote_chan_reserve_sat\x18\a \x01(\x03R\x14remoteChanReserveSat\x12.\n" + - "\tinitiator\x18\b \x01(\x0e2\x10.lnrpc.InitiatorR\tinitiator\x12>\n" + - "\x0fcommitment_type\x18\t \x01(\x0e2\x15.lnrpc.CommitmentTypeR\x0ecommitmentType\x126\n" + - "\x17num_forwarding_packages\x18\n" + - " \x01(\x03R\x15numForwardingPackages\x12*\n" + - "\x11chan_status_flags\x18\v \x01(\tR\x0fchanStatusFlags\x12\x18\n" + - "\aprivate\x18\f \x01(\bR\aprivate\x12\x12\n" + - "\x04memo\x18\r \x01(\tR\x04memo\x12.\n" + - "\x13custom_channel_data\x18\" \x01(\fR\x11customChannelData\x1a\xe8\x02\n" + - "\x12PendingOpenChannel\x12G\n" + - "\achannel\x18\x01 \x01(\v2-.lnrpc.PendingChannelsResponse.PendingChannelR\achannel\x12\x1d\n" + - "\n" + - "commit_fee\x18\x04 \x01(\x03R\tcommitFee\x12#\n" + - "\rcommit_weight\x18\x05 \x01(\x03R\fcommitWeight\x12\x1c\n" + - "\n" + - "fee_per_kw\x18\x06 \x01(\x03R\bfeePerKw\x122\n" + - "\x15funding_expiry_blocks\x18\x03 \x01(\x05R\x13fundingExpiryBlocks\x12<\n" + - "\x1aconfirmations_until_active\x18\a \x01(\rR\x18confirmationsUntilActive\x12/\n" + - "\x13confirmation_height\x18\b \x01(\rR\x12confirmationHeightJ\x04\b\x02\x10\x03\x1a\xfa\x02\n" + - "\x13WaitingCloseChannel\x12G\n" + - "\achannel\x18\x01 \x01(\v2-.lnrpc.PendingChannelsResponse.PendingChannelR\achannel\x12#\n" + - "\rlimbo_balance\x18\x02 \x01(\x03R\flimboBalance\x12L\n" + - "\vcommitments\x18\x03 \x01(\v2*.lnrpc.PendingChannelsResponse.CommitmentsR\vcommitments\x12!\n" + - "\fclosing_txid\x18\x04 \x01(\tR\vclosingTxid\x12$\n" + - "\x0eclosing_tx_hex\x18\x05 \x01(\tR\fclosingTxHex\x12;\n" + - "\x1ablocks_til_close_confirmed\x18\x06 \x01(\rR\x17blocksTilCloseConfirmed\x12!\n" + - "\fclose_height\x18\a \x01(\rR\vcloseHeight\x1a\xa3\x02\n" + - "\vCommitments\x12\x1d\n" + - "\n" + - "local_txid\x18\x01 \x01(\tR\tlocalTxid\x12\x1f\n" + - "\vremote_txid\x18\x02 \x01(\tR\n" + - "remoteTxid\x12.\n" + - "\x13remote_pending_txid\x18\x03 \x01(\tR\x11remotePendingTxid\x12/\n" + - "\x14local_commit_fee_sat\x18\x04 \x01(\x04R\x11localCommitFeeSat\x121\n" + - "\x15remote_commit_fee_sat\x18\x05 \x01(\x04R\x12remoteCommitFeeSat\x12@\n" + - "\x1dremote_pending_commit_fee_sat\x18\x06 \x01(\x04R\x19remotePendingCommitFeeSat\x1a{\n" + - "\rClosedChannel\x12G\n" + - "\achannel\x18\x01 \x01(\v2-.lnrpc.PendingChannelsResponse.PendingChannelR\achannel\x12!\n" + - "\fclosing_txid\x18\x02 \x01(\tR\vclosingTxid\x1a\xee\x03\n" + - "\x12ForceClosedChannel\x12G\n" + - "\achannel\x18\x01 \x01(\v2-.lnrpc.PendingChannelsResponse.PendingChannelR\achannel\x12!\n" + - "\fclosing_txid\x18\x02 \x01(\tR\vclosingTxid\x12#\n" + - "\rlimbo_balance\x18\x03 \x01(\x03R\flimboBalance\x12'\n" + - "\x0fmaturity_height\x18\x04 \x01(\rR\x0ematurityHeight\x12.\n" + - "\x13blocks_til_maturity\x18\x05 \x01(\x05R\x11blocksTilMaturity\x12+\n" + - "\x11recovered_balance\x18\x06 \x01(\x03R\x10recoveredBalance\x127\n" + - "\rpending_htlcs\x18\b \x03(\v2\x12.lnrpc.PendingHTLCR\fpendingHtlcs\x12U\n" + - "\x06anchor\x18\t \x01(\x0e2=.lnrpc.PendingChannelsResponse.ForceClosedChannel.AnchorStateR\x06anchor\"1\n" + - "\vAnchorState\x12\t\n" + - "\x05LIMBO\x10\x00\x12\r\n" + - "\tRECOVERED\x10\x01\x12\b\n" + - "\x04LOST\x10\x02\"\x1a\n" + - "\x18ChannelEventSubscription\"?\n" + - "\x13ChannelCommitUpdate\x12(\n" + - "\achannel\x18\x01 \x01(\v2\x0e.lnrpc.ChannelR\achannel\"\xc6\x06\n" + - "\x12ChannelEventUpdate\x123\n" + - "\fopen_channel\x18\x01 \x01(\v2\x0e.lnrpc.ChannelH\x00R\vopenChannel\x12C\n" + - "\x0eclosed_channel\x18\x02 \x01(\v2\x1a.lnrpc.ChannelCloseSummaryH\x00R\rclosedChannel\x12<\n" + - "\x0eactive_channel\x18\x03 \x01(\v2\x13.lnrpc.ChannelPointH\x00R\ractiveChannel\x12@\n" + - "\x10inactive_channel\x18\x04 \x01(\v2\x13.lnrpc.ChannelPointH\x00R\x0finactiveChannel\x12H\n" + - "\x14pending_open_channel\x18\x06 \x01(\v2\x14.lnrpc.PendingUpdateH\x00R\x12pendingOpenChannel\x12K\n" + - "\x16fully_resolved_channel\x18\a \x01(\v2\x13.lnrpc.ChannelPointH\x00R\x14fullyResolvedChannel\x12M\n" + - "\x17channel_funding_timeout\x18\b \x01(\v2\x13.lnrpc.ChannelPointH\x00R\x15channelFundingTimeout\x12E\n" + - "\x0fupdated_channel\x18\t \x01(\v2\x1a.lnrpc.ChannelCommitUpdateH\x00R\x0eupdatedChannel\x128\n" + - "\x04type\x18\x05 \x01(\x0e2$.lnrpc.ChannelEventUpdate.UpdateTypeR\x04type\"\xc3\x01\n" + - "\n" + - "UpdateType\x12\x10\n" + - "\fOPEN_CHANNEL\x10\x00\x12\x12\n" + - "\x0eCLOSED_CHANNEL\x10\x01\x12\x12\n" + - "\x0eACTIVE_CHANNEL\x10\x02\x12\x14\n" + - "\x10INACTIVE_CHANNEL\x10\x03\x12\x18\n" + - "\x14PENDING_OPEN_CHANNEL\x10\x04\x12\x1a\n" + - "\x16FULLY_RESOLVED_CHANNEL\x10\x05\x12\x1b\n" + - "\x17CHANNEL_FUNDING_TIMEOUT\x10\x06\x12\x12\n" + - "\x0eCHANNEL_UPDATE\x10\aB\t\n" + - "\achannel\"t\n" + - "\x14WalletAccountBalance\x12+\n" + - "\x11confirmed_balance\x18\x01 \x01(\x03R\x10confirmedBalance\x12/\n" + - "\x13unconfirmed_balance\x18\x02 \x01(\x03R\x12unconfirmedBalance\"M\n" + - "\x14WalletBalanceRequest\x12\x18\n" + - "\aaccount\x18\x01 \x01(\tR\aaccount\x12\x1b\n" + - "\tmin_confs\x18\x02 \x01(\x05R\bminConfs\"\xbd\x03\n" + - "\x15WalletBalanceResponse\x12#\n" + - "\rtotal_balance\x18\x01 \x01(\x03R\ftotalBalance\x12+\n" + - "\x11confirmed_balance\x18\x02 \x01(\x03R\x10confirmedBalance\x12/\n" + - "\x13unconfirmed_balance\x18\x03 \x01(\x03R\x12unconfirmedBalance\x12%\n" + - "\x0elocked_balance\x18\x05 \x01(\x03R\rlockedBalance\x12?\n" + - "\x1creserved_balance_anchor_chan\x18\x06 \x01(\x03R\x19reservedBalanceAnchorChan\x12Y\n" + - "\x0faccount_balance\x18\x04 \x03(\v20.lnrpc.WalletBalanceResponse.AccountBalanceEntryR\x0eaccountBalance\x1a^\n" + - "\x13AccountBalanceEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x121\n" + - "\x05value\x18\x02 \x01(\v2\x1b.lnrpc.WalletAccountBalanceR\x05value:\x028\x01\".\n" + - "\x06Amount\x12\x10\n" + - "\x03sat\x18\x01 \x01(\x04R\x03sat\x12\x12\n" + - "\x04msat\x18\x02 \x01(\x04R\x04msat\"\x17\n" + - "\x15ChannelBalanceRequest\"\xb0\x04\n" + - "\x16ChannelBalanceResponse\x12\x1c\n" + - "\abalance\x18\x01 \x01(\x03B\x02\x18\x01R\abalance\x124\n" + - "\x14pending_open_balance\x18\x02 \x01(\x03B\x02\x18\x01R\x12pendingOpenBalance\x122\n" + - "\rlocal_balance\x18\x03 \x01(\v2\r.lnrpc.AmountR\flocalBalance\x124\n" + - "\x0eremote_balance\x18\x04 \x01(\v2\r.lnrpc.AmountR\rremoteBalance\x12E\n" + - "\x17unsettled_local_balance\x18\x05 \x01(\v2\r.lnrpc.AmountR\x15unsettledLocalBalance\x12G\n" + - "\x18unsettled_remote_balance\x18\x06 \x01(\v2\r.lnrpc.AmountR\x16unsettledRemoteBalance\x12J\n" + - "\x1apending_open_local_balance\x18\a \x01(\v2\r.lnrpc.AmountR\x17pendingOpenLocalBalance\x12L\n" + - "\x1bpending_open_remote_balance\x18\b \x01(\v2\r.lnrpc.AmountR\x18pendingOpenRemoteBalance\x12.\n" + - "\x13custom_channel_data\x18\t \x01(\fR\x11customChannelData\"\x9e\a\n" + - "\x12QueryRoutesRequest\x12\x17\n" + - "\apub_key\x18\x01 \x01(\tR\x06pubKey\x12\x10\n" + - "\x03amt\x18\x02 \x01(\x03R\x03amt\x12\x19\n" + - "\bamt_msat\x18\f \x01(\x03R\aamtMsat\x12(\n" + - "\x10final_cltv_delta\x18\x04 \x01(\x05R\x0efinalCltvDelta\x12,\n" + - "\tfee_limit\x18\x05 \x01(\v2\x0f.lnrpc.FeeLimitR\bfeeLimit\x12#\n" + - "\rignored_nodes\x18\x06 \x03(\fR\fignoredNodes\x12;\n" + - "\rignored_edges\x18\a \x03(\v2\x12.lnrpc.EdgeLocatorB\x02\x18\x01R\fignoredEdges\x12$\n" + - "\x0esource_pub_key\x18\b \x01(\tR\fsourcePubKey\x12.\n" + - "\x13use_mission_control\x18\t \x01(\bR\x11useMissionControl\x124\n" + - "\rignored_pairs\x18\n" + - " \x03(\v2\x0f.lnrpc.NodePairR\fignoredPairs\x12\x1d\n" + - "\n" + - "cltv_limit\x18\v \x01(\rR\tcltvLimit\x12`\n" + - "\x13dest_custom_records\x18\r \x03(\v20.lnrpc.QueryRoutesRequest.DestCustomRecordsEntryR\x11destCustomRecords\x12&\n" + - "\x0flast_hop_pubkey\x18\x0f \x01(\fR\rlastHopPubkey\x121\n" + - "\vroute_hints\x18\x10 \x03(\v2\x10.lnrpc.RouteHintR\n" + - "routeHints\x12M\n" + - "\x15blinded_payment_paths\x18\x13 \x03(\v2\x19.lnrpc.BlindedPaymentPathR\x13blindedPaymentPaths\x126\n" + - "\rdest_features\x18\x11 \x03(\x0e2\x11.lnrpc.FeatureBitR\fdestFeatures\x12\x1b\n" + - "\ttime_pref\x18\x12 \x01(\x01R\btimePref\x12*\n" + - "\x11outgoing_chan_ids\x18\x14 \x03(\x04R\x0foutgoingChanIds\x1aD\n" + - "\x16DestCustomRecordsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\x04R\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\fR\x05value:\x028\x01J\x04\b\x03\x10\x04J\x04\b\x0e\x10\x0f\".\n" + - "\bNodePair\x12\x12\n" + - "\x04from\x18\x01 \x01(\fR\x04from\x12\x0e\n" + - "\x02to\x18\x02 \x01(\fR\x02to\"]\n" + - "\vEdgeLocator\x12!\n" + - "\n" + - "channel_id\x18\x01 \x01(\x04B\x020\x01R\tchannelId\x12+\n" + - "\x11direction_reverse\x18\x02 \x01(\bR\x10directionReverse\"^\n" + - "\x13QueryRoutesResponse\x12$\n" + - "\x06routes\x18\x01 \x03(\v2\f.lnrpc.RouteR\x06routes\x12!\n" + - "\fsuccess_prob\x18\x02 \x01(\x01R\vsuccessProb\"\xa5\x05\n" + - "\x03Hop\x12\x1b\n" + - "\achan_id\x18\x01 \x01(\x04B\x020\x01R\x06chanId\x12'\n" + - "\rchan_capacity\x18\x02 \x01(\x03B\x02\x18\x01R\fchanCapacity\x12(\n" + - "\x0eamt_to_forward\x18\x03 \x01(\x03B\x02\x18\x01R\famtToForward\x12\x14\n" + - "\x03fee\x18\x04 \x01(\x03B\x02\x18\x01R\x03fee\x12\x16\n" + - "\x06expiry\x18\x05 \x01(\rR\x06expiry\x12-\n" + - "\x13amt_to_forward_msat\x18\x06 \x01(\x03R\x10amtToForwardMsat\x12\x19\n" + - "\bfee_msat\x18\a \x01(\x03R\afeeMsat\x12\x17\n" + - "\apub_key\x18\b \x01(\tR\x06pubKey\x12#\n" + - "\vtlv_payload\x18\t \x01(\bB\x02\x18\x01R\n" + - "tlvPayload\x12/\n" + - "\n" + - "mpp_record\x18\n" + - " \x01(\v2\x10.lnrpc.MPPRecordR\tmppRecord\x12/\n" + - "\n" + - "amp_record\x18\f \x01(\v2\x10.lnrpc.AMPRecordR\tampRecord\x12D\n" + - "\x0ecustom_records\x18\v \x03(\v2\x1d.lnrpc.Hop.CustomRecordsEntryR\rcustomRecords\x12\x1a\n" + - "\bmetadata\x18\r \x01(\fR\bmetadata\x12%\n" + - "\x0eblinding_point\x18\x0e \x01(\fR\rblindingPoint\x12%\n" + - "\x0eencrypted_data\x18\x0f \x01(\fR\rencryptedData\x12$\n" + - "\x0etotal_amt_msat\x18\x10 \x01(\x04R\ftotalAmtMsat\x1a@\n" + - "\x12CustomRecordsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\x04R\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\fR\x05value:\x028\x01\"T\n" + - "\tMPPRecord\x12!\n" + - "\fpayment_addr\x18\v \x01(\fR\vpaymentAddr\x12$\n" + - "\x0etotal_amt_msat\x18\n" + - " \x01(\x03R\ftotalAmtMsat\"b\n" + - "\tAMPRecord\x12\x1d\n" + - "\n" + - "root_share\x18\x01 \x01(\fR\trootShare\x12\x15\n" + - "\x06set_id\x18\x02 \x01(\fR\x05setId\x12\x1f\n" + - "\vchild_index\x18\x03 \x01(\rR\n" + - "childIndex\"\xc4\x02\n" + - "\x05Route\x12&\n" + - "\x0ftotal_time_lock\x18\x01 \x01(\rR\rtotalTimeLock\x12!\n" + - "\n" + - "total_fees\x18\x02 \x01(\x03B\x02\x18\x01R\ttotalFees\x12\x1f\n" + - "\ttotal_amt\x18\x03 \x01(\x03B\x02\x18\x01R\btotalAmt\x12\x1e\n" + - "\x04hops\x18\x04 \x03(\v2\n" + - ".lnrpc.HopR\x04hops\x12&\n" + - "\x0ftotal_fees_msat\x18\x05 \x01(\x03R\rtotalFeesMsat\x12$\n" + - "\x0etotal_amt_msat\x18\x06 \x01(\x03R\ftotalAmtMsat\x121\n" + - "\x15first_hop_amount_msat\x18\a \x01(\x03R\x12firstHopAmountMsat\x12.\n" + - "\x13custom_channel_data\x18\b \x01(\fR\x11customChannelData\"\x83\x01\n" + - "\x0fNodeInfoRequest\x12\x17\n" + - "\apub_key\x18\x01 \x01(\tR\x06pubKey\x12)\n" + - "\x10include_channels\x18\x02 \x01(\bR\x0fincludeChannels\x12,\n" + - "\x12include_auth_proof\x18\x03 \x01(\bR\x10includeAuthProof\"\xae\x01\n" + - "\bNodeInfo\x12(\n" + - "\x04node\x18\x01 \x01(\v2\x14.lnrpc.LightningNodeR\x04node\x12!\n" + - "\fnum_channels\x18\x02 \x01(\rR\vnumChannels\x12%\n" + - "\x0etotal_capacity\x18\x03 \x01(\x03R\rtotalCapacity\x12.\n" + - "\bchannels\x18\x04 \x03(\v2\x12.lnrpc.ChannelEdgeR\bchannels\"\xc6\x03\n" + - "\rLightningNode\x12\x1f\n" + - "\vlast_update\x18\x01 \x01(\rR\n" + - "lastUpdate\x12\x17\n" + - "\apub_key\x18\x02 \x01(\tR\x06pubKey\x12\x14\n" + - "\x05alias\x18\x03 \x01(\tR\x05alias\x120\n" + - "\taddresses\x18\x04 \x03(\v2\x12.lnrpc.NodeAddressR\taddresses\x12\x14\n" + - "\x05color\x18\x05 \x01(\tR\x05color\x12>\n" + - "\bfeatures\x18\x06 \x03(\v2\".lnrpc.LightningNode.FeaturesEntryR\bfeatures\x12N\n" + - "\x0ecustom_records\x18\a \x03(\v2'.lnrpc.LightningNode.CustomRecordsEntryR\rcustomRecords\x1aK\n" + - "\rFeaturesEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\rR\x03key\x12$\n" + - "\x05value\x18\x02 \x01(\v2\x0e.lnrpc.FeatureR\x05value:\x028\x01\x1a@\n" + - "\x12CustomRecordsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\x04R\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\fR\x05value:\x028\x01\";\n" + - "\vNodeAddress\x12\x18\n" + - "\anetwork\x18\x01 \x01(\tR\anetwork\x12\x12\n" + - "\x04addr\x18\x02 \x01(\tR\x04addr\"\x89\x04\n" + - "\rRoutingPolicy\x12&\n" + - "\x0ftime_lock_delta\x18\x01 \x01(\rR\rtimeLockDelta\x12\x19\n" + - "\bmin_htlc\x18\x02 \x01(\x03R\aminHtlc\x12\"\n" + - "\rfee_base_msat\x18\x03 \x01(\x03R\vfeeBaseMsat\x12-\n" + - "\x13fee_rate_milli_msat\x18\x04 \x01(\x03R\x10feeRateMilliMsat\x12\x1a\n" + - "\bdisabled\x18\x05 \x01(\bR\bdisabled\x12\"\n" + - "\rmax_htlc_msat\x18\x06 \x01(\x04R\vmaxHtlcMsat\x12\x1f\n" + - "\vlast_update\x18\a \x01(\rR\n" + - "lastUpdate\x12N\n" + - "\x0ecustom_records\x18\b \x03(\v2'.lnrpc.RoutingPolicy.CustomRecordsEntryR\rcustomRecords\x121\n" + - "\x15inbound_fee_base_msat\x18\t \x01(\x05R\x12inboundFeeBaseMsat\x12<\n" + - "\x1binbound_fee_rate_milli_msat\x18\n" + - " \x01(\x05R\x17inboundFeeRateMilliMsat\x1a@\n" + - "\x12CustomRecordsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\x04R\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\fR\x05value:\x028\x01\"\x92\x01\n" + - "\x10ChannelAuthProof\x12\x1b\n" + - "\tnode_sig1\x18\x01 \x01(\fR\bnodeSig1\x12!\n" + - "\fbitcoin_sig1\x18\x02 \x01(\fR\vbitcoinSig1\x12\x1b\n" + - "\tnode_sig2\x18\x03 \x01(\fR\bnodeSig2\x12!\n" + - "\fbitcoin_sig2\x18\x04 \x01(\fR\vbitcoinSig2\"\x84\x04\n" + - "\vChannelEdge\x12!\n" + - "\n" + - "channel_id\x18\x01 \x01(\x04B\x020\x01R\tchannelId\x12\x1d\n" + - "\n" + - "chan_point\x18\x02 \x01(\tR\tchanPoint\x12#\n" + - "\vlast_update\x18\x03 \x01(\rB\x02\x18\x01R\n" + - "lastUpdate\x12\x1b\n" + - "\tnode1_pub\x18\x04 \x01(\tR\bnode1Pub\x12\x1b\n" + - "\tnode2_pub\x18\x05 \x01(\tR\bnode2Pub\x12\x1a\n" + - "\bcapacity\x18\x06 \x01(\x03R\bcapacity\x127\n" + - "\fnode1_policy\x18\a \x01(\v2\x14.lnrpc.RoutingPolicyR\vnode1Policy\x127\n" + - "\fnode2_policy\x18\b \x01(\v2\x14.lnrpc.RoutingPolicyR\vnode2Policy\x12L\n" + - "\x0ecustom_records\x18\t \x03(\v2%.lnrpc.ChannelEdge.CustomRecordsEntryR\rcustomRecords\x126\n" + - "\n" + - "auth_proof\x18\n" + - " \x01(\v2\x17.lnrpc.ChannelAuthProofR\tauthProof\x1a@\n" + - "\x12CustomRecordsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\x04R\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\fR\x05value:\x028\x01\"t\n" + - "\x13ChannelGraphRequest\x12/\n" + - "\x13include_unannounced\x18\x01 \x01(\bR\x12includeUnannounced\x12,\n" + - "\x12include_auth_proof\x18\x02 \x01(\bR\x10includeAuthProof\"d\n" + - "\fChannelGraph\x12*\n" + - "\x05nodes\x18\x01 \x03(\v2\x14.lnrpc.LightningNodeR\x05nodes\x12(\n" + - "\x05edges\x18\x02 \x03(\v2\x12.lnrpc.ChannelEdgeR\x05edges\"A\n" + - "\x12NodeMetricsRequest\x12+\n" + - "\x05types\x18\x01 \x03(\x0e2\x15.lnrpc.NodeMetricTypeR\x05types\"\xe1\x01\n" + - "\x13NodeMetricsResponse\x12l\n" + - "\x16betweenness_centrality\x18\x01 \x03(\v25.lnrpc.NodeMetricsResponse.BetweennessCentralityEntryR\x15betweennessCentrality\x1a\\\n" + - "\x1aBetweennessCentralityEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x12(\n" + - "\x05value\x18\x02 \x01(\v2\x12.lnrpc.FloatMetricR\x05value:\x028\x01\"N\n" + - "\vFloatMetric\x12\x14\n" + - "\x05value\x18\x01 \x01(\x01R\x05value\x12)\n" + - "\x10normalized_value\x18\x02 \x01(\x01R\x0fnormalizedValue\"{\n" + - "\x0fChanInfoRequest\x12\x1b\n" + - "\achan_id\x18\x01 \x01(\x04B\x020\x01R\x06chanId\x12\x1d\n" + - "\n" + - "chan_point\x18\x02 \x01(\tR\tchanPoint\x12,\n" + - "\x12include_auth_proof\x18\x03 \x01(\bR\x10includeAuthProof\"\x14\n" + - "\x12NetworkInfoRequest\"\xd5\x03\n" + - "\vNetworkInfo\x12%\n" + - "\x0egraph_diameter\x18\x01 \x01(\rR\rgraphDiameter\x12$\n" + - "\x0eavg_out_degree\x18\x02 \x01(\x01R\favgOutDegree\x12$\n" + - "\x0emax_out_degree\x18\x03 \x01(\rR\fmaxOutDegree\x12\x1b\n" + - "\tnum_nodes\x18\x04 \x01(\rR\bnumNodes\x12!\n" + - "\fnum_channels\x18\x05 \x01(\rR\vnumChannels\x124\n" + - "\x16total_network_capacity\x18\x06 \x01(\x03R\x14totalNetworkCapacity\x12(\n" + - "\x10avg_channel_size\x18\a \x01(\x01R\x0eavgChannelSize\x12(\n" + - "\x10min_channel_size\x18\b \x01(\x03R\x0eminChannelSize\x12(\n" + - "\x10max_channel_size\x18\t \x01(\x03R\x0emaxChannelSize\x125\n" + - "\x17median_channel_size_sat\x18\n" + - " \x01(\x03R\x14medianChannelSizeSat\x12(\n" + - "\x10num_zombie_chans\x18\v \x01(\x04R\x0enumZombieChans\"\r\n" + - "\vStopRequest\"&\n" + - "\fStopResponse\x12\x16\n" + - "\x06status\x18\x01 \x01(\tR\x06status\"\x1b\n" + - "\x19GraphTopologySubscription\"\xcd\x01\n" + - "\x13GraphTopologyUpdate\x124\n" + - "\fnode_updates\x18\x01 \x03(\v2\x11.lnrpc.NodeUpdateR\vnodeUpdates\x12A\n" + - "\x0fchannel_updates\x18\x02 \x03(\v2\x18.lnrpc.ChannelEdgeUpdateR\x0echannelUpdates\x12=\n" + - "\fclosed_chans\x18\x03 \x03(\v2\x1a.lnrpc.ClosedChannelUpdateR\vclosedChans\"\xef\x02\n" + - "\n" + - "NodeUpdate\x12 \n" + - "\taddresses\x18\x01 \x03(\tB\x02\x18\x01R\taddresses\x12!\n" + - "\fidentity_key\x18\x02 \x01(\tR\videntityKey\x12+\n" + - "\x0fglobal_features\x18\x03 \x01(\fB\x02\x18\x01R\x0eglobalFeatures\x12\x14\n" + - "\x05alias\x18\x04 \x01(\tR\x05alias\x12\x14\n" + - "\x05color\x18\x05 \x01(\tR\x05color\x129\n" + - "\x0enode_addresses\x18\a \x03(\v2\x12.lnrpc.NodeAddressR\rnodeAddresses\x12;\n" + - "\bfeatures\x18\x06 \x03(\v2\x1f.lnrpc.NodeUpdate.FeaturesEntryR\bfeatures\x1aK\n" + - "\rFeaturesEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\rR\x03key\x12$\n" + - "\x05value\x18\x02 \x01(\v2\x0e.lnrpc.FeatureR\x05value:\x028\x01\"\x91\x02\n" + - "\x11ChannelEdgeUpdate\x12\x1b\n" + - "\achan_id\x18\x01 \x01(\x04B\x020\x01R\x06chanId\x122\n" + - "\n" + - "chan_point\x18\x02 \x01(\v2\x13.lnrpc.ChannelPointR\tchanPoint\x12\x1a\n" + - "\bcapacity\x18\x03 \x01(\x03R\bcapacity\x12;\n" + - "\x0erouting_policy\x18\x04 \x01(\v2\x14.lnrpc.RoutingPolicyR\rroutingPolicy\x12)\n" + - "\x10advertising_node\x18\x05 \x01(\tR\x0fadvertisingNode\x12'\n" + - "\x0fconnecting_node\x18\x06 \x01(\tR\x0econnectingNode\"\xa7\x01\n" + - "\x13ClosedChannelUpdate\x12\x1b\n" + - "\achan_id\x18\x01 \x01(\x04B\x020\x01R\x06chanId\x12\x1a\n" + - "\bcapacity\x18\x02 \x01(\x03R\bcapacity\x12#\n" + - "\rclosed_height\x18\x03 \x01(\rR\fclosedHeight\x122\n" + - "\n" + - "chan_point\x18\x04 \x01(\v2\x13.lnrpc.ChannelPointR\tchanPoint\"\xcf\x01\n" + - "\aHopHint\x12\x17\n" + - "\anode_id\x18\x01 \x01(\tR\x06nodeId\x12\x1b\n" + - "\achan_id\x18\x02 \x01(\x04B\x020\x01R\x06chanId\x12\"\n" + - "\rfee_base_msat\x18\x03 \x01(\rR\vfeeBaseMsat\x12>\n" + - "\x1bfee_proportional_millionths\x18\x04 \x01(\rR\x19feeProportionalMillionths\x12*\n" + - "\x11cltv_expiry_delta\x18\x05 \x01(\rR\x0fcltvExpiryDelta\"\x1e\n" + - "\x05SetID\x12\x15\n" + - "\x06set_id\x18\x01 \x01(\fR\x05setId\"8\n" + - "\tRouteHint\x12+\n" + - "\thop_hints\x18\x01 \x03(\v2\x0e.lnrpc.HopHintR\bhopHints\"\xc4\x02\n" + - "\x12BlindedPaymentPath\x125\n" + - "\fblinded_path\x18\x01 \x01(\v2\x12.lnrpc.BlindedPathR\vblindedPath\x12\"\n" + - "\rbase_fee_msat\x18\x02 \x01(\x04R\vbaseFeeMsat\x122\n" + - "\x15proportional_fee_rate\x18\x03 \x01(\rR\x13proportionalFeeRate\x12(\n" + - "\x10total_cltv_delta\x18\x04 \x01(\rR\x0etotalCltvDelta\x12\"\n" + - "\rhtlc_min_msat\x18\x05 \x01(\x04R\vhtlcMinMsat\x12\"\n" + - "\rhtlc_max_msat\x18\x06 \x01(\x04R\vhtlcMaxMsat\x12-\n" + - "\bfeatures\x18\a \x03(\x0e2\x11.lnrpc.FeatureBitR\bfeatures\"\x97\x01\n" + - "\vBlindedPath\x12+\n" + - "\x11introduction_node\x18\x01 \x01(\fR\x10introductionNode\x12%\n" + - "\x0eblinding_point\x18\x02 \x01(\fR\rblindingPoint\x124\n" + - "\fblinded_hops\x18\x03 \x03(\v2\x11.lnrpc.BlindedHopR\vblindedHops\"V\n" + - "\n" + - "BlindedHop\x12!\n" + - "\fblinded_node\x18\x01 \x01(\fR\vblindedNode\x12%\n" + - "\x0eencrypted_data\x18\x02 \x01(\fR\rencryptedData\"\xa8\x01\n" + - "\x0fAMPInvoiceState\x12-\n" + - "\x05state\x18\x01 \x01(\x0e2\x17.lnrpc.InvoiceHTLCStateR\x05state\x12!\n" + - "\fsettle_index\x18\x02 \x01(\x04R\vsettleIndex\x12\x1f\n" + - "\vsettle_time\x18\x03 \x01(\x03R\n" + - "settleTime\x12\"\n" + - "\ramt_paid_msat\x18\x05 \x01(\x03R\vamtPaidMsat\"\xac\n" + - "\n" + - "\aInvoice\x12\x12\n" + - "\x04memo\x18\x01 \x01(\tR\x04memo\x12\x1d\n" + - "\n" + - "r_preimage\x18\x03 \x01(\fR\trPreimage\x12\x15\n" + - "\x06r_hash\x18\x04 \x01(\fR\x05rHash\x12\x14\n" + - "\x05value\x18\x05 \x01(\x03R\x05value\x12\x1d\n" + - "\n" + - "value_msat\x18\x17 \x01(\x03R\tvalueMsat\x12\x1c\n" + - "\asettled\x18\x06 \x01(\bB\x02\x18\x01R\asettled\x12#\n" + - "\rcreation_date\x18\a \x01(\x03R\fcreationDate\x12\x1f\n" + - "\vsettle_date\x18\b \x01(\x03R\n" + - "settleDate\x12'\n" + - "\x0fpayment_request\x18\t \x01(\tR\x0epaymentRequest\x12)\n" + - "\x10description_hash\x18\n" + - " \x01(\fR\x0fdescriptionHash\x12\x16\n" + - "\x06expiry\x18\v \x01(\x03R\x06expiry\x12#\n" + - "\rfallback_addr\x18\f \x01(\tR\ffallbackAddr\x12\x1f\n" + - "\vcltv_expiry\x18\r \x01(\x04R\n" + - "cltvExpiry\x121\n" + - "\vroute_hints\x18\x0e \x03(\v2\x10.lnrpc.RouteHintR\n" + - "routeHints\x12\x18\n" + - "\aprivate\x18\x0f \x01(\bR\aprivate\x12\x1b\n" + - "\tadd_index\x18\x10 \x01(\x04R\baddIndex\x12!\n" + - "\fsettle_index\x18\x11 \x01(\x04R\vsettleIndex\x12\x1d\n" + - "\bamt_paid\x18\x12 \x01(\x03B\x02\x18\x01R\aamtPaid\x12 \n" + - "\famt_paid_sat\x18\x13 \x01(\x03R\n" + - "amtPaidSat\x12\"\n" + - "\ramt_paid_msat\x18\x14 \x01(\x03R\vamtPaidMsat\x121\n" + - "\x05state\x18\x15 \x01(\x0e2\x1b.lnrpc.Invoice.InvoiceStateR\x05state\x12(\n" + - "\x05htlcs\x18\x16 \x03(\v2\x12.lnrpc.InvoiceHTLCR\x05htlcs\x128\n" + - "\bfeatures\x18\x18 \x03(\v2\x1c.lnrpc.Invoice.FeaturesEntryR\bfeatures\x12\x1d\n" + - "\n" + - "is_keysend\x18\x19 \x01(\bR\tisKeysend\x12!\n" + - "\fpayment_addr\x18\x1a \x01(\fR\vpaymentAddr\x12\x15\n" + - "\x06is_amp\x18\x1b \x01(\bR\x05isAmp\x12O\n" + - "\x11amp_invoice_state\x18\x1c \x03(\v2#.lnrpc.Invoice.AmpInvoiceStateEntryR\x0fampInvoiceState\x12\x1d\n" + - "\n" + - "is_blinded\x18\x1d \x01(\bR\tisBlinded\x12H\n" + - "\x13blinded_path_config\x18\x1e \x01(\v2\x18.lnrpc.BlindedPathConfigR\x11blindedPathConfig\x1aK\n" + - "\rFeaturesEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\rR\x03key\x12$\n" + - "\x05value\x18\x02 \x01(\v2\x0e.lnrpc.FeatureR\x05value:\x028\x01\x1aZ\n" + - "\x14AmpInvoiceStateEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x12,\n" + - "\x05value\x18\x02 \x01(\v2\x16.lnrpc.AMPInvoiceStateR\x05value:\x028\x01\"A\n" + - "\fInvoiceState\x12\b\n" + - "\x04OPEN\x10\x00\x12\v\n" + - "\aSETTLED\x10\x01\x12\f\n" + - "\bCANCELED\x10\x02\x12\f\n" + - "\bACCEPTED\x10\x03J\x04\b\x02\x10\x03\"\xa3\x02\n" + - "\x11BlindedPathConfig\x12.\n" + - "\x11min_num_real_hops\x18\x01 \x01(\rH\x00R\x0eminNumRealHops\x88\x01\x01\x12\x1e\n" + - "\bnum_hops\x18\x02 \x01(\rH\x01R\anumHops\x88\x01\x01\x12'\n" + - "\rmax_num_paths\x18\x03 \x01(\rH\x02R\vmaxNumPaths\x88\x01\x01\x12,\n" + - "\x12node_omission_list\x18\x04 \x03(\fR\x10nodeOmissionList\x122\n" + - "\x15incoming_channel_list\x18\x05 \x03(\x04R\x13incomingChannelListB\x14\n" + - "\x12_min_num_real_hopsB\v\n" + - "\t_num_hopsB\x10\n" + - "\x0e_max_num_paths\"\xac\x04\n" + - "\vInvoiceHTLC\x12\x1b\n" + - "\achan_id\x18\x01 \x01(\x04B\x020\x01R\x06chanId\x12\x1d\n" + - "\n" + - "htlc_index\x18\x02 \x01(\x04R\thtlcIndex\x12\x19\n" + - "\bamt_msat\x18\x03 \x01(\x04R\aamtMsat\x12#\n" + - "\raccept_height\x18\x04 \x01(\x05R\facceptHeight\x12\x1f\n" + - "\vaccept_time\x18\x05 \x01(\x03R\n" + - "acceptTime\x12!\n" + - "\fresolve_time\x18\x06 \x01(\x03R\vresolveTime\x12#\n" + - "\rexpiry_height\x18\a \x01(\x05R\fexpiryHeight\x12-\n" + - "\x05state\x18\b \x01(\x0e2\x17.lnrpc.InvoiceHTLCStateR\x05state\x12L\n" + - "\x0ecustom_records\x18\t \x03(\v2%.lnrpc.InvoiceHTLC.CustomRecordsEntryR\rcustomRecords\x12+\n" + - "\x12mpp_total_amt_msat\x18\n" + - " \x01(\x04R\x0fmppTotalAmtMsat\x12\x1c\n" + - "\x03amp\x18\v \x01(\v2\n" + - ".lnrpc.AMPR\x03amp\x12.\n" + - "\x13custom_channel_data\x18\f \x01(\fR\x11customChannelData\x1a@\n" + - "\x12CustomRecordsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\x04R\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\fR\x05value:\x028\x01\"\x8c\x01\n" + - "\x03AMP\x12\x1d\n" + - "\n" + - "root_share\x18\x01 \x01(\fR\trootShare\x12\x15\n" + - "\x06set_id\x18\x02 \x01(\fR\x05setId\x12\x1f\n" + - "\vchild_index\x18\x03 \x01(\rR\n" + - "childIndex\x12\x12\n" + - "\x04hash\x18\x04 \x01(\fR\x04hash\x12\x1a\n" + - "\bpreimage\x18\x05 \x01(\fR\bpreimage\"\x94\x01\n" + - "\x12AddInvoiceResponse\x12\x15\n" + - "\x06r_hash\x18\x01 \x01(\fR\x05rHash\x12'\n" + - "\x0fpayment_request\x18\x02 \x01(\tR\x0epaymentRequest\x12\x1b\n" + - "\tadd_index\x18\x10 \x01(\x04R\baddIndex\x12!\n" + - "\fpayment_addr\x18\x11 \x01(\fR\vpaymentAddr\"F\n" + - "\vPaymentHash\x12 \n" + - "\n" + - "r_hash_str\x18\x01 \x01(\tB\x02\x18\x01R\brHashStr\x12\x15\n" + - "\x06r_hash\x18\x02 \x01(\fR\x05rHash\"\xfc\x01\n" + - "\x12ListInvoiceRequest\x12!\n" + - "\fpending_only\x18\x01 \x01(\bR\vpendingOnly\x12!\n" + - "\findex_offset\x18\x04 \x01(\x04R\vindexOffset\x12(\n" + - "\x10num_max_invoices\x18\x05 \x01(\x04R\x0enumMaxInvoices\x12\x1a\n" + - "\breversed\x18\x06 \x01(\bR\breversed\x12.\n" + - "\x13creation_date_start\x18\a \x01(\x04R\x11creationDateStart\x12*\n" + - "\x11creation_date_end\x18\b \x01(\x04R\x0fcreationDateEnd\"\x9b\x01\n" + - "\x13ListInvoiceResponse\x12*\n" + - "\binvoices\x18\x01 \x03(\v2\x0e.lnrpc.InvoiceR\binvoices\x12*\n" + - "\x11last_index_offset\x18\x02 \x01(\x04R\x0flastIndexOffset\x12,\n" + - "\x12first_index_offset\x18\x03 \x01(\x04R\x10firstIndexOffset\"U\n" + - "\x13InvoiceSubscription\x12\x1b\n" + - "\tadd_index\x18\x01 \x01(\x04R\baddIndex\x12!\n" + - "\fsettle_index\x18\x02 \x01(\x04R\vsettleIndex\":\n" + - "\x15DelCanceledInvoiceReq\x12!\n" + - "\finvoice_hash\x18\x01 \x01(\tR\vinvoiceHash\"0\n" + - "\x16DelCanceledInvoiceResp\x12\x16\n" + - "\x06status\x18\x01 \x01(\tR\x06status\"\xcb\x06\n" + - "\aPayment\x12!\n" + - "\fpayment_hash\x18\x01 \x01(\tR\vpaymentHash\x12\x18\n" + - "\x05value\x18\x02 \x01(\x03B\x02\x18\x01R\x05value\x12'\n" + - "\rcreation_date\x18\x03 \x01(\x03B\x02\x18\x01R\fcreationDate\x12\x14\n" + - "\x03fee\x18\x05 \x01(\x03B\x02\x18\x01R\x03fee\x12)\n" + - "\x10payment_preimage\x18\x06 \x01(\tR\x0fpaymentPreimage\x12\x1b\n" + - "\tvalue_sat\x18\a \x01(\x03R\bvalueSat\x12\x1d\n" + - "\n" + - "value_msat\x18\b \x01(\x03R\tvalueMsat\x12'\n" + - "\x0fpayment_request\x18\t \x01(\tR\x0epaymentRequest\x124\n" + - "\x06status\x18\n" + - " \x01(\x0e2\x1c.lnrpc.Payment.PaymentStatusR\x06status\x12\x17\n" + - "\afee_sat\x18\v \x01(\x03R\x06feeSat\x12\x19\n" + - "\bfee_msat\x18\f \x01(\x03R\afeeMsat\x12(\n" + - "\x10creation_time_ns\x18\r \x01(\x03R\x0ecreationTimeNs\x12(\n" + - "\x05htlcs\x18\x0e \x03(\v2\x12.lnrpc.HTLCAttemptR\x05htlcs\x12#\n" + - "\rpayment_index\x18\x0f \x01(\x04R\fpaymentIndex\x12B\n" + - "\x0efailure_reason\x18\x10 \x01(\x0e2\x1b.lnrpc.PaymentFailureReasonR\rfailureReason\x12b\n" + - "\x18first_hop_custom_records\x18\x11 \x03(\v2).lnrpc.Payment.FirstHopCustomRecordsEntryR\x15firstHopCustomRecords\x1aH\n" + - "\x1aFirstHopCustomRecordsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\x04R\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\fR\x05value:\x028\x01\"Y\n" + - "\rPaymentStatus\x12\x0f\n" + - "\aUNKNOWN\x10\x00\x1a\x02\b\x01\x12\r\n" + - "\tIN_FLIGHT\x10\x01\x12\r\n" + - "\tSUCCEEDED\x10\x02\x12\n" + - "\n" + - "\x06FAILED\x10\x03\x12\r\n" + - "\tINITIATED\x10\x04J\x04\b\x04\x10\x05\"\xd5\x02\n" + - "\vHTLCAttempt\x12\x1d\n" + - "\n" + - "attempt_id\x18\a \x01(\x04R\tattemptId\x125\n" + - "\x06status\x18\x01 \x01(\x0e2\x1d.lnrpc.HTLCAttempt.HTLCStatusR\x06status\x12\"\n" + - "\x05route\x18\x02 \x01(\v2\f.lnrpc.RouteR\x05route\x12&\n" + - "\x0fattempt_time_ns\x18\x03 \x01(\x03R\rattemptTimeNs\x12&\n" + - "\x0fresolve_time_ns\x18\x04 \x01(\x03R\rresolveTimeNs\x12(\n" + - "\afailure\x18\x05 \x01(\v2\x0e.lnrpc.FailureR\afailure\x12\x1a\n" + - "\bpreimage\x18\x06 \x01(\fR\bpreimage\"6\n" + - "\n" + - "HTLCStatus\x12\r\n" + - "\tIN_FLIGHT\x10\x00\x12\r\n" + - "\tSUCCEEDED\x10\x01\x12\n" + - "\n" + - "\x06FAILED\x10\x02\"\xd1\x02\n" + - "\x13ListPaymentsRequest\x12-\n" + - "\x12include_incomplete\x18\x01 \x01(\bR\x11includeIncomplete\x12!\n" + - "\findex_offset\x18\x02 \x01(\x04R\vindexOffset\x12!\n" + - "\fmax_payments\x18\x03 \x01(\x04R\vmaxPayments\x12\x1a\n" + - "\breversed\x18\x04 \x01(\bR\breversed\x120\n" + - "\x14count_total_payments\x18\x05 \x01(\bR\x12countTotalPayments\x12.\n" + - "\x13creation_date_start\x18\x06 \x01(\x04R\x11creationDateStart\x12*\n" + - "\x11creation_date_end\x18\a \x01(\x04R\x0fcreationDateEnd\x12\x1b\n" + - "\tomit_hops\x18\b \x01(\bR\bomitHops\"\xca\x01\n" + - "\x14ListPaymentsResponse\x12*\n" + - "\bpayments\x18\x01 \x03(\v2\x0e.lnrpc.PaymentR\bpayments\x12,\n" + - "\x12first_index_offset\x18\x02 \x01(\x04R\x10firstIndexOffset\x12*\n" + - "\x11last_index_offset\x18\x03 \x01(\x04R\x0flastIndexOffset\x12,\n" + - "\x12total_num_payments\x18\x04 \x01(\x04R\x10totalNumPayments\"e\n" + - "\x14DeletePaymentRequest\x12!\n" + - "\fpayment_hash\x18\x01 \x01(\fR\vpaymentHash\x12*\n" + - "\x11failed_htlcs_only\x18\x02 \x01(\bR\x0ffailedHtlcsOnly\"\x9b\x01\n" + - "\x18DeleteAllPaymentsRequest\x120\n" + - "\x14failed_payments_only\x18\x01 \x01(\bR\x12failedPaymentsOnly\x12*\n" + - "\x11failed_htlcs_only\x18\x02 \x01(\bR\x0ffailedHtlcsOnly\x12!\n" + - "\fall_payments\x18\x03 \x01(\bR\vallPayments\"/\n" + - "\x15DeletePaymentResponse\x12\x16\n" + - "\x06status\x18\x01 \x01(\tR\x06status\"3\n" + - "\x19DeleteAllPaymentsResponse\x12\x16\n" + - "\x06status\x18\x01 \x01(\tR\x06status\"\xbf\x01\n" + - "\x15AbandonChannelRequest\x128\n" + - "\rchannel_point\x18\x01 \x01(\v2\x13.lnrpc.ChannelPointR\fchannelPoint\x129\n" + - "\x19pending_funding_shim_only\x18\x02 \x01(\bR\x16pendingFundingShimOnly\x121\n" + - "\x16i_know_what_i_am_doing\x18\x03 \x01(\bR\x11iKnowWhatIAmDoing\"0\n" + - "\x16AbandonChannelResponse\x12\x16\n" + - "\x06status\x18\x01 \x01(\tR\x06status\"F\n" + - "\x11DebugLevelRequest\x12\x12\n" + - "\x04show\x18\x01 \x01(\bR\x04show\x12\x1d\n" + - "\n" + - "level_spec\x18\x02 \x01(\tR\tlevelSpec\"5\n" + - "\x12DebugLevelResponse\x12\x1f\n" + - "\vsub_systems\x18\x01 \x01(\tR\n" + - "subSystems\"'\n" + - "\fPayReqString\x12\x17\n" + - "\apay_req\x18\x01 \x01(\tR\x06payReq\"\xf0\x04\n" + - "\x06PayReq\x12 \n" + - "\vdestination\x18\x01 \x01(\tR\vdestination\x12!\n" + - "\fpayment_hash\x18\x02 \x01(\tR\vpaymentHash\x12!\n" + - "\fnum_satoshis\x18\x03 \x01(\x03R\vnumSatoshis\x12\x1c\n" + - "\ttimestamp\x18\x04 \x01(\x03R\ttimestamp\x12\x16\n" + - "\x06expiry\x18\x05 \x01(\x03R\x06expiry\x12 \n" + - "\vdescription\x18\x06 \x01(\tR\vdescription\x12)\n" + - "\x10description_hash\x18\a \x01(\tR\x0fdescriptionHash\x12#\n" + - "\rfallback_addr\x18\b \x01(\tR\ffallbackAddr\x12\x1f\n" + - "\vcltv_expiry\x18\t \x01(\x03R\n" + - "cltvExpiry\x121\n" + - "\vroute_hints\x18\n" + - " \x03(\v2\x10.lnrpc.RouteHintR\n" + - "routeHints\x12!\n" + - "\fpayment_addr\x18\v \x01(\fR\vpaymentAddr\x12\x19\n" + - "\bnum_msat\x18\f \x01(\x03R\anumMsat\x127\n" + - "\bfeatures\x18\r \x03(\v2\x1b.lnrpc.PayReq.FeaturesEntryR\bfeatures\x12>\n" + - "\rblinded_paths\x18\x0e \x03(\v2\x19.lnrpc.BlindedPaymentPathR\fblindedPaths\x1aK\n" + - "\rFeaturesEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\rR\x03key\x12$\n" + - "\x05value\x18\x02 \x01(\v2\x0e.lnrpc.FeatureR\x05value:\x028\x01\"Y\n" + - "\aFeature\x12\x12\n" + - "\x04name\x18\x02 \x01(\tR\x04name\x12\x1f\n" + - "\vis_required\x18\x03 \x01(\bR\n" + - "isRequired\x12\x19\n" + - "\bis_known\x18\x04 \x01(\bR\aisKnown\"\x12\n" + - "\x10FeeReportRequest\"\x95\x02\n" + - "\x10ChannelFeeReport\x12\x1b\n" + - "\achan_id\x18\x05 \x01(\x04B\x020\x01R\x06chanId\x12#\n" + - "\rchannel_point\x18\x01 \x01(\tR\fchannelPoint\x12\"\n" + - "\rbase_fee_msat\x18\x02 \x01(\x03R\vbaseFeeMsat\x12\x1e\n" + - "\vfee_per_mil\x18\x03 \x01(\x03R\tfeePerMil\x12\x19\n" + - "\bfee_rate\x18\x04 \x01(\x01R\afeeRate\x121\n" + - "\x15inbound_base_fee_msat\x18\x06 \x01(\x05R\x12inboundBaseFeeMsat\x12-\n" + - "\x13inbound_fee_per_mil\x18\a \x01(\x05R\x10inboundFeePerMil\"\xb5\x01\n" + - "\x11FeeReportResponse\x12:\n" + - "\fchannel_fees\x18\x01 \x03(\v2\x17.lnrpc.ChannelFeeReportR\vchannelFees\x12\x1e\n" + - "\vday_fee_sum\x18\x02 \x01(\x04R\tdayFeeSum\x12 \n" + - "\fweek_fee_sum\x18\x03 \x01(\x04R\n" + - "weekFeeSum\x12\"\n" + - "\rmonth_fee_sum\x18\x04 \x01(\x04R\vmonthFeeSum\"R\n" + - "\n" + - "InboundFee\x12\"\n" + - "\rbase_fee_msat\x18\x01 \x01(\x05R\vbaseFeeMsat\x12 \n" + - "\ffee_rate_ppm\x18\x02 \x01(\x05R\n" + - "feeRatePpm\"\xda\x03\n" + - "\x13PolicyUpdateRequest\x12\x18\n" + - "\x06global\x18\x01 \x01(\bH\x00R\x06global\x124\n" + - "\n" + - "chan_point\x18\x02 \x01(\v2\x13.lnrpc.ChannelPointH\x00R\tchanPoint\x12\"\n" + - "\rbase_fee_msat\x18\x03 \x01(\x03R\vbaseFeeMsat\x12\x19\n" + - "\bfee_rate\x18\x04 \x01(\x01R\afeeRate\x12 \n" + - "\ffee_rate_ppm\x18\t \x01(\rR\n" + - "feeRatePpm\x12&\n" + - "\x0ftime_lock_delta\x18\x05 \x01(\rR\rtimeLockDelta\x12\"\n" + - "\rmax_htlc_msat\x18\x06 \x01(\x04R\vmaxHtlcMsat\x12\"\n" + - "\rmin_htlc_msat\x18\a \x01(\x04R\vminHtlcMsat\x125\n" + - "\x17min_htlc_msat_specified\x18\b \x01(\bR\x14minHtlcMsatSpecified\x122\n" + - "\vinbound_fee\x18\n" + - " \x01(\v2\x11.lnrpc.InboundFeeR\n" + - "inboundFee\x12.\n" + - "\x13create_missing_edge\x18\v \x01(\bR\x11createMissingEdgeB\a\n" + - "\x05scope\"\x8c\x01\n" + - "\fFailedUpdate\x12+\n" + - "\boutpoint\x18\x01 \x01(\v2\x0f.lnrpc.OutPointR\boutpoint\x12,\n" + - "\x06reason\x18\x02 \x01(\x0e2\x14.lnrpc.UpdateFailureR\x06reason\x12!\n" + - "\fupdate_error\x18\x03 \x01(\tR\vupdateError\"R\n" + - "\x14PolicyUpdateResponse\x12:\n" + - "\x0efailed_updates\x18\x01 \x03(\v2\x13.lnrpc.FailedUpdateR\rfailedUpdates\"\xa1\x02\n" + - "\x18ForwardingHistoryRequest\x12\x1d\n" + - "\n" + - "start_time\x18\x01 \x01(\x04R\tstartTime\x12\x19\n" + - "\bend_time\x18\x02 \x01(\x04R\aendTime\x12!\n" + - "\findex_offset\x18\x03 \x01(\rR\vindexOffset\x12$\n" + - "\x0enum_max_events\x18\x04 \x01(\rR\fnumMaxEvents\x12*\n" + - "\x11peer_alias_lookup\x18\x05 \x01(\bR\x0fpeerAliasLookup\x12*\n" + - "\x11incoming_chan_ids\x18\x06 \x03(\x04R\x0fincomingChanIds\x12*\n" + - "\x11outgoing_chan_ids\x18\a \x03(\x04R\x0foutgoingChanIds\"\x8d\x04\n" + - "\x0fForwardingEvent\x12 \n" + - "\ttimestamp\x18\x01 \x01(\x04B\x02\x18\x01R\ttimestamp\x12 \n" + - "\n" + - "chan_id_in\x18\x02 \x01(\x04B\x020\x01R\bchanIdIn\x12\"\n" + - "\vchan_id_out\x18\x04 \x01(\x04B\x020\x01R\tchanIdOut\x12\x15\n" + - "\x06amt_in\x18\x05 \x01(\x04R\x05amtIn\x12\x17\n" + - "\aamt_out\x18\x06 \x01(\x04R\x06amtOut\x12\x10\n" + - "\x03fee\x18\a \x01(\x04R\x03fee\x12\x19\n" + - "\bfee_msat\x18\b \x01(\x04R\afeeMsat\x12\x1e\n" + - "\vamt_in_msat\x18\t \x01(\x04R\tamtInMsat\x12 \n" + - "\famt_out_msat\x18\n" + - " \x01(\x04R\n" + - "amtOutMsat\x12!\n" + - "\ftimestamp_ns\x18\v \x01(\x04R\vtimestampNs\x12\"\n" + - "\rpeer_alias_in\x18\f \x01(\tR\vpeerAliasIn\x12$\n" + - "\x0epeer_alias_out\x18\r \x01(\tR\fpeerAliasOut\x12-\n" + - "\x10incoming_htlc_id\x18\x0e \x01(\x04H\x00R\x0eincomingHtlcId\x88\x01\x01\x12-\n" + - "\x10outgoing_htlc_id\x18\x0f \x01(\x04H\x01R\x0eoutgoingHtlcId\x88\x01\x01B\x13\n" + - "\x11_incoming_htlc_idB\x13\n" + - "\x11_outgoing_htlc_id\"\x8c\x01\n" + - "\x19ForwardingHistoryResponse\x12C\n" + - "\x11forwarding_events\x18\x01 \x03(\v2\x16.lnrpc.ForwardingEventR\x10forwardingEvents\x12*\n" + - "\x11last_offset_index\x18\x02 \x01(\rR\x0flastOffsetIndex\"P\n" + - "\x1aExportChannelBackupRequest\x122\n" + - "\n" + - "chan_point\x18\x01 \x01(\v2\x13.lnrpc.ChannelPointR\tchanPoint\"d\n" + - "\rChannelBackup\x122\n" + - "\n" + - "chan_point\x18\x01 \x01(\v2\x13.lnrpc.ChannelPointR\tchanPoint\x12\x1f\n" + - "\vchan_backup\x18\x02 \x01(\fR\n" + - "chanBackup\"s\n" + - "\x0fMultiChanBackup\x124\n" + - "\vchan_points\x18\x01 \x03(\v2\x13.lnrpc.ChannelPointR\n" + - "chanPoints\x12*\n" + - "\x11multi_chan_backup\x18\x02 \x01(\fR\x0fmultiChanBackup\"\x19\n" + - "\x17ChanBackupExportRequest\"\x9f\x01\n" + - "\x12ChanBackupSnapshot\x12E\n" + - "\x13single_chan_backups\x18\x01 \x01(\v2\x15.lnrpc.ChannelBackupsR\x11singleChanBackups\x12B\n" + - "\x11multi_chan_backup\x18\x02 \x01(\v2\x16.lnrpc.MultiChanBackupR\x0fmultiChanBackup\"I\n" + - "\x0eChannelBackups\x127\n" + - "\fchan_backups\x18\x01 \x03(\v2\x14.lnrpc.ChannelBackupR\vchanBackups\"\x8e\x01\n" + - "\x18RestoreChanBackupRequest\x12:\n" + - "\fchan_backups\x18\x01 \x01(\v2\x15.lnrpc.ChannelBackupsH\x00R\vchanBackups\x12,\n" + - "\x11multi_chan_backup\x18\x02 \x01(\fH\x00R\x0fmultiChanBackupB\b\n" + - "\x06backup\":\n" + - "\x15RestoreBackupResponse\x12!\n" + - "\fnum_restored\x18\x01 \x01(\rR\vnumRestored\"\x1b\n" + - "\x19ChannelBackupSubscription\";\n" + - "\x18VerifyChanBackupResponse\x12\x1f\n" + - "\vchan_points\x18\x01 \x03(\tR\n" + - "chanPoints\"D\n" + - "\x12MacaroonPermission\x12\x16\n" + - "\x06entity\x18\x01 \x01(\tR\x06entity\x12\x16\n" + - "\x06action\x18\x02 \x01(\tR\x06action\"\xb0\x01\n" + - "\x13BakeMacaroonRequest\x12;\n" + - "\vpermissions\x18\x01 \x03(\v2\x19.lnrpc.MacaroonPermissionR\vpermissions\x12\x1e\n" + - "\vroot_key_id\x18\x02 \x01(\x04R\trootKeyId\x12<\n" + - "\x1aallow_external_permissions\x18\x03 \x01(\bR\x18allowExternalPermissions\"2\n" + - "\x14BakeMacaroonResponse\x12\x1a\n" + - "\bmacaroon\x18\x01 \x01(\tR\bmacaroon\"\x18\n" + - "\x16ListMacaroonIDsRequest\";\n" + - "\x17ListMacaroonIDsResponse\x12 \n" + - "\froot_key_ids\x18\x01 \x03(\x04R\n" + - "rootKeyIds\"9\n" + - "\x17DeleteMacaroonIDRequest\x12\x1e\n" + - "\vroot_key_id\x18\x01 \x01(\x04R\trootKeyId\"4\n" + - "\x18DeleteMacaroonIDResponse\x12\x18\n" + - "\adeleted\x18\x01 \x01(\bR\adeleted\"U\n" + - "\x16MacaroonPermissionList\x12;\n" + - "\vpermissions\x18\x01 \x03(\v2\x19.lnrpc.MacaroonPermissionR\vpermissions\"\x18\n" + - "\x16ListPermissionsRequest\"\xe4\x01\n" + - "\x17ListPermissionsResponse\x12d\n" + - "\x12method_permissions\x18\x01 \x03(\v25.lnrpc.ListPermissionsResponse.MethodPermissionsEntryR\x11methodPermissions\x1ac\n" + - "\x16MethodPermissionsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x123\n" + - "\x05value\x18\x02 \x01(\v2\x1d.lnrpc.MacaroonPermissionListR\x05value:\x028\x01\"\xcc\b\n" + - "\aFailure\x12.\n" + - "\x04code\x18\x01 \x01(\x0e2\x1a.lnrpc.Failure.FailureCodeR\x04code\x12;\n" + - "\x0echannel_update\x18\x03 \x01(\v2\x14.lnrpc.ChannelUpdateR\rchannelUpdate\x12\x1b\n" + - "\thtlc_msat\x18\x04 \x01(\x04R\bhtlcMsat\x12\"\n" + - "\ronion_sha_256\x18\x05 \x01(\fR\vonionSha256\x12\x1f\n" + - "\vcltv_expiry\x18\x06 \x01(\rR\n" + - "cltvExpiry\x12\x14\n" + - "\x05flags\x18\a \x01(\rR\x05flags\x120\n" + - "\x14failure_source_index\x18\b \x01(\rR\x12failureSourceIndex\x12\x16\n" + - "\x06height\x18\t \x01(\rR\x06height\"\x8b\x06\n" + - "\vFailureCode\x12\f\n" + - "\bRESERVED\x10\x00\x12(\n" + - "$INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS\x10\x01\x12\x1c\n" + - "\x18INCORRECT_PAYMENT_AMOUNT\x10\x02\x12\x1f\n" + - "\x1bFINAL_INCORRECT_CLTV_EXPIRY\x10\x03\x12\x1f\n" + - "\x1bFINAL_INCORRECT_HTLC_AMOUNT\x10\x04\x12\x19\n" + - "\x15FINAL_EXPIRY_TOO_SOON\x10\x05\x12\x11\n" + - "\rINVALID_REALM\x10\x06\x12\x13\n" + - "\x0fEXPIRY_TOO_SOON\x10\a\x12\x19\n" + - "\x15INVALID_ONION_VERSION\x10\b\x12\x16\n" + - "\x12INVALID_ONION_HMAC\x10\t\x12\x15\n" + - "\x11INVALID_ONION_KEY\x10\n" + - "\x12\x18\n" + - "\x14AMOUNT_BELOW_MINIMUM\x10\v\x12\x14\n" + - "\x10FEE_INSUFFICIENT\x10\f\x12\x19\n" + - "\x15INCORRECT_CLTV_EXPIRY\x10\r\x12\x14\n" + - "\x10CHANNEL_DISABLED\x10\x0e\x12\x1d\n" + - "\x19TEMPORARY_CHANNEL_FAILURE\x10\x0f\x12!\n" + - "\x1dREQUIRED_NODE_FEATURE_MISSING\x10\x10\x12$\n" + - " REQUIRED_CHANNEL_FEATURE_MISSING\x10\x11\x12\x15\n" + - "\x11UNKNOWN_NEXT_PEER\x10\x12\x12\x1a\n" + - "\x16TEMPORARY_NODE_FAILURE\x10\x13\x12\x1a\n" + - "\x16PERMANENT_NODE_FAILURE\x10\x14\x12\x1d\n" + - "\x19PERMANENT_CHANNEL_FAILURE\x10\x15\x12\x12\n" + - "\x0eEXPIRY_TOO_FAR\x10\x16\x12\x0f\n" + - "\vMPP_TIMEOUT\x10\x17\x12\x19\n" + - "\x15INVALID_ONION_PAYLOAD\x10\x18\x12\x1a\n" + - "\x16INVALID_ONION_BLINDING\x10\x19\x12\x15\n" + - "\x10INTERNAL_FAILURE\x10\xe5\a\x12\x14\n" + - "\x0fUNKNOWN_FAILURE\x10\xe6\a\x12\x17\n" + - "\x12UNREADABLE_FAILURE\x10\xe7\aJ\x04\b\x02\x10\x03\"\xb3\x03\n" + - "\rChannelUpdate\x12\x1c\n" + - "\tsignature\x18\x01 \x01(\fR\tsignature\x12\x1d\n" + - "\n" + - "chain_hash\x18\x02 \x01(\fR\tchainHash\x12\x1b\n" + - "\achan_id\x18\x03 \x01(\x04B\x020\x01R\x06chanId\x12\x1c\n" + - "\ttimestamp\x18\x04 \x01(\rR\ttimestamp\x12#\n" + - "\rmessage_flags\x18\n" + - " \x01(\rR\fmessageFlags\x12#\n" + - "\rchannel_flags\x18\x05 \x01(\rR\fchannelFlags\x12&\n" + - "\x0ftime_lock_delta\x18\x06 \x01(\rR\rtimeLockDelta\x12*\n" + - "\x11htlc_minimum_msat\x18\a \x01(\x04R\x0fhtlcMinimumMsat\x12\x19\n" + - "\bbase_fee\x18\b \x01(\rR\abaseFee\x12\x19\n" + - "\bfee_rate\x18\t \x01(\rR\afeeRate\x12*\n" + - "\x11htlc_maximum_msat\x18\v \x01(\x04R\x0fhtlcMaximumMsat\x12*\n" + - "\x11extra_opaque_data\x18\f \x01(\fR\x0fextraOpaqueData\"]\n" + - "\n" + - "MacaroonId\x12\x14\n" + - "\x05nonce\x18\x01 \x01(\fR\x05nonce\x12\x1c\n" + - "\tstorageId\x18\x02 \x01(\fR\tstorageId\x12\x1b\n" + - "\x03ops\x18\x03 \x03(\v2\t.lnrpc.OpR\x03ops\"6\n" + - "\x02Op\x12\x16\n" + - "\x06entity\x18\x01 \x01(\tR\x06entity\x12\x18\n" + - "\aactions\x18\x02 \x03(\tR\aactions\"\xdd\x01\n" + - "\x13CheckMacPermRequest\x12\x1a\n" + - "\bmacaroon\x18\x01 \x01(\fR\bmacaroon\x12;\n" + - "\vpermissions\x18\x02 \x03(\v2\x19.lnrpc.MacaroonPermissionR\vpermissions\x12\x1e\n" + - "\n" + - "fullMethod\x18\x03 \x01(\tR\n" + - "fullMethod\x12M\n" + - "$check_default_perms_from_full_method\x18\x04 \x01(\bR\x1fcheckDefaultPermsFromFullMethod\",\n" + - "\x14CheckMacPermResponse\x12\x14\n" + - "\x05valid\x18\x01 \x01(\bR\x05valid\"\xa4\x04\n" + - "\x14RPCMiddlewareRequest\x12\x1d\n" + - "\n" + - "request_id\x18\x01 \x01(\x04R\trequestId\x12!\n" + - "\fraw_macaroon\x18\x02 \x01(\fR\vrawMacaroon\x126\n" + - "\x17custom_caveat_condition\x18\x03 \x01(\tR\x15customCaveatCondition\x124\n" + - "\vstream_auth\x18\x04 \x01(\v2\x11.lnrpc.StreamAuthH\x00R\n" + - "streamAuth\x12-\n" + - "\arequest\x18\x05 \x01(\v2\x11.lnrpc.RPCMessageH\x00R\arequest\x12/\n" + - "\bresponse\x18\x06 \x01(\v2\x11.lnrpc.RPCMessageH\x00R\bresponse\x12#\n" + - "\freg_complete\x18\b \x01(\bH\x00R\vregComplete\x12\x15\n" + - "\x06msg_id\x18\a \x01(\x04R\x05msgId\x12U\n" + - "\x0emetadata_pairs\x18\t \x03(\v2..lnrpc.RPCMiddlewareRequest.MetadataPairsEntryR\rmetadataPairs\x1aW\n" + - "\x12MetadataPairsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x12+\n" + - "\x05value\x18\x02 \x01(\v2\x15.lnrpc.MetadataValuesR\x05value:\x028\x01B\x10\n" + - "\x0eintercept_type\"(\n" + - "\x0eMetadataValues\x12\x16\n" + - "\x06values\x18\x01 \x03(\tR\x06values\"4\n" + - "\n" + - "StreamAuth\x12&\n" + - "\x0fmethod_full_uri\x18\x01 \x01(\tR\rmethodFullUri\"\xab\x01\n" + - "\n" + - "RPCMessage\x12&\n" + - "\x0fmethod_full_uri\x18\x01 \x01(\tR\rmethodFullUri\x12\x1d\n" + - "\n" + - "stream_rpc\x18\x02 \x01(\bR\tstreamRpc\x12\x1b\n" + - "\ttype_name\x18\x03 \x01(\tR\btypeName\x12\x1e\n" + - "\n" + - "serialized\x18\x04 \x01(\fR\n" + - "serialized\x12\x19\n" + - "\bis_error\x18\x05 \x01(\bR\aisError\"\xc0\x01\n" + - "\x15RPCMiddlewareResponse\x12\x1c\n" + - "\n" + - "ref_msg_id\x18\x01 \x01(\x04R\brefMsgId\x12;\n" + - "\bregister\x18\x02 \x01(\v2\x1d.lnrpc.MiddlewareRegistrationH\x00R\bregister\x126\n" + - "\bfeedback\x18\x03 \x01(\v2\x18.lnrpc.InterceptFeedbackH\x00R\bfeedbackB\x14\n" + - "\x12middleware_message\"\xa6\x01\n" + - "\x16MiddlewareRegistration\x12'\n" + - "\x0fmiddleware_name\x18\x01 \x01(\tR\x0emiddlewareName\x12=\n" + - "\x1bcustom_macaroon_caveat_name\x18\x02 \x01(\tR\x18customMacaroonCaveatName\x12$\n" + - "\x0eread_only_mode\x18\x03 \x01(\bR\freadOnlyMode\"\x8b\x01\n" + - "\x11InterceptFeedback\x12\x14\n" + - "\x05error\x18\x01 \x01(\tR\x05error\x12)\n" + - "\x10replace_response\x18\x02 \x01(\bR\x0freplaceResponse\x125\n" + - "\x16replacement_serialized\x18\x03 \x01(\fR\x15replacementSerialized*\xcb\x02\n" + - "\x10OutputScriptType\x12\x1b\n" + - "\x17SCRIPT_TYPE_PUBKEY_HASH\x10\x00\x12\x1b\n" + - "\x17SCRIPT_TYPE_SCRIPT_HASH\x10\x01\x12&\n" + - "\"SCRIPT_TYPE_WITNESS_V0_PUBKEY_HASH\x10\x02\x12&\n" + - "\"SCRIPT_TYPE_WITNESS_V0_SCRIPT_HASH\x10\x03\x12\x16\n" + - "\x12SCRIPT_TYPE_PUBKEY\x10\x04\x12\x18\n" + - "\x14SCRIPT_TYPE_MULTISIG\x10\x05\x12\x18\n" + - "\x14SCRIPT_TYPE_NULLDATA\x10\x06\x12\x1c\n" + - "\x18SCRIPT_TYPE_NON_STANDARD\x10\a\x12\x1f\n" + - "\x1bSCRIPT_TYPE_WITNESS_UNKNOWN\x10\b\x12\"\n" + - "\x1eSCRIPT_TYPE_WITNESS_V1_TAPROOT\x10\t*b\n" + - "\x15CoinSelectionStrategy\x12\x1e\n" + - "\x1aSTRATEGY_USE_GLOBAL_CONFIG\x10\x00\x12\x14\n" + - "\x10STRATEGY_LARGEST\x10\x01\x12\x13\n" + - "\x0fSTRATEGY_RANDOM\x10\x02*\xac\x01\n" + - "\vAddressType\x12\x17\n" + - "\x13WITNESS_PUBKEY_HASH\x10\x00\x12\x16\n" + - "\x12NESTED_PUBKEY_HASH\x10\x01\x12\x1e\n" + - "\x1aUNUSED_WITNESS_PUBKEY_HASH\x10\x02\x12\x1d\n" + - "\x19UNUSED_NESTED_PUBKEY_HASH\x10\x03\x12\x12\n" + - "\x0eTAPROOT_PUBKEY\x10\x04\x12\x19\n" + - "\x15UNUSED_TAPROOT_PUBKEY\x10\x05*\xd3\x01\n" + - "\x0eCommitmentType\x12\x1b\n" + - "\x17UNKNOWN_COMMITMENT_TYPE\x10\x00\x12\n" + - "\n" + - "\x06LEGACY\x10\x01\x12\x15\n" + - "\x11STATIC_REMOTE_KEY\x10\x02\x12\v\n" + - "\aANCHORS\x10\x03\x12\x19\n" + - "\x15SCRIPT_ENFORCED_LEASE\x10\x04\x12\v\n" + - "\aTAPROOT\x10\a\x12\x18\n" + - "\x14SIMPLE_TAPROOT_FINAL\x10\a\x12\x12\n" + - "\x0eSIMPLE_TAPROOT\x10\x05\x12\x1a\n" + - "\x16SIMPLE_TAPROOT_OVERLAY\x10\x06\x1a\x02\x10\x01*a\n" + - "\tInitiator\x12\x15\n" + - "\x11INITIATOR_UNKNOWN\x10\x00\x12\x13\n" + - "\x0fINITIATOR_LOCAL\x10\x01\x12\x14\n" + - "\x10INITIATOR_REMOTE\x10\x02\x12\x12\n" + - "\x0eINITIATOR_BOTH\x10\x03*`\n" + - "\x0eResolutionType\x12\x10\n" + - "\fTYPE_UNKNOWN\x10\x00\x12\n" + - "\n" + - "\x06ANCHOR\x10\x01\x12\x11\n" + - "\rINCOMING_HTLC\x10\x02\x12\x11\n" + - "\rOUTGOING_HTLC\x10\x03\x12\n" + - "\n" + - "\x06COMMIT\x10\x04*q\n" + - "\x11ResolutionOutcome\x12\x13\n" + - "\x0fOUTCOME_UNKNOWN\x10\x00\x12\v\n" + - "\aCLAIMED\x10\x01\x12\r\n" + - "\tUNCLAIMED\x10\x02\x12\r\n" + - "\tABANDONED\x10\x03\x12\x0f\n" + - "\vFIRST_STAGE\x10\x04\x12\v\n" + - "\aTIMEOUT\x10\x05*\x91\x01\n" + - "\x10GraphCacheStatus\x12\x1f\n" + - "\x1bGRAPH_CACHE_STATUS_DISABLED\x10\x00\x12\x1e\n" + - "\x1aGRAPH_CACHE_STATUS_LOADING\x10\x01\x12\x1d\n" + - "\x19GRAPH_CACHE_STATUS_LOADED\x10\x02\x12\x1d\n" + - "\x19GRAPH_CACHE_STATUS_FAILED\x10\x03*9\n" + - "\x0eNodeMetricType\x12\v\n" + - "\aUNKNOWN\x10\x00\x12\x1a\n" + - "\x16BETWEENNESS_CENTRALITY\x10\x01*;\n" + - "\x10InvoiceHTLCState\x12\f\n" + - "\bACCEPTED\x10\x00\x12\v\n" + - "\aSETTLED\x10\x01\x12\f\n" + - "\bCANCELED\x10\x02*\xf6\x01\n" + - "\x14PaymentFailureReason\x12\x17\n" + - "\x13FAILURE_REASON_NONE\x10\x00\x12\x1a\n" + - "\x16FAILURE_REASON_TIMEOUT\x10\x01\x12\x1b\n" + - "\x17FAILURE_REASON_NO_ROUTE\x10\x02\x12\x18\n" + - "\x14FAILURE_REASON_ERROR\x10\x03\x12,\n" + - "(FAILURE_REASON_INCORRECT_PAYMENT_DETAILS\x10\x04\x12'\n" + - "#FAILURE_REASON_INSUFFICIENT_BALANCE\x10\x05\x12\x1b\n" + - "\x17FAILURE_REASON_CANCELED\x10\x06*\x89\x05\n" + - "\n" + - "FeatureBit\x12\x18\n" + - "\x14DATALOSS_PROTECT_REQ\x10\x00\x12\x18\n" + - "\x14DATALOSS_PROTECT_OPT\x10\x01\x12\x17\n" + - "\x13INITIAL_ROUING_SYNC\x10\x03\x12\x1f\n" + - "\x1bUPFRONT_SHUTDOWN_SCRIPT_REQ\x10\x04\x12\x1f\n" + - "\x1bUPFRONT_SHUTDOWN_SCRIPT_OPT\x10\x05\x12\x16\n" + - "\x12GOSSIP_QUERIES_REQ\x10\x06\x12\x16\n" + - "\x12GOSSIP_QUERIES_OPT\x10\a\x12\x11\n" + - "\rTLV_ONION_REQ\x10\b\x12\x11\n" + - "\rTLV_ONION_OPT\x10\t\x12\x1a\n" + - "\x16EXT_GOSSIP_QUERIES_REQ\x10\n" + - "\x12\x1a\n" + - "\x16EXT_GOSSIP_QUERIES_OPT\x10\v\x12\x19\n" + - "\x15STATIC_REMOTE_KEY_REQ\x10\f\x12\x19\n" + - "\x15STATIC_REMOTE_KEY_OPT\x10\r\x12\x14\n" + - "\x10PAYMENT_ADDR_REQ\x10\x0e\x12\x14\n" + - "\x10PAYMENT_ADDR_OPT\x10\x0f\x12\v\n" + - "\aMPP_REQ\x10\x10\x12\v\n" + - "\aMPP_OPT\x10\x11\x12\x16\n" + - "\x12WUMBO_CHANNELS_REQ\x10\x12\x12\x16\n" + - "\x12WUMBO_CHANNELS_OPT\x10\x13\x12\x0f\n" + - "\vANCHORS_REQ\x10\x14\x12\x0f\n" + - "\vANCHORS_OPT\x10\x15\x12\x1d\n" + - "\x19ANCHORS_ZERO_FEE_HTLC_REQ\x10\x16\x12\x1d\n" + - "\x19ANCHORS_ZERO_FEE_HTLC_OPT\x10\x17\x12\x1b\n" + - "\x17ROUTE_BLINDING_REQUIRED\x10\x18\x12\x1b\n" + - "\x17ROUTE_BLINDING_OPTIONAL\x10\x19\x12\v\n" + - "\aAMP_REQ\x10\x1e\x12\v\n" + - "\aAMP_OPT\x10\x1f*\xac\x01\n" + - "\rUpdateFailure\x12\x1a\n" + - "\x16UPDATE_FAILURE_UNKNOWN\x10\x00\x12\x1a\n" + - "\x16UPDATE_FAILURE_PENDING\x10\x01\x12\x1c\n" + - "\x18UPDATE_FAILURE_NOT_FOUND\x10\x02\x12\x1f\n" + - "\x1bUPDATE_FAILURE_INTERNAL_ERR\x10\x03\x12$\n" + - " UPDATE_FAILURE_INVALID_PARAMETER\x10\x042\xb9'\n" + - "\tLightning\x12J\n" + - "\rWalletBalance\x12\x1b.lnrpc.WalletBalanceRequest\x1a\x1c.lnrpc.WalletBalanceResponse\x12M\n" + - "\x0eChannelBalance\x12\x1c.lnrpc.ChannelBalanceRequest\x1a\x1d.lnrpc.ChannelBalanceResponse\x12K\n" + - "\x0fGetTransactions\x12\x1d.lnrpc.GetTransactionsRequest\x1a\x19.lnrpc.TransactionDetails\x12D\n" + - "\vEstimateFee\x12\x19.lnrpc.EstimateFeeRequest\x1a\x1a.lnrpc.EstimateFeeResponse\x12>\n" + - "\tSendCoins\x12\x17.lnrpc.SendCoinsRequest\x1a\x18.lnrpc.SendCoinsResponse\x12D\n" + - "\vListUnspent\x12\x19.lnrpc.ListUnspentRequest\x1a\x1a.lnrpc.ListUnspentResponse\x12L\n" + - "\x15SubscribeTransactions\x12\x1d.lnrpc.GetTransactionsRequest\x1a\x12.lnrpc.Transaction0\x01\x12;\n" + - "\bSendMany\x12\x16.lnrpc.SendManyRequest\x1a\x17.lnrpc.SendManyResponse\x12A\n" + - "\n" + - "NewAddress\x12\x18.lnrpc.NewAddressRequest\x1a\x19.lnrpc.NewAddressResponse\x12D\n" + - "\vSignMessage\x12\x19.lnrpc.SignMessageRequest\x1a\x1a.lnrpc.SignMessageResponse\x12J\n" + - "\rVerifyMessage\x12\x1b.lnrpc.VerifyMessageRequest\x1a\x1c.lnrpc.VerifyMessageResponse\x12D\n" + - "\vConnectPeer\x12\x19.lnrpc.ConnectPeerRequest\x1a\x1a.lnrpc.ConnectPeerResponse\x12M\n" + - "\x0eDisconnectPeer\x12\x1c.lnrpc.DisconnectPeerRequest\x1a\x1d.lnrpc.DisconnectPeerResponse\x12>\n" + - "\tListPeers\x12\x17.lnrpc.ListPeersRequest\x1a\x18.lnrpc.ListPeersResponse\x12G\n" + - "\x13SubscribePeerEvents\x12\x1c.lnrpc.PeerEventSubscription\x1a\x10.lnrpc.PeerEvent0\x01\x128\n" + - "\aGetInfo\x12\x15.lnrpc.GetInfoRequest\x1a\x16.lnrpc.GetInfoResponse\x12G\n" + - "\fGetDebugInfo\x12\x1a.lnrpc.GetDebugInfoRequest\x1a\x1b.lnrpc.GetDebugInfoResponse\x12P\n" + - "\x0fGetRecoveryInfo\x12\x1d.lnrpc.GetRecoveryInfoRequest\x1a\x1e.lnrpc.GetRecoveryInfoResponse\x12P\n" + - "\x0fPendingChannels\x12\x1d.lnrpc.PendingChannelsRequest\x1a\x1e.lnrpc.PendingChannelsResponse\x12G\n" + - "\fListChannels\x12\x1a.lnrpc.ListChannelsRequest\x1a\x1b.lnrpc.ListChannelsResponse\x12V\n" + - "\x16SubscribeChannelEvents\x12\x1f.lnrpc.ChannelEventSubscription\x1a\x19.lnrpc.ChannelEventUpdate0\x01\x12M\n" + - "\x0eClosedChannels\x12\x1c.lnrpc.ClosedChannelsRequest\x1a\x1d.lnrpc.ClosedChannelsResponse\x12A\n" + - "\x0fOpenChannelSync\x12\x19.lnrpc.OpenChannelRequest\x1a\x13.lnrpc.ChannelPoint\x12C\n" + - "\vOpenChannel\x12\x19.lnrpc.OpenChannelRequest\x1a\x17.lnrpc.OpenStatusUpdate0\x01\x12S\n" + - "\x10BatchOpenChannel\x12\x1e.lnrpc.BatchOpenChannelRequest\x1a\x1f.lnrpc.BatchOpenChannelResponse\x12L\n" + - "\x10FundingStateStep\x12\x1b.lnrpc.FundingTransitionMsg\x1a\x1b.lnrpc.FundingStateStepResp\x12P\n" + - "\x0fChannelAcceptor\x12\x1c.lnrpc.ChannelAcceptResponse\x1a\x1b.lnrpc.ChannelAcceptRequest(\x010\x01\x12F\n" + - "\fCloseChannel\x12\x1a.lnrpc.CloseChannelRequest\x1a\x18.lnrpc.CloseStatusUpdate0\x01\x12M\n" + - "\x0eAbandonChannel\x12\x1c.lnrpc.AbandonChannelRequest\x1a\x1d.lnrpc.AbandonChannelResponse\x127\n" + - "\n" + - "AddInvoice\x12\x0e.lnrpc.Invoice\x1a\x19.lnrpc.AddInvoiceResponse\x12E\n" + - "\fListInvoices\x12\x19.lnrpc.ListInvoiceRequest\x1a\x1a.lnrpc.ListInvoiceResponse\x123\n" + - "\rLookupInvoice\x12\x12.lnrpc.PaymentHash\x1a\x0e.lnrpc.Invoice\x12A\n" + - "\x11SubscribeInvoices\x12\x1a.lnrpc.InvoiceSubscription\x1a\x0e.lnrpc.Invoice0\x01\x12T\n" + - "\x15DeleteCanceledInvoice\x12\x1c.lnrpc.DelCanceledInvoiceReq\x1a\x1d.lnrpc.DelCanceledInvoiceResp\x122\n" + - "\fDecodePayReq\x12\x13.lnrpc.PayReqString\x1a\r.lnrpc.PayReq\x12G\n" + - "\fListPayments\x12\x1a.lnrpc.ListPaymentsRequest\x1a\x1b.lnrpc.ListPaymentsResponse\x12J\n" + - "\rDeletePayment\x12\x1b.lnrpc.DeletePaymentRequest\x1a\x1c.lnrpc.DeletePaymentResponse\x12V\n" + - "\x11DeleteAllPayments\x12\x1f.lnrpc.DeleteAllPaymentsRequest\x1a .lnrpc.DeleteAllPaymentsResponse\x12@\n" + - "\rDescribeGraph\x12\x1a.lnrpc.ChannelGraphRequest\x1a\x13.lnrpc.ChannelGraph\x12G\n" + - "\x0eGetNodeMetrics\x12\x19.lnrpc.NodeMetricsRequest\x1a\x1a.lnrpc.NodeMetricsResponse\x129\n" + - "\vGetChanInfo\x12\x16.lnrpc.ChanInfoRequest\x1a\x12.lnrpc.ChannelEdge\x126\n" + - "\vGetNodeInfo\x12\x16.lnrpc.NodeInfoRequest\x1a\x0f.lnrpc.NodeInfo\x12D\n" + - "\vQueryRoutes\x12\x19.lnrpc.QueryRoutesRequest\x1a\x1a.lnrpc.QueryRoutesResponse\x12?\n" + - "\x0eGetNetworkInfo\x12\x19.lnrpc.NetworkInfoRequest\x1a\x12.lnrpc.NetworkInfo\x125\n" + - "\n" + - "StopDaemon\x12\x12.lnrpc.StopRequest\x1a\x13.lnrpc.StopResponse\x12W\n" + - "\x15SubscribeChannelGraph\x12 .lnrpc.GraphTopologySubscription\x1a\x1a.lnrpc.GraphTopologyUpdate0\x01\x12A\n" + - "\n" + - "DebugLevel\x12\x18.lnrpc.DebugLevelRequest\x1a\x19.lnrpc.DebugLevelResponse\x12>\n" + - "\tFeeReport\x12\x17.lnrpc.FeeReportRequest\x1a\x18.lnrpc.FeeReportResponse\x12N\n" + - "\x13UpdateChannelPolicy\x12\x1a.lnrpc.PolicyUpdateRequest\x1a\x1b.lnrpc.PolicyUpdateResponse\x12V\n" + - "\x11ForwardingHistory\x12\x1f.lnrpc.ForwardingHistoryRequest\x1a .lnrpc.ForwardingHistoryResponse\x12N\n" + - "\x13ExportChannelBackup\x12!.lnrpc.ExportChannelBackupRequest\x1a\x14.lnrpc.ChannelBackup\x12T\n" + - "\x17ExportAllChannelBackups\x12\x1e.lnrpc.ChanBackupExportRequest\x1a\x19.lnrpc.ChanBackupSnapshot\x12N\n" + - "\x10VerifyChanBackup\x12\x19.lnrpc.ChanBackupSnapshot\x1a\x1f.lnrpc.VerifyChanBackupResponse\x12V\n" + - "\x15RestoreChannelBackups\x12\x1f.lnrpc.RestoreChanBackupRequest\x1a\x1c.lnrpc.RestoreBackupResponse\x12X\n" + - "\x17SubscribeChannelBackups\x12 .lnrpc.ChannelBackupSubscription\x1a\x19.lnrpc.ChanBackupSnapshot0\x01\x12G\n" + - "\fBakeMacaroon\x12\x1a.lnrpc.BakeMacaroonRequest\x1a\x1b.lnrpc.BakeMacaroonResponse\x12P\n" + - "\x0fListMacaroonIDs\x12\x1d.lnrpc.ListMacaroonIDsRequest\x1a\x1e.lnrpc.ListMacaroonIDsResponse\x12S\n" + - "\x10DeleteMacaroonID\x12\x1e.lnrpc.DeleteMacaroonIDRequest\x1a\x1f.lnrpc.DeleteMacaroonIDResponse\x12P\n" + - "\x0fListPermissions\x12\x1d.lnrpc.ListPermissionsRequest\x1a\x1e.lnrpc.ListPermissionsResponse\x12S\n" + - "\x18CheckMacaroonPermissions\x12\x1a.lnrpc.CheckMacPermRequest\x1a\x1b.lnrpc.CheckMacPermResponse\x12V\n" + - "\x15RegisterRPCMiddleware\x12\x1c.lnrpc.RPCMiddlewareResponse\x1a\x1b.lnrpc.RPCMiddlewareRequest(\x010\x01\x12V\n" + - "\x11SendCustomMessage\x12\x1f.lnrpc.SendCustomMessageRequest\x1a .lnrpc.SendCustomMessageResponse\x12X\n" + - "\x17SubscribeCustomMessages\x12%.lnrpc.SubscribeCustomMessagesRequest\x1a\x14.lnrpc.CustomMessage0\x01\x12S\n" + - "\x10SendOnionMessage\x12\x1e.lnrpc.SendOnionMessageRequest\x1a\x1f.lnrpc.SendOnionMessageResponse\x12[\n" + - "\x16SubscribeOnionMessages\x12$.lnrpc.SubscribeOnionMessagesRequest\x1a\x19.lnrpc.OnionMessageUpdate0\x01\x12D\n" + - "\vListAliases\x12\x19.lnrpc.ListAliasesRequest\x1a\x1a.lnrpc.ListAliasesResponse\x12_\n" + - "\x14LookupHtlcResolution\x12\".lnrpc.LookupHtlcResolutionRequest\x1a#.lnrpc.LookupHtlcResolutionResponseB'Z%github.com/lightningnetwork/lnd/lnrpcb\x06proto3" +var file_lightning_proto_rawDesc = []byte{ + 0x0a, 0x0f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x12, 0x05, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x22, 0x55, 0x0a, 0x1b, 0x4c, 0x6f, 0x6f, 0x6b, + 0x75, 0x70, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, + 0x12, 0x1d, 0x0a, 0x0a, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x68, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, + 0x54, 0x0a, 0x1c, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, + 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x18, 0x0a, 0x07, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x07, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x66, 0x66, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6f, 0x66, 0x66, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x22, 0x20, 0x0a, 0x1e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, + 0x62, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4b, 0x0a, 0x0d, 0x43, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x65, 0x65, 0x72, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x65, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, + 0x64, 0x61, 0x74, 0x61, 0x22, 0x56, 0x0a, 0x18, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x70, 0x65, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, + 0x70, 0x65, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x33, 0x0a, 0x19, + 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x22, 0xe6, 0x01, 0x0a, 0x04, 0x55, 0x74, 0x78, 0x6f, 0x12, 0x35, 0x0a, 0x0c, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x09, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x61, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6b, + 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, + 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xe0, 0x01, 0x0a, 0x0c, 0x4f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x38, 0x0a, 0x0b, 0x6f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x53, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x6f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, + 0x1b, 0x0a, 0x09, 0x70, 0x6b, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x70, 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x21, 0x0a, 0x0c, + 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, + 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x69, 0x73, 0x5f, 0x6f, 0x75, + 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0c, 0x69, 0x73, 0x4f, 0x75, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0xce, 0x03, + 0x0a, 0x0b, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, + 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2b, + 0x0a, 0x11, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x6e, 0x75, 0x6d, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, 0x0a, + 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1d, 0x0a, 0x0a, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x46, 0x65, 0x65, 0x73, 0x12, 0x29, 0x0a, 0x0e, 0x64, + 0x65, 0x73, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x08, 0x20, + 0x03, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0d, 0x64, 0x65, 0x73, 0x74, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0e, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x44, 0x65, 0x74, + 0x61, 0x69, 0x6c, 0x52, 0x0d, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, + 0x6c, 0x73, 0x12, 0x1c, 0x0a, 0x0a, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x5f, 0x68, 0x65, 0x78, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x61, 0x77, 0x54, 0x78, 0x48, 0x65, 0x78, + 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x46, 0x0a, 0x12, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, + 0x75, 0x73, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x0c, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x72, 0x65, 0x76, 0x69, + 0x6f, 0x75, 0x73, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x11, 0x70, 0x72, 0x65, + 0x76, 0x69, 0x6f, 0x75, 0x73, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x22, 0xc2, + 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, 0x0a, 0x0a, + 0x65, 0x6e, 0x64, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x09, 0x65, 0x6e, 0x64, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, + 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x69, 0x6e, 0x64, + 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x6d, 0x61, 0x78, 0x5f, + 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x0f, 0x6d, 0x61, 0x78, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x22, 0x8c, 0x01, 0x0a, 0x12, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x36, 0x0a, 0x0c, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, + 0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x66, 0x69, 0x72, 0x73, 0x74, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x22, 0x68, 0x0a, 0x08, 0x46, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, + 0x0a, 0x05, 0x66, 0x69, 0x78, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, + 0x05, 0x66, 0x69, 0x78, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0a, 0x66, 0x69, 0x78, 0x65, 0x64, 0x5f, + 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x09, 0x66, 0x69, + 0x78, 0x65, 0x64, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1a, 0x0a, 0x07, 0x70, 0x65, 0x72, 0x63, 0x65, + 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x07, 0x70, 0x65, 0x72, 0x63, + 0x65, 0x6e, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0xea, 0x05, 0x0a, + 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, + 0x64, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, + 0x12, 0x23, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x64, 0x65, 0x73, 0x74, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6d, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x03, 0x61, 0x6d, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x6d, + 0x73, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x6d, 0x74, 0x4d, 0x73, + 0x61, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, + 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x32, 0x0a, 0x13, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x11, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, + 0x61, 0x73, 0x68, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x63, 0x6c, 0x74, 0x76, + 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x66, 0x69, + 0x6e, 0x61, 0x6c, 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x2c, 0x0a, 0x09, + 0x66, 0x65, 0x65, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, + 0x52, 0x08, 0x66, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2c, 0x0a, 0x10, 0x6f, 0x75, + 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, + 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x61, 0x73, 0x74, + 0x5f, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x0d, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, + 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x63, 0x6c, 0x74, 0x76, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, + 0x59, 0x0a, 0x13, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, + 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x2e, 0x44, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x64, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x6c, + 0x6c, 0x6f, 0x77, 0x5f, 0x73, 0x65, 0x6c, 0x66, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x53, 0x65, 0x6c, + 0x66, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x0d, 0x64, 0x65, 0x73, 0x74, + 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0e, 0x32, + 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, + 0x69, 0x74, 0x52, 0x0c, 0x64, 0x65, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, + 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, + 0x18, 0x10, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, + 0x64, 0x64, 0x72, 0x1a, 0x44, 0x0a, 0x16, 0x44, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb4, 0x01, 0x0a, 0x0c, 0x53, 0x65, + 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, + 0x29, 0x0a, 0x10, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x65, 0x69, 0x6d, + 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x31, 0x0a, 0x0d, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, + 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x21, 0x0a, + 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, + 0x22, 0x95, 0x01, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x32, 0x0a, 0x13, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x11, 0x70, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x22, + 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, + 0x74, 0x65, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0xec, 0x04, 0x0a, 0x14, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x50, 0x75, 0x62, 0x6b, + 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x48, 0x61, 0x73, + 0x68, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, + 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x75, + 0x73, 0x68, 0x5f, 0x61, 0x6d, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x70, 0x75, + 0x73, 0x68, 0x41, 0x6d, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x75, 0x73, 0x74, 0x5f, 0x6c, 0x69, + 0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x64, 0x75, 0x73, 0x74, 0x4c, + 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2d, 0x0a, 0x13, 0x6d, 0x61, 0x78, 0x5f, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x5f, 0x69, 0x6e, 0x5f, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x49, 0x6e, 0x46, 0x6c, 0x69, + 0x67, 0x68, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x72, + 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x12, 0x19, 0x0a, 0x08, + 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, + 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x12, 0x1c, 0x0a, 0x0a, 0x66, 0x65, 0x65, 0x5f, 0x70, + 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x66, 0x65, 0x65, + 0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x65, 0x6c, + 0x61, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x63, 0x73, 0x76, 0x44, 0x65, 0x6c, + 0x61, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, + 0x65, 0x64, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, + 0x6d, 0x61, 0x78, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x73, + 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x6c, 0x61, 0x67, + 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, + 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x77, 0x61, 0x6e, 0x74, 0x73, 0x5f, 0x7a, + 0x65, 0x72, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, + 0x77, 0x61, 0x6e, 0x74, 0x73, 0x5a, 0x65, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x28, 0x0a, + 0x10, 0x77, 0x61, 0x6e, 0x74, 0x73, 0x5f, 0x73, 0x63, 0x69, 0x64, 0x5f, 0x61, 0x6c, 0x69, 0x61, + 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x77, 0x61, 0x6e, 0x74, 0x73, 0x53, 0x63, + 0x69, 0x64, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x22, 0x90, 0x03, 0x0a, 0x15, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, + 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x75, 0x70, 0x66, 0x72, 0x6f, + 0x6e, 0x74, 0x5f, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0f, 0x75, 0x70, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, + 0x77, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x63, 0x73, 0x76, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, + 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, + 0x12, 0x2b, 0x0a, 0x12, 0x69, 0x6e, 0x5f, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x6d, 0x61, + 0x78, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x69, 0x6e, + 0x46, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x4d, 0x61, 0x78, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x24, 0x0a, + 0x0e, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, + 0x69, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, + 0x63, 0x49, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x69, 0x6e, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x70, + 0x74, 0x5f, 0x64, 0x65, 0x70, 0x74, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6d, + 0x69, 0x6e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x44, 0x65, 0x70, 0x74, 0x68, 0x12, 0x1b, 0x0a, + 0x09, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x08, 0x7a, 0x65, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x66, 0x22, 0x9d, 0x01, 0x0a, 0x0c, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x12, 0x66, + 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x10, 0x66, 0x75, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x66, + 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x5f, 0x73, 0x74, 0x72, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0e, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x54, 0x78, 0x69, 0x64, 0x53, 0x74, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x0e, 0x0a, 0x0c, 0x66, 0x75, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x22, 0x67, 0x0a, 0x08, 0x4f, 0x75, + 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x78, 0x69, 0x64, 0x5f, 0x62, + 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x74, 0x78, 0x69, 0x64, + 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x69, 0x64, 0x5f, 0x73, 0x74, + 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x78, 0x69, 0x64, 0x53, 0x74, 0x72, + 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, + 0x64, 0x65, 0x78, 0x22, 0x52, 0x0a, 0x10, 0x50, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x4f, + 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x69, 0x73, 0x5f, 0x6f, 0x75, 0x72, 0x5f, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x4f, 0x75, + 0x72, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x3e, 0x0a, 0x10, 0x4c, 0x69, 0x67, 0x68, 0x74, + 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x70, + 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, + 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x22, 0xe7, 0x02, 0x0a, 0x12, 0x45, 0x73, 0x74, 0x69, + 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4f, + 0x0a, 0x0c, 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, + 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, + 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x0c, 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, + 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, + 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, + 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, + 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x54, 0x0a, 0x17, 0x63, 0x6f, + 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, + 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x15, 0x63, 0x6f, 0x69, 0x6e, 0x53, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, + 0x1a, 0x3f, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x22, 0x87, 0x01, 0x0a, 0x13, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x65, 0x65, + 0x5f, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x66, 0x65, 0x65, 0x53, + 0x61, 0x74, 0x12, 0x33, 0x0a, 0x14, 0x66, 0x65, 0x65, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x61, + 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x42, 0x02, 0x18, 0x01, 0x52, 0x11, 0x66, 0x65, 0x65, 0x72, 0x61, 0x74, 0x65, 0x53, 0x61, 0x74, + 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, + 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, + 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x22, 0xc1, 0x03, 0x0a, 0x0f, + 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x4c, 0x0a, 0x0c, 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, + 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x64, + 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x0c, 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, + 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x22, + 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, + 0x74, 0x65, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, + 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, + 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1b, + 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, + 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x54, 0x0a, 0x17, 0x63, 0x6f, 0x69, 0x6e, + 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, + 0x65, 0x67, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, + 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x15, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x1a, 0x3f, + 0x0a, 0x11, 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, + 0x26, 0x0a, 0x10, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x22, 0xa9, 0x03, 0x0a, 0x10, 0x53, 0x65, 0x6e, 0x64, + 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, + 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, + 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, + 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x24, 0x0a, + 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, + 0x79, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x61, 0x6c, 0x6c, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x65, 0x6e, 0x64, 0x41, 0x6c, 0x6c, 0x12, 0x14, + 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, + 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, + 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, + 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x54, + 0x0a, 0x17, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x15, 0x63, + 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, + 0x74, 0x65, 0x67, 0x79, 0x12, 0x2d, 0x0a, 0x09, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x73, 0x22, 0x27, 0x0a, 0x11, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x22, 0x68, 0x0a, 0x12, + 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, + 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x18, 0x0a, 0x07, + 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x38, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, + 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, + 0x05, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x52, 0x05, 0x75, 0x74, 0x78, 0x6f, 0x73, + 0x22, 0x55, 0x0a, 0x11, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x2e, 0x0a, 0x12, 0x4e, 0x65, 0x77, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x47, 0x0a, 0x12, 0x53, 0x69, 0x67, 0x6e, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, + 0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, + 0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x48, 0x61, 0x73, 0x68, + 0x22, 0x33, 0x0a, 0x13, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x46, 0x0a, 0x14, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, + 0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, + 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x45, 0x0a, + 0x15, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, + 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, + 0x62, 0x6b, 0x65, 0x79, 0x22, 0x6f, 0x0a, 0x12, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, + 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x04, 0x61, 0x64, + 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x65, 0x72, 0x6d, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x70, 0x65, 0x72, 0x6d, 0x12, 0x18, 0x0a, 0x07, 0x74, + 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x74, 0x69, + 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x22, 0x2d, 0x0a, 0x13, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x22, 0x30, 0x0a, 0x15, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, + 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0x30, 0x0a, 0x16, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xa3, 0x02, 0x0a, 0x04, 0x48, 0x54, 0x4c, + 0x43, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x16, 0x0a, + 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x6c, 0x6f, + 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x68, 0x61, 0x73, 0x68, 0x4c, 0x6f, + 0x63, 0x6b, 0x12, 0x2b, 0x0a, 0x11, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, + 0x1d, 0x0a, 0x0a, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x09, 0x68, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2d, + 0x0a, 0x12, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x66, 0x6f, 0x72, 0x77, + 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x32, 0x0a, + 0x15, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x74, 0x6c, 0x63, + 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x13, 0x66, 0x6f, + 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x64, 0x65, + 0x78, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x49, 0x6e, 0x22, 0x84, + 0x02, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, + 0x61, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x65, 0x6c, + 0x61, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x63, 0x73, 0x76, 0x44, 0x65, 0x6c, + 0x61, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x63, 0x68, + 0x61, 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x24, 0x0a, 0x0e, + 0x64, 0x75, 0x73, 0x74, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x64, 0x75, 0x73, 0x74, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x53, + 0x61, 0x74, 0x12, 0x2f, 0x0a, 0x14, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x11, 0x6d, 0x61, 0x78, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x74, 0x4d, + 0x73, 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, + 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x48, + 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, 0x61, + 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, + 0x48, 0x74, 0x6c, 0x63, 0x73, 0x22, 0xdd, 0x0b, 0x0a, 0x07, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6d, + 0x6f, 0x74, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x23, + 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, + 0x69, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, + 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x23, 0x0a, 0x0d, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x62, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74, + 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x46, 0x65, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, + 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1c, 0x0a, 0x0a, + 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x08, 0x66, 0x65, 0x65, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x2b, 0x0a, 0x11, 0x75, 0x6e, + 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x75, 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, + 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x5f, 0x73, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x73, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x53, 0x61, 0x74, 0x6f, 0x73, + 0x68, 0x69, 0x73, 0x53, 0x65, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x17, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x5f, 0x73, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, + 0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x53, + 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x73, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x12, + 0x1f, 0x0a, 0x0b, 0x6e, 0x75, 0x6d, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x0e, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x6e, 0x75, 0x6d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, + 0x12, 0x30, 0x0a, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x74, 0x6c, 0x63, + 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x48, 0x54, 0x4c, 0x43, 0x52, 0x0c, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, + 0x63, 0x73, 0x12, 0x1f, 0x0a, 0x09, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, + 0x10, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x63, 0x73, 0x76, 0x44, 0x65, + 0x6c, 0x61, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x11, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, + 0x09, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x09, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x63, + 0x68, 0x61, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, + 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x37, 0x0a, 0x16, 0x6c, 0x6f, 0x63, 0x61, 0x6c, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, + 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x13, 0x6c, 0x6f, 0x63, + 0x61, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, + 0x12, 0x39, 0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, + 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x15, 0x20, 0x01, 0x28, + 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x14, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, + 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x2e, 0x0a, 0x11, 0x73, + 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x16, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0f, 0x73, 0x74, 0x61, 0x74, + 0x69, 0x63, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x3e, 0x0a, 0x0f, 0x63, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x1a, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6c, + 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x17, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6c, + 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x18, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x12, + 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x19, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x75, 0x73, 0x68, 0x5f, 0x61, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x70, + 0x75, 0x73, 0x68, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x61, 0x74, 0x12, 0x1f, 0x0a, 0x0b, + 0x74, 0x68, 0x61, 0x77, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x1c, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0a, 0x74, 0x68, 0x61, 0x77, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x46, 0x0a, + 0x11, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, + 0x74, 0x73, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, + 0x6e, 0x74, 0x73, 0x52, 0x10, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, + 0x61, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x48, 0x0a, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, + 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x1e, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x52, 0x11, 0x72, 0x65, + 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x12, + 0x1f, 0x0a, 0x0b, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x73, 0x63, 0x69, 0x64, 0x73, 0x18, 0x1f, + 0x20, 0x03, 0x28, 0x04, 0x52, 0x0a, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x53, 0x63, 0x69, 0x64, 0x73, + 0x12, 0x1b, 0x0a, 0x09, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x20, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x08, 0x7a, 0x65, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x37, 0x0a, + 0x18, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x73, 0x63, 0x69, 0x64, 0x18, 0x21, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x15, 0x7a, 0x65, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x66, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, + 0x65, 0x64, 0x53, 0x63, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x61, + 0x6c, 0x69, 0x61, 0x73, 0x18, 0x22, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x65, 0x65, 0x72, + 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x2a, 0x0a, 0x0f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x73, 0x63, + 0x69, 0x64, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x23, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, + 0x30, 0x01, 0x52, 0x0d, 0x70, 0x65, 0x65, 0x72, 0x53, 0x63, 0x69, 0x64, 0x41, 0x6c, 0x69, 0x61, + 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, 0x24, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x25, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x44, 0x61, 0x74, 0x61, 0x22, 0xdf, 0x01, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, + 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x23, + 0x0a, 0x0d, 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4f, + 0x6e, 0x6c, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6f, 0x6e, + 0x6c, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, + 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x70, 0x72, 0x69, 0x76, + 0x61, 0x74, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x65, 0x65, 0x72, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x65, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x70, + 0x65, 0x65, 0x72, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x70, 0x65, 0x65, 0x72, 0x41, 0x6c, 0x69, 0x61, + 0x73, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x22, 0x42, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x2a, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0x41, 0x0a, 0x08, 0x41, + 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x61, 0x73, 0x65, 0x5f, + 0x73, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x62, 0x61, 0x73, 0x65, + 0x53, 0x63, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x04, 0x52, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x22, 0x14, + 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x22, 0x45, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, 0x61, + 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x0a, 0x61, + 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, + 0x52, 0x09, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x73, 0x22, 0xe6, 0x06, 0x0a, 0x13, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x75, 0x6d, 0x6d, + 0x61, 0x72, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, + 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, + 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x48, 0x61, 0x73, 0x68, 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, + 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, + 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x23, 0x0a, 0x0d, + 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6b, 0x65, + 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x21, 0x0a, + 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x65, 0x74, 0x74, 0x6c, + 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x74, 0x69, 0x6d, + 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, + 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x0a, 0x63, 0x6c, 0x6f, + 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6c, 0x6f, + 0x73, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x75, 0x72, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x37, 0x0a, 0x0e, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, + 0x6f, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x0d, 0x6f, 0x70, 0x65, 0x6e, + 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x39, 0x0a, 0x0f, 0x63, 0x6c, 0x6f, + 0x73, 0x65, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x0c, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, + 0x61, 0x74, 0x6f, 0x72, 0x52, 0x0e, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x49, 0x6e, 0x69, 0x74, 0x69, + 0x61, 0x74, 0x6f, 0x72, 0x12, 0x33, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x72, 0x65, + 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x6c, 0x69, + 0x61, 0x73, 0x5f, 0x73, 0x63, 0x69, 0x64, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x04, 0x52, 0x0a, + 0x61, 0x6c, 0x69, 0x61, 0x73, 0x53, 0x63, 0x69, 0x64, 0x73, 0x12, 0x3b, 0x0a, 0x18, 0x7a, 0x65, + 0x72, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, + 0x64, 0x5f, 0x73, 0x63, 0x69, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, + 0x52, 0x15, 0x7a, 0x65, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x66, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, + 0x6d, 0x65, 0x64, 0x53, 0x63, 0x69, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x10, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x22, 0x8a, 0x01, 0x0a, 0x0b, 0x43, 0x6c, 0x6f, 0x73, + 0x75, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4f, 0x50, 0x45, + 0x52, 0x41, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x00, 0x12, 0x15, + 0x0a, 0x11, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x5f, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x43, 0x4c, + 0x4f, 0x53, 0x45, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, + 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x02, 0x12, 0x10, 0x0a, + 0x0c, 0x42, 0x52, 0x45, 0x41, 0x43, 0x48, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x03, 0x12, + 0x14, 0x0a, 0x10, 0x46, 0x55, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, + 0x4c, 0x45, 0x44, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x41, 0x42, 0x41, 0x4e, 0x44, 0x4f, 0x4e, + 0x45, 0x44, 0x10, 0x05, 0x22, 0xeb, 0x01, 0x0a, 0x0a, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x54, + 0x79, 0x70, 0x65, 0x52, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x32, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x63, 0x6f, 0x6d, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x63, 0x6f, 0x6d, 0x65, 0x52, 0x07, + 0x6f, 0x75, 0x74, 0x63, 0x6f, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, + 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x53, 0x61, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x74, 0x78, 0x69, + 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x77, 0x65, 0x65, 0x70, 0x54, 0x78, + 0x69, 0x64, 0x22, 0xde, 0x01, 0x0a, 0x15, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, + 0x63, 0x6f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0b, 0x63, 0x6f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x1f, + 0x0a, 0x0b, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x12, + 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x46, 0x6f, 0x72, + 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x72, 0x65, 0x61, 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x06, 0x62, 0x72, 0x65, 0x61, 0x63, 0x68, 0x12, 0x29, 0x0a, 0x10, 0x66, 0x75, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x61, 0x6e, + 0x63, 0x65, 0x6c, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, + 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x62, 0x61, 0x6e, 0x64, 0x6f, + 0x6e, 0x65, 0x64, 0x22, 0x50, 0x0a, 0x16, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, + 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, + 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x08, 0x63, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0x8b, 0x05, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x17, + 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x62, 0x79, 0x74, 0x65, 0x73, 0x53, 0x65, 0x6e, 0x74, + 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x63, 0x76, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x62, 0x79, 0x74, 0x65, 0x73, 0x52, 0x65, 0x63, 0x76, 0x12, + 0x19, 0x0a, 0x08, 0x73, 0x61, 0x74, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x07, 0x73, 0x61, 0x74, 0x53, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x61, + 0x74, 0x5f, 0x72, 0x65, 0x63, 0x76, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x73, 0x61, + 0x74, 0x52, 0x65, 0x63, 0x76, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, + 0x1b, 0x0a, 0x09, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x08, 0x70, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x31, 0x0a, 0x09, + 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x2e, 0x53, 0x79, 0x6e, + 0x63, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x35, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x2e, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, + 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x65, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, + 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x6c, 0x61, 0x70, 0x5f, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x66, 0x6c, 0x61, + 0x70, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x66, + 0x6c, 0x61, 0x70, 0x5f, 0x6e, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x6c, 0x61, + 0x73, 0x74, 0x46, 0x6c, 0x61, 0x70, 0x4e, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, + 0x5f, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x0f, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x50, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79, + 0x6c, 0x6f, 0x61, 0x64, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x22, 0x50, 0x0a, 0x08, 0x53, 0x79, 0x6e, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, + 0x0c, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x10, 0x00, 0x12, + 0x0f, 0x0a, 0x0b, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x10, 0x01, + 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x41, 0x53, 0x53, 0x49, 0x56, 0x45, 0x5f, 0x53, 0x59, 0x4e, 0x43, + 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x49, 0x4e, 0x4e, 0x45, 0x44, 0x5f, 0x53, 0x59, 0x4e, + 0x43, 0x10, 0x03, 0x22, 0x46, 0x0a, 0x10, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x65, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x35, 0x0a, 0x10, 0x4c, + 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x21, 0x0a, 0x0c, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x22, 0x36, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, + 0x65, 0x65, 0x72, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0x17, 0x0a, 0x15, 0x50, 0x65, + 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x22, 0x84, 0x01, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2e, 0x0a, 0x09, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x45, 0x45, 0x52, 0x5f, + 0x4f, 0x4e, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x45, 0x45, 0x52, + 0x5f, 0x4f, 0x46, 0x46, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x01, 0x22, 0x10, 0x0a, 0x0e, 0x47, 0x65, + 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x82, 0x07, 0x0a, + 0x0f, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0e, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x27, 0x0a, 0x0f, 0x69, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x50, 0x75, + 0x62, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, + 0x6c, 0x6f, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, + 0x12, 0x30, 0x0a, 0x14, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, + 0x6e, 0x75, 0x6d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x75, 0x6d, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x11, 0x6e, 0x75, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x6e, 0x75, 0x6d, 0x5f, 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x13, 0x6e, 0x75, 0x6d, 0x49, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x65, + 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6e, 0x75, 0x6d, 0x50, 0x65, + 0x65, 0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, + 0x68, 0x61, 0x73, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x32, 0x0a, 0x15, 0x62, 0x65, 0x73, 0x74, 0x5f, 0x68, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0d, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x62, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x79, 0x6e, + 0x63, 0x65, 0x64, 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0d, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x54, 0x6f, 0x43, 0x68, 0x61, 0x69, + 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x5f, 0x74, 0x6f, 0x5f, 0x67, + 0x72, 0x61, 0x70, 0x68, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x79, 0x6e, 0x63, + 0x65, 0x64, 0x54, 0x6f, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x1c, 0x0a, 0x07, 0x74, 0x65, 0x73, + 0x74, 0x6e, 0x65, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, + 0x74, 0x65, 0x73, 0x74, 0x6e, 0x65, 0x74, 0x12, 0x24, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x68, 0x61, 0x69, 0x6e, 0x52, 0x06, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x12, 0x0a, + 0x04, 0x75, 0x72, 0x69, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x75, 0x72, 0x69, + 0x73, 0x12, 0x40, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x13, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x5f, 0x68, + 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x18, + 0x15, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x48, 0x74, + 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x3f, 0x0a, + 0x1c, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x68, 0x74, 0x6c, + 0x63, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x16, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x19, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x48, + 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x4b, + 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, 0x0b, 0x10, + 0x0c, 0x22, 0x15, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, + 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa4, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, + 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3f, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x27, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x62, + 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x03, 0x6c, 0x6f, 0x67, 0x1a, 0x39, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, + 0x18, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x87, 0x01, 0x0a, 0x17, 0x47, 0x65, + 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, + 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x72, 0x65, + 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, + 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x46, + 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, + 0x65, 0x73, 0x73, 0x22, 0x3b, 0x0a, 0x05, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x18, 0x0a, 0x05, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, + 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, + 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, + 0x22, 0x4d, 0x0a, 0x11, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4f, 0x70, 0x65, 0x6e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x38, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, + 0x94, 0x01, 0x0a, 0x0b, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, + 0x1d, 0x0a, 0x0a, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x09, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x61, 0x74, 0x12, 0x1b, + 0x0a, 0x09, 0x70, 0x6b, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x08, 0x70, 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x69, + 0x73, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, + 0x73, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x22, 0x9a, 0x02, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, + 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, + 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x40, 0x0a, 0x12, 0x6c, 0x6f, + 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x10, 0x6c, 0x6f, 0x63, 0x61, + 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x42, 0x0a, 0x13, + 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x6f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x11, 0x72, + 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x12, 0x41, 0x0a, 0x12, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x6f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x52, 0x11, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x73, 0x22, 0xbf, 0x02, 0x0a, 0x13, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0d, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0c, + 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, + 0x74, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x64, 0x65, + 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x22, 0x0a, + 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, + 0x65, 0x12, 0x29, 0x0a, 0x11, 0x6d, 0x61, 0x78, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, + 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x6d, 0x61, + 0x78, 0x46, 0x65, 0x65, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, + 0x6e, 0x6f, 0x5f, 0x77, 0x61, 0x69, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6e, + 0x6f, 0x57, 0x61, 0x69, 0x74, 0x22, 0xd3, 0x01, 0x0a, 0x11, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x3b, 0x0a, 0x0d, 0x63, + 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, + 0x65, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x3a, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, + 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6c, 0x6f, 0x73, + 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x43, + 0x6c, 0x6f, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x69, 0x6e, + 0x73, 0x74, 0x61, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x48, 0x00, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, + 0x74, 0x42, 0x08, 0x0a, 0x06, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x22, 0x90, 0x01, 0x0a, 0x0d, + 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x69, + 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, + 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, + 0x6e, 0x64, 0x65, 0x78, 0x12, 0x22, 0x0a, 0x0d, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, + 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66, 0x65, 0x65, + 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x61, + 0x6c, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x74, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x54, 0x78, 0x22, 0x3b, + 0x0a, 0x0d, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, + 0x2a, 0x0a, 0x11, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, + 0x74, 0x6c, 0x63, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x6e, 0x75, 0x6d, 0x50, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x22, 0x79, 0x0a, 0x13, 0x52, + 0x65, 0x61, 0x64, 0x79, 0x46, 0x6f, 0x72, 0x50, 0x73, 0x62, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x66, 0x75, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x66, + 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0d, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x73, 0x62, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x04, 0x70, 0x73, 0x62, 0x74, 0x22, 0xc9, 0x02, 0x0a, 0x17, 0x42, 0x61, 0x74, 0x63, 0x68, + 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x33, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x08, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, + 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, + 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, + 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x54, 0x0a, 0x17, + 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, + 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x15, 0x63, 0x6f, 0x69, + 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, + 0x67, 0x79, 0x22, 0x89, 0x06, 0x0a, 0x10, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, + 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6e, 0x6f, + 0x64, 0x65, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x6c, 0x6f, 0x63, 0x61, + 0x6c, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x46, 0x75, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x75, + 0x73, 0x68, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x70, 0x75, + 0x73, 0x68, 0x53, 0x61, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, + 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, + 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x73, + 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x72, + 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x73, 0x76, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x23, 0x0a, + 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, + 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x43, 0x0a, 0x1f, 0x72, 0x65, + 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x69, + 0x6e, 0x5f, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x1a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x61, 0x78, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x49, 0x6e, 0x46, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, + 0x28, 0x0a, 0x10, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x74, + 0x6c, 0x63, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, + 0x65, 0x4d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, + 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x73, 0x76, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x73, 0x76, 0x12, 0x1b, 0x0a, + 0x09, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x08, 0x7a, 0x65, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x63, + 0x69, 0x64, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, + 0x73, 0x63, 0x69, 0x64, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x73, + 0x65, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x62, 0x61, 0x73, + 0x65, 0x46, 0x65, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, + 0x18, 0x10, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, + 0x20, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x18, + 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x42, 0x61, 0x73, 0x65, 0x46, 0x65, + 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, + 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x46, 0x65, 0x65, 0x52, + 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x68, + 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x13, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, + 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, + 0x6d, 0x6f, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x22, 0x5b, + 0x0a, 0x18, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x10, 0x70, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0f, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0xcb, 0x08, 0x0a, 0x12, + 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, + 0x79, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, + 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, + 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6e, 0x6f, 0x64, + 0x65, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x12, 0x6e, 0x6f, 0x64, 0x65, 0x5f, + 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x10, 0x6e, 0x6f, 0x64, 0x65, 0x50, 0x75, 0x62, + 0x6b, 0x65, 0x79, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x30, 0x0a, 0x14, 0x6c, 0x6f, 0x63, + 0x61, 0x6c, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x46, 0x75, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x70, + 0x75, 0x73, 0x68, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x70, + 0x75, 0x73, 0x68, 0x53, 0x61, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, + 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, + 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, + 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, + 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x72, + 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x73, 0x76, + 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, + 0x66, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, + 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, + 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, + 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x35, 0x0a, 0x0c, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, + 0x73, 0x68, 0x69, 0x6d, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x52, 0x0b, + 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x43, 0x0a, 0x1f, 0x72, + 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, + 0x69, 0x6e, 0x5f, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0f, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x1a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x61, 0x78, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x49, 0x6e, 0x46, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x4d, 0x73, 0x61, 0x74, + 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x68, + 0x74, 0x6c, 0x63, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, + 0x74, 0x65, 0x4d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, + 0x78, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x73, 0x76, 0x18, 0x11, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x73, 0x76, 0x12, 0x3e, + 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, + 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, + 0x0a, 0x09, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x13, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x08, 0x7a, 0x65, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x1d, 0x0a, 0x0a, 0x73, + 0x63, 0x69, 0x64, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x09, 0x73, 0x63, 0x69, 0x64, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, + 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x15, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x62, 0x61, + 0x73, 0x65, 0x46, 0x65, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, + 0x65, 0x18, 0x16, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, + 0x12, 0x20, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, + 0x18, 0x17, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x42, 0x61, 0x73, 0x65, 0x46, + 0x65, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, + 0x74, 0x65, 0x18, 0x18, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x46, 0x65, 0x65, + 0x52, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, + 0x68, 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, + 0x19, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, + 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, + 0x75, 0x6e, 0x64, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x66, + 0x75, 0x6e, 0x64, 0x4d, 0x61, 0x78, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, 0x1b, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x12, 0x2d, 0x0a, 0x09, 0x6f, 0x75, + 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x1c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, + 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x22, 0xf3, 0x01, 0x0a, 0x10, 0x4f, 0x70, + 0x65, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x39, + 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x68, + 0x61, 0x6e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x37, 0x0a, 0x09, 0x63, 0x68, 0x61, + 0x6e, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4f, 0x70, 0x65, 0x6e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x4f, 0x70, + 0x65, 0x6e, 0x12, 0x39, 0x0a, 0x09, 0x70, 0x73, 0x62, 0x74, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, + 0x61, 0x64, 0x79, 0x46, 0x6f, 0x72, 0x50, 0x73, 0x62, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x48, 0x00, 0x52, 0x08, 0x70, 0x73, 0x62, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x12, 0x26, 0x0a, + 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, + 0x68, 0x61, 0x6e, 0x49, 0x64, 0x42, 0x08, 0x0a, 0x06, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x22, + 0x48, 0x0a, 0x0a, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1d, 0x0a, + 0x0a, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x09, 0x6b, 0x65, 0x79, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x1b, 0x0a, 0x09, + 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x08, 0x6b, 0x65, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x5f, 0x0a, 0x0d, 0x4b, 0x65, 0x79, + 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x61, + 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0b, 0x72, 0x61, 0x77, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2a, + 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, + 0x6f, 0x72, 0x52, 0x06, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x22, 0x88, 0x02, 0x0a, 0x0d, 0x43, + 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x10, 0x0a, 0x03, + 0x61, 0x6d, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x61, 0x6d, 0x74, 0x12, 0x32, + 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, + 0x6e, 0x74, 0x12, 0x31, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, + 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x52, 0x08, 0x6c, 0x6f, 0x63, + 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, + 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x65, 0x6d, 0x6f, 0x74, + 0x65, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, + 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, + 0x74, 0x68, 0x61, 0x77, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0a, 0x74, 0x68, 0x61, 0x77, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, + 0x06, 0x6d, 0x75, 0x73, 0x69, 0x67, 0x32, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6d, + 0x75, 0x73, 0x69, 0x67, 0x32, 0x22, 0x6e, 0x0a, 0x08, 0x50, 0x73, 0x62, 0x74, 0x53, 0x68, 0x69, + 0x6d, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x61, 0x73, + 0x65, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x62, 0x61, + 0x73, 0x65, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x6f, 0x5f, 0x70, 0x75, 0x62, + 0x6c, 0x69, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6e, 0x6f, 0x50, 0x75, + 0x62, 0x6c, 0x69, 0x73, 0x68, 0x22, 0x85, 0x01, 0x0a, 0x0b, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x5f, 0x73, 0x68, 0x69, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, + 0x53, 0x68, 0x69, 0x6d, 0x48, 0x00, 0x52, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x2e, 0x0a, 0x09, 0x70, 0x73, 0x62, 0x74, 0x5f, 0x73, 0x68, + 0x69, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x73, 0x62, 0x74, 0x53, 0x68, 0x69, 0x6d, 0x48, 0x00, 0x52, 0x08, 0x70, 0x73, 0x62, + 0x74, 0x53, 0x68, 0x69, 0x6d, 0x42, 0x06, 0x0a, 0x04, 0x73, 0x68, 0x69, 0x6d, 0x22, 0x3b, 0x0a, + 0x11, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x43, 0x61, 0x6e, 0x63, + 0x65, 0x6c, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, + 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x22, 0x81, 0x01, 0x0a, 0x11, 0x46, + 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x73, 0x62, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, + 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, 0x62, + 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x6b, 0x69, + 0x70, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x22, 0x80, + 0x01, 0x0a, 0x13, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x73, 0x62, 0x74, 0x46, 0x69, + 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, + 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, + 0x6e, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, + 0x20, 0x0a, 0x0c, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x61, 0x77, 0x54, + 0x78, 0x22, 0x99, 0x02, 0x0a, 0x14, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x67, 0x12, 0x39, 0x0a, 0x0d, 0x73, 0x68, + 0x69, 0x6d, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x53, 0x68, 0x69, 0x6d, 0x48, 0x00, 0x52, 0x0c, 0x73, 0x68, 0x69, 0x6d, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x3b, 0x0a, 0x0b, 0x73, 0x68, 0x69, 0x6d, 0x5f, 0x63, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x43, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x68, 0x69, 0x6d, 0x43, 0x61, 0x6e, 0x63, + 0x65, 0x6c, 0x12, 0x3b, 0x0a, 0x0b, 0x70, 0x73, 0x62, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, + 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x73, 0x62, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, + 0x79, 0x48, 0x00, 0x52, 0x0a, 0x70, 0x73, 0x62, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, + 0x41, 0x0a, 0x0d, 0x70, 0x73, 0x62, 0x74, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, + 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x73, 0x62, 0x74, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, + 0x7a, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x70, 0x73, 0x62, 0x74, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, + 0x7a, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x22, 0x16, 0x0a, + 0x14, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x65, + 0x70, 0x52, 0x65, 0x73, 0x70, 0x22, 0xcc, 0x01, 0x0a, 0x0b, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x48, 0x54, 0x4c, 0x43, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, + 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, + 0x67, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x75, 0x74, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x75, 0x74, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, + 0x79, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, + 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x2e, + 0x0a, 0x13, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x74, 0x69, 0x6c, 0x5f, 0x6d, 0x61, 0x74, + 0x75, 0x72, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x73, 0x54, 0x69, 0x6c, 0x4d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x73, + 0x74, 0x61, 0x67, 0x65, 0x22, 0x3e, 0x0a, 0x16, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, + 0x0a, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x52, + 0x61, 0x77, 0x54, 0x78, 0x22, 0x80, 0x15, 0x0a, 0x17, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x2e, 0x0a, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x6d, 0x62, 0x6f, 0x5f, + 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x4c, 0x69, 0x6d, 0x62, 0x6f, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x12, 0x65, 0x0a, 0x15, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x70, 0x65, 0x6e, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x31, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x52, 0x13, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x6a, 0x0a, 0x18, 0x70, 0x65, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x02, 0x18, 0x01, 0x52, 0x16, 0x70, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x73, 0x12, 0x76, 0x0a, 0x1e, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x66, + 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x6f, 0x72, 0x63, + 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x1b, + 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, + 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x68, 0x0a, 0x16, 0x77, + 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x57, 0x61, 0x69, 0x74, + 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, + 0x14, 0x77, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x1a, 0xe3, 0x04, 0x0a, 0x0e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x26, 0x0a, 0x0f, 0x72, 0x65, 0x6d, 0x6f, + 0x74, 0x65, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x50, 0x75, 0x62, + 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, + 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, + 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x42, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, + 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, + 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x33, 0x0a, + 0x16, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x6c, + 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, + 0x61, 0x74, 0x12, 0x35, 0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x14, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x52, + 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x2e, 0x0a, 0x09, 0x69, 0x6e, 0x69, + 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x09, + 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x6e, 0x75, 0x6d, + 0x5f, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x63, 0x6b, + 0x61, 0x67, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x6e, 0x75, 0x6d, 0x46, + 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, + 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x68, + 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x18, 0x0a, + 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, + 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, + 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x12, 0x2e, 0x0a, 0x13, 0x63, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x64, 0x61, + 0x74, 0x61, 0x18, 0x22, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x1a, 0xe8, 0x02, 0x0a, 0x12, + 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x12, 0x47, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x63, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x46, 0x65, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, + 0x1c, 0x0a, 0x0a, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x08, 0x66, 0x65, 0x65, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x32, 0x0a, + 0x15, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x66, 0x75, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x73, 0x12, 0x3c, 0x0a, 0x1a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x5f, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x18, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x55, 0x6e, 0x74, 0x69, 0x6c, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, + 0x2f, 0x0a, 0x13, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x1a, 0x9a, 0x02, 0x0a, 0x13, 0x57, 0x61, 0x69, 0x74, 0x69, + 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x47, + 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x2d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x07, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x69, 0x6d, 0x62, 0x6f, + 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, + 0x6c, 0x69, 0x6d, 0x62, 0x6f, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x4c, 0x0a, 0x0b, + 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x2a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x0b, 0x63, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, + 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x12, 0x24, 0x0a, + 0x0e, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x5f, 0x68, 0x65, 0x78, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x78, + 0x48, 0x65, 0x78, 0x1a, 0xa3, 0x02, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x54, 0x78, + 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x74, 0x78, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x54, + 0x78, 0x69, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x70, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x11, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, + 0x78, 0x69, 0x64, 0x12, 0x2f, 0x0a, 0x14, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x11, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x46, 0x65, + 0x65, 0x53, 0x61, 0x74, 0x12, 0x31, 0x0a, 0x15, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x46, 0x65, 0x65, 0x53, 0x61, 0x74, 0x12, 0x40, 0x0a, 0x1d, 0x72, 0x65, 0x6d, 0x6f, 0x74, + 0x65, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x19, + 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x46, 0x65, 0x65, 0x53, 0x61, 0x74, 0x1a, 0x7b, 0x0a, 0x0d, 0x43, 0x6c, 0x6f, + 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x47, 0x0a, 0x07, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x74, + 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x69, + 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x1a, 0xee, 0x03, 0x0a, 0x12, 0x46, 0x6f, 0x72, 0x63, 0x65, + 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x47, 0x0a, + 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x07, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, + 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, + 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x69, 0x6d, + 0x62, 0x6f, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0c, 0x6c, 0x69, 0x6d, 0x62, 0x6f, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x27, + 0x0a, 0x0f, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, + 0x79, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x73, 0x5f, 0x74, 0x69, 0x6c, 0x5f, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x54, 0x69, 0x6c, 0x4d, + 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x6f, 0x76, + 0x65, 0x72, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x10, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x65, 0x64, 0x42, 0x61, 0x6c, + 0x61, 0x6e, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, + 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x54, 0x4c, 0x43, 0x52, + 0x0c, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x55, 0x0a, + 0x06, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x3d, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x6f, + 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x2e, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x06, 0x61, 0x6e, + 0x63, 0x68, 0x6f, 0x72, 0x22, 0x31, 0x0a, 0x0b, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x4c, 0x49, 0x4d, 0x42, 0x4f, 0x10, 0x00, 0x12, 0x0d, + 0x0a, 0x09, 0x52, 0x45, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x45, 0x44, 0x10, 0x01, 0x12, 0x08, 0x0a, + 0x04, 0x4c, 0x4f, 0x53, 0x54, 0x10, 0x02, 0x22, 0x1a, 0x0a, 0x18, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x22, 0xeb, 0x05, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x33, 0x0a, 0x0c, 0x6f, 0x70, + 0x65, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x48, 0x00, 0x52, 0x0b, 0x6f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, + 0x43, 0x0a, 0x0e, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x75, 0x6d, 0x6d, + 0x61, 0x72, 0x79, 0x48, 0x00, 0x52, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x3c, 0x0a, 0x0e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x12, 0x40, 0x0a, 0x10, 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x48, 0x00, 0x52, 0x0f, 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x48, 0x0a, 0x14, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, + 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x12, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x4b, + 0x0a, 0x16, 0x66, 0x75, 0x6c, 0x6c, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x64, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, + 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x14, 0x66, 0x75, 0x6c, 0x6c, 0x79, 0x52, 0x65, 0x73, 0x6f, + 0x6c, 0x76, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x4d, 0x0a, 0x17, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x48, 0x00, 0x52, 0x15, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x46, 0x75, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x38, 0x0a, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x22, 0xaf, 0x01, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x4f, 0x50, 0x45, 0x4e, 0x5f, 0x43, 0x48, 0x41, 0x4e, + 0x4e, 0x45, 0x4c, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x44, 0x5f, + 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x41, 0x43, 0x54, + 0x49, 0x56, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x02, 0x12, 0x14, 0x0a, + 0x10, 0x49, 0x4e, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, + 0x4c, 0x10, 0x03, 0x12, 0x18, 0x0a, 0x14, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x4f, + 0x50, 0x45, 0x4e, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x04, 0x12, 0x1a, 0x0a, + 0x16, 0x46, 0x55, 0x4c, 0x4c, 0x59, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x4c, 0x56, 0x45, 0x44, 0x5f, + 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x05, 0x12, 0x1b, 0x0a, 0x17, 0x43, 0x48, 0x41, + 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x46, 0x55, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x54, 0x49, 0x4d, + 0x45, 0x4f, 0x55, 0x54, 0x10, 0x06, 0x42, 0x09, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x22, 0x74, 0x0a, 0x14, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x42, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x12, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, + 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x4d, 0x0a, 0x14, 0x57, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, + 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, + 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x22, 0xbd, 0x03, 0x0a, 0x15, 0x57, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, + 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, + 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x12, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x62, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6c, 0x6f, 0x63, + 0x6b, 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x3f, 0x0a, 0x1c, 0x72, 0x65, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, + 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x19, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x12, 0x59, 0x0a, 0x0f, 0x61, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x04, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x1a, 0x5e, 0x0a, 0x13, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x2e, 0x0a, 0x06, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x10, 0x0a, 0x03, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x73, + 0x61, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x04, 0x6d, 0x73, 0x61, 0x74, 0x22, 0x17, 0x0a, 0x15, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, + 0xb0, 0x04, 0x0a, 0x16, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x07, 0x62, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, + 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x14, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x12, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x32, + 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x12, 0x34, 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x62, 0x61, 0x6c, + 0x61, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74, + 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x17, 0x75, 0x6e, 0x73, 0x65, + 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x15, 0x75, 0x6e, 0x73, 0x65, 0x74, 0x74, + 0x6c, 0x65, 0x64, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, + 0x47, 0x0a, 0x18, 0x75, 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x6d, + 0x6f, 0x74, 0x65, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x52, 0x16, 0x75, 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x6d, 0x6f, 0x74, + 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x1a, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x17, 0x70, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x61, 0x6c, + 0x61, 0x6e, 0x63, 0x65, 0x12, 0x4c, 0x0a, 0x1b, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, + 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x62, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x18, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x44, 0x61, + 0x74, 0x61, 0x22, 0xc8, 0x07, 0x0a, 0x12, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, + 0x65, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6d, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x03, 0x61, 0x6d, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, + 0x28, 0x0a, 0x10, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x64, 0x65, + 0x6c, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x66, 0x69, 0x6e, 0x61, 0x6c, + 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x2c, 0x0a, 0x09, 0x66, 0x65, 0x65, + 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x52, 0x08, 0x66, + 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x6f, 0x72, + 0x65, 0x64, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, + 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x0d, + 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x65, 0x64, 0x67, 0x65, 0x73, 0x18, 0x07, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x64, 0x67, 0x65, + 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x69, 0x67, 0x6e, + 0x6f, 0x72, 0x65, 0x64, 0x45, 0x64, 0x67, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, + 0x2e, 0x0a, 0x13, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x75, 0x73, + 0x65, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, + 0x34, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x73, + 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, + 0x6f, 0x64, 0x65, 0x50, 0x61, 0x69, 0x72, 0x52, 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, + 0x50, 0x61, 0x69, 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x6c, 0x69, + 0x6d, 0x69, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x63, 0x6c, 0x74, 0x76, 0x4c, + 0x69, 0x6d, 0x69, 0x74, 0x12, 0x60, 0x0a, 0x13, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x30, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x65, 0x73, + 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x11, 0x64, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, + 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x2e, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, + 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x04, + 0x42, 0x04, 0x18, 0x01, 0x30, 0x01, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, + 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x68, + 0x6f, 0x70, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x31, + 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x10, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x48, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, + 0x73, 0x12, 0x4d, 0x0a, 0x15, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x74, 0x68, 0x52, 0x13, 0x62, 0x6c, 0x69, + 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x74, 0x68, 0x73, + 0x12, 0x36, 0x0a, 0x0d, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, 0x52, 0x0c, 0x64, 0x65, 0x73, 0x74, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, + 0x5f, 0x70, 0x72, 0x65, 0x66, 0x18, 0x12, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x74, 0x69, 0x6d, + 0x65, 0x50, 0x72, 0x65, 0x66, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, + 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, 0x04, + 0x52, 0x0f, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, + 0x73, 0x1a, 0x44, 0x0a, 0x16, 0x44, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, + 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x2e, 0x0a, + 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x50, 0x61, 0x69, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, + 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, + 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x74, 0x6f, 0x22, 0x5d, 0x0a, + 0x0b, 0x45, 0x64, 0x67, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x21, 0x0a, 0x0a, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, + 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, + 0x2b, 0x0a, 0x11, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x76, + 0x65, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x64, 0x69, 0x72, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x22, 0x5e, 0x0a, 0x13, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x62, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, + 0x0b, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x50, 0x72, 0x6f, 0x62, 0x22, 0xa5, 0x05, 0x0a, + 0x03, 0x48, 0x6f, 0x70, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, + 0x64, 0x12, 0x27, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, + 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x63, 0x68, + 0x61, 0x6e, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x28, 0x0a, 0x0e, 0x61, 0x6d, + 0x74, 0x5f, 0x74, 0x6f, 0x5f, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x61, 0x6d, 0x74, 0x54, 0x6f, 0x46, 0x6f, 0x72, + 0x77, 0x61, 0x72, 0x64, 0x12, 0x14, 0x0a, 0x03, 0x66, 0x65, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x03, 0x66, 0x65, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, + 0x70, 0x69, 0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, + 0x72, 0x79, 0x12, 0x2d, 0x0a, 0x13, 0x61, 0x6d, 0x74, 0x5f, 0x74, 0x6f, 0x5f, 0x66, 0x6f, 0x72, + 0x77, 0x61, 0x72, 0x64, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x10, 0x61, 0x6d, 0x74, 0x54, 0x6f, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x4d, 0x73, 0x61, + 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x07, 0x66, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x17, 0x0a, 0x07, + 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, + 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0b, 0x74, 0x6c, 0x76, 0x5f, 0x70, 0x61, 0x79, + 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, + 0x74, 0x6c, 0x76, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x2f, 0x0a, 0x0a, 0x6d, 0x70, + 0x70, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x50, 0x50, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x52, 0x09, 0x6d, 0x70, 0x70, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x2f, 0x0a, 0x0a, 0x61, + 0x6d, 0x70, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x4d, 0x50, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x52, 0x09, 0x61, 0x6d, 0x70, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x44, 0x0a, 0x0e, + 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x0b, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x6f, 0x70, + 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0d, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x25, + 0x0a, 0x0e, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, + 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, + 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x10, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x74, 0x4d, 0x73, + 0x61, 0x74, 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x22, 0x54, 0x0a, 0x09, 0x4d, 0x50, 0x50, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, + 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x41, 0x64, 0x64, 0x72, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, + 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x22, 0x62, 0x0a, 0x09, 0x41, 0x4d, + 0x50, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x5f, + 0x73, 0x68, 0x61, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x6f, 0x6f, + 0x74, 0x53, 0x68, 0x61, 0x72, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, + 0x0b, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0xc4, + 0x02, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, + 0x12, 0x21, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x46, + 0x65, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x41, 0x6d, 0x74, 0x12, 0x1e, 0x0a, 0x04, 0x68, 0x6f, 0x70, 0x73, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x6f, 0x70, 0x52, 0x04, + 0x68, 0x6f, 0x70, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x65, + 0x65, 0x73, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x46, 0x65, 0x65, 0x73, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x24, 0x0a, 0x0e, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x74, 0x4d, 0x73, + 0x61, 0x74, 0x12, 0x31, 0x0a, 0x15, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x70, 0x5f, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x12, 0x66, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x41, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x44, 0x61, 0x74, 0x61, 0x22, 0x83, 0x01, 0x0a, 0x0f, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, + 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, + 0x63, 0x6c, 0x75, 0x64, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x2c, 0x0a, + 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, + 0x6f, 0x6f, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, + 0x64, 0x65, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0xae, 0x01, 0x0a, 0x08, + 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x28, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, + 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, + 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, + 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x2e, 0x0a, 0x08, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, + 0x67, 0x65, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0xc6, 0x03, 0x0a, + 0x0d, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x1f, + 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, + 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x61, + 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x30, + 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, + 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x12, 0x3e, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x4e, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, + 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, + 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x3b, 0x0a, 0x0b, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x12, + 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, + 0x64, 0x72, 0x22, 0x89, 0x04, 0x0a, 0x0d, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, + 0x6b, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x74, + 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, + 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, + 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x12, 0x22, 0x0a, 0x0d, 0x66, 0x65, 0x65, 0x5f, 0x62, + 0x61, 0x73, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x66, 0x65, 0x65, 0x42, 0x61, 0x73, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2d, 0x0a, 0x13, 0x66, + 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x5f, 0x6d, 0x73, + 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, + 0x65, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, + 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x64, 0x69, + 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x74, + 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, + 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x61, + 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x4e, 0x0a, 0x0e, 0x63, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x08, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, + 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, + 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x31, 0x0a, 0x15, 0x69, + 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, + 0x6d, 0x73, 0x61, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x69, 0x6e, 0x62, 0x6f, + 0x75, 0x6e, 0x64, 0x46, 0x65, 0x65, 0x42, 0x61, 0x73, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x3c, + 0x0a, 0x1b, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, + 0x74, 0x65, 0x5f, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x17, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x65, 0x65, 0x52, + 0x61, 0x74, 0x65, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x4d, 0x73, 0x61, 0x74, 0x1a, 0x40, 0x0a, 0x12, + 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x92, + 0x01, 0x0a, 0x10, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, + 0x6f, 0x6f, 0x66, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x73, 0x69, 0x67, 0x31, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x53, 0x69, 0x67, 0x31, + 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x69, 0x67, 0x31, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x62, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x53, + 0x69, 0x67, 0x31, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x73, 0x69, 0x67, 0x32, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x53, 0x69, 0x67, 0x32, + 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x69, 0x67, 0x32, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x62, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x53, + 0x69, 0x67, 0x32, 0x22, 0x84, 0x04, 0x0a, 0x0b, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, + 0x64, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, 0x63, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, + 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, + 0x64, 0x65, 0x31, 0x5f, 0x70, 0x75, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, + 0x6f, 0x64, 0x65, 0x31, 0x50, 0x75, 0x62, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x32, + 0x5f, 0x70, 0x75, 0x62, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, + 0x32, 0x50, 0x75, 0x62, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, + 0x12, 0x37, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x31, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, + 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0b, 0x6e, 0x6f, + 0x64, 0x65, 0x31, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x37, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, + 0x65, 0x32, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x32, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x12, 0x4c, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x2e, 0x43, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, + 0x12, 0x36, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x09, 0x61, + 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x74, 0x0a, 0x13, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x2f, 0x0a, 0x13, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x75, 0x6e, 0x61, + 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x55, 0x6e, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, + 0x65, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x61, 0x75, + 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, + 0x22, 0x64, 0x0a, 0x0c, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, + 0x12, 0x2a, 0x0a, 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, + 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x05, + 0x65, 0x64, 0x67, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x52, + 0x05, 0x65, 0x64, 0x67, 0x65, 0x73, 0x22, 0x41, 0x0a, 0x12, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x05, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x22, 0xe1, 0x01, 0x0a, 0x13, 0x4e, 0x6f, + 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x6c, 0x0a, 0x16, 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x6e, 0x65, 0x73, 0x73, + 0x5f, 0x63, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x35, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x42, 0x65, + 0x74, 0x77, 0x65, 0x65, 0x6e, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, + 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x15, 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, + 0x6e, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x1a, + 0x5c, 0x0a, 0x1a, 0x42, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x65, + 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x28, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x4d, 0x65, 0x74, 0x72, + 0x69, 0x63, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4e, 0x0a, + 0x0b, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0f, 0x6e, 0x6f, + 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x7b, 0x0a, + 0x0f, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, + 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, + 0x6f, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, + 0x65, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0x14, 0x0a, 0x12, 0x4e, 0x65, + 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x22, 0xd5, 0x03, 0x0a, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, + 0x12, 0x25, 0x0a, 0x0e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x5f, 0x64, 0x69, 0x61, 0x6d, 0x65, 0x74, + 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x67, 0x72, 0x61, 0x70, 0x68, 0x44, + 0x69, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x76, 0x67, 0x5f, 0x6f, + 0x75, 0x74, 0x5f, 0x64, 0x65, 0x67, 0x72, 0x65, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, + 0x0c, 0x61, 0x76, 0x67, 0x4f, 0x75, 0x74, 0x44, 0x65, 0x67, 0x72, 0x65, 0x65, 0x12, 0x24, 0x0a, + 0x0e, 0x6d, 0x61, 0x78, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x67, 0x72, 0x65, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x61, 0x78, 0x4f, 0x75, 0x74, 0x44, 0x65, 0x67, + 0x72, 0x65, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x75, 0x6d, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6e, 0x75, 0x6d, 0x4e, 0x6f, 0x64, 0x65, 0x73, + 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x6e, 0x65, 0x74, + 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x14, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, + 0x6b, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x61, 0x76, 0x67, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x01, 0x52, 0x0e, 0x61, 0x76, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, + 0x69, 0x7a, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6d, + 0x69, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x28, 0x0a, + 0x10, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x7a, + 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x65, 0x64, 0x69, 0x61, + 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x73, + 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x6e, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x53, 0x61, 0x74, 0x12, 0x28, + 0x0a, 0x10, 0x6e, 0x75, 0x6d, 0x5f, 0x7a, 0x6f, 0x6d, 0x62, 0x69, 0x65, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x6e, 0x75, 0x6d, 0x5a, 0x6f, 0x6d, + 0x62, 0x69, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x73, 0x22, 0x0d, 0x0a, 0x0b, 0x53, 0x74, 0x6f, 0x70, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x26, 0x0a, 0x0c, 0x53, 0x74, 0x6f, 0x70, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, + 0x1b, 0x0a, 0x19, 0x47, 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, + 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xcd, 0x01, 0x0a, + 0x13, 0x47, 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x12, 0x34, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x6e, + 0x6f, 0x64, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x41, 0x0a, 0x0f, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0e, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x3d, 0x0a, + 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x73, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, + 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, + 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x73, 0x22, 0xef, 0x02, 0x0a, + 0x0a, 0x4e, 0x6f, 0x64, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x09, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x42, 0x02, + 0x18, 0x01, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x21, 0x0a, + 0x0c, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x4b, 0x65, 0x79, + 0x12, 0x2b, 0x0a, 0x0f, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0e, 0x67, + 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x14, 0x0a, + 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, + 0x69, 0x61, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x12, 0x39, 0x0a, 0x0e, 0x6e, 0x6f, 0x64, + 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x0d, 0x6e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, + 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, + 0x6f, 0x64, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x91, + 0x02, 0x0a, 0x11, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, + 0x64, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, + 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, + 0x79, 0x12, 0x3b, 0x0a, 0x0e, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, + 0x0d, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x29, + 0x0a, 0x10, 0x61, 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x6f, + 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x61, 0x64, 0x76, 0x65, 0x72, 0x74, + 0x69, 0x73, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x4e, 0x6f, + 0x64, 0x65, 0x22, 0xa7, 0x01, 0x0a, 0x13, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, + 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, + 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, + 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, + 0x69, 0x74, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x68, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, + 0x65, 0x64, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, + 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0xcf, 0x01, 0x0a, + 0x07, 0x48, 0x6f, 0x70, 0x48, 0x69, 0x6e, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x49, + 0x64, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x22, + 0x0a, 0x0d, 0x66, 0x65, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x66, 0x65, 0x65, 0x42, 0x61, 0x73, 0x65, 0x4d, 0x73, + 0x61, 0x74, 0x12, 0x3e, 0x0a, 0x1b, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x72, + 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x6f, 0x6e, 0x74, 0x68, + 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x19, 0x66, 0x65, 0x65, 0x50, 0x72, 0x6f, 0x70, + 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x6f, 0x6e, 0x74, + 0x68, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, + 0x79, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x63, + 0x6c, 0x74, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x22, 0x1e, + 0x0a, 0x05, 0x53, 0x65, 0x74, 0x49, 0x44, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, 0x64, 0x22, 0x38, + 0x0a, 0x09, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x12, 0x2b, 0x0a, 0x09, 0x68, + 0x6f, 0x70, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x6f, 0x70, 0x48, 0x69, 0x6e, 0x74, 0x52, 0x08, + 0x68, 0x6f, 0x70, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x22, 0xc4, 0x02, 0x0a, 0x12, 0x42, 0x6c, 0x69, + 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x74, 0x68, 0x12, + 0x35, 0x0a, 0x0c, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6c, + 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x52, 0x0b, 0x62, 0x6c, 0x69, 0x6e, 0x64, + 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x12, 0x22, 0x0a, 0x0d, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, + 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x62, + 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, + 0x6f, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, + 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x70, 0x6f, + 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x46, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x28, + 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x64, 0x65, 0x6c, + 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, + 0x6c, 0x74, 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x0d, 0x68, 0x74, 0x6c, 0x63, + 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0b, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x69, 0x6e, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, + 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0b, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x61, 0x78, 0x4d, 0x73, 0x61, 0x74, + 0x12, 0x2d, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, + 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x42, 0x69, 0x74, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x22, + 0x97, 0x01, 0x0a, 0x0b, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x12, + 0x2b, 0x0a, 0x11, 0x69, 0x6e, 0x74, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x69, 0x6e, 0x74, 0x72, + 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x25, 0x0a, 0x0e, + 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x6f, + 0x69, 0x6e, 0x74, 0x12, 0x34, 0x0a, 0x0c, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x68, + 0x6f, 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x48, 0x6f, 0x70, 0x52, 0x0b, 0x62, 0x6c, + 0x69, 0x6e, 0x64, 0x65, 0x64, 0x48, 0x6f, 0x70, 0x73, 0x22, 0x56, 0x0a, 0x0a, 0x42, 0x6c, 0x69, + 0x6e, 0x64, 0x65, 0x64, 0x48, 0x6f, 0x70, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x69, 0x6e, 0x64, + 0x65, 0x64, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x62, + 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, + 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, + 0x61, 0x22, 0xa8, 0x01, 0x0a, 0x0f, 0x41, 0x4d, 0x50, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x2d, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x69, + 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x65, 0x74, 0x74, + 0x6c, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, + 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x65, + 0x74, 0x74, 0x6c, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x6d, 0x74, 0x5f, + 0x70, 0x61, 0x69, 0x64, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0b, 0x61, 0x6d, 0x74, 0x50, 0x61, 0x69, 0x64, 0x4d, 0x73, 0x61, 0x74, 0x22, 0xac, 0x0a, 0x0a, + 0x07, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x6d, 0x6f, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x12, 0x1d, 0x0a, 0x0a, + 0x72, 0x5f, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x09, 0x72, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x72, + 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x48, 0x61, + 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x17, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1c, 0x0a, 0x07, 0x73, 0x65, 0x74, 0x74, 0x6c, + 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x73, 0x65, + 0x74, 0x74, 0x6c, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, + 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0a, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x12, + 0x16, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x61, 0x6c, 0x6c, 0x62, + 0x61, 0x63, 0x6b, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1f, 0x0a, 0x0b, + 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x0d, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x31, 0x0a, + 0x0b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x0e, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, + 0x48, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x73, + 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, + 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x10, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x61, + 0x64, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, + 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x11, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, + 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1d, 0x0a, 0x08, 0x61, 0x6d, + 0x74, 0x5f, 0x70, 0x61, 0x69, 0x64, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, + 0x52, 0x07, 0x61, 0x6d, 0x74, 0x50, 0x61, 0x69, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x61, 0x6d, 0x74, + 0x5f, 0x70, 0x61, 0x69, 0x64, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0a, 0x61, 0x6d, 0x74, 0x50, 0x61, 0x69, 0x64, 0x53, 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x61, + 0x6d, 0x74, 0x5f, 0x70, 0x61, 0x69, 0x64, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x14, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0b, 0x61, 0x6d, 0x74, 0x50, 0x61, 0x69, 0x64, 0x4d, 0x73, 0x61, 0x74, 0x12, + 0x31, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x2e, 0x49, + 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x16, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x48, 0x54, 0x4c, 0x43, 0x52, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x38, 0x0a, 0x08, + 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x18, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x2e, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x6b, 0x65, 0x79, + 0x73, 0x65, 0x6e, 0x64, 0x18, 0x19, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x4b, 0x65, + 0x79, 0x73, 0x65, 0x6e, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, 0x5f, 0x61, + 0x6d, 0x70, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x69, 0x73, 0x41, 0x6d, 0x70, 0x12, + 0x4f, 0x0a, 0x11, 0x61, 0x6d, 0x70, 0x5f, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x18, 0x1c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x2e, 0x41, 0x6d, 0x70, 0x49, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x0f, 0x61, 0x6d, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x18, 0x1d, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x12, + 0x48, 0x0a, 0x13, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x5f, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, + 0x61, 0x74, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x5a, 0x0a, 0x14, 0x41, 0x6d, 0x70, 0x49, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x4d, 0x50, 0x49, 0x6e, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x22, 0x41, 0x0a, 0x0c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, + 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x41, 0x4e, + 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x43, 0x43, 0x45, 0x50, + 0x54, 0x45, 0x44, 0x10, 0x03, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xa3, 0x02, 0x0a, 0x11, + 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x12, 0x2e, 0x0a, 0x11, 0x6d, 0x69, 0x6e, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, 0x72, 0x65, 0x61, + 0x6c, 0x5f, 0x68, 0x6f, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x00, 0x52, 0x0e, + 0x6d, 0x69, 0x6e, 0x4e, 0x75, 0x6d, 0x52, 0x65, 0x61, 0x6c, 0x48, 0x6f, 0x70, 0x73, 0x88, 0x01, + 0x01, 0x12, 0x1e, 0x0a, 0x08, 0x6e, 0x75, 0x6d, 0x5f, 0x68, 0x6f, 0x70, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0d, 0x48, 0x01, 0x52, 0x07, 0x6e, 0x75, 0x6d, 0x48, 0x6f, 0x70, 0x73, 0x88, 0x01, + 0x01, 0x12, 0x27, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x74, + 0x68, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x02, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x4e, + 0x75, 0x6d, 0x50, 0x61, 0x74, 0x68, 0x73, 0x88, 0x01, 0x01, 0x12, 0x2c, 0x0a, 0x12, 0x6e, 0x6f, + 0x64, 0x65, 0x5f, 0x6f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x73, 0x74, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x10, 0x6e, 0x6f, 0x64, 0x65, 0x4f, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x69, 0x6e, 0x63, 0x6f, + 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x6c, 0x69, 0x73, + 0x74, 0x18, 0x05, 0x20, 0x03, 0x28, 0x04, 0x52, 0x13, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, + 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x14, 0x0a, 0x12, + 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, 0x72, 0x65, 0x61, 0x6c, 0x5f, 0x68, 0x6f, + 0x70, 0x73, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, 0x68, 0x6f, 0x70, 0x73, 0x42, + 0x10, 0x0a, 0x0e, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x74, 0x68, + 0x73, 0x22, 0xac, 0x04, 0x0a, 0x0b, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, + 0x43, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1d, + 0x0a, 0x0a, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x09, 0x68, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x19, 0x0a, + 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x07, 0x61, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x63, 0x63, 0x65, + 0x70, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0c, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1f, 0x0a, + 0x0b, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, + 0x0a, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x54, 0x69, 0x6d, + 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x68, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, + 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x2d, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x4c, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, + 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, + 0x43, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x73, 0x12, 0x2b, 0x0a, 0x12, 0x6d, 0x70, 0x70, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0f, 0x6d, 0x70, 0x70, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, + 0x12, 0x1c, 0x0a, 0x03, 0x61, 0x6d, 0x70, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x4d, 0x50, 0x52, 0x03, 0x61, 0x6d, 0x70, 0x12, 0x2e, + 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x63, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x1a, 0x40, + 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x22, 0x8c, 0x01, 0x0a, 0x03, 0x41, 0x4d, 0x50, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x74, + 0x5f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x6f, + 0x6f, 0x74, 0x53, 0x68, 0x61, 0x72, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1f, + 0x0a, 0x0b, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, + 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, + 0x61, 0x73, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, + 0x94, 0x01, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x48, 0x61, 0x73, 0x68, 0x12, 0x27, 0x0a, + 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x5f, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x18, 0x10, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x61, 0x64, 0x64, 0x49, 0x6e, + 0x64, 0x65, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, + 0x64, 0x64, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x22, 0x46, 0x0a, 0x0b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x20, 0x0a, 0x0a, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, + 0x73, 0x74, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x72, + 0x48, 0x61, 0x73, 0x68, 0x53, 0x74, 0x72, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x5f, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x48, 0x61, 0x73, 0x68, 0x22, 0xfc, + 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x70, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x64, 0x65, + 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, + 0x69, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6e, + 0x75, 0x6d, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x6e, 0x75, 0x6d, 0x4d, 0x61, 0x78, 0x49, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, + 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, + 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, + 0x74, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, + 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, + 0x74, 0x65, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x64, 0x22, 0x9b, 0x01, + 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x08, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x08, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, + 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6c, 0x61, + 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x2c, 0x0a, + 0x12, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, + 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x66, 0x69, 0x72, 0x73, 0x74, + 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x55, 0x0a, 0x13, 0x49, + 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x61, 0x64, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, + 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x22, 0x3a, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, + 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x12, 0x21, 0x0a, 0x0c, 0x69, + 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x61, 0x73, 0x68, 0x22, 0x30, + 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x49, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x22, 0xcb, 0x06, 0x0a, 0x07, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, + 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, + 0x18, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, + 0x18, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x27, 0x0a, 0x0d, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, + 0x74, 0x65, 0x12, 0x14, 0x0a, 0x03, 0x66, 0x65, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x42, + 0x02, 0x18, 0x01, 0x52, 0x03, 0x66, 0x65, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x70, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x65, 0x69, 0x6d, + 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x73, 0x61, 0x74, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x61, 0x74, + 0x12, 0x1d, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, + 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x17, + 0x0a, 0x07, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x06, 0x66, 0x65, 0x65, 0x53, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x6d, + 0x73, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x66, 0x65, 0x65, 0x4d, 0x73, + 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x5f, 0x6e, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x4e, 0x73, 0x12, 0x28, 0x0a, 0x05, + 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x52, + 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x42, 0x0a, 0x0e, 0x66, + 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x10, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, + 0x62, 0x0a, 0x18, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x70, 0x5f, 0x63, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x29, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x2e, 0x46, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x15, 0x66, 0x69, + 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x73, 0x1a, 0x48, 0x0a, 0x1a, 0x46, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x59, 0x0a, + 0x0d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0f, + 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x1a, 0x02, 0x08, 0x01, 0x12, + 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x5f, 0x46, 0x4c, 0x49, 0x47, 0x48, 0x54, 0x10, 0x01, 0x12, 0x0d, + 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0a, 0x0a, + 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x49, + 0x54, 0x49, 0x41, 0x54, 0x45, 0x44, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0xd5, + 0x02, 0x0a, 0x0b, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x09, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x49, 0x64, 0x12, 0x35, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, + 0x74, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x61, 0x74, 0x74, 0x65, + 0x6d, 0x70, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x4e, 0x73, + 0x12, 0x26, 0x0a, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x5f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x6c, + 0x76, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x4e, 0x73, 0x12, 0x28, 0x0a, 0x07, 0x66, 0x61, 0x69, 0x6c, + 0x75, 0x72, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x07, 0x66, 0x61, 0x69, 0x6c, 0x75, + 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x36, + 0x0a, 0x0a, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0d, 0x0a, 0x09, + 0x49, 0x4e, 0x5f, 0x46, 0x4c, 0x49, 0x47, 0x48, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, + 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, + 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x22, 0xb4, 0x02, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x50, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d, + 0x0a, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x70, + 0x6c, 0x65, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x69, 0x6e, 0x63, 0x6c, + 0x75, 0x64, 0x65, 0x49, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x21, 0x0a, + 0x0c, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, + 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x64, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x64, 0x12, + 0x30, 0x0a, 0x14, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, + 0x74, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, + 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, + 0x74, 0x65, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x64, 0x22, 0xca, 0x01, + 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x08, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x08, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, + 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, + 0x66, 0x69, 0x72, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, + 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, + 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6c, 0x61, 0x73, + 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x2c, 0x0a, 0x12, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x4e, + 0x75, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x65, 0x0a, 0x14, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, + 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x2a, 0x0a, 0x11, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, + 0x68, 0x74, 0x6c, 0x63, 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x4f, 0x6e, 0x6c, + 0x79, 0x22, 0x9b, 0x01, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x30, + 0x0a, 0x14, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x66, 0x61, + 0x69, 0x6c, 0x65, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x4f, 0x6e, 0x6c, 0x79, + 0x12, 0x2a, 0x0a, 0x11, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, + 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x66, 0x61, 0x69, + 0x6c, 0x65, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x21, 0x0a, 0x0c, + 0x61, 0x6c, 0x6c, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0b, 0x61, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, + 0x2f, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x22, 0x33, 0x0a, 0x19, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xbf, 0x01, 0x0a, 0x15, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, + 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x38, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0c, 0x63, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x19, 0x70, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x68, 0x69, + 0x6d, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x70, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, + 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x31, 0x0a, 0x16, 0x69, 0x5f, 0x6b, 0x6e, 0x6f, 0x77, 0x5f, 0x77, + 0x68, 0x61, 0x74, 0x5f, 0x69, 0x5f, 0x61, 0x6d, 0x5f, 0x64, 0x6f, 0x69, 0x6e, 0x67, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x69, 0x4b, 0x6e, 0x6f, 0x77, 0x57, 0x68, 0x61, 0x74, 0x49, + 0x41, 0x6d, 0x44, 0x6f, 0x69, 0x6e, 0x67, 0x22, 0x30, 0x0a, 0x16, 0x41, 0x62, 0x61, 0x6e, 0x64, + 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x46, 0x0a, 0x11, 0x44, 0x65, 0x62, + 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, + 0x0a, 0x04, 0x73, 0x68, 0x6f, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x73, 0x68, + 0x6f, 0x77, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x5f, 0x73, 0x70, 0x65, 0x63, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x53, 0x70, 0x65, + 0x63, 0x22, 0x35, 0x0a, 0x12, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x75, 0x62, 0x5f, 0x73, + 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x75, + 0x62, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x27, 0x0a, 0x0c, 0x50, 0x61, 0x79, 0x52, + 0x65, 0x71, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x5f, + 0x72, 0x65, 0x71, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x61, 0x79, 0x52, 0x65, + 0x71, 0x22, 0xf0, 0x04, 0x0a, 0x06, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x20, 0x0a, 0x0b, + 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, + 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, + 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, + 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x53, 0x61, 0x74, 0x6f, + 0x73, 0x68, 0x69, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x0a, 0x10, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x61, 0x6c, 0x6c, 0x62, + 0x61, 0x63, 0x6b, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1f, 0x0a, 0x0b, + 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x31, 0x0a, + 0x0b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, + 0x48, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x73, + 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, + 0x64, 0x64, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x75, 0x6d, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, + 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6e, 0x75, 0x6d, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x37, + 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x2e, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x3e, 0x0a, 0x0d, 0x62, 0x6c, 0x69, 0x6e, 0x64, + 0x65, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x74, 0x68, 0x52, 0x0c, 0x62, 0x6c, 0x69, 0x6e, 0x64, + 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x73, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x22, 0x59, 0x0a, 0x07, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, + 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x69, 0x72, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x22, + 0x12, 0x0a, 0x10, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x22, 0x95, 0x02, 0x0a, 0x10, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x46, + 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, + 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x62, 0x61, + 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1e, + 0x0a, 0x0b, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6d, 0x69, 0x6c, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x09, 0x66, 0x65, 0x65, 0x50, 0x65, 0x72, 0x4d, 0x69, 0x6c, 0x12, 0x19, + 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, + 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x31, 0x0a, 0x15, 0x69, 0x6e, 0x62, + 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, + 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, + 0x64, 0x42, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2d, 0x0a, 0x13, + 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, + 0x6d, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x69, 0x6e, 0x62, 0x6f, 0x75, + 0x6e, 0x64, 0x46, 0x65, 0x65, 0x50, 0x65, 0x72, 0x4d, 0x69, 0x6c, 0x22, 0xb5, 0x01, 0x0a, 0x11, + 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3a, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x65, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x46, 0x65, 0x65, 0x73, 0x12, 0x1e, 0x0a, + 0x0b, 0x64, 0x61, 0x79, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x09, 0x64, 0x61, 0x79, 0x46, 0x65, 0x65, 0x53, 0x75, 0x6d, 0x12, 0x20, 0x0a, + 0x0c, 0x77, 0x65, 0x65, 0x6b, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0a, 0x77, 0x65, 0x65, 0x6b, 0x46, 0x65, 0x65, 0x53, 0x75, 0x6d, 0x12, + 0x22, 0x0a, 0x0d, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x75, 0x6d, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x46, 0x65, 0x65, + 0x53, 0x75, 0x6d, 0x22, 0x52, 0x0a, 0x0a, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x65, + 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, + 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65, + 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, + 0x65, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x66, 0x65, 0x65, + 0x52, 0x61, 0x74, 0x65, 0x50, 0x70, 0x6d, 0x22, 0xda, 0x03, 0x0a, 0x13, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x18, 0x0a, 0x06, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, + 0x00, 0x52, 0x06, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x12, 0x34, 0x0a, 0x0a, 0x63, 0x68, 0x61, + 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, + 0x22, 0x0a, 0x0d, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x4d, + 0x73, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x20, + 0x0a, 0x0c, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x50, 0x70, 0x6d, + 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, + 0x6c, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, + 0x6f, 0x63, 0x6b, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, + 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0b, 0x6d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, + 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, + 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, + 0x74, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x14, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x53, 0x70, + 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x12, 0x32, 0x0a, 0x0b, 0x69, 0x6e, 0x62, 0x6f, 0x75, + 0x6e, 0x64, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x65, 0x65, 0x52, + 0x0a, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x65, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x64, + 0x67, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x45, 0x64, 0x67, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x73, + 0x63, 0x6f, 0x70, 0x65, 0x22, 0x8c, 0x01, 0x0a, 0x0c, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x12, 0x21, 0x0a, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x22, 0x52, 0x0a, 0x14, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0e, 0x66, + 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, + 0x65, 0x64, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0xa1, 0x02, 0x0a, 0x18, 0x46, 0x6f, 0x72, 0x77, + 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, + 0x69, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, + 0x0a, 0x0c, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, + 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x6e, 0x75, 0x6d, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6e, 0x75, 0x6d, 0x4d, 0x61, + 0x78, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x65, 0x65, 0x72, 0x5f, + 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0f, 0x70, 0x65, 0x65, 0x72, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4c, 0x6f, 0x6f, + 0x6b, 0x75, 0x70, 0x12, 0x2a, 0x0a, 0x11, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, + 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x04, 0x52, 0x0f, + 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x73, 0x12, + 0x2a, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, + 0x5f, 0x69, 0x64, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x04, 0x52, 0x0f, 0x6f, 0x75, 0x74, 0x67, + 0x6f, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x73, 0x22, 0x8d, 0x04, 0x0a, 0x0f, + 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, + 0x20, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x04, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x12, 0x20, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x5f, 0x69, 0x6e, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x49, + 0x64, 0x49, 0x6e, 0x12, 0x22, 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x5f, 0x6f, + 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, 0x63, 0x68, + 0x61, 0x6e, 0x49, 0x64, 0x4f, 0x75, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x61, 0x6d, 0x74, 0x5f, 0x69, + 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x61, 0x6d, 0x74, 0x49, 0x6e, 0x12, 0x17, + 0x0a, 0x07, 0x61, 0x6d, 0x74, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x06, 0x61, 0x6d, 0x74, 0x4f, 0x75, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x65, 0x65, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x66, 0x65, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, + 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x66, 0x65, 0x65, + 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x61, 0x6d, 0x74, 0x5f, 0x69, 0x6e, 0x5f, 0x6d, + 0x73, 0x61, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x61, 0x6d, 0x74, 0x49, 0x6e, + 0x4d, 0x73, 0x61, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x61, 0x6d, 0x74, 0x5f, 0x6f, 0x75, 0x74, 0x5f, + 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x61, 0x6d, 0x74, 0x4f, + 0x75, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x5f, 0x6e, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x4e, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x70, 0x65, 0x65, + 0x72, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x69, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x70, 0x65, 0x65, 0x72, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x49, 0x6e, 0x12, 0x24, 0x0a, + 0x0e, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6f, 0x75, 0x74, 0x18, + 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x41, 0x6c, 0x69, 0x61, 0x73, + 0x4f, 0x75, 0x74, 0x12, 0x2d, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, + 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, + 0x0e, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x64, 0x88, + 0x01, 0x01, 0x12, 0x2d, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x68, + 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x0e, + 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x64, 0x88, 0x01, + 0x01, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x68, + 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x64, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x6f, 0x75, 0x74, 0x67, 0x6f, + 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x64, 0x22, 0x8c, 0x01, 0x0a, 0x19, + 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x11, 0x66, 0x6f, 0x72, + 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, + 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x10, 0x66, 0x6f, + 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2a, + 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x4f, + 0x66, 0x66, 0x73, 0x65, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x50, 0x0a, 0x1a, 0x45, 0x78, + 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, + 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, + 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x64, 0x0a, 0x0d, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x32, 0x0a, + 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x22, 0x73, 0x0a, 0x0f, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x34, 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, + 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6d, + 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, + 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, 0x19, 0x0a, 0x17, 0x43, 0x68, 0x61, 0x6e, 0x42, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x22, 0x9f, 0x01, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, + 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x45, 0x0a, 0x13, 0x73, 0x69, 0x6e, + 0x67, 0x6c, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x52, 0x11, 0x73, + 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, + 0x12, 0x42, 0x0a, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, + 0x6b, 0x75, 0x70, 0x52, 0x0f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x22, 0x49, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x37, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x22, + 0x8e, 0x01, 0x0a, 0x18, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x42, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x0c, + 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x68, 0x61, + 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x6d, 0x75, 0x6c, 0x74, + 0x69, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, + 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x42, 0x08, 0x0a, 0x06, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, + 0x22, 0x3a, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, + 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, + 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x0b, 0x6e, 0x75, 0x6d, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x22, 0x1b, 0x0a, 0x19, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3b, 0x0a, 0x18, 0x56, 0x65, 0x72, + 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6e, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x22, 0x44, 0x0a, 0x12, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, + 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xb0, 0x01, 0x0a, + 0x13, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, + 0x64, 0x12, 0x3c, 0x0a, 0x1a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x45, 0x78, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, + 0x32, 0x0a, 0x14, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, + 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, + 0x6f, 0x6f, 0x6e, 0x22, 0x18, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, + 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3b, 0x0a, + 0x17, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x74, + 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, 0x52, 0x0a, + 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x73, 0x22, 0x39, 0x0a, 0x17, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, 0x65, + 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, + 0x4b, 0x65, 0x79, 0x49, 0x64, 0x22, 0x34, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, + 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0x55, 0x0a, 0x16, 0x4d, + 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x22, 0x18, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xe4, 0x01, 0x0a, + 0x17, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x12, 0x6d, 0x65, 0x74, 0x68, + 0x6f, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x6d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x63, + 0x0a, 0x16, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x33, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0xcc, 0x08, 0x0a, 0x07, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, + 0x2e, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x46, 0x61, + 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, + 0x3b, 0x0a, 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0d, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, + 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x08, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x6e, 0x69, + 0x6f, 0x6e, 0x5f, 0x73, 0x68, 0x61, 0x5f, 0x32, 0x35, 0x36, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0b, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x53, 0x68, 0x61, 0x32, 0x35, 0x36, 0x12, 0x1f, 0x0a, + 0x0b, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x66, + 0x6c, 0x61, 0x67, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x12, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x8b, + 0x06, 0x0a, 0x0b, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x0c, + 0x0a, 0x08, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45, 0x44, 0x10, 0x00, 0x12, 0x28, 0x0a, 0x24, + 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4b, + 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, + 0x41, 0x49, 0x4c, 0x53, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, + 0x45, 0x43, 0x54, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x4d, 0x4f, 0x55, + 0x4e, 0x54, 0x10, 0x02, 0x12, 0x1f, 0x0a, 0x1b, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x5f, 0x49, 0x4e, + 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x43, 0x4c, 0x54, 0x56, 0x5f, 0x45, 0x58, 0x50, + 0x49, 0x52, 0x59, 0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x5f, 0x49, + 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x4d, + 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x04, 0x12, 0x19, 0x0a, 0x15, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x5f, + 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, 0x10, + 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x52, 0x45, 0x41, + 0x4c, 0x4d, 0x10, 0x06, 0x12, 0x13, 0x0a, 0x0f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, + 0x4f, 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, 0x10, 0x07, 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x56, + 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, + 0x4f, 0x4e, 0x10, 0x08, 0x12, 0x16, 0x0a, 0x12, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, + 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x48, 0x4d, 0x41, 0x43, 0x10, 0x09, 0x12, 0x15, 0x0a, 0x11, + 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x4b, 0x45, + 0x59, 0x10, 0x0a, 0x12, 0x18, 0x0a, 0x14, 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x42, 0x45, + 0x4c, 0x4f, 0x57, 0x5f, 0x4d, 0x49, 0x4e, 0x49, 0x4d, 0x55, 0x4d, 0x10, 0x0b, 0x12, 0x14, 0x0a, + 0x10, 0x46, 0x45, 0x45, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, + 0x54, 0x10, 0x0c, 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, + 0x5f, 0x43, 0x4c, 0x54, 0x56, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x10, 0x0d, 0x12, 0x14, + 0x0a, 0x10, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, + 0x45, 0x44, 0x10, 0x0e, 0x12, 0x1d, 0x0a, 0x19, 0x54, 0x45, 0x4d, 0x50, 0x4f, 0x52, 0x41, 0x52, + 0x59, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, + 0x45, 0x10, 0x0f, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x45, 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x5f, + 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x45, 0x41, 0x54, 0x55, 0x52, 0x45, 0x5f, 0x4d, 0x49, 0x53, + 0x53, 0x49, 0x4e, 0x47, 0x10, 0x10, 0x12, 0x24, 0x0a, 0x20, 0x52, 0x45, 0x51, 0x55, 0x49, 0x52, + 0x45, 0x44, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x46, 0x45, 0x41, 0x54, 0x55, + 0x52, 0x45, 0x5f, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, 0x10, 0x11, 0x12, 0x15, 0x0a, 0x11, + 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x4e, 0x45, 0x58, 0x54, 0x5f, 0x50, 0x45, 0x45, + 0x52, 0x10, 0x12, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x45, 0x4d, 0x50, 0x4f, 0x52, 0x41, 0x52, 0x59, + 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x13, 0x12, + 0x1a, 0x0a, 0x16, 0x50, 0x45, 0x52, 0x4d, 0x41, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x44, + 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x14, 0x12, 0x1d, 0x0a, 0x19, 0x50, + 0x45, 0x52, 0x4d, 0x41, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, + 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x15, 0x12, 0x12, 0x0a, 0x0e, 0x45, 0x58, + 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x46, 0x41, 0x52, 0x10, 0x16, 0x12, 0x0f, + 0x0a, 0x0b, 0x4d, 0x50, 0x50, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x17, 0x12, + 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, + 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x18, 0x12, 0x1a, 0x0a, 0x16, 0x49, 0x4e, + 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x42, 0x4c, 0x49, 0x4e, + 0x44, 0x49, 0x4e, 0x47, 0x10, 0x19, 0x12, 0x15, 0x0a, 0x10, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, + 0x41, 0x4c, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0xe5, 0x07, 0x12, 0x14, 0x0a, + 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, + 0x10, 0xe6, 0x07, 0x12, 0x17, 0x0a, 0x12, 0x55, 0x4e, 0x52, 0x45, 0x41, 0x44, 0x41, 0x42, 0x4c, + 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0xe7, 0x07, 0x4a, 0x04, 0x08, 0x02, + 0x10, 0x03, 0x22, 0xb3, 0x03, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x48, 0x61, 0x73, + 0x68, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1c, + 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x23, 0x0a, 0x0d, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x46, 0x6c, 0x61, 0x67, + 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x6c, 0x61, + 0x67, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, + 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x2a, + 0x0a, 0x11, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x6d, + 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x68, 0x74, 0x6c, 0x63, 0x4d, + 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, + 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x62, 0x61, + 0x73, 0x65, 0x46, 0x65, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, + 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, + 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, + 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x68, 0x74, 0x6c, + 0x63, 0x4d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2a, 0x0a, 0x11, + 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x5f, 0x64, 0x61, 0x74, + 0x61, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x65, 0x78, 0x74, 0x72, 0x61, 0x4f, 0x70, + 0x61, 0x71, 0x75, 0x65, 0x44, 0x61, 0x74, 0x61, 0x22, 0x5d, 0x0a, 0x0a, 0x4d, 0x61, 0x63, 0x61, + 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, + 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x09, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x03, 0x6f, 0x70, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4f, 0x70, 0x52, 0x03, 0x6f, 0x70, 0x73, 0x22, 0x36, 0x0a, 0x02, 0x4f, 0x70, 0x12, 0x16, 0x0a, + 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, + 0xdd, 0x01, 0x0a, 0x13, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, + 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, + 0x6f, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, + 0x12, 0x1e, 0x0a, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x12, 0x4d, 0x0a, 0x24, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, + 0x74, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x73, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x66, 0x75, 0x6c, + 0x6c, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1f, + 0x63, 0x68, 0x65, 0x63, 0x6b, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x65, 0x72, 0x6d, + 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x46, 0x75, 0x6c, 0x6c, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x22, + 0x2c, 0x0a, 0x14, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x22, 0xa4, 0x04, + 0x0a, 0x14, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x61, 0x77, 0x5f, 0x6d, 0x61, 0x63, + 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x72, 0x61, 0x77, + 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x17, 0x63, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x5f, 0x63, 0x61, 0x76, 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x63, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x43, 0x61, 0x76, 0x65, 0x61, 0x74, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x34, 0x0a, 0x0b, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x41, 0x75, 0x74, 0x68, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x41, 0x75, 0x74, 0x68, 0x12, 0x2d, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x52, 0x50, 0x43, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x07, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x52, 0x50, 0x43, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0c, 0x72, 0x65, 0x67, 0x5f, 0x63, 0x6f, + 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0b, + 0x72, 0x65, 0x67, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6d, + 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6d, 0x73, 0x67, + 0x49, 0x64, 0x12, 0x55, 0x0a, 0x0e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x70, + 0x61, 0x69, 0x72, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x50, 0x61, 0x69, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x50, 0x61, 0x69, 0x72, 0x73, 0x1a, 0x57, 0x0a, 0x12, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x61, 0x69, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x2b, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x42, 0x10, 0x0a, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x22, 0x28, 0x0a, 0x0e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x34, + 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x41, 0x75, 0x74, 0x68, 0x12, 0x26, 0x0a, 0x0f, + 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x75, 0x72, 0x69, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x46, 0x75, 0x6c, + 0x6c, 0x55, 0x72, 0x69, 0x22, 0xab, 0x01, 0x0a, 0x0a, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x66, 0x75, + 0x6c, 0x6c, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x46, 0x75, 0x6c, 0x6c, 0x55, 0x72, 0x69, 0x12, 0x1d, 0x0a, 0x0a, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x72, 0x70, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x70, 0x63, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x79, + 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, + 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61, + 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x65, 0x72, + 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x22, 0xc0, 0x01, 0x0a, 0x15, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, + 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x0a, + 0x72, 0x65, 0x66, 0x5f, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x08, 0x72, 0x65, 0x66, 0x4d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x08, 0x72, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x08, 0x72, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x36, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x64, 0x62, + 0x61, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x46, 0x65, 0x65, 0x64, 0x62, + 0x61, 0x63, 0x6b, 0x48, 0x00, 0x52, 0x08, 0x66, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x42, + 0x14, 0x0a, 0x12, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xa6, 0x01, 0x0a, 0x16, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, + 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x69, 0x64, 0x64, 0x6c, + 0x65, 0x77, 0x61, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x1b, 0x63, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x5f, 0x63, 0x61, 0x76, + 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x18, + 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x43, 0x61, + 0x76, 0x65, 0x61, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x61, 0x64, + 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0c, 0x72, 0x65, 0x61, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x22, 0x8b, + 0x01, 0x0a, 0x11, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x46, 0x65, 0x65, 0x64, + 0x62, 0x61, 0x63, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, + 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x16, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x2a, 0xcb, 0x02, 0x0a, + 0x10, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x00, 0x12, 0x1b, + 0x0a, 0x17, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x43, + 0x52, 0x49, 0x50, 0x54, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x26, 0x0a, 0x22, 0x53, + 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, + 0x53, 0x53, 0x5f, 0x56, 0x30, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, + 0x48, 0x10, 0x02, 0x12, 0x26, 0x0a, 0x22, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x56, 0x30, 0x5f, 0x53, 0x43, + 0x52, 0x49, 0x50, 0x54, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, 0x53, + 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, + 0x59, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x53, 0x49, 0x47, 0x10, 0x05, 0x12, 0x18, 0x0a, + 0x14, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x55, 0x4c, + 0x4c, 0x44, 0x41, 0x54, 0x41, 0x10, 0x06, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x43, 0x52, 0x49, 0x50, + 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x4e, 0x44, + 0x41, 0x52, 0x44, 0x10, 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x55, 0x4e, 0x4b, + 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x08, 0x12, 0x22, 0x0a, 0x1e, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x56, 0x31, + 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x10, 0x09, 0x2a, 0x62, 0x0a, 0x15, 0x43, 0x6f, + 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, + 0x65, 0x67, 0x79, 0x12, 0x1e, 0x0a, 0x1a, 0x53, 0x54, 0x52, 0x41, 0x54, 0x45, 0x47, 0x59, 0x5f, + 0x55, 0x53, 0x45, 0x5f, 0x47, 0x4c, 0x4f, 0x42, 0x41, 0x4c, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, + 0x47, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x54, 0x52, 0x41, 0x54, 0x45, 0x47, 0x59, 0x5f, + 0x4c, 0x41, 0x52, 0x47, 0x45, 0x53, 0x54, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, 0x52, + 0x41, 0x54, 0x45, 0x47, 0x59, 0x5f, 0x52, 0x41, 0x4e, 0x44, 0x4f, 0x4d, 0x10, 0x02, 0x2a, 0xac, + 0x01, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, + 0x0a, 0x13, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, + 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x4e, 0x45, 0x53, 0x54, 0x45, + 0x44, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, + 0x1e, 0x0a, 0x1a, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, + 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x02, 0x12, + 0x1d, 0x0a, 0x19, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, 0x5f, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, + 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 0x12, 0x12, + 0x0a, 0x0e, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, + 0x10, 0x04, 0x12, 0x19, 0x0a, 0x15, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, 0x5f, 0x54, 0x41, 0x50, + 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x10, 0x05, 0x2a, 0xa8, 0x01, + 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x1b, 0x0a, 0x17, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, + 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x0a, 0x0a, + 0x06, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, + 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x02, + 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x10, 0x03, 0x12, 0x19, 0x0a, + 0x15, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x45, 0x4e, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x44, + 0x5f, 0x4c, 0x45, 0x41, 0x53, 0x45, 0x10, 0x04, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x49, 0x4d, 0x50, + 0x4c, 0x45, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x10, 0x05, 0x12, 0x1a, 0x0a, 0x16, + 0x53, 0x49, 0x4d, 0x50, 0x4c, 0x45, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4f, + 0x56, 0x45, 0x52, 0x4c, 0x41, 0x59, 0x10, 0x06, 0x2a, 0x61, 0x0a, 0x09, 0x49, 0x6e, 0x69, 0x74, + 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, + 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, + 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x10, + 0x01, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x52, + 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x10, 0x02, 0x12, 0x12, 0x0a, 0x0e, 0x49, 0x4e, 0x49, 0x54, 0x49, + 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x42, 0x4f, 0x54, 0x48, 0x10, 0x03, 0x2a, 0x60, 0x0a, 0x0e, 0x52, + 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, + 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, + 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x49, + 0x4e, 0x43, 0x4f, 0x4d, 0x49, 0x4e, 0x47, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x10, 0x02, 0x12, 0x11, + 0x0a, 0x0d, 0x4f, 0x55, 0x54, 0x47, 0x4f, 0x49, 0x4e, 0x47, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x10, + 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x04, 0x2a, 0x71, 0x0a, + 0x11, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x63, 0x6f, + 0x6d, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x55, 0x54, 0x43, 0x4f, 0x4d, 0x45, 0x5f, 0x55, 0x4e, + 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4c, 0x41, 0x49, 0x4d, + 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x45, + 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x41, 0x42, 0x41, 0x4e, 0x44, 0x4f, 0x4e, 0x45, 0x44, + 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x46, 0x49, 0x52, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x47, + 0x45, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x05, + 0x2a, 0x39, 0x0a, 0x0e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, + 0x1a, 0x0a, 0x16, 0x42, 0x45, 0x54, 0x57, 0x45, 0x45, 0x4e, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x43, + 0x45, 0x4e, 0x54, 0x52, 0x41, 0x4c, 0x49, 0x54, 0x59, 0x10, 0x01, 0x2a, 0x3b, 0x0a, 0x10, 0x49, + 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, + 0x0c, 0x0a, 0x08, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, + 0x07, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x41, + 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x2a, 0xf6, 0x01, 0x0a, 0x14, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x12, 0x17, 0x0a, 0x13, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, + 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x46, 0x41, + 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x49, 0x4d, + 0x45, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, + 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x4f, 0x55, 0x54, + 0x45, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, + 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x2c, 0x0a, + 0x28, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, + 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, + 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x04, 0x12, 0x27, 0x0a, 0x23, 0x46, + 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, + 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, + 0x43, 0x45, 0x10, 0x05, 0x12, 0x1b, 0x0a, 0x17, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, + 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, + 0x06, 0x2a, 0x89, 0x05, 0x0a, 0x0a, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, + 0x12, 0x18, 0x0a, 0x14, 0x44, 0x41, 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x53, 0x5f, 0x50, 0x52, 0x4f, + 0x54, 0x45, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x41, + 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x45, 0x43, 0x54, 0x5f, 0x4f, + 0x50, 0x54, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x5f, + 0x52, 0x4f, 0x55, 0x49, 0x4e, 0x47, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x10, 0x03, 0x12, 0x1f, 0x0a, + 0x1b, 0x55, 0x50, 0x46, 0x52, 0x4f, 0x4e, 0x54, 0x5f, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, + 0x4e, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x04, 0x12, 0x1f, + 0x0a, 0x1b, 0x55, 0x50, 0x46, 0x52, 0x4f, 0x4e, 0x54, 0x5f, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, + 0x57, 0x4e, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x05, 0x12, + 0x16, 0x0a, 0x12, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, + 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x47, 0x4f, 0x53, 0x53, 0x49, + 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x07, 0x12, + 0x11, 0x0a, 0x0d, 0x54, 0x4c, 0x56, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x51, + 0x10, 0x08, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x4c, 0x56, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, + 0x4f, 0x50, 0x54, 0x10, 0x09, 0x12, 0x1a, 0x0a, 0x16, 0x45, 0x58, 0x54, 0x5f, 0x47, 0x4f, 0x53, + 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, + 0x0a, 0x12, 0x1a, 0x0a, 0x16, 0x45, 0x58, 0x54, 0x5f, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, + 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0b, 0x12, 0x19, 0x0a, + 0x15, 0x53, 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, + 0x45, 0x59, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x0c, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x54, 0x41, 0x54, + 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x4f, 0x50, + 0x54, 0x10, 0x0d, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, + 0x44, 0x44, 0x52, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x0e, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x41, 0x59, + 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0f, 0x12, + 0x0b, 0x0a, 0x07, 0x4d, 0x50, 0x50, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x10, 0x12, 0x0b, 0x0a, 0x07, + 0x4d, 0x50, 0x50, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x11, 0x12, 0x16, 0x0a, 0x12, 0x57, 0x55, 0x4d, + 0x42, 0x4f, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, + 0x12, 0x12, 0x16, 0x0a, 0x12, 0x57, 0x55, 0x4d, 0x42, 0x4f, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, + 0x45, 0x4c, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x13, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x4e, 0x43, + 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x14, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x4e, + 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x15, 0x12, 0x1d, 0x0a, 0x19, 0x41, + 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x5a, 0x45, 0x52, 0x4f, 0x5f, 0x46, 0x45, 0x45, 0x5f, + 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x16, 0x12, 0x1d, 0x0a, 0x19, 0x41, 0x4e, + 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x5a, 0x45, 0x52, 0x4f, 0x5f, 0x46, 0x45, 0x45, 0x5f, 0x48, + 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x17, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x4f, 0x55, + 0x54, 0x45, 0x5f, 0x42, 0x4c, 0x49, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x52, 0x45, 0x51, 0x55, + 0x49, 0x52, 0x45, 0x44, 0x10, 0x18, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x5f, + 0x42, 0x4c, 0x49, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x41, + 0x4c, 0x10, 0x19, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4d, 0x50, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x1e, + 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4d, 0x50, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x1f, 0x2a, 0xac, 0x01, + 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, + 0x1a, 0x0a, 0x16, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, + 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x55, + 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x50, 0x45, + 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x55, 0x50, 0x44, 0x41, 0x54, + 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, + 0x55, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, + 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, + 0x5f, 0x45, 0x52, 0x52, 0x10, 0x03, 0x12, 0x24, 0x0a, 0x20, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, + 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, + 0x5f, 0x50, 0x41, 0x52, 0x41, 0x4d, 0x45, 0x54, 0x45, 0x52, 0x10, 0x04, 0x32, 0x99, 0x28, 0x0a, + 0x09, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x4a, 0x0a, 0x0d, 0x57, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, + 0x6c, 0x73, 0x12, 0x44, 0x0a, 0x0b, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, + 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, + 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x53, 0x65, 0x6e, 0x64, + 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, + 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, + 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, + 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, + 0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, + 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x01, 0x12, 0x3b, 0x0a, 0x08, + 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0a, 0x4e, 0x65, 0x77, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, + 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, + 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, + 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x19, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, + 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, + 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, + 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, + 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, + 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x38, 0x0a, 0x07, + 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x44, 0x65, 0x62, + 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, + 0x65, 0x74, 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, + 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x50, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, + 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, + 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x16, + 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, + 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0f, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, + 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x43, 0x0a, 0x0b, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, + 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x53, 0x0a, 0x10, 0x42, + 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, + 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, + 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, + 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x4c, 0x0a, 0x10, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x53, 0x74, 0x65, 0x70, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, + 0x67, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x73, 0x70, 0x12, 0x50, + 0x0a, 0x0f, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x6f, + 0x72, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, + 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, + 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, + 0x12, 0x46, 0x0a, 0x0c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0e, 0x41, 0x62, 0x61, 0x6e, + 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, 0x01, 0x12, 0x3f, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x12, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x46, 0x0a, 0x0b, 0x53, 0x65, 0x6e, + 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, + 0x01, 0x12, 0x46, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, + 0x53, 0x79, 0x6e, 0x63, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, + 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x37, 0x0a, 0x0a, 0x41, 0x64, 0x64, + 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x45, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, + 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x0d, 0x4c, 0x6f, 0x6f, + 0x6b, 0x75, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x1a, 0x0e, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x41, + 0x0a, 0x11, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, + 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, + 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x30, + 0x01, 0x12, 0x54, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x61, 0x6e, 0x63, 0x65, + 0x6c, 0x65, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x49, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x44, 0x65, 0x6c, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x49, 0x6e, 0x76, 0x6f, + 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x32, 0x0a, 0x0c, 0x44, 0x65, 0x63, 0x6f, 0x64, + 0x65, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x1a, 0x0d, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x47, 0x0a, 0x0c, 0x4c, + 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x56, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0d, 0x44, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x62, 0x65, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x47, 0x0a, 0x0e, 0x47, 0x65, + 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x19, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x12, 0x36, + 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, + 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x44, 0x0a, 0x0b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0e, + 0x47, 0x65, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x35, 0x0a, + 0x0a, 0x53, 0x74, 0x6f, 0x70, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x12, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, + 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x20, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, + 0x6f, 0x67, 0x79, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, + 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, + 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x41, 0x0a, + 0x0a, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x18, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, + 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x3e, 0x0a, 0x09, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, + 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x4e, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x56, 0x0a, 0x11, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, + 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, + 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, + 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x13, 0x45, 0x78, 0x70, 0x6f, + 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, + 0x21, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x54, 0x0a, 0x17, 0x45, 0x78, 0x70, 0x6f, + 0x72, 0x74, 0x41, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x73, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, + 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, + 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x4e, + 0x0a, 0x10, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x1a, 0x1f, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, 0x6e, + 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, + 0x0a, 0x15, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, + 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x17, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, + 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, + 0x73, 0x12, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, + 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x30, 0x01, + 0x12, 0x47, 0x0a, 0x0c, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, + 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, + 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x4c, 0x69, 0x73, + 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x12, 0x1d, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, + 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, + 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x10, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x12, + 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, + 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, + 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x50, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x53, 0x0a, 0x18, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x61, 0x72, + 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, + 0x65, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x65, 0x72, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, + 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, + 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x1b, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, + 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, + 0x56, 0x0a, 0x11, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, + 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, + 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x17, 0x53, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x62, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x73, 0x12, 0x25, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x62, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x30, + 0x01, 0x12, 0x44, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, + 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, + 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x14, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, + 0x70, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x22, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x48, 0x74, + 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, + 0x75, 0x70, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, + 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} var ( file_lightning_proto_rawDescOnce sync.Once - file_lightning_proto_rawDescData []byte + file_lightning_proto_rawDescData = file_lightning_proto_rawDesc ) func file_lightning_proto_rawDescGZIP() []byte { file_lightning_proto_rawDescOnce.Do(func() { - file_lightning_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_lightning_proto_rawDesc), len(file_lightning_proto_rawDesc))) + file_lightning_proto_rawDescData = protoimpl.X.CompressGZIP(file_lightning_proto_rawDescData) }) return file_lightning_proto_rawDescData } -var file_lightning_proto_enumTypes = make([]protoimpl.EnumInfo, 22) -var file_lightning_proto_msgTypes = make([]protoimpl.MessageInfo, 234) -var file_lightning_proto_goTypes = []any{ +var file_lightning_proto_enumTypes = make([]protoimpl.EnumInfo, 21) +var file_lightning_proto_msgTypes = make([]protoimpl.MessageInfo, 232) +var file_lightning_proto_goTypes = []interface{}{ (OutputScriptType)(0), // 0: lnrpc.OutputScriptType (CoinSelectionStrategy)(0), // 1: lnrpc.CoinSelectionStrategy (AddressType)(0), // 2: lnrpc.AddressType @@ -20164,595 +22207,594 @@ var file_lightning_proto_goTypes = []any{ (Initiator)(0), // 4: lnrpc.Initiator (ResolutionType)(0), // 5: lnrpc.ResolutionType (ResolutionOutcome)(0), // 6: lnrpc.ResolutionOutcome - (GraphCacheStatus)(0), // 7: lnrpc.GraphCacheStatus - (NodeMetricType)(0), // 8: lnrpc.NodeMetricType - (InvoiceHTLCState)(0), // 9: lnrpc.InvoiceHTLCState - (PaymentFailureReason)(0), // 10: lnrpc.PaymentFailureReason - (FeatureBit)(0), // 11: lnrpc.FeatureBit - (UpdateFailure)(0), // 12: lnrpc.UpdateFailure - (ChannelCloseSummary_ClosureType)(0), // 13: lnrpc.ChannelCloseSummary.ClosureType - (Peer_SyncType)(0), // 14: lnrpc.Peer.SyncType - (PeerEvent_EventType)(0), // 15: lnrpc.PeerEvent.EventType - (PendingChannelsResponse_ForceClosedChannel_AnchorState)(0), // 16: lnrpc.PendingChannelsResponse.ForceClosedChannel.AnchorState - (ChannelEventUpdate_UpdateType)(0), // 17: lnrpc.ChannelEventUpdate.UpdateType - (Invoice_InvoiceState)(0), // 18: lnrpc.Invoice.InvoiceState - (Payment_PaymentStatus)(0), // 19: lnrpc.Payment.PaymentStatus - (HTLCAttempt_HTLCStatus)(0), // 20: lnrpc.HTLCAttempt.HTLCStatus - (Failure_FailureCode)(0), // 21: lnrpc.Failure.FailureCode - (*LookupHtlcResolutionRequest)(nil), // 22: lnrpc.LookupHtlcResolutionRequest - (*LookupHtlcResolutionResponse)(nil), // 23: lnrpc.LookupHtlcResolutionResponse - (*SubscribeCustomMessagesRequest)(nil), // 24: lnrpc.SubscribeCustomMessagesRequest - (*CustomMessage)(nil), // 25: lnrpc.CustomMessage - (*SendCustomMessageRequest)(nil), // 26: lnrpc.SendCustomMessageRequest - (*SendCustomMessageResponse)(nil), // 27: lnrpc.SendCustomMessageResponse - (*SubscribeOnionMessagesRequest)(nil), // 28: lnrpc.SubscribeOnionMessagesRequest - (*OnionMessageUpdate)(nil), // 29: lnrpc.OnionMessageUpdate - (*SendOnionMessageRequest)(nil), // 30: lnrpc.SendOnionMessageRequest - (*SendOnionMessageResponse)(nil), // 31: lnrpc.SendOnionMessageResponse - (*Utxo)(nil), // 32: lnrpc.Utxo - (*OutputDetail)(nil), // 33: lnrpc.OutputDetail - (*Transaction)(nil), // 34: lnrpc.Transaction - (*GetTransactionsRequest)(nil), // 35: lnrpc.GetTransactionsRequest - (*TransactionDetails)(nil), // 36: lnrpc.TransactionDetails - (*FeeLimit)(nil), // 37: lnrpc.FeeLimit - (*ChannelAcceptRequest)(nil), // 38: lnrpc.ChannelAcceptRequest - (*ChannelAcceptResponse)(nil), // 39: lnrpc.ChannelAcceptResponse - (*ChannelPoint)(nil), // 40: lnrpc.ChannelPoint - (*OutPoint)(nil), // 41: lnrpc.OutPoint - (*PreviousOutPoint)(nil), // 42: lnrpc.PreviousOutPoint - (*LightningAddress)(nil), // 43: lnrpc.LightningAddress - (*EstimateFeeRequest)(nil), // 44: lnrpc.EstimateFeeRequest - (*EstimateFeeResponse)(nil), // 45: lnrpc.EstimateFeeResponse - (*SendManyRequest)(nil), // 46: lnrpc.SendManyRequest - (*SendManyResponse)(nil), // 47: lnrpc.SendManyResponse - (*SendCoinsRequest)(nil), // 48: lnrpc.SendCoinsRequest - (*SendCoinsResponse)(nil), // 49: lnrpc.SendCoinsResponse - (*ListUnspentRequest)(nil), // 50: lnrpc.ListUnspentRequest - (*ListUnspentResponse)(nil), // 51: lnrpc.ListUnspentResponse - (*NewAddressRequest)(nil), // 52: lnrpc.NewAddressRequest - (*NewAddressResponse)(nil), // 53: lnrpc.NewAddressResponse - (*SignMessageRequest)(nil), // 54: lnrpc.SignMessageRequest - (*SignMessageResponse)(nil), // 55: lnrpc.SignMessageResponse - (*VerifyMessageRequest)(nil), // 56: lnrpc.VerifyMessageRequest - (*VerifyMessageResponse)(nil), // 57: lnrpc.VerifyMessageResponse - (*ConnectPeerRequest)(nil), // 58: lnrpc.ConnectPeerRequest - (*ConnectPeerResponse)(nil), // 59: lnrpc.ConnectPeerResponse - (*DisconnectPeerRequest)(nil), // 60: lnrpc.DisconnectPeerRequest - (*DisconnectPeerResponse)(nil), // 61: lnrpc.DisconnectPeerResponse - (*HTLC)(nil), // 62: lnrpc.HTLC - (*ChannelConstraints)(nil), // 63: lnrpc.ChannelConstraints - (*Channel)(nil), // 64: lnrpc.Channel - (*ListChannelsRequest)(nil), // 65: lnrpc.ListChannelsRequest - (*ListChannelsResponse)(nil), // 66: lnrpc.ListChannelsResponse - (*AliasMap)(nil), // 67: lnrpc.AliasMap - (*ListAliasesRequest)(nil), // 68: lnrpc.ListAliasesRequest - (*ListAliasesResponse)(nil), // 69: lnrpc.ListAliasesResponse - (*ChannelCloseSummary)(nil), // 70: lnrpc.ChannelCloseSummary - (*Resolution)(nil), // 71: lnrpc.Resolution - (*ClosedChannelsRequest)(nil), // 72: lnrpc.ClosedChannelsRequest - (*ClosedChannelsResponse)(nil), // 73: lnrpc.ClosedChannelsResponse - (*Peer)(nil), // 74: lnrpc.Peer - (*TimestampedError)(nil), // 75: lnrpc.TimestampedError - (*ListPeersRequest)(nil), // 76: lnrpc.ListPeersRequest - (*ListPeersResponse)(nil), // 77: lnrpc.ListPeersResponse - (*PeerEventSubscription)(nil), // 78: lnrpc.PeerEventSubscription - (*PeerEvent)(nil), // 79: lnrpc.PeerEvent - (*GetInfoRequest)(nil), // 80: lnrpc.GetInfoRequest - (*GetInfoResponse)(nil), // 81: lnrpc.GetInfoResponse - (*GetDebugInfoRequest)(nil), // 82: lnrpc.GetDebugInfoRequest - (*GetDebugInfoResponse)(nil), // 83: lnrpc.GetDebugInfoResponse - (*GetRecoveryInfoRequest)(nil), // 84: lnrpc.GetRecoveryInfoRequest - (*GetRecoveryInfoResponse)(nil), // 85: lnrpc.GetRecoveryInfoResponse - (*Chain)(nil), // 86: lnrpc.Chain - (*ChannelOpenUpdate)(nil), // 87: lnrpc.ChannelOpenUpdate - (*CloseOutput)(nil), // 88: lnrpc.CloseOutput - (*ChannelCloseUpdate)(nil), // 89: lnrpc.ChannelCloseUpdate - (*CloseChannelRequest)(nil), // 90: lnrpc.CloseChannelRequest - (*CloseStatusUpdate)(nil), // 91: lnrpc.CloseStatusUpdate - (*PendingUpdate)(nil), // 92: lnrpc.PendingUpdate - (*InstantUpdate)(nil), // 93: lnrpc.InstantUpdate - (*ReadyForPsbtFunding)(nil), // 94: lnrpc.ReadyForPsbtFunding - (*BatchOpenChannelRequest)(nil), // 95: lnrpc.BatchOpenChannelRequest - (*BatchOpenChannel)(nil), // 96: lnrpc.BatchOpenChannel - (*BatchOpenChannelResponse)(nil), // 97: lnrpc.BatchOpenChannelResponse - (*OpenChannelRequest)(nil), // 98: lnrpc.OpenChannelRequest - (*OpenStatusUpdate)(nil), // 99: lnrpc.OpenStatusUpdate - (*KeyLocator)(nil), // 100: lnrpc.KeyLocator - (*KeyDescriptor)(nil), // 101: lnrpc.KeyDescriptor - (*ChanPointShim)(nil), // 102: lnrpc.ChanPointShim - (*PsbtShim)(nil), // 103: lnrpc.PsbtShim - (*FundingShim)(nil), // 104: lnrpc.FundingShim - (*FundingShimCancel)(nil), // 105: lnrpc.FundingShimCancel - (*FundingPsbtVerify)(nil), // 106: lnrpc.FundingPsbtVerify - (*FundingPsbtFinalize)(nil), // 107: lnrpc.FundingPsbtFinalize - (*FundingTransitionMsg)(nil), // 108: lnrpc.FundingTransitionMsg - (*FundingStateStepResp)(nil), // 109: lnrpc.FundingStateStepResp - (*PendingHTLC)(nil), // 110: lnrpc.PendingHTLC - (*PendingChannelsRequest)(nil), // 111: lnrpc.PendingChannelsRequest - (*PendingChannelsResponse)(nil), // 112: lnrpc.PendingChannelsResponse - (*ChannelEventSubscription)(nil), // 113: lnrpc.ChannelEventSubscription - (*ChannelCommitUpdate)(nil), // 114: lnrpc.ChannelCommitUpdate - (*ChannelEventUpdate)(nil), // 115: lnrpc.ChannelEventUpdate - (*WalletAccountBalance)(nil), // 116: lnrpc.WalletAccountBalance - (*WalletBalanceRequest)(nil), // 117: lnrpc.WalletBalanceRequest - (*WalletBalanceResponse)(nil), // 118: lnrpc.WalletBalanceResponse - (*Amount)(nil), // 119: lnrpc.Amount - (*ChannelBalanceRequest)(nil), // 120: lnrpc.ChannelBalanceRequest - (*ChannelBalanceResponse)(nil), // 121: lnrpc.ChannelBalanceResponse - (*QueryRoutesRequest)(nil), // 122: lnrpc.QueryRoutesRequest - (*NodePair)(nil), // 123: lnrpc.NodePair - (*EdgeLocator)(nil), // 124: lnrpc.EdgeLocator - (*QueryRoutesResponse)(nil), // 125: lnrpc.QueryRoutesResponse - (*Hop)(nil), // 126: lnrpc.Hop - (*MPPRecord)(nil), // 127: lnrpc.MPPRecord - (*AMPRecord)(nil), // 128: lnrpc.AMPRecord - (*Route)(nil), // 129: lnrpc.Route - (*NodeInfoRequest)(nil), // 130: lnrpc.NodeInfoRequest - (*NodeInfo)(nil), // 131: lnrpc.NodeInfo - (*LightningNode)(nil), // 132: lnrpc.LightningNode - (*NodeAddress)(nil), // 133: lnrpc.NodeAddress - (*RoutingPolicy)(nil), // 134: lnrpc.RoutingPolicy - (*ChannelAuthProof)(nil), // 135: lnrpc.ChannelAuthProof - (*ChannelEdge)(nil), // 136: lnrpc.ChannelEdge - (*ChannelGraphRequest)(nil), // 137: lnrpc.ChannelGraphRequest - (*ChannelGraph)(nil), // 138: lnrpc.ChannelGraph - (*NodeMetricsRequest)(nil), // 139: lnrpc.NodeMetricsRequest - (*NodeMetricsResponse)(nil), // 140: lnrpc.NodeMetricsResponse - (*FloatMetric)(nil), // 141: lnrpc.FloatMetric - (*ChanInfoRequest)(nil), // 142: lnrpc.ChanInfoRequest - (*NetworkInfoRequest)(nil), // 143: lnrpc.NetworkInfoRequest - (*NetworkInfo)(nil), // 144: lnrpc.NetworkInfo - (*StopRequest)(nil), // 145: lnrpc.StopRequest - (*StopResponse)(nil), // 146: lnrpc.StopResponse - (*GraphTopologySubscription)(nil), // 147: lnrpc.GraphTopologySubscription - (*GraphTopologyUpdate)(nil), // 148: lnrpc.GraphTopologyUpdate - (*NodeUpdate)(nil), // 149: lnrpc.NodeUpdate - (*ChannelEdgeUpdate)(nil), // 150: lnrpc.ChannelEdgeUpdate - (*ClosedChannelUpdate)(nil), // 151: lnrpc.ClosedChannelUpdate - (*HopHint)(nil), // 152: lnrpc.HopHint - (*SetID)(nil), // 153: lnrpc.SetID - (*RouteHint)(nil), // 154: lnrpc.RouteHint - (*BlindedPaymentPath)(nil), // 155: lnrpc.BlindedPaymentPath - (*BlindedPath)(nil), // 156: lnrpc.BlindedPath - (*BlindedHop)(nil), // 157: lnrpc.BlindedHop - (*AMPInvoiceState)(nil), // 158: lnrpc.AMPInvoiceState - (*Invoice)(nil), // 159: lnrpc.Invoice - (*BlindedPathConfig)(nil), // 160: lnrpc.BlindedPathConfig - (*InvoiceHTLC)(nil), // 161: lnrpc.InvoiceHTLC - (*AMP)(nil), // 162: lnrpc.AMP - (*AddInvoiceResponse)(nil), // 163: lnrpc.AddInvoiceResponse - (*PaymentHash)(nil), // 164: lnrpc.PaymentHash - (*ListInvoiceRequest)(nil), // 165: lnrpc.ListInvoiceRequest - (*ListInvoiceResponse)(nil), // 166: lnrpc.ListInvoiceResponse - (*InvoiceSubscription)(nil), // 167: lnrpc.InvoiceSubscription - (*DelCanceledInvoiceReq)(nil), // 168: lnrpc.DelCanceledInvoiceReq - (*DelCanceledInvoiceResp)(nil), // 169: lnrpc.DelCanceledInvoiceResp - (*Payment)(nil), // 170: lnrpc.Payment - (*HTLCAttempt)(nil), // 171: lnrpc.HTLCAttempt - (*ListPaymentsRequest)(nil), // 172: lnrpc.ListPaymentsRequest - (*ListPaymentsResponse)(nil), // 173: lnrpc.ListPaymentsResponse - (*DeletePaymentRequest)(nil), // 174: lnrpc.DeletePaymentRequest - (*DeleteAllPaymentsRequest)(nil), // 175: lnrpc.DeleteAllPaymentsRequest - (*DeletePaymentResponse)(nil), // 176: lnrpc.DeletePaymentResponse - (*DeleteAllPaymentsResponse)(nil), // 177: lnrpc.DeleteAllPaymentsResponse - (*AbandonChannelRequest)(nil), // 178: lnrpc.AbandonChannelRequest - (*AbandonChannelResponse)(nil), // 179: lnrpc.AbandonChannelResponse - (*DebugLevelRequest)(nil), // 180: lnrpc.DebugLevelRequest - (*DebugLevelResponse)(nil), // 181: lnrpc.DebugLevelResponse - (*PayReqString)(nil), // 182: lnrpc.PayReqString - (*PayReq)(nil), // 183: lnrpc.PayReq - (*Feature)(nil), // 184: lnrpc.Feature - (*FeeReportRequest)(nil), // 185: lnrpc.FeeReportRequest - (*ChannelFeeReport)(nil), // 186: lnrpc.ChannelFeeReport - (*FeeReportResponse)(nil), // 187: lnrpc.FeeReportResponse - (*InboundFee)(nil), // 188: lnrpc.InboundFee - (*PolicyUpdateRequest)(nil), // 189: lnrpc.PolicyUpdateRequest - (*FailedUpdate)(nil), // 190: lnrpc.FailedUpdate - (*PolicyUpdateResponse)(nil), // 191: lnrpc.PolicyUpdateResponse - (*ForwardingHistoryRequest)(nil), // 192: lnrpc.ForwardingHistoryRequest - (*ForwardingEvent)(nil), // 193: lnrpc.ForwardingEvent - (*ForwardingHistoryResponse)(nil), // 194: lnrpc.ForwardingHistoryResponse - (*ExportChannelBackupRequest)(nil), // 195: lnrpc.ExportChannelBackupRequest - (*ChannelBackup)(nil), // 196: lnrpc.ChannelBackup - (*MultiChanBackup)(nil), // 197: lnrpc.MultiChanBackup - (*ChanBackupExportRequest)(nil), // 198: lnrpc.ChanBackupExportRequest - (*ChanBackupSnapshot)(nil), // 199: lnrpc.ChanBackupSnapshot - (*ChannelBackups)(nil), // 200: lnrpc.ChannelBackups - (*RestoreChanBackupRequest)(nil), // 201: lnrpc.RestoreChanBackupRequest - (*RestoreBackupResponse)(nil), // 202: lnrpc.RestoreBackupResponse - (*ChannelBackupSubscription)(nil), // 203: lnrpc.ChannelBackupSubscription - (*VerifyChanBackupResponse)(nil), // 204: lnrpc.VerifyChanBackupResponse - (*MacaroonPermission)(nil), // 205: lnrpc.MacaroonPermission - (*BakeMacaroonRequest)(nil), // 206: lnrpc.BakeMacaroonRequest - (*BakeMacaroonResponse)(nil), // 207: lnrpc.BakeMacaroonResponse - (*ListMacaroonIDsRequest)(nil), // 208: lnrpc.ListMacaroonIDsRequest - (*ListMacaroonIDsResponse)(nil), // 209: lnrpc.ListMacaroonIDsResponse - (*DeleteMacaroonIDRequest)(nil), // 210: lnrpc.DeleteMacaroonIDRequest - (*DeleteMacaroonIDResponse)(nil), // 211: lnrpc.DeleteMacaroonIDResponse - (*MacaroonPermissionList)(nil), // 212: lnrpc.MacaroonPermissionList - (*ListPermissionsRequest)(nil), // 213: lnrpc.ListPermissionsRequest - (*ListPermissionsResponse)(nil), // 214: lnrpc.ListPermissionsResponse - (*Failure)(nil), // 215: lnrpc.Failure - (*ChannelUpdate)(nil), // 216: lnrpc.ChannelUpdate - (*MacaroonId)(nil), // 217: lnrpc.MacaroonId - (*Op)(nil), // 218: lnrpc.Op - (*CheckMacPermRequest)(nil), // 219: lnrpc.CheckMacPermRequest - (*CheckMacPermResponse)(nil), // 220: lnrpc.CheckMacPermResponse - (*RPCMiddlewareRequest)(nil), // 221: lnrpc.RPCMiddlewareRequest - (*MetadataValues)(nil), // 222: lnrpc.MetadataValues - (*StreamAuth)(nil), // 223: lnrpc.StreamAuth - (*RPCMessage)(nil), // 224: lnrpc.RPCMessage - (*RPCMiddlewareResponse)(nil), // 225: lnrpc.RPCMiddlewareResponse - (*MiddlewareRegistration)(nil), // 226: lnrpc.MiddlewareRegistration - (*InterceptFeedback)(nil), // 227: lnrpc.InterceptFeedback - nil, // 228: lnrpc.OnionMessageUpdate.CustomRecordsEntry - nil, // 229: lnrpc.EstimateFeeRequest.AddrToAmountEntry - nil, // 230: lnrpc.SendManyRequest.AddrToAmountEntry - nil, // 231: lnrpc.Peer.FeaturesEntry - nil, // 232: lnrpc.GetInfoResponse.FeaturesEntry - nil, // 233: lnrpc.GetDebugInfoResponse.ConfigEntry - (*PendingChannelsResponse_PendingChannel)(nil), // 234: lnrpc.PendingChannelsResponse.PendingChannel - (*PendingChannelsResponse_PendingOpenChannel)(nil), // 235: lnrpc.PendingChannelsResponse.PendingOpenChannel - (*PendingChannelsResponse_WaitingCloseChannel)(nil), // 236: lnrpc.PendingChannelsResponse.WaitingCloseChannel - (*PendingChannelsResponse_Commitments)(nil), // 237: lnrpc.PendingChannelsResponse.Commitments - (*PendingChannelsResponse_ClosedChannel)(nil), // 238: lnrpc.PendingChannelsResponse.ClosedChannel - (*PendingChannelsResponse_ForceClosedChannel)(nil), // 239: lnrpc.PendingChannelsResponse.ForceClosedChannel - nil, // 240: lnrpc.WalletBalanceResponse.AccountBalanceEntry - nil, // 241: lnrpc.QueryRoutesRequest.DestCustomRecordsEntry - nil, // 242: lnrpc.Hop.CustomRecordsEntry - nil, // 243: lnrpc.LightningNode.FeaturesEntry - nil, // 244: lnrpc.LightningNode.CustomRecordsEntry - nil, // 245: lnrpc.RoutingPolicy.CustomRecordsEntry - nil, // 246: lnrpc.ChannelEdge.CustomRecordsEntry - nil, // 247: lnrpc.NodeMetricsResponse.BetweennessCentralityEntry - nil, // 248: lnrpc.NodeUpdate.FeaturesEntry - nil, // 249: lnrpc.Invoice.FeaturesEntry - nil, // 250: lnrpc.Invoice.AmpInvoiceStateEntry - nil, // 251: lnrpc.InvoiceHTLC.CustomRecordsEntry - nil, // 252: lnrpc.Payment.FirstHopCustomRecordsEntry - nil, // 253: lnrpc.PayReq.FeaturesEntry - nil, // 254: lnrpc.ListPermissionsResponse.MethodPermissionsEntry - nil, // 255: lnrpc.RPCMiddlewareRequest.MetadataPairsEntry + (NodeMetricType)(0), // 7: lnrpc.NodeMetricType + (InvoiceHTLCState)(0), // 8: lnrpc.InvoiceHTLCState + (PaymentFailureReason)(0), // 9: lnrpc.PaymentFailureReason + (FeatureBit)(0), // 10: lnrpc.FeatureBit + (UpdateFailure)(0), // 11: lnrpc.UpdateFailure + (ChannelCloseSummary_ClosureType)(0), // 12: lnrpc.ChannelCloseSummary.ClosureType + (Peer_SyncType)(0), // 13: lnrpc.Peer.SyncType + (PeerEvent_EventType)(0), // 14: lnrpc.PeerEvent.EventType + (PendingChannelsResponse_ForceClosedChannel_AnchorState)(0), // 15: lnrpc.PendingChannelsResponse.ForceClosedChannel.AnchorState + (ChannelEventUpdate_UpdateType)(0), // 16: lnrpc.ChannelEventUpdate.UpdateType + (Invoice_InvoiceState)(0), // 17: lnrpc.Invoice.InvoiceState + (Payment_PaymentStatus)(0), // 18: lnrpc.Payment.PaymentStatus + (HTLCAttempt_HTLCStatus)(0), // 19: lnrpc.HTLCAttempt.HTLCStatus + (Failure_FailureCode)(0), // 20: lnrpc.Failure.FailureCode + (*LookupHtlcResolutionRequest)(nil), // 21: lnrpc.LookupHtlcResolutionRequest + (*LookupHtlcResolutionResponse)(nil), // 22: lnrpc.LookupHtlcResolutionResponse + (*SubscribeCustomMessagesRequest)(nil), // 23: lnrpc.SubscribeCustomMessagesRequest + (*CustomMessage)(nil), // 24: lnrpc.CustomMessage + (*SendCustomMessageRequest)(nil), // 25: lnrpc.SendCustomMessageRequest + (*SendCustomMessageResponse)(nil), // 26: lnrpc.SendCustomMessageResponse + (*Utxo)(nil), // 27: lnrpc.Utxo + (*OutputDetail)(nil), // 28: lnrpc.OutputDetail + (*Transaction)(nil), // 29: lnrpc.Transaction + (*GetTransactionsRequest)(nil), // 30: lnrpc.GetTransactionsRequest + (*TransactionDetails)(nil), // 31: lnrpc.TransactionDetails + (*FeeLimit)(nil), // 32: lnrpc.FeeLimit + (*SendRequest)(nil), // 33: lnrpc.SendRequest + (*SendResponse)(nil), // 34: lnrpc.SendResponse + (*SendToRouteRequest)(nil), // 35: lnrpc.SendToRouteRequest + (*ChannelAcceptRequest)(nil), // 36: lnrpc.ChannelAcceptRequest + (*ChannelAcceptResponse)(nil), // 37: lnrpc.ChannelAcceptResponse + (*ChannelPoint)(nil), // 38: lnrpc.ChannelPoint + (*OutPoint)(nil), // 39: lnrpc.OutPoint + (*PreviousOutPoint)(nil), // 40: lnrpc.PreviousOutPoint + (*LightningAddress)(nil), // 41: lnrpc.LightningAddress + (*EstimateFeeRequest)(nil), // 42: lnrpc.EstimateFeeRequest + (*EstimateFeeResponse)(nil), // 43: lnrpc.EstimateFeeResponse + (*SendManyRequest)(nil), // 44: lnrpc.SendManyRequest + (*SendManyResponse)(nil), // 45: lnrpc.SendManyResponse + (*SendCoinsRequest)(nil), // 46: lnrpc.SendCoinsRequest + (*SendCoinsResponse)(nil), // 47: lnrpc.SendCoinsResponse + (*ListUnspentRequest)(nil), // 48: lnrpc.ListUnspentRequest + (*ListUnspentResponse)(nil), // 49: lnrpc.ListUnspentResponse + (*NewAddressRequest)(nil), // 50: lnrpc.NewAddressRequest + (*NewAddressResponse)(nil), // 51: lnrpc.NewAddressResponse + (*SignMessageRequest)(nil), // 52: lnrpc.SignMessageRequest + (*SignMessageResponse)(nil), // 53: lnrpc.SignMessageResponse + (*VerifyMessageRequest)(nil), // 54: lnrpc.VerifyMessageRequest + (*VerifyMessageResponse)(nil), // 55: lnrpc.VerifyMessageResponse + (*ConnectPeerRequest)(nil), // 56: lnrpc.ConnectPeerRequest + (*ConnectPeerResponse)(nil), // 57: lnrpc.ConnectPeerResponse + (*DisconnectPeerRequest)(nil), // 58: lnrpc.DisconnectPeerRequest + (*DisconnectPeerResponse)(nil), // 59: lnrpc.DisconnectPeerResponse + (*HTLC)(nil), // 60: lnrpc.HTLC + (*ChannelConstraints)(nil), // 61: lnrpc.ChannelConstraints + (*Channel)(nil), // 62: lnrpc.Channel + (*ListChannelsRequest)(nil), // 63: lnrpc.ListChannelsRequest + (*ListChannelsResponse)(nil), // 64: lnrpc.ListChannelsResponse + (*AliasMap)(nil), // 65: lnrpc.AliasMap + (*ListAliasesRequest)(nil), // 66: lnrpc.ListAliasesRequest + (*ListAliasesResponse)(nil), // 67: lnrpc.ListAliasesResponse + (*ChannelCloseSummary)(nil), // 68: lnrpc.ChannelCloseSummary + (*Resolution)(nil), // 69: lnrpc.Resolution + (*ClosedChannelsRequest)(nil), // 70: lnrpc.ClosedChannelsRequest + (*ClosedChannelsResponse)(nil), // 71: lnrpc.ClosedChannelsResponse + (*Peer)(nil), // 72: lnrpc.Peer + (*TimestampedError)(nil), // 73: lnrpc.TimestampedError + (*ListPeersRequest)(nil), // 74: lnrpc.ListPeersRequest + (*ListPeersResponse)(nil), // 75: lnrpc.ListPeersResponse + (*PeerEventSubscription)(nil), // 76: lnrpc.PeerEventSubscription + (*PeerEvent)(nil), // 77: lnrpc.PeerEvent + (*GetInfoRequest)(nil), // 78: lnrpc.GetInfoRequest + (*GetInfoResponse)(nil), // 79: lnrpc.GetInfoResponse + (*GetDebugInfoRequest)(nil), // 80: lnrpc.GetDebugInfoRequest + (*GetDebugInfoResponse)(nil), // 81: lnrpc.GetDebugInfoResponse + (*GetRecoveryInfoRequest)(nil), // 82: lnrpc.GetRecoveryInfoRequest + (*GetRecoveryInfoResponse)(nil), // 83: lnrpc.GetRecoveryInfoResponse + (*Chain)(nil), // 84: lnrpc.Chain + (*ChannelOpenUpdate)(nil), // 85: lnrpc.ChannelOpenUpdate + (*CloseOutput)(nil), // 86: lnrpc.CloseOutput + (*ChannelCloseUpdate)(nil), // 87: lnrpc.ChannelCloseUpdate + (*CloseChannelRequest)(nil), // 88: lnrpc.CloseChannelRequest + (*CloseStatusUpdate)(nil), // 89: lnrpc.CloseStatusUpdate + (*PendingUpdate)(nil), // 90: lnrpc.PendingUpdate + (*InstantUpdate)(nil), // 91: lnrpc.InstantUpdate + (*ReadyForPsbtFunding)(nil), // 92: lnrpc.ReadyForPsbtFunding + (*BatchOpenChannelRequest)(nil), // 93: lnrpc.BatchOpenChannelRequest + (*BatchOpenChannel)(nil), // 94: lnrpc.BatchOpenChannel + (*BatchOpenChannelResponse)(nil), // 95: lnrpc.BatchOpenChannelResponse + (*OpenChannelRequest)(nil), // 96: lnrpc.OpenChannelRequest + (*OpenStatusUpdate)(nil), // 97: lnrpc.OpenStatusUpdate + (*KeyLocator)(nil), // 98: lnrpc.KeyLocator + (*KeyDescriptor)(nil), // 99: lnrpc.KeyDescriptor + (*ChanPointShim)(nil), // 100: lnrpc.ChanPointShim + (*PsbtShim)(nil), // 101: lnrpc.PsbtShim + (*FundingShim)(nil), // 102: lnrpc.FundingShim + (*FundingShimCancel)(nil), // 103: lnrpc.FundingShimCancel + (*FundingPsbtVerify)(nil), // 104: lnrpc.FundingPsbtVerify + (*FundingPsbtFinalize)(nil), // 105: lnrpc.FundingPsbtFinalize + (*FundingTransitionMsg)(nil), // 106: lnrpc.FundingTransitionMsg + (*FundingStateStepResp)(nil), // 107: lnrpc.FundingStateStepResp + (*PendingHTLC)(nil), // 108: lnrpc.PendingHTLC + (*PendingChannelsRequest)(nil), // 109: lnrpc.PendingChannelsRequest + (*PendingChannelsResponse)(nil), // 110: lnrpc.PendingChannelsResponse + (*ChannelEventSubscription)(nil), // 111: lnrpc.ChannelEventSubscription + (*ChannelEventUpdate)(nil), // 112: lnrpc.ChannelEventUpdate + (*WalletAccountBalance)(nil), // 113: lnrpc.WalletAccountBalance + (*WalletBalanceRequest)(nil), // 114: lnrpc.WalletBalanceRequest + (*WalletBalanceResponse)(nil), // 115: lnrpc.WalletBalanceResponse + (*Amount)(nil), // 116: lnrpc.Amount + (*ChannelBalanceRequest)(nil), // 117: lnrpc.ChannelBalanceRequest + (*ChannelBalanceResponse)(nil), // 118: lnrpc.ChannelBalanceResponse + (*QueryRoutesRequest)(nil), // 119: lnrpc.QueryRoutesRequest + (*NodePair)(nil), // 120: lnrpc.NodePair + (*EdgeLocator)(nil), // 121: lnrpc.EdgeLocator + (*QueryRoutesResponse)(nil), // 122: lnrpc.QueryRoutesResponse + (*Hop)(nil), // 123: lnrpc.Hop + (*MPPRecord)(nil), // 124: lnrpc.MPPRecord + (*AMPRecord)(nil), // 125: lnrpc.AMPRecord + (*Route)(nil), // 126: lnrpc.Route + (*NodeInfoRequest)(nil), // 127: lnrpc.NodeInfoRequest + (*NodeInfo)(nil), // 128: lnrpc.NodeInfo + (*LightningNode)(nil), // 129: lnrpc.LightningNode + (*NodeAddress)(nil), // 130: lnrpc.NodeAddress + (*RoutingPolicy)(nil), // 131: lnrpc.RoutingPolicy + (*ChannelAuthProof)(nil), // 132: lnrpc.ChannelAuthProof + (*ChannelEdge)(nil), // 133: lnrpc.ChannelEdge + (*ChannelGraphRequest)(nil), // 134: lnrpc.ChannelGraphRequest + (*ChannelGraph)(nil), // 135: lnrpc.ChannelGraph + (*NodeMetricsRequest)(nil), // 136: lnrpc.NodeMetricsRequest + (*NodeMetricsResponse)(nil), // 137: lnrpc.NodeMetricsResponse + (*FloatMetric)(nil), // 138: lnrpc.FloatMetric + (*ChanInfoRequest)(nil), // 139: lnrpc.ChanInfoRequest + (*NetworkInfoRequest)(nil), // 140: lnrpc.NetworkInfoRequest + (*NetworkInfo)(nil), // 141: lnrpc.NetworkInfo + (*StopRequest)(nil), // 142: lnrpc.StopRequest + (*StopResponse)(nil), // 143: lnrpc.StopResponse + (*GraphTopologySubscription)(nil), // 144: lnrpc.GraphTopologySubscription + (*GraphTopologyUpdate)(nil), // 145: lnrpc.GraphTopologyUpdate + (*NodeUpdate)(nil), // 146: lnrpc.NodeUpdate + (*ChannelEdgeUpdate)(nil), // 147: lnrpc.ChannelEdgeUpdate + (*ClosedChannelUpdate)(nil), // 148: lnrpc.ClosedChannelUpdate + (*HopHint)(nil), // 149: lnrpc.HopHint + (*SetID)(nil), // 150: lnrpc.SetID + (*RouteHint)(nil), // 151: lnrpc.RouteHint + (*BlindedPaymentPath)(nil), // 152: lnrpc.BlindedPaymentPath + (*BlindedPath)(nil), // 153: lnrpc.BlindedPath + (*BlindedHop)(nil), // 154: lnrpc.BlindedHop + (*AMPInvoiceState)(nil), // 155: lnrpc.AMPInvoiceState + (*Invoice)(nil), // 156: lnrpc.Invoice + (*BlindedPathConfig)(nil), // 157: lnrpc.BlindedPathConfig + (*InvoiceHTLC)(nil), // 158: lnrpc.InvoiceHTLC + (*AMP)(nil), // 159: lnrpc.AMP + (*AddInvoiceResponse)(nil), // 160: lnrpc.AddInvoiceResponse + (*PaymentHash)(nil), // 161: lnrpc.PaymentHash + (*ListInvoiceRequest)(nil), // 162: lnrpc.ListInvoiceRequest + (*ListInvoiceResponse)(nil), // 163: lnrpc.ListInvoiceResponse + (*InvoiceSubscription)(nil), // 164: lnrpc.InvoiceSubscription + (*DelCanceledInvoiceReq)(nil), // 165: lnrpc.DelCanceledInvoiceReq + (*DelCanceledInvoiceResp)(nil), // 166: lnrpc.DelCanceledInvoiceResp + (*Payment)(nil), // 167: lnrpc.Payment + (*HTLCAttempt)(nil), // 168: lnrpc.HTLCAttempt + (*ListPaymentsRequest)(nil), // 169: lnrpc.ListPaymentsRequest + (*ListPaymentsResponse)(nil), // 170: lnrpc.ListPaymentsResponse + (*DeletePaymentRequest)(nil), // 171: lnrpc.DeletePaymentRequest + (*DeleteAllPaymentsRequest)(nil), // 172: lnrpc.DeleteAllPaymentsRequest + (*DeletePaymentResponse)(nil), // 173: lnrpc.DeletePaymentResponse + (*DeleteAllPaymentsResponse)(nil), // 174: lnrpc.DeleteAllPaymentsResponse + (*AbandonChannelRequest)(nil), // 175: lnrpc.AbandonChannelRequest + (*AbandonChannelResponse)(nil), // 176: lnrpc.AbandonChannelResponse + (*DebugLevelRequest)(nil), // 177: lnrpc.DebugLevelRequest + (*DebugLevelResponse)(nil), // 178: lnrpc.DebugLevelResponse + (*PayReqString)(nil), // 179: lnrpc.PayReqString + (*PayReq)(nil), // 180: lnrpc.PayReq + (*Feature)(nil), // 181: lnrpc.Feature + (*FeeReportRequest)(nil), // 182: lnrpc.FeeReportRequest + (*ChannelFeeReport)(nil), // 183: lnrpc.ChannelFeeReport + (*FeeReportResponse)(nil), // 184: lnrpc.FeeReportResponse + (*InboundFee)(nil), // 185: lnrpc.InboundFee + (*PolicyUpdateRequest)(nil), // 186: lnrpc.PolicyUpdateRequest + (*FailedUpdate)(nil), // 187: lnrpc.FailedUpdate + (*PolicyUpdateResponse)(nil), // 188: lnrpc.PolicyUpdateResponse + (*ForwardingHistoryRequest)(nil), // 189: lnrpc.ForwardingHistoryRequest + (*ForwardingEvent)(nil), // 190: lnrpc.ForwardingEvent + (*ForwardingHistoryResponse)(nil), // 191: lnrpc.ForwardingHistoryResponse + (*ExportChannelBackupRequest)(nil), // 192: lnrpc.ExportChannelBackupRequest + (*ChannelBackup)(nil), // 193: lnrpc.ChannelBackup + (*MultiChanBackup)(nil), // 194: lnrpc.MultiChanBackup + (*ChanBackupExportRequest)(nil), // 195: lnrpc.ChanBackupExportRequest + (*ChanBackupSnapshot)(nil), // 196: lnrpc.ChanBackupSnapshot + (*ChannelBackups)(nil), // 197: lnrpc.ChannelBackups + (*RestoreChanBackupRequest)(nil), // 198: lnrpc.RestoreChanBackupRequest + (*RestoreBackupResponse)(nil), // 199: lnrpc.RestoreBackupResponse + (*ChannelBackupSubscription)(nil), // 200: lnrpc.ChannelBackupSubscription + (*VerifyChanBackupResponse)(nil), // 201: lnrpc.VerifyChanBackupResponse + (*MacaroonPermission)(nil), // 202: lnrpc.MacaroonPermission + (*BakeMacaroonRequest)(nil), // 203: lnrpc.BakeMacaroonRequest + (*BakeMacaroonResponse)(nil), // 204: lnrpc.BakeMacaroonResponse + (*ListMacaroonIDsRequest)(nil), // 205: lnrpc.ListMacaroonIDsRequest + (*ListMacaroonIDsResponse)(nil), // 206: lnrpc.ListMacaroonIDsResponse + (*DeleteMacaroonIDRequest)(nil), // 207: lnrpc.DeleteMacaroonIDRequest + (*DeleteMacaroonIDResponse)(nil), // 208: lnrpc.DeleteMacaroonIDResponse + (*MacaroonPermissionList)(nil), // 209: lnrpc.MacaroonPermissionList + (*ListPermissionsRequest)(nil), // 210: lnrpc.ListPermissionsRequest + (*ListPermissionsResponse)(nil), // 211: lnrpc.ListPermissionsResponse + (*Failure)(nil), // 212: lnrpc.Failure + (*ChannelUpdate)(nil), // 213: lnrpc.ChannelUpdate + (*MacaroonId)(nil), // 214: lnrpc.MacaroonId + (*Op)(nil), // 215: lnrpc.Op + (*CheckMacPermRequest)(nil), // 216: lnrpc.CheckMacPermRequest + (*CheckMacPermResponse)(nil), // 217: lnrpc.CheckMacPermResponse + (*RPCMiddlewareRequest)(nil), // 218: lnrpc.RPCMiddlewareRequest + (*MetadataValues)(nil), // 219: lnrpc.MetadataValues + (*StreamAuth)(nil), // 220: lnrpc.StreamAuth + (*RPCMessage)(nil), // 221: lnrpc.RPCMessage + (*RPCMiddlewareResponse)(nil), // 222: lnrpc.RPCMiddlewareResponse + (*MiddlewareRegistration)(nil), // 223: lnrpc.MiddlewareRegistration + (*InterceptFeedback)(nil), // 224: lnrpc.InterceptFeedback + nil, // 225: lnrpc.SendRequest.DestCustomRecordsEntry + nil, // 226: lnrpc.EstimateFeeRequest.AddrToAmountEntry + nil, // 227: lnrpc.SendManyRequest.AddrToAmountEntry + nil, // 228: lnrpc.Peer.FeaturesEntry + nil, // 229: lnrpc.GetInfoResponse.FeaturesEntry + nil, // 230: lnrpc.GetDebugInfoResponse.ConfigEntry + (*PendingChannelsResponse_PendingChannel)(nil), // 231: lnrpc.PendingChannelsResponse.PendingChannel + (*PendingChannelsResponse_PendingOpenChannel)(nil), // 232: lnrpc.PendingChannelsResponse.PendingOpenChannel + (*PendingChannelsResponse_WaitingCloseChannel)(nil), // 233: lnrpc.PendingChannelsResponse.WaitingCloseChannel + (*PendingChannelsResponse_Commitments)(nil), // 234: lnrpc.PendingChannelsResponse.Commitments + (*PendingChannelsResponse_ClosedChannel)(nil), // 235: lnrpc.PendingChannelsResponse.ClosedChannel + (*PendingChannelsResponse_ForceClosedChannel)(nil), // 236: lnrpc.PendingChannelsResponse.ForceClosedChannel + nil, // 237: lnrpc.WalletBalanceResponse.AccountBalanceEntry + nil, // 238: lnrpc.QueryRoutesRequest.DestCustomRecordsEntry + nil, // 239: lnrpc.Hop.CustomRecordsEntry + nil, // 240: lnrpc.LightningNode.FeaturesEntry + nil, // 241: lnrpc.LightningNode.CustomRecordsEntry + nil, // 242: lnrpc.RoutingPolicy.CustomRecordsEntry + nil, // 243: lnrpc.ChannelEdge.CustomRecordsEntry + nil, // 244: lnrpc.NodeMetricsResponse.BetweennessCentralityEntry + nil, // 245: lnrpc.NodeUpdate.FeaturesEntry + nil, // 246: lnrpc.Invoice.FeaturesEntry + nil, // 247: lnrpc.Invoice.AmpInvoiceStateEntry + nil, // 248: lnrpc.InvoiceHTLC.CustomRecordsEntry + nil, // 249: lnrpc.Payment.FirstHopCustomRecordsEntry + nil, // 250: lnrpc.PayReq.FeaturesEntry + nil, // 251: lnrpc.ListPermissionsResponse.MethodPermissionsEntry + nil, // 252: lnrpc.RPCMiddlewareRequest.MetadataPairsEntry } var file_lightning_proto_depIdxs = []int32{ - 156, // 0: lnrpc.OnionMessageUpdate.reply_path:type_name -> lnrpc.BlindedPath - 228, // 1: lnrpc.OnionMessageUpdate.custom_records:type_name -> lnrpc.OnionMessageUpdate.CustomRecordsEntry - 2, // 2: lnrpc.Utxo.address_type:type_name -> lnrpc.AddressType - 41, // 3: lnrpc.Utxo.outpoint:type_name -> lnrpc.OutPoint - 0, // 4: lnrpc.OutputDetail.output_type:type_name -> lnrpc.OutputScriptType - 33, // 5: lnrpc.Transaction.output_details:type_name -> lnrpc.OutputDetail - 42, // 6: lnrpc.Transaction.previous_outpoints:type_name -> lnrpc.PreviousOutPoint - 34, // 7: lnrpc.TransactionDetails.transactions:type_name -> lnrpc.Transaction - 3, // 8: lnrpc.ChannelAcceptRequest.commitment_type:type_name -> lnrpc.CommitmentType - 229, // 9: lnrpc.EstimateFeeRequest.AddrToAmount:type_name -> lnrpc.EstimateFeeRequest.AddrToAmountEntry - 1, // 10: lnrpc.EstimateFeeRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy - 41, // 11: lnrpc.EstimateFeeRequest.inputs:type_name -> lnrpc.OutPoint - 41, // 12: lnrpc.EstimateFeeResponse.inputs:type_name -> lnrpc.OutPoint - 230, // 13: lnrpc.SendManyRequest.AddrToAmount:type_name -> lnrpc.SendManyRequest.AddrToAmountEntry - 1, // 14: lnrpc.SendManyRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy - 1, // 15: lnrpc.SendCoinsRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy - 41, // 16: lnrpc.SendCoinsRequest.outpoints:type_name -> lnrpc.OutPoint - 32, // 17: lnrpc.ListUnspentResponse.utxos:type_name -> lnrpc.Utxo - 2, // 18: lnrpc.NewAddressRequest.type:type_name -> lnrpc.AddressType - 43, // 19: lnrpc.ConnectPeerRequest.addr:type_name -> lnrpc.LightningAddress - 62, // 20: lnrpc.Channel.pending_htlcs:type_name -> lnrpc.HTLC - 3, // 21: lnrpc.Channel.commitment_type:type_name -> lnrpc.CommitmentType - 63, // 22: lnrpc.Channel.local_constraints:type_name -> lnrpc.ChannelConstraints - 63, // 23: lnrpc.Channel.remote_constraints:type_name -> lnrpc.ChannelConstraints - 64, // 24: lnrpc.ListChannelsResponse.channels:type_name -> lnrpc.Channel - 67, // 25: lnrpc.ListAliasesResponse.alias_maps:type_name -> lnrpc.AliasMap - 13, // 26: lnrpc.ChannelCloseSummary.close_type:type_name -> lnrpc.ChannelCloseSummary.ClosureType - 4, // 27: lnrpc.ChannelCloseSummary.open_initiator:type_name -> lnrpc.Initiator - 4, // 28: lnrpc.ChannelCloseSummary.close_initiator:type_name -> lnrpc.Initiator - 71, // 29: lnrpc.ChannelCloseSummary.resolutions:type_name -> lnrpc.Resolution - 5, // 30: lnrpc.Resolution.resolution_type:type_name -> lnrpc.ResolutionType - 6, // 31: lnrpc.Resolution.outcome:type_name -> lnrpc.ResolutionOutcome - 41, // 32: lnrpc.Resolution.outpoint:type_name -> lnrpc.OutPoint - 70, // 33: lnrpc.ClosedChannelsResponse.channels:type_name -> lnrpc.ChannelCloseSummary - 14, // 34: lnrpc.Peer.sync_type:type_name -> lnrpc.Peer.SyncType - 231, // 35: lnrpc.Peer.features:type_name -> lnrpc.Peer.FeaturesEntry - 75, // 36: lnrpc.Peer.errors:type_name -> lnrpc.TimestampedError - 74, // 37: lnrpc.ListPeersResponse.peers:type_name -> lnrpc.Peer - 15, // 38: lnrpc.PeerEvent.type:type_name -> lnrpc.PeerEvent.EventType - 86, // 39: lnrpc.GetInfoResponse.chains:type_name -> lnrpc.Chain - 232, // 40: lnrpc.GetInfoResponse.features:type_name -> lnrpc.GetInfoResponse.FeaturesEntry - 7, // 41: lnrpc.GetInfoResponse.graph_cache_status:type_name -> lnrpc.GraphCacheStatus - 233, // 42: lnrpc.GetDebugInfoResponse.config:type_name -> lnrpc.GetDebugInfoResponse.ConfigEntry - 40, // 43: lnrpc.ChannelOpenUpdate.channel_point:type_name -> lnrpc.ChannelPoint - 88, // 44: lnrpc.ChannelCloseUpdate.local_close_output:type_name -> lnrpc.CloseOutput - 88, // 45: lnrpc.ChannelCloseUpdate.remote_close_output:type_name -> lnrpc.CloseOutput - 88, // 46: lnrpc.ChannelCloseUpdate.additional_outputs:type_name -> lnrpc.CloseOutput - 40, // 47: lnrpc.CloseChannelRequest.channel_point:type_name -> lnrpc.ChannelPoint - 92, // 48: lnrpc.CloseStatusUpdate.close_pending:type_name -> lnrpc.PendingUpdate - 89, // 49: lnrpc.CloseStatusUpdate.chan_close:type_name -> lnrpc.ChannelCloseUpdate - 93, // 50: lnrpc.CloseStatusUpdate.close_instant:type_name -> lnrpc.InstantUpdate - 96, // 51: lnrpc.BatchOpenChannelRequest.channels:type_name -> lnrpc.BatchOpenChannel + 2, // 0: lnrpc.Utxo.address_type:type_name -> lnrpc.AddressType + 39, // 1: lnrpc.Utxo.outpoint:type_name -> lnrpc.OutPoint + 0, // 2: lnrpc.OutputDetail.output_type:type_name -> lnrpc.OutputScriptType + 28, // 3: lnrpc.Transaction.output_details:type_name -> lnrpc.OutputDetail + 40, // 4: lnrpc.Transaction.previous_outpoints:type_name -> lnrpc.PreviousOutPoint + 29, // 5: lnrpc.TransactionDetails.transactions:type_name -> lnrpc.Transaction + 32, // 6: lnrpc.SendRequest.fee_limit:type_name -> lnrpc.FeeLimit + 225, // 7: lnrpc.SendRequest.dest_custom_records:type_name -> lnrpc.SendRequest.DestCustomRecordsEntry + 10, // 8: lnrpc.SendRequest.dest_features:type_name -> lnrpc.FeatureBit + 126, // 9: lnrpc.SendResponse.payment_route:type_name -> lnrpc.Route + 126, // 10: lnrpc.SendToRouteRequest.route:type_name -> lnrpc.Route + 3, // 11: lnrpc.ChannelAcceptRequest.commitment_type:type_name -> lnrpc.CommitmentType + 226, // 12: lnrpc.EstimateFeeRequest.AddrToAmount:type_name -> lnrpc.EstimateFeeRequest.AddrToAmountEntry + 1, // 13: lnrpc.EstimateFeeRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy + 227, // 14: lnrpc.SendManyRequest.AddrToAmount:type_name -> lnrpc.SendManyRequest.AddrToAmountEntry + 1, // 15: lnrpc.SendManyRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy + 1, // 16: lnrpc.SendCoinsRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy + 39, // 17: lnrpc.SendCoinsRequest.outpoints:type_name -> lnrpc.OutPoint + 27, // 18: lnrpc.ListUnspentResponse.utxos:type_name -> lnrpc.Utxo + 2, // 19: lnrpc.NewAddressRequest.type:type_name -> lnrpc.AddressType + 41, // 20: lnrpc.ConnectPeerRequest.addr:type_name -> lnrpc.LightningAddress + 60, // 21: lnrpc.Channel.pending_htlcs:type_name -> lnrpc.HTLC + 3, // 22: lnrpc.Channel.commitment_type:type_name -> lnrpc.CommitmentType + 61, // 23: lnrpc.Channel.local_constraints:type_name -> lnrpc.ChannelConstraints + 61, // 24: lnrpc.Channel.remote_constraints:type_name -> lnrpc.ChannelConstraints + 62, // 25: lnrpc.ListChannelsResponse.channels:type_name -> lnrpc.Channel + 65, // 26: lnrpc.ListAliasesResponse.alias_maps:type_name -> lnrpc.AliasMap + 12, // 27: lnrpc.ChannelCloseSummary.close_type:type_name -> lnrpc.ChannelCloseSummary.ClosureType + 4, // 28: lnrpc.ChannelCloseSummary.open_initiator:type_name -> lnrpc.Initiator + 4, // 29: lnrpc.ChannelCloseSummary.close_initiator:type_name -> lnrpc.Initiator + 69, // 30: lnrpc.ChannelCloseSummary.resolutions:type_name -> lnrpc.Resolution + 5, // 31: lnrpc.Resolution.resolution_type:type_name -> lnrpc.ResolutionType + 6, // 32: lnrpc.Resolution.outcome:type_name -> lnrpc.ResolutionOutcome + 39, // 33: lnrpc.Resolution.outpoint:type_name -> lnrpc.OutPoint + 68, // 34: lnrpc.ClosedChannelsResponse.channels:type_name -> lnrpc.ChannelCloseSummary + 13, // 35: lnrpc.Peer.sync_type:type_name -> lnrpc.Peer.SyncType + 228, // 36: lnrpc.Peer.features:type_name -> lnrpc.Peer.FeaturesEntry + 73, // 37: lnrpc.Peer.errors:type_name -> lnrpc.TimestampedError + 72, // 38: lnrpc.ListPeersResponse.peers:type_name -> lnrpc.Peer + 14, // 39: lnrpc.PeerEvent.type:type_name -> lnrpc.PeerEvent.EventType + 84, // 40: lnrpc.GetInfoResponse.chains:type_name -> lnrpc.Chain + 229, // 41: lnrpc.GetInfoResponse.features:type_name -> lnrpc.GetInfoResponse.FeaturesEntry + 230, // 42: lnrpc.GetDebugInfoResponse.config:type_name -> lnrpc.GetDebugInfoResponse.ConfigEntry + 38, // 43: lnrpc.ChannelOpenUpdate.channel_point:type_name -> lnrpc.ChannelPoint + 86, // 44: lnrpc.ChannelCloseUpdate.local_close_output:type_name -> lnrpc.CloseOutput + 86, // 45: lnrpc.ChannelCloseUpdate.remote_close_output:type_name -> lnrpc.CloseOutput + 86, // 46: lnrpc.ChannelCloseUpdate.additional_outputs:type_name -> lnrpc.CloseOutput + 38, // 47: lnrpc.CloseChannelRequest.channel_point:type_name -> lnrpc.ChannelPoint + 90, // 48: lnrpc.CloseStatusUpdate.close_pending:type_name -> lnrpc.PendingUpdate + 87, // 49: lnrpc.CloseStatusUpdate.chan_close:type_name -> lnrpc.ChannelCloseUpdate + 91, // 50: lnrpc.CloseStatusUpdate.close_instant:type_name -> lnrpc.InstantUpdate + 94, // 51: lnrpc.BatchOpenChannelRequest.channels:type_name -> lnrpc.BatchOpenChannel 1, // 52: lnrpc.BatchOpenChannelRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy 3, // 53: lnrpc.BatchOpenChannel.commitment_type:type_name -> lnrpc.CommitmentType - 92, // 54: lnrpc.BatchOpenChannelResponse.pending_channels:type_name -> lnrpc.PendingUpdate - 104, // 55: lnrpc.OpenChannelRequest.funding_shim:type_name -> lnrpc.FundingShim + 90, // 54: lnrpc.BatchOpenChannelResponse.pending_channels:type_name -> lnrpc.PendingUpdate + 102, // 55: lnrpc.OpenChannelRequest.funding_shim:type_name -> lnrpc.FundingShim 3, // 56: lnrpc.OpenChannelRequest.commitment_type:type_name -> lnrpc.CommitmentType - 41, // 57: lnrpc.OpenChannelRequest.outpoints:type_name -> lnrpc.OutPoint - 92, // 58: lnrpc.OpenStatusUpdate.chan_pending:type_name -> lnrpc.PendingUpdate - 87, // 59: lnrpc.OpenStatusUpdate.chan_open:type_name -> lnrpc.ChannelOpenUpdate - 94, // 60: lnrpc.OpenStatusUpdate.psbt_fund:type_name -> lnrpc.ReadyForPsbtFunding - 100, // 61: lnrpc.KeyDescriptor.key_loc:type_name -> lnrpc.KeyLocator - 40, // 62: lnrpc.ChanPointShim.chan_point:type_name -> lnrpc.ChannelPoint - 101, // 63: lnrpc.ChanPointShim.local_key:type_name -> lnrpc.KeyDescriptor - 102, // 64: lnrpc.FundingShim.chan_point_shim:type_name -> lnrpc.ChanPointShim - 103, // 65: lnrpc.FundingShim.psbt_shim:type_name -> lnrpc.PsbtShim - 104, // 66: lnrpc.FundingTransitionMsg.shim_register:type_name -> lnrpc.FundingShim - 105, // 67: lnrpc.FundingTransitionMsg.shim_cancel:type_name -> lnrpc.FundingShimCancel - 106, // 68: lnrpc.FundingTransitionMsg.psbt_verify:type_name -> lnrpc.FundingPsbtVerify - 107, // 69: lnrpc.FundingTransitionMsg.psbt_finalize:type_name -> lnrpc.FundingPsbtFinalize - 235, // 70: lnrpc.PendingChannelsResponse.pending_open_channels:type_name -> lnrpc.PendingChannelsResponse.PendingOpenChannel - 238, // 71: lnrpc.PendingChannelsResponse.pending_closing_channels:type_name -> lnrpc.PendingChannelsResponse.ClosedChannel - 239, // 72: lnrpc.PendingChannelsResponse.pending_force_closing_channels:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel - 236, // 73: lnrpc.PendingChannelsResponse.waiting_close_channels:type_name -> lnrpc.PendingChannelsResponse.WaitingCloseChannel - 64, // 74: lnrpc.ChannelCommitUpdate.channel:type_name -> lnrpc.Channel - 64, // 75: lnrpc.ChannelEventUpdate.open_channel:type_name -> lnrpc.Channel - 70, // 76: lnrpc.ChannelEventUpdate.closed_channel:type_name -> lnrpc.ChannelCloseSummary - 40, // 77: lnrpc.ChannelEventUpdate.active_channel:type_name -> lnrpc.ChannelPoint - 40, // 78: lnrpc.ChannelEventUpdate.inactive_channel:type_name -> lnrpc.ChannelPoint - 92, // 79: lnrpc.ChannelEventUpdate.pending_open_channel:type_name -> lnrpc.PendingUpdate - 40, // 80: lnrpc.ChannelEventUpdate.fully_resolved_channel:type_name -> lnrpc.ChannelPoint - 40, // 81: lnrpc.ChannelEventUpdate.channel_funding_timeout:type_name -> lnrpc.ChannelPoint - 114, // 82: lnrpc.ChannelEventUpdate.updated_channel:type_name -> lnrpc.ChannelCommitUpdate - 17, // 83: lnrpc.ChannelEventUpdate.type:type_name -> lnrpc.ChannelEventUpdate.UpdateType - 240, // 84: lnrpc.WalletBalanceResponse.account_balance:type_name -> lnrpc.WalletBalanceResponse.AccountBalanceEntry - 119, // 85: lnrpc.ChannelBalanceResponse.local_balance:type_name -> lnrpc.Amount - 119, // 86: lnrpc.ChannelBalanceResponse.remote_balance:type_name -> lnrpc.Amount - 119, // 87: lnrpc.ChannelBalanceResponse.unsettled_local_balance:type_name -> lnrpc.Amount - 119, // 88: lnrpc.ChannelBalanceResponse.unsettled_remote_balance:type_name -> lnrpc.Amount - 119, // 89: lnrpc.ChannelBalanceResponse.pending_open_local_balance:type_name -> lnrpc.Amount - 119, // 90: lnrpc.ChannelBalanceResponse.pending_open_remote_balance:type_name -> lnrpc.Amount - 37, // 91: lnrpc.QueryRoutesRequest.fee_limit:type_name -> lnrpc.FeeLimit - 124, // 92: lnrpc.QueryRoutesRequest.ignored_edges:type_name -> lnrpc.EdgeLocator - 123, // 93: lnrpc.QueryRoutesRequest.ignored_pairs:type_name -> lnrpc.NodePair - 241, // 94: lnrpc.QueryRoutesRequest.dest_custom_records:type_name -> lnrpc.QueryRoutesRequest.DestCustomRecordsEntry - 154, // 95: lnrpc.QueryRoutesRequest.route_hints:type_name -> lnrpc.RouteHint - 155, // 96: lnrpc.QueryRoutesRequest.blinded_payment_paths:type_name -> lnrpc.BlindedPaymentPath - 11, // 97: lnrpc.QueryRoutesRequest.dest_features:type_name -> lnrpc.FeatureBit - 129, // 98: lnrpc.QueryRoutesResponse.routes:type_name -> lnrpc.Route - 127, // 99: lnrpc.Hop.mpp_record:type_name -> lnrpc.MPPRecord - 128, // 100: lnrpc.Hop.amp_record:type_name -> lnrpc.AMPRecord - 242, // 101: lnrpc.Hop.custom_records:type_name -> lnrpc.Hop.CustomRecordsEntry - 126, // 102: lnrpc.Route.hops:type_name -> lnrpc.Hop - 132, // 103: lnrpc.NodeInfo.node:type_name -> lnrpc.LightningNode - 136, // 104: lnrpc.NodeInfo.channels:type_name -> lnrpc.ChannelEdge - 133, // 105: lnrpc.LightningNode.addresses:type_name -> lnrpc.NodeAddress - 243, // 106: lnrpc.LightningNode.features:type_name -> lnrpc.LightningNode.FeaturesEntry - 244, // 107: lnrpc.LightningNode.custom_records:type_name -> lnrpc.LightningNode.CustomRecordsEntry - 245, // 108: lnrpc.RoutingPolicy.custom_records:type_name -> lnrpc.RoutingPolicy.CustomRecordsEntry - 134, // 109: lnrpc.ChannelEdge.node1_policy:type_name -> lnrpc.RoutingPolicy - 134, // 110: lnrpc.ChannelEdge.node2_policy:type_name -> lnrpc.RoutingPolicy - 246, // 111: lnrpc.ChannelEdge.custom_records:type_name -> lnrpc.ChannelEdge.CustomRecordsEntry - 135, // 112: lnrpc.ChannelEdge.auth_proof:type_name -> lnrpc.ChannelAuthProof - 132, // 113: lnrpc.ChannelGraph.nodes:type_name -> lnrpc.LightningNode - 136, // 114: lnrpc.ChannelGraph.edges:type_name -> lnrpc.ChannelEdge - 8, // 115: lnrpc.NodeMetricsRequest.types:type_name -> lnrpc.NodeMetricType - 247, // 116: lnrpc.NodeMetricsResponse.betweenness_centrality:type_name -> lnrpc.NodeMetricsResponse.BetweennessCentralityEntry - 149, // 117: lnrpc.GraphTopologyUpdate.node_updates:type_name -> lnrpc.NodeUpdate - 150, // 118: lnrpc.GraphTopologyUpdate.channel_updates:type_name -> lnrpc.ChannelEdgeUpdate - 151, // 119: lnrpc.GraphTopologyUpdate.closed_chans:type_name -> lnrpc.ClosedChannelUpdate - 133, // 120: lnrpc.NodeUpdate.node_addresses:type_name -> lnrpc.NodeAddress - 248, // 121: lnrpc.NodeUpdate.features:type_name -> lnrpc.NodeUpdate.FeaturesEntry - 40, // 122: lnrpc.ChannelEdgeUpdate.chan_point:type_name -> lnrpc.ChannelPoint - 134, // 123: lnrpc.ChannelEdgeUpdate.routing_policy:type_name -> lnrpc.RoutingPolicy - 40, // 124: lnrpc.ClosedChannelUpdate.chan_point:type_name -> lnrpc.ChannelPoint - 152, // 125: lnrpc.RouteHint.hop_hints:type_name -> lnrpc.HopHint - 156, // 126: lnrpc.BlindedPaymentPath.blinded_path:type_name -> lnrpc.BlindedPath - 11, // 127: lnrpc.BlindedPaymentPath.features:type_name -> lnrpc.FeatureBit - 157, // 128: lnrpc.BlindedPath.blinded_hops:type_name -> lnrpc.BlindedHop - 9, // 129: lnrpc.AMPInvoiceState.state:type_name -> lnrpc.InvoiceHTLCState - 154, // 130: lnrpc.Invoice.route_hints:type_name -> lnrpc.RouteHint - 18, // 131: lnrpc.Invoice.state:type_name -> lnrpc.Invoice.InvoiceState - 161, // 132: lnrpc.Invoice.htlcs:type_name -> lnrpc.InvoiceHTLC - 249, // 133: lnrpc.Invoice.features:type_name -> lnrpc.Invoice.FeaturesEntry - 250, // 134: lnrpc.Invoice.amp_invoice_state:type_name -> lnrpc.Invoice.AmpInvoiceStateEntry - 160, // 135: lnrpc.Invoice.blinded_path_config:type_name -> lnrpc.BlindedPathConfig - 9, // 136: lnrpc.InvoiceHTLC.state:type_name -> lnrpc.InvoiceHTLCState - 251, // 137: lnrpc.InvoiceHTLC.custom_records:type_name -> lnrpc.InvoiceHTLC.CustomRecordsEntry - 162, // 138: lnrpc.InvoiceHTLC.amp:type_name -> lnrpc.AMP - 159, // 139: lnrpc.ListInvoiceResponse.invoices:type_name -> lnrpc.Invoice - 19, // 140: lnrpc.Payment.status:type_name -> lnrpc.Payment.PaymentStatus - 171, // 141: lnrpc.Payment.htlcs:type_name -> lnrpc.HTLCAttempt - 10, // 142: lnrpc.Payment.failure_reason:type_name -> lnrpc.PaymentFailureReason - 252, // 143: lnrpc.Payment.first_hop_custom_records:type_name -> lnrpc.Payment.FirstHopCustomRecordsEntry - 20, // 144: lnrpc.HTLCAttempt.status:type_name -> lnrpc.HTLCAttempt.HTLCStatus - 129, // 145: lnrpc.HTLCAttempt.route:type_name -> lnrpc.Route - 215, // 146: lnrpc.HTLCAttempt.failure:type_name -> lnrpc.Failure - 170, // 147: lnrpc.ListPaymentsResponse.payments:type_name -> lnrpc.Payment - 40, // 148: lnrpc.AbandonChannelRequest.channel_point:type_name -> lnrpc.ChannelPoint - 154, // 149: lnrpc.PayReq.route_hints:type_name -> lnrpc.RouteHint - 253, // 150: lnrpc.PayReq.features:type_name -> lnrpc.PayReq.FeaturesEntry - 155, // 151: lnrpc.PayReq.blinded_paths:type_name -> lnrpc.BlindedPaymentPath - 186, // 152: lnrpc.FeeReportResponse.channel_fees:type_name -> lnrpc.ChannelFeeReport - 40, // 153: lnrpc.PolicyUpdateRequest.chan_point:type_name -> lnrpc.ChannelPoint - 188, // 154: lnrpc.PolicyUpdateRequest.inbound_fee:type_name -> lnrpc.InboundFee - 41, // 155: lnrpc.FailedUpdate.outpoint:type_name -> lnrpc.OutPoint - 12, // 156: lnrpc.FailedUpdate.reason:type_name -> lnrpc.UpdateFailure - 190, // 157: lnrpc.PolicyUpdateResponse.failed_updates:type_name -> lnrpc.FailedUpdate - 193, // 158: lnrpc.ForwardingHistoryResponse.forwarding_events:type_name -> lnrpc.ForwardingEvent - 40, // 159: lnrpc.ExportChannelBackupRequest.chan_point:type_name -> lnrpc.ChannelPoint - 40, // 160: lnrpc.ChannelBackup.chan_point:type_name -> lnrpc.ChannelPoint - 40, // 161: lnrpc.MultiChanBackup.chan_points:type_name -> lnrpc.ChannelPoint - 200, // 162: lnrpc.ChanBackupSnapshot.single_chan_backups:type_name -> lnrpc.ChannelBackups - 197, // 163: lnrpc.ChanBackupSnapshot.multi_chan_backup:type_name -> lnrpc.MultiChanBackup - 196, // 164: lnrpc.ChannelBackups.chan_backups:type_name -> lnrpc.ChannelBackup - 200, // 165: lnrpc.RestoreChanBackupRequest.chan_backups:type_name -> lnrpc.ChannelBackups - 205, // 166: lnrpc.BakeMacaroonRequest.permissions:type_name -> lnrpc.MacaroonPermission - 205, // 167: lnrpc.MacaroonPermissionList.permissions:type_name -> lnrpc.MacaroonPermission - 254, // 168: lnrpc.ListPermissionsResponse.method_permissions:type_name -> lnrpc.ListPermissionsResponse.MethodPermissionsEntry - 21, // 169: lnrpc.Failure.code:type_name -> lnrpc.Failure.FailureCode - 216, // 170: lnrpc.Failure.channel_update:type_name -> lnrpc.ChannelUpdate - 218, // 171: lnrpc.MacaroonId.ops:type_name -> lnrpc.Op - 205, // 172: lnrpc.CheckMacPermRequest.permissions:type_name -> lnrpc.MacaroonPermission - 223, // 173: lnrpc.RPCMiddlewareRequest.stream_auth:type_name -> lnrpc.StreamAuth - 224, // 174: lnrpc.RPCMiddlewareRequest.request:type_name -> lnrpc.RPCMessage - 224, // 175: lnrpc.RPCMiddlewareRequest.response:type_name -> lnrpc.RPCMessage - 255, // 176: lnrpc.RPCMiddlewareRequest.metadata_pairs:type_name -> lnrpc.RPCMiddlewareRequest.MetadataPairsEntry - 226, // 177: lnrpc.RPCMiddlewareResponse.register:type_name -> lnrpc.MiddlewareRegistration - 227, // 178: lnrpc.RPCMiddlewareResponse.feedback:type_name -> lnrpc.InterceptFeedback - 184, // 179: lnrpc.Peer.FeaturesEntry.value:type_name -> lnrpc.Feature - 184, // 180: lnrpc.GetInfoResponse.FeaturesEntry.value:type_name -> lnrpc.Feature - 4, // 181: lnrpc.PendingChannelsResponse.PendingChannel.initiator:type_name -> lnrpc.Initiator - 3, // 182: lnrpc.PendingChannelsResponse.PendingChannel.commitment_type:type_name -> lnrpc.CommitmentType - 234, // 183: lnrpc.PendingChannelsResponse.PendingOpenChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel - 234, // 184: lnrpc.PendingChannelsResponse.WaitingCloseChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel - 237, // 185: lnrpc.PendingChannelsResponse.WaitingCloseChannel.commitments:type_name -> lnrpc.PendingChannelsResponse.Commitments - 234, // 186: lnrpc.PendingChannelsResponse.ClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel - 234, // 187: lnrpc.PendingChannelsResponse.ForceClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel - 110, // 188: lnrpc.PendingChannelsResponse.ForceClosedChannel.pending_htlcs:type_name -> lnrpc.PendingHTLC - 16, // 189: lnrpc.PendingChannelsResponse.ForceClosedChannel.anchor:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel.AnchorState - 116, // 190: lnrpc.WalletBalanceResponse.AccountBalanceEntry.value:type_name -> lnrpc.WalletAccountBalance - 184, // 191: lnrpc.LightningNode.FeaturesEntry.value:type_name -> lnrpc.Feature - 141, // 192: lnrpc.NodeMetricsResponse.BetweennessCentralityEntry.value:type_name -> lnrpc.FloatMetric - 184, // 193: lnrpc.NodeUpdate.FeaturesEntry.value:type_name -> lnrpc.Feature - 184, // 194: lnrpc.Invoice.FeaturesEntry.value:type_name -> lnrpc.Feature - 158, // 195: lnrpc.Invoice.AmpInvoiceStateEntry.value:type_name -> lnrpc.AMPInvoiceState - 184, // 196: lnrpc.PayReq.FeaturesEntry.value:type_name -> lnrpc.Feature - 212, // 197: lnrpc.ListPermissionsResponse.MethodPermissionsEntry.value:type_name -> lnrpc.MacaroonPermissionList - 222, // 198: lnrpc.RPCMiddlewareRequest.MetadataPairsEntry.value:type_name -> lnrpc.MetadataValues - 117, // 199: lnrpc.Lightning.WalletBalance:input_type -> lnrpc.WalletBalanceRequest - 120, // 200: lnrpc.Lightning.ChannelBalance:input_type -> lnrpc.ChannelBalanceRequest - 35, // 201: lnrpc.Lightning.GetTransactions:input_type -> lnrpc.GetTransactionsRequest - 44, // 202: lnrpc.Lightning.EstimateFee:input_type -> lnrpc.EstimateFeeRequest - 48, // 203: lnrpc.Lightning.SendCoins:input_type -> lnrpc.SendCoinsRequest - 50, // 204: lnrpc.Lightning.ListUnspent:input_type -> lnrpc.ListUnspentRequest - 35, // 205: lnrpc.Lightning.SubscribeTransactions:input_type -> lnrpc.GetTransactionsRequest - 46, // 206: lnrpc.Lightning.SendMany:input_type -> lnrpc.SendManyRequest - 52, // 207: lnrpc.Lightning.NewAddress:input_type -> lnrpc.NewAddressRequest - 54, // 208: lnrpc.Lightning.SignMessage:input_type -> lnrpc.SignMessageRequest - 56, // 209: lnrpc.Lightning.VerifyMessage:input_type -> lnrpc.VerifyMessageRequest - 58, // 210: lnrpc.Lightning.ConnectPeer:input_type -> lnrpc.ConnectPeerRequest - 60, // 211: lnrpc.Lightning.DisconnectPeer:input_type -> lnrpc.DisconnectPeerRequest - 76, // 212: lnrpc.Lightning.ListPeers:input_type -> lnrpc.ListPeersRequest - 78, // 213: lnrpc.Lightning.SubscribePeerEvents:input_type -> lnrpc.PeerEventSubscription - 80, // 214: lnrpc.Lightning.GetInfo:input_type -> lnrpc.GetInfoRequest - 82, // 215: lnrpc.Lightning.GetDebugInfo:input_type -> lnrpc.GetDebugInfoRequest - 84, // 216: lnrpc.Lightning.GetRecoveryInfo:input_type -> lnrpc.GetRecoveryInfoRequest - 111, // 217: lnrpc.Lightning.PendingChannels:input_type -> lnrpc.PendingChannelsRequest - 65, // 218: lnrpc.Lightning.ListChannels:input_type -> lnrpc.ListChannelsRequest - 113, // 219: lnrpc.Lightning.SubscribeChannelEvents:input_type -> lnrpc.ChannelEventSubscription - 72, // 220: lnrpc.Lightning.ClosedChannels:input_type -> lnrpc.ClosedChannelsRequest - 98, // 221: lnrpc.Lightning.OpenChannelSync:input_type -> lnrpc.OpenChannelRequest - 98, // 222: lnrpc.Lightning.OpenChannel:input_type -> lnrpc.OpenChannelRequest - 95, // 223: lnrpc.Lightning.BatchOpenChannel:input_type -> lnrpc.BatchOpenChannelRequest - 108, // 224: lnrpc.Lightning.FundingStateStep:input_type -> lnrpc.FundingTransitionMsg - 39, // 225: lnrpc.Lightning.ChannelAcceptor:input_type -> lnrpc.ChannelAcceptResponse - 90, // 226: lnrpc.Lightning.CloseChannel:input_type -> lnrpc.CloseChannelRequest - 178, // 227: lnrpc.Lightning.AbandonChannel:input_type -> lnrpc.AbandonChannelRequest - 159, // 228: lnrpc.Lightning.AddInvoice:input_type -> lnrpc.Invoice - 165, // 229: lnrpc.Lightning.ListInvoices:input_type -> lnrpc.ListInvoiceRequest - 164, // 230: lnrpc.Lightning.LookupInvoice:input_type -> lnrpc.PaymentHash - 167, // 231: lnrpc.Lightning.SubscribeInvoices:input_type -> lnrpc.InvoiceSubscription - 168, // 232: lnrpc.Lightning.DeleteCanceledInvoice:input_type -> lnrpc.DelCanceledInvoiceReq - 182, // 233: lnrpc.Lightning.DecodePayReq:input_type -> lnrpc.PayReqString - 172, // 234: lnrpc.Lightning.ListPayments:input_type -> lnrpc.ListPaymentsRequest - 174, // 235: lnrpc.Lightning.DeletePayment:input_type -> lnrpc.DeletePaymentRequest - 175, // 236: lnrpc.Lightning.DeleteAllPayments:input_type -> lnrpc.DeleteAllPaymentsRequest - 137, // 237: lnrpc.Lightning.DescribeGraph:input_type -> lnrpc.ChannelGraphRequest - 139, // 238: lnrpc.Lightning.GetNodeMetrics:input_type -> lnrpc.NodeMetricsRequest - 142, // 239: lnrpc.Lightning.GetChanInfo:input_type -> lnrpc.ChanInfoRequest - 130, // 240: lnrpc.Lightning.GetNodeInfo:input_type -> lnrpc.NodeInfoRequest - 122, // 241: lnrpc.Lightning.QueryRoutes:input_type -> lnrpc.QueryRoutesRequest - 143, // 242: lnrpc.Lightning.GetNetworkInfo:input_type -> lnrpc.NetworkInfoRequest - 145, // 243: lnrpc.Lightning.StopDaemon:input_type -> lnrpc.StopRequest - 147, // 244: lnrpc.Lightning.SubscribeChannelGraph:input_type -> lnrpc.GraphTopologySubscription - 180, // 245: lnrpc.Lightning.DebugLevel:input_type -> lnrpc.DebugLevelRequest - 185, // 246: lnrpc.Lightning.FeeReport:input_type -> lnrpc.FeeReportRequest - 189, // 247: lnrpc.Lightning.UpdateChannelPolicy:input_type -> lnrpc.PolicyUpdateRequest - 192, // 248: lnrpc.Lightning.ForwardingHistory:input_type -> lnrpc.ForwardingHistoryRequest - 195, // 249: lnrpc.Lightning.ExportChannelBackup:input_type -> lnrpc.ExportChannelBackupRequest - 198, // 250: lnrpc.Lightning.ExportAllChannelBackups:input_type -> lnrpc.ChanBackupExportRequest - 199, // 251: lnrpc.Lightning.VerifyChanBackup:input_type -> lnrpc.ChanBackupSnapshot - 201, // 252: lnrpc.Lightning.RestoreChannelBackups:input_type -> lnrpc.RestoreChanBackupRequest - 203, // 253: lnrpc.Lightning.SubscribeChannelBackups:input_type -> lnrpc.ChannelBackupSubscription - 206, // 254: lnrpc.Lightning.BakeMacaroon:input_type -> lnrpc.BakeMacaroonRequest - 208, // 255: lnrpc.Lightning.ListMacaroonIDs:input_type -> lnrpc.ListMacaroonIDsRequest - 210, // 256: lnrpc.Lightning.DeleteMacaroonID:input_type -> lnrpc.DeleteMacaroonIDRequest - 213, // 257: lnrpc.Lightning.ListPermissions:input_type -> lnrpc.ListPermissionsRequest - 219, // 258: lnrpc.Lightning.CheckMacaroonPermissions:input_type -> lnrpc.CheckMacPermRequest - 225, // 259: lnrpc.Lightning.RegisterRPCMiddleware:input_type -> lnrpc.RPCMiddlewareResponse - 26, // 260: lnrpc.Lightning.SendCustomMessage:input_type -> lnrpc.SendCustomMessageRequest - 24, // 261: lnrpc.Lightning.SubscribeCustomMessages:input_type -> lnrpc.SubscribeCustomMessagesRequest - 30, // 262: lnrpc.Lightning.SendOnionMessage:input_type -> lnrpc.SendOnionMessageRequest - 28, // 263: lnrpc.Lightning.SubscribeOnionMessages:input_type -> lnrpc.SubscribeOnionMessagesRequest - 68, // 264: lnrpc.Lightning.ListAliases:input_type -> lnrpc.ListAliasesRequest - 22, // 265: lnrpc.Lightning.LookupHtlcResolution:input_type -> lnrpc.LookupHtlcResolutionRequest - 118, // 266: lnrpc.Lightning.WalletBalance:output_type -> lnrpc.WalletBalanceResponse - 121, // 267: lnrpc.Lightning.ChannelBalance:output_type -> lnrpc.ChannelBalanceResponse - 36, // 268: lnrpc.Lightning.GetTransactions:output_type -> lnrpc.TransactionDetails - 45, // 269: lnrpc.Lightning.EstimateFee:output_type -> lnrpc.EstimateFeeResponse - 49, // 270: lnrpc.Lightning.SendCoins:output_type -> lnrpc.SendCoinsResponse - 51, // 271: lnrpc.Lightning.ListUnspent:output_type -> lnrpc.ListUnspentResponse - 34, // 272: lnrpc.Lightning.SubscribeTransactions:output_type -> lnrpc.Transaction - 47, // 273: lnrpc.Lightning.SendMany:output_type -> lnrpc.SendManyResponse - 53, // 274: lnrpc.Lightning.NewAddress:output_type -> lnrpc.NewAddressResponse - 55, // 275: lnrpc.Lightning.SignMessage:output_type -> lnrpc.SignMessageResponse - 57, // 276: lnrpc.Lightning.VerifyMessage:output_type -> lnrpc.VerifyMessageResponse - 59, // 277: lnrpc.Lightning.ConnectPeer:output_type -> lnrpc.ConnectPeerResponse - 61, // 278: lnrpc.Lightning.DisconnectPeer:output_type -> lnrpc.DisconnectPeerResponse - 77, // 279: lnrpc.Lightning.ListPeers:output_type -> lnrpc.ListPeersResponse - 79, // 280: lnrpc.Lightning.SubscribePeerEvents:output_type -> lnrpc.PeerEvent - 81, // 281: lnrpc.Lightning.GetInfo:output_type -> lnrpc.GetInfoResponse - 83, // 282: lnrpc.Lightning.GetDebugInfo:output_type -> lnrpc.GetDebugInfoResponse - 85, // 283: lnrpc.Lightning.GetRecoveryInfo:output_type -> lnrpc.GetRecoveryInfoResponse - 112, // 284: lnrpc.Lightning.PendingChannels:output_type -> lnrpc.PendingChannelsResponse - 66, // 285: lnrpc.Lightning.ListChannels:output_type -> lnrpc.ListChannelsResponse - 115, // 286: lnrpc.Lightning.SubscribeChannelEvents:output_type -> lnrpc.ChannelEventUpdate - 73, // 287: lnrpc.Lightning.ClosedChannels:output_type -> lnrpc.ClosedChannelsResponse - 40, // 288: lnrpc.Lightning.OpenChannelSync:output_type -> lnrpc.ChannelPoint - 99, // 289: lnrpc.Lightning.OpenChannel:output_type -> lnrpc.OpenStatusUpdate - 97, // 290: lnrpc.Lightning.BatchOpenChannel:output_type -> lnrpc.BatchOpenChannelResponse - 109, // 291: lnrpc.Lightning.FundingStateStep:output_type -> lnrpc.FundingStateStepResp - 38, // 292: lnrpc.Lightning.ChannelAcceptor:output_type -> lnrpc.ChannelAcceptRequest - 91, // 293: lnrpc.Lightning.CloseChannel:output_type -> lnrpc.CloseStatusUpdate - 179, // 294: lnrpc.Lightning.AbandonChannel:output_type -> lnrpc.AbandonChannelResponse - 163, // 295: lnrpc.Lightning.AddInvoice:output_type -> lnrpc.AddInvoiceResponse - 166, // 296: lnrpc.Lightning.ListInvoices:output_type -> lnrpc.ListInvoiceResponse - 159, // 297: lnrpc.Lightning.LookupInvoice:output_type -> lnrpc.Invoice - 159, // 298: lnrpc.Lightning.SubscribeInvoices:output_type -> lnrpc.Invoice - 169, // 299: lnrpc.Lightning.DeleteCanceledInvoice:output_type -> lnrpc.DelCanceledInvoiceResp - 183, // 300: lnrpc.Lightning.DecodePayReq:output_type -> lnrpc.PayReq - 173, // 301: lnrpc.Lightning.ListPayments:output_type -> lnrpc.ListPaymentsResponse - 176, // 302: lnrpc.Lightning.DeletePayment:output_type -> lnrpc.DeletePaymentResponse - 177, // 303: lnrpc.Lightning.DeleteAllPayments:output_type -> lnrpc.DeleteAllPaymentsResponse - 138, // 304: lnrpc.Lightning.DescribeGraph:output_type -> lnrpc.ChannelGraph - 140, // 305: lnrpc.Lightning.GetNodeMetrics:output_type -> lnrpc.NodeMetricsResponse - 136, // 306: lnrpc.Lightning.GetChanInfo:output_type -> lnrpc.ChannelEdge - 131, // 307: lnrpc.Lightning.GetNodeInfo:output_type -> lnrpc.NodeInfo - 125, // 308: lnrpc.Lightning.QueryRoutes:output_type -> lnrpc.QueryRoutesResponse - 144, // 309: lnrpc.Lightning.GetNetworkInfo:output_type -> lnrpc.NetworkInfo - 146, // 310: lnrpc.Lightning.StopDaemon:output_type -> lnrpc.StopResponse - 148, // 311: lnrpc.Lightning.SubscribeChannelGraph:output_type -> lnrpc.GraphTopologyUpdate - 181, // 312: lnrpc.Lightning.DebugLevel:output_type -> lnrpc.DebugLevelResponse - 187, // 313: lnrpc.Lightning.FeeReport:output_type -> lnrpc.FeeReportResponse - 191, // 314: lnrpc.Lightning.UpdateChannelPolicy:output_type -> lnrpc.PolicyUpdateResponse - 194, // 315: lnrpc.Lightning.ForwardingHistory:output_type -> lnrpc.ForwardingHistoryResponse - 196, // 316: lnrpc.Lightning.ExportChannelBackup:output_type -> lnrpc.ChannelBackup - 199, // 317: lnrpc.Lightning.ExportAllChannelBackups:output_type -> lnrpc.ChanBackupSnapshot - 204, // 318: lnrpc.Lightning.VerifyChanBackup:output_type -> lnrpc.VerifyChanBackupResponse - 202, // 319: lnrpc.Lightning.RestoreChannelBackups:output_type -> lnrpc.RestoreBackupResponse - 199, // 320: lnrpc.Lightning.SubscribeChannelBackups:output_type -> lnrpc.ChanBackupSnapshot - 207, // 321: lnrpc.Lightning.BakeMacaroon:output_type -> lnrpc.BakeMacaroonResponse - 209, // 322: lnrpc.Lightning.ListMacaroonIDs:output_type -> lnrpc.ListMacaroonIDsResponse - 211, // 323: lnrpc.Lightning.DeleteMacaroonID:output_type -> lnrpc.DeleteMacaroonIDResponse - 214, // 324: lnrpc.Lightning.ListPermissions:output_type -> lnrpc.ListPermissionsResponse - 220, // 325: lnrpc.Lightning.CheckMacaroonPermissions:output_type -> lnrpc.CheckMacPermResponse - 221, // 326: lnrpc.Lightning.RegisterRPCMiddleware:output_type -> lnrpc.RPCMiddlewareRequest - 27, // 327: lnrpc.Lightning.SendCustomMessage:output_type -> lnrpc.SendCustomMessageResponse - 25, // 328: lnrpc.Lightning.SubscribeCustomMessages:output_type -> lnrpc.CustomMessage - 31, // 329: lnrpc.Lightning.SendOnionMessage:output_type -> lnrpc.SendOnionMessageResponse - 29, // 330: lnrpc.Lightning.SubscribeOnionMessages:output_type -> lnrpc.OnionMessageUpdate - 69, // 331: lnrpc.Lightning.ListAliases:output_type -> lnrpc.ListAliasesResponse - 23, // 332: lnrpc.Lightning.LookupHtlcResolution:output_type -> lnrpc.LookupHtlcResolutionResponse - 266, // [266:333] is the sub-list for method output_type - 199, // [199:266] is the sub-list for method input_type - 199, // [199:199] is the sub-list for extension type_name - 199, // [199:199] is the sub-list for extension extendee - 0, // [0:199] is the sub-list for field type_name + 39, // 57: lnrpc.OpenChannelRequest.outpoints:type_name -> lnrpc.OutPoint + 90, // 58: lnrpc.OpenStatusUpdate.chan_pending:type_name -> lnrpc.PendingUpdate + 85, // 59: lnrpc.OpenStatusUpdate.chan_open:type_name -> lnrpc.ChannelOpenUpdate + 92, // 60: lnrpc.OpenStatusUpdate.psbt_fund:type_name -> lnrpc.ReadyForPsbtFunding + 98, // 61: lnrpc.KeyDescriptor.key_loc:type_name -> lnrpc.KeyLocator + 38, // 62: lnrpc.ChanPointShim.chan_point:type_name -> lnrpc.ChannelPoint + 99, // 63: lnrpc.ChanPointShim.local_key:type_name -> lnrpc.KeyDescriptor + 100, // 64: lnrpc.FundingShim.chan_point_shim:type_name -> lnrpc.ChanPointShim + 101, // 65: lnrpc.FundingShim.psbt_shim:type_name -> lnrpc.PsbtShim + 102, // 66: lnrpc.FundingTransitionMsg.shim_register:type_name -> lnrpc.FundingShim + 103, // 67: lnrpc.FundingTransitionMsg.shim_cancel:type_name -> lnrpc.FundingShimCancel + 104, // 68: lnrpc.FundingTransitionMsg.psbt_verify:type_name -> lnrpc.FundingPsbtVerify + 105, // 69: lnrpc.FundingTransitionMsg.psbt_finalize:type_name -> lnrpc.FundingPsbtFinalize + 232, // 70: lnrpc.PendingChannelsResponse.pending_open_channels:type_name -> lnrpc.PendingChannelsResponse.PendingOpenChannel + 235, // 71: lnrpc.PendingChannelsResponse.pending_closing_channels:type_name -> lnrpc.PendingChannelsResponse.ClosedChannel + 236, // 72: lnrpc.PendingChannelsResponse.pending_force_closing_channels:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel + 233, // 73: lnrpc.PendingChannelsResponse.waiting_close_channels:type_name -> lnrpc.PendingChannelsResponse.WaitingCloseChannel + 62, // 74: lnrpc.ChannelEventUpdate.open_channel:type_name -> lnrpc.Channel + 68, // 75: lnrpc.ChannelEventUpdate.closed_channel:type_name -> lnrpc.ChannelCloseSummary + 38, // 76: lnrpc.ChannelEventUpdate.active_channel:type_name -> lnrpc.ChannelPoint + 38, // 77: lnrpc.ChannelEventUpdate.inactive_channel:type_name -> lnrpc.ChannelPoint + 90, // 78: lnrpc.ChannelEventUpdate.pending_open_channel:type_name -> lnrpc.PendingUpdate + 38, // 79: lnrpc.ChannelEventUpdate.fully_resolved_channel:type_name -> lnrpc.ChannelPoint + 38, // 80: lnrpc.ChannelEventUpdate.channel_funding_timeout:type_name -> lnrpc.ChannelPoint + 16, // 81: lnrpc.ChannelEventUpdate.type:type_name -> lnrpc.ChannelEventUpdate.UpdateType + 237, // 82: lnrpc.WalletBalanceResponse.account_balance:type_name -> lnrpc.WalletBalanceResponse.AccountBalanceEntry + 116, // 83: lnrpc.ChannelBalanceResponse.local_balance:type_name -> lnrpc.Amount + 116, // 84: lnrpc.ChannelBalanceResponse.remote_balance:type_name -> lnrpc.Amount + 116, // 85: lnrpc.ChannelBalanceResponse.unsettled_local_balance:type_name -> lnrpc.Amount + 116, // 86: lnrpc.ChannelBalanceResponse.unsettled_remote_balance:type_name -> lnrpc.Amount + 116, // 87: lnrpc.ChannelBalanceResponse.pending_open_local_balance:type_name -> lnrpc.Amount + 116, // 88: lnrpc.ChannelBalanceResponse.pending_open_remote_balance:type_name -> lnrpc.Amount + 32, // 89: lnrpc.QueryRoutesRequest.fee_limit:type_name -> lnrpc.FeeLimit + 121, // 90: lnrpc.QueryRoutesRequest.ignored_edges:type_name -> lnrpc.EdgeLocator + 120, // 91: lnrpc.QueryRoutesRequest.ignored_pairs:type_name -> lnrpc.NodePair + 238, // 92: lnrpc.QueryRoutesRequest.dest_custom_records:type_name -> lnrpc.QueryRoutesRequest.DestCustomRecordsEntry + 151, // 93: lnrpc.QueryRoutesRequest.route_hints:type_name -> lnrpc.RouteHint + 152, // 94: lnrpc.QueryRoutesRequest.blinded_payment_paths:type_name -> lnrpc.BlindedPaymentPath + 10, // 95: lnrpc.QueryRoutesRequest.dest_features:type_name -> lnrpc.FeatureBit + 126, // 96: lnrpc.QueryRoutesResponse.routes:type_name -> lnrpc.Route + 124, // 97: lnrpc.Hop.mpp_record:type_name -> lnrpc.MPPRecord + 125, // 98: lnrpc.Hop.amp_record:type_name -> lnrpc.AMPRecord + 239, // 99: lnrpc.Hop.custom_records:type_name -> lnrpc.Hop.CustomRecordsEntry + 123, // 100: lnrpc.Route.hops:type_name -> lnrpc.Hop + 129, // 101: lnrpc.NodeInfo.node:type_name -> lnrpc.LightningNode + 133, // 102: lnrpc.NodeInfo.channels:type_name -> lnrpc.ChannelEdge + 130, // 103: lnrpc.LightningNode.addresses:type_name -> lnrpc.NodeAddress + 240, // 104: lnrpc.LightningNode.features:type_name -> lnrpc.LightningNode.FeaturesEntry + 241, // 105: lnrpc.LightningNode.custom_records:type_name -> lnrpc.LightningNode.CustomRecordsEntry + 242, // 106: lnrpc.RoutingPolicy.custom_records:type_name -> lnrpc.RoutingPolicy.CustomRecordsEntry + 131, // 107: lnrpc.ChannelEdge.node1_policy:type_name -> lnrpc.RoutingPolicy + 131, // 108: lnrpc.ChannelEdge.node2_policy:type_name -> lnrpc.RoutingPolicy + 243, // 109: lnrpc.ChannelEdge.custom_records:type_name -> lnrpc.ChannelEdge.CustomRecordsEntry + 132, // 110: lnrpc.ChannelEdge.auth_proof:type_name -> lnrpc.ChannelAuthProof + 129, // 111: lnrpc.ChannelGraph.nodes:type_name -> lnrpc.LightningNode + 133, // 112: lnrpc.ChannelGraph.edges:type_name -> lnrpc.ChannelEdge + 7, // 113: lnrpc.NodeMetricsRequest.types:type_name -> lnrpc.NodeMetricType + 244, // 114: lnrpc.NodeMetricsResponse.betweenness_centrality:type_name -> lnrpc.NodeMetricsResponse.BetweennessCentralityEntry + 146, // 115: lnrpc.GraphTopologyUpdate.node_updates:type_name -> lnrpc.NodeUpdate + 147, // 116: lnrpc.GraphTopologyUpdate.channel_updates:type_name -> lnrpc.ChannelEdgeUpdate + 148, // 117: lnrpc.GraphTopologyUpdate.closed_chans:type_name -> lnrpc.ClosedChannelUpdate + 130, // 118: lnrpc.NodeUpdate.node_addresses:type_name -> lnrpc.NodeAddress + 245, // 119: lnrpc.NodeUpdate.features:type_name -> lnrpc.NodeUpdate.FeaturesEntry + 38, // 120: lnrpc.ChannelEdgeUpdate.chan_point:type_name -> lnrpc.ChannelPoint + 131, // 121: lnrpc.ChannelEdgeUpdate.routing_policy:type_name -> lnrpc.RoutingPolicy + 38, // 122: lnrpc.ClosedChannelUpdate.chan_point:type_name -> lnrpc.ChannelPoint + 149, // 123: lnrpc.RouteHint.hop_hints:type_name -> lnrpc.HopHint + 153, // 124: lnrpc.BlindedPaymentPath.blinded_path:type_name -> lnrpc.BlindedPath + 10, // 125: lnrpc.BlindedPaymentPath.features:type_name -> lnrpc.FeatureBit + 154, // 126: lnrpc.BlindedPath.blinded_hops:type_name -> lnrpc.BlindedHop + 8, // 127: lnrpc.AMPInvoiceState.state:type_name -> lnrpc.InvoiceHTLCState + 151, // 128: lnrpc.Invoice.route_hints:type_name -> lnrpc.RouteHint + 17, // 129: lnrpc.Invoice.state:type_name -> lnrpc.Invoice.InvoiceState + 158, // 130: lnrpc.Invoice.htlcs:type_name -> lnrpc.InvoiceHTLC + 246, // 131: lnrpc.Invoice.features:type_name -> lnrpc.Invoice.FeaturesEntry + 247, // 132: lnrpc.Invoice.amp_invoice_state:type_name -> lnrpc.Invoice.AmpInvoiceStateEntry + 157, // 133: lnrpc.Invoice.blinded_path_config:type_name -> lnrpc.BlindedPathConfig + 8, // 134: lnrpc.InvoiceHTLC.state:type_name -> lnrpc.InvoiceHTLCState + 248, // 135: lnrpc.InvoiceHTLC.custom_records:type_name -> lnrpc.InvoiceHTLC.CustomRecordsEntry + 159, // 136: lnrpc.InvoiceHTLC.amp:type_name -> lnrpc.AMP + 156, // 137: lnrpc.ListInvoiceResponse.invoices:type_name -> lnrpc.Invoice + 18, // 138: lnrpc.Payment.status:type_name -> lnrpc.Payment.PaymentStatus + 168, // 139: lnrpc.Payment.htlcs:type_name -> lnrpc.HTLCAttempt + 9, // 140: lnrpc.Payment.failure_reason:type_name -> lnrpc.PaymentFailureReason + 249, // 141: lnrpc.Payment.first_hop_custom_records:type_name -> lnrpc.Payment.FirstHopCustomRecordsEntry + 19, // 142: lnrpc.HTLCAttempt.status:type_name -> lnrpc.HTLCAttempt.HTLCStatus + 126, // 143: lnrpc.HTLCAttempt.route:type_name -> lnrpc.Route + 212, // 144: lnrpc.HTLCAttempt.failure:type_name -> lnrpc.Failure + 167, // 145: lnrpc.ListPaymentsResponse.payments:type_name -> lnrpc.Payment + 38, // 146: lnrpc.AbandonChannelRequest.channel_point:type_name -> lnrpc.ChannelPoint + 151, // 147: lnrpc.PayReq.route_hints:type_name -> lnrpc.RouteHint + 250, // 148: lnrpc.PayReq.features:type_name -> lnrpc.PayReq.FeaturesEntry + 152, // 149: lnrpc.PayReq.blinded_paths:type_name -> lnrpc.BlindedPaymentPath + 183, // 150: lnrpc.FeeReportResponse.channel_fees:type_name -> lnrpc.ChannelFeeReport + 38, // 151: lnrpc.PolicyUpdateRequest.chan_point:type_name -> lnrpc.ChannelPoint + 185, // 152: lnrpc.PolicyUpdateRequest.inbound_fee:type_name -> lnrpc.InboundFee + 39, // 153: lnrpc.FailedUpdate.outpoint:type_name -> lnrpc.OutPoint + 11, // 154: lnrpc.FailedUpdate.reason:type_name -> lnrpc.UpdateFailure + 187, // 155: lnrpc.PolicyUpdateResponse.failed_updates:type_name -> lnrpc.FailedUpdate + 190, // 156: lnrpc.ForwardingHistoryResponse.forwarding_events:type_name -> lnrpc.ForwardingEvent + 38, // 157: lnrpc.ExportChannelBackupRequest.chan_point:type_name -> lnrpc.ChannelPoint + 38, // 158: lnrpc.ChannelBackup.chan_point:type_name -> lnrpc.ChannelPoint + 38, // 159: lnrpc.MultiChanBackup.chan_points:type_name -> lnrpc.ChannelPoint + 197, // 160: lnrpc.ChanBackupSnapshot.single_chan_backups:type_name -> lnrpc.ChannelBackups + 194, // 161: lnrpc.ChanBackupSnapshot.multi_chan_backup:type_name -> lnrpc.MultiChanBackup + 193, // 162: lnrpc.ChannelBackups.chan_backups:type_name -> lnrpc.ChannelBackup + 197, // 163: lnrpc.RestoreChanBackupRequest.chan_backups:type_name -> lnrpc.ChannelBackups + 202, // 164: lnrpc.BakeMacaroonRequest.permissions:type_name -> lnrpc.MacaroonPermission + 202, // 165: lnrpc.MacaroonPermissionList.permissions:type_name -> lnrpc.MacaroonPermission + 251, // 166: lnrpc.ListPermissionsResponse.method_permissions:type_name -> lnrpc.ListPermissionsResponse.MethodPermissionsEntry + 20, // 167: lnrpc.Failure.code:type_name -> lnrpc.Failure.FailureCode + 213, // 168: lnrpc.Failure.channel_update:type_name -> lnrpc.ChannelUpdate + 215, // 169: lnrpc.MacaroonId.ops:type_name -> lnrpc.Op + 202, // 170: lnrpc.CheckMacPermRequest.permissions:type_name -> lnrpc.MacaroonPermission + 220, // 171: lnrpc.RPCMiddlewareRequest.stream_auth:type_name -> lnrpc.StreamAuth + 221, // 172: lnrpc.RPCMiddlewareRequest.request:type_name -> lnrpc.RPCMessage + 221, // 173: lnrpc.RPCMiddlewareRequest.response:type_name -> lnrpc.RPCMessage + 252, // 174: lnrpc.RPCMiddlewareRequest.metadata_pairs:type_name -> lnrpc.RPCMiddlewareRequest.MetadataPairsEntry + 223, // 175: lnrpc.RPCMiddlewareResponse.register:type_name -> lnrpc.MiddlewareRegistration + 224, // 176: lnrpc.RPCMiddlewareResponse.feedback:type_name -> lnrpc.InterceptFeedback + 181, // 177: lnrpc.Peer.FeaturesEntry.value:type_name -> lnrpc.Feature + 181, // 178: lnrpc.GetInfoResponse.FeaturesEntry.value:type_name -> lnrpc.Feature + 4, // 179: lnrpc.PendingChannelsResponse.PendingChannel.initiator:type_name -> lnrpc.Initiator + 3, // 180: lnrpc.PendingChannelsResponse.PendingChannel.commitment_type:type_name -> lnrpc.CommitmentType + 231, // 181: lnrpc.PendingChannelsResponse.PendingOpenChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel + 231, // 182: lnrpc.PendingChannelsResponse.WaitingCloseChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel + 234, // 183: lnrpc.PendingChannelsResponse.WaitingCloseChannel.commitments:type_name -> lnrpc.PendingChannelsResponse.Commitments + 231, // 184: lnrpc.PendingChannelsResponse.ClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel + 231, // 185: lnrpc.PendingChannelsResponse.ForceClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel + 108, // 186: lnrpc.PendingChannelsResponse.ForceClosedChannel.pending_htlcs:type_name -> lnrpc.PendingHTLC + 15, // 187: lnrpc.PendingChannelsResponse.ForceClosedChannel.anchor:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel.AnchorState + 113, // 188: lnrpc.WalletBalanceResponse.AccountBalanceEntry.value:type_name -> lnrpc.WalletAccountBalance + 181, // 189: lnrpc.LightningNode.FeaturesEntry.value:type_name -> lnrpc.Feature + 138, // 190: lnrpc.NodeMetricsResponse.BetweennessCentralityEntry.value:type_name -> lnrpc.FloatMetric + 181, // 191: lnrpc.NodeUpdate.FeaturesEntry.value:type_name -> lnrpc.Feature + 181, // 192: lnrpc.Invoice.FeaturesEntry.value:type_name -> lnrpc.Feature + 155, // 193: lnrpc.Invoice.AmpInvoiceStateEntry.value:type_name -> lnrpc.AMPInvoiceState + 181, // 194: lnrpc.PayReq.FeaturesEntry.value:type_name -> lnrpc.Feature + 209, // 195: lnrpc.ListPermissionsResponse.MethodPermissionsEntry.value:type_name -> lnrpc.MacaroonPermissionList + 219, // 196: lnrpc.RPCMiddlewareRequest.MetadataPairsEntry.value:type_name -> lnrpc.MetadataValues + 114, // 197: lnrpc.Lightning.WalletBalance:input_type -> lnrpc.WalletBalanceRequest + 117, // 198: lnrpc.Lightning.ChannelBalance:input_type -> lnrpc.ChannelBalanceRequest + 30, // 199: lnrpc.Lightning.GetTransactions:input_type -> lnrpc.GetTransactionsRequest + 42, // 200: lnrpc.Lightning.EstimateFee:input_type -> lnrpc.EstimateFeeRequest + 46, // 201: lnrpc.Lightning.SendCoins:input_type -> lnrpc.SendCoinsRequest + 48, // 202: lnrpc.Lightning.ListUnspent:input_type -> lnrpc.ListUnspentRequest + 30, // 203: lnrpc.Lightning.SubscribeTransactions:input_type -> lnrpc.GetTransactionsRequest + 44, // 204: lnrpc.Lightning.SendMany:input_type -> lnrpc.SendManyRequest + 50, // 205: lnrpc.Lightning.NewAddress:input_type -> lnrpc.NewAddressRequest + 52, // 206: lnrpc.Lightning.SignMessage:input_type -> lnrpc.SignMessageRequest + 54, // 207: lnrpc.Lightning.VerifyMessage:input_type -> lnrpc.VerifyMessageRequest + 56, // 208: lnrpc.Lightning.ConnectPeer:input_type -> lnrpc.ConnectPeerRequest + 58, // 209: lnrpc.Lightning.DisconnectPeer:input_type -> lnrpc.DisconnectPeerRequest + 74, // 210: lnrpc.Lightning.ListPeers:input_type -> lnrpc.ListPeersRequest + 76, // 211: lnrpc.Lightning.SubscribePeerEvents:input_type -> lnrpc.PeerEventSubscription + 78, // 212: lnrpc.Lightning.GetInfo:input_type -> lnrpc.GetInfoRequest + 80, // 213: lnrpc.Lightning.GetDebugInfo:input_type -> lnrpc.GetDebugInfoRequest + 82, // 214: lnrpc.Lightning.GetRecoveryInfo:input_type -> lnrpc.GetRecoveryInfoRequest + 109, // 215: lnrpc.Lightning.PendingChannels:input_type -> lnrpc.PendingChannelsRequest + 63, // 216: lnrpc.Lightning.ListChannels:input_type -> lnrpc.ListChannelsRequest + 111, // 217: lnrpc.Lightning.SubscribeChannelEvents:input_type -> lnrpc.ChannelEventSubscription + 70, // 218: lnrpc.Lightning.ClosedChannels:input_type -> lnrpc.ClosedChannelsRequest + 96, // 219: lnrpc.Lightning.OpenChannelSync:input_type -> lnrpc.OpenChannelRequest + 96, // 220: lnrpc.Lightning.OpenChannel:input_type -> lnrpc.OpenChannelRequest + 93, // 221: lnrpc.Lightning.BatchOpenChannel:input_type -> lnrpc.BatchOpenChannelRequest + 106, // 222: lnrpc.Lightning.FundingStateStep:input_type -> lnrpc.FundingTransitionMsg + 37, // 223: lnrpc.Lightning.ChannelAcceptor:input_type -> lnrpc.ChannelAcceptResponse + 88, // 224: lnrpc.Lightning.CloseChannel:input_type -> lnrpc.CloseChannelRequest + 175, // 225: lnrpc.Lightning.AbandonChannel:input_type -> lnrpc.AbandonChannelRequest + 33, // 226: lnrpc.Lightning.SendPayment:input_type -> lnrpc.SendRequest + 33, // 227: lnrpc.Lightning.SendPaymentSync:input_type -> lnrpc.SendRequest + 35, // 228: lnrpc.Lightning.SendToRoute:input_type -> lnrpc.SendToRouteRequest + 35, // 229: lnrpc.Lightning.SendToRouteSync:input_type -> lnrpc.SendToRouteRequest + 156, // 230: lnrpc.Lightning.AddInvoice:input_type -> lnrpc.Invoice + 162, // 231: lnrpc.Lightning.ListInvoices:input_type -> lnrpc.ListInvoiceRequest + 161, // 232: lnrpc.Lightning.LookupInvoice:input_type -> lnrpc.PaymentHash + 164, // 233: lnrpc.Lightning.SubscribeInvoices:input_type -> lnrpc.InvoiceSubscription + 165, // 234: lnrpc.Lightning.DeleteCanceledInvoice:input_type -> lnrpc.DelCanceledInvoiceReq + 179, // 235: lnrpc.Lightning.DecodePayReq:input_type -> lnrpc.PayReqString + 169, // 236: lnrpc.Lightning.ListPayments:input_type -> lnrpc.ListPaymentsRequest + 171, // 237: lnrpc.Lightning.DeletePayment:input_type -> lnrpc.DeletePaymentRequest + 172, // 238: lnrpc.Lightning.DeleteAllPayments:input_type -> lnrpc.DeleteAllPaymentsRequest + 134, // 239: lnrpc.Lightning.DescribeGraph:input_type -> lnrpc.ChannelGraphRequest + 136, // 240: lnrpc.Lightning.GetNodeMetrics:input_type -> lnrpc.NodeMetricsRequest + 139, // 241: lnrpc.Lightning.GetChanInfo:input_type -> lnrpc.ChanInfoRequest + 127, // 242: lnrpc.Lightning.GetNodeInfo:input_type -> lnrpc.NodeInfoRequest + 119, // 243: lnrpc.Lightning.QueryRoutes:input_type -> lnrpc.QueryRoutesRequest + 140, // 244: lnrpc.Lightning.GetNetworkInfo:input_type -> lnrpc.NetworkInfoRequest + 142, // 245: lnrpc.Lightning.StopDaemon:input_type -> lnrpc.StopRequest + 144, // 246: lnrpc.Lightning.SubscribeChannelGraph:input_type -> lnrpc.GraphTopologySubscription + 177, // 247: lnrpc.Lightning.DebugLevel:input_type -> lnrpc.DebugLevelRequest + 182, // 248: lnrpc.Lightning.FeeReport:input_type -> lnrpc.FeeReportRequest + 186, // 249: lnrpc.Lightning.UpdateChannelPolicy:input_type -> lnrpc.PolicyUpdateRequest + 189, // 250: lnrpc.Lightning.ForwardingHistory:input_type -> lnrpc.ForwardingHistoryRequest + 192, // 251: lnrpc.Lightning.ExportChannelBackup:input_type -> lnrpc.ExportChannelBackupRequest + 195, // 252: lnrpc.Lightning.ExportAllChannelBackups:input_type -> lnrpc.ChanBackupExportRequest + 196, // 253: lnrpc.Lightning.VerifyChanBackup:input_type -> lnrpc.ChanBackupSnapshot + 198, // 254: lnrpc.Lightning.RestoreChannelBackups:input_type -> lnrpc.RestoreChanBackupRequest + 200, // 255: lnrpc.Lightning.SubscribeChannelBackups:input_type -> lnrpc.ChannelBackupSubscription + 203, // 256: lnrpc.Lightning.BakeMacaroon:input_type -> lnrpc.BakeMacaroonRequest + 205, // 257: lnrpc.Lightning.ListMacaroonIDs:input_type -> lnrpc.ListMacaroonIDsRequest + 207, // 258: lnrpc.Lightning.DeleteMacaroonID:input_type -> lnrpc.DeleteMacaroonIDRequest + 210, // 259: lnrpc.Lightning.ListPermissions:input_type -> lnrpc.ListPermissionsRequest + 216, // 260: lnrpc.Lightning.CheckMacaroonPermissions:input_type -> lnrpc.CheckMacPermRequest + 222, // 261: lnrpc.Lightning.RegisterRPCMiddleware:input_type -> lnrpc.RPCMiddlewareResponse + 25, // 262: lnrpc.Lightning.SendCustomMessage:input_type -> lnrpc.SendCustomMessageRequest + 23, // 263: lnrpc.Lightning.SubscribeCustomMessages:input_type -> lnrpc.SubscribeCustomMessagesRequest + 66, // 264: lnrpc.Lightning.ListAliases:input_type -> lnrpc.ListAliasesRequest + 21, // 265: lnrpc.Lightning.LookupHtlcResolution:input_type -> lnrpc.LookupHtlcResolutionRequest + 115, // 266: lnrpc.Lightning.WalletBalance:output_type -> lnrpc.WalletBalanceResponse + 118, // 267: lnrpc.Lightning.ChannelBalance:output_type -> lnrpc.ChannelBalanceResponse + 31, // 268: lnrpc.Lightning.GetTransactions:output_type -> lnrpc.TransactionDetails + 43, // 269: lnrpc.Lightning.EstimateFee:output_type -> lnrpc.EstimateFeeResponse + 47, // 270: lnrpc.Lightning.SendCoins:output_type -> lnrpc.SendCoinsResponse + 49, // 271: lnrpc.Lightning.ListUnspent:output_type -> lnrpc.ListUnspentResponse + 29, // 272: lnrpc.Lightning.SubscribeTransactions:output_type -> lnrpc.Transaction + 45, // 273: lnrpc.Lightning.SendMany:output_type -> lnrpc.SendManyResponse + 51, // 274: lnrpc.Lightning.NewAddress:output_type -> lnrpc.NewAddressResponse + 53, // 275: lnrpc.Lightning.SignMessage:output_type -> lnrpc.SignMessageResponse + 55, // 276: lnrpc.Lightning.VerifyMessage:output_type -> lnrpc.VerifyMessageResponse + 57, // 277: lnrpc.Lightning.ConnectPeer:output_type -> lnrpc.ConnectPeerResponse + 59, // 278: lnrpc.Lightning.DisconnectPeer:output_type -> lnrpc.DisconnectPeerResponse + 75, // 279: lnrpc.Lightning.ListPeers:output_type -> lnrpc.ListPeersResponse + 77, // 280: lnrpc.Lightning.SubscribePeerEvents:output_type -> lnrpc.PeerEvent + 79, // 281: lnrpc.Lightning.GetInfo:output_type -> lnrpc.GetInfoResponse + 81, // 282: lnrpc.Lightning.GetDebugInfo:output_type -> lnrpc.GetDebugInfoResponse + 83, // 283: lnrpc.Lightning.GetRecoveryInfo:output_type -> lnrpc.GetRecoveryInfoResponse + 110, // 284: lnrpc.Lightning.PendingChannels:output_type -> lnrpc.PendingChannelsResponse + 64, // 285: lnrpc.Lightning.ListChannels:output_type -> lnrpc.ListChannelsResponse + 112, // 286: lnrpc.Lightning.SubscribeChannelEvents:output_type -> lnrpc.ChannelEventUpdate + 71, // 287: lnrpc.Lightning.ClosedChannels:output_type -> lnrpc.ClosedChannelsResponse + 38, // 288: lnrpc.Lightning.OpenChannelSync:output_type -> lnrpc.ChannelPoint + 97, // 289: lnrpc.Lightning.OpenChannel:output_type -> lnrpc.OpenStatusUpdate + 95, // 290: lnrpc.Lightning.BatchOpenChannel:output_type -> lnrpc.BatchOpenChannelResponse + 107, // 291: lnrpc.Lightning.FundingStateStep:output_type -> lnrpc.FundingStateStepResp + 36, // 292: lnrpc.Lightning.ChannelAcceptor:output_type -> lnrpc.ChannelAcceptRequest + 89, // 293: lnrpc.Lightning.CloseChannel:output_type -> lnrpc.CloseStatusUpdate + 176, // 294: lnrpc.Lightning.AbandonChannel:output_type -> lnrpc.AbandonChannelResponse + 34, // 295: lnrpc.Lightning.SendPayment:output_type -> lnrpc.SendResponse + 34, // 296: lnrpc.Lightning.SendPaymentSync:output_type -> lnrpc.SendResponse + 34, // 297: lnrpc.Lightning.SendToRoute:output_type -> lnrpc.SendResponse + 34, // 298: lnrpc.Lightning.SendToRouteSync:output_type -> lnrpc.SendResponse + 160, // 299: lnrpc.Lightning.AddInvoice:output_type -> lnrpc.AddInvoiceResponse + 163, // 300: lnrpc.Lightning.ListInvoices:output_type -> lnrpc.ListInvoiceResponse + 156, // 301: lnrpc.Lightning.LookupInvoice:output_type -> lnrpc.Invoice + 156, // 302: lnrpc.Lightning.SubscribeInvoices:output_type -> lnrpc.Invoice + 166, // 303: lnrpc.Lightning.DeleteCanceledInvoice:output_type -> lnrpc.DelCanceledInvoiceResp + 180, // 304: lnrpc.Lightning.DecodePayReq:output_type -> lnrpc.PayReq + 170, // 305: lnrpc.Lightning.ListPayments:output_type -> lnrpc.ListPaymentsResponse + 173, // 306: lnrpc.Lightning.DeletePayment:output_type -> lnrpc.DeletePaymentResponse + 174, // 307: lnrpc.Lightning.DeleteAllPayments:output_type -> lnrpc.DeleteAllPaymentsResponse + 135, // 308: lnrpc.Lightning.DescribeGraph:output_type -> lnrpc.ChannelGraph + 137, // 309: lnrpc.Lightning.GetNodeMetrics:output_type -> lnrpc.NodeMetricsResponse + 133, // 310: lnrpc.Lightning.GetChanInfo:output_type -> lnrpc.ChannelEdge + 128, // 311: lnrpc.Lightning.GetNodeInfo:output_type -> lnrpc.NodeInfo + 122, // 312: lnrpc.Lightning.QueryRoutes:output_type -> lnrpc.QueryRoutesResponse + 141, // 313: lnrpc.Lightning.GetNetworkInfo:output_type -> lnrpc.NetworkInfo + 143, // 314: lnrpc.Lightning.StopDaemon:output_type -> lnrpc.StopResponse + 145, // 315: lnrpc.Lightning.SubscribeChannelGraph:output_type -> lnrpc.GraphTopologyUpdate + 178, // 316: lnrpc.Lightning.DebugLevel:output_type -> lnrpc.DebugLevelResponse + 184, // 317: lnrpc.Lightning.FeeReport:output_type -> lnrpc.FeeReportResponse + 188, // 318: lnrpc.Lightning.UpdateChannelPolicy:output_type -> lnrpc.PolicyUpdateResponse + 191, // 319: lnrpc.Lightning.ForwardingHistory:output_type -> lnrpc.ForwardingHistoryResponse + 193, // 320: lnrpc.Lightning.ExportChannelBackup:output_type -> lnrpc.ChannelBackup + 196, // 321: lnrpc.Lightning.ExportAllChannelBackups:output_type -> lnrpc.ChanBackupSnapshot + 201, // 322: lnrpc.Lightning.VerifyChanBackup:output_type -> lnrpc.VerifyChanBackupResponse + 199, // 323: lnrpc.Lightning.RestoreChannelBackups:output_type -> lnrpc.RestoreBackupResponse + 196, // 324: lnrpc.Lightning.SubscribeChannelBackups:output_type -> lnrpc.ChanBackupSnapshot + 204, // 325: lnrpc.Lightning.BakeMacaroon:output_type -> lnrpc.BakeMacaroonResponse + 206, // 326: lnrpc.Lightning.ListMacaroonIDs:output_type -> lnrpc.ListMacaroonIDsResponse + 208, // 327: lnrpc.Lightning.DeleteMacaroonID:output_type -> lnrpc.DeleteMacaroonIDResponse + 211, // 328: lnrpc.Lightning.ListPermissions:output_type -> lnrpc.ListPermissionsResponse + 217, // 329: lnrpc.Lightning.CheckMacaroonPermissions:output_type -> lnrpc.CheckMacPermResponse + 218, // 330: lnrpc.Lightning.RegisterRPCMiddleware:output_type -> lnrpc.RPCMiddlewareRequest + 26, // 331: lnrpc.Lightning.SendCustomMessage:output_type -> lnrpc.SendCustomMessageResponse + 24, // 332: lnrpc.Lightning.SubscribeCustomMessages:output_type -> lnrpc.CustomMessage + 67, // 333: lnrpc.Lightning.ListAliases:output_type -> lnrpc.ListAliasesResponse + 22, // 334: lnrpc.Lightning.LookupHtlcResolution:output_type -> lnrpc.LookupHtlcResolutionResponse + 266, // [266:335] is the sub-list for method output_type + 197, // [197:266] is the sub-list for method input_type + 197, // [197:197] is the sub-list for extension type_name + 197, // [197:197] is the sub-list for extension extendee + 0, // [0:197] is the sub-list for field type_name } func init() { file_lightning_proto_init() } @@ -20760,36 +22802,2558 @@ func file_lightning_proto_init() { if File_lightning_proto != nil { return } - file_lightning_proto_msgTypes[15].OneofWrappers = []any{ + if !protoimpl.UnsafeEnabled { + file_lightning_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LookupHtlcResolutionRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LookupHtlcResolutionResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SubscribeCustomMessagesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CustomMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendCustomMessageRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendCustomMessageResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Utxo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OutputDetail); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Transaction); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetTransactionsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TransactionDetails); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeeLimit); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendToRouteRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelAcceptRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelAcceptResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelPoint); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OutPoint); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PreviousOutPoint); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LightningAddress); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EstimateFeeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EstimateFeeResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendManyRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendManyResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendCoinsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendCoinsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListUnspentRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListUnspentResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NewAddressRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NewAddressResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignMessageRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignMessageResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VerifyMessageRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VerifyMessageResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConnectPeerRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConnectPeerResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DisconnectPeerRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DisconnectPeerResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HTLC); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelConstraints); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Channel); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListChannelsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListChannelsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AliasMap); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListAliasesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[46].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListAliasesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[47].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelCloseSummary); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[48].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Resolution); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ClosedChannelsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[50].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ClosedChannelsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[51].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Peer); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[52].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TimestampedError); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[53].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListPeersRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[54].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListPeersResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[55].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PeerEventSubscription); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[56].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PeerEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[57].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetInfoRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[58].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetInfoResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[59].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetDebugInfoRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[60].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetDebugInfoResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[61].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetRecoveryInfoRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[62].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetRecoveryInfoResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[63].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Chain); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[64].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelOpenUpdate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[65].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CloseOutput); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[66].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelCloseUpdate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[67].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CloseChannelRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[68].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CloseStatusUpdate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[69].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PendingUpdate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[70].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InstantUpdate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[71].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReadyForPsbtFunding); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[72].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BatchOpenChannelRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[73].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BatchOpenChannel); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[74].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BatchOpenChannelResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[75].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OpenChannelRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[76].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OpenStatusUpdate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[77].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KeyLocator); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[78].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KeyDescriptor); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[79].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChanPointShim); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[80].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PsbtShim); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[81].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FundingShim); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[82].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FundingShimCancel); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[83].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FundingPsbtVerify); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[84].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FundingPsbtFinalize); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[85].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FundingTransitionMsg); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[86].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FundingStateStepResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[87].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PendingHTLC); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[88].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PendingChannelsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[89].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PendingChannelsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[90].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelEventSubscription); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[91].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelEventUpdate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[92].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WalletAccountBalance); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[93].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WalletBalanceRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[94].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WalletBalanceResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[95].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Amount); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[96].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelBalanceRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[97].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelBalanceResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[98].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*QueryRoutesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[99].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NodePair); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[100].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EdgeLocator); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[101].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*QueryRoutesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[102].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Hop); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[103].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MPPRecord); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[104].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AMPRecord); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[105].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Route); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[106].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NodeInfoRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[107].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NodeInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[108].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LightningNode); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[109].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NodeAddress); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[110].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RoutingPolicy); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[111].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelAuthProof); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[112].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelEdge); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[113].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelGraphRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[114].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelGraph); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[115].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NodeMetricsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[116].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NodeMetricsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[117].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FloatMetric); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[118].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChanInfoRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[119].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NetworkInfoRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[120].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NetworkInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[121].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StopRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[122].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StopResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[123].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GraphTopologySubscription); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[124].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GraphTopologyUpdate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[125].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NodeUpdate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[126].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelEdgeUpdate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[127].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ClosedChannelUpdate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[128].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HopHint); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[129].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetID); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[130].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RouteHint); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[131].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BlindedPaymentPath); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[132].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BlindedPath); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[133].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BlindedHop); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[134].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AMPInvoiceState); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[135].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Invoice); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[136].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BlindedPathConfig); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[137].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InvoiceHTLC); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[138].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AMP); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[139].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddInvoiceResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[140].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PaymentHash); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[141].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListInvoiceRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[142].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListInvoiceResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[143].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InvoiceSubscription); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[144].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DelCanceledInvoiceReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[145].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DelCanceledInvoiceResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[146].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Payment); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[147].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HTLCAttempt); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[148].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListPaymentsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[149].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListPaymentsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[150].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeletePaymentRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[151].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteAllPaymentsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[152].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeletePaymentResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[153].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteAllPaymentsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[154].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AbandonChannelRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[155].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AbandonChannelResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[156].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DebugLevelRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[157].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DebugLevelResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[158].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PayReqString); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[159].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PayReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[160].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Feature); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[161].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeeReportRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[162].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelFeeReport); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[163].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeeReportResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[164].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InboundFee); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[165].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PolicyUpdateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[166].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FailedUpdate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[167].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PolicyUpdateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[168].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ForwardingHistoryRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[169].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ForwardingEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[170].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ForwardingHistoryResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[171].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExportChannelBackupRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[172].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelBackup); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[173].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MultiChanBackup); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[174].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChanBackupExportRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[175].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChanBackupSnapshot); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[176].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelBackups); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[177].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RestoreChanBackupRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[178].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RestoreBackupResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[179].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelBackupSubscription); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[180].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VerifyChanBackupResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[181].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MacaroonPermission); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[182].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BakeMacaroonRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[183].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BakeMacaroonResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[184].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListMacaroonIDsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[185].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListMacaroonIDsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[186].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteMacaroonIDRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[187].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteMacaroonIDResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[188].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MacaroonPermissionList); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[189].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListPermissionsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[190].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListPermissionsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[191].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Failure); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[192].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelUpdate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[193].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MacaroonId); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[194].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Op); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[195].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CheckMacPermRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[196].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CheckMacPermResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[197].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RPCMiddlewareRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[198].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MetadataValues); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[199].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StreamAuth); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[200].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RPCMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[201].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RPCMiddlewareResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[202].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MiddlewareRegistration); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[203].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InterceptFeedback); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[210].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PendingChannelsResponse_PendingChannel); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[211].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PendingChannelsResponse_PendingOpenChannel); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[212].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PendingChannelsResponse_WaitingCloseChannel); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[213].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PendingChannelsResponse_Commitments); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[214].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PendingChannelsResponse_ClosedChannel); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[215].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PendingChannelsResponse_ForceClosedChannel); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_lightning_proto_msgTypes[11].OneofWrappers = []interface{}{ (*FeeLimit_Fixed)(nil), (*FeeLimit_FixedMsat)(nil), (*FeeLimit_Percent)(nil), } - file_lightning_proto_msgTypes[18].OneofWrappers = []any{ + file_lightning_proto_msgTypes[17].OneofWrappers = []interface{}{ (*ChannelPoint_FundingTxidBytes)(nil), (*ChannelPoint_FundingTxidStr)(nil), } - file_lightning_proto_msgTypes[69].OneofWrappers = []any{ + file_lightning_proto_msgTypes[68].OneofWrappers = []interface{}{ (*CloseStatusUpdate_ClosePending)(nil), (*CloseStatusUpdate_ChanClose)(nil), (*CloseStatusUpdate_CloseInstant)(nil), } - file_lightning_proto_msgTypes[77].OneofWrappers = []any{ + file_lightning_proto_msgTypes[76].OneofWrappers = []interface{}{ (*OpenStatusUpdate_ChanPending)(nil), (*OpenStatusUpdate_ChanOpen)(nil), (*OpenStatusUpdate_PsbtFund)(nil), } - file_lightning_proto_msgTypes[82].OneofWrappers = []any{ + file_lightning_proto_msgTypes[81].OneofWrappers = []interface{}{ (*FundingShim_ChanPointShim)(nil), (*FundingShim_PsbtShim)(nil), } - file_lightning_proto_msgTypes[86].OneofWrappers = []any{ + file_lightning_proto_msgTypes[85].OneofWrappers = []interface{}{ (*FundingTransitionMsg_ShimRegister)(nil), (*FundingTransitionMsg_ShimCancel)(nil), (*FundingTransitionMsg_PsbtVerify)(nil), (*FundingTransitionMsg_PsbtFinalize)(nil), } - file_lightning_proto_msgTypes[93].OneofWrappers = []any{ + file_lightning_proto_msgTypes[91].OneofWrappers = []interface{}{ (*ChannelEventUpdate_OpenChannel)(nil), (*ChannelEventUpdate_ClosedChannel)(nil), (*ChannelEventUpdate_ActiveChannel)(nil), @@ -20797,25 +25361,24 @@ func file_lightning_proto_init() { (*ChannelEventUpdate_PendingOpenChannel)(nil), (*ChannelEventUpdate_FullyResolvedChannel)(nil), (*ChannelEventUpdate_ChannelFundingTimeout)(nil), - (*ChannelEventUpdate_UpdatedChannel)(nil), } - file_lightning_proto_msgTypes[138].OneofWrappers = []any{} - file_lightning_proto_msgTypes[167].OneofWrappers = []any{ + file_lightning_proto_msgTypes[136].OneofWrappers = []interface{}{} + file_lightning_proto_msgTypes[165].OneofWrappers = []interface{}{ (*PolicyUpdateRequest_Global)(nil), (*PolicyUpdateRequest_ChanPoint)(nil), } - file_lightning_proto_msgTypes[171].OneofWrappers = []any{} - file_lightning_proto_msgTypes[179].OneofWrappers = []any{ + file_lightning_proto_msgTypes[169].OneofWrappers = []interface{}{} + file_lightning_proto_msgTypes[177].OneofWrappers = []interface{}{ (*RestoreChanBackupRequest_ChanBackups)(nil), (*RestoreChanBackupRequest_MultiChanBackup)(nil), } - file_lightning_proto_msgTypes[199].OneofWrappers = []any{ + file_lightning_proto_msgTypes[197].OneofWrappers = []interface{}{ (*RPCMiddlewareRequest_StreamAuth)(nil), (*RPCMiddlewareRequest_Request)(nil), (*RPCMiddlewareRequest_Response)(nil), (*RPCMiddlewareRequest_RegComplete)(nil), } - file_lightning_proto_msgTypes[203].OneofWrappers = []any{ + file_lightning_proto_msgTypes[201].OneofWrappers = []interface{}{ (*RPCMiddlewareResponse_Register)(nil), (*RPCMiddlewareResponse_Feedback)(nil), } @@ -20823,9 +25386,9 @@ func file_lightning_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_lightning_proto_rawDesc), len(file_lightning_proto_rawDesc)), - NumEnums: 22, - NumMessages: 234, + RawDescriptor: file_lightning_proto_rawDesc, + NumEnums: 21, + NumMessages: 232, NumExtensions: 0, NumServices: 1, }, @@ -20835,6 +25398,7 @@ func file_lightning_proto_init() { MessageInfos: file_lightning_proto_msgTypes, }.Build() File_lightning_proto = out.File + file_lightning_proto_rawDesc = nil file_lightning_proto_goTypes = nil file_lightning_proto_depIdxs = nil } diff --git a/lnrpc/lightning.pb.gw.go b/lnrpc/lightning.pb.gw.go index 00b1d8e84..14f7e9388 100644 --- a/lnrpc/lightning.pb.gw.go +++ b/lnrpc/lightning.pb.gw.go @@ -550,21 +550,10 @@ func local_request_Lightning_GetInfo_0(ctx context.Context, marshaler runtime.Ma } -var ( - filter_Lightning_GetDebugInfo_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} -) - func request_Lightning_GetDebugInfo_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetDebugInfoRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_GetDebugInfo_0); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - msg, err := client.GetDebugInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err @@ -574,13 +563,6 @@ func local_request_Lightning_GetDebugInfo_0(ctx context.Context, marshaler runti var protoReq GetDebugInfoRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_GetDebugInfo_0); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - msg, err := server.GetDebugInfo(ctx, &protoReq) return msg, metadata, err @@ -1044,6 +1026,117 @@ func local_request_Lightning_AbandonChannel_0(ctx context.Context, marshaler run } +func request_Lightning_SendPayment_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (Lightning_SendPaymentClient, runtime.ServerMetadata, error) { + var metadata runtime.ServerMetadata + stream, err := client.SendPayment(ctx) + if err != nil { + grpclog.Infof("Failed to start streaming: %v", err) + return nil, metadata, err + } + dec := marshaler.NewDecoder(req.Body) + handleSend := func() error { + var protoReq SendRequest + err := dec.Decode(&protoReq) + if err == io.EOF { + return err + } + if err != nil { + grpclog.Infof("Failed to decode request: %v", err) + return err + } + if err := stream.Send(&protoReq); err != nil { + grpclog.Infof("Failed to send request: %v", err) + return err + } + return nil + } + go func() { + for { + if err := handleSend(); err != nil { + break + } + } + if err := stream.CloseSend(); err != nil { + grpclog.Infof("Failed to terminate client stream: %v", err) + } + }() + header, err := stream.Header() + if err != nil { + grpclog.Infof("Failed to get header from client: %v", err) + return nil, metadata, err + } + metadata.HeaderMD = header + return stream, metadata, nil +} + +func request_Lightning_SendPaymentSync_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SendRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.SendPaymentSync(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Lightning_SendPaymentSync_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SendRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.SendPaymentSync(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Lightning_SendToRouteSync_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SendToRouteRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.SendToRouteSync(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Lightning_SendToRouteSync_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SendToRouteRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.SendToRouteSync(ctx, &protoReq) + return msg, metadata, err + +} + func request_Lightning_AddInvoice_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq Invoice var metadata runtime.ServerMetadata @@ -2430,57 +2523,6 @@ func request_Lightning_SubscribeCustomMessages_0(ctx context.Context, marshaler } -func request_Lightning_SendOnionMessage_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq SendOnionMessageRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := client.SendOnionMessage(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) - return msg, metadata, err - -} - -func local_request_Lightning_SendOnionMessage_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq SendOnionMessageRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.SendOnionMessage(ctx, &protoReq) - return msg, metadata, err - -} - -func request_Lightning_SubscribeOnionMessages_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (Lightning_SubscribeOnionMessagesClient, runtime.ServerMetadata, error) { - var protoReq SubscribeOnionMessagesRequest - var metadata runtime.ServerMetadata - - stream, err := client.SubscribeOnionMessages(ctx, &protoReq) - if err != nil { - return nil, metadata, err - } - header, err := stream.Header() - if err != nil { - return nil, metadata, err - } - metadata.HeaderMD = header - return stream, metadata, nil - -} - func request_Lightning_ListAliases_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq ListAliasesRequest var metadata runtime.ServerMetadata @@ -3194,6 +3236,63 @@ func RegisterLightningHandlerServer(ctx context.Context, mux *runtime.ServeMux, }) + mux.Handle("POST", pattern_Lightning_SendPayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + }) + + mux.Handle("POST", pattern_Lightning_SendPaymentSync_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/SendPaymentSync", runtime.WithHTTPPathPattern("/v1/channels/transactions")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Lightning_SendPaymentSync_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Lightning_SendPaymentSync_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Lightning_SendToRouteSync_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/SendToRouteSync", runtime.WithHTTPPathPattern("/v1/channels/transactions/route")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Lightning_SendToRouteSync_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Lightning_SendToRouteSync_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("POST", pattern_Lightning_AddInvoice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -3979,38 +4078,6 @@ func RegisterLightningHandlerServer(ctx context.Context, mux *runtime.ServeMux, return }) - mux.Handle("POST", pattern_Lightning_SendOnionMessage_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - var stream runtime.ServerTransportStream - ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - var err error - var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/SendOnionMessage", runtime.WithHTTPPathPattern("/v1/onionmessage")) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_Lightning_SendOnionMessage_0(annotatedContext, inboundMarshaler, server, req, pathParams) - md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) - if err != nil { - runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) - return - } - - forward_Lightning_SendOnionMessage_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_Lightning_SubscribeOnionMessages_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") - _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - }) - mux.Handle("GET", pattern_Lightning_ListAliases_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -4740,6 +4807,72 @@ func RegisterLightningHandlerClient(ctx context.Context, mux *runtime.ServeMux, }) + mux.Handle("POST", pattern_Lightning_SendPayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/SendPayment", runtime.WithHTTPPathPattern("/v1/channels/transaction-stream")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Lightning_SendPayment_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Lightning_SendPayment_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Lightning_SendPaymentSync_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/SendPaymentSync", runtime.WithHTTPPathPattern("/v1/channels/transactions")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Lightning_SendPaymentSync_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Lightning_SendPaymentSync_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Lightning_SendToRouteSync_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/SendToRouteSync", runtime.WithHTTPPathPattern("/v1/channels/transactions/route")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Lightning_SendToRouteSync_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Lightning_SendToRouteSync_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("POST", pattern_Lightning_AddInvoice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -5510,50 +5643,6 @@ func RegisterLightningHandlerClient(ctx context.Context, mux *runtime.ServeMux, }) - mux.Handle("POST", pattern_Lightning_SendOnionMessage_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - var err error - var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/SendOnionMessage", runtime.WithHTTPPathPattern("/v1/onionmessage")) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := request_Lightning_SendOnionMessage_0(annotatedContext, inboundMarshaler, client, req, pathParams) - annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) - if err != nil { - runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) - return - } - - forward_Lightning_SendOnionMessage_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_Lightning_SubscribeOnionMessages_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - var err error - var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/SubscribeOnionMessages", runtime.WithHTTPPathPattern("/v1/onionmessage/subscribe")) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := request_Lightning_SubscribeOnionMessages_0(annotatedContext, inboundMarshaler, client, req, pathParams) - annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) - if err != nil { - runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) - return - } - - forward_Lightning_SubscribeOnionMessages_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) - - }) - mux.Handle("GET", pattern_Lightning_ListAliases_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -5660,6 +5749,12 @@ var ( pattern_Lightning_AbandonChannel_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 1, 0, 4, 1, 5, 4}, []string{"v1", "channels", "abandon", "channel_point.funding_txid_str", "channel_point.output_index"}, "")) + pattern_Lightning_SendPayment_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "channels", "transaction-stream"}, "")) + + pattern_Lightning_SendPaymentSync_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "channels", "transactions"}, "")) + + pattern_Lightning_SendToRouteSync_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "channels", "transactions", "route"}, "")) + pattern_Lightning_AddInvoice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "invoices"}, "")) pattern_Lightning_ListInvoices_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "invoices"}, "")) @@ -5730,10 +5825,6 @@ var ( pattern_Lightning_SubscribeCustomMessages_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "custommessage", "subscribe"}, "")) - pattern_Lightning_SendOnionMessage_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "onionmessage"}, "")) - - pattern_Lightning_SubscribeOnionMessages_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "onionmessage", "subscribe"}, "")) - pattern_Lightning_ListAliases_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "aliases", "list"}, "")) pattern_Lightning_LookupHtlcResolution_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 1, 0, 4, 1, 5, 3}, []string{"v1", "htlc-resolution", "chan_id", "htlc_index"}, "")) @@ -5798,6 +5889,12 @@ var ( forward_Lightning_AbandonChannel_0 = runtime.ForwardResponseMessage + forward_Lightning_SendPayment_0 = runtime.ForwardResponseStream + + forward_Lightning_SendPaymentSync_0 = runtime.ForwardResponseMessage + + forward_Lightning_SendToRouteSync_0 = runtime.ForwardResponseMessage + forward_Lightning_AddInvoice_0 = runtime.ForwardResponseMessage forward_Lightning_ListInvoices_0 = runtime.ForwardResponseMessage @@ -5868,10 +5965,6 @@ var ( forward_Lightning_SubscribeCustomMessages_0 = runtime.ForwardResponseStream - forward_Lightning_SendOnionMessage_0 = runtime.ForwardResponseMessage - - forward_Lightning_SubscribeOnionMessages_0 = runtime.ForwardResponseStream - forward_Lightning_ListAliases_0 = runtime.ForwardResponseMessage forward_Lightning_LookupHtlcResolution_0 = runtime.ForwardResponseMessage diff --git a/lnrpc/lightning.pb.json.go b/lnrpc/lightning.pb.json.go index 4b0e4d756..1eac53dc1 100644 --- a/lnrpc/lightning.pb.json.go +++ b/lnrpc/lightning.pb.json.go @@ -806,6 +806,56 @@ func RegisterLightningJSONCallbacks(registry map[string]func(ctx context.Context callback(string(respBytes), nil) } + registry["lnrpc.Lightning.SendPaymentSync"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &SendRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewLightningClient(conn) + resp, err := client.SendPaymentSync(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + + registry["lnrpc.Lightning.SendToRouteSync"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &SendToRouteRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewLightningClient(conn) + resp, err := client.SendToRouteSync(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + registry["lnrpc.Lightning.AddInvoice"] = func(ctx context.Context, conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { @@ -1699,73 +1749,6 @@ func RegisterLightningJSONCallbacks(registry map[string]func(ctx context.Context }() } - registry["lnrpc.Lightning.SendOnionMessage"] = func(ctx context.Context, - conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { - - req := &SendOnionMessageRequest{} - err := marshaler.Unmarshal([]byte(reqJSON), req) - if err != nil { - callback("", err) - return - } - - client := NewLightningClient(conn) - resp, err := client.SendOnionMessage(ctx, req) - if err != nil { - callback("", err) - return - } - - respBytes, err := marshaler.Marshal(resp) - if err != nil { - callback("", err) - return - } - callback(string(respBytes), nil) - } - - registry["lnrpc.Lightning.SubscribeOnionMessages"] = func(ctx context.Context, - conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { - - req := &SubscribeOnionMessagesRequest{} - err := marshaler.Unmarshal([]byte(reqJSON), req) - if err != nil { - callback("", err) - return - } - - client := NewLightningClient(conn) - stream, err := client.SubscribeOnionMessages(ctx, req) - if err != nil { - callback("", err) - return - } - - go func() { - for { - select { - case <-stream.Context().Done(): - callback("", stream.Context().Err()) - return - default: - } - - resp, err := stream.Recv() - if err != nil { - callback("", err) - return - } - - respBytes, err := marshaler.Marshal(resp) - if err != nil { - callback("", err) - return - } - callback(string(respBytes), nil) - } - }() - } - registry["lnrpc.Lightning.ListAliases"] = func(ctx context.Context, conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { diff --git a/lnrpc/lightning.proto b/lnrpc/lightning.proto index 30b78f663..9c9abddfc 100644 --- a/lnrpc/lightning.proto +++ b/lnrpc/lightning.proto @@ -262,6 +262,47 @@ service Lightning { */ rpc AbandonChannel (AbandonChannelRequest) returns (AbandonChannelResponse); + /* lncli: `sendpayment` + Deprecated, use routerrpc.SendPaymentV2. SendPayment dispatches a + bi-directional streaming RPC for sending payments through the Lightning + Network. A single RPC invocation creates a persistent bi-directional + stream allowing clients to rapidly send payments through the Lightning + Network with a single persistent connection. + */ + rpc SendPayment (stream SendRequest) returns (stream SendResponse) { + option deprecated = true; + } + + /* + Deprecated, use routerrpc.SendPaymentV2. SendPaymentSync is the synchronous + non-streaming version of SendPayment. This RPC is intended to be consumed by + clients of the REST proxy. Additionally, this RPC expects the destination's + public key and the payment hash (if any) to be encoded as hex strings. + */ + rpc SendPaymentSync (SendRequest) returns (SendResponse) { + option deprecated = true; + } + + /* lncli: `sendtoroute` + Deprecated, use routerrpc.SendToRouteV2. SendToRoute is a bi-directional + streaming RPC for sending payment through the Lightning Network. This + method differs from SendPayment in that it allows users to specify a full + route manually. This can be used for things like rebalancing, and atomic + swaps. + */ + rpc SendToRoute (stream SendToRouteRequest) returns (stream SendResponse) { + option deprecated = true; + } + + /* + Deprecated, use routerrpc.SendToRouteV2. SendToRouteSync is a synchronous + version of SendToRoute. It Will block until the payment either fails or + succeeds. + */ + rpc SendToRouteSync (SendToRouteRequest) returns (SendResponse) { + option deprecated = true; + } + /* lncli: `addinvoice` AddInvoice attempts to add a new invoice to the invoice database. Any duplicated invoices are rejected, therefore all invoices *must* have a @@ -556,18 +597,6 @@ service Lightning { rpc SubscribeCustomMessages (SubscribeCustomMessagesRequest) returns (stream CustomMessage); - /* lncli: `sendonion` - SendOnionMessage sends an onion message to a peer. - */ - rpc SendOnionMessage (SendOnionMessageRequest) - returns (SendOnionMessageResponse); - - /* lncli: `subscribeonion` - SubscribeOnionMessages subscribes to a stream of incoming onion messages. - */ - rpc SubscribeOnionMessages (SubscribeOnionMessagesRequest) - returns (stream OnionMessageUpdate); - /* lncli: `listaliases` ListAliases returns the set of all aliases that have ever existed with their confirmed SCID (if it exists) and/or the base SCID (in the case of @@ -613,8 +642,7 @@ message CustomMessage { } message SendCustomMessageRequest { - // Peer to which the message will be sent. Represented as a byte-encoded - // public key + // Peer to send the message to bytes peer = 1; // Message type. This value needs to be in the custom range (>= 32768). @@ -632,74 +660,6 @@ message SendCustomMessageResponse { string status = 1; } -message SubscribeOnionMessagesRequest { -} - -message OnionMessageUpdate { - // Peer from which this message originates. Represented as a byte-encoded - // public key. - bytes peer = 1; - - // PathKey is used to derive the blinded node id by tweaking the hop's - // static public key. The hop uses the corresponding blinded private key - // together with the sender's ephemeral key to perform ECDH and obtain the - // shared secret for decrypting the onion payload. Separately, for - // decrypting `encrypted_recipient_data`, the recipient performs ECDH - // between its static node private key and the path_key to derive the - // decryption key. - bytes path_key = 2; - - // Serialized Sphinx onion packet (BOLT 4) containing the layered, per-hop - // encrypted payloads and routing instructions used to forward this message - // along its designated path. - bytes onion = 3; - - /* - reply_path is the blinded path that should be used when replying to a - received message. The introduction_node field is passed through verbatim - from the wire. It may carry either the 33-byte SEC1 compressed pubkey - form or the 9-byte sciddir form. The sciddir form consists of a 1-byte - direction selector (0x00 or 0x01) followed by an 8-byte short channel ID. - Subscribers that intend to reply resolve the sciddir form against their - local channel graph. - */ - BlindedPath reply_path = 4; - - // encrypted_recipient_data is the encrypted data that contains the - // forwarding information for an onion message. It contains either - // next_node_id or short_channel_id for each non-final node. It MAY contain - // the path_id for the final node. - bytes encrypted_recipient_data = 5; - - // Custom onion message tlv records. These are customized fields that are - // not defined by LND and cannot be extracted. - map custom_records = 6; -} - -message SendOnionMessageRequest { - // Peer to send the message to - bytes peer = 1; - - // PathKey is used to derive the blinded node id by tweaking the hop's - // static public key. The hop uses the corresponding blinded private key - // together with the sender's ephemeral key to perform ECDH and obtain the - // shared secret for decrypting the onion payload. Separately, for - // decrypting `encrypted_recipient_data`, the recipient performs ECDH - // between its static node private key and the path_key to derive the - // decryption key. - bytes path_key = 2; - - // Serialized Sphinx onion packet (BOLT 4) containing the layered, per-hop - // encrypted payloads and routing instructions used to forward this message - // along its designated path. - bytes onion = 3; -} - -message SendOnionMessageResponse { - // The status of the onion message send operation. - string status = 1; -} - message Utxo { // The type of address AddressType address_type = 1; @@ -862,6 +822,139 @@ message FeeLimit { } } +message SendRequest { + /* + The identity pubkey of the payment recipient. When using REST, this field + must be encoded as base64. + */ + bytes dest = 1; + + /* + The hex-encoded identity pubkey of the payment recipient. Deprecated now + that the REST gateway supports base64 encoding of bytes fields. + */ + string dest_string = 2 [deprecated = true]; + + /* + The amount to send expressed in satoshis. + + The fields amt and amt_msat are mutually exclusive. + */ + int64 amt = 3; + + /* + The amount to send expressed in millisatoshis. + + The fields amt and amt_msat are mutually exclusive. + */ + int64 amt_msat = 12; + + /* + The hash to use within the payment's HTLC. When using REST, this field + must be encoded as base64. + */ + bytes payment_hash = 4; + + /* + The hex-encoded hash to use within the payment's HTLC. Deprecated now + that the REST gateway supports base64 encoding of bytes fields. + */ + string payment_hash_string = 5 [deprecated = true]; + + /* + A bare-bones invoice for a payment within the Lightning Network. With the + details of the invoice, the sender has all the data necessary to send a + payment to the recipient. + */ + string payment_request = 6; + + /* + The CLTV delta from the current height that should be used to set the + timelock for the final hop. + */ + int32 final_cltv_delta = 7; + + /* + The maximum number of satoshis that will be paid as a fee of the payment. + This value can be represented either as a percentage of the amount being + sent, or as a fixed amount of the maximum fee the user is willing the pay to + send the payment. If not specified, lnd will use a default value of 100% + fees for small amounts (<=1k sat) or 5% fees for larger amounts. + */ + FeeLimit fee_limit = 8; + + /* + The channel id of the channel that must be taken to the first hop. If zero, + any channel may be used. + */ + uint64 outgoing_chan_id = 9 [jstype = JS_STRING]; + + /* + The pubkey of the last hop of the route. If empty, any hop may be used. + */ + bytes last_hop_pubkey = 13; + + /* + An optional maximum total time lock for the route. This should not exceed + lnd's `--max-cltv-expiry` setting. If zero, then the value of + `--max-cltv-expiry` is enforced. + */ + uint32 cltv_limit = 10; + + /* + An optional field that can be used to pass an arbitrary set of TLV records + to a peer which understands the new records. This can be used to pass + application specific data during the payment attempt. Record types are + required to be in the custom range >= 65536. When using REST, the values + must be encoded as base64. + */ + map dest_custom_records = 11; + + // If set, circular payments to self are permitted. + bool allow_self_payment = 14; + + /* + Features assumed to be supported by the final node. All transitive feature + dependencies must also be set properly. For a given feature bit pair, either + optional or remote may be set, but not both. If this field is nil or empty, + the router will try to load destination features from the graph as a + fallback. + */ + repeated FeatureBit dest_features = 15; + + /* + The payment address of the generated invoice. This is also called + payment secret in specifications (e.g. BOLT 11). + */ + bytes payment_addr = 16; +} + +message SendResponse { + string payment_error = 1; + bytes payment_preimage = 2; + Route payment_route = 3; + bytes payment_hash = 4; +} + +message SendToRouteRequest { + /* + The payment hash to use for the HTLC. When using REST, this field must be + encoded as base64. + */ + bytes payment_hash = 1; + + /* + An optional hex-encoded payment hash to be used for the HTLC. Deprecated now + that the REST gateway supports base64 encoding of bytes fields. + */ + string payment_hash_string = 2 [deprecated = true]; + + reserved 3; + + // Route that should be used to attempt to complete the payment. + Route route = 4; +} + message ChannelAcceptRequest { // The pubkey of the node that wishes to open an inbound channel. bytes node_pubkey = 1; @@ -1066,9 +1159,6 @@ message EstimateFeeRequest { // The strategy to use for selecting coins during fees estimation. CoinSelectionStrategy coin_selection_strategy = 5; - - // A list of selected inputs for the transaction. - repeated OutPoint inputs = 6; } message EstimateFeeResponse { @@ -1081,9 +1171,6 @@ message EstimateFeeResponse { // The fee rate in satoshi/vbyte. uint64 sat_per_vbyte = 3; - - // A list of selected inputs for the transaction the estimate is for. - repeated OutPoint inputs = 4; } message SendManyRequest { @@ -1312,11 +1399,6 @@ message HTLC { } enum CommitmentType { - // Allow multiple enum names to map to the same numeric value so the - // taproot channel types can expose short, canonical aliases without - // breaking on-wire compatibility with the historic names. - option allow_alias = true; - /* Returned when the commitment type isn't known or unavailable. */ @@ -1353,25 +1435,8 @@ enum CommitmentType { SCRIPT_ENFORCED_LEASE = 4; /* - The production taproot channel type that uses musig2 for the funding - output and the new tapscript features, with final scripts and feature - bits 80/81. This is the recommended taproot variant; new integrations - should select this enum value. - */ - TAPROOT = 7; - - /* - Deprecated alias for TAPROOT, preserved so existing clients that select - the production taproot channel type by its historic name continue to - compile and serialize against the same wire value. - */ - SIMPLE_TAPROOT_FINAL = 7; - - /* - A legacy taproot channel type that uses musig2 for the funding output and - the new tapscript features, but with development scripts and the staging - feature bits. Retained for compatibility with peers that have not upgraded - to TAPROOT; new integrations should prefer TAPROOT. + A channel that uses musig2 for the funding output, and the new tapscript + features where relevant. */ SIMPLE_TAPROOT = 5; @@ -1913,13 +1978,6 @@ message PeerEvent { message GetInfoRequest { } -enum GraphCacheStatus { - GRAPH_CACHE_STATUS_DISABLED = 0; - GRAPH_CACHE_STATUS_LOADING = 1; - GRAPH_CACHE_STATUS_LOADED = 2; - GRAPH_CACHE_STATUS_FAILED = 3; -} - message GetInfoResponse { // The version of the LND software that the node is running. string version = 14; @@ -1994,19 +2052,9 @@ message GetInfoResponse { // Indicates whether final htlc resolutions are stored on disk. bool store_final_htlc_resolutions = 22; - - // Whether the wallet is fully synced to the best chain. This indicates the - // wallet's internal sync state with the backing chain source. - bool wallet_synced = 23; - - // The current status of the in-memory graph cache. - GraphCacheStatus graph_cache_status = 24; } message GetDebugInfoRequest { - // If set to true, the log file content will be included in the response. - // By default, only the config information is returned. - bool include_log = 1; } message GetDebugInfoResponse { @@ -2842,24 +2890,6 @@ message PendingChannelsResponse { // The raw hex encoded bytes of the closing transaction. Included if // include_raw_tx in the request is true. string closing_tx_hex = 5; - - /* - Remaining number of confirmations until the channel closure is - considered final and removed from waiting close. Channel closes - require multiple confirmations for reorg protection — the exact - number scales with channel capacity. A closing transaction that - gets reorganized out of the chain resets this counter. When the - closing transaction is not yet confirmed, this value equals the - total number of confirmations required. - */ - uint32 blocks_til_close_confirmed = 6; - - /* - The block height at which the closing transaction was first confirmed. - This will be zero if the closing transaction has not yet confirmed, or - if this information is not available for older channels. - */ - uint32 close_height = 7; } message Commitments { @@ -2964,10 +2994,6 @@ message PendingChannelsResponse { message ChannelEventSubscription { } -message ChannelCommitUpdate { - Channel channel = 1; -} - message ChannelEventUpdate { oneof channel { Channel open_channel = 1; @@ -2977,7 +3003,6 @@ message ChannelEventUpdate { PendingUpdate pending_open_channel = 6; ChannelPoint fully_resolved_channel = 7; ChannelPoint channel_funding_timeout = 8; - ChannelCommitUpdate updated_channel = 9; } enum UpdateType { @@ -2988,7 +3013,6 @@ message ChannelEventUpdate { PENDING_OPEN_CHANNEL = 4; FULLY_RESOLVED_CHANNEL = 5; CHANNEL_FUNDING_TIMEOUT = 6; - CHANNEL_UPDATE = 7; } UpdateType type = 5; @@ -3162,7 +3186,11 @@ message QueryRoutesRequest { */ map dest_custom_records = 13; - reserved 14; + /* + Deprecated, use outgoing_chan_ids. The channel id of the channel that must + be taken to the first hop. If zero, any channel may be used. + */ + uint64 outgoing_chan_id = 14 [jstype = JS_STRING, deprecated = true]; /* The pubkey of the last hop of the route. If empty, any hop may be used. @@ -4433,10 +4461,6 @@ message ListPaymentsRequest { // If set, returns all payments with a creation date less than or equal to // it. Measured in seconds since the unix epoch. uint64 creation_date_end = 7; - - // If set, omit hop-level route data for HTLC attempts to reduce query - // cost and response size. - bool omit_hops = 8; } message ListPaymentsResponse { diff --git a/lnrpc/lightning.swagger.json b/lnrpc/lightning.swagger.json index c87fcb1f5..2fd4580da 100644 --- a/lnrpc/lightning.swagger.json +++ b/lnrpc/lightning.swagger.json @@ -670,6 +670,115 @@ ] } }, + "/v1/channels/transaction-stream": { + "post": { + "summary": "lncli: `sendpayment`\nDeprecated, use routerrpc.SendPaymentV2. SendPayment dispatches a\nbi-directional streaming RPC for sending payments through the Lightning\nNetwork. A single RPC invocation creates a persistent bi-directional\nstream allowing clients to rapidly send payments through the Lightning\nNetwork with a single persistent connection.", + "operationId": "Lightning_SendPayment", + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "schema": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/lnrpcSendResponse" + }, + "error": { + "$ref": "#/definitions/rpcStatus" + } + }, + "title": "Stream result of lnrpcSendResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "description": " (streaming inputs)", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/lnrpcSendRequest" + } + } + ], + "tags": [ + "Lightning" + ] + } + }, + "/v1/channels/transactions": { + "post": { + "summary": "Deprecated, use routerrpc.SendPaymentV2. SendPaymentSync is the synchronous\nnon-streaming version of SendPayment. This RPC is intended to be consumed by\nclients of the REST proxy. Additionally, this RPC expects the destination's\npublic key and the payment hash (if any) to be encoded as hex strings.", + "operationId": "Lightning_SendPaymentSync", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/lnrpcSendResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/lnrpcSendRequest" + } + } + ], + "tags": [ + "Lightning" + ] + } + }, + "/v1/channels/transactions/route": { + "post": { + "summary": "Deprecated, use routerrpc.SendToRouteV2. SendToRouteSync is a synchronous\nversion of SendToRoute. It Will block until the payment either fails or\nsucceeds.", + "operationId": "Lightning_SendToRouteSync", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/lnrpcSendResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/lnrpcSendToRouteRequest" + } + } + ], + "tags": [ + "Lightning" + ] + } + }, "/v1/channels/{channel_point.funding_txid_str}/{channel_point.output_index}": { "delete": { "summary": "lncli: `closechannel`\nCloseChannel attempts to close an active channel identified by its channel\noutpoint (ChannelPoint). The actions of this method can additionally be\naugmented to attempt a force close after a timeout period in the case of an\ninactive peer. If a non-force close (cooperative closure) is requested,\nthen the user can specify either a target number of blocks until the\nclosure transaction is confirmed, or a manual fee rate. If neither are\nspecified, then a default lax, block confirmation target is used.", @@ -986,15 +1095,6 @@ } } }, - "parameters": [ - { - "name": "include_log", - "description": "If set to true, the log file content will be included in the response.\nBy default, only the config information is returned.", - "in": "query", - "required": false, - "type": "boolean" - } - ], "tags": [ "Lightning" ] @@ -1357,6 +1457,14 @@ "required": false, "type": "string" }, + { + "name": "outgoing_chan_id", + "description": "Deprecated, use outgoing_chan_ids. The channel id of the channel that must\nbe taken to the first hop. If zero, any channel may be used.", + "in": "query", + "required": false, + "type": "string", + "format": "uint64" + }, { "name": "last_hop_pubkey", "description": "The pubkey of the last hop of the route. If empty, any hop may be used.", @@ -1530,6 +1638,11 @@ }, "description": "An optional field that can be used to pass an arbitrary set of TLV records\nto a peer which understands the new records. This can be used to pass\napplication specific data during the payment attempt. If the destination\ndoes not support the specified records, an error will be returned.\nRecord types are required to be in the custom range \u003e= 65536. When using\nREST, the values must be encoded as base64." }, + "outgoing_chan_id": { + "type": "string", + "format": "uint64", + "description": "Deprecated, use outgoing_chan_ids. The channel id of the channel that must\nbe taken to the first hop. If zero, any channel may be used." + }, "last_hop_pubkey": { "type": "string", "format": "byte", @@ -2151,71 +2264,6 @@ ] } }, - "/v1/onionmessage": { - "post": { - "summary": "lncli: `sendonion`\nSendOnionMessage sends an onion message to a peer.", - "operationId": "Lightning_SendOnionMessage", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/lnrpcSendOnionMessageResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/rpcStatus" - } - } - }, - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/lnrpcSendOnionMessageRequest" - } - } - ], - "tags": [ - "Lightning" - ] - } - }, - "/v1/onionmessage/subscribe": { - "get": { - "summary": "lncli: `subscribeonion`\nSubscribeOnionMessages subscribes to a stream of incoming onion messages.", - "operationId": "Lightning_SubscribeOnionMessages", - "responses": { - "200": { - "description": "A successful response.(streaming responses)", - "schema": { - "type": "object", - "properties": { - "result": { - "$ref": "#/definitions/lnrpcOnionMessageUpdate" - }, - "error": { - "$ref": "#/definitions/rpcStatus" - } - }, - "title": "Stream result of lnrpcOnionMessageUpdate" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/rpcStatus" - } - } - }, - "tags": [ - "Lightning" - ] - } - }, "/v1/payment": { "delete": { "summary": "lncli: `deletepayments`\nDeletePayment deletes an outgoing payment from DB. Note that it will not\nattempt to delete an In-Flight payment, since that would be unsafe.", @@ -2327,13 +2375,6 @@ "required": false, "type": "string", "format": "uint64" - }, - { - "name": "omit_hops", - "description": "If set, omit hop-level route data for HTLC attempts to reduce query\ncost and response size.", - "in": "query", - "required": false, - "type": "boolean" } ], "tags": [ @@ -3019,8 +3060,7 @@ "INACTIVE_CHANNEL", "PENDING_OPEN_CHANNEL", "FULLY_RESOLVED_CHANNEL", - "CHANNEL_FUNDING_TIMEOUT", - "CHANNEL_UPDATE" + "CHANNEL_FUNDING_TIMEOUT" ], "default": "OPEN_CHANNEL" }, @@ -3333,16 +3373,6 @@ "closing_tx_hex": { "type": "string", "description": "The raw hex encoded bytes of the closing transaction. Included if\ninclude_raw_tx in the request is true." - }, - "blocks_til_close_confirmed": { - "type": "integer", - "format": "int64", - "description": "Remaining number of confirmations until the channel closure is\nconsidered final and removed from waiting close. Channel closes\nrequire multiple confirmations for reorg protection — the exact\nnumber scales with channel capacity. A closing transaction that\ngets reorganized out of the chain resets this counter. When the\nclosing transaction is not yet confirmed, this value equals the\ntotal number of confirmations required." - }, - "close_height": { - "type": "integer", - "format": "int64", - "description": "The block height at which the closing transaction was first confirmed.\nThis will be zero if the closing transaction has not yet confirmed, or\nif this information is not available for older channels." } } }, @@ -4383,14 +4413,6 @@ } } }, - "lnrpcChannelCommitUpdate": { - "type": "object", - "properties": { - "channel": { - "$ref": "#/definitions/lnrpcChannel" - } - } - }, "lnrpcChannelConstraints": { "type": "object", "properties": { @@ -4522,9 +4544,6 @@ "channel_funding_timeout": { "$ref": "#/definitions/lnrpcChannelPoint" }, - "updated_channel": { - "$ref": "#/definitions/lnrpcChannelCommitUpdate" - }, "type": { "$ref": "#/definitions/ChannelEventUpdateUpdateType" } @@ -4806,13 +4825,11 @@ "STATIC_REMOTE_KEY", "ANCHORS", "SCRIPT_ENFORCED_LEASE", - "TAPROOT", - "SIMPLE_TAPROOT_FINAL", "SIMPLE_TAPROOT", "SIMPLE_TAPROOT_OVERLAY" ], "default": "UNKNOWN_COMMITMENT_TYPE", - "description": " - UNKNOWN_COMMITMENT_TYPE: Returned when the commitment type isn't known or unavailable.\n - LEGACY: A channel using the legacy commitment format having tweaked to_remote\nkeys.\n - STATIC_REMOTE_KEY: A channel that uses the modern commitment format where the key in the\noutput of the remote party does not change each state. This makes back\nup and recovery easier as when the channel is closed, the funds go\ndirectly to that key.\n - ANCHORS: A channel that uses a commitment format that has anchor outputs on the\ncommitments, allowing fee bumping after a force close transaction has\nbeen broadcast.\n - SCRIPT_ENFORCED_LEASE: A channel that uses a commitment type that builds upon the anchors\ncommitment format, but in addition requires a CLTV clause to spend outputs\npaying to the channel initiator. This is intended for use on leased channels\nto guarantee that the channel initiator has no incentives to close a leased\nchannel before its maturity date.\n - TAPROOT: The production taproot channel type that uses musig2 for the funding\noutput and the new tapscript features, with final scripts and feature\nbits 80/81. This is the recommended taproot variant; new integrations\nshould select this enum value.\n - SIMPLE_TAPROOT_FINAL: Deprecated alias for TAPROOT, preserved so existing clients that select\nthe production taproot channel type by its historic name continue to\ncompile and serialize against the same wire value.\n - SIMPLE_TAPROOT: A legacy taproot channel type that uses musig2 for the funding output and\nthe new tapscript features, but with development scripts and the staging\nfeature bits. Retained for compatibility with peers that have not upgraded\nto TAPROOT; new integrations should prefer TAPROOT.\n - SIMPLE_TAPROOT_OVERLAY: Identical to the SIMPLE_TAPROOT channel type, but with extra functionality.\nThis channel type also commits to additional meta data in the tapscript\nleaves for the scripts in a channel." + "description": " - UNKNOWN_COMMITMENT_TYPE: Returned when the commitment type isn't known or unavailable.\n - LEGACY: A channel using the legacy commitment format having tweaked to_remote\nkeys.\n - STATIC_REMOTE_KEY: A channel that uses the modern commitment format where the key in the\noutput of the remote party does not change each state. This makes back\nup and recovery easier as when the channel is closed, the funds go\ndirectly to that key.\n - ANCHORS: A channel that uses a commitment format that has anchor outputs on the\ncommitments, allowing fee bumping after a force close transaction has\nbeen broadcast.\n - SCRIPT_ENFORCED_LEASE: A channel that uses a commitment type that builds upon the anchors\ncommitment format, but in addition requires a CLTV clause to spend outputs\npaying to the channel initiator. This is intended for use on leased channels\nto guarantee that the channel initiator has no incentives to close a leased\nchannel before its maturity date.\n - SIMPLE_TAPROOT: A channel that uses musig2 for the funding output, and the new tapscript\nfeatures where relevant.\n - SIMPLE_TAPROOT_OVERLAY: Identical to the SIMPLE_TAPROOT channel type, but with extra functionality.\nThis channel type also commits to additional meta data in the tapscript\nleaves for the scripts in a channel." }, "lnrpcConnectPeerRequest": { "type": "object", @@ -4956,14 +4973,6 @@ "type": "string", "format": "uint64", "description": "The fee rate in satoshi/vbyte." - }, - "inputs": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/lnrpcOutPoint" - }, - "description": "A list of selected inputs for the transaction the estimate is for." } } }, @@ -5474,14 +5483,6 @@ "store_final_htlc_resolutions": { "type": "boolean", "description": "Indicates whether final htlc resolutions are stored on disk." - }, - "wallet_synced": { - "type": "boolean", - "description": "Whether the wallet is fully synced to the best chain. This indicates the\nwallet's internal sync state with the backing chain source." - }, - "graph_cache_status": { - "$ref": "#/definitions/lnrpcGraphCacheStatus", - "description": "The current status of the in-memory graph cache." } } }, @@ -5503,16 +5504,6 @@ } } }, - "lnrpcGraphCacheStatus": { - "type": "string", - "enum": [ - "GRAPH_CACHE_STATUS_DISABLED", - "GRAPH_CACHE_STATUS_LOADING", - "GRAPH_CACHE_STATUS_LOADED", - "GRAPH_CACHE_STATUS_FAILED" - ], - "default": "GRAPH_CACHE_STATUS_DISABLED" - }, "lnrpcGraphTopologyUpdate": { "type": "object", "properties": { @@ -6486,43 +6477,6 @@ } } }, - "lnrpcOnionMessageUpdate": { - "type": "object", - "properties": { - "peer": { - "type": "string", - "format": "byte", - "description": "Peer from which this message originates. Represented as a byte-encoded\npublic key." - }, - "path_key": { - "type": "string", - "format": "byte", - "description": "PathKey is used to derive the blinded node id by tweaking the hop's\nstatic public key. The hop uses the corresponding blinded private key\ntogether with the sender's ephemeral key to perform ECDH and obtain the\nshared secret for decrypting the onion payload. Separately, for\ndecrypting `encrypted_recipient_data`, the recipient performs ECDH\nbetween its static node private key and the path_key to derive the\ndecryption key." - }, - "onion": { - "type": "string", - "format": "byte", - "description": "Serialized Sphinx onion packet (BOLT 4) containing the layered, per-hop\nencrypted payloads and routing instructions used to forward this message\nalong its designated path." - }, - "reply_path": { - "$ref": "#/definitions/lnrpcBlindedPath", - "description": "reply_path is the blinded path that should be used when replying to a\nreceived message. The introduction_node field is passed through verbatim\nfrom the wire. It may carry either the 33-byte SEC1 compressed pubkey\nform or the 9-byte sciddir form. The sciddir form consists of a 1-byte\ndirection selector (0x00 or 0x01) followed by an 8-byte short channel ID.\nSubscribers that intend to reply resolve the sciddir form against their\nlocal channel graph." - }, - "encrypted_recipient_data": { - "type": "string", - "format": "byte", - "description": "encrypted_recipient_data is the encrypted data that contains the\nforwarding information for an onion message. It contains either\nnext_node_id or short_channel_id for each non-final node. It MAY contain\nthe path_id for the final node." - }, - "custom_records": { - "type": "object", - "additionalProperties": { - "type": "string", - "format": "byte" - }, - "description": "Custom onion message tlv records. These are customized fields that are\nnot defined by LND and cannot be extracted." - } - } - }, "lnrpcOpenChannelRequest": { "type": "object", "properties": { @@ -7561,7 +7515,7 @@ "peer": { "type": "string", "format": "byte", - "title": "Peer to which the message will be sent. Represented as a byte-encoded\npublic key" + "title": "Peer to send the message to" }, "type": { "type": "integer", @@ -7638,32 +7592,125 @@ } } }, - "lnrpcSendOnionMessageRequest": { + "lnrpcSendRequest": { "type": "object", "properties": { - "peer": { + "dest": { "type": "string", "format": "byte", - "title": "Peer to send the message to" + "description": "The identity pubkey of the payment recipient. When using REST, this field\nmust be encoded as base64." }, - "path_key": { + "dest_string": { "type": "string", - "format": "byte", - "description": "PathKey is used to derive the blinded node id by tweaking the hop's\nstatic public key. The hop uses the corresponding blinded private key\ntogether with the sender's ephemeral key to perform ECDH and obtain the\nshared secret for decrypting the onion payload. Separately, for\ndecrypting `encrypted_recipient_data`, the recipient performs ECDH\nbetween its static node private key and the path_key to derive the\ndecryption key." + "description": "The hex-encoded identity pubkey of the payment recipient. Deprecated now\nthat the REST gateway supports base64 encoding of bytes fields." }, - "onion": { + "amt": { + "type": "string", + "format": "int64", + "description": "The amount to send expressed in satoshis.\n\nThe fields amt and amt_msat are mutually exclusive." + }, + "amt_msat": { + "type": "string", + "format": "int64", + "description": "The amount to send expressed in millisatoshis.\n\nThe fields amt and amt_msat are mutually exclusive." + }, + "payment_hash": { "type": "string", "format": "byte", - "description": "Serialized Sphinx onion packet (BOLT 4) containing the layered, per-hop\nencrypted payloads and routing instructions used to forward this message\nalong its designated path." + "description": "The hash to use within the payment's HTLC. When using REST, this field\nmust be encoded as base64." + }, + "payment_hash_string": { + "type": "string", + "description": "The hex-encoded hash to use within the payment's HTLC. Deprecated now\nthat the REST gateway supports base64 encoding of bytes fields." + }, + "payment_request": { + "type": "string", + "description": "A bare-bones invoice for a payment within the Lightning Network. With the\ndetails of the invoice, the sender has all the data necessary to send a\npayment to the recipient." + }, + "final_cltv_delta": { + "type": "integer", + "format": "int32", + "description": "The CLTV delta from the current height that should be used to set the\ntimelock for the final hop." + }, + "fee_limit": { + "$ref": "#/definitions/lnrpcFeeLimit", + "description": "The maximum number of satoshis that will be paid as a fee of the payment.\nThis value can be represented either as a percentage of the amount being\nsent, or as a fixed amount of the maximum fee the user is willing the pay to\nsend the payment. If not specified, lnd will use a default value of 100%\nfees for small amounts (\u003c=1k sat) or 5% fees for larger amounts." + }, + "outgoing_chan_id": { + "type": "string", + "format": "uint64", + "description": "The channel id of the channel that must be taken to the first hop. If zero,\nany channel may be used." + }, + "last_hop_pubkey": { + "type": "string", + "format": "byte", + "description": "The pubkey of the last hop of the route. If empty, any hop may be used." + }, + "cltv_limit": { + "type": "integer", + "format": "int64", + "description": "An optional maximum total time lock for the route. This should not exceed\nlnd's `--max-cltv-expiry` setting. If zero, then the value of\n`--max-cltv-expiry` is enforced." + }, + "dest_custom_records": { + "type": "object", + "additionalProperties": { + "type": "string", + "format": "byte" + }, + "description": "An optional field that can be used to pass an arbitrary set of TLV records\nto a peer which understands the new records. This can be used to pass\napplication specific data during the payment attempt. Record types are\nrequired to be in the custom range \u003e= 65536. When using REST, the values\nmust be encoded as base64." + }, + "allow_self_payment": { + "type": "boolean", + "description": "If set, circular payments to self are permitted." + }, + "dest_features": { + "type": "array", + "items": { + "$ref": "#/definitions/lnrpcFeatureBit" + }, + "description": "Features assumed to be supported by the final node. All transitive feature\ndependencies must also be set properly. For a given feature bit pair, either\noptional or remote may be set, but not both. If this field is nil or empty,\nthe router will try to load destination features from the graph as a\nfallback." + }, + "payment_addr": { + "type": "string", + "format": "byte", + "description": "The payment address of the generated invoice. This is also called\npayment secret in specifications (e.g. BOLT 11)." } } }, - "lnrpcSendOnionMessageResponse": { + "lnrpcSendResponse": { "type": "object", "properties": { - "status": { + "payment_error": { + "type": "string" + }, + "payment_preimage": { "type": "string", - "description": "The status of the onion message send operation." + "format": "byte" + }, + "payment_route": { + "$ref": "#/definitions/lnrpcRoute" + }, + "payment_hash": { + "type": "string", + "format": "byte" + } + } + }, + "lnrpcSendToRouteRequest": { + "type": "object", + "properties": { + "payment_hash": { + "type": "string", + "format": "byte", + "description": "The payment hash to use for the HTLC. When using REST, this field must be\nencoded as base64." + }, + "payment_hash_string": { + "type": "string", + "description": "An optional hex-encoded payment hash to be used for the HTLC. Deprecated now\nthat the REST gateway supports base64 encoding of bytes fields." + }, + "route": { + "$ref": "#/definitions/lnrpcRoute", + "description": "Route that should be used to attempt to complete the payment." } } }, diff --git a/lnrpc/lightning.yaml b/lnrpc/lightning.yaml index 079271714..7bf186c6c 100644 --- a/lnrpc/lightning.yaml +++ b/lnrpc/lightning.yaml @@ -75,6 +75,17 @@ http: delete: "/v1/channels/{channel_point.funding_txid_str}/{channel_point.output_index}" - selector: lnrpc.Lightning.AbandonChannel delete: "/v1/channels/abandon/{channel_point.funding_txid_str}/{channel_point.output_index}" + - selector: lnrpc.Lightning.SendPayment + post: "/v1/channels/transaction-stream" + body: "*" + - selector: lnrpc.Lightning.SendPaymentSync + post: "/v1/channels/transactions" + body: "*" + - selector: lnrpc.Lightning.SendToRoute + # deprecated, no REST endpoint + - selector: lnrpc.Lightning.SendToRouteSync + post: "/v1/channels/transactions/route" + body: "*" - selector: lnrpc.Lightning.AddInvoice post: "/v1/invoices" body: "*" @@ -155,12 +166,7 @@ http: post: "/v1/custommessage" body: "*" - selector: lnrpc.Lightning.SubscribeCustomMessages - get: "/v1/custommessage/subscribe" - - selector: lnrpc.Lightning.SendOnionMessage - post: "/v1/onionmessage" - body: "*" - - selector: lnrpc.Lightning.SubscribeOnionMessages - get: "/v1/onionmessage/subscribe" + get: "/v1/custommessage/subscribe" - selector: lnrpc.Lightning.ListAliases get: "/v1/aliases/list" - selector: lnrpc.Lightning.LookupHtlcResolution diff --git a/lnrpc/lightning_grpc.pb.go b/lnrpc/lightning_grpc.pb.go index f0fb70d1d..bedfa8d8d 100644 --- a/lnrpc/lightning_grpc.pb.go +++ b/lnrpc/lightning_grpc.pb.go @@ -185,6 +185,35 @@ type LightningClient interface { // never broadcast. Only available for non-externally funded channels in dev // build. AbandonChannel(ctx context.Context, in *AbandonChannelRequest, opts ...grpc.CallOption) (*AbandonChannelResponse, error) + // Deprecated: Do not use. + // lncli: `sendpayment` + // Deprecated, use routerrpc.SendPaymentV2. SendPayment dispatches a + // bi-directional streaming RPC for sending payments through the Lightning + // Network. A single RPC invocation creates a persistent bi-directional + // stream allowing clients to rapidly send payments through the Lightning + // Network with a single persistent connection. + SendPayment(ctx context.Context, opts ...grpc.CallOption) (Lightning_SendPaymentClient, error) + // Deprecated: Do not use. + // + // Deprecated, use routerrpc.SendPaymentV2. SendPaymentSync is the synchronous + // non-streaming version of SendPayment. This RPC is intended to be consumed by + // clients of the REST proxy. Additionally, this RPC expects the destination's + // public key and the payment hash (if any) to be encoded as hex strings. + SendPaymentSync(ctx context.Context, in *SendRequest, opts ...grpc.CallOption) (*SendResponse, error) + // Deprecated: Do not use. + // lncli: `sendtoroute` + // Deprecated, use routerrpc.SendToRouteV2. SendToRoute is a bi-directional + // streaming RPC for sending payment through the Lightning Network. This + // method differs from SendPayment in that it allows users to specify a full + // route manually. This can be used for things like rebalancing, and atomic + // swaps. + SendToRoute(ctx context.Context, opts ...grpc.CallOption) (Lightning_SendToRouteClient, error) + // Deprecated: Do not use. + // + // Deprecated, use routerrpc.SendToRouteV2. SendToRouteSync is a synchronous + // version of SendToRoute. It Will block until the payment either fails or + // succeeds. + SendToRouteSync(ctx context.Context, in *SendToRouteRequest, opts ...grpc.CallOption) (*SendResponse, error) // lncli: `addinvoice` // AddInvoice attempts to add a new invoice to the invoice database. Any // duplicated invoices are rejected, therefore all invoices *must* have a @@ -388,12 +417,6 @@ type LightningClient interface { // needs to be compiled with the `dev` build tag, and the message type to // override should be specified in lnd's experimental protocol configuration. SubscribeCustomMessages(ctx context.Context, in *SubscribeCustomMessagesRequest, opts ...grpc.CallOption) (Lightning_SubscribeCustomMessagesClient, error) - // lncli: `sendonion` - // SendOnionMessage sends an onion message to a peer. - SendOnionMessage(ctx context.Context, in *SendOnionMessageRequest, opts ...grpc.CallOption) (*SendOnionMessageResponse, error) - // lncli: `subscribeonion` - // SubscribeOnionMessages subscribes to a stream of incoming onion messages. - SubscribeOnionMessages(ctx context.Context, in *SubscribeOnionMessagesRequest, opts ...grpc.CallOption) (Lightning_SubscribeOnionMessagesClient, error) // lncli: `listaliases` // ListAliases returns the set of all aliases that have ever existed with // their confirmed SCID (if it exists) and/or the base SCID (in the case of @@ -811,6 +834,90 @@ func (c *lightningClient) AbandonChannel(ctx context.Context, in *AbandonChannel return out, nil } +// Deprecated: Do not use. +func (c *lightningClient) SendPayment(ctx context.Context, opts ...grpc.CallOption) (Lightning_SendPaymentClient, error) { + stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[6], "/lnrpc.Lightning/SendPayment", opts...) + if err != nil { + return nil, err + } + x := &lightningSendPaymentClient{stream} + return x, nil +} + +type Lightning_SendPaymentClient interface { + Send(*SendRequest) error + Recv() (*SendResponse, error) + grpc.ClientStream +} + +type lightningSendPaymentClient struct { + grpc.ClientStream +} + +func (x *lightningSendPaymentClient) Send(m *SendRequest) error { + return x.ClientStream.SendMsg(m) +} + +func (x *lightningSendPaymentClient) Recv() (*SendResponse, error) { + m := new(SendResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// Deprecated: Do not use. +func (c *lightningClient) SendPaymentSync(ctx context.Context, in *SendRequest, opts ...grpc.CallOption) (*SendResponse, error) { + out := new(SendResponse) + err := c.cc.Invoke(ctx, "/lnrpc.Lightning/SendPaymentSync", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Deprecated: Do not use. +func (c *lightningClient) SendToRoute(ctx context.Context, opts ...grpc.CallOption) (Lightning_SendToRouteClient, error) { + stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[7], "/lnrpc.Lightning/SendToRoute", opts...) + if err != nil { + return nil, err + } + x := &lightningSendToRouteClient{stream} + return x, nil +} + +type Lightning_SendToRouteClient interface { + Send(*SendToRouteRequest) error + Recv() (*SendResponse, error) + grpc.ClientStream +} + +type lightningSendToRouteClient struct { + grpc.ClientStream +} + +func (x *lightningSendToRouteClient) Send(m *SendToRouteRequest) error { + return x.ClientStream.SendMsg(m) +} + +func (x *lightningSendToRouteClient) Recv() (*SendResponse, error) { + m := new(SendResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// Deprecated: Do not use. +func (c *lightningClient) SendToRouteSync(ctx context.Context, in *SendToRouteRequest, opts ...grpc.CallOption) (*SendResponse, error) { + out := new(SendResponse) + err := c.cc.Invoke(ctx, "/lnrpc.Lightning/SendToRouteSync", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *lightningClient) AddInvoice(ctx context.Context, in *Invoice, opts ...grpc.CallOption) (*AddInvoiceResponse, error) { out := new(AddInvoiceResponse) err := c.cc.Invoke(ctx, "/lnrpc.Lightning/AddInvoice", in, out, opts...) @@ -839,7 +946,7 @@ func (c *lightningClient) LookupInvoice(ctx context.Context, in *PaymentHash, op } func (c *lightningClient) SubscribeInvoices(ctx context.Context, in *InvoiceSubscription, opts ...grpc.CallOption) (Lightning_SubscribeInvoicesClient, error) { - stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[6], "/lnrpc.Lightning/SubscribeInvoices", opts...) + stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[8], "/lnrpc.Lightning/SubscribeInvoices", opts...) if err != nil { return nil, err } @@ -979,7 +1086,7 @@ func (c *lightningClient) StopDaemon(ctx context.Context, in *StopRequest, opts } func (c *lightningClient) SubscribeChannelGraph(ctx context.Context, in *GraphTopologySubscription, opts ...grpc.CallOption) (Lightning_SubscribeChannelGraphClient, error) { - stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[7], "/lnrpc.Lightning/SubscribeChannelGraph", opts...) + stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[9], "/lnrpc.Lightning/SubscribeChannelGraph", opts...) if err != nil { return nil, err } @@ -1083,7 +1190,7 @@ func (c *lightningClient) RestoreChannelBackups(ctx context.Context, in *Restore } func (c *lightningClient) SubscribeChannelBackups(ctx context.Context, in *ChannelBackupSubscription, opts ...grpc.CallOption) (Lightning_SubscribeChannelBackupsClient, error) { - stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[8], "/lnrpc.Lightning/SubscribeChannelBackups", opts...) + stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[10], "/lnrpc.Lightning/SubscribeChannelBackups", opts...) if err != nil { return nil, err } @@ -1160,7 +1267,7 @@ func (c *lightningClient) CheckMacaroonPermissions(ctx context.Context, in *Chec } func (c *lightningClient) RegisterRPCMiddleware(ctx context.Context, opts ...grpc.CallOption) (Lightning_RegisterRPCMiddlewareClient, error) { - stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[9], "/lnrpc.Lightning/RegisterRPCMiddleware", opts...) + stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[11], "/lnrpc.Lightning/RegisterRPCMiddleware", opts...) if err != nil { return nil, err } @@ -1200,7 +1307,7 @@ func (c *lightningClient) SendCustomMessage(ctx context.Context, in *SendCustomM } func (c *lightningClient) SubscribeCustomMessages(ctx context.Context, in *SubscribeCustomMessagesRequest, opts ...grpc.CallOption) (Lightning_SubscribeCustomMessagesClient, error) { - stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[10], "/lnrpc.Lightning/SubscribeCustomMessages", opts...) + stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[12], "/lnrpc.Lightning/SubscribeCustomMessages", opts...) if err != nil { return nil, err } @@ -1231,47 +1338,6 @@ func (x *lightningSubscribeCustomMessagesClient) Recv() (*CustomMessage, error) return m, nil } -func (c *lightningClient) SendOnionMessage(ctx context.Context, in *SendOnionMessageRequest, opts ...grpc.CallOption) (*SendOnionMessageResponse, error) { - out := new(SendOnionMessageResponse) - err := c.cc.Invoke(ctx, "/lnrpc.Lightning/SendOnionMessage", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *lightningClient) SubscribeOnionMessages(ctx context.Context, in *SubscribeOnionMessagesRequest, opts ...grpc.CallOption) (Lightning_SubscribeOnionMessagesClient, error) { - stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[11], "/lnrpc.Lightning/SubscribeOnionMessages", opts...) - if err != nil { - return nil, err - } - x := &lightningSubscribeOnionMessagesClient{stream} - if err := x.ClientStream.SendMsg(in); err != nil { - return nil, err - } - if err := x.ClientStream.CloseSend(); err != nil { - return nil, err - } - return x, nil -} - -type Lightning_SubscribeOnionMessagesClient interface { - Recv() (*OnionMessageUpdate, error) - grpc.ClientStream -} - -type lightningSubscribeOnionMessagesClient struct { - grpc.ClientStream -} - -func (x *lightningSubscribeOnionMessagesClient) Recv() (*OnionMessageUpdate, error) { - m := new(OnionMessageUpdate) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - func (c *lightningClient) ListAliases(ctx context.Context, in *ListAliasesRequest, opts ...grpc.CallOption) (*ListAliasesResponse, error) { out := new(ListAliasesResponse) err := c.cc.Invoke(ctx, "/lnrpc.Lightning/ListAliases", in, out, opts...) @@ -1461,6 +1527,35 @@ type LightningServer interface { // never broadcast. Only available for non-externally funded channels in dev // build. AbandonChannel(context.Context, *AbandonChannelRequest) (*AbandonChannelResponse, error) + // Deprecated: Do not use. + // lncli: `sendpayment` + // Deprecated, use routerrpc.SendPaymentV2. SendPayment dispatches a + // bi-directional streaming RPC for sending payments through the Lightning + // Network. A single RPC invocation creates a persistent bi-directional + // stream allowing clients to rapidly send payments through the Lightning + // Network with a single persistent connection. + SendPayment(Lightning_SendPaymentServer) error + // Deprecated: Do not use. + // + // Deprecated, use routerrpc.SendPaymentV2. SendPaymentSync is the synchronous + // non-streaming version of SendPayment. This RPC is intended to be consumed by + // clients of the REST proxy. Additionally, this RPC expects the destination's + // public key and the payment hash (if any) to be encoded as hex strings. + SendPaymentSync(context.Context, *SendRequest) (*SendResponse, error) + // Deprecated: Do not use. + // lncli: `sendtoroute` + // Deprecated, use routerrpc.SendToRouteV2. SendToRoute is a bi-directional + // streaming RPC for sending payment through the Lightning Network. This + // method differs from SendPayment in that it allows users to specify a full + // route manually. This can be used for things like rebalancing, and atomic + // swaps. + SendToRoute(Lightning_SendToRouteServer) error + // Deprecated: Do not use. + // + // Deprecated, use routerrpc.SendToRouteV2. SendToRouteSync is a synchronous + // version of SendToRoute. It Will block until the payment either fails or + // succeeds. + SendToRouteSync(context.Context, *SendToRouteRequest) (*SendResponse, error) // lncli: `addinvoice` // AddInvoice attempts to add a new invoice to the invoice database. Any // duplicated invoices are rejected, therefore all invoices *must* have a @@ -1664,12 +1759,6 @@ type LightningServer interface { // needs to be compiled with the `dev` build tag, and the message type to // override should be specified in lnd's experimental protocol configuration. SubscribeCustomMessages(*SubscribeCustomMessagesRequest, Lightning_SubscribeCustomMessagesServer) error - // lncli: `sendonion` - // SendOnionMessage sends an onion message to a peer. - SendOnionMessage(context.Context, *SendOnionMessageRequest) (*SendOnionMessageResponse, error) - // lncli: `subscribeonion` - // SubscribeOnionMessages subscribes to a stream of incoming onion messages. - SubscribeOnionMessages(*SubscribeOnionMessagesRequest, Lightning_SubscribeOnionMessagesServer) error // lncli: `listaliases` // ListAliases returns the set of all aliases that have ever existed with // their confirmed SCID (if it exists) and/or the base SCID (in the case of @@ -1773,6 +1862,18 @@ func (UnimplementedLightningServer) CloseChannel(*CloseChannelRequest, Lightning func (UnimplementedLightningServer) AbandonChannel(context.Context, *AbandonChannelRequest) (*AbandonChannelResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method AbandonChannel not implemented") } +func (UnimplementedLightningServer) SendPayment(Lightning_SendPaymentServer) error { + return status.Errorf(codes.Unimplemented, "method SendPayment not implemented") +} +func (UnimplementedLightningServer) SendPaymentSync(context.Context, *SendRequest) (*SendResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SendPaymentSync not implemented") +} +func (UnimplementedLightningServer) SendToRoute(Lightning_SendToRouteServer) error { + return status.Errorf(codes.Unimplemented, "method SendToRoute not implemented") +} +func (UnimplementedLightningServer) SendToRouteSync(context.Context, *SendToRouteRequest) (*SendResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SendToRouteSync not implemented") +} func (UnimplementedLightningServer) AddInvoice(context.Context, *Invoice) (*AddInvoiceResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method AddInvoice not implemented") } @@ -1875,12 +1976,6 @@ func (UnimplementedLightningServer) SendCustomMessage(context.Context, *SendCust func (UnimplementedLightningServer) SubscribeCustomMessages(*SubscribeCustomMessagesRequest, Lightning_SubscribeCustomMessagesServer) error { return status.Errorf(codes.Unimplemented, "method SubscribeCustomMessages not implemented") } -func (UnimplementedLightningServer) SendOnionMessage(context.Context, *SendOnionMessageRequest) (*SendOnionMessageResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method SendOnionMessage not implemented") -} -func (UnimplementedLightningServer) SubscribeOnionMessages(*SubscribeOnionMessagesRequest, Lightning_SubscribeOnionMessagesServer) error { - return status.Errorf(codes.Unimplemented, "method SubscribeOnionMessages not implemented") -} func (UnimplementedLightningServer) ListAliases(context.Context, *ListAliasesRequest) (*ListAliasesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListAliases not implemented") } @@ -2445,6 +2540,94 @@ func _Lightning_AbandonChannel_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _Lightning_SendPayment_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(LightningServer).SendPayment(&lightningSendPaymentServer{stream}) +} + +type Lightning_SendPaymentServer interface { + Send(*SendResponse) error + Recv() (*SendRequest, error) + grpc.ServerStream +} + +type lightningSendPaymentServer struct { + grpc.ServerStream +} + +func (x *lightningSendPaymentServer) Send(m *SendResponse) error { + return x.ServerStream.SendMsg(m) +} + +func (x *lightningSendPaymentServer) Recv() (*SendRequest, error) { + m := new(SendRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _Lightning_SendPaymentSync_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SendRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LightningServer).SendPaymentSync(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lnrpc.Lightning/SendPaymentSync", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LightningServer).SendPaymentSync(ctx, req.(*SendRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Lightning_SendToRoute_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(LightningServer).SendToRoute(&lightningSendToRouteServer{stream}) +} + +type Lightning_SendToRouteServer interface { + Send(*SendResponse) error + Recv() (*SendToRouteRequest, error) + grpc.ServerStream +} + +type lightningSendToRouteServer struct { + grpc.ServerStream +} + +func (x *lightningSendToRouteServer) Send(m *SendResponse) error { + return x.ServerStream.SendMsg(m) +} + +func (x *lightningSendToRouteServer) Recv() (*SendToRouteRequest, error) { + m := new(SendToRouteRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _Lightning_SendToRouteSync_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SendToRouteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LightningServer).SendToRouteSync(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lnrpc.Lightning/SendToRouteSync", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LightningServer).SendToRouteSync(ctx, req.(*SendToRouteRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Lightning_AddInvoice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Invoice) if err := dec(in); err != nil { @@ -3077,45 +3260,6 @@ func (x *lightningSubscribeCustomMessagesServer) Send(m *CustomMessage) error { return x.ServerStream.SendMsg(m) } -func _Lightning_SendOnionMessage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(SendOnionMessageRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(LightningServer).SendOnionMessage(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/lnrpc.Lightning/SendOnionMessage", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(LightningServer).SendOnionMessage(ctx, req.(*SendOnionMessageRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Lightning_SubscribeOnionMessages_Handler(srv interface{}, stream grpc.ServerStream) error { - m := new(SubscribeOnionMessagesRequest) - if err := stream.RecvMsg(m); err != nil { - return err - } - return srv.(LightningServer).SubscribeOnionMessages(m, &lightningSubscribeOnionMessagesServer{stream}) -} - -type Lightning_SubscribeOnionMessagesServer interface { - Send(*OnionMessageUpdate) error - grpc.ServerStream -} - -type lightningSubscribeOnionMessagesServer struct { - grpc.ServerStream -} - -func (x *lightningSubscribeOnionMessagesServer) Send(m *OnionMessageUpdate) error { - return x.ServerStream.SendMsg(m) -} - func _Lightning_ListAliases_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListAliasesRequest) if err := dec(in); err != nil { @@ -3251,6 +3395,14 @@ var Lightning_ServiceDesc = grpc.ServiceDesc{ MethodName: "AbandonChannel", Handler: _Lightning_AbandonChannel_Handler, }, + { + MethodName: "SendPaymentSync", + Handler: _Lightning_SendPaymentSync_Handler, + }, + { + MethodName: "SendToRouteSync", + Handler: _Lightning_SendToRouteSync_Handler, + }, { MethodName: "AddInvoice", Handler: _Lightning_AddInvoice_Handler, @@ -3367,10 +3519,6 @@ var Lightning_ServiceDesc = grpc.ServiceDesc{ MethodName: "SendCustomMessage", Handler: _Lightning_SendCustomMessage_Handler, }, - { - MethodName: "SendOnionMessage", - Handler: _Lightning_SendOnionMessage_Handler, - }, { MethodName: "ListAliases", Handler: _Lightning_ListAliases_Handler, @@ -3412,6 +3560,18 @@ var Lightning_ServiceDesc = grpc.ServiceDesc{ Handler: _Lightning_CloseChannel_Handler, ServerStreams: true, }, + { + StreamName: "SendPayment", + Handler: _Lightning_SendPayment_Handler, + ServerStreams: true, + ClientStreams: true, + }, + { + StreamName: "SendToRoute", + Handler: _Lightning_SendToRoute_Handler, + ServerStreams: true, + ClientStreams: true, + }, { StreamName: "SubscribeInvoices", Handler: _Lightning_SubscribeInvoices_Handler, @@ -3438,11 +3598,6 @@ var Lightning_ServiceDesc = grpc.ServiceDesc{ Handler: _Lightning_SubscribeCustomMessages_Handler, ServerStreams: true, }, - { - StreamName: "SubscribeOnionMessages", - Handler: _Lightning_SubscribeOnionMessages_Handler, - ServerStreams: true, - }, }, Metadata: "lightning.proto", } diff --git a/lnrpc/lnclipb/lncli.pb.go b/lnrpc/lnclipb/lncli.pb.go index 1f34e363d..94b28ad1f 100644 --- a/lnrpc/lnclipb/lncli.pb.go +++ b/lnrpc/lnclipb/lncli.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.33.0 // protoc v3.21.12 // source: lnclipb/lncli.proto @@ -12,7 +12,6 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" - unsafe "unsafe" ) const ( @@ -23,20 +22,23 @@ const ( ) type VersionResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The version information for lncli. Lncli *verrpc.Version `protobuf:"bytes,1,opt,name=lncli,proto3" json:"lncli,omitempty"` // The version information for lnd. - Lnd *verrpc.Version `protobuf:"bytes,2,opt,name=lnd,proto3" json:"lnd,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Lnd *verrpc.Version `protobuf:"bytes,2,opt,name=lnd,proto3" json:"lnd,omitempty"` } func (x *VersionResponse) Reset() { *x = VersionResponse{} - mi := &file_lnclipb_lncli_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_lnclipb_lncli_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *VersionResponse) String() string { @@ -47,7 +49,7 @@ func (*VersionResponse) ProtoMessage() {} func (x *VersionResponse) ProtoReflect() protoreflect.Message { mi := &file_lnclipb_lncli_proto_msgTypes[0] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -78,27 +80,36 @@ func (x *VersionResponse) GetLnd() *verrpc.Version { var File_lnclipb_lncli_proto protoreflect.FileDescriptor -const file_lnclipb_lncli_proto_rawDesc = "" + - "\n" + - "\x13lnclipb/lncli.proto\x12\alnclipb\x1a\x13verrpc/verrpc.proto\"[\n" + - "\x0fVersionResponse\x12%\n" + - "\x05lncli\x18\x01 \x01(\v2\x0f.verrpc.VersionR\x05lncli\x12!\n" + - "\x03lnd\x18\x02 \x01(\v2\x0f.verrpc.VersionR\x03lndB/Z-github.com/lightningnetwork/lnd/lnrpc/lnclipbb\x06proto3" +var file_lnclipb_lncli_proto_rawDesc = []byte{ + 0x0a, 0x13, 0x6c, 0x6e, 0x63, 0x6c, 0x69, 0x70, 0x62, 0x2f, 0x6c, 0x6e, 0x63, 0x6c, 0x69, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x6c, 0x6e, 0x63, 0x6c, 0x69, 0x70, 0x62, 0x1a, 0x13, + 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2f, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0x5b, 0x0a, 0x0f, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x05, 0x6c, 0x6e, 0x63, 0x6c, 0x69, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x6c, 0x6e, 0x63, 0x6c, 0x69, 0x12, 0x21, 0x0a, + 0x03, 0x6c, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x65, 0x72, + 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x6c, 0x6e, 0x64, + 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, + 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, + 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x6c, 0x6e, 0x63, 0x6c, 0x69, 0x70, + 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} var ( file_lnclipb_lncli_proto_rawDescOnce sync.Once - file_lnclipb_lncli_proto_rawDescData []byte + file_lnclipb_lncli_proto_rawDescData = file_lnclipb_lncli_proto_rawDesc ) func file_lnclipb_lncli_proto_rawDescGZIP() []byte { file_lnclipb_lncli_proto_rawDescOnce.Do(func() { - file_lnclipb_lncli_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_lnclipb_lncli_proto_rawDesc), len(file_lnclipb_lncli_proto_rawDesc))) + file_lnclipb_lncli_proto_rawDescData = protoimpl.X.CompressGZIP(file_lnclipb_lncli_proto_rawDescData) }) return file_lnclipb_lncli_proto_rawDescData } var file_lnclipb_lncli_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_lnclipb_lncli_proto_goTypes = []any{ +var file_lnclipb_lncli_proto_goTypes = []interface{}{ (*VersionResponse)(nil), // 0: lnclipb.VersionResponse (*verrpc.Version)(nil), // 1: verrpc.Version } @@ -117,11 +128,25 @@ func file_lnclipb_lncli_proto_init() { if File_lnclipb_lncli_proto != nil { return } + if !protoimpl.UnsafeEnabled { + file_lnclipb_lncli_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VersionResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_lnclipb_lncli_proto_rawDesc), len(file_lnclipb_lncli_proto_rawDesc)), + RawDescriptor: file_lnclipb_lncli_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, @@ -132,6 +157,7 @@ func file_lnclipb_lncli_proto_init() { MessageInfos: file_lnclipb_lncli_proto_msgTypes, }.Build() File_lnclipb_lncli_proto = out.File + file_lnclipb_lncli_proto_rawDesc = nil file_lnclipb_lncli_proto_goTypes = nil file_lnclipb_lncli_proto_depIdxs = nil } diff --git a/lnrpc/marshall_utils.go b/lnrpc/marshall_utils.go index 04cc2c339..05a9e9a90 100644 --- a/lnrpc/marshall_utils.go +++ b/lnrpc/marshall_utils.go @@ -7,10 +7,10 @@ import ( "maps" "slices" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/wallet" "github.com/lightningnetwork/lnd/aliasmgr" "github.com/lightningnetwork/lnd/fn/v2" diff --git a/lnrpc/metadata.go b/lnrpc/metadata.go index 7336b1da4..a6e89949b 100644 --- a/lnrpc/metadata.go +++ b/lnrpc/metadata.go @@ -11,6 +11,7 @@ var ( // runtime so we need to keep a hard coded list here. LndClientStreamingURIs = []*regexp.Regexp{ regexp.MustCompile("^/v1/channels/acceptor$"), + regexp.MustCompile("^/v1/channels/transaction-stream$"), regexp.MustCompile("^/v2/router/htlcinterceptor$"), regexp.MustCompile("^/v1/middleware$"), } diff --git a/lnrpc/neutrinorpc/neutrino.pb.go b/lnrpc/neutrinorpc/neutrino.pb.go index a48378cfb..0879e22a5 100644 --- a/lnrpc/neutrinorpc/neutrino.pb.go +++ b/lnrpc/neutrinorpc/neutrino.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.33.0 // protoc v3.21.12 // source: neutrinorpc/neutrino.proto @@ -11,7 +11,6 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" - unsafe "unsafe" ) const ( @@ -22,16 +21,18 @@ const ( ) type StatusRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *StatusRequest) Reset() { *x = StatusRequest{} - mi := &file_neutrinorpc_neutrino_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *StatusRequest) String() string { @@ -42,7 +43,7 @@ func (*StatusRequest) ProtoMessage() {} func (x *StatusRequest) ProtoReflect() protoreflect.Message { mi := &file_neutrinorpc_neutrino_proto_msgTypes[0] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -58,7 +59,10 @@ func (*StatusRequest) Descriptor() ([]byte, []int) { } type StatusResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Indicates whether the neutrino backend is active or not. Active bool `protobuf:"varint,1,opt,name=active,proto3" json:"active,omitempty"` // Is fully synced. @@ -68,16 +72,16 @@ type StatusResponse struct { // Best block hash. BlockHash string `protobuf:"bytes,4,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"` // Connected peers. - Peers []string `protobuf:"bytes,5,rep,name=peers,proto3" json:"peers,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Peers []string `protobuf:"bytes,5,rep,name=peers,proto3" json:"peers,omitempty"` } func (x *StatusResponse) Reset() { *x = StatusResponse{} - mi := &file_neutrinorpc_neutrino_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *StatusResponse) String() string { @@ -88,7 +92,7 @@ func (*StatusResponse) ProtoMessage() {} func (x *StatusResponse) ProtoReflect() protoreflect.Message { mi := &file_neutrinorpc_neutrino_proto_msgTypes[1] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -139,18 +143,21 @@ func (x *StatusResponse) GetPeers() []string { } type AddPeerRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Peer to add. - PeerAddrs string `protobuf:"bytes,1,opt,name=peer_addrs,json=peerAddrs,proto3" json:"peer_addrs,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Peer to add. + PeerAddrs string `protobuf:"bytes,1,opt,name=peer_addrs,json=peerAddrs,proto3" json:"peer_addrs,omitempty"` } func (x *AddPeerRequest) Reset() { *x = AddPeerRequest{} - mi := &file_neutrinorpc_neutrino_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *AddPeerRequest) String() string { @@ -161,7 +168,7 @@ func (*AddPeerRequest) ProtoMessage() {} func (x *AddPeerRequest) ProtoReflect() protoreflect.Message { mi := &file_neutrinorpc_neutrino_proto_msgTypes[2] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -184,16 +191,18 @@ func (x *AddPeerRequest) GetPeerAddrs() string { } type AddPeerResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *AddPeerResponse) Reset() { *x = AddPeerResponse{} - mi := &file_neutrinorpc_neutrino_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *AddPeerResponse) String() string { @@ -204,7 +213,7 @@ func (*AddPeerResponse) ProtoMessage() {} func (x *AddPeerResponse) ProtoReflect() protoreflect.Message { mi := &file_neutrinorpc_neutrino_proto_msgTypes[3] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -220,18 +229,21 @@ func (*AddPeerResponse) Descriptor() ([]byte, []int) { } type DisconnectPeerRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Peer to disconnect. - PeerAddrs string `protobuf:"bytes,1,opt,name=peer_addrs,json=peerAddrs,proto3" json:"peer_addrs,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Peer to disconnect. + PeerAddrs string `protobuf:"bytes,1,opt,name=peer_addrs,json=peerAddrs,proto3" json:"peer_addrs,omitempty"` } func (x *DisconnectPeerRequest) Reset() { *x = DisconnectPeerRequest{} - mi := &file_neutrinorpc_neutrino_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *DisconnectPeerRequest) String() string { @@ -242,7 +254,7 @@ func (*DisconnectPeerRequest) ProtoMessage() {} func (x *DisconnectPeerRequest) ProtoReflect() protoreflect.Message { mi := &file_neutrinorpc_neutrino_proto_msgTypes[4] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -265,16 +277,18 @@ func (x *DisconnectPeerRequest) GetPeerAddrs() string { } type DisconnectPeerResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *DisconnectPeerResponse) Reset() { *x = DisconnectPeerResponse{} - mi := &file_neutrinorpc_neutrino_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *DisconnectPeerResponse) String() string { @@ -285,7 +299,7 @@ func (*DisconnectPeerResponse) ProtoMessage() {} func (x *DisconnectPeerResponse) ProtoReflect() protoreflect.Message { mi := &file_neutrinorpc_neutrino_proto_msgTypes[5] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -301,18 +315,21 @@ func (*DisconnectPeerResponse) Descriptor() ([]byte, []int) { } type IsBannedRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Peer to lookup. - PeerAddrs string `protobuf:"bytes,1,opt,name=peer_addrs,json=peerAddrs,proto3" json:"peer_addrs,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Peer to lookup. + PeerAddrs string `protobuf:"bytes,1,opt,name=peer_addrs,json=peerAddrs,proto3" json:"peer_addrs,omitempty"` } func (x *IsBannedRequest) Reset() { *x = IsBannedRequest{} - mi := &file_neutrinorpc_neutrino_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *IsBannedRequest) String() string { @@ -323,7 +340,7 @@ func (*IsBannedRequest) ProtoMessage() {} func (x *IsBannedRequest) ProtoReflect() protoreflect.Message { mi := &file_neutrinorpc_neutrino_proto_msgTypes[6] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -346,17 +363,20 @@ func (x *IsBannedRequest) GetPeerAddrs() string { } type IsBannedResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Banned bool `protobuf:"varint,1,opt,name=banned,proto3" json:"banned,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Banned bool `protobuf:"varint,1,opt,name=banned,proto3" json:"banned,omitempty"` } func (x *IsBannedResponse) Reset() { *x = IsBannedResponse{} - mi := &file_neutrinorpc_neutrino_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *IsBannedResponse) String() string { @@ -367,7 +387,7 @@ func (*IsBannedResponse) ProtoMessage() {} func (x *IsBannedResponse) ProtoReflect() protoreflect.Message { mi := &file_neutrinorpc_neutrino_proto_msgTypes[7] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -390,18 +410,21 @@ func (x *IsBannedResponse) GetBanned() bool { } type GetBlockHeaderRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Block hash in hex notation. - Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Block hash in hex notation. + Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` } func (x *GetBlockHeaderRequest) Reset() { *x = GetBlockHeaderRequest{} - mi := &file_neutrinorpc_neutrino_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetBlockHeaderRequest) String() string { @@ -412,7 +435,7 @@ func (*GetBlockHeaderRequest) ProtoMessage() {} func (x *GetBlockHeaderRequest) ProtoReflect() protoreflect.Message { mi := &file_neutrinorpc_neutrino_proto_msgTypes[8] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -435,7 +458,10 @@ func (x *GetBlockHeaderRequest) GetHash() string { } type GetBlockHeaderResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The block hash (same as provided). Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` // The number of confirmations. @@ -465,16 +491,16 @@ type GetBlockHeaderResponse struct { // The hash of the previous block. PreviousBlockHash string `protobuf:"bytes,14,opt,name=previous_block_hash,json=previousBlockHash,proto3" json:"previous_block_hash,omitempty"` // The raw hex of the block. - RawHex []byte `protobuf:"bytes,15,opt,name=raw_hex,json=rawHex,proto3" json:"raw_hex,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + RawHex []byte `protobuf:"bytes,15,opt,name=raw_hex,json=rawHex,proto3" json:"raw_hex,omitempty"` } func (x *GetBlockHeaderResponse) Reset() { *x = GetBlockHeaderResponse{} - mi := &file_neutrinorpc_neutrino_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetBlockHeaderResponse) String() string { @@ -485,7 +511,7 @@ func (*GetBlockHeaderResponse) ProtoMessage() {} func (x *GetBlockHeaderResponse) ProtoReflect() protoreflect.Message { mi := &file_neutrinorpc_neutrino_proto_msgTypes[9] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -606,18 +632,21 @@ func (x *GetBlockHeaderResponse) GetRawHex() []byte { } type GetBlockRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Block hash in hex notation. - Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Block hash in hex notation. + Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` } func (x *GetBlockRequest) Reset() { *x = GetBlockRequest{} - mi := &file_neutrinorpc_neutrino_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetBlockRequest) String() string { @@ -628,7 +657,7 @@ func (*GetBlockRequest) ProtoMessage() {} func (x *GetBlockRequest) ProtoReflect() protoreflect.Message { mi := &file_neutrinorpc_neutrino_proto_msgTypes[10] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -651,7 +680,10 @@ func (x *GetBlockRequest) GetHash() string { } type GetBlockResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The block hash (same as provided). Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` // The number of confirmations. @@ -683,16 +715,16 @@ type GetBlockResponse struct { // The hash of the previous block. PreviousBlockHash string `protobuf:"bytes,15,opt,name=previous_block_hash,json=previousBlockHash,proto3" json:"previous_block_hash,omitempty"` // The raw hex of the block. - RawHex []byte `protobuf:"bytes,16,opt,name=raw_hex,json=rawHex,proto3" json:"raw_hex,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + RawHex []byte `protobuf:"bytes,16,opt,name=raw_hex,json=rawHex,proto3" json:"raw_hex,omitempty"` } func (x *GetBlockResponse) Reset() { *x = GetBlockResponse{} - mi := &file_neutrinorpc_neutrino_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetBlockResponse) String() string { @@ -703,7 +735,7 @@ func (*GetBlockResponse) ProtoMessage() {} func (x *GetBlockResponse) ProtoReflect() protoreflect.Message { mi := &file_neutrinorpc_neutrino_proto_msgTypes[11] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -831,18 +863,21 @@ func (x *GetBlockResponse) GetRawHex() []byte { } type GetCFilterRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Block hash in hex notation. - Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Block hash in hex notation. + Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` } func (x *GetCFilterRequest) Reset() { *x = GetCFilterRequest{} - mi := &file_neutrinorpc_neutrino_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetCFilterRequest) String() string { @@ -853,7 +888,7 @@ func (*GetCFilterRequest) ProtoMessage() {} func (x *GetCFilterRequest) ProtoReflect() protoreflect.Message { mi := &file_neutrinorpc_neutrino_proto_msgTypes[12] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -876,18 +911,21 @@ func (x *GetCFilterRequest) GetHash() string { } type GetCFilterResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // GCS filter. - Filter []byte `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // GCS filter. + Filter []byte `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` } func (x *GetCFilterResponse) Reset() { *x = GetCFilterResponse{} - mi := &file_neutrinorpc_neutrino_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetCFilterResponse) String() string { @@ -898,7 +936,7 @@ func (*GetCFilterResponse) ProtoMessage() {} func (x *GetCFilterResponse) ProtoReflect() protoreflect.Message { mi := &file_neutrinorpc_neutrino_proto_msgTypes[13] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -921,18 +959,21 @@ func (x *GetCFilterResponse) GetFilter() []byte { } type GetBlockHashRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The block height or index. - Height int32 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The block height or index. + Height int32 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` } func (x *GetBlockHashRequest) Reset() { *x = GetBlockHashRequest{} - mi := &file_neutrinorpc_neutrino_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetBlockHashRequest) String() string { @@ -943,7 +984,7 @@ func (*GetBlockHashRequest) ProtoMessage() {} func (x *GetBlockHashRequest) ProtoReflect() protoreflect.Message { mi := &file_neutrinorpc_neutrino_proto_msgTypes[14] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -966,18 +1007,21 @@ func (x *GetBlockHashRequest) GetHeight() int32 { } type GetBlockHashResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The block hash. - Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The block hash. + Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` } func (x *GetBlockHashResponse) Reset() { *x = GetBlockHashResponse{} - mi := &file_neutrinorpc_neutrino_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_neutrinorpc_neutrino_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetBlockHashResponse) String() string { @@ -988,7 +1032,7 @@ func (*GetBlockHashResponse) ProtoMessage() {} func (x *GetBlockHashResponse) ProtoReflect() protoreflect.Message { mi := &file_neutrinorpc_neutrino_proto_msgTypes[15] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1012,108 +1056,168 @@ func (x *GetBlockHashResponse) GetHash() string { var File_neutrinorpc_neutrino_proto protoreflect.FileDescriptor -const file_neutrinorpc_neutrino_proto_rawDesc = "" + - "\n" + - "\x1aneutrinorpc/neutrino.proto\x12\vneutrinorpc\"\x0f\n" + - "\rStatusRequest\"\x98\x01\n" + - "\x0eStatusResponse\x12\x16\n" + - "\x06active\x18\x01 \x01(\bR\x06active\x12\x16\n" + - "\x06synced\x18\x02 \x01(\bR\x06synced\x12!\n" + - "\fblock_height\x18\x03 \x01(\x05R\vblockHeight\x12\x1d\n" + - "\n" + - "block_hash\x18\x04 \x01(\tR\tblockHash\x12\x14\n" + - "\x05peers\x18\x05 \x03(\tR\x05peers\"/\n" + - "\x0eAddPeerRequest\x12\x1d\n" + - "\n" + - "peer_addrs\x18\x01 \x01(\tR\tpeerAddrs\"\x11\n" + - "\x0fAddPeerResponse\"6\n" + - "\x15DisconnectPeerRequest\x12\x1d\n" + - "\n" + - "peer_addrs\x18\x01 \x01(\tR\tpeerAddrs\"\x18\n" + - "\x16DisconnectPeerResponse\"0\n" + - "\x0fIsBannedRequest\x12\x1d\n" + - "\n" + - "peer_addrs\x18\x01 \x01(\tR\tpeerAddrs\"*\n" + - "\x10IsBannedResponse\x12\x16\n" + - "\x06banned\x18\x01 \x01(\bR\x06banned\"+\n" + - "\x15GetBlockHeaderRequest\x12\x12\n" + - "\x04hash\x18\x01 \x01(\tR\x04hash\"\xaf\x03\n" + - "\x16GetBlockHeaderResponse\x12\x12\n" + - "\x04hash\x18\x01 \x01(\tR\x04hash\x12$\n" + - "\rconfirmations\x18\x02 \x01(\x03R\rconfirmations\x12#\n" + - "\rstripped_size\x18\x03 \x01(\x03R\fstrippedSize\x12\x12\n" + - "\x04size\x18\x04 \x01(\x03R\x04size\x12\x16\n" + - "\x06weight\x18\x05 \x01(\x03R\x06weight\x12\x16\n" + - "\x06height\x18\x06 \x01(\x05R\x06height\x12\x18\n" + - "\aversion\x18\a \x01(\x05R\aversion\x12\x1f\n" + - "\vversion_hex\x18\b \x01(\tR\n" + - "versionHex\x12\x1e\n" + - "\n" + - "merkleroot\x18\t \x01(\tR\n" + - "merkleroot\x12\x12\n" + - "\x04time\x18\n" + - " \x01(\x03R\x04time\x12\x14\n" + - "\x05nonce\x18\v \x01(\rR\x05nonce\x12\x12\n" + - "\x04bits\x18\f \x01(\tR\x04bits\x12\x10\n" + - "\x03ntx\x18\r \x01(\x05R\x03ntx\x12.\n" + - "\x13previous_block_hash\x18\x0e \x01(\tR\x11previousBlockHash\x12\x17\n" + - "\araw_hex\x18\x0f \x01(\fR\x06rawHex\"%\n" + - "\x0fGetBlockRequest\x12\x12\n" + - "\x04hash\x18\x01 \x01(\tR\x04hash\"\xb9\x03\n" + - "\x10GetBlockResponse\x12\x12\n" + - "\x04hash\x18\x01 \x01(\tR\x04hash\x12$\n" + - "\rconfirmations\x18\x02 \x01(\x03R\rconfirmations\x12#\n" + - "\rstripped_size\x18\x03 \x01(\x03R\fstrippedSize\x12\x12\n" + - "\x04size\x18\x04 \x01(\x03R\x04size\x12\x16\n" + - "\x06weight\x18\x05 \x01(\x03R\x06weight\x12\x16\n" + - "\x06height\x18\x06 \x01(\x05R\x06height\x12\x18\n" + - "\aversion\x18\a \x01(\x05R\aversion\x12\x1f\n" + - "\vversion_hex\x18\b \x01(\tR\n" + - "versionHex\x12\x1e\n" + - "\n" + - "merkleroot\x18\t \x01(\tR\n" + - "merkleroot\x12\x0e\n" + - "\x02tx\x18\n" + - " \x03(\tR\x02tx\x12\x12\n" + - "\x04time\x18\v \x01(\x03R\x04time\x12\x14\n" + - "\x05nonce\x18\f \x01(\rR\x05nonce\x12\x12\n" + - "\x04bits\x18\r \x01(\tR\x04bits\x12\x10\n" + - "\x03ntx\x18\x0e \x01(\x05R\x03ntx\x12.\n" + - "\x13previous_block_hash\x18\x0f \x01(\tR\x11previousBlockHash\x12\x17\n" + - "\araw_hex\x18\x10 \x01(\fR\x06rawHex\"'\n" + - "\x11GetCFilterRequest\x12\x12\n" + - "\x04hash\x18\x01 \x01(\tR\x04hash\",\n" + - "\x12GetCFilterResponse\x12\x16\n" + - "\x06filter\x18\x01 \x01(\fR\x06filter\"-\n" + - "\x13GetBlockHashRequest\x12\x16\n" + - "\x06height\x18\x01 \x01(\x05R\x06height\"*\n" + - "\x14GetBlockHashResponse\x12\x12\n" + - "\x04hash\x18\x01 \x01(\tR\x04hash2\x87\x05\n" + - "\vNeutrinoKit\x12A\n" + - "\x06Status\x12\x1a.neutrinorpc.StatusRequest\x1a\x1b.neutrinorpc.StatusResponse\x12D\n" + - "\aAddPeer\x12\x1b.neutrinorpc.AddPeerRequest\x1a\x1c.neutrinorpc.AddPeerResponse\x12Y\n" + - "\x0eDisconnectPeer\x12\".neutrinorpc.DisconnectPeerRequest\x1a#.neutrinorpc.DisconnectPeerResponse\x12G\n" + - "\bIsBanned\x12\x1c.neutrinorpc.IsBannedRequest\x1a\x1d.neutrinorpc.IsBannedResponse\x12Y\n" + - "\x0eGetBlockHeader\x12\".neutrinorpc.GetBlockHeaderRequest\x1a#.neutrinorpc.GetBlockHeaderResponse\x12G\n" + - "\bGetBlock\x12\x1c.neutrinorpc.GetBlockRequest\x1a\x1d.neutrinorpc.GetBlockResponse\x12M\n" + - "\n" + - "GetCFilter\x12\x1e.neutrinorpc.GetCFilterRequest\x1a\x1f.neutrinorpc.GetCFilterResponse\x12X\n" + - "\fGetBlockHash\x12 .neutrinorpc.GetBlockHashRequest\x1a!.neutrinorpc.GetBlockHashResponse\"\x03\x88\x02\x01B3Z1github.com/lightningnetwork/lnd/lnrpc/neutrinorpcb\x06proto3" +var file_neutrinorpc_neutrino_proto_rawDesc = []byte{ + 0x0a, 0x1a, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2f, 0x6e, 0x65, + 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x6e, 0x65, + 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x22, 0x0f, 0x0a, 0x0d, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x98, 0x01, 0x0a, 0x0e, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x61, + 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x12, 0x21, 0x0a, + 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, + 0x14, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, + 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0x2f, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x50, 0x65, 0x65, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x5f, + 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x65, 0x65, + 0x72, 0x41, 0x64, 0x64, 0x72, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x41, 0x64, 0x64, 0x50, 0x65, 0x65, + 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x0a, 0x15, 0x44, 0x69, 0x73, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x65, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, + 0x73, 0x22, 0x18, 0x0a, 0x16, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, + 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x30, 0x0a, 0x0f, 0x49, + 0x73, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x70, 0x65, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x73, 0x22, 0x2a, 0x0a, + 0x10, 0x49, 0x73, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x06, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x22, 0x2b, 0x0a, 0x15, 0x47, 0x65, 0x74, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, 0xaf, 0x03, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x73, + 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x64, 0x53, 0x69, 0x7a, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, + 0x73, 0x69, 0x7a, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x68, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, + 0x0a, 0x0b, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x65, 0x78, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x78, 0x12, + 0x1e, 0x0a, 0x0a, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x72, 0x6f, 0x6f, 0x74, 0x12, + 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x74, + 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x69, 0x74, + 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x69, 0x74, 0x73, 0x12, 0x10, 0x0a, + 0x03, 0x6e, 0x74, 0x78, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6e, 0x74, 0x78, 0x12, + 0x2e, 0x0a, 0x13, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x70, 0x72, + 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, + 0x17, 0x0a, 0x07, 0x72, 0x61, 0x77, 0x5f, 0x68, 0x65, 0x78, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x06, 0x72, 0x61, 0x77, 0x48, 0x65, 0x78, 0x22, 0x25, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, + 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, + 0xb9, 0x03, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x23, + 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x64, 0x53, + 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, + 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x65, 0x78, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48, + 0x65, 0x78, 0x12, 0x1e, 0x0a, 0x0a, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x72, 0x6f, 0x6f, 0x74, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x72, 0x6f, + 0x6f, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x78, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x02, + 0x74, 0x78, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, + 0x0c, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x62, 0x69, 0x74, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x69, 0x74, 0x73, + 0x12, 0x10, 0x0a, 0x03, 0x6e, 0x74, 0x78, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6e, + 0x74, 0x78, 0x12, 0x2e, 0x0a, 0x13, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x11, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, + 0x73, 0x68, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x61, 0x77, 0x5f, 0x68, 0x65, 0x78, 0x18, 0x10, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x61, 0x77, 0x48, 0x65, 0x78, 0x22, 0x27, 0x0a, 0x11, 0x47, + 0x65, 0x74, 0x43, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x68, 0x61, 0x73, 0x68, 0x22, 0x2c, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x43, 0x46, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x22, 0x2d, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, + 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x22, 0x2a, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, + 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x32, 0x87, 0x05, + 0x0a, 0x0b, 0x4e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x4b, 0x69, 0x74, 0x12, 0x41, 0x0a, + 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, + 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x44, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x50, 0x65, 0x65, 0x72, 0x12, 0x1b, 0x2e, 0x6e, 0x65, + 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x65, 0x65, + 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, + 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x0e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x22, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, + 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6e, + 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x47, 0x0a, 0x08, 0x49, 0x73, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x12, 0x1c, 0x2e, + 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x73, 0x42, 0x61, + 0x6e, 0x6e, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6e, 0x65, + 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x73, 0x42, 0x61, 0x6e, 0x6e, + 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x0e, 0x47, 0x65, + 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x22, 0x2e, 0x6e, + 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x23, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x47, + 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x12, 0x1c, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, + 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1d, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, + 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x43, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x6e, + 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x46, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6e, + 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x46, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, + 0x0c, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x20, 0x2e, + 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x21, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, + 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2f, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} var ( file_neutrinorpc_neutrino_proto_rawDescOnce sync.Once - file_neutrinorpc_neutrino_proto_rawDescData []byte + file_neutrinorpc_neutrino_proto_rawDescData = file_neutrinorpc_neutrino_proto_rawDesc ) func file_neutrinorpc_neutrino_proto_rawDescGZIP() []byte { file_neutrinorpc_neutrino_proto_rawDescOnce.Do(func() { - file_neutrinorpc_neutrino_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_neutrinorpc_neutrino_proto_rawDesc), len(file_neutrinorpc_neutrino_proto_rawDesc))) + file_neutrinorpc_neutrino_proto_rawDescData = protoimpl.X.CompressGZIP(file_neutrinorpc_neutrino_proto_rawDescData) }) return file_neutrinorpc_neutrino_proto_rawDescData } var file_neutrinorpc_neutrino_proto_msgTypes = make([]protoimpl.MessageInfo, 16) -var file_neutrinorpc_neutrino_proto_goTypes = []any{ +var file_neutrinorpc_neutrino_proto_goTypes = []interface{}{ (*StatusRequest)(nil), // 0: neutrinorpc.StatusRequest (*StatusResponse)(nil), // 1: neutrinorpc.StatusResponse (*AddPeerRequest)(nil), // 2: neutrinorpc.AddPeerRequest @@ -1160,11 +1264,205 @@ func file_neutrinorpc_neutrino_proto_init() { if File_neutrinorpc_neutrino_proto != nil { return } + if !protoimpl.UnsafeEnabled { + file_neutrinorpc_neutrino_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatusRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatusResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddPeerRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddPeerResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DisconnectPeerRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DisconnectPeerResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*IsBannedRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*IsBannedResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlockHeaderRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlockHeaderResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlockRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlockResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetCFilterRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetCFilterResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlockHashRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_neutrinorpc_neutrino_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlockHashResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_neutrinorpc_neutrino_proto_rawDesc), len(file_neutrinorpc_neutrino_proto_rawDesc)), + RawDescriptor: file_neutrinorpc_neutrino_proto_rawDesc, NumEnums: 0, NumMessages: 16, NumExtensions: 0, @@ -1175,6 +1473,7 @@ func file_neutrinorpc_neutrino_proto_init() { MessageInfos: file_neutrinorpc_neutrino_proto_msgTypes, }.Build() File_neutrinorpc_neutrino_proto = out.File + file_neutrinorpc_neutrino_proto_rawDesc = nil file_neutrinorpc_neutrino_proto_goTypes = nil file_neutrinorpc_neutrino_proto_depIdxs = nil } diff --git a/lnrpc/neutrinorpc/neutrino_server.go b/lnrpc/neutrinorpc/neutrino_server.go index 55df46e93..7154be7db 100644 --- a/lnrpc/neutrinorpc/neutrino_server.go +++ b/lnrpc/neutrinorpc/neutrino_server.go @@ -9,8 +9,8 @@ import ( "fmt" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/lightningnetwork/lnd/lnrpc" "google.golang.org/grpc" diff --git a/lnrpc/peersrpc/peers.pb.go b/lnrpc/peersrpc/peers.pb.go index b29ae4218..cd16013cd 100644 --- a/lnrpc/peersrpc/peers.pb.go +++ b/lnrpc/peersrpc/peers.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.33.0 // protoc v3.21.12 // source: peersrpc/peers.proto @@ -12,7 +12,6 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" - unsafe "unsafe" ) const ( @@ -138,20 +137,23 @@ func (FeatureSet) EnumDescriptor() ([]byte, []int) { } type UpdateAddressAction struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Determines the kind of action. Action UpdateAction `protobuf:"varint,1,opt,name=action,proto3,enum=peersrpc.UpdateAction" json:"action,omitempty"` // The address used to apply the update action. - Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` } func (x *UpdateAddressAction) Reset() { *x = UpdateAddressAction{} - mi := &file_peersrpc_peers_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_peersrpc_peers_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *UpdateAddressAction) String() string { @@ -162,7 +164,7 @@ func (*UpdateAddressAction) ProtoMessage() {} func (x *UpdateAddressAction) ProtoReflect() protoreflect.Message { mi := &file_peersrpc_peers_proto_msgTypes[0] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -192,20 +194,23 @@ func (x *UpdateAddressAction) GetAddress() string { } type UpdateFeatureAction struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Determines the kind of action. Action UpdateAction `protobuf:"varint,1,opt,name=action,proto3,enum=peersrpc.UpdateAction" json:"action,omitempty"` // The feature bit used to apply the update action. - FeatureBit lnrpc.FeatureBit `protobuf:"varint,2,opt,name=feature_bit,json=featureBit,proto3,enum=lnrpc.FeatureBit" json:"feature_bit,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + FeatureBit lnrpc.FeatureBit `protobuf:"varint,2,opt,name=feature_bit,json=featureBit,proto3,enum=lnrpc.FeatureBit" json:"feature_bit,omitempty"` } func (x *UpdateFeatureAction) Reset() { *x = UpdateFeatureAction{} - mi := &file_peersrpc_peers_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_peersrpc_peers_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *UpdateFeatureAction) String() string { @@ -216,7 +221,7 @@ func (*UpdateFeatureAction) ProtoMessage() {} func (x *UpdateFeatureAction) ProtoReflect() protoreflect.Message { mi := &file_peersrpc_peers_proto_msgTypes[1] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -246,7 +251,10 @@ func (x *UpdateFeatureAction) GetFeatureBit() lnrpc.FeatureBit { } type NodeAnnouncementUpdateRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Set of changes for the features that the node supports. FeatureUpdates []*UpdateFeatureAction `protobuf:"bytes,1,rep,name=feature_updates,json=featureUpdates,proto3" json:"feature_updates,omitempty"` // Color is the node's color in hex code format. @@ -255,15 +263,15 @@ type NodeAnnouncementUpdateRequest struct { Alias string `protobuf:"bytes,3,opt,name=alias,proto3" json:"alias,omitempty"` // Set of changes for the node's known addresses. AddressUpdates []*UpdateAddressAction `protobuf:"bytes,4,rep,name=address_updates,json=addressUpdates,proto3" json:"address_updates,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *NodeAnnouncementUpdateRequest) Reset() { *x = NodeAnnouncementUpdateRequest{} - mi := &file_peersrpc_peers_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_peersrpc_peers_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *NodeAnnouncementUpdateRequest) String() string { @@ -274,7 +282,7 @@ func (*NodeAnnouncementUpdateRequest) ProtoMessage() {} func (x *NodeAnnouncementUpdateRequest) ProtoReflect() protoreflect.Message { mi := &file_peersrpc_peers_proto_msgTypes[2] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -318,17 +326,20 @@ func (x *NodeAnnouncementUpdateRequest) GetAddressUpdates() []*UpdateAddressActi } type NodeAnnouncementUpdateResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Ops []*lnrpc.Op `protobuf:"bytes,1,rep,name=ops,proto3" json:"ops,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Ops []*lnrpc.Op `protobuf:"bytes,1,rep,name=ops,proto3" json:"ops,omitempty"` } func (x *NodeAnnouncementUpdateResponse) Reset() { *x = NodeAnnouncementUpdateResponse{} - mi := &file_peersrpc_peers_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_peersrpc_peers_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *NodeAnnouncementUpdateResponse) String() string { @@ -339,7 +350,7 @@ func (*NodeAnnouncementUpdateResponse) ProtoMessage() {} func (x *NodeAnnouncementUpdateResponse) ProtoReflect() protoreflect.Message { mi := &file_peersrpc_peers_proto_msgTypes[3] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -363,52 +374,79 @@ func (x *NodeAnnouncementUpdateResponse) GetOps() []*lnrpc.Op { var File_peersrpc_peers_proto protoreflect.FileDescriptor -const file_peersrpc_peers_proto_rawDesc = "" + - "\n" + - "\x14peersrpc/peers.proto\x12\bpeersrpc\x1a\x0flightning.proto\"_\n" + - "\x13UpdateAddressAction\x12.\n" + - "\x06action\x18\x01 \x01(\x0e2\x16.peersrpc.UpdateActionR\x06action\x12\x18\n" + - "\aaddress\x18\x02 \x01(\tR\aaddress\"y\n" + - "\x13UpdateFeatureAction\x12.\n" + - "\x06action\x18\x01 \x01(\x0e2\x16.peersrpc.UpdateActionR\x06action\x122\n" + - "\vfeature_bit\x18\x02 \x01(\x0e2\x11.lnrpc.FeatureBitR\n" + - "featureBit\"\xdb\x01\n" + - "\x1dNodeAnnouncementUpdateRequest\x12F\n" + - "\x0ffeature_updates\x18\x01 \x03(\v2\x1d.peersrpc.UpdateFeatureActionR\x0efeatureUpdates\x12\x14\n" + - "\x05color\x18\x02 \x01(\tR\x05color\x12\x14\n" + - "\x05alias\x18\x03 \x01(\tR\x05alias\x12F\n" + - "\x0faddress_updates\x18\x04 \x03(\v2\x1d.peersrpc.UpdateAddressActionR\x0eaddressUpdates\"=\n" + - "\x1eNodeAnnouncementUpdateResponse\x12\x1b\n" + - "\x03ops\x18\x01 \x03(\v2\t.lnrpc.OpR\x03ops*#\n" + - "\fUpdateAction\x12\a\n" + - "\x03ADD\x10\x00\x12\n" + - "\n" + - "\x06REMOVE\x10\x01*i\n" + - "\n" + - "FeatureSet\x12\f\n" + - "\bSET_INIT\x10\x00\x12\x15\n" + - "\x11SET_LEGACY_GLOBAL\x10\x01\x12\x10\n" + - "\fSET_NODE_ANN\x10\x02\x12\x0f\n" + - "\vSET_INVOICE\x10\x03\x12\x13\n" + - "\x0fSET_INVOICE_AMP\x10\x042t\n" + - "\x05Peers\x12k\n" + - "\x16UpdateNodeAnnouncement\x12'.peersrpc.NodeAnnouncementUpdateRequest\x1a(.peersrpc.NodeAnnouncementUpdateResponseB0Z.github.com/lightningnetwork/lnd/lnrpc/peersrpcb\x06proto3" +var file_peersrpc_peers_proto_rawDesc = []byte{ + 0x0a, 0x14, 0x70, 0x65, 0x65, 0x72, 0x73, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x65, 0x65, 0x72, 0x73, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x70, 0x65, 0x65, 0x72, 0x73, 0x72, 0x70, 0x63, + 0x1a, 0x0f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0x5f, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x73, + 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x22, 0x79, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x06, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x70, 0x65, 0x65, 0x72, + 0x73, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x0b, 0x66, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x5f, 0x62, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, + 0x74, 0x52, 0x0a, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, 0x22, 0xdb, 0x01, + 0x0a, 0x1d, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x46, 0x0a, 0x0f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x73, + 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x12, 0x14, 0x0a, + 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, + 0x69, 0x61, 0x73, 0x12, 0x46, 0x0a, 0x0f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, + 0x65, 0x65, 0x72, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0x3d, 0x0a, 0x1e, 0x4e, + 0x6f, 0x64, 0x65, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, + 0x03, 0x6f, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x52, 0x03, 0x6f, 0x70, 0x73, 0x2a, 0x23, 0x0a, 0x0c, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x44, + 0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x45, 0x4d, 0x4f, 0x56, 0x45, 0x10, 0x01, 0x2a, + 0x69, 0x0a, 0x0a, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x12, 0x0c, 0x0a, + 0x08, 0x53, 0x45, 0x54, 0x5f, 0x49, 0x4e, 0x49, 0x54, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x53, + 0x45, 0x54, 0x5f, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, 0x47, 0x4c, 0x4f, 0x42, 0x41, 0x4c, + 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x41, + 0x4e, 0x4e, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x45, 0x54, 0x5f, 0x49, 0x4e, 0x56, 0x4f, + 0x49, 0x43, 0x45, 0x10, 0x03, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x45, 0x54, 0x5f, 0x49, 0x4e, 0x56, + 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x41, 0x4d, 0x50, 0x10, 0x04, 0x32, 0x74, 0x0a, 0x05, 0x50, 0x65, + 0x65, 0x72, 0x73, 0x12, 0x6b, 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x64, + 0x65, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x27, 0x2e, + 0x70, 0x65, 0x65, 0x72, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x6e, 0x6e, + 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x73, 0x72, 0x70, + 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, + 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, + 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x72, + 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} var ( file_peersrpc_peers_proto_rawDescOnce sync.Once - file_peersrpc_peers_proto_rawDescData []byte + file_peersrpc_peers_proto_rawDescData = file_peersrpc_peers_proto_rawDesc ) func file_peersrpc_peers_proto_rawDescGZIP() []byte { file_peersrpc_peers_proto_rawDescOnce.Do(func() { - file_peersrpc_peers_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_peersrpc_peers_proto_rawDesc), len(file_peersrpc_peers_proto_rawDesc))) + file_peersrpc_peers_proto_rawDescData = protoimpl.X.CompressGZIP(file_peersrpc_peers_proto_rawDescData) }) return file_peersrpc_peers_proto_rawDescData } var file_peersrpc_peers_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_peersrpc_peers_proto_msgTypes = make([]protoimpl.MessageInfo, 4) -var file_peersrpc_peers_proto_goTypes = []any{ +var file_peersrpc_peers_proto_goTypes = []interface{}{ (UpdateAction)(0), // 0: peersrpc.UpdateAction (FeatureSet)(0), // 1: peersrpc.FeatureSet (*UpdateAddressAction)(nil), // 2: peersrpc.UpdateAddressAction @@ -439,11 +477,61 @@ func file_peersrpc_peers_proto_init() { if File_peersrpc_peers_proto != nil { return } + if !protoimpl.UnsafeEnabled { + file_peersrpc_peers_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateAddressAction); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_peersrpc_peers_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateFeatureAction); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_peersrpc_peers_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NodeAnnouncementUpdateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_peersrpc_peers_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NodeAnnouncementUpdateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_peersrpc_peers_proto_rawDesc), len(file_peersrpc_peers_proto_rawDesc)), + RawDescriptor: file_peersrpc_peers_proto_rawDesc, NumEnums: 2, NumMessages: 4, NumExtensions: 0, @@ -455,6 +543,7 @@ func file_peersrpc_peers_proto_init() { MessageInfos: file_peersrpc_peers_proto_msgTypes, }.Build() File_peersrpc_peers_proto = out.File + file_peersrpc_peers_proto_rawDesc = nil file_peersrpc_peers_proto_goTypes = nil file_peersrpc_peers_proto_depIdxs = nil } diff --git a/lnrpc/routerrpc/forward_interceptor.go b/lnrpc/routerrpc/forward_interceptor.go index a1a065ff5..61adf8f2b 100644 --- a/lnrpc/routerrpc/forward_interceptor.go +++ b/lnrpc/routerrpc/forward_interceptor.go @@ -100,17 +100,6 @@ func (r *forwardInterceptor) onIntercept( InWireCustomRecords: htlc.InWireCustomRecords, } - // A node-ID forward has no requested outgoing channel. Expose the - // requested pubkey and report the reserved NodeIDForwardSCID sentinel - // rather than a zero SCID. Older un-upgraded protobuf clients do not - // know about outgoing_requested_node_id and would otherwise interpret - // a zero SCID as an exit hop. - htlc.OutgoingNodeID.WhenSome(func(nodeID [33]byte) { - interceptionRequest.OutgoingRequestedNodeId = nodeID[:] - interceptionRequest.OutgoingRequestedChanId = - htlcswitch.NodeIDForwardSCID - }) - return r.stream.Send(interceptionRequest) } diff --git a/lnrpc/routerrpc/parse_duration.go b/lnrpc/routerrpc/parse_duration.go deleted file mode 100644 index b523d224e..000000000 --- a/lnrpc/routerrpc/parse_duration.go +++ /dev/null @@ -1,109 +0,0 @@ -package routerrpc - -import ( - "fmt" - "time" -) - -// parseDuration parses a duration string using a hybrid approach. It first -// attempts to use the standard library time.ParseDuration, which supports -// ns, us, ms, s, m, h. If that fails, it falls back to custom parsing for -// user-friendly units: d (days), w (weeks), M (months), y (years). -// -// Examples: -// - Standard Go: "-24h", "-1.5h", "-30m" -// - Custom units: "-1d", "-1w", "-1M", "-1y" -// -// All durations should be negative to indicate "time ago". -func parseDuration(durationStr string) (time.Duration, error) { - // First, try the standard library parser. - duration, err := time.ParseDuration(durationStr) - if err == nil { - // Enforce negative durations to prevent confusion. - if duration >= 0 { - return 0, fmt.Errorf("duration must be negative to " + - "indicate time in the past (e.g., -1w, -24h)") - } - - return duration, nil - } - - // Fall back to custom parsing for d, w, M, y units. - if len(durationStr) < 2 { - return 0, fmt.Errorf("duration too short") - } - - // Duration strings should start with a minus sign for "ago". - if durationStr[0] != '-' { - return 0, fmt.Errorf("duration must be " + - "negative (e.g., -1w, -24h)") - } - - // Strip the minus sign. - durationStr = durationStr[1:] - - // Find where the numeric part ends. We allow digits and a single - // decimal point so that custom units accept fractional values like - // "-1.5d", matching the behaviour of the standard Go parser. - var ( - numStr string - unit string - hasDot bool - ) - for i, ch := range durationStr { - if ch == '.' && !hasDot { - hasDot = true - continue - } - if ch < '0' || ch > '9' { - numStr = durationStr[:i] - unit = durationStr[i:] - break - } - } - - if numStr == "" { - return 0, fmt.Errorf("no numeric value found") - } - if unit == "" { - return 0, fmt.Errorf("no unit specified") - } - - var value float64 - _, parseErr := fmt.Sscanf(numStr, "%f", &value) - if parseErr != nil { - return 0, fmt.Errorf("invalid numeric value: %w", parseErr) - } - - // Calculate the duration based on the custom unit. - var customDuration time.Duration - switch unit { - case "d": - customDuration = time.Duration(value * 24 * float64(time.Hour)) - - case "w": - customDuration = time.Duration( - value * 7 * 24 * float64(time.Hour), - ) - - case "M": - // Average month = 30.44 days. - customDuration = time.Duration( - value * 30.44 * 24 * float64(time.Hour), - ) - - case "y": - // Average year = 365.25 days. - customDuration = time.Duration( - value * 365.25 * 24 * float64(time.Hour), - ) - - default: - // Not a custom unit we recognize, return the original error. - return 0, fmt.Errorf("unknown time unit: %s (supported: ns, "+ - "us, ms, s, m, h, d, w, M, y)", unit) - } - - // Return negative duration (going back in time). - return -customDuration, nil -} diff --git a/lnrpc/routerrpc/parse_duration_test.go b/lnrpc/routerrpc/parse_duration_test.go deleted file mode 100644 index 66c8c3bbd..000000000 --- a/lnrpc/routerrpc/parse_duration_test.go +++ /dev/null @@ -1,243 +0,0 @@ -package routerrpc - -import ( - "fmt" - "testing" - "time" - - "github.com/stretchr/testify/require" - "pgregory.net/rapid" -) - -// TestParseDuration tests the hybrid duration parsing with explicit examples. -func TestParseDuration(t *testing.T) { - t.Parallel() - - //nolint:ll - tests := []struct { - name string - input string - expected time.Duration - wantErr bool - }{ - // Standard Go durations. - { - name: "standard go hours", - input: "-24h", - expected: -24 * time.Hour, - }, - { - name: "standard go fractional hours", - input: "-1.5h", - expected: time.Duration(-1.5 * float64(time.Hour)), - }, - { - name: "standard go minutes", - input: "-30m", - expected: -30 * time.Minute, - }, - { - name: "standard go seconds", - input: "-60s", - expected: -60 * time.Second, - }, - { - name: "standard go milliseconds", - input: "-500ms", - expected: -500 * time.Millisecond, - }, - { - name: "standard go microseconds", - input: "-1000us", - expected: -1000 * time.Microsecond, - }, - { - name: "standard go complex", - input: "-2h30m45s", - expected: -(2*time.Hour + 30*time.Minute + 45*time.Second), - }, - - // Custom units. - { - name: "custom days", - input: "-1d", - expected: -24 * time.Hour, - }, - { - name: "custom multiple days", - input: "-7d", - expected: -7 * 24 * time.Hour, - }, - { - name: "custom weeks", - input: "-1w", - expected: -7 * 24 * time.Hour, - }, - { - name: "custom multiple weeks", - input: "-4w", - expected: -4 * 7 * 24 * time.Hour, - }, - { - name: "custom months", - input: "-1M", - expected: time.Duration(-30.44 * 24 * float64(time.Hour)), - }, - { - name: "custom years", - input: "-1y", - expected: time.Duration(-365.25 * 24 * float64(time.Hour)), - }, - - // Error cases. - { - name: "positive duration", - input: "1h", - wantErr: true, - }, - { - name: "no minus sign custom", - input: "1d", - wantErr: true, - }, - { - name: "empty string", - input: "", - wantErr: true, - }, - { - name: "just minus", - input: "-", - wantErr: true, - }, - { - name: "no number", - input: "-d", - wantErr: true, - }, - { - name: "no unit", - input: "-5", - wantErr: true, - }, - { - name: "invalid unit", - input: "-1x", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - got, err := parseDuration(tt.input) - if tt.wantErr { - require.Error(t, err) - return - } - - require.NoError(t, err) - require.Equal(t, tt.expected, got) - }) - } -} - -// TestParseDurationProperties uses property-based testing to verify invariants. -func TestParseDurationProperties(t *testing.T) { - t.Parallel() - - // Test that all valid standard Go durations work. - t.Run("standard go durations", func(t *testing.T) { - rapid.Check(t, func(rt *rapid.T) { - // Generate a random duration string using - // time.Duration's String() method. - hours := rapid.IntRange(-8760, -1).Draw(rt, "hours") - minutes := rapid.IntRange(0, 59).Draw(rt, "minutes") - seconds := rapid.IntRange(0, 59).Draw(rt, "seconds") - - d := time.Duration(hours)*time.Hour + - time.Duration(minutes)*time.Minute + - time.Duration(seconds)*time.Second - - // Parse it back. - parsed, err := parseDuration(d.String()) - require.NoError(rt, err) - - // Should match original (within a small margin for - // float precision). - diff := parsed - d - if diff < 0 { - diff = -diff - } - require.Less( - rt, diff, time.Microsecond, - "parsed duration differs: got %v, want %v", - parsed, d, - ) - }) - }) - - // Test that custom units produce negative durations. - t.Run("custom units negative", func(t *testing.T) { - rapid.Check(t, func(rt *rapid.T) { - value := rapid.IntRange(1, 1000).Draw(rt, "value") - unit := rapid.SampledFrom([]string{"d", "w", "M", "y"}). - Draw(rt, "unit") - - input := fmt.Sprintf("-%d%s", value, unit) - - parsed, err := parseDuration(input) - require.NoError(rt, err) - require.Less( - rt, parsed, time.Duration(0), - "custom unit should produce negative duration", - ) - }) - }) - - // Test that days are always 24 hours. - t.Run("days invariant", func(t *testing.T) { - rapid.Check(t, func(rt *rapid.T) { - days := rapid.IntRange(1, 365).Draw(rt, "days") - input := fmt.Sprintf("-%dd", days) - - parsed, err := parseDuration(input) - require.NoError(rt, err) - - expected := time.Duration(-days) * 24 * time.Hour - require.Equal(rt, expected, parsed) - }) - }) - - // Test that weeks are always 7 days. - t.Run("weeks invariant", func(t *testing.T) { - rapid.Check(t, func(rt *rapid.T) { - weeks := rapid.IntRange(1, 52).Draw(rt, "weeks") - input := fmt.Sprintf("-%dw", weeks) - - parsed, err := parseDuration(input) - require.NoError(rt, err) - - expected := time.Duration(-weeks) * 7 * 24 * time.Hour - require.Equal(rt, expected, parsed) - }) - }) - - // Test that positive durations always error. - t.Run("positive durations error", func(t *testing.T) { - rapid.Check(t, func(rt *rapid.T) { - value := rapid.IntRange(1, 1000).Draw(rt, "value") - unit := rapid.SampledFrom([]string{ - "s", "m", "h", "d", "w", "M", "y", - }).Draw(rt, "unit") - - // Positive duration (no minus sign). - input := fmt.Sprintf("%d%s", value, unit) - - _, err := parseDuration(input) - require.Error(rt, err, - "positive duration should error: %s", input) - }) - }) -} diff --git a/lnrpc/routerrpc/router.pb.go b/lnrpc/routerrpc/router.pb.go index a1ada9c93..2a7b2deae 100644 --- a/lnrpc/routerrpc/router.pb.go +++ b/lnrpc/routerrpc/router.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.33.0 // protoc v3.21.12 // source: routerrpc/router.proto @@ -12,7 +12,6 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" - unsafe "unsafe" ) const ( @@ -25,34 +24,29 @@ const ( type FailureDetail int32 const ( - FailureDetail_UNKNOWN FailureDetail = 0 - FailureDetail_NO_DETAIL FailureDetail = 1 - FailureDetail_ONION_DECODE FailureDetail = 2 - FailureDetail_LINK_NOT_ELIGIBLE FailureDetail = 3 - FailureDetail_ON_CHAIN_TIMEOUT FailureDetail = 4 - FailureDetail_HTLC_EXCEEDS_MAX FailureDetail = 5 - FailureDetail_INSUFFICIENT_BALANCE FailureDetail = 6 - FailureDetail_INCOMPLETE_FORWARD FailureDetail = 7 - FailureDetail_HTLC_ADD_FAILED FailureDetail = 8 - FailureDetail_FORWARDS_DISABLED FailureDetail = 9 - FailureDetail_INVOICE_CANCELED FailureDetail = 10 - FailureDetail_INVOICE_UNDERPAID FailureDetail = 11 - FailureDetail_INVOICE_EXPIRY_TOO_SOON FailureDetail = 12 - FailureDetail_INVOICE_NOT_OPEN FailureDetail = 13 - FailureDetail_MPP_INVOICE_TIMEOUT FailureDetail = 14 - FailureDetail_ADDRESS_MISMATCH FailureDetail = 15 - FailureDetail_SET_TOTAL_MISMATCH FailureDetail = 16 - FailureDetail_SET_TOTAL_TOO_LOW FailureDetail = 17 - FailureDetail_SET_OVERPAID FailureDetail = 18 - FailureDetail_UNKNOWN_INVOICE FailureDetail = 19 - FailureDetail_INVALID_KEYSEND FailureDetail = 20 - FailureDetail_MPP_IN_PROGRESS FailureDetail = 21 - FailureDetail_CIRCULAR_ROUTE FailureDetail = 22 - FailureDetail_INVOICE_ALREADY_SETTLED FailureDetail = 23 - FailureDetail_HTLC_INVOICE_TYPE_MISMATCH FailureDetail = 24 - FailureDetail_AMP_ERROR FailureDetail = 25 - FailureDetail_AMP_RECONSTRUCTION FailureDetail = 26 - FailureDetail_EXTERNAL_VALIDATION_FAILED FailureDetail = 27 + FailureDetail_UNKNOWN FailureDetail = 0 + FailureDetail_NO_DETAIL FailureDetail = 1 + FailureDetail_ONION_DECODE FailureDetail = 2 + FailureDetail_LINK_NOT_ELIGIBLE FailureDetail = 3 + FailureDetail_ON_CHAIN_TIMEOUT FailureDetail = 4 + FailureDetail_HTLC_EXCEEDS_MAX FailureDetail = 5 + FailureDetail_INSUFFICIENT_BALANCE FailureDetail = 6 + FailureDetail_INCOMPLETE_FORWARD FailureDetail = 7 + FailureDetail_HTLC_ADD_FAILED FailureDetail = 8 + FailureDetail_FORWARDS_DISABLED FailureDetail = 9 + FailureDetail_INVOICE_CANCELED FailureDetail = 10 + FailureDetail_INVOICE_UNDERPAID FailureDetail = 11 + FailureDetail_INVOICE_EXPIRY_TOO_SOON FailureDetail = 12 + FailureDetail_INVOICE_NOT_OPEN FailureDetail = 13 + FailureDetail_MPP_INVOICE_TIMEOUT FailureDetail = 14 + FailureDetail_ADDRESS_MISMATCH FailureDetail = 15 + FailureDetail_SET_TOTAL_MISMATCH FailureDetail = 16 + FailureDetail_SET_TOTAL_TOO_LOW FailureDetail = 17 + FailureDetail_SET_OVERPAID FailureDetail = 18 + FailureDetail_UNKNOWN_INVOICE FailureDetail = 19 + FailureDetail_INVALID_KEYSEND FailureDetail = 20 + FailureDetail_MPP_IN_PROGRESS FailureDetail = 21 + FailureDetail_CIRCULAR_ROUTE FailureDetail = 22 ) // Enum value maps for FailureDetail. @@ -81,41 +75,31 @@ var ( 20: "INVALID_KEYSEND", 21: "MPP_IN_PROGRESS", 22: "CIRCULAR_ROUTE", - 23: "INVOICE_ALREADY_SETTLED", - 24: "HTLC_INVOICE_TYPE_MISMATCH", - 25: "AMP_ERROR", - 26: "AMP_RECONSTRUCTION", - 27: "EXTERNAL_VALIDATION_FAILED", } FailureDetail_value = map[string]int32{ - "UNKNOWN": 0, - "NO_DETAIL": 1, - "ONION_DECODE": 2, - "LINK_NOT_ELIGIBLE": 3, - "ON_CHAIN_TIMEOUT": 4, - "HTLC_EXCEEDS_MAX": 5, - "INSUFFICIENT_BALANCE": 6, - "INCOMPLETE_FORWARD": 7, - "HTLC_ADD_FAILED": 8, - "FORWARDS_DISABLED": 9, - "INVOICE_CANCELED": 10, - "INVOICE_UNDERPAID": 11, - "INVOICE_EXPIRY_TOO_SOON": 12, - "INVOICE_NOT_OPEN": 13, - "MPP_INVOICE_TIMEOUT": 14, - "ADDRESS_MISMATCH": 15, - "SET_TOTAL_MISMATCH": 16, - "SET_TOTAL_TOO_LOW": 17, - "SET_OVERPAID": 18, - "UNKNOWN_INVOICE": 19, - "INVALID_KEYSEND": 20, - "MPP_IN_PROGRESS": 21, - "CIRCULAR_ROUTE": 22, - "INVOICE_ALREADY_SETTLED": 23, - "HTLC_INVOICE_TYPE_MISMATCH": 24, - "AMP_ERROR": 25, - "AMP_RECONSTRUCTION": 26, - "EXTERNAL_VALIDATION_FAILED": 27, + "UNKNOWN": 0, + "NO_DETAIL": 1, + "ONION_DECODE": 2, + "LINK_NOT_ELIGIBLE": 3, + "ON_CHAIN_TIMEOUT": 4, + "HTLC_EXCEEDS_MAX": 5, + "INSUFFICIENT_BALANCE": 6, + "INCOMPLETE_FORWARD": 7, + "HTLC_ADD_FAILED": 8, + "FORWARDS_DISABLED": 9, + "INVOICE_CANCELED": 10, + "INVOICE_UNDERPAID": 11, + "INVOICE_EXPIRY_TOO_SOON": 12, + "INVOICE_NOT_OPEN": 13, + "MPP_INVOICE_TIMEOUT": 14, + "ADDRESS_MISMATCH": 15, + "SET_TOTAL_MISMATCH": 16, + "SET_TOTAL_TOO_LOW": 17, + "SET_OVERPAID": 18, + "UNKNOWN_INVOICE": 19, + "INVALID_KEYSEND": 20, + "MPP_IN_PROGRESS": 21, + "CIRCULAR_ROUTE": 22, } ) @@ -146,6 +130,76 @@ func (FailureDetail) EnumDescriptor() ([]byte, []int) { return file_routerrpc_router_proto_rawDescGZIP(), []int{0} } +type PaymentState int32 + +const ( + // Payment is still in flight. + PaymentState_IN_FLIGHT PaymentState = 0 + // Payment completed successfully. + PaymentState_SUCCEEDED PaymentState = 1 + // There are more routes to try, but the payment timeout was exceeded. + PaymentState_FAILED_TIMEOUT PaymentState = 2 + // All possible routes were tried and failed permanently. Or were no + // routes to the destination at all. + PaymentState_FAILED_NO_ROUTE PaymentState = 3 + // A non-recoverable error has occurred. + PaymentState_FAILED_ERROR PaymentState = 4 + // Payment details incorrect (unknown hash, invalid amt or + // invalid final cltv delta) + PaymentState_FAILED_INCORRECT_PAYMENT_DETAILS PaymentState = 5 + // Insufficient local balance. + PaymentState_FAILED_INSUFFICIENT_BALANCE PaymentState = 6 +) + +// Enum value maps for PaymentState. +var ( + PaymentState_name = map[int32]string{ + 0: "IN_FLIGHT", + 1: "SUCCEEDED", + 2: "FAILED_TIMEOUT", + 3: "FAILED_NO_ROUTE", + 4: "FAILED_ERROR", + 5: "FAILED_INCORRECT_PAYMENT_DETAILS", + 6: "FAILED_INSUFFICIENT_BALANCE", + } + PaymentState_value = map[string]int32{ + "IN_FLIGHT": 0, + "SUCCEEDED": 1, + "FAILED_TIMEOUT": 2, + "FAILED_NO_ROUTE": 3, + "FAILED_ERROR": 4, + "FAILED_INCORRECT_PAYMENT_DETAILS": 5, + "FAILED_INSUFFICIENT_BALANCE": 6, + } +) + +func (x PaymentState) Enum() *PaymentState { + p := new(PaymentState) + *p = x + return p +} + +func (x PaymentState) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (PaymentState) Descriptor() protoreflect.EnumDescriptor { + return file_routerrpc_router_proto_enumTypes[1].Descriptor() +} + +func (PaymentState) Type() protoreflect.EnumType { + return &file_routerrpc_router_proto_enumTypes[1] +} + +func (x PaymentState) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use PaymentState.Descriptor instead. +func (PaymentState) EnumDescriptor() ([]byte, []int) { + return file_routerrpc_router_proto_rawDescGZIP(), []int{1} +} + type ResolveHoldForwardAction int32 const ( @@ -188,11 +242,11 @@ func (x ResolveHoldForwardAction) String() string { } func (ResolveHoldForwardAction) Descriptor() protoreflect.EnumDescriptor { - return file_routerrpc_router_proto_enumTypes[1].Descriptor() + return file_routerrpc_router_proto_enumTypes[2].Descriptor() } func (ResolveHoldForwardAction) Type() protoreflect.EnumType { - return &file_routerrpc_router_proto_enumTypes[1] + return &file_routerrpc_router_proto_enumTypes[2] } func (x ResolveHoldForwardAction) Number() protoreflect.EnumNumber { @@ -201,7 +255,7 @@ func (x ResolveHoldForwardAction) Number() protoreflect.EnumNumber { // Deprecated: Use ResolveHoldForwardAction.Descriptor instead. func (ResolveHoldForwardAction) EnumDescriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{1} + return file_routerrpc_router_proto_rawDescGZIP(), []int{2} } type ChanStatusAction int32 @@ -237,11 +291,11 @@ func (x ChanStatusAction) String() string { } func (ChanStatusAction) Descriptor() protoreflect.EnumDescriptor { - return file_routerrpc_router_proto_enumTypes[2].Descriptor() + return file_routerrpc_router_proto_enumTypes[3].Descriptor() } func (ChanStatusAction) Type() protoreflect.EnumType { - return &file_routerrpc_router_proto_enumTypes[2] + return &file_routerrpc_router_proto_enumTypes[3] } func (x ChanStatusAction) Number() protoreflect.EnumNumber { @@ -250,7 +304,7 @@ func (x ChanStatusAction) Number() protoreflect.EnumNumber { // Deprecated: Use ChanStatusAction.Descriptor instead. func (ChanStatusAction) EnumDescriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{2} + return file_routerrpc_router_proto_rawDescGZIP(), []int{3} } type MissionControlConfig_ProbabilityModel int32 @@ -283,11 +337,11 @@ func (x MissionControlConfig_ProbabilityModel) String() string { } func (MissionControlConfig_ProbabilityModel) Descriptor() protoreflect.EnumDescriptor { - return file_routerrpc_router_proto_enumTypes[3].Descriptor() + return file_routerrpc_router_proto_enumTypes[4].Descriptor() } func (MissionControlConfig_ProbabilityModel) Type() protoreflect.EnumType { - return &file_routerrpc_router_proto_enumTypes[3] + return &file_routerrpc_router_proto_enumTypes[4] } func (x MissionControlConfig_ProbabilityModel) Number() protoreflect.EnumNumber { @@ -296,7 +350,7 @@ func (x MissionControlConfig_ProbabilityModel) Number() protoreflect.EnumNumber // Deprecated: Use MissionControlConfig_ProbabilityModel.Descriptor instead. func (MissionControlConfig_ProbabilityModel) EnumDescriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{18, 0} + return file_routerrpc_router_proto_rawDescGZIP(), []int{19, 0} } type HtlcEvent_EventType int32 @@ -335,11 +389,11 @@ func (x HtlcEvent_EventType) String() string { } func (HtlcEvent_EventType) Descriptor() protoreflect.EnumDescriptor { - return file_routerrpc_router_proto_enumTypes[4].Descriptor() + return file_routerrpc_router_proto_enumTypes[5].Descriptor() } func (HtlcEvent_EventType) Type() protoreflect.EnumType { - return &file_routerrpc_router_proto_enumTypes[4] + return &file_routerrpc_router_proto_enumTypes[5] } func (x HtlcEvent_EventType) Number() protoreflect.EnumNumber { @@ -348,11 +402,14 @@ func (x HtlcEvent_EventType) Number() protoreflect.EnumNumber { // Deprecated: Use HtlcEvent_EventType.Descriptor instead. func (HtlcEvent_EventType) EnumDescriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{26, 0} + return file_routerrpc_router_proto_rawDescGZIP(), []int{27, 0} } type SendPaymentRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The identity pubkey of the payment recipient Dest []byte `protobuf:"bytes,1,opt,name=dest,proto3" json:"dest,omitempty"` // Number of satoshis to send. @@ -383,6 +440,12 @@ type SendPaymentRequest struct { // // The fields fee_limit_sat and fee_limit_msat are mutually exclusive. FeeLimitSat int64 `protobuf:"varint,7,opt,name=fee_limit_sat,json=feeLimitSat,proto3" json:"fee_limit_sat,omitempty"` + // Deprecated, use outgoing_chan_ids. The channel id of the channel that must + // be taken to the first hop. If zero, any channel may be used (unless + // outgoing_chan_ids are set). + // + // Deprecated: Marked as deprecated in routerrpc/router.proto. + OutgoingChanId uint64 `protobuf:"varint,8,opt,name=outgoing_chan_id,json=outgoingChanId,proto3" json:"outgoing_chan_id,omitempty"` // An optional maximum total time lock for the route. This should not // exceed lnd's `--max-cltv-expiry` setting. If zero, then the value of // `--max-cltv-expiry` is enforced. @@ -394,7 +457,7 @@ type SendPaymentRequest struct { // application specific data during the payment attempt. Record types are // required to be in the custom range >= 65536. When using REST, the values // must be encoded as base64. - DestCustomRecords map[uint64][]byte `protobuf:"bytes,11,rep,name=dest_custom_records,json=destCustomRecords,proto3" json:"dest_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + DestCustomRecords map[uint64][]byte `protobuf:"bytes,11,rep,name=dest_custom_records,json=destCustomRecords,proto3" json:"dest_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // Number of millisatoshis to send. // // The fields amt and amt_msat are mutually exclusive. @@ -450,16 +513,16 @@ type SendPaymentRequest struct { // specific data during the payment attempt. Record types are required to be in // the custom range >= 65536. When using REST, the values must be encoded as // base64. - FirstHopCustomRecords map[uint64][]byte `protobuf:"bytes,25,rep,name=first_hop_custom_records,json=firstHopCustomRecords,proto3" json:"first_hop_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + FirstHopCustomRecords map[uint64][]byte `protobuf:"bytes,25,rep,name=first_hop_custom_records,json=firstHopCustomRecords,proto3" json:"first_hop_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *SendPaymentRequest) Reset() { *x = SendPaymentRequest{} - mi := &file_routerrpc_router_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SendPaymentRequest) String() string { @@ -470,7 +533,7 @@ func (*SendPaymentRequest) ProtoMessage() {} func (x *SendPaymentRequest) ProtoReflect() protoreflect.Message { mi := &file_routerrpc_router_proto_msgTypes[0] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -534,6 +597,14 @@ func (x *SendPaymentRequest) GetFeeLimitSat() int64 { return 0 } +// Deprecated: Marked as deprecated in routerrpc/router.proto. +func (x *SendPaymentRequest) GetOutgoingChanId() uint64 { + if x != nil { + return x.OutgoingChanId + } + return 0 +} + func (x *SendPaymentRequest) GetCltvLimit() int32 { if x != nil { return x.CltvLimit @@ -654,21 +725,24 @@ func (x *SendPaymentRequest) GetFirstHopCustomRecords() map[uint64][]byte { } type TrackPaymentRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The hash of the payment to look up. PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"` // If set, only the final payment update is streamed back. Intermediate updates // that show which htlcs are still in flight are suppressed. NoInflightUpdates bool `protobuf:"varint,2,opt,name=no_inflight_updates,json=noInflightUpdates,proto3" json:"no_inflight_updates,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *TrackPaymentRequest) Reset() { *x = TrackPaymentRequest{} - mi := &file_routerrpc_router_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *TrackPaymentRequest) String() string { @@ -679,7 +753,7 @@ func (*TrackPaymentRequest) ProtoMessage() {} func (x *TrackPaymentRequest) ProtoReflect() protoreflect.Message { mi := &file_routerrpc_router_proto_msgTypes[1] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -709,19 +783,22 @@ func (x *TrackPaymentRequest) GetNoInflightUpdates() bool { } type TrackPaymentsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // If set, only the final payment updates are streamed back. Intermediate // updates that show which htlcs are still in flight are suppressed. NoInflightUpdates bool `protobuf:"varint,1,opt,name=no_inflight_updates,json=noInflightUpdates,proto3" json:"no_inflight_updates,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *TrackPaymentsRequest) Reset() { *x = TrackPaymentsRequest{} - mi := &file_routerrpc_router_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *TrackPaymentsRequest) String() string { @@ -732,7 +809,7 @@ func (*TrackPaymentsRequest) ProtoMessage() {} func (x *TrackPaymentsRequest) ProtoReflect() protoreflect.Message { mi := &file_routerrpc_router_proto_msgTypes[2] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -755,7 +832,10 @@ func (x *TrackPaymentsRequest) GetNoInflightUpdates() bool { } type RouteFeeRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The destination one wishes to obtain a routing fee quote to. If set, this // parameter requires the amt_sat parameter also to be set. This parameter // combination triggers a graph based routing fee estimation as opposed to a @@ -777,23 +857,17 @@ type RouteFeeRequest struct { // timeout is reached. Note that the probing process itself can take longer // than the timeout if the HTLC becomes delayed or stuck. Canceling the context // of this call will not cancel the payment loop, the duration is only - // controlled by the timeout parameter. If the field is not set or is - // explicitly set to zero, the default value of 60 seconds will be applied. + // controlled by the timeout parameter. Timeout uint32 `protobuf:"varint,4,opt,name=timeout,proto3" json:"timeout,omitempty"` - // The channel ids of the channels that are allowed for the first hop. If - // empty, any channel may be used. This field is applicable to both - // graph-based fee estimation (using dest + amt_sat) and probe-based - // estimation (using payment_request). - OutgoingChanIds []uint64 `protobuf:"varint,5,rep,packed,name=outgoing_chan_ids,json=outgoingChanIds,proto3" json:"outgoing_chan_ids,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *RouteFeeRequest) Reset() { *x = RouteFeeRequest{} - mi := &file_routerrpc_router_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *RouteFeeRequest) String() string { @@ -804,7 +878,7 @@ func (*RouteFeeRequest) ProtoMessage() {} func (x *RouteFeeRequest) ProtoReflect() protoreflect.Message { mi := &file_routerrpc_router_proto_msgTypes[3] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -847,15 +921,11 @@ func (x *RouteFeeRequest) GetTimeout() uint32 { return 0 } -func (x *RouteFeeRequest) GetOutgoingChanIds() []uint64 { - if x != nil { - return x.OutgoingChanIds - } - return nil -} - type RouteFeeResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // A lower bound of the estimated fee to the target destination within the // network, expressed in milli-satoshis. RoutingFeeMsat int64 `protobuf:"varint,1,opt,name=routing_fee_msat,json=routingFeeMsat,proto3" json:"routing_fee_msat,omitempty"` @@ -866,15 +936,15 @@ type RouteFeeResponse struct { // An indication whether a probing payment succeeded or whether and why it // failed. FAILURE_REASON_NONE indicates success. FailureReason lnrpc.PaymentFailureReason `protobuf:"varint,5,opt,name=failure_reason,json=failureReason,proto3,enum=lnrpc.PaymentFailureReason" json:"failure_reason,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *RouteFeeResponse) Reset() { *x = RouteFeeResponse{} - mi := &file_routerrpc_router_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *RouteFeeResponse) String() string { @@ -885,7 +955,7 @@ func (*RouteFeeResponse) ProtoMessage() {} func (x *RouteFeeResponse) ProtoReflect() protoreflect.Message { mi := &file_routerrpc_router_proto_msgTypes[4] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -922,7 +992,10 @@ func (x *RouteFeeResponse) GetFailureReason() lnrpc.PaymentFailureReason { } type SendToRouteRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The payment hash to use for the HTLC. PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"` // Route that should be used to attempt to complete the payment. @@ -937,16 +1010,16 @@ type SendToRouteRequest struct { // specific data during the payment attempt. Record types are required to be in // the custom range >= 65536. When using REST, the values must be encoded as // base64. - FirstHopCustomRecords map[uint64][]byte `protobuf:"bytes,4,rep,name=first_hop_custom_records,json=firstHopCustomRecords,proto3" json:"first_hop_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + FirstHopCustomRecords map[uint64][]byte `protobuf:"bytes,4,rep,name=first_hop_custom_records,json=firstHopCustomRecords,proto3" json:"first_hop_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *SendToRouteRequest) Reset() { *x = SendToRouteRequest{} - mi := &file_routerrpc_router_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SendToRouteRequest) String() string { @@ -957,7 +1030,7 @@ func (*SendToRouteRequest) ProtoMessage() {} func (x *SendToRouteRequest) ProtoReflect() protoreflect.Message { mi := &file_routerrpc_router_proto_msgTypes[5] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1000,17 +1073,76 @@ func (x *SendToRouteRequest) GetFirstHopCustomRecords() map[uint64][]byte { return nil } -type ResetMissionControlRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields +type SendToRouteResponse struct { + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The preimage obtained by making the payment. + Preimage []byte `protobuf:"bytes,1,opt,name=preimage,proto3" json:"preimage,omitempty"` + // The failure message in case the payment failed. + Failure *lnrpc.Failure `protobuf:"bytes,2,opt,name=failure,proto3" json:"failure,omitempty"` +} + +func (x *SendToRouteResponse) Reset() { + *x = SendToRouteResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SendToRouteResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendToRouteResponse) ProtoMessage() {} + +func (x *SendToRouteResponse) ProtoReflect() protoreflect.Message { + mi := &file_routerrpc_router_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendToRouteResponse.ProtoReflect.Descriptor instead. +func (*SendToRouteResponse) Descriptor() ([]byte, []int) { + return file_routerrpc_router_proto_rawDescGZIP(), []int{6} +} + +func (x *SendToRouteResponse) GetPreimage() []byte { + if x != nil { + return x.Preimage + } + return nil +} + +func (x *SendToRouteResponse) GetFailure() *lnrpc.Failure { + if x != nil { + return x.Failure + } + return nil +} + +type ResetMissionControlRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *ResetMissionControlRequest) Reset() { *x = ResetMissionControlRequest{} - mi := &file_routerrpc_router_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ResetMissionControlRequest) String() string { @@ -1020,8 +1152,8 @@ func (x *ResetMissionControlRequest) String() string { func (*ResetMissionControlRequest) ProtoMessage() {} func (x *ResetMissionControlRequest) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[6] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1033,20 +1165,22 @@ func (x *ResetMissionControlRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ResetMissionControlRequest.ProtoReflect.Descriptor instead. func (*ResetMissionControlRequest) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{6} + return file_routerrpc_router_proto_rawDescGZIP(), []int{7} } type ResetMissionControlResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *ResetMissionControlResponse) Reset() { *x = ResetMissionControlResponse{} - mi := &file_routerrpc_router_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ResetMissionControlResponse) String() string { @@ -1056,8 +1190,8 @@ func (x *ResetMissionControlResponse) String() string { func (*ResetMissionControlResponse) ProtoMessage() {} func (x *ResetMissionControlResponse) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[7] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1069,20 +1203,22 @@ func (x *ResetMissionControlResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ResetMissionControlResponse.ProtoReflect.Descriptor instead. func (*ResetMissionControlResponse) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{7} + return file_routerrpc_router_proto_rawDescGZIP(), []int{8} } type QueryMissionControlRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *QueryMissionControlRequest) Reset() { *x = QueryMissionControlRequest{} - mi := &file_routerrpc_router_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *QueryMissionControlRequest) String() string { @@ -1092,8 +1228,8 @@ func (x *QueryMissionControlRequest) String() string { func (*QueryMissionControlRequest) ProtoMessage() {} func (x *QueryMissionControlRequest) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[8] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1105,23 +1241,26 @@ func (x *QueryMissionControlRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryMissionControlRequest.ProtoReflect.Descriptor instead. func (*QueryMissionControlRequest) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{8} + return file_routerrpc_router_proto_rawDescGZIP(), []int{9} } // QueryMissionControlResponse contains mission control state. type QueryMissionControlResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Node pair-level mission control state. - Pairs []*PairHistory `protobuf:"bytes,2,rep,name=pairs,proto3" json:"pairs,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Node pair-level mission control state. + Pairs []*PairHistory `protobuf:"bytes,2,rep,name=pairs,proto3" json:"pairs,omitempty"` } func (x *QueryMissionControlResponse) Reset() { *x = QueryMissionControlResponse{} - mi := &file_routerrpc_router_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *QueryMissionControlResponse) String() string { @@ -1131,8 +1270,8 @@ func (x *QueryMissionControlResponse) String() string { func (*QueryMissionControlResponse) ProtoMessage() {} func (x *QueryMissionControlResponse) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[9] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1144,7 +1283,7 @@ func (x *QueryMissionControlResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryMissionControlResponse.ProtoReflect.Descriptor instead. func (*QueryMissionControlResponse) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{9} + return file_routerrpc_router_proto_rawDescGZIP(), []int{10} } func (x *QueryMissionControlResponse) GetPairs() []*PairHistory { @@ -1155,22 +1294,25 @@ func (x *QueryMissionControlResponse) GetPairs() []*PairHistory { } type XImportMissionControlRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Node pair-level mission control state to be imported. Pairs []*PairHistory `protobuf:"bytes,1,rep,name=pairs,proto3" json:"pairs,omitempty"` // Whether to force override MC pair history. Note that even with force // override the failure pair is imported before the success pair and both // still clamp existing failure/success amounts. - Force bool `protobuf:"varint,2,opt,name=force,proto3" json:"force,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Force bool `protobuf:"varint,2,opt,name=force,proto3" json:"force,omitempty"` } func (x *XImportMissionControlRequest) Reset() { *x = XImportMissionControlRequest{} - mi := &file_routerrpc_router_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *XImportMissionControlRequest) String() string { @@ -1180,8 +1322,8 @@ func (x *XImportMissionControlRequest) String() string { func (*XImportMissionControlRequest) ProtoMessage() {} func (x *XImportMissionControlRequest) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[10] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1193,7 +1335,7 @@ func (x *XImportMissionControlRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use XImportMissionControlRequest.ProtoReflect.Descriptor instead. func (*XImportMissionControlRequest) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{10} + return file_routerrpc_router_proto_rawDescGZIP(), []int{11} } func (x *XImportMissionControlRequest) GetPairs() []*PairHistory { @@ -1211,16 +1353,18 @@ func (x *XImportMissionControlRequest) GetForce() bool { } type XImportMissionControlResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *XImportMissionControlResponse) Reset() { *x = XImportMissionControlResponse{} - mi := &file_routerrpc_router_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *XImportMissionControlResponse) String() string { @@ -1230,8 +1374,8 @@ func (x *XImportMissionControlResponse) String() string { func (*XImportMissionControlResponse) ProtoMessage() {} func (x *XImportMissionControlResponse) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[11] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1243,26 +1387,29 @@ func (x *XImportMissionControlResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use XImportMissionControlResponse.ProtoReflect.Descriptor instead. func (*XImportMissionControlResponse) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{11} + return file_routerrpc_router_proto_rawDescGZIP(), []int{12} } // PairHistory contains the mission control state for a particular node pair. type PairHistory struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The source node pubkey of the pair. NodeFrom []byte `protobuf:"bytes,1,opt,name=node_from,json=nodeFrom,proto3" json:"node_from,omitempty"` // The destination node pubkey of the pair. - NodeTo []byte `protobuf:"bytes,2,opt,name=node_to,json=nodeTo,proto3" json:"node_to,omitempty"` - History *PairData `protobuf:"bytes,7,opt,name=history,proto3" json:"history,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + NodeTo []byte `protobuf:"bytes,2,opt,name=node_to,json=nodeTo,proto3" json:"node_to,omitempty"` + History *PairData `protobuf:"bytes,7,opt,name=history,proto3" json:"history,omitempty"` } func (x *PairHistory) Reset() { *x = PairHistory{} - mi := &file_routerrpc_router_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PairHistory) String() string { @@ -1272,8 +1419,8 @@ func (x *PairHistory) String() string { func (*PairHistory) ProtoMessage() {} func (x *PairHistory) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[12] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1285,7 +1432,7 @@ func (x *PairHistory) ProtoReflect() protoreflect.Message { // Deprecated: Use PairHistory.ProtoReflect.Descriptor instead. func (*PairHistory) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{12} + return file_routerrpc_router_proto_rawDescGZIP(), []int{13} } func (x *PairHistory) GetNodeFrom() []byte { @@ -1310,7 +1457,10 @@ func (x *PairHistory) GetHistory() *PairData { } type PairData struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Time of last failure. FailTime int64 `protobuf:"varint,1,opt,name=fail_time,json=failTime,proto3" json:"fail_time,omitempty"` // Lowest amount that failed to forward rounded to whole sats. This may be @@ -1325,15 +1475,15 @@ type PairData struct { SuccessAmtSat int64 `protobuf:"varint,6,opt,name=success_amt_sat,json=successAmtSat,proto3" json:"success_amt_sat,omitempty"` // Highest amount that we could successfully forward in millisats. SuccessAmtMsat int64 `protobuf:"varint,7,opt,name=success_amt_msat,json=successAmtMsat,proto3" json:"success_amt_msat,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *PairData) Reset() { *x = PairData{} - mi := &file_routerrpc_router_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PairData) String() string { @@ -1343,8 +1493,8 @@ func (x *PairData) String() string { func (*PairData) ProtoMessage() {} func (x *PairData) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[13] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1356,7 +1506,7 @@ func (x *PairData) ProtoReflect() protoreflect.Message { // Deprecated: Use PairData.ProtoReflect.Descriptor instead. func (*PairData) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{13} + return file_routerrpc_router_proto_rawDescGZIP(), []int{14} } func (x *PairData) GetFailTime() int64 { @@ -1402,16 +1552,18 @@ func (x *PairData) GetSuccessAmtMsat() int64 { } type GetMissionControlConfigRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *GetMissionControlConfigRequest) Reset() { *x = GetMissionControlConfigRequest{} - mi := &file_routerrpc_router_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetMissionControlConfigRequest) String() string { @@ -1421,8 +1573,8 @@ func (x *GetMissionControlConfigRequest) String() string { func (*GetMissionControlConfigRequest) ProtoMessage() {} func (x *GetMissionControlConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[14] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1434,22 +1586,25 @@ func (x *GetMissionControlConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMissionControlConfigRequest.ProtoReflect.Descriptor instead. func (*GetMissionControlConfigRequest) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{14} + return file_routerrpc_router_proto_rawDescGZIP(), []int{15} } type GetMissionControlConfigResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Mission control's currently active config. - Config *MissionControlConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Mission control's currently active config. + Config *MissionControlConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` } func (x *GetMissionControlConfigResponse) Reset() { *x = GetMissionControlConfigResponse{} - mi := &file_routerrpc_router_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetMissionControlConfigResponse) String() string { @@ -1459,8 +1614,8 @@ func (x *GetMissionControlConfigResponse) String() string { func (*GetMissionControlConfigResponse) ProtoMessage() {} func (x *GetMissionControlConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[15] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1472,7 +1627,7 @@ func (x *GetMissionControlConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMissionControlConfigResponse.ProtoReflect.Descriptor instead. func (*GetMissionControlConfigResponse) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{15} + return file_routerrpc_router_proto_rawDescGZIP(), []int{16} } func (x *GetMissionControlConfigResponse) GetConfig() *MissionControlConfig { @@ -1483,19 +1638,22 @@ func (x *GetMissionControlConfigResponse) GetConfig() *MissionControlConfig { } type SetMissionControlConfigRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The config to set for mission control. Note that all values *must* be set, // because the full config will be applied. - Config *MissionControlConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Config *MissionControlConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` } func (x *SetMissionControlConfigRequest) Reset() { *x = SetMissionControlConfigRequest{} - mi := &file_routerrpc_router_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SetMissionControlConfigRequest) String() string { @@ -1505,8 +1663,8 @@ func (x *SetMissionControlConfigRequest) String() string { func (*SetMissionControlConfigRequest) ProtoMessage() {} func (x *SetMissionControlConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[16] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1518,7 +1676,7 @@ func (x *SetMissionControlConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetMissionControlConfigRequest.ProtoReflect.Descriptor instead. func (*SetMissionControlConfigRequest) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{16} + return file_routerrpc_router_proto_rawDescGZIP(), []int{17} } func (x *SetMissionControlConfigRequest) GetConfig() *MissionControlConfig { @@ -1529,16 +1687,18 @@ func (x *SetMissionControlConfigRequest) GetConfig() *MissionControlConfig { } type SetMissionControlConfigResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *SetMissionControlConfigResponse) Reset() { *x = SetMissionControlConfigResponse{} - mi := &file_routerrpc_router_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SetMissionControlConfigResponse) String() string { @@ -1548,8 +1708,8 @@ func (x *SetMissionControlConfigResponse) String() string { func (*SetMissionControlConfigResponse) ProtoMessage() {} func (x *SetMissionControlConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[17] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1561,11 +1721,14 @@ func (x *SetMissionControlConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetMissionControlConfigResponse.ProtoReflect.Descriptor instead. func (*SetMissionControlConfigResponse) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{17} + return file_routerrpc_router_proto_rawDescGZIP(), []int{18} } type MissionControlConfig struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Deprecated, use AprioriParameters. The amount of time mission control will // take to restore a penalized node or channel back to 50% success probability, // expressed in seconds. Setting this value to a higher value will penalize @@ -1601,20 +1764,20 @@ type MissionControlConfig struct { Model MissionControlConfig_ProbabilityModel `protobuf:"varint,6,opt,name=model,proto3,enum=routerrpc.MissionControlConfig_ProbabilityModel" json:"model,omitempty"` // EstimatorConfig is populated dependent on the estimator type. // - // Types that are valid to be assigned to EstimatorConfig: + // Types that are assignable to EstimatorConfig: // // *MissionControlConfig_Apriori // *MissionControlConfig_Bimodal EstimatorConfig isMissionControlConfig_EstimatorConfig `protobuf_oneof:"EstimatorConfig"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *MissionControlConfig) Reset() { *x = MissionControlConfig{} - mi := &file_routerrpc_router_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *MissionControlConfig) String() string { @@ -1624,8 +1787,8 @@ func (x *MissionControlConfig) String() string { func (*MissionControlConfig) ProtoMessage() {} func (x *MissionControlConfig) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[18] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1637,7 +1800,7 @@ func (x *MissionControlConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use MissionControlConfig.ProtoReflect.Descriptor instead. func (*MissionControlConfig) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{18} + return file_routerrpc_router_proto_rawDescGZIP(), []int{19} } // Deprecated: Marked as deprecated in routerrpc/router.proto. @@ -1685,27 +1848,23 @@ func (x *MissionControlConfig) GetModel() MissionControlConfig_ProbabilityModel return MissionControlConfig_APRIORI } -func (x *MissionControlConfig) GetEstimatorConfig() isMissionControlConfig_EstimatorConfig { - if x != nil { - return x.EstimatorConfig +func (m *MissionControlConfig) GetEstimatorConfig() isMissionControlConfig_EstimatorConfig { + if m != nil { + return m.EstimatorConfig } return nil } func (x *MissionControlConfig) GetApriori() *AprioriParameters { - if x != nil { - if x, ok := x.EstimatorConfig.(*MissionControlConfig_Apriori); ok { - return x.Apriori - } + if x, ok := x.GetEstimatorConfig().(*MissionControlConfig_Apriori); ok { + return x.Apriori } return nil } func (x *MissionControlConfig) GetBimodal() *BimodalParameters { - if x != nil { - if x, ok := x.EstimatorConfig.(*MissionControlConfig_Bimodal); ok { - return x.Bimodal - } + if x, ok := x.GetEstimatorConfig().(*MissionControlConfig_Bimodal); ok { + return x.Bimodal } return nil } @@ -1727,7 +1886,10 @@ func (*MissionControlConfig_Apriori) isMissionControlConfig_EstimatorConfig() {} func (*MissionControlConfig_Bimodal) isMissionControlConfig_EstimatorConfig() {} type BimodalParameters struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // NodeWeight defines how strongly other previous forwardings on channels of a // router should be taken into account when computing a channel's probability // to route. The allowed values are in the range [0, 1], where a value of 0 @@ -1743,16 +1905,16 @@ type BimodalParameters struct { // DecayTime describes the information decay of knowledge about previous // successes and failures in channels. The smaller the decay time, the quicker // we forget about past forwardings. - DecayTime uint64 `protobuf:"varint,3,opt,name=decay_time,json=decayTime,proto3" json:"decay_time,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + DecayTime uint64 `protobuf:"varint,3,opt,name=decay_time,json=decayTime,proto3" json:"decay_time,omitempty"` } func (x *BimodalParameters) Reset() { *x = BimodalParameters{} - mi := &file_routerrpc_router_proto_msgTypes[19] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *BimodalParameters) String() string { @@ -1762,8 +1924,8 @@ func (x *BimodalParameters) String() string { func (*BimodalParameters) ProtoMessage() {} func (x *BimodalParameters) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[19] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1775,7 +1937,7 @@ func (x *BimodalParameters) ProtoReflect() protoreflect.Message { // Deprecated: Use BimodalParameters.ProtoReflect.Descriptor instead. func (*BimodalParameters) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{19} + return file_routerrpc_router_proto_rawDescGZIP(), []int{20} } func (x *BimodalParameters) GetNodeWeight() float64 { @@ -1800,7 +1962,10 @@ func (x *BimodalParameters) GetDecayTime() uint64 { } type AprioriParameters struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The amount of time mission control will take to restore a penalized node // or channel back to 50% success probability, expressed in seconds. Setting // this value to a higher value will penalize failures for longer, making @@ -1824,15 +1989,15 @@ type AprioriParameters struct { // applied. A value of 1.0 disables the capacity factor. Allowed values are in // [0.75, 1.0]. CapacityFraction float64 `protobuf:"fixed64,4,opt,name=capacity_fraction,json=capacityFraction,proto3" json:"capacity_fraction,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *AprioriParameters) Reset() { *x = AprioriParameters{} - mi := &file_routerrpc_router_proto_msgTypes[20] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *AprioriParameters) String() string { @@ -1842,8 +2007,8 @@ func (x *AprioriParameters) String() string { func (*AprioriParameters) ProtoMessage() {} func (x *AprioriParameters) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[20] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1855,7 +2020,7 @@ func (x *AprioriParameters) ProtoReflect() protoreflect.Message { // Deprecated: Use AprioriParameters.ProtoReflect.Descriptor instead. func (*AprioriParameters) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{20} + return file_routerrpc_router_proto_rawDescGZIP(), []int{21} } func (x *AprioriParameters) GetHalfLifeSeconds() uint64 { @@ -1887,22 +2052,25 @@ func (x *AprioriParameters) GetCapacityFraction() float64 { } type QueryProbabilityRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The source node pubkey of the pair. FromNode []byte `protobuf:"bytes,1,opt,name=from_node,json=fromNode,proto3" json:"from_node,omitempty"` // The destination node pubkey of the pair. ToNode []byte `protobuf:"bytes,2,opt,name=to_node,json=toNode,proto3" json:"to_node,omitempty"` // The amount for which to calculate a probability. - AmtMsat int64 `protobuf:"varint,3,opt,name=amt_msat,json=amtMsat,proto3" json:"amt_msat,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + AmtMsat int64 `protobuf:"varint,3,opt,name=amt_msat,json=amtMsat,proto3" json:"amt_msat,omitempty"` } func (x *QueryProbabilityRequest) Reset() { *x = QueryProbabilityRequest{} - mi := &file_routerrpc_router_proto_msgTypes[21] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *QueryProbabilityRequest) String() string { @@ -1912,8 +2080,8 @@ func (x *QueryProbabilityRequest) String() string { func (*QueryProbabilityRequest) ProtoMessage() {} func (x *QueryProbabilityRequest) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[21] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1925,7 +2093,7 @@ func (x *QueryProbabilityRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryProbabilityRequest.ProtoReflect.Descriptor instead. func (*QueryProbabilityRequest) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{21} + return file_routerrpc_router_proto_rawDescGZIP(), []int{22} } func (x *QueryProbabilityRequest) GetFromNode() []byte { @@ -1950,20 +2118,23 @@ func (x *QueryProbabilityRequest) GetAmtMsat() int64 { } type QueryProbabilityResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The success probability for the requested pair. Probability float64 `protobuf:"fixed64,1,opt,name=probability,proto3" json:"probability,omitempty"` // The historical data for the requested pair. - History *PairData `protobuf:"bytes,2,opt,name=history,proto3" json:"history,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + History *PairData `protobuf:"bytes,2,opt,name=history,proto3" json:"history,omitempty"` } func (x *QueryProbabilityResponse) Reset() { *x = QueryProbabilityResponse{} - mi := &file_routerrpc_router_proto_msgTypes[22] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *QueryProbabilityResponse) String() string { @@ -1973,8 +2144,8 @@ func (x *QueryProbabilityResponse) String() string { func (*QueryProbabilityResponse) ProtoMessage() {} func (x *QueryProbabilityResponse) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[22] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1986,7 +2157,7 @@ func (x *QueryProbabilityResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryProbabilityResponse.ProtoReflect.Descriptor instead. func (*QueryProbabilityResponse) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{22} + return file_routerrpc_router_proto_rawDescGZIP(), []int{23} } func (x *QueryProbabilityResponse) GetProbability() float64 { @@ -2004,7 +2175,10 @@ func (x *QueryProbabilityResponse) GetHistory() *PairData { } type BuildRouteRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The amount to send expressed in msat. If set to zero, the minimum routable // amount is used. AmtMsat int64 `protobuf:"varint,1,opt,name=amt_msat,json=amtMsat,proto3" json:"amt_msat,omitempty"` @@ -2025,16 +2199,16 @@ type BuildRouteRequest struct { // specific data during the payment attempt. Record types are required to be in // the custom range >= 65536. When using REST, the values must be encoded as // base64. - FirstHopCustomRecords map[uint64][]byte `protobuf:"bytes,6,rep,name=first_hop_custom_records,json=firstHopCustomRecords,proto3" json:"first_hop_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + FirstHopCustomRecords map[uint64][]byte `protobuf:"bytes,6,rep,name=first_hop_custom_records,json=firstHopCustomRecords,proto3" json:"first_hop_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *BuildRouteRequest) Reset() { *x = BuildRouteRequest{} - mi := &file_routerrpc_router_proto_msgTypes[23] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *BuildRouteRequest) String() string { @@ -2044,8 +2218,8 @@ func (x *BuildRouteRequest) String() string { func (*BuildRouteRequest) ProtoMessage() {} func (x *BuildRouteRequest) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[23] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2057,7 +2231,7 @@ func (x *BuildRouteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BuildRouteRequest.ProtoReflect.Descriptor instead. func (*BuildRouteRequest) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{23} + return file_routerrpc_router_proto_rawDescGZIP(), []int{24} } func (x *BuildRouteRequest) GetAmtMsat() int64 { @@ -2103,18 +2277,21 @@ func (x *BuildRouteRequest) GetFirstHopCustomRecords() map[uint64][]byte { } type BuildRouteResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Fully specified route that can be used to execute the payment. - Route *lnrpc.Route `protobuf:"bytes,1,opt,name=route,proto3" json:"route,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Fully specified route that can be used to execute the payment. + Route *lnrpc.Route `protobuf:"bytes,1,opt,name=route,proto3" json:"route,omitempty"` } func (x *BuildRouteResponse) Reset() { *x = BuildRouteResponse{} - mi := &file_routerrpc_router_proto_msgTypes[24] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *BuildRouteResponse) String() string { @@ -2124,8 +2301,8 @@ func (x *BuildRouteResponse) String() string { func (*BuildRouteResponse) ProtoMessage() {} func (x *BuildRouteResponse) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[24] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2137,7 +2314,7 @@ func (x *BuildRouteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BuildRouteResponse.ProtoReflect.Descriptor instead. func (*BuildRouteResponse) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{24} + return file_routerrpc_router_proto_rawDescGZIP(), []int{25} } func (x *BuildRouteResponse) GetRoute() *lnrpc.Route { @@ -2148,16 +2325,18 @@ func (x *BuildRouteResponse) GetRoute() *lnrpc.Route { } type SubscribeHtlcEventsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *SubscribeHtlcEventsRequest) Reset() { *x = SubscribeHtlcEventsRequest{} - mi := &file_routerrpc_router_proto_msgTypes[25] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SubscribeHtlcEventsRequest) String() string { @@ -2167,8 +2346,8 @@ func (x *SubscribeHtlcEventsRequest) String() string { func (*SubscribeHtlcEventsRequest) ProtoMessage() {} func (x *SubscribeHtlcEventsRequest) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[25] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2180,7 +2359,7 @@ func (x *SubscribeHtlcEventsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SubscribeHtlcEventsRequest.ProtoReflect.Descriptor instead. func (*SubscribeHtlcEventsRequest) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{25} + return file_routerrpc_router_proto_rawDescGZIP(), []int{26} } // HtlcEvent contains the htlc event that was processed. These are served on a @@ -2190,7 +2369,10 @@ func (*SubscribeHtlcEventsRequest) Descriptor() ([]byte, []int) { // should be de-duplicated by the htlc's unique combination of incoming and // outgoing channel id and htlc id. [EXPERIMENTAL] type HtlcEvent struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The short channel id that the incoming htlc arrived at our node on. This // value is zero for sends. IncomingChannelId uint64 `protobuf:"varint,1,opt,name=incoming_channel_id,json=incomingChannelId,proto3" json:"incoming_channel_id,omitempty"` @@ -2208,7 +2390,7 @@ type HtlcEvent struct { // The event type indicates whether the htlc was part of a send, receive or // forward. EventType HtlcEvent_EventType `protobuf:"varint,6,opt,name=event_type,json=eventType,proto3,enum=routerrpc.HtlcEvent_EventType" json:"event_type,omitempty"` - // Types that are valid to be assigned to Event: + // Types that are assignable to Event: // // *HtlcEvent_ForwardEvent // *HtlcEvent_ForwardFailEvent @@ -2216,16 +2398,16 @@ type HtlcEvent struct { // *HtlcEvent_LinkFailEvent // *HtlcEvent_SubscribedEvent // *HtlcEvent_FinalHtlcEvent - Event isHtlcEvent_Event `protobuf_oneof:"event"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Event isHtlcEvent_Event `protobuf_oneof:"event"` } func (x *HtlcEvent) Reset() { *x = HtlcEvent{} - mi := &file_routerrpc_router_proto_msgTypes[26] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *HtlcEvent) String() string { @@ -2235,8 +2417,8 @@ func (x *HtlcEvent) String() string { func (*HtlcEvent) ProtoMessage() {} func (x *HtlcEvent) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[26] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[27] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2248,7 +2430,7 @@ func (x *HtlcEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use HtlcEvent.ProtoReflect.Descriptor instead. func (*HtlcEvent) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{26} + return file_routerrpc_router_proto_rawDescGZIP(), []int{27} } func (x *HtlcEvent) GetIncomingChannelId() uint64 { @@ -2293,63 +2475,51 @@ func (x *HtlcEvent) GetEventType() HtlcEvent_EventType { return HtlcEvent_UNKNOWN } -func (x *HtlcEvent) GetEvent() isHtlcEvent_Event { - if x != nil { - return x.Event +func (m *HtlcEvent) GetEvent() isHtlcEvent_Event { + if m != nil { + return m.Event } return nil } func (x *HtlcEvent) GetForwardEvent() *ForwardEvent { - if x != nil { - if x, ok := x.Event.(*HtlcEvent_ForwardEvent); ok { - return x.ForwardEvent - } + if x, ok := x.GetEvent().(*HtlcEvent_ForwardEvent); ok { + return x.ForwardEvent } return nil } func (x *HtlcEvent) GetForwardFailEvent() *ForwardFailEvent { - if x != nil { - if x, ok := x.Event.(*HtlcEvent_ForwardFailEvent); ok { - return x.ForwardFailEvent - } + if x, ok := x.GetEvent().(*HtlcEvent_ForwardFailEvent); ok { + return x.ForwardFailEvent } return nil } func (x *HtlcEvent) GetSettleEvent() *SettleEvent { - if x != nil { - if x, ok := x.Event.(*HtlcEvent_SettleEvent); ok { - return x.SettleEvent - } + if x, ok := x.GetEvent().(*HtlcEvent_SettleEvent); ok { + return x.SettleEvent } return nil } func (x *HtlcEvent) GetLinkFailEvent() *LinkFailEvent { - if x != nil { - if x, ok := x.Event.(*HtlcEvent_LinkFailEvent); ok { - return x.LinkFailEvent - } + if x, ok := x.GetEvent().(*HtlcEvent_LinkFailEvent); ok { + return x.LinkFailEvent } return nil } func (x *HtlcEvent) GetSubscribedEvent() *SubscribedEvent { - if x != nil { - if x, ok := x.Event.(*HtlcEvent_SubscribedEvent); ok { - return x.SubscribedEvent - } + if x, ok := x.GetEvent().(*HtlcEvent_SubscribedEvent); ok { + return x.SubscribedEvent } return nil } func (x *HtlcEvent) GetFinalHtlcEvent() *FinalHtlcEvent { - if x != nil { - if x, ok := x.Event.(*HtlcEvent_FinalHtlcEvent); ok { - return x.FinalHtlcEvent - } + if x, ok := x.GetEvent().(*HtlcEvent_FinalHtlcEvent); ok { + return x.FinalHtlcEvent } return nil } @@ -2395,7 +2565,10 @@ func (*HtlcEvent_SubscribedEvent) isHtlcEvent_Event() {} func (*HtlcEvent_FinalHtlcEvent) isHtlcEvent_Event() {} type HtlcInfo struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The timelock on the incoming htlc. IncomingTimelock uint32 `protobuf:"varint,1,opt,name=incoming_timelock,json=incomingTimelock,proto3" json:"incoming_timelock,omitempty"` // The timelock on the outgoing htlc. @@ -2404,15 +2577,15 @@ type HtlcInfo struct { IncomingAmtMsat uint64 `protobuf:"varint,3,opt,name=incoming_amt_msat,json=incomingAmtMsat,proto3" json:"incoming_amt_msat,omitempty"` // The amount of the outgoing htlc. OutgoingAmtMsat uint64 `protobuf:"varint,4,opt,name=outgoing_amt_msat,json=outgoingAmtMsat,proto3" json:"outgoing_amt_msat,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *HtlcInfo) Reset() { *x = HtlcInfo{} - mi := &file_routerrpc_router_proto_msgTypes[27] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *HtlcInfo) String() string { @@ -2422,8 +2595,8 @@ func (x *HtlcInfo) String() string { func (*HtlcInfo) ProtoMessage() {} func (x *HtlcInfo) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[27] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[28] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2435,7 +2608,7 @@ func (x *HtlcInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use HtlcInfo.ProtoReflect.Descriptor instead. func (*HtlcInfo) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{27} + return file_routerrpc_router_proto_rawDescGZIP(), []int{28} } func (x *HtlcInfo) GetIncomingTimelock() uint32 { @@ -2467,18 +2640,21 @@ func (x *HtlcInfo) GetOutgoingAmtMsat() uint64 { } type ForwardEvent struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Info contains details about the htlc that was forwarded. - Info *HtlcInfo `protobuf:"bytes,1,opt,name=info,proto3" json:"info,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Info contains details about the htlc that was forwarded. + Info *HtlcInfo `protobuf:"bytes,1,opt,name=info,proto3" json:"info,omitempty"` } func (x *ForwardEvent) Reset() { *x = ForwardEvent{} - mi := &file_routerrpc_router_proto_msgTypes[28] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ForwardEvent) String() string { @@ -2488,8 +2664,8 @@ func (x *ForwardEvent) String() string { func (*ForwardEvent) ProtoMessage() {} func (x *ForwardEvent) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[28] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[29] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2501,7 +2677,7 @@ func (x *ForwardEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ForwardEvent.ProtoReflect.Descriptor instead. func (*ForwardEvent) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{28} + return file_routerrpc_router_proto_rawDescGZIP(), []int{29} } func (x *ForwardEvent) GetInfo() *HtlcInfo { @@ -2512,16 +2688,18 @@ func (x *ForwardEvent) GetInfo() *HtlcInfo { } type ForwardFailEvent struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *ForwardFailEvent) Reset() { *x = ForwardFailEvent{} - mi := &file_routerrpc_router_proto_msgTypes[29] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ForwardFailEvent) String() string { @@ -2531,8 +2709,8 @@ func (x *ForwardFailEvent) String() string { func (*ForwardFailEvent) ProtoMessage() {} func (x *ForwardFailEvent) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[29] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[30] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2544,22 +2722,25 @@ func (x *ForwardFailEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ForwardFailEvent.ProtoReflect.Descriptor instead. func (*ForwardFailEvent) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{29} + return file_routerrpc_router_proto_rawDescGZIP(), []int{30} } type SettleEvent struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The revealed preimage. - Preimage []byte `protobuf:"bytes,1,opt,name=preimage,proto3" json:"preimage,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The revealed preimage. + Preimage []byte `protobuf:"bytes,1,opt,name=preimage,proto3" json:"preimage,omitempty"` } func (x *SettleEvent) Reset() { *x = SettleEvent{} - mi := &file_routerrpc_router_proto_msgTypes[30] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SettleEvent) String() string { @@ -2569,8 +2750,8 @@ func (x *SettleEvent) String() string { func (*SettleEvent) ProtoMessage() {} func (x *SettleEvent) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[30] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[31] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2582,7 +2763,7 @@ func (x *SettleEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SettleEvent.ProtoReflect.Descriptor instead. func (*SettleEvent) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{30} + return file_routerrpc_router_proto_rawDescGZIP(), []int{31} } func (x *SettleEvent) GetPreimage() []byte { @@ -2593,18 +2774,21 @@ func (x *SettleEvent) GetPreimage() []byte { } type FinalHtlcEvent struct { - state protoimpl.MessageState `protogen:"open.v1"` - Settled bool `protobuf:"varint,1,opt,name=settled,proto3" json:"settled,omitempty"` - Offchain bool `protobuf:"varint,2,opt,name=offchain,proto3" json:"offchain,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Settled bool `protobuf:"varint,1,opt,name=settled,proto3" json:"settled,omitempty"` + Offchain bool `protobuf:"varint,2,opt,name=offchain,proto3" json:"offchain,omitempty"` } func (x *FinalHtlcEvent) Reset() { *x = FinalHtlcEvent{} - mi := &file_routerrpc_router_proto_msgTypes[31] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *FinalHtlcEvent) String() string { @@ -2614,8 +2798,8 @@ func (x *FinalHtlcEvent) String() string { func (*FinalHtlcEvent) ProtoMessage() {} func (x *FinalHtlcEvent) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[31] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[32] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2627,7 +2811,7 @@ func (x *FinalHtlcEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use FinalHtlcEvent.ProtoReflect.Descriptor instead. func (*FinalHtlcEvent) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{31} + return file_routerrpc_router_proto_rawDescGZIP(), []int{32} } func (x *FinalHtlcEvent) GetSettled() bool { @@ -2645,16 +2829,18 @@ func (x *FinalHtlcEvent) GetOffchain() bool { } type SubscribedEvent struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *SubscribedEvent) Reset() { *x = SubscribedEvent{} - mi := &file_routerrpc_router_proto_msgTypes[32] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SubscribedEvent) String() string { @@ -2664,8 +2850,8 @@ func (x *SubscribedEvent) String() string { func (*SubscribedEvent) ProtoMessage() {} func (x *SubscribedEvent) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[32] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[33] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2677,11 +2863,14 @@ func (x *SubscribedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SubscribedEvent.ProtoReflect.Descriptor instead. func (*SubscribedEvent) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{32} + return file_routerrpc_router_proto_rawDescGZIP(), []int{33} } type LinkFailEvent struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Info contains details about the htlc that we failed. Info *HtlcInfo `protobuf:"bytes,1,opt,name=info,proto3" json:"info,omitempty"` // FailureCode is the BOLT error code for the failure. @@ -2692,15 +2881,15 @@ type LinkFailEvent struct { FailureDetail FailureDetail `protobuf:"varint,3,opt,name=failure_detail,json=failureDetail,proto3,enum=routerrpc.FailureDetail" json:"failure_detail,omitempty"` // A string representation of the link failure. FailureString string `protobuf:"bytes,4,opt,name=failure_string,json=failureString,proto3" json:"failure_string,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *LinkFailEvent) Reset() { *x = LinkFailEvent{} - mi := &file_routerrpc_router_proto_msgTypes[33] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *LinkFailEvent) String() string { @@ -2710,8 +2899,8 @@ func (x *LinkFailEvent) String() string { func (*LinkFailEvent) ProtoMessage() {} func (x *LinkFailEvent) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[33] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[34] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2723,7 +2912,7 @@ func (x *LinkFailEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use LinkFailEvent.ProtoReflect.Descriptor instead. func (*LinkFailEvent) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{33} + return file_routerrpc_router_proto_rawDescGZIP(), []int{34} } func (x *LinkFailEvent) GetInfo() *HtlcInfo { @@ -2754,21 +2943,90 @@ func (x *LinkFailEvent) GetFailureString() string { return "" } +type PaymentStatus struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Current state the payment is in. + State PaymentState `protobuf:"varint,1,opt,name=state,proto3,enum=routerrpc.PaymentState" json:"state,omitempty"` + // The pre-image of the payment when state is SUCCEEDED. + Preimage []byte `protobuf:"bytes,2,opt,name=preimage,proto3" json:"preimage,omitempty"` + // The HTLCs made in attempt to settle the payment [EXPERIMENTAL]. + Htlcs []*lnrpc.HTLCAttempt `protobuf:"bytes,4,rep,name=htlcs,proto3" json:"htlcs,omitempty"` +} + +func (x *PaymentStatus) Reset() { + *x = PaymentStatus{} + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PaymentStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PaymentStatus) ProtoMessage() {} + +func (x *PaymentStatus) ProtoReflect() protoreflect.Message { + mi := &file_routerrpc_router_proto_msgTypes[35] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PaymentStatus.ProtoReflect.Descriptor instead. +func (*PaymentStatus) Descriptor() ([]byte, []int) { + return file_routerrpc_router_proto_rawDescGZIP(), []int{35} +} + +func (x *PaymentStatus) GetState() PaymentState { + if x != nil { + return x.State + } + return PaymentState_IN_FLIGHT +} + +func (x *PaymentStatus) GetPreimage() []byte { + if x != nil { + return x.Preimage + } + return nil +} + +func (x *PaymentStatus) GetHtlcs() []*lnrpc.HTLCAttempt { + if x != nil { + return x.Htlcs + } + return nil +} + type CircuitKey struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // / The id of the channel that the is part of this circuit. ChanId uint64 `protobuf:"varint,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"` // / The index of the incoming htlc in the incoming channel. - HtlcId uint64 `protobuf:"varint,2,opt,name=htlc_id,json=htlcId,proto3" json:"htlc_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + HtlcId uint64 `protobuf:"varint,2,opt,name=htlc_id,json=htlcId,proto3" json:"htlc_id,omitempty"` } func (x *CircuitKey) Reset() { *x = CircuitKey{} - mi := &file_routerrpc_router_proto_msgTypes[34] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *CircuitKey) String() string { @@ -2778,8 +3036,8 @@ func (x *CircuitKey) String() string { func (*CircuitKey) ProtoMessage() {} func (x *CircuitKey) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[34] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[36] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2791,7 +3049,7 @@ func (x *CircuitKey) ProtoReflect() protoreflect.Message { // Deprecated: Use CircuitKey.ProtoReflect.Descriptor instead. func (*CircuitKey) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{34} + return file_routerrpc_router_proto_rawDescGZIP(), []int{36} } func (x *CircuitKey) GetChanId() uint64 { @@ -2809,7 +3067,10 @@ func (x *CircuitKey) GetHtlcId() uint64 { } type ForwardHtlcInterceptRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The key of this forwarded htlc. It defines the incoming channel id and // the index in this channel. // @@ -2827,15 +3088,14 @@ type ForwardHtlcInterceptRequest struct { // The requested outgoing channel id for this forwarded htlc. Because of // non-strict forwarding, this isn't necessarily the channel over which the // packet will be forwarded eventually. A different channel to the same peer - // may be selected as well. This is set to a sentinel value (all bits set) - // if the outgoing_requested_node_id is specified for blinded routes. + // may be selected as well. OutgoingRequestedChanId uint64 `protobuf:"varint,7,opt,name=outgoing_requested_chan_id,json=outgoingRequestedChanId,proto3" json:"outgoing_requested_chan_id,omitempty"` // The outgoing htlc amount. OutgoingAmountMsat uint64 `protobuf:"varint,3,opt,name=outgoing_amount_msat,json=outgoingAmountMsat,proto3" json:"outgoing_amount_msat,omitempty"` // The outgoing htlc expiry. OutgoingExpiry uint32 `protobuf:"varint,4,opt,name=outgoing_expiry,json=outgoingExpiry,proto3" json:"outgoing_expiry,omitempty"` // Any custom records that were present in the payload. - CustomRecords map[uint64][]byte `protobuf:"bytes,8,rep,name=custom_records,json=customRecords,proto3" json:"custom_records,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + CustomRecords map[uint64][]byte `protobuf:"bytes,8,rep,name=custom_records,json=customRecords,proto3" json:"custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // The onion blob for the next hop OnionBlob []byte `protobuf:"bytes,9,opt,name=onion_blob,json=onionBlob,proto3" json:"onion_blob,omitempty"` // The block height at which this htlc will be auto-failed to prevent the @@ -2843,29 +3103,16 @@ type ForwardHtlcInterceptRequest struct { // settlement deadline instead and no automatic fail-back is attempted. AutoFailHeight int32 `protobuf:"varint,10,opt,name=auto_fail_height,json=autoFailHeight,proto3" json:"auto_fail_height,omitempty"` // The custom records of the peer's incoming p2p wire message. - InWireCustomRecords map[uint64][]byte `protobuf:"bytes,11,rep,name=in_wire_custom_records,json=inWireCustomRecords,proto3" json:"in_wire_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - // The requested outgoing node for a blinded forward. When non-empty, this - // field contains exactly one 33-byte compressed public key and - // outgoing_requested_chan_id is set to 18446744073709551615 - // (0xffffffffffffffff). Clients MUST NOT interpret that value as an actual - // channel ID; the presence of this field identifies a node-addressed - // forward. - // - // The possible next-hop representations are: - // - // node ID empty, channel ID 0: final receive; - // node ID empty, ordinary channel ID: channel-addressed forward; - // node ID present, channel ID MaxUint64: node-addressed forward. - OutgoingRequestedNodeId []byte `protobuf:"bytes,12,opt,name=outgoing_requested_node_id,json=outgoingRequestedNodeId,proto3" json:"outgoing_requested_node_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + InWireCustomRecords map[uint64][]byte `protobuf:"bytes,11,rep,name=in_wire_custom_records,json=inWireCustomRecords,proto3" json:"in_wire_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *ForwardHtlcInterceptRequest) Reset() { *x = ForwardHtlcInterceptRequest{} - mi := &file_routerrpc_router_proto_msgTypes[35] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ForwardHtlcInterceptRequest) String() string { @@ -2875,8 +3122,8 @@ func (x *ForwardHtlcInterceptRequest) String() string { func (*ForwardHtlcInterceptRequest) ProtoMessage() {} func (x *ForwardHtlcInterceptRequest) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[35] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[37] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2888,7 +3135,7 @@ func (x *ForwardHtlcInterceptRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ForwardHtlcInterceptRequest.ProtoReflect.Descriptor instead. func (*ForwardHtlcInterceptRequest) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{35} + return file_routerrpc_router_proto_rawDescGZIP(), []int{37} } func (x *ForwardHtlcInterceptRequest) GetIncomingCircuitKey() *CircuitKey { @@ -2968,13 +3215,6 @@ func (x *ForwardHtlcInterceptRequest) GetInWireCustomRecords() map[uint64][]byte return nil } -func (x *ForwardHtlcInterceptRequest) GetOutgoingRequestedNodeId() []byte { - if x != nil { - return x.OutgoingRequestedNodeId - } - return nil -} - // * // ForwardHtlcInterceptResponse enables the caller to resolve a previously hold // forward. The caller can choose either to: @@ -2992,7 +3232,10 @@ func (x *ForwardHtlcInterceptRequest) GetOutgoingRequestedNodeId() []byte { // on-chain. Clients should reconnect to receive any held HTLCs that remain // unresolved. type ForwardHtlcInterceptResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // * // The key of this forwarded htlc. It defines the incoming channel id and // the index in this channel. @@ -3028,16 +3271,16 @@ type ForwardHtlcInterceptResponse struct { // This map will merge with the existing set of custom records (if any), // replacing any conflicting types. Note that there currently is no support // for deleting existing custom records (they can only be replaced). - OutWireCustomRecords map[uint64][]byte `protobuf:"bytes,8,rep,name=out_wire_custom_records,json=outWireCustomRecords,proto3" json:"out_wire_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + OutWireCustomRecords map[uint64][]byte `protobuf:"bytes,8,rep,name=out_wire_custom_records,json=outWireCustomRecords,proto3" json:"out_wire_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *ForwardHtlcInterceptResponse) Reset() { *x = ForwardHtlcInterceptResponse{} - mi := &file_routerrpc_router_proto_msgTypes[36] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ForwardHtlcInterceptResponse) String() string { @@ -3047,8 +3290,8 @@ func (x *ForwardHtlcInterceptResponse) String() string { func (*ForwardHtlcInterceptResponse) ProtoMessage() {} func (x *ForwardHtlcInterceptResponse) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[36] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[38] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3060,7 +3303,7 @@ func (x *ForwardHtlcInterceptResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ForwardHtlcInterceptResponse.ProtoReflect.Descriptor instead. func (*ForwardHtlcInterceptResponse) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{36} + return file_routerrpc_router_proto_rawDescGZIP(), []int{38} } func (x *ForwardHtlcInterceptResponse) GetIncomingCircuitKey() *CircuitKey { @@ -3120,18 +3363,21 @@ func (x *ForwardHtlcInterceptResponse) GetOutWireCustomRecords() map[uint64][]by } type UpdateChanStatusRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ChanPoint *lnrpc.ChannelPoint `protobuf:"bytes,1,opt,name=chan_point,json=chanPoint,proto3" json:"chan_point,omitempty"` - Action ChanStatusAction `protobuf:"varint,2,opt,name=action,proto3,enum=routerrpc.ChanStatusAction" json:"action,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ChanPoint *lnrpc.ChannelPoint `protobuf:"bytes,1,opt,name=chan_point,json=chanPoint,proto3" json:"chan_point,omitempty"` + Action ChanStatusAction `protobuf:"varint,2,opt,name=action,proto3,enum=routerrpc.ChanStatusAction" json:"action,omitempty"` } func (x *UpdateChanStatusRequest) Reset() { *x = UpdateChanStatusRequest{} - mi := &file_routerrpc_router_proto_msgTypes[37] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[39] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *UpdateChanStatusRequest) String() string { @@ -3141,8 +3387,8 @@ func (x *UpdateChanStatusRequest) String() string { func (*UpdateChanStatusRequest) ProtoMessage() {} func (x *UpdateChanStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[37] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[39] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3154,7 +3400,7 @@ func (x *UpdateChanStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateChanStatusRequest.ProtoReflect.Descriptor instead. func (*UpdateChanStatusRequest) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{37} + return file_routerrpc_router_proto_rawDescGZIP(), []int{39} } func (x *UpdateChanStatusRequest) GetChanPoint() *lnrpc.ChannelPoint { @@ -3172,16 +3418,18 @@ func (x *UpdateChanStatusRequest) GetAction() ChanStatusAction { } type UpdateChanStatusResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *UpdateChanStatusResponse) Reset() { *x = UpdateChanStatusResponse{} - mi := &file_routerrpc_router_proto_msgTypes[38] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *UpdateChanStatusResponse) String() string { @@ -3191,8 +3439,8 @@ func (x *UpdateChanStatusResponse) String() string { func (*UpdateChanStatusResponse) ProtoMessage() {} func (x *UpdateChanStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[38] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[40] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3204,21 +3452,24 @@ func (x *UpdateChanStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateChanStatusResponse.ProtoReflect.Descriptor instead. func (*UpdateChanStatusResponse) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{38} + return file_routerrpc_router_proto_rawDescGZIP(), []int{40} } type AddAliasesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - AliasMaps []*lnrpc.AliasMap `protobuf:"bytes,1,rep,name=alias_maps,json=aliasMaps,proto3" json:"alias_maps,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AliasMaps []*lnrpc.AliasMap `protobuf:"bytes,1,rep,name=alias_maps,json=aliasMaps,proto3" json:"alias_maps,omitempty"` } func (x *AddAliasesRequest) Reset() { *x = AddAliasesRequest{} - mi := &file_routerrpc_router_proto_msgTypes[39] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *AddAliasesRequest) String() string { @@ -3228,8 +3479,8 @@ func (x *AddAliasesRequest) String() string { func (*AddAliasesRequest) ProtoMessage() {} func (x *AddAliasesRequest) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[39] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[41] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3241,7 +3492,7 @@ func (x *AddAliasesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AddAliasesRequest.ProtoReflect.Descriptor instead. func (*AddAliasesRequest) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{39} + return file_routerrpc_router_proto_rawDescGZIP(), []int{41} } func (x *AddAliasesRequest) GetAliasMaps() []*lnrpc.AliasMap { @@ -3252,17 +3503,20 @@ func (x *AddAliasesRequest) GetAliasMaps() []*lnrpc.AliasMap { } type AddAliasesResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - AliasMaps []*lnrpc.AliasMap `protobuf:"bytes,1,rep,name=alias_maps,json=aliasMaps,proto3" json:"alias_maps,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AliasMaps []*lnrpc.AliasMap `protobuf:"bytes,1,rep,name=alias_maps,json=aliasMaps,proto3" json:"alias_maps,omitempty"` } func (x *AddAliasesResponse) Reset() { *x = AddAliasesResponse{} - mi := &file_routerrpc_router_proto_msgTypes[40] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[42] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *AddAliasesResponse) String() string { @@ -3272,8 +3526,8 @@ func (x *AddAliasesResponse) String() string { func (*AddAliasesResponse) ProtoMessage() {} func (x *AddAliasesResponse) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[40] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[42] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3285,7 +3539,7 @@ func (x *AddAliasesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AddAliasesResponse.ProtoReflect.Descriptor instead. func (*AddAliasesResponse) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{40} + return file_routerrpc_router_proto_rawDescGZIP(), []int{42} } func (x *AddAliasesResponse) GetAliasMaps() []*lnrpc.AliasMap { @@ -3296,17 +3550,20 @@ func (x *AddAliasesResponse) GetAliasMaps() []*lnrpc.AliasMap { } type DeleteAliasesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - AliasMaps []*lnrpc.AliasMap `protobuf:"bytes,1,rep,name=alias_maps,json=aliasMaps,proto3" json:"alias_maps,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AliasMaps []*lnrpc.AliasMap `protobuf:"bytes,1,rep,name=alias_maps,json=aliasMaps,proto3" json:"alias_maps,omitempty"` } func (x *DeleteAliasesRequest) Reset() { *x = DeleteAliasesRequest{} - mi := &file_routerrpc_router_proto_msgTypes[41] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[43] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *DeleteAliasesRequest) String() string { @@ -3316,8 +3573,8 @@ func (x *DeleteAliasesRequest) String() string { func (*DeleteAliasesRequest) ProtoMessage() {} func (x *DeleteAliasesRequest) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[41] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[43] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3329,7 +3586,7 @@ func (x *DeleteAliasesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteAliasesRequest.ProtoReflect.Descriptor instead. func (*DeleteAliasesRequest) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{41} + return file_routerrpc_router_proto_rawDescGZIP(), []int{43} } func (x *DeleteAliasesRequest) GetAliasMaps() []*lnrpc.AliasMap { @@ -3340,17 +3597,20 @@ func (x *DeleteAliasesRequest) GetAliasMaps() []*lnrpc.AliasMap { } type DeleteAliasesResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - AliasMaps []*lnrpc.AliasMap `protobuf:"bytes,1,rep,name=alias_maps,json=aliasMaps,proto3" json:"alias_maps,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AliasMaps []*lnrpc.AliasMap `protobuf:"bytes,1,rep,name=alias_maps,json=aliasMaps,proto3" json:"alias_maps,omitempty"` } func (x *DeleteAliasesResponse) Reset() { *x = DeleteAliasesResponse{} - mi := &file_routerrpc_router_proto_msgTypes[42] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[44] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *DeleteAliasesResponse) String() string { @@ -3360,8 +3620,8 @@ func (x *DeleteAliasesResponse) String() string { func (*DeleteAliasesResponse) ProtoMessage() {} func (x *DeleteAliasesResponse) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[42] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[44] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3373,7 +3633,7 @@ func (x *DeleteAliasesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteAliasesResponse.ProtoReflect.Descriptor instead. func (*DeleteAliasesResponse) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{42} + return file_routerrpc_router_proto_rawDescGZIP(), []int{44} } func (x *DeleteAliasesResponse) GetAliasMaps() []*lnrpc.AliasMap { @@ -3384,18 +3644,21 @@ func (x *DeleteAliasesResponse) GetAliasMaps() []*lnrpc.AliasMap { } type FindBaseAliasRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The alias we want to look up the base scid for. - Alias uint64 `protobuf:"varint,1,opt,name=alias,proto3" json:"alias,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The alias we want to look up the base scid for. + Alias uint64 `protobuf:"varint,1,opt,name=alias,proto3" json:"alias,omitempty"` } func (x *FindBaseAliasRequest) Reset() { *x = FindBaseAliasRequest{} - mi := &file_routerrpc_router_proto_msgTypes[43] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[45] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *FindBaseAliasRequest) String() string { @@ -3405,8 +3668,8 @@ func (x *FindBaseAliasRequest) String() string { func (*FindBaseAliasRequest) ProtoMessage() {} func (x *FindBaseAliasRequest) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[43] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[45] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3418,7 +3681,7 @@ func (x *FindBaseAliasRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FindBaseAliasRequest.ProtoReflect.Descriptor instead. func (*FindBaseAliasRequest) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{43} + return file_routerrpc_router_proto_rawDescGZIP(), []int{45} } func (x *FindBaseAliasRequest) GetAlias() uint64 { @@ -3429,18 +3692,21 @@ func (x *FindBaseAliasRequest) GetAlias() uint64 { } type FindBaseAliasResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The base scid that resulted from the base scid look up. - Base uint64 `protobuf:"varint,1,opt,name=base,proto3" json:"base,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The base scid that resulted from the base scid look up. + Base uint64 `protobuf:"varint,1,opt,name=base,proto3" json:"base,omitempty"` } func (x *FindBaseAliasResponse) Reset() { *x = FindBaseAliasResponse{} - mi := &file_routerrpc_router_proto_msgTypes[44] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[46] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *FindBaseAliasResponse) String() string { @@ -3450,8 +3716,8 @@ func (x *FindBaseAliasResponse) String() string { func (*FindBaseAliasResponse) ProtoMessage() {} func (x *FindBaseAliasResponse) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[44] - if x != nil { + mi := &file_routerrpc_router_proto_msgTypes[46] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3463,7 +3729,7 @@ func (x *FindBaseAliasResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FindBaseAliasResponse.ProtoReflect.Descriptor instead. func (*FindBaseAliasResponse) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{44} + return file_routerrpc_router_proto_rawDescGZIP(), []int{46} } func (x *FindBaseAliasResponse) GetBase() uint64 { @@ -3473,643 +3739,873 @@ func (x *FindBaseAliasResponse) GetBase() uint64 { return 0 } -type DeleteForwardingHistoryRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Specify the cutoff time for deletion using one of the following options. - // Events with a timestamp at or before the cutoff are deleted. - // - // Types that are valid to be assigned to TimeSpec: - // - // *DeleteForwardingHistoryRequest_DeleteBeforeTime - // *DeleteForwardingHistoryRequest_DeleteBeforeDuration - TimeSpec isDeleteForwardingHistoryRequest_TimeSpec `protobuf_oneof:"time_spec"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *DeleteForwardingHistoryRequest) Reset() { - *x = DeleteForwardingHistoryRequest{} - mi := &file_routerrpc_router_proto_msgTypes[45] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *DeleteForwardingHistoryRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeleteForwardingHistoryRequest) ProtoMessage() {} - -func (x *DeleteForwardingHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[45] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DeleteForwardingHistoryRequest.ProtoReflect.Descriptor instead. -func (*DeleteForwardingHistoryRequest) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{45} -} - -func (x *DeleteForwardingHistoryRequest) GetTimeSpec() isDeleteForwardingHistoryRequest_TimeSpec { - if x != nil { - return x.TimeSpec - } - return nil -} - -func (x *DeleteForwardingHistoryRequest) GetDeleteBeforeTime() uint64 { - if x != nil { - if x, ok := x.TimeSpec.(*DeleteForwardingHistoryRequest_DeleteBeforeTime); ok { - return x.DeleteBeforeTime - } - } - return 0 -} - -func (x *DeleteForwardingHistoryRequest) GetDeleteBeforeDuration() string { - if x != nil { - if x, ok := x.TimeSpec.(*DeleteForwardingHistoryRequest_DeleteBeforeDuration); ok { - return x.DeleteBeforeDuration - } - } - return "" -} - -type isDeleteForwardingHistoryRequest_TimeSpec interface { - isDeleteForwardingHistoryRequest_TimeSpec() -} - -type DeleteForwardingHistoryRequest_DeleteBeforeTime struct { - // Absolute Unix timestamp (seconds). Events at or before this time - // are deleted. - DeleteBeforeTime uint64 `protobuf:"varint,1,opt,name=delete_before_time,json=deleteBeforeTime,proto3,oneof"` -} - -type DeleteForwardingHistoryRequest_DeleteBeforeDuration struct { - // Relative duration string indicating how far back to delete, e.g. - // "-30d" deletes events at or before 30 days ago. - // Standard Go: "-24h", "-1.5h" - // Custom units: "-1d", "-1w", "-1M", "-1y" - // Supported: ns, us/µs, ms, s, m, h, d (days), w (weeks), - // M (months=30.44d), y (years=365.25d). - // Use negative values to specify time in the past. - DeleteBeforeDuration string `protobuf:"bytes,2,opt,name=delete_before_duration,json=deleteBeforeDuration,proto3,oneof"` -} - -func (*DeleteForwardingHistoryRequest_DeleteBeforeTime) isDeleteForwardingHistoryRequest_TimeSpec() {} - -func (*DeleteForwardingHistoryRequest_DeleteBeforeDuration) isDeleteForwardingHistoryRequest_TimeSpec() { -} - -type DeleteForwardingHistoryResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Number of forwarding events deleted. - EventsDeleted uint64 `protobuf:"varint,1,opt,name=events_deleted,json=eventsDeleted,proto3" json:"events_deleted,omitempty"` - // Total fees earned from deleted events (in millisatoshis). - // This is the sum of (amt_in - amt_out) for all deleted events, which - // can be used for accounting purposes. - TotalFeeMsat int64 `protobuf:"varint,2,opt,name=total_fee_msat,json=totalFeeMsat,proto3" json:"total_fee_msat,omitempty"` - // Status message. - Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *DeleteForwardingHistoryResponse) Reset() { - *x = DeleteForwardingHistoryResponse{} - mi := &file_routerrpc_router_proto_msgTypes[46] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *DeleteForwardingHistoryResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeleteForwardingHistoryResponse) ProtoMessage() {} - -func (x *DeleteForwardingHistoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_routerrpc_router_proto_msgTypes[46] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DeleteForwardingHistoryResponse.ProtoReflect.Descriptor instead. -func (*DeleteForwardingHistoryResponse) Descriptor() ([]byte, []int) { - return file_routerrpc_router_proto_rawDescGZIP(), []int{46} -} - -func (x *DeleteForwardingHistoryResponse) GetEventsDeleted() uint64 { - if x != nil { - return x.EventsDeleted - } - return 0 -} - -func (x *DeleteForwardingHistoryResponse) GetTotalFeeMsat() int64 { - if x != nil { - return x.TotalFeeMsat - } - return 0 -} - -func (x *DeleteForwardingHistoryResponse) GetStatus() string { - if x != nil { - return x.Status - } - return "" -} - var File_routerrpc_router_proto protoreflect.FileDescriptor -const file_routerrpc_router_proto_rawDesc = "" + - "\n" + - "\x16routerrpc/router.proto\x12\trouterrpc\x1a\x0flightning.proto\"\xa7\t\n" + - "\x12SendPaymentRequest\x12\x12\n" + - "\x04dest\x18\x01 \x01(\fR\x04dest\x12\x10\n" + - "\x03amt\x18\x02 \x01(\x03R\x03amt\x12!\n" + - "\fpayment_hash\x18\x03 \x01(\fR\vpaymentHash\x12(\n" + - "\x10final_cltv_delta\x18\x04 \x01(\x05R\x0efinalCltvDelta\x12'\n" + - "\x0fpayment_request\x18\x05 \x01(\tR\x0epaymentRequest\x12'\n" + - "\x0ftimeout_seconds\x18\x06 \x01(\x05R\x0etimeoutSeconds\x12\"\n" + - "\rfee_limit_sat\x18\a \x01(\x03R\vfeeLimitSat\x12\x1d\n" + - "\n" + - "cltv_limit\x18\t \x01(\x05R\tcltvLimit\x121\n" + - "\vroute_hints\x18\n" + - " \x03(\v2\x10.lnrpc.RouteHintR\n" + - "routeHints\x12d\n" + - "\x13dest_custom_records\x18\v \x03(\v24.routerrpc.SendPaymentRequest.DestCustomRecordsEntryR\x11destCustomRecords\x12\x19\n" + - "\bamt_msat\x18\f \x01(\x03R\aamtMsat\x12$\n" + - "\x0efee_limit_msat\x18\r \x01(\x03R\ffeeLimitMsat\x12&\n" + - "\x0flast_hop_pubkey\x18\x0e \x01(\fR\rlastHopPubkey\x12,\n" + - "\x12allow_self_payment\x18\x0f \x01(\bR\x10allowSelfPayment\x126\n" + - "\rdest_features\x18\x10 \x03(\x0e2\x11.lnrpc.FeatureBitR\fdestFeatures\x12\x1b\n" + - "\tmax_parts\x18\x11 \x01(\rR\bmaxParts\x12.\n" + - "\x13no_inflight_updates\x18\x12 \x01(\bR\x11noInflightUpdates\x12*\n" + - "\x11outgoing_chan_ids\x18\x13 \x03(\x04R\x0foutgoingChanIds\x12!\n" + - "\fpayment_addr\x18\x14 \x01(\fR\vpaymentAddr\x12-\n" + - "\x13max_shard_size_msat\x18\x15 \x01(\x04R\x10maxShardSizeMsat\x12\x10\n" + - "\x03amp\x18\x16 \x01(\bR\x03amp\x12\x1b\n" + - "\ttime_pref\x18\x17 \x01(\x01R\btimePref\x12\x1e\n" + - "\n" + - "cancelable\x18\x18 \x01(\bR\n" + - "cancelable\x12q\n" + - "\x18first_hop_custom_records\x18\x19 \x03(\v28.routerrpc.SendPaymentRequest.FirstHopCustomRecordsEntryR\x15firstHopCustomRecords\x1aD\n" + - "\x16DestCustomRecordsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\x04R\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\fR\x05value:\x028\x01\x1aH\n" + - "\x1aFirstHopCustomRecordsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\x04R\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\fR\x05value:\x028\x01J\x04\b\b\x10\t\"h\n" + - "\x13TrackPaymentRequest\x12!\n" + - "\fpayment_hash\x18\x01 \x01(\fR\vpaymentHash\x12.\n" + - "\x13no_inflight_updates\x18\x02 \x01(\bR\x11noInflightUpdates\"F\n" + - "\x14TrackPaymentsRequest\x12.\n" + - "\x13no_inflight_updates\x18\x01 \x01(\bR\x11noInflightUpdates\"\xad\x01\n" + - "\x0fRouteFeeRequest\x12\x12\n" + - "\x04dest\x18\x01 \x01(\fR\x04dest\x12\x17\n" + - "\aamt_sat\x18\x02 \x01(\x03R\x06amtSat\x12'\n" + - "\x0fpayment_request\x18\x03 \x01(\tR\x0epaymentRequest\x12\x18\n" + - "\atimeout\x18\x04 \x01(\rR\atimeout\x12*\n" + - "\x11outgoing_chan_ids\x18\x05 \x03(\x04R\x0foutgoingChanIds\"\xa8\x01\n" + - "\x10RouteFeeResponse\x12(\n" + - "\x10routing_fee_msat\x18\x01 \x01(\x03R\x0eroutingFeeMsat\x12&\n" + - "\x0ftime_lock_delay\x18\x02 \x01(\x03R\rtimeLockDelay\x12B\n" + - "\x0efailure_reason\x18\x05 \x01(\x0e2\x1b.lnrpc.PaymentFailureReasonR\rfailureReason\"\xbc\x02\n" + - "\x12SendToRouteRequest\x12!\n" + - "\fpayment_hash\x18\x01 \x01(\fR\vpaymentHash\x12\"\n" + - "\x05route\x18\x02 \x01(\v2\f.lnrpc.RouteR\x05route\x12\"\n" + - "\rskip_temp_err\x18\x03 \x01(\bR\vskipTempErr\x12q\n" + - "\x18first_hop_custom_records\x18\x04 \x03(\v28.routerrpc.SendToRouteRequest.FirstHopCustomRecordsEntryR\x15firstHopCustomRecords\x1aH\n" + - "\x1aFirstHopCustomRecordsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\x04R\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\fR\x05value:\x028\x01\"\x1c\n" + - "\x1aResetMissionControlRequest\"\x1d\n" + - "\x1bResetMissionControlResponse\"\x1c\n" + - "\x1aQueryMissionControlRequest\"Q\n" + - "\x1bQueryMissionControlResponse\x12,\n" + - "\x05pairs\x18\x02 \x03(\v2\x16.routerrpc.PairHistoryR\x05pairsJ\x04\b\x01\x10\x02\"b\n" + - "\x1cXImportMissionControlRequest\x12,\n" + - "\x05pairs\x18\x01 \x03(\v2\x16.routerrpc.PairHistoryR\x05pairs\x12\x14\n" + - "\x05force\x18\x02 \x01(\bR\x05force\"\x1f\n" + - "\x1dXImportMissionControlResponse\"\x8a\x01\n" + - "\vPairHistory\x12\x1b\n" + - "\tnode_from\x18\x01 \x01(\fR\bnodeFrom\x12\x17\n" + - "\anode_to\x18\x02 \x01(\fR\x06nodeTo\x12-\n" + - "\ahistory\x18\a \x01(\v2\x13.routerrpc.PairDataR\ahistoryJ\x04\b\x03\x10\x04J\x04\b\x04\x10\x05J\x04\b\x05\x10\x06J\x04\b\x06\x10\a\"\xe8\x01\n" + - "\bPairData\x12\x1b\n" + - "\tfail_time\x18\x01 \x01(\x03R\bfailTime\x12 \n" + - "\ffail_amt_sat\x18\x02 \x01(\x03R\n" + - "failAmtSat\x12\"\n" + - "\rfail_amt_msat\x18\x04 \x01(\x03R\vfailAmtMsat\x12!\n" + - "\fsuccess_time\x18\x05 \x01(\x03R\vsuccessTime\x12&\n" + - "\x0fsuccess_amt_sat\x18\x06 \x01(\x03R\rsuccessAmtSat\x12(\n" + - "\x10success_amt_msat\x18\a \x01(\x03R\x0esuccessAmtMsatJ\x04\b\x03\x10\x04\" \n" + - "\x1eGetMissionControlConfigRequest\"Z\n" + - "\x1fGetMissionControlConfigResponse\x127\n" + - "\x06config\x18\x01 \x01(\v2\x1f.routerrpc.MissionControlConfigR\x06config\"Y\n" + - "\x1eSetMissionControlConfigRequest\x127\n" + - "\x06config\x18\x01 \x01(\v2\x1f.routerrpc.MissionControlConfigR\x06config\"!\n" + - "\x1fSetMissionControlConfigResponse\"\x89\x04\n" + - "\x14MissionControlConfig\x12.\n" + - "\x11half_life_seconds\x18\x01 \x01(\x04B\x02\x18\x01R\x0fhalfLifeSeconds\x12+\n" + - "\x0fhop_probability\x18\x02 \x01(\x02B\x02\x18\x01R\x0ehopProbability\x12\x1a\n" + - "\x06weight\x18\x03 \x01(\x02B\x02\x18\x01R\x06weight\x126\n" + - "\x17maximum_payment_results\x18\x04 \x01(\rR\x15maximumPaymentResults\x12C\n" + - "\x1eminimum_failure_relax_interval\x18\x05 \x01(\x04R\x1bminimumFailureRelaxInterval\x12F\n" + - "\x05model\x18\x06 \x01(\x0e20.routerrpc.MissionControlConfig.ProbabilityModelR\x05model\x128\n" + - "\aapriori\x18\a \x01(\v2\x1c.routerrpc.AprioriParametersH\x00R\aapriori\x128\n" + - "\abimodal\x18\b \x01(\v2\x1c.routerrpc.BimodalParametersH\x00R\abimodal\",\n" + - "\x10ProbabilityModel\x12\v\n" + - "\aAPRIORI\x10\x00\x12\v\n" + - "\aBIMODAL\x10\x01B\x11\n" + - "\x0fEstimatorConfig\"r\n" + - "\x11BimodalParameters\x12\x1f\n" + - "\vnode_weight\x18\x01 \x01(\x01R\n" + - "nodeWeight\x12\x1d\n" + - "\n" + - "scale_msat\x18\x02 \x01(\x04R\tscaleMsat\x12\x1d\n" + - "\n" + - "decay_time\x18\x03 \x01(\x04R\tdecayTime\"\xad\x01\n" + - "\x11AprioriParameters\x12*\n" + - "\x11half_life_seconds\x18\x01 \x01(\x04R\x0fhalfLifeSeconds\x12'\n" + - "\x0fhop_probability\x18\x02 \x01(\x01R\x0ehopProbability\x12\x16\n" + - "\x06weight\x18\x03 \x01(\x01R\x06weight\x12+\n" + - "\x11capacity_fraction\x18\x04 \x01(\x01R\x10capacityFraction\"j\n" + - "\x17QueryProbabilityRequest\x12\x1b\n" + - "\tfrom_node\x18\x01 \x01(\fR\bfromNode\x12\x17\n" + - "\ato_node\x18\x02 \x01(\fR\x06toNode\x12\x19\n" + - "\bamt_msat\x18\x03 \x01(\x03R\aamtMsat\"k\n" + - "\x18QueryProbabilityResponse\x12 \n" + - "\vprobability\x18\x01 \x01(\x01R\vprobability\x12-\n" + - "\ahistory\x18\x02 \x01(\v2\x13.routerrpc.PairDataR\ahistory\"\x86\x03\n" + - "\x11BuildRouteRequest\x12\x19\n" + - "\bamt_msat\x18\x01 \x01(\x03R\aamtMsat\x12(\n" + - "\x10final_cltv_delta\x18\x02 \x01(\x05R\x0efinalCltvDelta\x12,\n" + - "\x10outgoing_chan_id\x18\x03 \x01(\x04B\x020\x01R\x0eoutgoingChanId\x12\x1f\n" + - "\vhop_pubkeys\x18\x04 \x03(\fR\n" + - "hopPubkeys\x12!\n" + - "\fpayment_addr\x18\x05 \x01(\fR\vpaymentAddr\x12p\n" + - "\x18first_hop_custom_records\x18\x06 \x03(\v27.routerrpc.BuildRouteRequest.FirstHopCustomRecordsEntryR\x15firstHopCustomRecords\x1aH\n" + - "\x1aFirstHopCustomRecordsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\x04R\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\fR\x05value:\x028\x01\"8\n" + - "\x12BuildRouteResponse\x12\"\n" + - "\x05route\x18\x01 \x01(\v2\f.lnrpc.RouteR\x05route\"\x1c\n" + - "\x1aSubscribeHtlcEventsRequest\"\x86\x06\n" + - "\tHtlcEvent\x12.\n" + - "\x13incoming_channel_id\x18\x01 \x01(\x04R\x11incomingChannelId\x12.\n" + - "\x13outgoing_channel_id\x18\x02 \x01(\x04R\x11outgoingChannelId\x12(\n" + - "\x10incoming_htlc_id\x18\x03 \x01(\x04R\x0eincomingHtlcId\x12(\n" + - "\x10outgoing_htlc_id\x18\x04 \x01(\x04R\x0eoutgoingHtlcId\x12!\n" + - "\ftimestamp_ns\x18\x05 \x01(\x04R\vtimestampNs\x12=\n" + - "\n" + - "event_type\x18\x06 \x01(\x0e2\x1e.routerrpc.HtlcEvent.EventTypeR\teventType\x12>\n" + - "\rforward_event\x18\a \x01(\v2\x17.routerrpc.ForwardEventH\x00R\fforwardEvent\x12K\n" + - "\x12forward_fail_event\x18\b \x01(\v2\x1b.routerrpc.ForwardFailEventH\x00R\x10forwardFailEvent\x12;\n" + - "\fsettle_event\x18\t \x01(\v2\x16.routerrpc.SettleEventH\x00R\vsettleEvent\x12B\n" + - "\x0flink_fail_event\x18\n" + - " \x01(\v2\x18.routerrpc.LinkFailEventH\x00R\rlinkFailEvent\x12G\n" + - "\x10subscribed_event\x18\v \x01(\v2\x1a.routerrpc.SubscribedEventH\x00R\x0fsubscribedEvent\x12E\n" + - "\x10final_htlc_event\x18\f \x01(\v2\x19.routerrpc.FinalHtlcEventH\x00R\x0efinalHtlcEvent\"<\n" + - "\tEventType\x12\v\n" + - "\aUNKNOWN\x10\x00\x12\b\n" + - "\x04SEND\x10\x01\x12\v\n" + - "\aRECEIVE\x10\x02\x12\v\n" + - "\aFORWARD\x10\x03B\a\n" + - "\x05event\"\xbc\x01\n" + - "\bHtlcInfo\x12+\n" + - "\x11incoming_timelock\x18\x01 \x01(\rR\x10incomingTimelock\x12+\n" + - "\x11outgoing_timelock\x18\x02 \x01(\rR\x10outgoingTimelock\x12*\n" + - "\x11incoming_amt_msat\x18\x03 \x01(\x04R\x0fincomingAmtMsat\x12*\n" + - "\x11outgoing_amt_msat\x18\x04 \x01(\x04R\x0foutgoingAmtMsat\"7\n" + - "\fForwardEvent\x12'\n" + - "\x04info\x18\x01 \x01(\v2\x13.routerrpc.HtlcInfoR\x04info\"\x12\n" + - "\x10ForwardFailEvent\")\n" + - "\vSettleEvent\x12\x1a\n" + - "\bpreimage\x18\x01 \x01(\fR\bpreimage\"F\n" + - "\x0eFinalHtlcEvent\x12\x18\n" + - "\asettled\x18\x01 \x01(\bR\asettled\x12\x1a\n" + - "\boffchain\x18\x02 \x01(\bR\boffchain\"\x11\n" + - "\x0fSubscribedEvent\"\xdf\x01\n" + - "\rLinkFailEvent\x12'\n" + - "\x04info\x18\x01 \x01(\v2\x13.routerrpc.HtlcInfoR\x04info\x12=\n" + - "\fwire_failure\x18\x02 \x01(\x0e2\x1a.lnrpc.Failure.FailureCodeR\vwireFailure\x12?\n" + - "\x0efailure_detail\x18\x03 \x01(\x0e2\x18.routerrpc.FailureDetailR\rfailureDetail\x12%\n" + - "\x0efailure_string\x18\x04 \x01(\tR\rfailureString\">\n" + - "\n" + - "CircuitKey\x12\x17\n" + - "\achan_id\x18\x01 \x01(\x04R\x06chanId\x12\x17\n" + - "\ahtlc_id\x18\x02 \x01(\x04R\x06htlcId\"\xe4\x06\n" + - "\x1bForwardHtlcInterceptRequest\x12G\n" + - "\x14incoming_circuit_key\x18\x01 \x01(\v2\x15.routerrpc.CircuitKeyR\x12incomingCircuitKey\x120\n" + - "\x14incoming_amount_msat\x18\x05 \x01(\x04R\x12incomingAmountMsat\x12'\n" + - "\x0fincoming_expiry\x18\x06 \x01(\rR\x0eincomingExpiry\x12!\n" + - "\fpayment_hash\x18\x02 \x01(\fR\vpaymentHash\x12;\n" + - "\x1aoutgoing_requested_chan_id\x18\a \x01(\x04R\x17outgoingRequestedChanId\x120\n" + - "\x14outgoing_amount_msat\x18\x03 \x01(\x04R\x12outgoingAmountMsat\x12'\n" + - "\x0foutgoing_expiry\x18\x04 \x01(\rR\x0eoutgoingExpiry\x12`\n" + - "\x0ecustom_records\x18\b \x03(\v29.routerrpc.ForwardHtlcInterceptRequest.CustomRecordsEntryR\rcustomRecords\x12\x1d\n" + - "\n" + - "onion_blob\x18\t \x01(\fR\tonionBlob\x12(\n" + - "\x10auto_fail_height\x18\n" + - " \x01(\x05R\x0eautoFailHeight\x12t\n" + - "\x16in_wire_custom_records\x18\v \x03(\v2?.routerrpc.ForwardHtlcInterceptRequest.InWireCustomRecordsEntryR\x13inWireCustomRecords\x12;\n" + - "\x1aoutgoing_requested_node_id\x18\f \x01(\fR\x17outgoingRequestedNodeId\x1a@\n" + - "\x12CustomRecordsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\x04R\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\fR\x05value:\x028\x01\x1aF\n" + - "\x18InWireCustomRecordsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\x04R\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\fR\x05value:\x028\x01\"\xb9\x04\n" + - "\x1cForwardHtlcInterceptResponse\x12G\n" + - "\x14incoming_circuit_key\x18\x01 \x01(\v2\x15.routerrpc.CircuitKeyR\x12incomingCircuitKey\x12;\n" + - "\x06action\x18\x02 \x01(\x0e2#.routerrpc.ResolveHoldForwardActionR\x06action\x12\x1a\n" + - "\bpreimage\x18\x03 \x01(\fR\bpreimage\x12'\n" + - "\x0ffailure_message\x18\x04 \x01(\fR\x0efailureMessage\x12=\n" + - "\ffailure_code\x18\x05 \x01(\x0e2\x1a.lnrpc.Failure.FailureCodeR\vfailureCode\x12$\n" + - "\x0ein_amount_msat\x18\x06 \x01(\x04R\finAmountMsat\x12&\n" + - "\x0fout_amount_msat\x18\a \x01(\x04R\routAmountMsat\x12x\n" + - "\x17out_wire_custom_records\x18\b \x03(\v2A.routerrpc.ForwardHtlcInterceptResponse.OutWireCustomRecordsEntryR\x14outWireCustomRecords\x1aG\n" + - "\x19OutWireCustomRecordsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\x04R\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\fR\x05value:\x028\x01\"\x82\x01\n" + - "\x17UpdateChanStatusRequest\x122\n" + - "\n" + - "chan_point\x18\x01 \x01(\v2\x13.lnrpc.ChannelPointR\tchanPoint\x123\n" + - "\x06action\x18\x02 \x01(\x0e2\x1b.routerrpc.ChanStatusActionR\x06action\"\x1a\n" + - "\x18UpdateChanStatusResponse\"C\n" + - "\x11AddAliasesRequest\x12.\n" + - "\n" + - "alias_maps\x18\x01 \x03(\v2\x0f.lnrpc.AliasMapR\taliasMaps\"D\n" + - "\x12AddAliasesResponse\x12.\n" + - "\n" + - "alias_maps\x18\x01 \x03(\v2\x0f.lnrpc.AliasMapR\taliasMaps\"F\n" + - "\x14DeleteAliasesRequest\x12.\n" + - "\n" + - "alias_maps\x18\x01 \x03(\v2\x0f.lnrpc.AliasMapR\taliasMaps\"G\n" + - "\x15DeleteAliasesResponse\x12.\n" + - "\n" + - "alias_maps\x18\x01 \x03(\v2\x0f.lnrpc.AliasMapR\taliasMaps\",\n" + - "\x14FindBaseAliasRequest\x12\x14\n" + - "\x05alias\x18\x01 \x01(\x04R\x05alias\"+\n" + - "\x15FindBaseAliasResponse\x12\x12\n" + - "\x04base\x18\x01 \x01(\x04R\x04base\"\x95\x01\n" + - "\x1eDeleteForwardingHistoryRequest\x12.\n" + - "\x12delete_before_time\x18\x01 \x01(\x04H\x00R\x10deleteBeforeTime\x126\n" + - "\x16delete_before_duration\x18\x02 \x01(\tH\x00R\x14deleteBeforeDurationB\v\n" + - "\ttime_spec\"\x86\x01\n" + - "\x1fDeleteForwardingHistoryResponse\x12%\n" + - "\x0eevents_deleted\x18\x01 \x01(\x04R\reventsDeleted\x12$\n" + - "\x0etotal_fee_msat\x18\x02 \x01(\x03R\ftotalFeeMsat\x12\x16\n" + - "\x06status\x18\x03 \x01(\tR\x06status*\x85\x05\n" + - "\rFailureDetail\x12\v\n" + - "\aUNKNOWN\x10\x00\x12\r\n" + - "\tNO_DETAIL\x10\x01\x12\x10\n" + - "\fONION_DECODE\x10\x02\x12\x15\n" + - "\x11LINK_NOT_ELIGIBLE\x10\x03\x12\x14\n" + - "\x10ON_CHAIN_TIMEOUT\x10\x04\x12\x14\n" + - "\x10HTLC_EXCEEDS_MAX\x10\x05\x12\x18\n" + - "\x14INSUFFICIENT_BALANCE\x10\x06\x12\x16\n" + - "\x12INCOMPLETE_FORWARD\x10\a\x12\x13\n" + - "\x0fHTLC_ADD_FAILED\x10\b\x12\x15\n" + - "\x11FORWARDS_DISABLED\x10\t\x12\x14\n" + - "\x10INVOICE_CANCELED\x10\n" + - "\x12\x15\n" + - "\x11INVOICE_UNDERPAID\x10\v\x12\x1b\n" + - "\x17INVOICE_EXPIRY_TOO_SOON\x10\f\x12\x14\n" + - "\x10INVOICE_NOT_OPEN\x10\r\x12\x17\n" + - "\x13MPP_INVOICE_TIMEOUT\x10\x0e\x12\x14\n" + - "\x10ADDRESS_MISMATCH\x10\x0f\x12\x16\n" + - "\x12SET_TOTAL_MISMATCH\x10\x10\x12\x15\n" + - "\x11SET_TOTAL_TOO_LOW\x10\x11\x12\x10\n" + - "\fSET_OVERPAID\x10\x12\x12\x13\n" + - "\x0fUNKNOWN_INVOICE\x10\x13\x12\x13\n" + - "\x0fINVALID_KEYSEND\x10\x14\x12\x13\n" + - "\x0fMPP_IN_PROGRESS\x10\x15\x12\x12\n" + - "\x0eCIRCULAR_ROUTE\x10\x16\x12\x1b\n" + - "\x17INVOICE_ALREADY_SETTLED\x10\x17\x12\x1e\n" + - "\x1aHTLC_INVOICE_TYPE_MISMATCH\x10\x18\x12\r\n" + - "\tAMP_ERROR\x10\x19\x12\x16\n" + - "\x12AMP_RECONSTRUCTION\x10\x1a\x12\x1e\n" + - "\x1aEXTERNAL_VALIDATION_FAILED\x10\x1b*Q\n" + - "\x18ResolveHoldForwardAction\x12\n" + - "\n" + - "\x06SETTLE\x10\x00\x12\b\n" + - "\x04FAIL\x10\x01\x12\n" + - "\n" + - "\x06RESUME\x10\x02\x12\x13\n" + - "\x0fRESUME_MODIFIED\x10\x03*5\n" + - "\x10ChanStatusAction\x12\n" + - "\n" + - "\x06ENABLE\x10\x00\x12\v\n" + - "\aDISABLE\x10\x01\x12\b\n" + - "\x04AUTO\x10\x022\xc5\r\n" + - "\x06Router\x12@\n" + - "\rSendPaymentV2\x12\x1d.routerrpc.SendPaymentRequest\x1a\x0e.lnrpc.Payment0\x01\x12B\n" + - "\x0eTrackPaymentV2\x12\x1e.routerrpc.TrackPaymentRequest\x1a\x0e.lnrpc.Payment0\x01\x12B\n" + - "\rTrackPayments\x12\x1f.routerrpc.TrackPaymentsRequest\x1a\x0e.lnrpc.Payment0\x01\x12K\n" + - "\x10EstimateRouteFee\x12\x1a.routerrpc.RouteFeeRequest\x1a\x1b.routerrpc.RouteFeeResponse\x12B\n" + - "\rSendToRouteV2\x12\x1d.routerrpc.SendToRouteRequest\x1a\x12.lnrpc.HTLCAttempt\x12d\n" + - "\x13ResetMissionControl\x12%.routerrpc.ResetMissionControlRequest\x1a&.routerrpc.ResetMissionControlResponse\x12d\n" + - "\x13QueryMissionControl\x12%.routerrpc.QueryMissionControlRequest\x1a&.routerrpc.QueryMissionControlResponse\x12j\n" + - "\x15XImportMissionControl\x12'.routerrpc.XImportMissionControlRequest\x1a(.routerrpc.XImportMissionControlResponse\x12p\n" + - "\x17GetMissionControlConfig\x12).routerrpc.GetMissionControlConfigRequest\x1a*.routerrpc.GetMissionControlConfigResponse\x12p\n" + - "\x17SetMissionControlConfig\x12).routerrpc.SetMissionControlConfigRequest\x1a*.routerrpc.SetMissionControlConfigResponse\x12[\n" + - "\x10QueryProbability\x12\".routerrpc.QueryProbabilityRequest\x1a#.routerrpc.QueryProbabilityResponse\x12I\n" + - "\n" + - "BuildRoute\x12\x1c.routerrpc.BuildRouteRequest\x1a\x1d.routerrpc.BuildRouteResponse\x12T\n" + - "\x13SubscribeHtlcEvents\x12%.routerrpc.SubscribeHtlcEventsRequest\x1a\x14.routerrpc.HtlcEvent0\x01\x12f\n" + - "\x0fHtlcInterceptor\x12'.routerrpc.ForwardHtlcInterceptResponse\x1a&.routerrpc.ForwardHtlcInterceptRequest(\x010\x01\x12[\n" + - "\x10UpdateChanStatus\x12\".routerrpc.UpdateChanStatusRequest\x1a#.routerrpc.UpdateChanStatusResponse\x12S\n" + - "\x14XAddLocalChanAliases\x12\x1c.routerrpc.AddAliasesRequest\x1a\x1d.routerrpc.AddAliasesResponse\x12\\\n" + - "\x17XDeleteLocalChanAliases\x12\x1f.routerrpc.DeleteAliasesRequest\x1a .routerrpc.DeleteAliasesResponse\x12\\\n" + - "\x17XFindBaseLocalChanAlias\x12\x1f.routerrpc.FindBaseAliasRequest\x1a .routerrpc.FindBaseAliasResponse\x12p\n" + - "\x17DeleteForwardingHistory\x12).routerrpc.DeleteForwardingHistoryRequest\x1a*.routerrpc.DeleteForwardingHistoryResponseB1Z/github.com/lightningnetwork/lnd/lnrpc/routerrpcb\x06proto3" +var file_routerrpc_router_proto_rawDesc = []byte{ + 0x0a, 0x16, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, + 0x72, 0x70, 0x63, 0x1a, 0x0f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd1, 0x09, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, + 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, + 0x10, 0x0a, 0x03, 0x61, 0x6d, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x61, 0x6d, + 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x48, 0x61, 0x73, 0x68, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x63, 0x6c, + 0x74, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, + 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x27, + 0x0a, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x6f, + 0x75, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x0e, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, + 0x12, 0x22, 0x0a, 0x0d, 0x66, 0x65, 0x65, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x73, 0x61, + 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, + 0x74, 0x53, 0x61, 0x74, 0x12, 0x2e, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x42, 0x04, + 0x18, 0x01, 0x30, 0x01, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x43, 0x68, + 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x6c, 0x69, 0x6d, + 0x69, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x63, 0x6c, 0x74, 0x76, 0x4c, 0x69, + 0x6d, 0x69, 0x74, 0x12, 0x31, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x68, 0x69, 0x6e, + 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x64, 0x0a, 0x13, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x63, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x0b, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x64, 0x65, 0x73, 0x74, 0x43, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x19, 0x0a, 0x08, + 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, + 0x61, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x65, 0x65, 0x5f, 0x6c, + 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0c, 0x66, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a, + 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, + 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x50, + 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x73, + 0x65, 0x6c, 0x66, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x10, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x53, 0x65, 0x6c, 0x66, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x0d, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x66, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, 0x52, 0x0c, 0x64, + 0x65, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6d, + 0x61, 0x78, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, + 0x6d, 0x61, 0x78, 0x50, 0x61, 0x72, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x6f, 0x5f, 0x69, + 0x6e, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, + 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x6e, 0x6f, 0x49, 0x6e, 0x66, 0x6c, 0x69, 0x67, 0x68, + 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x67, + 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x13, 0x20, + 0x03, 0x28, 0x04, 0x52, 0x0f, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, + 0x6e, 0x49, 0x64, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, + 0x61, 0x64, 0x64, 0x72, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x2d, 0x0a, 0x13, 0x6d, 0x61, 0x78, 0x5f, 0x73, + 0x68, 0x61, 0x72, 0x64, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x15, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x53, 0x68, 0x61, 0x72, 0x64, 0x53, 0x69, + 0x7a, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6d, 0x70, 0x18, 0x16, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x03, 0x61, 0x6d, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, + 0x5f, 0x70, 0x72, 0x65, 0x66, 0x18, 0x17, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x74, 0x69, 0x6d, + 0x65, 0x50, 0x72, 0x65, 0x66, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x61, + 0x62, 0x6c, 0x65, 0x18, 0x18, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x63, 0x61, 0x6e, 0x63, 0x65, + 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x71, 0x0a, 0x18, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x68, + 0x6f, 0x70, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x73, 0x18, 0x19, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x15, 0x66, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x44, 0x0a, 0x16, 0x44, 0x65, 0x73, 0x74, + 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x48, + 0x0a, 0x1a, 0x46, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x68, 0x0a, 0x13, 0x54, 0x72, 0x61, 0x63, + 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, + 0x73, 0x68, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x6f, 0x5f, 0x69, 0x6e, 0x66, 0x6c, 0x69, 0x67, 0x68, + 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x11, 0x6e, 0x6f, 0x49, 0x6e, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x73, 0x22, 0x46, 0x0a, 0x14, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x6f, + 0x5f, 0x69, 0x6e, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x6e, 0x6f, 0x49, 0x6e, 0x66, 0x6c, 0x69, + 0x67, 0x68, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0x81, 0x01, 0x0a, 0x0f, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, + 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x65, + 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x61, 0x6d, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x74, 0x53, 0x61, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x22, 0xa8, + 0x01, 0x0a, 0x10, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x66, + 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, + 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a, + 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, + 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x42, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, + 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x46, 0x61, 0x69, + 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, + 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xbc, 0x02, 0x0a, 0x12, 0x53, 0x65, + 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, + 0x61, 0x73, 0x68, 0x12, 0x22, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, + 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x5f, + 0x74, 0x65, 0x6d, 0x70, 0x5f, 0x65, 0x72, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, + 0x73, 0x6b, 0x69, 0x70, 0x54, 0x65, 0x6d, 0x70, 0x45, 0x72, 0x72, 0x12, 0x71, 0x0a, 0x18, 0x66, + 0x69, 0x72, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x70, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, + 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, + 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x72, + 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x15, 0x66, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, + 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x48, + 0x0a, 0x1a, 0x46, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x5b, 0x0a, 0x13, 0x53, 0x65, 0x6e, 0x64, + 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x66, + 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x07, 0x66, 0x61, + 0x69, 0x6c, 0x75, 0x72, 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x22, 0x1d, 0x0a, 0x1b, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x22, 0x51, 0x0a, 0x1b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x2c, 0x0a, 0x05, 0x70, 0x61, 0x69, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, + 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x48, + 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x05, 0x70, 0x61, 0x69, 0x72, 0x73, 0x4a, 0x04, 0x08, + 0x01, 0x10, 0x02, 0x22, 0x62, 0x0a, 0x1c, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x05, 0x70, 0x61, 0x69, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, + 0x61, 0x69, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x05, 0x70, 0x61, 0x69, 0x72, + 0x73, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x22, 0x1f, 0x0a, 0x1d, 0x58, 0x49, 0x6d, 0x70, 0x6f, + 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x8a, 0x01, 0x0a, 0x0b, 0x50, 0x61, 0x69, + 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, + 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6e, 0x6f, 0x64, + 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x74, 0x6f, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x54, 0x6f, 0x12, 0x2d, + 0x0a, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x69, 0x72, + 0x44, 0x61, 0x74, 0x61, 0x52, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x04, 0x08, + 0x03, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x4a, + 0x04, 0x08, 0x06, 0x10, 0x07, 0x22, 0xe8, 0x01, 0x0a, 0x08, 0x50, 0x61, 0x69, 0x72, 0x44, 0x61, + 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x66, 0x61, 0x69, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, + 0x20, 0x0a, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x61, 0x69, 0x6c, 0x41, 0x6d, 0x74, 0x53, 0x61, + 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, + 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66, 0x61, 0x69, 0x6c, 0x41, 0x6d, + 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x73, 0x75, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x75, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0d, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x41, 0x6d, 0x74, 0x53, 0x61, 0x74, + 0x12, 0x28, 0x0a, 0x10, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x61, 0x6d, 0x74, 0x5f, + 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x75, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, + 0x22, 0x20, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x22, 0x5a, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, + 0x63, 0x2e, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x59, + 0x0a, 0x1e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x37, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1f, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x21, 0x0a, 0x1f, 0x53, 0x65, 0x74, + 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x89, 0x04, 0x0a, + 0x14, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x0a, 0x11, 0x68, 0x61, 0x6c, 0x66, 0x5f, 0x6c, 0x69, + 0x66, 0x65, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, + 0x42, 0x02, 0x18, 0x01, 0x52, 0x0f, 0x68, 0x61, 0x6c, 0x66, 0x4c, 0x69, 0x66, 0x65, 0x53, 0x65, + 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x2b, 0x0a, 0x0f, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x72, 0x6f, + 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x42, 0x02, + 0x18, 0x01, 0x52, 0x0e, 0x68, 0x6f, 0x70, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, + 0x74, 0x79, 0x12, 0x1a, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x02, 0x42, 0x02, 0x18, 0x01, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x36, + 0x0a, 0x17, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x15, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x43, 0x0a, 0x1e, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, + 0x6d, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x78, 0x5f, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x1b, + 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, + 0x6c, 0x61, 0x78, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x46, 0x0a, 0x05, 0x6d, + 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x72, 0x6f, 0x75, + 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x72, 0x6f, 0x62, + 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x52, 0x05, 0x6d, 0x6f, + 0x64, 0x65, 0x6c, 0x12, 0x38, 0x0a, 0x07, 0x61, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x41, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, + 0x72, 0x73, 0x48, 0x00, 0x52, 0x07, 0x61, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x12, 0x38, 0x0a, + 0x07, 0x62, 0x69, 0x6d, 0x6f, 0x64, 0x61, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, + 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x69, 0x6d, 0x6f, 0x64, + 0x61, 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x48, 0x00, 0x52, 0x07, + 0x62, 0x69, 0x6d, 0x6f, 0x64, 0x61, 0x6c, 0x22, 0x2c, 0x0a, 0x10, 0x50, 0x72, 0x6f, 0x62, 0x61, + 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x41, + 0x50, 0x52, 0x49, 0x4f, 0x52, 0x49, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x42, 0x49, 0x4d, 0x4f, + 0x44, 0x41, 0x4c, 0x10, 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, + 0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x72, 0x0a, 0x11, 0x42, 0x69, 0x6d, 0x6f, + 0x64, 0x61, 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x1f, 0x0a, + 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x01, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x09, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1d, 0x0a, + 0x0a, 0x64, 0x65, 0x63, 0x61, 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x09, 0x64, 0x65, 0x63, 0x61, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x22, 0xad, 0x01, 0x0a, + 0x11, 0x41, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, + 0x72, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x61, 0x6c, 0x66, 0x5f, 0x6c, 0x69, 0x66, 0x65, 0x5f, + 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x68, + 0x61, 0x6c, 0x66, 0x4c, 0x69, 0x66, 0x65, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x27, + 0x0a, 0x0f, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, + 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0e, 0x68, 0x6f, 0x70, 0x50, 0x72, 0x6f, 0x62, + 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, + 0x2b, 0x0a, 0x11, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x5f, 0x66, 0x72, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x63, 0x61, 0x70, 0x61, + 0x63, 0x69, 0x74, 0x79, 0x46, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x6a, 0x0a, 0x17, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x72, 0x6f, 0x6d, 0x5f, + 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x66, 0x72, 0x6f, 0x6d, + 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6f, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x74, 0x6f, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, + 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x07, 0x61, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x22, 0x6b, 0x0a, 0x18, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, + 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x62, 0x61, + 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x2d, 0x0a, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, + 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, + 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x44, 0x61, 0x74, 0x61, 0x52, 0x07, 0x68, 0x69, + 0x73, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x86, 0x03, 0x0a, 0x11, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, + 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, + 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, + 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x0e, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61, + 0x12, 0x2c, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0e, + 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, + 0x0a, 0x0b, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x68, 0x6f, 0x70, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x12, + 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64, + 0x64, 0x72, 0x12, 0x70, 0x0a, 0x18, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x70, 0x5f, + 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x06, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x15, 0x66, + 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x48, 0x0a, 0x1a, 0x46, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, + 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x38, + 0x0a, 0x12, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x53, 0x75, 0x62, 0x73, + 0x63, 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x86, 0x06, 0x0a, 0x09, 0x48, 0x74, 0x6c, 0x63, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x11, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x11, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, + 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, + 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x64, 0x12, 0x28, + 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, + 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, + 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x4e, 0x73, 0x12, 0x3d, 0x0a, 0x0a, 0x65, + 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, + 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x3e, 0x0a, 0x0d, 0x66, 0x6f, + 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x17, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, + 0x72, 0x77, 0x61, 0x72, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x66, 0x6f, + 0x72, 0x77, 0x61, 0x72, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x4b, 0x0a, 0x12, 0x66, 0x6f, + 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, + 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x10, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x46, 0x61, + 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3b, 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, + 0x65, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x12, 0x42, 0x0a, 0x0f, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x66, 0x61, 0x69, + 0x6c, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x46, 0x61, + 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x6c, 0x69, 0x6e, 0x6b, 0x46, + 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x73, + 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, + 0x52, 0x0f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x45, 0x0a, 0x10, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x48, 0x74, 0x6c, + 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x48, + 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x3c, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, + 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, + 0x52, 0x45, 0x43, 0x45, 0x49, 0x56, 0x45, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x4f, 0x52, + 0x57, 0x41, 0x52, 0x44, 0x10, 0x03, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, + 0xbc, 0x01, 0x0a, 0x08, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2b, 0x0a, 0x11, + 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x6f, 0x63, + 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, + 0x67, 0x54, 0x69, 0x6d, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74, + 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x54, 0x69, + 0x6d, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x2a, 0x0a, 0x11, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, + 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x74, 0x4d, 0x73, + 0x61, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x61, + 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6f, + 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x22, 0x37, + 0x0a, 0x0c, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x27, + 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x72, + 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x66, + 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x12, 0x0a, 0x10, 0x46, 0x6f, 0x72, 0x77, 0x61, + 0x72, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x29, 0x0a, 0x0b, 0x53, + 0x65, 0x74, 0x74, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, + 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, + 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x46, 0x0a, 0x0e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x48, + 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x74, 0x74, + 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x65, 0x74, 0x74, 0x6c, + 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x22, 0x11, + 0x0a, 0x0f, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x22, 0xdf, 0x01, 0x0a, 0x0d, 0x4c, 0x69, 0x6e, 0x6b, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, + 0x6c, 0x63, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x3d, 0x0a, 0x0c, + 0x77, 0x69, 0x72, 0x65, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, + 0x72, 0x65, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x0b, + 0x77, 0x69, 0x72, 0x65, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x3f, 0x0a, 0x0e, 0x66, + 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, + 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x52, 0x0d, 0x66, + 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x25, 0x0a, 0x0e, + 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x53, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x22, 0x8a, 0x01, 0x0a, 0x0d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2d, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, + 0x12, 0x28, 0x0a, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, + 0x6d, 0x70, 0x74, 0x52, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, + 0x22, 0x3e, 0x0a, 0x0a, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x17, + 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x74, 0x6c, 0x63, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x74, 0x6c, 0x63, 0x49, 0x64, + 0x22, 0xa7, 0x06, 0x0a, 0x1b, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x47, 0x0a, 0x14, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x69, 0x72, + 0x63, 0x75, 0x69, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x69, 0x72, 0x63, 0x75, + 0x69, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, + 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x69, 0x6e, 0x63, + 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, + 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, + 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x69, + 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x45, 0x78, + 0x70, 0x69, 0x72, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, + 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x3b, 0x0a, 0x1a, 0x6f, 0x75, 0x74, 0x67, 0x6f, + 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x68, + 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x17, 0x6f, 0x75, 0x74, + 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x68, + 0x61, 0x6e, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, + 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x12, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, + 0x6e, 0x67, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, + 0x60, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, + 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, + 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x62, + 0x12, 0x28, 0x0a, 0x10, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x68, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x6f, + 0x46, 0x61, 0x69, 0x6c, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x74, 0x0a, 0x16, 0x69, 0x6e, + 0x5f, 0x77, 0x69, 0x72, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x72, 0x6f, 0x75, + 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, + 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x49, 0x6e, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, + 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x13, 0x69, 0x6e, 0x57, + 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, + 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x1a, 0x46, 0x0a, 0x18, 0x49, 0x6e, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb9, 0x04, 0x0a, 0x1c, 0x46, + 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, + 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x14, 0x69, + 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x5f, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, + 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, + 0x74, 0x4b, 0x65, 0x79, 0x12, 0x3b, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x48, 0x6f, 0x6c, 0x64, 0x46, 0x6f, 0x72, 0x77, + 0x61, 0x72, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x27, 0x0a, + 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x3d, 0x0a, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, + 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x46, 0x61, 0x69, + 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x0b, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, + 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x69, + 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6f, + 0x75, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x6f, 0x75, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, + 0x73, 0x61, 0x74, 0x12, 0x78, 0x0a, 0x17, 0x6f, 0x75, 0x74, 0x5f, 0x77, 0x69, 0x72, 0x65, 0x5f, + 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x08, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4f, 0x75, + 0x74, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x14, 0x6f, 0x75, 0x74, 0x57, 0x69, 0x72, 0x65, + 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x47, 0x0a, + 0x19, 0x4f, 0x75, 0x74, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x82, 0x01, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, + 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x41, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x1a, 0x0a, 0x18, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x43, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x41, 0x6c, + 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x0a, + 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, + 0x70, 0x52, 0x09, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x73, 0x22, 0x44, 0x0a, 0x12, + 0x41, 0x64, 0x64, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x0a, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x52, 0x09, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, + 0x70, 0x73, 0x22, 0x46, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x69, 0x61, + 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x0a, 0x61, 0x6c, + 0x69, 0x61, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x52, + 0x09, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x73, 0x22, 0x47, 0x0a, 0x15, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x0a, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6d, 0x61, 0x70, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x52, 0x09, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x4d, + 0x61, 0x70, 0x73, 0x22, 0x2c, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x64, 0x42, 0x61, 0x73, 0x65, 0x41, + 0x6c, 0x69, 0x61, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x61, + 0x6c, 0x69, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61, + 0x73, 0x22, 0x2b, 0x0a, 0x15, 0x46, 0x69, 0x6e, 0x64, 0x42, 0x61, 0x73, 0x65, 0x41, 0x6c, 0x69, + 0x61, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x61, + 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x2a, 0x81, + 0x04, 0x0a, 0x0d, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, + 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0d, 0x0a, + 0x09, 0x4e, 0x4f, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, + 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x02, 0x12, 0x15, + 0x0a, 0x11, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x45, 0x4c, 0x49, 0x47, 0x49, + 0x42, 0x4c, 0x45, 0x10, 0x03, 0x12, 0x14, 0x0a, 0x10, 0x4f, 0x4e, 0x5f, 0x43, 0x48, 0x41, 0x49, + 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x04, 0x12, 0x14, 0x0a, 0x10, 0x48, + 0x54, 0x4c, 0x43, 0x5f, 0x45, 0x58, 0x43, 0x45, 0x45, 0x44, 0x53, 0x5f, 0x4d, 0x41, 0x58, 0x10, + 0x05, 0x12, 0x18, 0x0a, 0x14, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, + 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x49, + 0x4e, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, + 0x44, 0x10, 0x07, 0x12, 0x13, 0x0a, 0x0f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x44, 0x44, 0x5f, + 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x08, 0x12, 0x15, 0x0a, 0x11, 0x46, 0x4f, 0x52, 0x57, + 0x41, 0x52, 0x44, 0x53, 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x09, 0x12, + 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, + 0x4c, 0x45, 0x44, 0x10, 0x0a, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, + 0x5f, 0x55, 0x4e, 0x44, 0x45, 0x52, 0x50, 0x41, 0x49, 0x44, 0x10, 0x0b, 0x12, 0x1b, 0x0a, 0x17, + 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, + 0x4f, 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, 0x10, 0x0c, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x56, + 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x0d, 0x12, + 0x17, 0x0a, 0x13, 0x4d, 0x50, 0x50, 0x5f, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x54, + 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x0e, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x44, 0x44, 0x52, + 0x45, 0x53, 0x53, 0x5f, 0x4d, 0x49, 0x53, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x10, 0x0f, 0x12, 0x16, + 0x0a, 0x12, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x4d, 0x49, 0x53, 0x4d, + 0x41, 0x54, 0x43, 0x48, 0x10, 0x10, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x4f, + 0x54, 0x41, 0x4c, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x4c, 0x4f, 0x57, 0x10, 0x11, 0x12, 0x10, 0x0a, + 0x0c, 0x53, 0x45, 0x54, 0x5f, 0x4f, 0x56, 0x45, 0x52, 0x50, 0x41, 0x49, 0x44, 0x10, 0x12, 0x12, + 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x49, 0x4e, 0x56, 0x4f, 0x49, + 0x43, 0x45, 0x10, 0x13, 0x12, 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, + 0x4b, 0x45, 0x59, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x14, 0x12, 0x13, 0x0a, 0x0f, 0x4d, 0x50, 0x50, + 0x5f, 0x49, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x15, 0x12, 0x12, + 0x0a, 0x0e, 0x43, 0x49, 0x52, 0x43, 0x55, 0x4c, 0x41, 0x52, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, + 0x10, 0x16, 0x2a, 0xae, 0x01, 0x0a, 0x0c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x5f, 0x46, 0x4c, 0x49, 0x47, 0x48, 0x54, + 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, + 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, + 0x4f, 0x55, 0x54, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, + 0x4e, 0x4f, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x03, 0x12, 0x10, 0x0a, 0x0c, 0x46, 0x41, + 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x24, 0x0a, 0x20, + 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, + 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, + 0x10, 0x05, 0x12, 0x1f, 0x0a, 0x1b, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x49, 0x4e, 0x53, + 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, + 0x45, 0x10, 0x06, 0x2a, 0x51, 0x0a, 0x18, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x48, 0x6f, + 0x6c, 0x64, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x0a, 0x0a, 0x06, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x46, + 0x41, 0x49, 0x4c, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x45, 0x53, 0x55, 0x4d, 0x45, 0x10, + 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x52, 0x45, 0x53, 0x55, 0x4d, 0x45, 0x5f, 0x4d, 0x4f, 0x44, 0x49, + 0x46, 0x49, 0x45, 0x44, 0x10, 0x03, 0x2a, 0x35, 0x0a, 0x10, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x4e, + 0x41, 0x42, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, + 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x55, 0x54, 0x4f, 0x10, 0x02, 0x32, 0xc6, 0x0e, + 0x0a, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x12, 0x40, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x32, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0e, 0x54, 0x72, + 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x32, 0x12, 0x1e, 0x2e, 0x72, + 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x42, + 0x0a, 0x0d, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, + 0x1f, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x63, + 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x30, 0x01, 0x12, 0x4b, 0x0a, 0x10, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, 0x1a, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, + 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x51, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x1d, + 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, + 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, + 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, + 0x02, 0x01, 0x12, 0x42, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x56, 0x32, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41, + 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x12, 0x64, 0x0a, 0x13, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x25, 0x2e, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x13, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x72, 0x6f, 0x75, + 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x15, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x27, 0x2e, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, + 0x0a, 0x17, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x70, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29, 0x2e, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x10, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, + 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x22, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, + 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, + 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x72, 0x6f, 0x75, + 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, + 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x49, 0x0a, 0x0a, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x1c, 0x2e, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, + 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x13, 0x53, 0x75, + 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x73, 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, + 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, + 0x72, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, + 0x12, 0x4d, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, + 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, + 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x03, 0x88, 0x02, 0x01, 0x30, 0x01, 0x12, + 0x4f, 0x0a, 0x0c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, + 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x63, + 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x18, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x03, 0x88, 0x02, 0x01, 0x30, 0x01, + 0x12, 0x66, 0x0a, 0x0f, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, + 0x74, 0x6f, 0x72, 0x12, 0x27, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, + 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, + 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x26, 0x2e, 0x72, + 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, + 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x2e, 0x72, + 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, + 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x14, 0x58, 0x41, 0x64, 0x64, 0x4c, 0x6f, 0x63, + 0x61, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x1c, 0x2e, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x41, 0x6c, 0x69, + 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x41, 0x6c, 0x69, 0x61, 0x73, + 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x17, 0x58, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x41, 0x6c, + 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x1f, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, + 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, + 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x17, 0x58, 0x46, 0x69, 0x6e, + 0x64, 0x42, 0x61, 0x73, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x41, 0x6c, + 0x69, 0x61, 0x73, 0x12, 0x1f, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, + 0x46, 0x69, 0x6e, 0x64, 0x42, 0x61, 0x73, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x46, 0x69, 0x6e, 0x64, 0x42, 0x61, 0x73, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, + 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} var ( file_routerrpc_router_proto_rawDescOnce sync.Once - file_routerrpc_router_proto_rawDescData []byte + file_routerrpc_router_proto_rawDescData = file_routerrpc_router_proto_rawDesc ) func file_routerrpc_router_proto_rawDescGZIP() []byte { file_routerrpc_router_proto_rawDescOnce.Do(func() { - file_routerrpc_router_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_routerrpc_router_proto_rawDesc), len(file_routerrpc_router_proto_rawDesc))) + file_routerrpc_router_proto_rawDescData = protoimpl.X.CompressGZIP(file_routerrpc_router_proto_rawDescData) }) return file_routerrpc_router_proto_rawDescData } -var file_routerrpc_router_proto_enumTypes = make([]protoimpl.EnumInfo, 5) +var file_routerrpc_router_proto_enumTypes = make([]protoimpl.EnumInfo, 6) var file_routerrpc_router_proto_msgTypes = make([]protoimpl.MessageInfo, 54) -var file_routerrpc_router_proto_goTypes = []any{ +var file_routerrpc_router_proto_goTypes = []interface{}{ (FailureDetail)(0), // 0: routerrpc.FailureDetail - (ResolveHoldForwardAction)(0), // 1: routerrpc.ResolveHoldForwardAction - (ChanStatusAction)(0), // 2: routerrpc.ChanStatusAction - (MissionControlConfig_ProbabilityModel)(0), // 3: routerrpc.MissionControlConfig.ProbabilityModel - (HtlcEvent_EventType)(0), // 4: routerrpc.HtlcEvent.EventType - (*SendPaymentRequest)(nil), // 5: routerrpc.SendPaymentRequest - (*TrackPaymentRequest)(nil), // 6: routerrpc.TrackPaymentRequest - (*TrackPaymentsRequest)(nil), // 7: routerrpc.TrackPaymentsRequest - (*RouteFeeRequest)(nil), // 8: routerrpc.RouteFeeRequest - (*RouteFeeResponse)(nil), // 9: routerrpc.RouteFeeResponse - (*SendToRouteRequest)(nil), // 10: routerrpc.SendToRouteRequest - (*ResetMissionControlRequest)(nil), // 11: routerrpc.ResetMissionControlRequest - (*ResetMissionControlResponse)(nil), // 12: routerrpc.ResetMissionControlResponse - (*QueryMissionControlRequest)(nil), // 13: routerrpc.QueryMissionControlRequest - (*QueryMissionControlResponse)(nil), // 14: routerrpc.QueryMissionControlResponse - (*XImportMissionControlRequest)(nil), // 15: routerrpc.XImportMissionControlRequest - (*XImportMissionControlResponse)(nil), // 16: routerrpc.XImportMissionControlResponse - (*PairHistory)(nil), // 17: routerrpc.PairHistory - (*PairData)(nil), // 18: routerrpc.PairData - (*GetMissionControlConfigRequest)(nil), // 19: routerrpc.GetMissionControlConfigRequest - (*GetMissionControlConfigResponse)(nil), // 20: routerrpc.GetMissionControlConfigResponse - (*SetMissionControlConfigRequest)(nil), // 21: routerrpc.SetMissionControlConfigRequest - (*SetMissionControlConfigResponse)(nil), // 22: routerrpc.SetMissionControlConfigResponse - (*MissionControlConfig)(nil), // 23: routerrpc.MissionControlConfig - (*BimodalParameters)(nil), // 24: routerrpc.BimodalParameters - (*AprioriParameters)(nil), // 25: routerrpc.AprioriParameters - (*QueryProbabilityRequest)(nil), // 26: routerrpc.QueryProbabilityRequest - (*QueryProbabilityResponse)(nil), // 27: routerrpc.QueryProbabilityResponse - (*BuildRouteRequest)(nil), // 28: routerrpc.BuildRouteRequest - (*BuildRouteResponse)(nil), // 29: routerrpc.BuildRouteResponse - (*SubscribeHtlcEventsRequest)(nil), // 30: routerrpc.SubscribeHtlcEventsRequest - (*HtlcEvent)(nil), // 31: routerrpc.HtlcEvent - (*HtlcInfo)(nil), // 32: routerrpc.HtlcInfo - (*ForwardEvent)(nil), // 33: routerrpc.ForwardEvent - (*ForwardFailEvent)(nil), // 34: routerrpc.ForwardFailEvent - (*SettleEvent)(nil), // 35: routerrpc.SettleEvent - (*FinalHtlcEvent)(nil), // 36: routerrpc.FinalHtlcEvent - (*SubscribedEvent)(nil), // 37: routerrpc.SubscribedEvent - (*LinkFailEvent)(nil), // 38: routerrpc.LinkFailEvent - (*CircuitKey)(nil), // 39: routerrpc.CircuitKey - (*ForwardHtlcInterceptRequest)(nil), // 40: routerrpc.ForwardHtlcInterceptRequest - (*ForwardHtlcInterceptResponse)(nil), // 41: routerrpc.ForwardHtlcInterceptResponse - (*UpdateChanStatusRequest)(nil), // 42: routerrpc.UpdateChanStatusRequest - (*UpdateChanStatusResponse)(nil), // 43: routerrpc.UpdateChanStatusResponse - (*AddAliasesRequest)(nil), // 44: routerrpc.AddAliasesRequest - (*AddAliasesResponse)(nil), // 45: routerrpc.AddAliasesResponse - (*DeleteAliasesRequest)(nil), // 46: routerrpc.DeleteAliasesRequest - (*DeleteAliasesResponse)(nil), // 47: routerrpc.DeleteAliasesResponse - (*FindBaseAliasRequest)(nil), // 48: routerrpc.FindBaseAliasRequest - (*FindBaseAliasResponse)(nil), // 49: routerrpc.FindBaseAliasResponse - (*DeleteForwardingHistoryRequest)(nil), // 50: routerrpc.DeleteForwardingHistoryRequest - (*DeleteForwardingHistoryResponse)(nil), // 51: routerrpc.DeleteForwardingHistoryResponse - nil, // 52: routerrpc.SendPaymentRequest.DestCustomRecordsEntry - nil, // 53: routerrpc.SendPaymentRequest.FirstHopCustomRecordsEntry - nil, // 54: routerrpc.SendToRouteRequest.FirstHopCustomRecordsEntry - nil, // 55: routerrpc.BuildRouteRequest.FirstHopCustomRecordsEntry - nil, // 56: routerrpc.ForwardHtlcInterceptRequest.CustomRecordsEntry - nil, // 57: routerrpc.ForwardHtlcInterceptRequest.InWireCustomRecordsEntry - nil, // 58: routerrpc.ForwardHtlcInterceptResponse.OutWireCustomRecordsEntry - (*lnrpc.RouteHint)(nil), // 59: lnrpc.RouteHint - (lnrpc.FeatureBit)(0), // 60: lnrpc.FeatureBit - (lnrpc.PaymentFailureReason)(0), // 61: lnrpc.PaymentFailureReason - (*lnrpc.Route)(nil), // 62: lnrpc.Route - (lnrpc.Failure_FailureCode)(0), // 63: lnrpc.Failure.FailureCode - (*lnrpc.ChannelPoint)(nil), // 64: lnrpc.ChannelPoint - (*lnrpc.AliasMap)(nil), // 65: lnrpc.AliasMap - (*lnrpc.Payment)(nil), // 66: lnrpc.Payment - (*lnrpc.HTLCAttempt)(nil), // 67: lnrpc.HTLCAttempt + (PaymentState)(0), // 1: routerrpc.PaymentState + (ResolveHoldForwardAction)(0), // 2: routerrpc.ResolveHoldForwardAction + (ChanStatusAction)(0), // 3: routerrpc.ChanStatusAction + (MissionControlConfig_ProbabilityModel)(0), // 4: routerrpc.MissionControlConfig.ProbabilityModel + (HtlcEvent_EventType)(0), // 5: routerrpc.HtlcEvent.EventType + (*SendPaymentRequest)(nil), // 6: routerrpc.SendPaymentRequest + (*TrackPaymentRequest)(nil), // 7: routerrpc.TrackPaymentRequest + (*TrackPaymentsRequest)(nil), // 8: routerrpc.TrackPaymentsRequest + (*RouteFeeRequest)(nil), // 9: routerrpc.RouteFeeRequest + (*RouteFeeResponse)(nil), // 10: routerrpc.RouteFeeResponse + (*SendToRouteRequest)(nil), // 11: routerrpc.SendToRouteRequest + (*SendToRouteResponse)(nil), // 12: routerrpc.SendToRouteResponse + (*ResetMissionControlRequest)(nil), // 13: routerrpc.ResetMissionControlRequest + (*ResetMissionControlResponse)(nil), // 14: routerrpc.ResetMissionControlResponse + (*QueryMissionControlRequest)(nil), // 15: routerrpc.QueryMissionControlRequest + (*QueryMissionControlResponse)(nil), // 16: routerrpc.QueryMissionControlResponse + (*XImportMissionControlRequest)(nil), // 17: routerrpc.XImportMissionControlRequest + (*XImportMissionControlResponse)(nil), // 18: routerrpc.XImportMissionControlResponse + (*PairHistory)(nil), // 19: routerrpc.PairHistory + (*PairData)(nil), // 20: routerrpc.PairData + (*GetMissionControlConfigRequest)(nil), // 21: routerrpc.GetMissionControlConfigRequest + (*GetMissionControlConfigResponse)(nil), // 22: routerrpc.GetMissionControlConfigResponse + (*SetMissionControlConfigRequest)(nil), // 23: routerrpc.SetMissionControlConfigRequest + (*SetMissionControlConfigResponse)(nil), // 24: routerrpc.SetMissionControlConfigResponse + (*MissionControlConfig)(nil), // 25: routerrpc.MissionControlConfig + (*BimodalParameters)(nil), // 26: routerrpc.BimodalParameters + (*AprioriParameters)(nil), // 27: routerrpc.AprioriParameters + (*QueryProbabilityRequest)(nil), // 28: routerrpc.QueryProbabilityRequest + (*QueryProbabilityResponse)(nil), // 29: routerrpc.QueryProbabilityResponse + (*BuildRouteRequest)(nil), // 30: routerrpc.BuildRouteRequest + (*BuildRouteResponse)(nil), // 31: routerrpc.BuildRouteResponse + (*SubscribeHtlcEventsRequest)(nil), // 32: routerrpc.SubscribeHtlcEventsRequest + (*HtlcEvent)(nil), // 33: routerrpc.HtlcEvent + (*HtlcInfo)(nil), // 34: routerrpc.HtlcInfo + (*ForwardEvent)(nil), // 35: routerrpc.ForwardEvent + (*ForwardFailEvent)(nil), // 36: routerrpc.ForwardFailEvent + (*SettleEvent)(nil), // 37: routerrpc.SettleEvent + (*FinalHtlcEvent)(nil), // 38: routerrpc.FinalHtlcEvent + (*SubscribedEvent)(nil), // 39: routerrpc.SubscribedEvent + (*LinkFailEvent)(nil), // 40: routerrpc.LinkFailEvent + (*PaymentStatus)(nil), // 41: routerrpc.PaymentStatus + (*CircuitKey)(nil), // 42: routerrpc.CircuitKey + (*ForwardHtlcInterceptRequest)(nil), // 43: routerrpc.ForwardHtlcInterceptRequest + (*ForwardHtlcInterceptResponse)(nil), // 44: routerrpc.ForwardHtlcInterceptResponse + (*UpdateChanStatusRequest)(nil), // 45: routerrpc.UpdateChanStatusRequest + (*UpdateChanStatusResponse)(nil), // 46: routerrpc.UpdateChanStatusResponse + (*AddAliasesRequest)(nil), // 47: routerrpc.AddAliasesRequest + (*AddAliasesResponse)(nil), // 48: routerrpc.AddAliasesResponse + (*DeleteAliasesRequest)(nil), // 49: routerrpc.DeleteAliasesRequest + (*DeleteAliasesResponse)(nil), // 50: routerrpc.DeleteAliasesResponse + (*FindBaseAliasRequest)(nil), // 51: routerrpc.FindBaseAliasRequest + (*FindBaseAliasResponse)(nil), // 52: routerrpc.FindBaseAliasResponse + nil, // 53: routerrpc.SendPaymentRequest.DestCustomRecordsEntry + nil, // 54: routerrpc.SendPaymentRequest.FirstHopCustomRecordsEntry + nil, // 55: routerrpc.SendToRouteRequest.FirstHopCustomRecordsEntry + nil, // 56: routerrpc.BuildRouteRequest.FirstHopCustomRecordsEntry + nil, // 57: routerrpc.ForwardHtlcInterceptRequest.CustomRecordsEntry + nil, // 58: routerrpc.ForwardHtlcInterceptRequest.InWireCustomRecordsEntry + nil, // 59: routerrpc.ForwardHtlcInterceptResponse.OutWireCustomRecordsEntry + (*lnrpc.RouteHint)(nil), // 60: lnrpc.RouteHint + (lnrpc.FeatureBit)(0), // 61: lnrpc.FeatureBit + (lnrpc.PaymentFailureReason)(0), // 62: lnrpc.PaymentFailureReason + (*lnrpc.Route)(nil), // 63: lnrpc.Route + (*lnrpc.Failure)(nil), // 64: lnrpc.Failure + (lnrpc.Failure_FailureCode)(0), // 65: lnrpc.Failure.FailureCode + (*lnrpc.HTLCAttempt)(nil), // 66: lnrpc.HTLCAttempt + (*lnrpc.ChannelPoint)(nil), // 67: lnrpc.ChannelPoint + (*lnrpc.AliasMap)(nil), // 68: lnrpc.AliasMap + (*lnrpc.Payment)(nil), // 69: lnrpc.Payment } var file_routerrpc_router_proto_depIdxs = []int32{ - 59, // 0: routerrpc.SendPaymentRequest.route_hints:type_name -> lnrpc.RouteHint - 52, // 1: routerrpc.SendPaymentRequest.dest_custom_records:type_name -> routerrpc.SendPaymentRequest.DestCustomRecordsEntry - 60, // 2: routerrpc.SendPaymentRequest.dest_features:type_name -> lnrpc.FeatureBit - 53, // 3: routerrpc.SendPaymentRequest.first_hop_custom_records:type_name -> routerrpc.SendPaymentRequest.FirstHopCustomRecordsEntry - 61, // 4: routerrpc.RouteFeeResponse.failure_reason:type_name -> lnrpc.PaymentFailureReason - 62, // 5: routerrpc.SendToRouteRequest.route:type_name -> lnrpc.Route - 54, // 6: routerrpc.SendToRouteRequest.first_hop_custom_records:type_name -> routerrpc.SendToRouteRequest.FirstHopCustomRecordsEntry - 17, // 7: routerrpc.QueryMissionControlResponse.pairs:type_name -> routerrpc.PairHistory - 17, // 8: routerrpc.XImportMissionControlRequest.pairs:type_name -> routerrpc.PairHistory - 18, // 9: routerrpc.PairHistory.history:type_name -> routerrpc.PairData - 23, // 10: routerrpc.GetMissionControlConfigResponse.config:type_name -> routerrpc.MissionControlConfig - 23, // 11: routerrpc.SetMissionControlConfigRequest.config:type_name -> routerrpc.MissionControlConfig - 3, // 12: routerrpc.MissionControlConfig.model:type_name -> routerrpc.MissionControlConfig.ProbabilityModel - 25, // 13: routerrpc.MissionControlConfig.apriori:type_name -> routerrpc.AprioriParameters - 24, // 14: routerrpc.MissionControlConfig.bimodal:type_name -> routerrpc.BimodalParameters - 18, // 15: routerrpc.QueryProbabilityResponse.history:type_name -> routerrpc.PairData - 55, // 16: routerrpc.BuildRouteRequest.first_hop_custom_records:type_name -> routerrpc.BuildRouteRequest.FirstHopCustomRecordsEntry - 62, // 17: routerrpc.BuildRouteResponse.route:type_name -> lnrpc.Route - 4, // 18: routerrpc.HtlcEvent.event_type:type_name -> routerrpc.HtlcEvent.EventType - 33, // 19: routerrpc.HtlcEvent.forward_event:type_name -> routerrpc.ForwardEvent - 34, // 20: routerrpc.HtlcEvent.forward_fail_event:type_name -> routerrpc.ForwardFailEvent - 35, // 21: routerrpc.HtlcEvent.settle_event:type_name -> routerrpc.SettleEvent - 38, // 22: routerrpc.HtlcEvent.link_fail_event:type_name -> routerrpc.LinkFailEvent - 37, // 23: routerrpc.HtlcEvent.subscribed_event:type_name -> routerrpc.SubscribedEvent - 36, // 24: routerrpc.HtlcEvent.final_htlc_event:type_name -> routerrpc.FinalHtlcEvent - 32, // 25: routerrpc.ForwardEvent.info:type_name -> routerrpc.HtlcInfo - 32, // 26: routerrpc.LinkFailEvent.info:type_name -> routerrpc.HtlcInfo - 63, // 27: routerrpc.LinkFailEvent.wire_failure:type_name -> lnrpc.Failure.FailureCode - 0, // 28: routerrpc.LinkFailEvent.failure_detail:type_name -> routerrpc.FailureDetail - 39, // 29: routerrpc.ForwardHtlcInterceptRequest.incoming_circuit_key:type_name -> routerrpc.CircuitKey - 56, // 30: routerrpc.ForwardHtlcInterceptRequest.custom_records:type_name -> routerrpc.ForwardHtlcInterceptRequest.CustomRecordsEntry - 57, // 31: routerrpc.ForwardHtlcInterceptRequest.in_wire_custom_records:type_name -> routerrpc.ForwardHtlcInterceptRequest.InWireCustomRecordsEntry - 39, // 32: routerrpc.ForwardHtlcInterceptResponse.incoming_circuit_key:type_name -> routerrpc.CircuitKey - 1, // 33: routerrpc.ForwardHtlcInterceptResponse.action:type_name -> routerrpc.ResolveHoldForwardAction - 63, // 34: routerrpc.ForwardHtlcInterceptResponse.failure_code:type_name -> lnrpc.Failure.FailureCode - 58, // 35: routerrpc.ForwardHtlcInterceptResponse.out_wire_custom_records:type_name -> routerrpc.ForwardHtlcInterceptResponse.OutWireCustomRecordsEntry - 64, // 36: routerrpc.UpdateChanStatusRequest.chan_point:type_name -> lnrpc.ChannelPoint - 2, // 37: routerrpc.UpdateChanStatusRequest.action:type_name -> routerrpc.ChanStatusAction - 65, // 38: routerrpc.AddAliasesRequest.alias_maps:type_name -> lnrpc.AliasMap - 65, // 39: routerrpc.AddAliasesResponse.alias_maps:type_name -> lnrpc.AliasMap - 65, // 40: routerrpc.DeleteAliasesRequest.alias_maps:type_name -> lnrpc.AliasMap - 65, // 41: routerrpc.DeleteAliasesResponse.alias_maps:type_name -> lnrpc.AliasMap - 5, // 42: routerrpc.Router.SendPaymentV2:input_type -> routerrpc.SendPaymentRequest - 6, // 43: routerrpc.Router.TrackPaymentV2:input_type -> routerrpc.TrackPaymentRequest - 7, // 44: routerrpc.Router.TrackPayments:input_type -> routerrpc.TrackPaymentsRequest - 8, // 45: routerrpc.Router.EstimateRouteFee:input_type -> routerrpc.RouteFeeRequest - 10, // 46: routerrpc.Router.SendToRouteV2:input_type -> routerrpc.SendToRouteRequest - 11, // 47: routerrpc.Router.ResetMissionControl:input_type -> routerrpc.ResetMissionControlRequest - 13, // 48: routerrpc.Router.QueryMissionControl:input_type -> routerrpc.QueryMissionControlRequest - 15, // 49: routerrpc.Router.XImportMissionControl:input_type -> routerrpc.XImportMissionControlRequest - 19, // 50: routerrpc.Router.GetMissionControlConfig:input_type -> routerrpc.GetMissionControlConfigRequest - 21, // 51: routerrpc.Router.SetMissionControlConfig:input_type -> routerrpc.SetMissionControlConfigRequest - 26, // 52: routerrpc.Router.QueryProbability:input_type -> routerrpc.QueryProbabilityRequest - 28, // 53: routerrpc.Router.BuildRoute:input_type -> routerrpc.BuildRouteRequest - 30, // 54: routerrpc.Router.SubscribeHtlcEvents:input_type -> routerrpc.SubscribeHtlcEventsRequest - 41, // 55: routerrpc.Router.HtlcInterceptor:input_type -> routerrpc.ForwardHtlcInterceptResponse - 42, // 56: routerrpc.Router.UpdateChanStatus:input_type -> routerrpc.UpdateChanStatusRequest - 44, // 57: routerrpc.Router.XAddLocalChanAliases:input_type -> routerrpc.AddAliasesRequest - 46, // 58: routerrpc.Router.XDeleteLocalChanAliases:input_type -> routerrpc.DeleteAliasesRequest - 48, // 59: routerrpc.Router.XFindBaseLocalChanAlias:input_type -> routerrpc.FindBaseAliasRequest - 50, // 60: routerrpc.Router.DeleteForwardingHistory:input_type -> routerrpc.DeleteForwardingHistoryRequest - 66, // 61: routerrpc.Router.SendPaymentV2:output_type -> lnrpc.Payment - 66, // 62: routerrpc.Router.TrackPaymentV2:output_type -> lnrpc.Payment - 66, // 63: routerrpc.Router.TrackPayments:output_type -> lnrpc.Payment - 9, // 64: routerrpc.Router.EstimateRouteFee:output_type -> routerrpc.RouteFeeResponse - 67, // 65: routerrpc.Router.SendToRouteV2:output_type -> lnrpc.HTLCAttempt - 12, // 66: routerrpc.Router.ResetMissionControl:output_type -> routerrpc.ResetMissionControlResponse - 14, // 67: routerrpc.Router.QueryMissionControl:output_type -> routerrpc.QueryMissionControlResponse - 16, // 68: routerrpc.Router.XImportMissionControl:output_type -> routerrpc.XImportMissionControlResponse - 20, // 69: routerrpc.Router.GetMissionControlConfig:output_type -> routerrpc.GetMissionControlConfigResponse - 22, // 70: routerrpc.Router.SetMissionControlConfig:output_type -> routerrpc.SetMissionControlConfigResponse - 27, // 71: routerrpc.Router.QueryProbability:output_type -> routerrpc.QueryProbabilityResponse - 29, // 72: routerrpc.Router.BuildRoute:output_type -> routerrpc.BuildRouteResponse - 31, // 73: routerrpc.Router.SubscribeHtlcEvents:output_type -> routerrpc.HtlcEvent - 40, // 74: routerrpc.Router.HtlcInterceptor:output_type -> routerrpc.ForwardHtlcInterceptRequest - 43, // 75: routerrpc.Router.UpdateChanStatus:output_type -> routerrpc.UpdateChanStatusResponse - 45, // 76: routerrpc.Router.XAddLocalChanAliases:output_type -> routerrpc.AddAliasesResponse - 47, // 77: routerrpc.Router.XDeleteLocalChanAliases:output_type -> routerrpc.DeleteAliasesResponse - 49, // 78: routerrpc.Router.XFindBaseLocalChanAlias:output_type -> routerrpc.FindBaseAliasResponse - 51, // 79: routerrpc.Router.DeleteForwardingHistory:output_type -> routerrpc.DeleteForwardingHistoryResponse - 61, // [61:80] is the sub-list for method output_type - 42, // [42:61] is the sub-list for method input_type - 42, // [42:42] is the sub-list for extension type_name - 42, // [42:42] is the sub-list for extension extendee - 0, // [0:42] is the sub-list for field type_name + 60, // 0: routerrpc.SendPaymentRequest.route_hints:type_name -> lnrpc.RouteHint + 53, // 1: routerrpc.SendPaymentRequest.dest_custom_records:type_name -> routerrpc.SendPaymentRequest.DestCustomRecordsEntry + 61, // 2: routerrpc.SendPaymentRequest.dest_features:type_name -> lnrpc.FeatureBit + 54, // 3: routerrpc.SendPaymentRequest.first_hop_custom_records:type_name -> routerrpc.SendPaymentRequest.FirstHopCustomRecordsEntry + 62, // 4: routerrpc.RouteFeeResponse.failure_reason:type_name -> lnrpc.PaymentFailureReason + 63, // 5: routerrpc.SendToRouteRequest.route:type_name -> lnrpc.Route + 55, // 6: routerrpc.SendToRouteRequest.first_hop_custom_records:type_name -> routerrpc.SendToRouteRequest.FirstHopCustomRecordsEntry + 64, // 7: routerrpc.SendToRouteResponse.failure:type_name -> lnrpc.Failure + 19, // 8: routerrpc.QueryMissionControlResponse.pairs:type_name -> routerrpc.PairHistory + 19, // 9: routerrpc.XImportMissionControlRequest.pairs:type_name -> routerrpc.PairHistory + 20, // 10: routerrpc.PairHistory.history:type_name -> routerrpc.PairData + 25, // 11: routerrpc.GetMissionControlConfigResponse.config:type_name -> routerrpc.MissionControlConfig + 25, // 12: routerrpc.SetMissionControlConfigRequest.config:type_name -> routerrpc.MissionControlConfig + 4, // 13: routerrpc.MissionControlConfig.model:type_name -> routerrpc.MissionControlConfig.ProbabilityModel + 27, // 14: routerrpc.MissionControlConfig.apriori:type_name -> routerrpc.AprioriParameters + 26, // 15: routerrpc.MissionControlConfig.bimodal:type_name -> routerrpc.BimodalParameters + 20, // 16: routerrpc.QueryProbabilityResponse.history:type_name -> routerrpc.PairData + 56, // 17: routerrpc.BuildRouteRequest.first_hop_custom_records:type_name -> routerrpc.BuildRouteRequest.FirstHopCustomRecordsEntry + 63, // 18: routerrpc.BuildRouteResponse.route:type_name -> lnrpc.Route + 5, // 19: routerrpc.HtlcEvent.event_type:type_name -> routerrpc.HtlcEvent.EventType + 35, // 20: routerrpc.HtlcEvent.forward_event:type_name -> routerrpc.ForwardEvent + 36, // 21: routerrpc.HtlcEvent.forward_fail_event:type_name -> routerrpc.ForwardFailEvent + 37, // 22: routerrpc.HtlcEvent.settle_event:type_name -> routerrpc.SettleEvent + 40, // 23: routerrpc.HtlcEvent.link_fail_event:type_name -> routerrpc.LinkFailEvent + 39, // 24: routerrpc.HtlcEvent.subscribed_event:type_name -> routerrpc.SubscribedEvent + 38, // 25: routerrpc.HtlcEvent.final_htlc_event:type_name -> routerrpc.FinalHtlcEvent + 34, // 26: routerrpc.ForwardEvent.info:type_name -> routerrpc.HtlcInfo + 34, // 27: routerrpc.LinkFailEvent.info:type_name -> routerrpc.HtlcInfo + 65, // 28: routerrpc.LinkFailEvent.wire_failure:type_name -> lnrpc.Failure.FailureCode + 0, // 29: routerrpc.LinkFailEvent.failure_detail:type_name -> routerrpc.FailureDetail + 1, // 30: routerrpc.PaymentStatus.state:type_name -> routerrpc.PaymentState + 66, // 31: routerrpc.PaymentStatus.htlcs:type_name -> lnrpc.HTLCAttempt + 42, // 32: routerrpc.ForwardHtlcInterceptRequest.incoming_circuit_key:type_name -> routerrpc.CircuitKey + 57, // 33: routerrpc.ForwardHtlcInterceptRequest.custom_records:type_name -> routerrpc.ForwardHtlcInterceptRequest.CustomRecordsEntry + 58, // 34: routerrpc.ForwardHtlcInterceptRequest.in_wire_custom_records:type_name -> routerrpc.ForwardHtlcInterceptRequest.InWireCustomRecordsEntry + 42, // 35: routerrpc.ForwardHtlcInterceptResponse.incoming_circuit_key:type_name -> routerrpc.CircuitKey + 2, // 36: routerrpc.ForwardHtlcInterceptResponse.action:type_name -> routerrpc.ResolveHoldForwardAction + 65, // 37: routerrpc.ForwardHtlcInterceptResponse.failure_code:type_name -> lnrpc.Failure.FailureCode + 59, // 38: routerrpc.ForwardHtlcInterceptResponse.out_wire_custom_records:type_name -> routerrpc.ForwardHtlcInterceptResponse.OutWireCustomRecordsEntry + 67, // 39: routerrpc.UpdateChanStatusRequest.chan_point:type_name -> lnrpc.ChannelPoint + 3, // 40: routerrpc.UpdateChanStatusRequest.action:type_name -> routerrpc.ChanStatusAction + 68, // 41: routerrpc.AddAliasesRequest.alias_maps:type_name -> lnrpc.AliasMap + 68, // 42: routerrpc.AddAliasesResponse.alias_maps:type_name -> lnrpc.AliasMap + 68, // 43: routerrpc.DeleteAliasesRequest.alias_maps:type_name -> lnrpc.AliasMap + 68, // 44: routerrpc.DeleteAliasesResponse.alias_maps:type_name -> lnrpc.AliasMap + 6, // 45: routerrpc.Router.SendPaymentV2:input_type -> routerrpc.SendPaymentRequest + 7, // 46: routerrpc.Router.TrackPaymentV2:input_type -> routerrpc.TrackPaymentRequest + 8, // 47: routerrpc.Router.TrackPayments:input_type -> routerrpc.TrackPaymentsRequest + 9, // 48: routerrpc.Router.EstimateRouteFee:input_type -> routerrpc.RouteFeeRequest + 11, // 49: routerrpc.Router.SendToRoute:input_type -> routerrpc.SendToRouteRequest + 11, // 50: routerrpc.Router.SendToRouteV2:input_type -> routerrpc.SendToRouteRequest + 13, // 51: routerrpc.Router.ResetMissionControl:input_type -> routerrpc.ResetMissionControlRequest + 15, // 52: routerrpc.Router.QueryMissionControl:input_type -> routerrpc.QueryMissionControlRequest + 17, // 53: routerrpc.Router.XImportMissionControl:input_type -> routerrpc.XImportMissionControlRequest + 21, // 54: routerrpc.Router.GetMissionControlConfig:input_type -> routerrpc.GetMissionControlConfigRequest + 23, // 55: routerrpc.Router.SetMissionControlConfig:input_type -> routerrpc.SetMissionControlConfigRequest + 28, // 56: routerrpc.Router.QueryProbability:input_type -> routerrpc.QueryProbabilityRequest + 30, // 57: routerrpc.Router.BuildRoute:input_type -> routerrpc.BuildRouteRequest + 32, // 58: routerrpc.Router.SubscribeHtlcEvents:input_type -> routerrpc.SubscribeHtlcEventsRequest + 6, // 59: routerrpc.Router.SendPayment:input_type -> routerrpc.SendPaymentRequest + 7, // 60: routerrpc.Router.TrackPayment:input_type -> routerrpc.TrackPaymentRequest + 44, // 61: routerrpc.Router.HtlcInterceptor:input_type -> routerrpc.ForwardHtlcInterceptResponse + 45, // 62: routerrpc.Router.UpdateChanStatus:input_type -> routerrpc.UpdateChanStatusRequest + 47, // 63: routerrpc.Router.XAddLocalChanAliases:input_type -> routerrpc.AddAliasesRequest + 49, // 64: routerrpc.Router.XDeleteLocalChanAliases:input_type -> routerrpc.DeleteAliasesRequest + 51, // 65: routerrpc.Router.XFindBaseLocalChanAlias:input_type -> routerrpc.FindBaseAliasRequest + 69, // 66: routerrpc.Router.SendPaymentV2:output_type -> lnrpc.Payment + 69, // 67: routerrpc.Router.TrackPaymentV2:output_type -> lnrpc.Payment + 69, // 68: routerrpc.Router.TrackPayments:output_type -> lnrpc.Payment + 10, // 69: routerrpc.Router.EstimateRouteFee:output_type -> routerrpc.RouteFeeResponse + 12, // 70: routerrpc.Router.SendToRoute:output_type -> routerrpc.SendToRouteResponse + 66, // 71: routerrpc.Router.SendToRouteV2:output_type -> lnrpc.HTLCAttempt + 14, // 72: routerrpc.Router.ResetMissionControl:output_type -> routerrpc.ResetMissionControlResponse + 16, // 73: routerrpc.Router.QueryMissionControl:output_type -> routerrpc.QueryMissionControlResponse + 18, // 74: routerrpc.Router.XImportMissionControl:output_type -> routerrpc.XImportMissionControlResponse + 22, // 75: routerrpc.Router.GetMissionControlConfig:output_type -> routerrpc.GetMissionControlConfigResponse + 24, // 76: routerrpc.Router.SetMissionControlConfig:output_type -> routerrpc.SetMissionControlConfigResponse + 29, // 77: routerrpc.Router.QueryProbability:output_type -> routerrpc.QueryProbabilityResponse + 31, // 78: routerrpc.Router.BuildRoute:output_type -> routerrpc.BuildRouteResponse + 33, // 79: routerrpc.Router.SubscribeHtlcEvents:output_type -> routerrpc.HtlcEvent + 41, // 80: routerrpc.Router.SendPayment:output_type -> routerrpc.PaymentStatus + 41, // 81: routerrpc.Router.TrackPayment:output_type -> routerrpc.PaymentStatus + 43, // 82: routerrpc.Router.HtlcInterceptor:output_type -> routerrpc.ForwardHtlcInterceptRequest + 46, // 83: routerrpc.Router.UpdateChanStatus:output_type -> routerrpc.UpdateChanStatusResponse + 48, // 84: routerrpc.Router.XAddLocalChanAliases:output_type -> routerrpc.AddAliasesResponse + 50, // 85: routerrpc.Router.XDeleteLocalChanAliases:output_type -> routerrpc.DeleteAliasesResponse + 52, // 86: routerrpc.Router.XFindBaseLocalChanAlias:output_type -> routerrpc.FindBaseAliasResponse + 66, // [66:87] is the sub-list for method output_type + 45, // [45:66] is the sub-list for method input_type + 45, // [45:45] is the sub-list for extension type_name + 45, // [45:45] is the sub-list for extension extendee + 0, // [0:45] is the sub-list for field type_name } func init() { file_routerrpc_router_proto_init() } @@ -4117,11 +4613,577 @@ func file_routerrpc_router_proto_init() { if File_routerrpc_router_proto != nil { return } - file_routerrpc_router_proto_msgTypes[18].OneofWrappers = []any{ + if !protoimpl.UnsafeEnabled { + file_routerrpc_router_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendPaymentRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TrackPaymentRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TrackPaymentsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RouteFeeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RouteFeeResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendToRouteRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendToRouteResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResetMissionControlRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResetMissionControlResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*QueryMissionControlRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*QueryMissionControlResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*XImportMissionControlRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*XImportMissionControlResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PairHistory); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PairData); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetMissionControlConfigRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetMissionControlConfigResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetMissionControlConfigRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetMissionControlConfigResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MissionControlConfig); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BimodalParameters); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AprioriParameters); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*QueryProbabilityRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*QueryProbabilityResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BuildRouteRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BuildRouteResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SubscribeHtlcEventsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HtlcEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HtlcInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ForwardEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ForwardFailEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SettleEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FinalHtlcEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SubscribedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LinkFailEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PaymentStatus); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CircuitKey); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ForwardHtlcInterceptRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ForwardHtlcInterceptResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateChanStatusRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateChanStatusResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddAliasesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddAliasesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteAliasesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteAliasesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FindBaseAliasRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[46].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FindBaseAliasResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_routerrpc_router_proto_msgTypes[19].OneofWrappers = []interface{}{ (*MissionControlConfig_Apriori)(nil), (*MissionControlConfig_Bimodal)(nil), } - file_routerrpc_router_proto_msgTypes[26].OneofWrappers = []any{ + file_routerrpc_router_proto_msgTypes[27].OneofWrappers = []interface{}{ (*HtlcEvent_ForwardEvent)(nil), (*HtlcEvent_ForwardFailEvent)(nil), (*HtlcEvent_SettleEvent)(nil), @@ -4129,16 +5191,12 @@ func file_routerrpc_router_proto_init() { (*HtlcEvent_SubscribedEvent)(nil), (*HtlcEvent_FinalHtlcEvent)(nil), } - file_routerrpc_router_proto_msgTypes[45].OneofWrappers = []any{ - (*DeleteForwardingHistoryRequest_DeleteBeforeTime)(nil), - (*DeleteForwardingHistoryRequest_DeleteBeforeDuration)(nil), - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_routerrpc_router_proto_rawDesc), len(file_routerrpc_router_proto_rawDesc)), - NumEnums: 5, + RawDescriptor: file_routerrpc_router_proto_rawDesc, + NumEnums: 6, NumMessages: 54, NumExtensions: 0, NumServices: 1, @@ -4149,6 +5207,7 @@ func file_routerrpc_router_proto_init() { MessageInfos: file_routerrpc_router_proto_msgTypes, }.Build() File_routerrpc_router_proto = out.File + file_routerrpc_router_proto_rawDesc = nil file_routerrpc_router_proto_goTypes = nil file_routerrpc_router_proto_depIdxs = nil } diff --git a/lnrpc/routerrpc/router.pb.gw.go b/lnrpc/routerrpc/router.pb.gw.go index 3a1f720b4..d08a7c841 100644 --- a/lnrpc/routerrpc/router.pb.gw.go +++ b/lnrpc/routerrpc/router.pb.gw.go @@ -657,40 +657,6 @@ func local_request_Router_XFindBaseLocalChanAlias_0(ctx context.Context, marshal } -func request_Router_DeleteForwardingHistory_0(ctx context.Context, marshaler runtime.Marshaler, client RouterClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq DeleteForwardingHistoryRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := client.DeleteForwardingHistory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) - return msg, metadata, err - -} - -func local_request_Router_DeleteForwardingHistory_0(ctx context.Context, marshaler runtime.Marshaler, server RouterServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq DeleteForwardingHistoryRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.DeleteForwardingHistory(ctx, &protoReq) - return msg, metadata, err - -} - // RegisterRouterHandlerServer registers the http handlers for service Router to "mux". // UnaryRPC :call RouterServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -1057,31 +1023,6 @@ func RegisterRouterHandlerServer(ctx context.Context, mux *runtime.ServeMux, ser }) - mux.Handle("POST", pattern_Router_DeleteForwardingHistory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - var stream runtime.ServerTransportStream - ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - var err error - var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/routerrpc.Router/DeleteForwardingHistory", runtime.WithHTTPPathPattern("/v2/router/fwdhistory/delete")) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_Router_DeleteForwardingHistory_0(annotatedContext, inboundMarshaler, server, req, pathParams) - md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) - if err != nil { - runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) - return - } - - forward_Router_DeleteForwardingHistory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - return nil } @@ -1519,28 +1460,6 @@ func RegisterRouterHandlerClient(ctx context.Context, mux *runtime.ServeMux, cli }) - mux.Handle("POST", pattern_Router_DeleteForwardingHistory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - var err error - var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/routerrpc.Router/DeleteForwardingHistory", runtime.WithHTTPPathPattern("/v2/router/fwdhistory/delete")) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := request_Router_DeleteForwardingHistory_0(annotatedContext, inboundMarshaler, client, req, pathParams) - annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) - if err != nil { - runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) - return - } - - forward_Router_DeleteForwardingHistory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - return nil } @@ -1580,8 +1499,6 @@ var ( pattern_Router_XDeleteLocalChanAliases_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "router", "x", "deletealiases"}, "")) pattern_Router_XFindBaseLocalChanAlias_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "router", "x", "findbasealias"}, "")) - - pattern_Router_DeleteForwardingHistory_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "router", "fwdhistory", "delete"}, "")) ) var ( @@ -1620,6 +1537,4 @@ var ( forward_Router_XDeleteLocalChanAliases_0 = runtime.ForwardResponseMessage forward_Router_XFindBaseLocalChanAlias_0 = runtime.ForwardResponseMessage - - forward_Router_DeleteForwardingHistory_0 = runtime.ForwardResponseMessage ) diff --git a/lnrpc/routerrpc/router.pb.json.go b/lnrpc/routerrpc/router.pb.json.go index bea817252..18fbc07fa 100644 --- a/lnrpc/routerrpc/router.pb.json.go +++ b/lnrpc/routerrpc/router.pb.json.go @@ -172,6 +172,31 @@ func RegisterRouterJSONCallbacks(registry map[string]func(ctx context.Context, callback(string(respBytes), nil) } + registry["routerrpc.Router.SendToRoute"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &SendToRouteRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewRouterClient(conn) + resp, err := client.SendToRoute(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + registry["routerrpc.Router.SendToRouteV2"] = func(ctx context.Context, conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { @@ -414,6 +439,90 @@ func RegisterRouterJSONCallbacks(registry map[string]func(ctx context.Context, }() } + registry["routerrpc.Router.SendPayment"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &SendPaymentRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewRouterClient(conn) + stream, err := client.SendPayment(ctx, req) + if err != nil { + callback("", err) + return + } + + go func() { + for { + select { + case <-stream.Context().Done(): + callback("", stream.Context().Err()) + return + default: + } + + resp, err := stream.Recv() + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + }() + } + + registry["routerrpc.Router.TrackPayment"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &TrackPaymentRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewRouterClient(conn) + stream, err := client.TrackPayment(ctx, req) + if err != nil { + callback("", err) + return + } + + go func() { + for { + select { + case <-stream.Context().Done(): + callback("", stream.Context().Err()) + return + default: + } + + resp, err := stream.Recv() + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + }() + } + registry["routerrpc.Router.UpdateChanStatus"] = func(ctx context.Context, conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { @@ -513,29 +622,4 @@ func RegisterRouterJSONCallbacks(registry map[string]func(ctx context.Context, } callback(string(respBytes), nil) } - - registry["routerrpc.Router.DeleteForwardingHistory"] = func(ctx context.Context, - conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { - - req := &DeleteForwardingHistoryRequest{} - err := marshaler.Unmarshal([]byte(reqJSON), req) - if err != nil { - callback("", err) - return - } - - client := NewRouterClient(conn) - resp, err := client.DeleteForwardingHistory(ctx, req) - if err != nil { - callback("", err) - return - } - - respBytes, err := marshaler.Marshal(resp) - if err != nil { - callback("", err) - return - } - callback(string(respBytes), nil) - } } diff --git a/lnrpc/routerrpc/router.proto b/lnrpc/routerrpc/router.proto index 8060b170b..8f5502675 100644 --- a/lnrpc/routerrpc/router.proto +++ b/lnrpc/routerrpc/router.proto @@ -27,7 +27,7 @@ option go_package = "github.com/lightningnetwork/lnd/lnrpc/routerrpc"; // Router is a service that offers advanced interaction with the router // subsystem of the daemon. service Router { - /* lncli: `sendpayment` + /* SendPaymentV2 attempts to route a payment described by the passed PaymentRequest to the final destination. The call returns a stream of payment updates. When using this RPC, make sure to set a fee limit, as the @@ -53,13 +53,24 @@ service Router { */ rpc TrackPayments (TrackPaymentsRequest) returns (stream lnrpc.Payment); - /* lncli: `estimateroutefee` + /* EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it may cost to send an HTLC to the target end destination. */ rpc EstimateRouteFee (RouteFeeRequest) returns (RouteFeeResponse); - /* lncli: `sendtoroute` + /* + Deprecated, use SendToRouteV2. SendToRoute attempts to make a payment via + the specified route. This method differs from SendPayment in that it + allows users to specify a full route manually. This can be used for + things like rebalancing, and atomic swaps. It differs from the newer + SendToRouteV2 in that it doesn't return the full HTLC information. + */ + rpc SendToRoute (SendToRouteRequest) returns (SendToRouteResponse) { + option deprecated = true; + } + + /* SendToRouteV2 attempts to make a payment via the specified route. This method differs from SendPayment in that it allows users to specify a full route manually. This can be used for things like rebalancing, and atomic @@ -130,6 +141,23 @@ service Router { rpc SubscribeHtlcEvents (SubscribeHtlcEventsRequest) returns (stream HtlcEvent); + /* + Deprecated, use SendPaymentV2. SendPayment attempts to route a payment + described by the passed PaymentRequest to the final destination. The call + returns a stream of payment status updates. + */ + rpc SendPayment (SendPaymentRequest) returns (stream PaymentStatus) { + option deprecated = true; + } + + /* + Deprecated, use TrackPaymentV2. TrackPayment returns an update stream for + the payment identified by the payment hash. + */ + rpc TrackPayment (TrackPaymentRequest) returns (stream PaymentStatus) { + option deprecated = true; + } + /** HtlcInterceptor dispatches a bi-directional streaming RPC in which Forwarded HTLC requests are sent to the client and the client responds with @@ -174,18 +202,6 @@ service Router { */ rpc XFindBaseLocalChanAlias (FindBaseAliasRequest) returns (FindBaseAliasResponse); - - /* lncli: `deletefwdhistory` - DeleteForwardingHistory allows the caller to delete forwarding history - events with a timestamp at or before a specified time. This is useful - for implementing data retention policies for privacy purposes. The call - deletes events in batches and returns statistics including the total number - of events deleted and the aggregate fees earned from those events. The - deletion is performed in a transaction-safe manner with configurable batch - sizes to avoid holding large database locks. - */ - rpc DeleteForwardingHistory (DeleteForwardingHistoryRequest) - returns (DeleteForwardingHistoryResponse); } message SendPaymentRequest { @@ -236,7 +252,12 @@ message SendPaymentRequest { */ int64 fee_limit_sat = 7; - reserved 8; + /* + Deprecated, use outgoing_chan_ids. The channel id of the channel that must + be taken to the first hop. If zero, any channel may be used (unless + outgoing_chan_ids are set). + */ + uint64 outgoing_chan_id = 8 [jstype = JS_STRING, deprecated = true]; /* An optional maximum total time lock for the route. This should not @@ -407,18 +428,9 @@ message RouteFeeRequest { timeout is reached. Note that the probing process itself can take longer than the timeout if the HTLC becomes delayed or stuck. Canceling the context of this call will not cancel the payment loop, the duration is only - controlled by the timeout parameter. If the field is not set or is - explicitly set to zero, the default value of 60 seconds will be applied. + controlled by the timeout parameter. */ uint32 timeout = 4; - - /* - The channel ids of the channels that are allowed for the first hop. If - empty, any channel may be used. This field is applicable to both - graph-based fee estimation (using dest + amt_sat) and probe-based - estimation (using payment_request). - */ - repeated uint64 outgoing_chan_ids = 5; } message RouteFeeResponse { @@ -467,6 +479,14 @@ message SendToRouteRequest { map first_hop_custom_records = 4; } +message SendToRouteResponse { + // The preimage obtained by making the payment. + bytes preimage = 1; + + // The failure message in case the payment failed. + lnrpc.Failure failure = 2; +} + message ResetMissionControlRequest { } @@ -891,11 +911,62 @@ enum FailureDetail { INVALID_KEYSEND = 20; MPP_IN_PROGRESS = 21; CIRCULAR_ROUTE = 22; - INVOICE_ALREADY_SETTLED = 23; - HTLC_INVOICE_TYPE_MISMATCH = 24; - AMP_ERROR = 25; - AMP_RECONSTRUCTION = 26; - EXTERNAL_VALIDATION_FAILED = 27; +} + +enum PaymentState { + /* + Payment is still in flight. + */ + IN_FLIGHT = 0; + + /* + Payment completed successfully. + */ + SUCCEEDED = 1; + + /* + There are more routes to try, but the payment timeout was exceeded. + */ + FAILED_TIMEOUT = 2; + + /* + All possible routes were tried and failed permanently. Or were no + routes to the destination at all. + */ + FAILED_NO_ROUTE = 3; + + /* + A non-recoverable error has occurred. + */ + FAILED_ERROR = 4; + + /* + Payment details incorrect (unknown hash, invalid amt or + invalid final cltv delta) + */ + FAILED_INCORRECT_PAYMENT_DETAILS = 5; + + /* + Insufficient local balance. + */ + FAILED_INSUFFICIENT_BALANCE = 6; +} + +message PaymentStatus { + // Current state the payment is in. + PaymentState state = 1; + + /* + The pre-image of the payment when state is SUCCEEDED. + */ + bytes preimage = 2; + + reserved 3; + + /* + The HTLCs made in attempt to settle the payment [EXPERIMENTAL]. + */ + repeated lnrpc.HTLCAttempt htlcs = 4; } message CircuitKey { @@ -932,8 +1003,7 @@ message ForwardHtlcInterceptRequest { // The requested outgoing channel id for this forwarded htlc. Because of // non-strict forwarding, this isn't necessarily the channel over which the // packet will be forwarded eventually. A different channel to the same peer - // may be selected as well. This is set to a sentinel value (all bits set) - // if the outgoing_requested_node_id is specified for blinded routes. + // may be selected as well. uint64 outgoing_requested_chan_id = 7; // The outgoing htlc amount. @@ -955,19 +1025,6 @@ message ForwardHtlcInterceptRequest { // The custom records of the peer's incoming p2p wire message. map in_wire_custom_records = 11; - - // The requested outgoing node for a blinded forward. When non-empty, this - // field contains exactly one 33-byte compressed public key and - // outgoing_requested_chan_id is set to 18446744073709551615 - // (0xffffffffffffffff). Clients MUST NOT interpret that value as an actual - // channel ID; the presence of this field identifies a node-addressed - // forward. - // - // The possible next-hop representations are: - // node ID empty, channel ID 0: final receive; - // node ID empty, ordinary channel ID: channel-addressed forward; - // node ID present, channel ID MaxUint64: node-addressed forward. - bytes outgoing_requested_node_id = 12; } /** @@ -1089,36 +1146,4 @@ message FindBaseAliasRequest { message FindBaseAliasResponse { // The base scid that resulted from the base scid look up. uint64 base = 1; -} - -message DeleteForwardingHistoryRequest { - // Specify the cutoff time for deletion using one of the following options. - // Events with a timestamp at or before the cutoff are deleted. - oneof time_spec { - // Absolute Unix timestamp (seconds). Events at or before this time - // are deleted. - uint64 delete_before_time = 1; - - // Relative duration string indicating how far back to delete, e.g. - // "-30d" deletes events at or before 30 days ago. - // Standard Go: "-24h", "-1.5h" - // Custom units: "-1d", "-1w", "-1M", "-1y" - // Supported: ns, us/µs, ms, s, m, h, d (days), w (weeks), - // M (months=30.44d), y (years=365.25d). - // Use negative values to specify time in the past. - string delete_before_duration = 2; - } -} - -message DeleteForwardingHistoryResponse { - // Number of forwarding events deleted. - uint64 events_deleted = 1; - - // Total fees earned from deleted events (in millisatoshis). - // This is the sum of (amt_in - amt_out) for all deleted events, which - // can be used for accounting purposes. - int64 total_fee_msat = 2; - - // Status message. - string status = 3; -} +} \ No newline at end of file diff --git a/lnrpc/routerrpc/router.swagger.json b/lnrpc/routerrpc/router.swagger.json index 6f8970389..4fdf61663 100644 --- a/lnrpc/routerrpc/router.swagger.json +++ b/lnrpc/routerrpc/router.swagger.json @@ -16,39 +16,6 @@ "application/json" ], "paths": { - "/v2/router/fwdhistory/delete": { - "post": { - "summary": "lncli: `deletefwdhistory`\nDeleteForwardingHistory allows the caller to delete forwarding history\nevents with a timestamp at or before a specified time. This is useful\nfor implementing data retention policies for privacy purposes. The call\ndeletes events in batches and returns statistics including the total number\nof events deleted and the aggregate fees earned from those events. The\ndeletion is performed in a transaction-safe manner with configurable batch\nsizes to avoid holding large database locks.", - "operationId": "Router_DeleteForwardingHistory", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/routerrpcDeleteForwardingHistoryResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/rpcStatus" - } - } - }, - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/routerrpcDeleteForwardingHistoryRequest" - } - } - ], - "tags": [ - "Router" - ] - } - }, "/v2/router/htlcevents": { "get": { "summary": "SubscribeHtlcEvents creates a uni-directional stream from the server to\nthe client which delivers a stream of htlc events.", @@ -359,7 +326,7 @@ }, "/v2/router/route/estimatefee": { "post": { - "summary": "lncli: `estimateroutefee`\nEstimateRouteFee allows callers to obtain a lower bound w.r.t how much it\nmay cost to send an HTLC to the target end destination.", + "summary": "EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it\nmay cost to send an HTLC to the target end destination.", "operationId": "Router_EstimateRouteFee", "responses": { "200": { @@ -392,7 +359,7 @@ }, "/v2/router/route/send": { "post": { - "summary": "lncli: `sendtoroute`\nSendToRouteV2 attempts to make a payment via the specified route. This\nmethod differs from SendPayment in that it allows users to specify a full\nroute manually. This can be used for things like rebalancing, and atomic\nswaps.", + "summary": "SendToRouteV2 attempts to make a payment via the specified route. This\nmethod differs from SendPayment in that it allows users to specify a full\nroute manually. This can be used for things like rebalancing, and atomic\nswaps.", "operationId": "Router_SendToRouteV2", "responses": { "200": { @@ -425,7 +392,7 @@ }, "/v2/router/send": { "post": { - "summary": "lncli: `sendpayment`\nSendPaymentV2 attempts to route a payment described by the passed\nPaymentRequest to the final destination. The call returns a stream of\npayment updates. When using this RPC, make sure to set a fee limit, as the\ndefault routing fee limit is 0 sats. Without a non-zero fee limit only\nroutes without fees will be attempted which often fails with\nFAILURE_REASON_NO_ROUTE.", + "summary": "SendPaymentV2 attempts to route a payment described by the passed\nPaymentRequest to the final destination. The call returns a stream of\npayment updates. When using this RPC, make sure to set a fee limit, as the\ndefault routing fee limit is 0 sats. Without a non-zero fee limit only\nroutes without fees will be attempted which often fails with\nFAILURE_REASON_NO_ROUTE.", "operationId": "Router_SendPaymentV2", "responses": { "200": { @@ -734,18 +701,6 @@ ], "default": "APRIORI" }, - "PaymentPaymentStatus": { - "type": "string", - "enum": [ - "UNKNOWN", - "IN_FLIGHT", - "SUCCEEDED", - "FAILED", - "INITIATED" - ], - "default": "UNKNOWN", - "description": " - UNKNOWN: Deprecated. This status will never be returned.\n - IN_FLIGHT: Payment has inflight HTLCs.\n - SUCCEEDED: Payment is settled.\n - FAILED: Payment is failed.\n - INITIATED: Payment is created and has not attempted any HTLCs." - }, "lnrpcAMPRecord": { "type": "object", "properties": { @@ -1141,7 +1096,7 @@ "description": "The optional payment request being fulfilled." }, "status": { - "$ref": "#/definitions/PaymentPaymentStatus", + "$ref": "#/definitions/lnrpcPaymentPaymentStatus", "description": "The status of the payment." }, "fee_sat": { @@ -1199,6 +1154,18 @@ "default": "FAILURE_REASON_NONE", "description": " - FAILURE_REASON_NONE: Payment isn't failed (yet).\n - FAILURE_REASON_TIMEOUT: There are more routes to try, but the payment timeout was exceeded.\n - FAILURE_REASON_NO_ROUTE: All possible routes were tried and failed permanently. Or were no\nroutes to the destination at all.\n - FAILURE_REASON_ERROR: A non-recoverable error has occured.\n - FAILURE_REASON_INCORRECT_PAYMENT_DETAILS: Payment details incorrect (unknown hash, invalid amt or\ninvalid final cltv delta)\n - FAILURE_REASON_INSUFFICIENT_BALANCE: Insufficient local balance.\n - FAILURE_REASON_CANCELED: The payment was canceled." }, + "lnrpcPaymentPaymentStatus": { + "type": "string", + "enum": [ + "UNKNOWN", + "IN_FLIGHT", + "SUCCEEDED", + "FAILED", + "INITIATED" + ], + "default": "UNKNOWN", + "description": " - UNKNOWN: Deprecated. This status will never be returned.\n - IN_FLIGHT: Payment has inflight HTLCs.\n - SUCCEEDED: Payment is settled.\n - FAILED: Payment is failed.\n - INITIATED: Payment is created and has not attempted any HTLCs." + }, "lnrpcRoute": { "type": "object", "properties": { @@ -1437,39 +1404,6 @@ } } }, - "routerrpcDeleteForwardingHistoryRequest": { - "type": "object", - "properties": { - "delete_before_time": { - "type": "string", - "format": "uint64", - "description": "Absolute Unix timestamp (seconds). Events at or before this time\nare deleted." - }, - "delete_before_duration": { - "type": "string", - "description": "Relative duration string indicating how far back to delete, e.g.\n\"-30d\" deletes events at or before 30 days ago.\nStandard Go: \"-24h\", \"-1.5h\"\nCustom units: \"-1d\", \"-1w\", \"-1M\", \"-1y\"\nSupported: ns, us/µs, ms, s, m, h, d (days), w (weeks),\nM (months=30.44d), y (years=365.25d).\nUse negative values to specify time in the past." - } - } - }, - "routerrpcDeleteForwardingHistoryResponse": { - "type": "object", - "properties": { - "events_deleted": { - "type": "string", - "format": "uint64", - "description": "Number of forwarding events deleted." - }, - "total_fee_msat": { - "type": "string", - "format": "int64", - "description": "Total fees earned from deleted events (in millisatoshis).\nThis is the sum of (amt_in - amt_out) for all deleted events, which\ncan be used for accounting purposes." - }, - "status": { - "type": "string", - "description": "Status message." - } - } - }, "routerrpcFailureDetail": { "type": "string", "enum": [ @@ -1495,12 +1429,7 @@ "UNKNOWN_INVOICE", "INVALID_KEYSEND", "MPP_IN_PROGRESS", - "CIRCULAR_ROUTE", - "INVOICE_ALREADY_SETTLED", - "HTLC_INVOICE_TYPE_MISMATCH", - "AMP_ERROR", - "AMP_RECONSTRUCTION", - "EXTERNAL_VALIDATION_FAILED" + "CIRCULAR_ROUTE" ], "default": "UNKNOWN" }, @@ -1572,7 +1501,7 @@ "outgoing_requested_chan_id": { "type": "string", "format": "uint64", - "description": "The requested outgoing channel id for this forwarded htlc. Because of\nnon-strict forwarding, this isn't necessarily the channel over which the\npacket will be forwarded eventually. A different channel to the same peer\nmay be selected as well. This is set to a sentinel value (all bits set)\nif the outgoing_requested_node_id is specified for blinded routes." + "description": "The requested outgoing channel id for this forwarded htlc. Because of\nnon-strict forwarding, this isn't necessarily the channel over which the\npacket will be forwarded eventually. A different channel to the same peer\nmay be selected as well." }, "outgoing_amount_msat": { "type": "string", @@ -1609,11 +1538,6 @@ "format": "byte" }, "description": "The custom records of the peer's incoming p2p wire message." - }, - "outgoing_requested_node_id": { - "type": "string", - "format": "byte", - "description": "The requested outgoing node for a blinded forward. When non-empty, this\nfield contains exactly one 33-byte compressed public key and\noutgoing_requested_chan_id is set to 18446744073709551615\n(0xffffffffffffffff). Clients MUST NOT interpret that value as an actual\nchannel ID; the presence of this field identifies a node-addressed\nforward.\n\nThe possible next-hop representations are:\n node ID empty, channel ID 0: final receive;\n node ID empty, ordinary channel ID: channel-addressed forward;\n node ID present, channel ID MaxUint64: node-addressed forward." } } }, @@ -1875,6 +1799,42 @@ }, "description": "PairHistory contains the mission control state for a particular node pair." }, + "routerrpcPaymentState": { + "type": "string", + "enum": [ + "IN_FLIGHT", + "SUCCEEDED", + "FAILED_TIMEOUT", + "FAILED_NO_ROUTE", + "FAILED_ERROR", + "FAILED_INCORRECT_PAYMENT_DETAILS", + "FAILED_INSUFFICIENT_BALANCE" + ], + "default": "IN_FLIGHT", + "description": " - IN_FLIGHT: Payment is still in flight.\n - SUCCEEDED: Payment completed successfully.\n - FAILED_TIMEOUT: There are more routes to try, but the payment timeout was exceeded.\n - FAILED_NO_ROUTE: All possible routes were tried and failed permanently. Or were no\nroutes to the destination at all.\n - FAILED_ERROR: A non-recoverable error has occurred.\n - FAILED_INCORRECT_PAYMENT_DETAILS: Payment details incorrect (unknown hash, invalid amt or\ninvalid final cltv delta)\n - FAILED_INSUFFICIENT_BALANCE: Insufficient local balance." + }, + "routerrpcPaymentStatus": { + "type": "object", + "properties": { + "state": { + "$ref": "#/definitions/routerrpcPaymentState", + "description": "Current state the payment is in." + }, + "preimage": { + "type": "string", + "format": "byte", + "description": "The pre-image of the payment when state is SUCCEEDED." + }, + "htlcs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/lnrpcHTLCAttempt" + }, + "description": "The HTLCs made in attempt to settle the payment [EXPERIMENTAL]." + } + } + }, "routerrpcQueryMissionControlResponse": { "type": "object", "properties": { @@ -1940,15 +1900,7 @@ "timeout": { "type": "integer", "format": "int64", - "description": "A user preference of how long a probe payment should maximally be allowed to\ntake, denoted in seconds. The probing payment loop is aborted if this\ntimeout is reached. Note that the probing process itself can take longer\nthan the timeout if the HTLC becomes delayed or stuck. Canceling the context\nof this call will not cancel the payment loop, the duration is only\ncontrolled by the timeout parameter. If the field is not set or is\nexplicitly set to zero, the default value of 60 seconds will be applied." - }, - "outgoing_chan_ids": { - "type": "array", - "items": { - "type": "string", - "format": "uint64" - }, - "description": "The channel ids of the channels that are allowed for the first hop. If\nempty, any channel may be used. This field is applicable to both\ngraph-based fee estimation (using dest + amt_sat) and probe-based\nestimation (using payment_request)." + "description": "A user preference of how long a probe payment should maximally be allowed to\ntake, denoted in seconds. The probing payment loop is aborted if this\ntimeout is reached. Note that the probing process itself can take longer\nthan the timeout if the HTLC becomes delayed or stuck. Canceling the context\nof this call will not cancel the payment loop, the duration is only\ncontrolled by the timeout parameter." } } }, @@ -2008,6 +1960,11 @@ "format": "int64", "description": "The maximum number of satoshis that will be paid as a fee of the payment.\nIf this field is left to the default value of 0, only zero-fee routes will\nbe considered. This usually means single hop routes connecting directly to\nthe destination. To send the payment without a fee limit, use max int here.\n\nThe fields fee_limit_sat and fee_limit_msat are mutually exclusive." }, + "outgoing_chan_id": { + "type": "string", + "format": "uint64", + "description": "Deprecated, use outgoing_chan_ids. The channel id of the channel that must\nbe taken to the first hop. If zero, any channel may be used (unless\noutgoing_chan_ids are set)." + }, "cltv_limit": { "type": "integer", "format": "int32", @@ -2131,6 +2088,20 @@ } } }, + "routerrpcSendToRouteResponse": { + "type": "object", + "properties": { + "preimage": { + "type": "string", + "format": "byte", + "description": "The preimage obtained by making the payment." + }, + "failure": { + "$ref": "#/definitions/lnrpcFailure", + "description": "The failure message in case the payment failed." + } + } + }, "routerrpcSetMissionControlConfigRequest": { "type": "object", "properties": { diff --git a/lnrpc/routerrpc/router.yaml b/lnrpc/routerrpc/router.yaml index cba907e10..c9ec2e041 100644 --- a/lnrpc/routerrpc/router.yaml +++ b/lnrpc/routerrpc/router.yaml @@ -13,6 +13,8 @@ http: - selector: routerrpc.Router.EstimateRouteFee post: "/v2/router/route/estimatefee" body: "*" + - selector: routerrpc.Router.SendToRoute + # deprecated, no REST endpoint - selector: routerrpc.Router.SendToRouteV2 post: "/v2/router/route/send" body: "*" @@ -36,6 +38,10 @@ http: body: "*" - selector: routerrpc.Router.SubscribeHtlcEvents get: "/v2/router/htlcevents" + - selector: routerrpc.Router.SendPayment + # deprecated, no REST endpoint + - selector: routerrpc.Router.TrackPayment + # deprecated, no REST endpoint - selector: routerrpc.Router.HtlcInterceptor post: "/v2/router/htlcinterceptor" body: "*" @@ -51,7 +57,4 @@ http: - selector: routerrpc.Router.XFindBaseLocalChanAlias post: "/v2/router/x/findbasealias" body: "*" - - selector: routerrpc.Router.DeleteForwardingHistory - post: "/v2/router/fwdhistory/delete" - body: "*" diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go index ce350ff5e..b8bc46670 100644 --- a/lnrpc/routerrpc/router_backend.go +++ b/lnrpc/routerrpc/router_backend.go @@ -1,7 +1,6 @@ package routerrpc import ( - "bytes" "context" "crypto/rand" "encoding/hex" @@ -11,11 +10,10 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" sphinx "github.com/lightningnetwork/lightning-onion" - "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/feature" "github.com/lightningnetwork/lnd/fn/v2" @@ -123,43 +121,13 @@ type RouterBackend struct { // channel data from the first hop of a route. ParseCustomChannelData func(message proto.Message) error - // ShouldSetExpAccountability returns a boolean indicating whether the - // experimental accountability bit should be set. - ShouldSetExpAccountability func() bool + // ShouldSetExpEndorsement returns a boolean indicating whether the + // experimental endorsement bit should be set. + ShouldSetExpEndorsement func() bool // Clock is the clock used to validate payment requests expiry. // It is useful for testing. Clock clock.Clock - - // ForwardingLog provides access to forwarding log database operations. - ForwardingLog ForwardingLogDB - - // MinForwardingHistoryAge is the minimum age a forwarding event must - // have before it can be deleted. If zero the handler defaults to 1 - // hour. - MinForwardingHistoryAge time.Duration - - // FwdHistoryDeleteBatchSize is the number of forwarding events deleted - // per database transaction. If zero the DB layer applies its own - // default (10 000). Exposed here so operators can tune the value via - // lnd.conf on resource-constrained nodes. - FwdHistoryDeleteBatchSize int -} - -// ForwardingLogDB defines the interface for forwarding log database operations. -// This interface allows the router RPC to interact with the forwarding log -// without depending directly on the channeldb implementation, making testing -// and future refactoring easier. -type ForwardingLogDB interface { - // DeleteForwardingEvents deletes all forwarding events with a - // timestamp at or before the specified endTime. The deletion is - // performed in batches of the given size to avoid holding large - // database locks. It returns statistics about the deletion including - // the number of events deleted and the total fees earned from those - // events. If the context is cancelled between batches, partial - // statistics are returned along with the context error. - DeleteForwardingEvents(ctx context.Context, endTime time.Time, - batchSize int) (channeldb.DeleteStats, error) } // MissionControl defines the mission control dependencies of routerrpc. @@ -447,8 +415,19 @@ func (r *RouterBackend) parseQueryRoutesRequest(in *lnrpc.QueryRoutesRequest) ( BlindedPaymentPathSet: blindedPathSet, } - if len(in.OutgoingChanIds) > 0 { + // We set the outgoing channel restrictions if the user provides a + // list of channel ids. We also handle the case where the user + // provides the deprecated `OutgoingChanId` field. + switch { + case len(in.OutgoingChanIds) > 0 && in.OutgoingChanId != 0: + return nil, errors.New("outgoing_chan_id and " + + "outgoing_chan_ids cannot both be set") + + case len(in.OutgoingChanIds) > 0: restrictions.OutgoingChannelIDs = in.OutgoingChanIds + + case in.OutgoingChanId != 0: + restrictions.OutgoingChannelIDs = []uint64{in.OutgoingChanId} } // Pass along a last hop restriction if specified. @@ -648,26 +627,10 @@ func (r *RouterBackend) MarshallRoute(route *route.Route) (*lnrpc.Route, error) // Allow the aux data parser to parse the custom records into // a human-readable JSON (if available). if r.ParseCustomChannelData != nil { - // Store the original custom data to check if parsing - // changed it. - originalCustomData := make([]byte, len(customData)) - copy(originalCustomData, customData) - err := r.ParseCustomChannelData(resp) if err != nil { return nil, err } - - // We make sure we only set this field if the parser - // changed the data otherwise we might mistakenly - // show other tlv custom wire data as custom channel - // data. - if bytes.Equal( - originalCustomData, resp.CustomChannelData, - ) { - - resp.CustomChannelData = nil - } } } @@ -675,11 +638,16 @@ func (r *RouterBackend) MarshallRoute(route *route.Route) (*lnrpc.Route, error) for i, hop := range route.Hops { fee := route.HopFee(i) - // Avoid per-hop graph lookups by using the incoming amount as a - // lower bound for the capacity. This is not the actual channel - // capacity, but it is a reasonable approximation that avoids - // slow graph lookups and works for closed/private channels too. - chanCapacity := incomingAmt.ToSatoshis() + // Channel capacity is not a defining property of a route. For + // backwards RPC compatibility, we retrieve it here from the + // graph. + chanCapacity, err := r.FetchChannelCapacity(hop.ChannelID) + if err != nil { + // If capacity cannot be retrieved, this may be a + // not-yet-received or private channel. Then report + // amount that is sent through the channel as capacity. + chanCapacity = incomingAmt.ToSatoshis() + } // Extract the MPP fields if present on this hop. var mpp *lnrpc.MPPRecord @@ -728,7 +696,6 @@ func (r *RouterBackend) MarshallRoute(route *route.Route) (*lnrpc.Route, error) blinding := hop.BlindingPoint.SerializeCompressed() resp.Hops[i].BlindingPoint = blinding } - incomingAmt = hop.AmtToForward } @@ -783,13 +750,6 @@ func UnmarshallHopWithPubkey(rpcHop *lnrpc.Hop, pubkey route.Vertex) (*route.Hop "blinding point is provided") } - // TotalAmtMsat is only defined for blinded payments, so it requires - // the encrypted recipient data that identifies this as a blinded hop. - if rpcHop.TotalAmtMsat != 0 && len(rpcHop.EncryptedData) == 0 { - return nil, errors.New("encrypted data should be present if " + - "blinded total amount is provided") - } - return hop, nil } @@ -839,17 +799,6 @@ func (r *RouterBackend) UnmarshallRoute(rpcroute *lnrpc.Route) ( hops := make([]*route.Hop, len(rpcroute.Hops)) for i, hop := range rpcroute.Hops { - // TotalAmtMsat is the sender-declared target for the blinded - // HTLC set. The final node checks that every part declares the - // same total and withholds fulfillment until the received parts - // reach that amount. Since only the final node performs - // this set-level check, BOLT 4 only permits the field in its - // payload. - if hop.TotalAmtMsat != 0 && i != len(rpcroute.Hops)-1 { - return nil, errors.New("blinded total amount can " + - "only be provided for the final hop") - } - routeHop, err := r.UnmarshallHop(hop, prevNodePubKey) if err != nil { return nil, err @@ -887,8 +836,21 @@ func (r *RouterBackend) extractIntentFromSendRequest( } payIntent.TimePref = rpcPayReq.TimePref + // Pass along restrictions on the outgoing channels that may be used. payIntent.OutgoingChannelIDs = rpcPayReq.OutgoingChanIds + // Add the deprecated single outgoing channel restriction if present. + if rpcPayReq.OutgoingChanId != 0 { + if payIntent.OutgoingChannelIDs != nil { + return nil, errors.New("outgoing_chan_id and " + + "outgoing_chan_ids are mutually exclusive") + } + + payIntent.OutgoingChannelIDs = append( + payIntent.OutgoingChannelIDs, rpcPayReq.OutgoingChanId, + ) + } + // Pass along a last hop restriction if specified. if len(rpcPayReq.LastHopPubkey) > 0 { lastHop, err := route.NewVertexFromBytes( @@ -968,19 +930,19 @@ func (r *RouterBackend) extractIntentFromSendRequest( } payIntent.FirstHopCustomRecords = firstHopRecords - // If the experimental accountable signal is not already set, propagate + // If the experimental endorsement signal is not already set, propagate // a zero value field if configured to set this signal. - if r.ShouldSetExpAccountability() { + if r.ShouldSetExpEndorsement() { if payIntent.FirstHopCustomRecords == nil { payIntent.FirstHopCustomRecords = make( map[uint64][]byte, ) } - t := uint64(lnwire.ExperimentalAccountableType) + t := uint64(lnwire.ExperimentalEndorsementType) if _, set := payIntent.FirstHopCustomRecords[t]; !set { payIntent.FirstHopCustomRecords[t] = []byte{ - lnwire.ExperimentalUnaccountable, + lnwire.ExperimentalUnendorsed, } } } @@ -1788,10 +1750,6 @@ func (r *RouterBackend) MarshallPayment(payment *paymentsdb.MPPayment) ( // If any of the htlcs have settled, extract a valid // preimage. if htlc.Settle != nil { - // For AMP payments all hashes will be different so we - // will only show the last htlc preimage, this is a - // current limitation for AMP payments because for - // MPP payments all hashes are the same. preimage = htlc.Settle.Preimage fee += htlc.Route.TotalFees() } diff --git a/lnrpc/routerrpc/router_backend_test.go b/lnrpc/routerrpc/router_backend_test.go index de3a662d8..a1095a36c 100644 --- a/lnrpc/routerrpc/router_backend_test.go +++ b/lnrpc/routerrpc/router_backend_test.go @@ -6,8 +6,8 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/lightningnetwork/lnd/lnmock" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwire" @@ -33,129 +33,39 @@ var ( node2 = route.Vertex{11} ) -// TestUnmarshallHopBlindedFieldsRequireEncryptedData verifies that callers -// cannot submit partial blinded-hop data through SendToRouteV2. -func TestUnmarshallHopBlindedFieldsRequireEncryptedData(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - hop *lnrpc.Hop - errText string - }{ - { - name: "total amount without encrypted data", - hop: &lnrpc.Hop{ - TotalAmtMsat: 1000, - }, - errText: "encrypted data should be present", - }, - { - name: "total amount with encrypted data", - hop: &lnrpc.Hop{ - TotalAmtMsat: 1000, - EncryptedData: []byte{1}, - }, - }, - { - name: "ordinary hop", - hop: &lnrpc.Hop{}, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - _, err := UnmarshallHopWithPubkey(test.hop, node1) - if test.errText != "" { - require.ErrorContains(t, err, test.errText) - return - } - - require.NoError(t, err) - }) - } -} - -// TestUnmarshallRouteBlindedTotalAmountFinalHop verifies that the blinded -// total amount is only accepted on the final hop of a caller-provided route. -func TestUnmarshallRouteBlindedTotalAmountFinalHop(t *testing.T) { - t.Parallel() - - blindedHop := func() *lnrpc.Hop { - return &lnrpc.Hop{ - PubKey: destKey, - EncryptedData: []byte{1}, - TotalAmtMsat: 1000, - } - } - regularHop := func() *lnrpc.Hop { - return &lnrpc.Hop{ - PubKey: destKey, - } - } - - tests := []struct { - name string - hops []*lnrpc.Hop - errText string - }{ - { - name: "intermediate hop", - hops: []*lnrpc.Hop{ - blindedHop(), regularHop(), - }, - errText: "blinded total amount can only be provided " + - "for the final hop", - }, - { - name: "final hop", - hops: []*lnrpc.Hop{ - regularHop(), blindedHop(), - }, - }, - } - - backend := &RouterBackend{ - SelfNode: sourceKey, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - _, err := backend.UnmarshallRoute(&lnrpc.Route{ - Hops: test.hops, - }) - if test.errText != "" { - require.ErrorContains(t, err, test.errText) - return - } - - require.NoError(t, err) - }) - } -} +var ( + singleChanID = "singleChanID" + multiChanID = "multiChanID" + bothChanIds = "bothChanIds" +) // TestQueryRoutes asserts that query routes rpc parameters are properly parsed // and passed onto path finding. func TestQueryRoutes(t *testing.T) { t.Run("no mission control", func(t *testing.T) { - testQueryRoutes(t, false, false, true) + testQueryRoutes(t, false, false, true, singleChanID) }) t.Run("no mission control and msat", func(t *testing.T) { - testQueryRoutes(t, false, true, true) + testQueryRoutes(t, false, true, true, singleChanID) }) t.Run("with mission control", func(t *testing.T) { - testQueryRoutes(t, true, false, true) + testQueryRoutes(t, true, false, true, singleChanID) }) t.Run("no mission control bad cltv limit", func(t *testing.T) { - testQueryRoutes(t, false, false, false) + testQueryRoutes(t, false, false, false, singleChanID) + }) + + t.Run("both outgoing chan id and chan ids", func(t *testing.T) { + testQueryRoutes(t, true, false, true, bothChanIds) + }) + + t.Run("multiple outgoing chan ids", func(t *testing.T) { + testQueryRoutes(t, false, true, true, multiChanID) }) } func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool, - setTimelock bool) { + setTimelock bool, outgoingChanConfig string) { ignoreNodeBytes, err := hex.DecodeString(ignoreNodeKey) if err != nil { @@ -172,6 +82,7 @@ func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool, var ( lastHop = route.Vertex{64} + outgoingChan = uint64(383322) outgoingChanIds = []uint64{383322, 383323} ) @@ -226,7 +137,17 @@ func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool, } } - request.OutgoingChanIds = outgoingChanIds + switch outgoingChanConfig { + case singleChanID: + request.OutgoingChanId = outgoingChan + + case multiChanID: + request.OutgoingChanIds = outgoingChanIds + + case bothChanIds: + request.OutgoingChanId = outgoingChan + request.OutgoingChanIds = outgoingChanIds + } findRoute := func(req *routing.RouteRequest) (*route.Route, float64, error) { @@ -269,9 +190,19 @@ func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool, t.Fatal("unexpected last hop") } - require.Equal( - t, restrictions.OutgoingChannelIDs, outgoingChanIds, - ) + switch outgoingChanConfig { + case singleChanID: + require.Equal( + t, restrictions.OutgoingChannelIDs, + []uint64{outgoingChan}, + ) + + case multiChanID: + require.Equal( + t, restrictions.OutgoingChannelIDs, + outgoingChanIds, + ) + } if !restrictions.DestFeatures.HasFeature(lnwire.MPPOptional) { t.Fatal("unexpected dest features") @@ -334,6 +265,13 @@ func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool, resp, err := backend.QueryRoutes(t.Context(), request) + // If we're using both OutgoingChanId and OutgoingChanIds, we should get + // an error. + if outgoingChanConfig == bothChanIds { + require.Error(t, err) + return + } + // If no MaxTotalTimelock was set for the QueryRoutes request, make // sure an error was returned. if !setTimelock { @@ -441,6 +379,7 @@ func TestUnmarshalMPP(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { testUnmarshalMPP(t, test) }) @@ -550,6 +489,7 @@ func TestUnmarshalAMP(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { testUnmarshalAMP(t, test) }) @@ -633,6 +573,17 @@ func TestExtractIntentFromSendRequest(t *testing.T) { valid: false, expectedErrorMsg: "time preference out of range", }, + { + name: "Outgoing channel exclusivity violation", + backend: &RouterBackend{}, + sendReq: &SendPaymentRequest{ + OutgoingChanId: 38484, + OutgoingChanIds: []uint64{383322}, + }, + valid: false, + expectedErrorMsg: "outgoing_chan_id and " + + "outgoing_chan_ids are mutually exclusive", + }, { name: "Invalid last hop pubkey length", backend: &RouterBackend{}, @@ -726,7 +677,7 @@ func TestExtractIntentFromSendRequest(t *testing.T) { { name: "Amount conflict, both sat and msat specified", backend: &RouterBackend{ - ShouldSetExpAccountability: func() bool { + ShouldSetExpEndorsement: func() bool { return true }, }, @@ -741,7 +692,7 @@ func TestExtractIntentFromSendRequest(t *testing.T) { { name: "Both dest and payment_request provided", backend: &RouterBackend{ - ShouldSetExpAccountability: func() bool { + ShouldSetExpEndorsement: func() bool { return false }, }, @@ -757,7 +708,7 @@ func TestExtractIntentFromSendRequest(t *testing.T) { { name: "Both payment_hash and payment_request provided", backend: &RouterBackend{ - ShouldSetExpAccountability: func() bool { + ShouldSetExpEndorsement: func() bool { return false }, }, @@ -774,7 +725,7 @@ func TestExtractIntentFromSendRequest(t *testing.T) { name: "Both final_cltv_delta and payment_request " + "provided", backend: &RouterBackend{ - ShouldSetExpAccountability: func() bool { + ShouldSetExpEndorsement: func() bool { return false }, }, @@ -790,7 +741,7 @@ func TestExtractIntentFromSendRequest(t *testing.T) { { name: "Invalid payment request length", backend: &RouterBackend{ - ShouldSetExpAccountability: func() bool { + ShouldSetExpEndorsement: func() bool { return false }, ActiveNetParams: &chaincfg.RegressionNetParams, @@ -805,7 +756,7 @@ func TestExtractIntentFromSendRequest(t *testing.T) { { name: "Expired invoice payment request", backend: &RouterBackend{ - ShouldSetExpAccountability: func() bool { + ShouldSetExpEndorsement: func() bool { return false }, ActiveNetParams: &chaincfg.RegressionNetParams, @@ -821,7 +772,7 @@ func TestExtractIntentFromSendRequest(t *testing.T) { { name: "Invoice missing payment address", backend: &RouterBackend{ - ShouldSetExpAccountability: func() bool { + ShouldSetExpEndorsement: func() bool { return false }, ActiveNetParams: &chaincfg.RegressionNetParams, @@ -838,7 +789,7 @@ func TestExtractIntentFromSendRequest(t *testing.T) { { name: "Invalid dest vertex length", backend: &RouterBackend{ - ShouldSetExpAccountability: func() bool { + ShouldSetExpEndorsement: func() bool { return false }, }, @@ -852,7 +803,7 @@ func TestExtractIntentFromSendRequest(t *testing.T) { { name: "Payment request with missing amount", backend: &RouterBackend{ - ShouldSetExpAccountability: func() bool { + ShouldSetExpEndorsement: func() bool { return false }, }, @@ -866,7 +817,7 @@ func TestExtractIntentFromSendRequest(t *testing.T) { { name: "Destination lacks AMP support", backend: &RouterBackend{ - ShouldSetExpAccountability: func() bool { + ShouldSetExpEndorsement: func() bool { return false }, }, @@ -883,7 +834,7 @@ func TestExtractIntentFromSendRequest(t *testing.T) { { name: "Invalid payment hash length", backend: &RouterBackend{ - ShouldSetExpAccountability: func() bool { + ShouldSetExpEndorsement: func() bool { return false }, }, @@ -898,7 +849,7 @@ func TestExtractIntentFromSendRequest(t *testing.T) { { name: "Payment amount exceeds maximum possible amount", backend: &RouterBackend{ - ShouldSetExpAccountability: func() bool { + ShouldSetExpEndorsement: func() bool { return false }, }, @@ -917,7 +868,7 @@ func TestExtractIntentFromSendRequest(t *testing.T) { name: "Reject self-payments if not permitted", backend: &RouterBackend{ MaxTotalTimelock: 1000, - ShouldSetExpAccountability: func() bool { + ShouldSetExpEndorsement: func() bool { return false }, SelfNode: target, @@ -934,7 +885,7 @@ func TestExtractIntentFromSendRequest(t *testing.T) { name: "Required and optional feature bits set", backend: &RouterBackend{ MaxTotalTimelock: 1000, - ShouldSetExpAccountability: func() bool { + ShouldSetExpEndorsement: func() bool { return false }, }, @@ -955,7 +906,7 @@ func TestExtractIntentFromSendRequest(t *testing.T) { name: "Valid send req parameters, payment settled", backend: &RouterBackend{ MaxTotalTimelock: 1000, - ShouldSetExpAccountability: func() bool { + ShouldSetExpEndorsement: func() bool { return false }, }, @@ -986,55 +937,3 @@ func TestExtractIntentFromSendRequest(t *testing.T) { }) } } - -// TestMarshallRouteChanCapacity verifies that MarshallRoute correctly sets the -// ChanCapacity for each hop based on the incoming amount at that hop, not -// the total route amount. This is a regression test to ensure the -// incomingAmt is updated per hop. -func TestMarshallRouteChanCapacity(t *testing.T) { - t.Parallel() - - // Build a two-hop route: source -> hop1 -> hop2 -> dest. - // - // TotalAmount (incoming to hop1) = 1000 msat - // hop1.AmtToForward (incoming to hop2) = 900 msat (after fee) - const ( - totalAmtMsat = lnwire.MilliSatoshi(1000) - hop1Forward = lnwire.MilliSatoshi(900) - hop2Forward = lnwire.MilliSatoshi(900) - ) - - hops := []*route.Hop{ - { - ChannelID: 1, - AmtToForward: hop1Forward, - PubKeyBytes: node1, - }, - { - ChannelID: 2, - AmtToForward: hop2Forward, - PubKeyBytes: node2, - }, - } - - r, err := route.NewRouteFromHops(totalAmtMsat, 100, sourceKey, hops) - require.NoError(t, err) - - backend := &RouterBackend{} - rpcRoute, err := backend.MarshallRoute(r) - require.NoError(t, err) - require.Len(t, rpcRoute.Hops, 2) - - // The first hop's capacity should reflect the total incoming amount - // (route.TotalAmount), converted to satoshis. - require.EqualValues( - t, totalAmtMsat.ToSatoshis(), rpcRoute.Hops[0].ChanCapacity, - ) - - // The second hop's capacity should reflect hop1's forwarded amount, not - // the total route amount. Before the fix, both hops incorrectly used - // the total route amount. - require.EqualValues( - t, hop1Forward.ToSatoshis(), rpcRoute.Hops[1].ChanCapacity, - ) -} diff --git a/lnrpc/routerrpc/router_grpc.pb.go b/lnrpc/routerrpc/router_grpc.pb.go index 38ad4b0ab..6e7e980fc 100644 --- a/lnrpc/routerrpc/router_grpc.pb.go +++ b/lnrpc/routerrpc/router_grpc.pb.go @@ -19,7 +19,6 @@ const _ = grpc.SupportPackageIsVersion7 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type RouterClient interface { - // lncli: `sendpayment` // SendPaymentV2 attempts to route a payment described by the passed // PaymentRequest to the final destination. The call returns a stream of // payment updates. When using this RPC, make sure to set a fee limit, as the @@ -38,11 +37,17 @@ type RouterClient interface { // payment attempt make sure to subscribe to this method before initiating any // payments. TrackPayments(ctx context.Context, in *TrackPaymentsRequest, opts ...grpc.CallOption) (Router_TrackPaymentsClient, error) - // lncli: `estimateroutefee` // EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it // may cost to send an HTLC to the target end destination. EstimateRouteFee(ctx context.Context, in *RouteFeeRequest, opts ...grpc.CallOption) (*RouteFeeResponse, error) - // lncli: `sendtoroute` + // Deprecated: Do not use. + // + // Deprecated, use SendToRouteV2. SendToRoute attempts to make a payment via + // the specified route. This method differs from SendPayment in that it + // allows users to specify a full route manually. This can be used for + // things like rebalancing, and atomic swaps. It differs from the newer + // SendToRouteV2 in that it doesn't return the full HTLC information. + SendToRoute(ctx context.Context, in *SendToRouteRequest, opts ...grpc.CallOption) (*SendToRouteResponse, error) // SendToRouteV2 attempts to make a payment via the specified route. This // method differs from SendPayment in that it allows users to specify a full // route manually. This can be used for things like rebalancing, and atomic @@ -87,6 +92,17 @@ type RouterClient interface { // SubscribeHtlcEvents creates a uni-directional stream from the server to // the client which delivers a stream of htlc events. SubscribeHtlcEvents(ctx context.Context, in *SubscribeHtlcEventsRequest, opts ...grpc.CallOption) (Router_SubscribeHtlcEventsClient, error) + // Deprecated: Do not use. + // + // Deprecated, use SendPaymentV2. SendPayment attempts to route a payment + // described by the passed PaymentRequest to the final destination. The call + // returns a stream of payment status updates. + SendPayment(ctx context.Context, in *SendPaymentRequest, opts ...grpc.CallOption) (Router_SendPaymentClient, error) + // Deprecated: Do not use. + // + // Deprecated, use TrackPaymentV2. TrackPayment returns an update stream for + // the payment identified by the payment hash. + TrackPayment(ctx context.Context, in *TrackPaymentRequest, opts ...grpc.CallOption) (Router_TrackPaymentClient, error) // * // HtlcInterceptor dispatches a bi-directional streaming RPC in which // Forwarded HTLC requests are sent to the client and the client responds with @@ -115,15 +131,6 @@ type RouterClient interface { // XFindBaseLocalChanAlias is an experimental API that looks up the base scid // for a local chan alias that was registered during the current runtime. XFindBaseLocalChanAlias(ctx context.Context, in *FindBaseAliasRequest, opts ...grpc.CallOption) (*FindBaseAliasResponse, error) - // lncli: `deletefwdhistory` - // DeleteForwardingHistory allows the caller to delete forwarding history - // events with a timestamp at or before a specified time. This is useful - // for implementing data retention policies for privacy purposes. The call - // deletes events in batches and returns statistics including the total number - // of events deleted and the aggregate fees earned from those events. The - // deletion is performed in a transaction-safe manner with configurable batch - // sizes to avoid holding large database locks. - DeleteForwardingHistory(ctx context.Context, in *DeleteForwardingHistoryRequest, opts ...grpc.CallOption) (*DeleteForwardingHistoryResponse, error) } type routerClient struct { @@ -239,6 +246,16 @@ func (c *routerClient) EstimateRouteFee(ctx context.Context, in *RouteFeeRequest return out, nil } +// Deprecated: Do not use. +func (c *routerClient) SendToRoute(ctx context.Context, in *SendToRouteRequest, opts ...grpc.CallOption) (*SendToRouteResponse, error) { + out := new(SendToRouteResponse) + err := c.cc.Invoke(ctx, "/routerrpc.Router/SendToRoute", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *routerClient) SendToRouteV2(ctx context.Context, in *SendToRouteRequest, opts ...grpc.CallOption) (*lnrpc.HTLCAttempt, error) { out := new(lnrpc.HTLCAttempt) err := c.cc.Invoke(ctx, "/routerrpc.Router/SendToRouteV2", in, out, opts...) @@ -343,8 +360,74 @@ func (x *routerSubscribeHtlcEventsClient) Recv() (*HtlcEvent, error) { return m, nil } +// Deprecated: Do not use. +func (c *routerClient) SendPayment(ctx context.Context, in *SendPaymentRequest, opts ...grpc.CallOption) (Router_SendPaymentClient, error) { + stream, err := c.cc.NewStream(ctx, &Router_ServiceDesc.Streams[4], "/routerrpc.Router/SendPayment", opts...) + if err != nil { + return nil, err + } + x := &routerSendPaymentClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type Router_SendPaymentClient interface { + Recv() (*PaymentStatus, error) + grpc.ClientStream +} + +type routerSendPaymentClient struct { + grpc.ClientStream +} + +func (x *routerSendPaymentClient) Recv() (*PaymentStatus, error) { + m := new(PaymentStatus) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// Deprecated: Do not use. +func (c *routerClient) TrackPayment(ctx context.Context, in *TrackPaymentRequest, opts ...grpc.CallOption) (Router_TrackPaymentClient, error) { + stream, err := c.cc.NewStream(ctx, &Router_ServiceDesc.Streams[5], "/routerrpc.Router/TrackPayment", opts...) + if err != nil { + return nil, err + } + x := &routerTrackPaymentClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type Router_TrackPaymentClient interface { + Recv() (*PaymentStatus, error) + grpc.ClientStream +} + +type routerTrackPaymentClient struct { + grpc.ClientStream +} + +func (x *routerTrackPaymentClient) Recv() (*PaymentStatus, error) { + m := new(PaymentStatus) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + func (c *routerClient) HtlcInterceptor(ctx context.Context, opts ...grpc.CallOption) (Router_HtlcInterceptorClient, error) { - stream, err := c.cc.NewStream(ctx, &Router_ServiceDesc.Streams[4], "/routerrpc.Router/HtlcInterceptor", opts...) + stream, err := c.cc.NewStream(ctx, &Router_ServiceDesc.Streams[6], "/routerrpc.Router/HtlcInterceptor", opts...) if err != nil { return nil, err } @@ -410,20 +493,10 @@ func (c *routerClient) XFindBaseLocalChanAlias(ctx context.Context, in *FindBase return out, nil } -func (c *routerClient) DeleteForwardingHistory(ctx context.Context, in *DeleteForwardingHistoryRequest, opts ...grpc.CallOption) (*DeleteForwardingHistoryResponse, error) { - out := new(DeleteForwardingHistoryResponse) - err := c.cc.Invoke(ctx, "/routerrpc.Router/DeleteForwardingHistory", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - // RouterServer is the server API for Router service. // All implementations must embed UnimplementedRouterServer // for forward compatibility type RouterServer interface { - // lncli: `sendpayment` // SendPaymentV2 attempts to route a payment described by the passed // PaymentRequest to the final destination. The call returns a stream of // payment updates. When using this RPC, make sure to set a fee limit, as the @@ -442,11 +515,17 @@ type RouterServer interface { // payment attempt make sure to subscribe to this method before initiating any // payments. TrackPayments(*TrackPaymentsRequest, Router_TrackPaymentsServer) error - // lncli: `estimateroutefee` // EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it // may cost to send an HTLC to the target end destination. EstimateRouteFee(context.Context, *RouteFeeRequest) (*RouteFeeResponse, error) - // lncli: `sendtoroute` + // Deprecated: Do not use. + // + // Deprecated, use SendToRouteV2. SendToRoute attempts to make a payment via + // the specified route. This method differs from SendPayment in that it + // allows users to specify a full route manually. This can be used for + // things like rebalancing, and atomic swaps. It differs from the newer + // SendToRouteV2 in that it doesn't return the full HTLC information. + SendToRoute(context.Context, *SendToRouteRequest) (*SendToRouteResponse, error) // SendToRouteV2 attempts to make a payment via the specified route. This // method differs from SendPayment in that it allows users to specify a full // route manually. This can be used for things like rebalancing, and atomic @@ -491,6 +570,17 @@ type RouterServer interface { // SubscribeHtlcEvents creates a uni-directional stream from the server to // the client which delivers a stream of htlc events. SubscribeHtlcEvents(*SubscribeHtlcEventsRequest, Router_SubscribeHtlcEventsServer) error + // Deprecated: Do not use. + // + // Deprecated, use SendPaymentV2. SendPayment attempts to route a payment + // described by the passed PaymentRequest to the final destination. The call + // returns a stream of payment status updates. + SendPayment(*SendPaymentRequest, Router_SendPaymentServer) error + // Deprecated: Do not use. + // + // Deprecated, use TrackPaymentV2. TrackPayment returns an update stream for + // the payment identified by the payment hash. + TrackPayment(*TrackPaymentRequest, Router_TrackPaymentServer) error // * // HtlcInterceptor dispatches a bi-directional streaming RPC in which // Forwarded HTLC requests are sent to the client and the client responds with @@ -519,15 +609,6 @@ type RouterServer interface { // XFindBaseLocalChanAlias is an experimental API that looks up the base scid // for a local chan alias that was registered during the current runtime. XFindBaseLocalChanAlias(context.Context, *FindBaseAliasRequest) (*FindBaseAliasResponse, error) - // lncli: `deletefwdhistory` - // DeleteForwardingHistory allows the caller to delete forwarding history - // events with a timestamp at or before a specified time. This is useful - // for implementing data retention policies for privacy purposes. The call - // deletes events in batches and returns statistics including the total number - // of events deleted and the aggregate fees earned from those events. The - // deletion is performed in a transaction-safe manner with configurable batch - // sizes to avoid holding large database locks. - DeleteForwardingHistory(context.Context, *DeleteForwardingHistoryRequest) (*DeleteForwardingHistoryResponse, error) mustEmbedUnimplementedRouterServer() } @@ -547,6 +628,9 @@ func (UnimplementedRouterServer) TrackPayments(*TrackPaymentsRequest, Router_Tra func (UnimplementedRouterServer) EstimateRouteFee(context.Context, *RouteFeeRequest) (*RouteFeeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method EstimateRouteFee not implemented") } +func (UnimplementedRouterServer) SendToRoute(context.Context, *SendToRouteRequest) (*SendToRouteResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SendToRoute not implemented") +} func (UnimplementedRouterServer) SendToRouteV2(context.Context, *SendToRouteRequest) (*lnrpc.HTLCAttempt, error) { return nil, status.Errorf(codes.Unimplemented, "method SendToRouteV2 not implemented") } @@ -574,6 +658,12 @@ func (UnimplementedRouterServer) BuildRoute(context.Context, *BuildRouteRequest) func (UnimplementedRouterServer) SubscribeHtlcEvents(*SubscribeHtlcEventsRequest, Router_SubscribeHtlcEventsServer) error { return status.Errorf(codes.Unimplemented, "method SubscribeHtlcEvents not implemented") } +func (UnimplementedRouterServer) SendPayment(*SendPaymentRequest, Router_SendPaymentServer) error { + return status.Errorf(codes.Unimplemented, "method SendPayment not implemented") +} +func (UnimplementedRouterServer) TrackPayment(*TrackPaymentRequest, Router_TrackPaymentServer) error { + return status.Errorf(codes.Unimplemented, "method TrackPayment not implemented") +} func (UnimplementedRouterServer) HtlcInterceptor(Router_HtlcInterceptorServer) error { return status.Errorf(codes.Unimplemented, "method HtlcInterceptor not implemented") } @@ -589,9 +679,6 @@ func (UnimplementedRouterServer) XDeleteLocalChanAliases(context.Context, *Delet func (UnimplementedRouterServer) XFindBaseLocalChanAlias(context.Context, *FindBaseAliasRequest) (*FindBaseAliasResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method XFindBaseLocalChanAlias not implemented") } -func (UnimplementedRouterServer) DeleteForwardingHistory(context.Context, *DeleteForwardingHistoryRequest) (*DeleteForwardingHistoryResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method DeleteForwardingHistory not implemented") -} func (UnimplementedRouterServer) mustEmbedUnimplementedRouterServer() {} // UnsafeRouterServer may be embedded to opt out of forward compatibility for this service. @@ -686,6 +773,24 @@ func _Router_EstimateRouteFee_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _Router_SendToRoute_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SendToRouteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RouterServer).SendToRoute(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/routerrpc.Router/SendToRoute", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RouterServer).SendToRoute(ctx, req.(*SendToRouteRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Router_SendToRouteV2_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SendToRouteRequest) if err := dec(in); err != nil { @@ -851,6 +956,48 @@ func (x *routerSubscribeHtlcEventsServer) Send(m *HtlcEvent) error { return x.ServerStream.SendMsg(m) } +func _Router_SendPayment_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(SendPaymentRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(RouterServer).SendPayment(m, &routerSendPaymentServer{stream}) +} + +type Router_SendPaymentServer interface { + Send(*PaymentStatus) error + grpc.ServerStream +} + +type routerSendPaymentServer struct { + grpc.ServerStream +} + +func (x *routerSendPaymentServer) Send(m *PaymentStatus) error { + return x.ServerStream.SendMsg(m) +} + +func _Router_TrackPayment_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(TrackPaymentRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(RouterServer).TrackPayment(m, &routerTrackPaymentServer{stream}) +} + +type Router_TrackPaymentServer interface { + Send(*PaymentStatus) error + grpc.ServerStream +} + +type routerTrackPaymentServer struct { + grpc.ServerStream +} + +func (x *routerTrackPaymentServer) Send(m *PaymentStatus) error { + return x.ServerStream.SendMsg(m) +} + func _Router_HtlcInterceptor_Handler(srv interface{}, stream grpc.ServerStream) error { return srv.(RouterServer).HtlcInterceptor(&routerHtlcInterceptorServer{stream}) } @@ -949,24 +1096,6 @@ func _Router_XFindBaseLocalChanAlias_Handler(srv interface{}, ctx context.Contex return interceptor(ctx, in, info, handler) } -func _Router_DeleteForwardingHistory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DeleteForwardingHistoryRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(RouterServer).DeleteForwardingHistory(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/routerrpc.Router/DeleteForwardingHistory", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(RouterServer).DeleteForwardingHistory(ctx, req.(*DeleteForwardingHistoryRequest)) - } - return interceptor(ctx, in, info, handler) -} - // Router_ServiceDesc is the grpc.ServiceDesc for Router service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -978,6 +1107,10 @@ var Router_ServiceDesc = grpc.ServiceDesc{ MethodName: "EstimateRouteFee", Handler: _Router_EstimateRouteFee_Handler, }, + { + MethodName: "SendToRoute", + Handler: _Router_SendToRoute_Handler, + }, { MethodName: "SendToRouteV2", Handler: _Router_SendToRouteV2_Handler, @@ -1026,10 +1159,6 @@ var Router_ServiceDesc = grpc.ServiceDesc{ MethodName: "XFindBaseLocalChanAlias", Handler: _Router_XFindBaseLocalChanAlias_Handler, }, - { - MethodName: "DeleteForwardingHistory", - Handler: _Router_DeleteForwardingHistory_Handler, - }, }, Streams: []grpc.StreamDesc{ { @@ -1052,6 +1181,16 @@ var Router_ServiceDesc = grpc.ServiceDesc{ Handler: _Router_SubscribeHtlcEvents_Handler, ServerStreams: true, }, + { + StreamName: "SendPayment", + Handler: _Router_SendPayment_Handler, + ServerStreams: true, + }, + { + StreamName: "TrackPayment", + Handler: _Router_TrackPayment_Handler, + ServerStreams: true, + }, { StreamName: "HtlcInterceptor", Handler: _Router_HtlcInterceptor_Handler, diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index 049207740..27cd85824 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -10,8 +10,8 @@ import ( "sync/atomic" "time" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/lightningnetwork/lnd/aliasmgr" "github.com/lightningnetwork/lnd/fn/v2" @@ -96,6 +96,10 @@ var ( Entity: "offchain", Action: "write", }}, + "/routerrpc.Router/SendToRoute": {{ + Entity: "offchain", + Action: "write", + }}, "/routerrpc.Router/TrackPaymentV2": {{ Entity: "offchain", Action: "read", @@ -140,6 +144,14 @@ var ( Entity: "offchain", Action: "read", }}, + "/routerrpc.Router/SendPayment": {{ + Entity: "offchain", + Action: "write", + }}, + "/routerrpc.Router/TrackPayment": {{ + Entity: "offchain", + Action: "read", + }}, "/routerrpc.Router/HtlcInterceptor": {{ Entity: "offchain", Action: "write", @@ -156,10 +168,6 @@ var ( Entity: "offchain", Action: "write", }}, - "/routerrpc.Router/DeleteForwardingHistory": {{ - Entity: "offchain", - Action: "write", - }}, } // DefaultRouterMacFilename is the default name of the router macaroon @@ -436,15 +444,12 @@ func (s *Server) EstimateRouteFee(ctx context.Context, return nil, errors.New("amount must be greater than 0") default: - return s.probeDestination( - req.Dest, req.AmtSat, req.OutgoingChanIds, - ) + return s.probeDestination(req.Dest, req.AmtSat) } case isProbeInvoice: return s.probePaymentRequest( ctx, req.PaymentRequest, req.Timeout, - req.OutgoingChanIds, ) } @@ -453,8 +458,8 @@ func (s *Server) EstimateRouteFee(ctx context.Context, // probeDestination estimates fees along a route to a destination based on the // contents of the local graph. -func (s *Server) probeDestination(dest []byte, amtSat int64, - outgoingChanIDs []uint64) (*RouteFeeResponse, error) { +func (s *Server) probeDestination(dest []byte, amtSat int64) (*RouteFeeResponse, + error) { destNode, err := route.NewVertexFromBytes(dest) if err != nil { @@ -469,16 +474,14 @@ func (s *Server) probeDestination(dest []byte, amtSat int64, // that target amount, we'll only request a single route. Set a // restriction for the default CLTV limit, otherwise we can find a route // that exceeds it and is useless to us. - backend := s.cfg.RouterBackend - mc := backend.MissionControl + mc := s.cfg.RouterBackend.MissionControl routeReq, err := routing.NewRouteRequest( - backend.SelfNode, &destNode, amtMsat, 0, + s.cfg.RouterBackend.SelfNode, &destNode, amtMsat, 0, &routing.RestrictParams{ - FeeLimit: routeFeeLimitSat, - CltvLimit: backend.MaxTotalTimelock, - ProbabilitySource: mc.GetProbability, - OutgoingChannelIDs: outgoingChanIDs, - }, nil, nil, nil, backend.DefaultFinalCltvDelta, + FeeLimit: routeFeeLimitSat, + CltvLimit: s.cfg.RouterBackend.MaxTotalTimelock, + ProbabilitySource: mc.GetProbability, + }, nil, nil, nil, s.cfg.RouterBackend.DefaultFinalCltvDelta, ) if err != nil { return nil, err @@ -515,27 +518,7 @@ func (s *Server) probeDestination(dest []byte, amtSat int64, // identify LSPs, the probe payment might use a different node id as the // final destination (the assumed LSP node id). func (s *Server) probePaymentRequest(ctx context.Context, paymentRequest string, - timeout uint32, outgoingChanIDs []uint64) (*RouteFeeResponse, error) { - - return s.probePaymentRequestWithSender( - ctx, paymentRequest, timeout, outgoingChanIDs, - s.sendProbePayment, - ) -} - -// probePaymentSender dispatches a probe payment request and returns the -// resulting fee estimate. It exists as a test seam so tests can inject a stub -// sender and inspect generated probe requests without running the payment -// lifecycle. -type probePaymentSender func(context.Context, - *SendPaymentRequest) (*RouteFeeResponse, error) - -// probePaymentRequestWithSender contains the implementation of -// probePaymentRequest. The sender is injected so tests can inspect generated -// probe requests without invoking the full payment lifecycle. -func (s *Server) probePaymentRequestWithSender(ctx context.Context, - paymentRequest string, timeout uint32, outgoingChanIDs []uint64, - sendProbePayment probePaymentSender) (*RouteFeeResponse, error) { + timeout uint32) (*RouteFeeResponse, error) { payReq, err := zpay32.Decode( paymentRequest, s.cfg.RouterBackend.ActiveNetParams, @@ -569,7 +552,6 @@ func (s *Server) probePaymentRequestWithSender(ctx context.Context, FeeLimitSat: routeFeeLimitSat, FinalCltvDelta: int32(payReq.MinFinalCLTVExpiry()), DestFeatures: MarshalFeatures(payReq.Features), - OutgoingChanIds: outgoingChanIDs, } // If the payment addresses is specified, then we'll also populate that @@ -590,13 +572,12 @@ func (s *Server) probePaymentRequestWithSender(ctx context.Context, probeRequest.Dest) probeRequest.RouteHints = invoicesrpc.CreateRPCRouteHints(hints) - - return sendProbePayment(ctx, probeRequest) + return s.sendProbePayment(ctx, probeRequest) } // If the heuristic indicates an LSP, we filter and group route hints by // public LSP nodes, then probe each unique LSP separately and return - // the route with the highest fee. + // the cheapest route. lspGroups, err := prepareLspRouteHints( hints, *payReq.MilliSat, s.cfg.RouterBackend.HasNode, ) @@ -627,16 +608,6 @@ func (s *Server) probePaymentRequestWithSender(ctx context.Context, lspHint := group.LspHopHint - // Each LSP probe must use a unique payment hash, otherwise the - // payment lifecycle will treat later probes as attempts on the - // first probe's payment and reuse its payment-level parameters. - var lspPaymentHash lntypes.Hash - _, err := crand.Read(lspPaymentHash[:]) - if err != nil { - return nil, fmt.Errorf("cannot generate random probe "+ - "preimage: %w", err) - } - log.Infof("Probing LSP with destination: %v", lspKey) // Create a new probe request for this LSP. @@ -646,11 +617,10 @@ func (s *Server) probePaymentRequestWithSender(ctx context.Context, MaxParts: probeRequest.MaxParts, AllowSelfPayment: probeRequest.AllowSelfPayment, AmtMsat: amtMsat, - PaymentHash: lspPaymentHash[:], + PaymentHash: probeRequest.PaymentHash, FeeLimitSat: probeRequest.FeeLimitSat, FinalCltvDelta: int32(lspHint.CLTVExpiryDelta), DestFeatures: probeRequest.DestFeatures, - OutgoingChanIds: probeRequest.OutgoingChanIds, } // Copy the payment address if present. @@ -678,7 +648,7 @@ func (s *Server) probePaymentRequestWithSender(ctx context.Context, lspProbeRequest.AmtMsat += int64(hopFee) // Dispatch the payment probe for this LSP. - resp, err := sendProbePayment(ctx, lspProbeRequest) + resp, err := s.sendProbePayment(ctx, lspProbeRequest) if err != nil { log.Warnf("Failed to probe LSP %v: %v", lspKey, err) continue @@ -832,7 +802,7 @@ func isLSP(routeHints [][]zpay32.HopHint, invoiceTarget []byte, // LspRouteGroup represents a group of route hints that share the same public // LSP destination node. This is needed when probing LSPs separately to find -// the route with the highest fee. +// the cheapest route. type LspRouteGroup struct { // LspHopHint is the hop hint for the LSP node with worst-case fees and // CLTV delta. @@ -1118,11 +1088,11 @@ func (s *Server) SendToRouteV2(ctx context.Context, // db. if req.SkipTempErr { attempt, err = s.cfg.Router.SendToRouteSkipTempErr( - ctx, hash, route, firstHopRecords, + hash, route, firstHopRecords, ) } else { attempt, err = s.cfg.Router.SendToRoute( - ctx, hash, route, firstHopRecords, + hash, route, firstHopRecords, ) } if attempt != nil { @@ -2002,90 +1972,3 @@ func (s *Server) UpdateChanStatus(_ context.Context, } return &UpdateChanStatusResponse{}, nil } - -// DeleteForwardingHistory deletes forwarding history events with a timestamp -// at or before a specified time. This method is useful for implementing data -// retention policies for privacy purposes. -func (s *Server) DeleteForwardingHistory(ctx context.Context, - req *DeleteForwardingHistoryRequest) (*DeleteForwardingHistoryResponse, - error) { - - now := s.cfg.RouterBackend.Clock.Now() - - // Determine the deletion cutoff time from the request. - var deleteBeforeTime time.Time - switch timeSpec := req.TimeSpec.(type) { - case *DeleteForwardingHistoryRequest_DeleteBeforeTime: - deleteBeforeTime = time.Unix( - int64(timeSpec.DeleteBeforeTime), 0, - ) - - case *DeleteForwardingHistoryRequest_DeleteBeforeDuration: - // Parse duration using hybrid approach: try standard library - // first, fall back to custom units (d, w, M, y) if needed. - duration, err := parseDuration(timeSpec.DeleteBeforeDuration) - if err != nil { - return nil, fmt.Errorf("invalid duration format: %w", - err) - } - - // Calculate the absolute time by adding the (negative) - // duration to now. - deleteBeforeTime = now.Add(duration) - - default: - return nil, fmt.Errorf("time specification required: either " + - "delete_before_time or delete_before_duration must " + - "be provided") - } - - // Guard against pre-epoch timestamps. A very large negative duration - // (e.g. -100y) would push deleteBeforeTime before the Unix epoch, - // causing uint64(endTime.UnixNano()) in the DB layer to wrap to a - // near-max value and delete the entire bucket. - if deleteBeforeTime.Before(time.Unix(0, 0)) { - return nil, fmt.Errorf("delete_before_time must not be " + - "before the Unix epoch") - } - - // Require the cutoff to be at least minAge in the past to prevent - // accidental deletion of recent data. The default is 1 hour; - // integration tests may lower this via the dev config flag. - minAge := s.cfg.RouterBackend.MinForwardingHistoryAge - if minAge == 0 { - minAge = time.Hour - } - if now.Sub(deleteBeforeTime) < minAge { - return nil, fmt.Errorf("delete_before_time must be at "+ - "least %v in the past to prevent accidental deletion "+ - "of recent data (requested: %v, now: %v)", - minAge, deleteBeforeTime, now) - } - - batchSize := s.cfg.RouterBackend.FwdHistoryDeleteBatchSize - - log.Infof("DeleteForwardingHistory: deleting events at or before %v "+ - "with batch size %d", deleteBeforeTime, batchSize) - - // Call the database deletion method, threading the request context - // through so the operation can be aborted between batches if the - // caller disconnects or times out. A batch size of 0 is fine — the - // DB layer applies the default. - stats, err := s.cfg.RouterBackend.ForwardingLog.DeleteForwardingEvents( - ctx, deleteBeforeTime, batchSize, - ) - if err != nil { - return nil, fmt.Errorf("failed to delete forwarding events: %w", - err) - } - - log.Infof("DeleteForwardingHistory: deleted %d events, total fees: "+ - "%d msat", stats.NumEventsDeleted, stats.TotalFeeMsat) - - return &DeleteForwardingHistoryResponse{ - EventsDeleted: stats.NumEventsDeleted, - TotalFeeMsat: stats.TotalFeeMsat, - Status: fmt.Sprintf("Successfully deleted %d forwarding events", - stats.NumEventsDeleted), - }, nil -} diff --git a/lnrpc/routerrpc/router_server_deprecated.go b/lnrpc/routerrpc/router_server_deprecated.go index 4b3ed6358..a08cafcdd 100644 --- a/lnrpc/routerrpc/router_server_deprecated.go +++ b/lnrpc/routerrpc/router_server_deprecated.go @@ -2,11 +2,125 @@ package routerrpc import ( "context" + "encoding/hex" + "errors" + "fmt" + "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" ) +// legacyTrackPaymentServer is a wrapper struct that transforms a stream of main +// rpc payment structs into the legacy PaymentStatus format. +type legacyTrackPaymentServer struct { + Router_TrackPaymentServer +} + +// Send converts a Payment object and sends it as a PaymentStatus object on the +// embedded stream. +func (i *legacyTrackPaymentServer) Send(p *lnrpc.Payment) error { + var state PaymentState + switch p.Status { + case lnrpc.Payment_IN_FLIGHT: + state = PaymentState_IN_FLIGHT + case lnrpc.Payment_SUCCEEDED: + state = PaymentState_SUCCEEDED + case lnrpc.Payment_FAILED: + switch p.FailureReason { + case lnrpc.PaymentFailureReason_FAILURE_REASON_NONE: + return fmt.Errorf("expected fail reason") + + case lnrpc.PaymentFailureReason_FAILURE_REASON_TIMEOUT: + state = PaymentState_FAILED_TIMEOUT + + case lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE: + state = PaymentState_FAILED_NO_ROUTE + + case lnrpc.PaymentFailureReason_FAILURE_REASON_ERROR: + state = PaymentState_FAILED_ERROR + + case lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS: + state = PaymentState_FAILED_INCORRECT_PAYMENT_DETAILS + + case lnrpc.PaymentFailureReason_FAILURE_REASON_INSUFFICIENT_BALANCE: + state = PaymentState_FAILED_INSUFFICIENT_BALANCE + + default: + return fmt.Errorf("unknown failure reason %v", + p.FailureReason) + } + default: + return fmt.Errorf("unknown state %v", p.Status) + } + + preimage, err := hex.DecodeString(p.PaymentPreimage) + if err != nil { + return err + } + + legacyState := PaymentStatus{ + State: state, + Preimage: preimage, + Htlcs: p.Htlcs, + } + + return i.Router_TrackPaymentServer.Send(&legacyState) +} + +// TrackPayment returns a stream of payment state updates. The stream is +// closed when the payment completes. +func (s *Server) TrackPayment(request *TrackPaymentRequest, + stream Router_TrackPaymentServer) error { + + legacyStream := legacyTrackPaymentServer{ + Router_TrackPaymentServer: stream, + } + return s.TrackPaymentV2(request, &legacyStream) +} + +// SendPayment attempts to route a payment described by the passed +// PaymentRequest to the final destination. If we are unable to route the +// payment, or cannot find a route that satisfies the constraints in the +// PaymentRequest, then an error will be returned. Otherwise, the payment +// pre-image, along with the final route will be returned. +func (s *Server) SendPayment(request *SendPaymentRequest, + stream Router_SendPaymentServer) error { + + if request.MaxParts > 1 { + return errors.New("for multi-part payments, use SendPaymentV2") + } + + legacyStream := legacyTrackPaymentServer{ + Router_TrackPaymentServer: stream, + } + return s.SendPaymentV2(request, &legacyStream) +} + +// SendToRoute sends a payment through a predefined route. The response of this +// call contains structured error information. +func (s *Server) SendToRoute(ctx context.Context, + req *SendToRouteRequest) (*SendToRouteResponse, error) { + + resp, err := s.SendToRouteV2(ctx, req) + if err != nil { + return nil, err + } + + if resp == nil { + return nil, nil + } + + // Need to convert to legacy response message because proto identifiers + // don't line up. + legacyResp := &SendToRouteResponse{ + Preimage: resp.Preimage, + Failure: resp.Failure, + } + + return legacyResp, nil +} + // QueryProbability returns the current success probability estimate for a // given node pair and amount. func (s *Server) QueryProbability(_ context.Context, diff --git a/lnrpc/routerrpc/router_server_test.go b/lnrpc/routerrpc/router_server_test.go index 12592a72e..a46a12940 100644 --- a/lnrpc/routerrpc/router_server_test.go +++ b/lnrpc/routerrpc/router_server_test.go @@ -7,9 +7,6 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwire" paymentsdb "github.com/lightningnetwork/lnd/payments/db" @@ -767,137 +764,3 @@ func TestPrepareLspRouteHints(t *testing.T) { require.Contains(t, err.Error(), "no public LSP nodes found") }) } - -// TestProbePaymentRequestUsesUniqueHashPerLSP verifies that each LSP probe is -// isolated from the other probes by using a distinct payment hash. -func TestProbePaymentRequestUsesUniqueHashPerLSP(t *testing.T) { - // Arrange: create three public LSPs and an invoice whose private - // destination can be reached through any of them. - destPrivKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - bobPrivKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - bobPubKey := bobPrivKey.PubKey() - - evePrivKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - evePubKey := evePrivKey.PubKey() - - davePrivKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - davePubKey := davePrivKey.PubKey() - - bobVertex := route.NewVertex(bobPubKey) - eveVertex := route.NewVertex(evePubKey) - daveVertex := route.NewVertex(davePubKey) - - publicNodes := map[route.Vertex]struct{}{ - bobVertex: {}, - eveVertex: {}, - daveVertex: {}, - } - hasNode := func(nodePub route.Vertex) (bool, error) { - _, ok := publicNodes[nodePub] - - return ok, nil - } - - server := &Server{ - cfg: &Config{ - RouterBackend: &RouterBackend{ - ActiveNetParams: &chaincfg.RegressionNetParams, - HasNode: hasNode, - }, - }, - } - - lspHint := func(pubKey *btcec.PublicKey, chanID uint64, - cltv uint16) zpay32.HopHint { - - return zpay32.HopHint{ - NodeID: pubKey, - ChannelID: chanID, - FeeBaseMSat: uint32(chanID * 1_000), - FeeProportionalMillionths: uint32(chanID), - CLTVExpiryDelta: cltv, - } - } - - bobHint := lspHint(bobPubKey, 1, 100) - eveHint := lspHint(evePubKey, 2, 200) - daveHint := lspHint(davePubKey, 3, 120) - - var paymentHash [32]byte - paymentHash[0] = 1 - invoice, err := zpay32.NewInvoice( - &chaincfg.RegressionNetParams, paymentHash, time.Unix(1, 0), - zpay32.Amount(lnwire.MilliSatoshi(100_000)), - zpay32.Description("multi lsp probe"), - zpay32.Destination(destPrivKey.PubKey()), - zpay32.RouteHint([]zpay32.HopHint{bobHint}), - zpay32.RouteHint([]zpay32.HopHint{eveHint}), - zpay32.RouteHint([]zpay32.HopHint{daveHint}), - ) - require.NoError(t, err) - - signer := zpay32.MessageSigner{ - SignCompact: func(msg []byte) ([]byte, error) { - hash := chainhash.HashB(msg) - - return ecdsa.SignCompact(destPrivKey, hash, true), nil - }, - } - payReq, err := invoice.Encode(signer) - require.NoError(t, err) - - seenHashes := make(map[[32]byte]struct{}) - probedDests := make(map[route.Vertex]struct{}) - outgoingChanIDs := []uint64{123, 456} - expectedCltv := map[route.Vertex]int32{ - bobVertex: int32(bobHint.CLTVExpiryDelta), - eveVertex: int32(eveHint.CLTVExpiryDelta), - daveVertex: int32(daveHint.CLTVExpiryDelta), - } - - sendProbe := func(_ context.Context, - req *SendPaymentRequest) (*RouteFeeResponse, error) { - - require.Len(t, req.PaymentHash, 32) - - var reqHash [32]byte - copy(reqHash[:], req.PaymentHash) - _, hashExists := seenHashes[reqHash] - require.False(t, hashExists, "reused payment hash") - seenHashes[reqHash] = struct{}{} - - var dest route.Vertex - copy(dest[:], req.Dest) - probedDests[dest] = struct{}{} - - require.Equal(t, expectedCltv[dest], req.FinalCltvDelta) - require.Equal(t, outgoingChanIDs, req.OutgoingChanIds) - - return &RouteFeeResponse{ - RoutingFeeMsat: int64(req.FinalCltvDelta), - TimeLockDelay: int64(req.FinalCltvDelta), - FailureReason: lnrpc. - PaymentFailureReason_FAILURE_REASON_NONE, - }, nil - } - - // Act: estimate the route fee with a stubbed probe sender that records - // the generated per-LSP probe requests. - _, err = server.probePaymentRequestWithSender( - t.Context(), payReq, 1, outgoingChanIDs, sendProbe, - ) - - // Assert: all LSPs were probed, each probe had a unique payment hash, - // and the probe request kept the CLTV delta for its target LSP. - require.NoError(t, err) - require.Len(t, seenHashes, MaxLspsToProbe) - require.Len(t, probedDests, MaxLspsToProbe) - require.Contains(t, probedDests, bobVertex) - require.Contains(t, probedDests, eveVertex) - require.Contains(t, probedDests, daveVertex) -} diff --git a/lnrpc/routerrpc/routing_config.go b/lnrpc/routerrpc/routing_config.go index c8393ebff..f113db94e 100644 --- a/lnrpc/routerrpc/routing_config.go +++ b/lnrpc/routerrpc/routing_config.go @@ -3,7 +3,7 @@ package routerrpc import ( "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" ) // RoutingConfig contains the configurable parameters that control routing. diff --git a/lnrpc/routerrpc/subscribe_events.go b/lnrpc/routerrpc/subscribe_events.go index 879223ff8..230d7a506 100644 --- a/lnrpc/routerrpc/subscribe_events.go +++ b/lnrpc/routerrpc/subscribe_events.go @@ -186,9 +186,6 @@ func rpcFailureResolution(invoiceFailure invoices.FailResolutionResult) ( case invoices.ResultInvoiceNotOpen: return FailureDetail_INVOICE_NOT_OPEN, nil - case invoices.ResultInvoiceAlreadySettled: - return FailureDetail_INVOICE_ALREADY_SETTLED, nil - case invoices.ResultMppTimeout: return FailureDetail_MPP_INVOICE_TIMEOUT, nil @@ -213,18 +210,6 @@ func rpcFailureResolution(invoiceFailure invoices.FailResolutionResult) ( case invoices.ResultMppInProgress: return FailureDetail_MPP_IN_PROGRESS, nil - case invoices.ResultHtlcInvoiceTypeMismatch: - return FailureDetail_HTLC_INVOICE_TYPE_MISMATCH, nil - - case invoices.ResultAmpError: - return FailureDetail_AMP_ERROR, nil - - case invoices.ResultAmpReconstruction: - return FailureDetail_AMP_RECONSTRUCTION, nil - - case invoices.ExternalValidationFailed: - return FailureDetail_EXTERNAL_VALIDATION_FAILED, nil - default: return 0, fmt.Errorf("unknown fail resolution: %v", invoiceFailure.FailureString()) diff --git a/lnrpc/rpc_utils.go b/lnrpc/rpc_utils.go index 26164754e..15e3f2f76 100644 --- a/lnrpc/rpc_utils.go +++ b/lnrpc/rpc_utils.go @@ -6,7 +6,7 @@ import ( "fmt" "sort" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/sweep" diff --git a/lnrpc/signrpc/signer.pb.go b/lnrpc/signrpc/signer.pb.go index 9d7b60050..4b5e5f987 100644 --- a/lnrpc/signrpc/signer.pb.go +++ b/lnrpc/signrpc/signer.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.33.0 // protoc v3.21.12 // source: signrpc/signer.proto @@ -11,7 +11,6 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" - unsafe "unsafe" ) const ( @@ -138,20 +137,23 @@ func (MuSig2Version) EnumDescriptor() ([]byte, []int) { } type KeyLocator struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The family of key being identified. KeyFamily int32 `protobuf:"varint,1,opt,name=key_family,json=keyFamily,proto3" json:"key_family,omitempty"` // The precise index of the key being identified. - KeyIndex int32 `protobuf:"varint,2,opt,name=key_index,json=keyIndex,proto3" json:"key_index,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + KeyIndex int32 `protobuf:"varint,2,opt,name=key_index,json=keyIndex,proto3" json:"key_index,omitempty"` } func (x *KeyLocator) Reset() { *x = KeyLocator{} - mi := &file_signrpc_signer_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *KeyLocator) String() string { @@ -162,7 +164,7 @@ func (*KeyLocator) ProtoMessage() {} func (x *KeyLocator) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[0] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -192,22 +194,25 @@ func (x *KeyLocator) GetKeyIndex() int32 { } type KeyDescriptor struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The raw bytes of the public key in the key pair being identified. Either // this or the KeyLocator must be specified. RawKeyBytes []byte `protobuf:"bytes,1,opt,name=raw_key_bytes,json=rawKeyBytes,proto3" json:"raw_key_bytes,omitempty"` // The key locator that identifies which private key to use for signing. // Either this or the raw bytes of the target public key must be specified. - KeyLoc *KeyLocator `protobuf:"bytes,2,opt,name=key_loc,json=keyLoc,proto3" json:"key_loc,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + KeyLoc *KeyLocator `protobuf:"bytes,2,opt,name=key_loc,json=keyLoc,proto3" json:"key_loc,omitempty"` } func (x *KeyDescriptor) Reset() { *x = KeyDescriptor{} - mi := &file_signrpc_signer_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *KeyDescriptor) String() string { @@ -218,7 +223,7 @@ func (*KeyDescriptor) ProtoMessage() {} func (x *KeyDescriptor) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[1] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -248,20 +253,23 @@ func (x *KeyDescriptor) GetKeyLoc() *KeyLocator { } type TxOut struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The value of the output being spent. Value int64 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"` // The script of the output being spent. - PkScript []byte `protobuf:"bytes,2,opt,name=pk_script,json=pkScript,proto3" json:"pk_script,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + PkScript []byte `protobuf:"bytes,2,opt,name=pk_script,json=pkScript,proto3" json:"pk_script,omitempty"` } func (x *TxOut) Reset() { *x = TxOut{} - mi := &file_signrpc_signer_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *TxOut) String() string { @@ -272,7 +280,7 @@ func (*TxOut) ProtoMessage() {} func (x *TxOut) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[2] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -302,7 +310,10 @@ func (x *TxOut) GetPkScript() []byte { } type SignDescriptor struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // A descriptor that precisely describes *which* key to use for signing. This // may provide the raw public key directly, or require the Signer to re-derive // the key according to the populated derivation path. @@ -353,16 +364,16 @@ type SignDescriptor struct { // method, either the tap_tweak, witness_script or both need to be specified. // Defaults to SegWit v0 signing to be backward compatible with older RPC // clients. - SignMethod SignMethod `protobuf:"varint,9,opt,name=sign_method,json=signMethod,proto3,enum=signrpc.SignMethod" json:"sign_method,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + SignMethod SignMethod `protobuf:"varint,9,opt,name=sign_method,json=signMethod,proto3,enum=signrpc.SignMethod" json:"sign_method,omitempty"` } func (x *SignDescriptor) Reset() { *x = SignDescriptor{} - mi := &file_signrpc_signer_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SignDescriptor) String() string { @@ -373,7 +384,7 @@ func (*SignDescriptor) ProtoMessage() {} func (x *SignDescriptor) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[3] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -452,23 +463,26 @@ func (x *SignDescriptor) GetSignMethod() SignMethod { } type SignReq struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The raw bytes of the transaction to be signed. RawTxBytes []byte `protobuf:"bytes,1,opt,name=raw_tx_bytes,json=rawTxBytes,proto3" json:"raw_tx_bytes,omitempty"` // A set of sign descriptors, for each input to be signed. SignDescs []*SignDescriptor `protobuf:"bytes,2,rep,name=sign_descs,json=signDescs,proto3" json:"sign_descs,omitempty"` // The full list of UTXO information for each of the inputs being spent. This // is required when spending one or more taproot (SegWit v1) outputs. - PrevOutputs []*TxOut `protobuf:"bytes,3,rep,name=prev_outputs,json=prevOutputs,proto3" json:"prev_outputs,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + PrevOutputs []*TxOut `protobuf:"bytes,3,rep,name=prev_outputs,json=prevOutputs,proto3" json:"prev_outputs,omitempty"` } func (x *SignReq) Reset() { *x = SignReq{} - mi := &file_signrpc_signer_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SignReq) String() string { @@ -479,7 +493,7 @@ func (*SignReq) ProtoMessage() {} func (x *SignReq) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[4] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -516,19 +530,22 @@ func (x *SignReq) GetPrevOutputs() []*TxOut { } type SignResp struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // A set of signatures realized in a fixed 64-byte format ordered in ascending // input order. - RawSigs [][]byte `protobuf:"bytes,1,rep,name=raw_sigs,json=rawSigs,proto3" json:"raw_sigs,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + RawSigs [][]byte `protobuf:"bytes,1,rep,name=raw_sigs,json=rawSigs,proto3" json:"raw_sigs,omitempty"` } func (x *SignResp) Reset() { *x = SignResp{} - mi := &file_signrpc_signer_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SignResp) String() string { @@ -539,7 +556,7 @@ func (*SignResp) ProtoMessage() {} func (x *SignResp) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[5] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -562,21 +579,24 @@ func (x *SignResp) GetRawSigs() [][]byte { } type InputScript struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The serializes witness stack for the specified input. Witness [][]byte `protobuf:"bytes,1,rep,name=witness,proto3" json:"witness,omitempty"` // The optional sig script for the specified witness that will only be set if // the input specified is a nested p2sh witness program. - SigScript []byte `protobuf:"bytes,2,opt,name=sig_script,json=sigScript,proto3" json:"sig_script,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + SigScript []byte `protobuf:"bytes,2,opt,name=sig_script,json=sigScript,proto3" json:"sig_script,omitempty"` } func (x *InputScript) Reset() { *x = InputScript{} - mi := &file_signrpc_signer_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *InputScript) String() string { @@ -587,7 +607,7 @@ func (*InputScript) ProtoMessage() {} func (x *InputScript) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[6] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -617,18 +637,21 @@ func (x *InputScript) GetSigScript() []byte { } type InputScriptResp struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The set of fully valid input scripts requested. - InputScripts []*InputScript `protobuf:"bytes,1,rep,name=input_scripts,json=inputScripts,proto3" json:"input_scripts,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The set of fully valid input scripts requested. + InputScripts []*InputScript `protobuf:"bytes,1,rep,name=input_scripts,json=inputScripts,proto3" json:"input_scripts,omitempty"` } func (x *InputScriptResp) Reset() { *x = InputScriptResp{} - mi := &file_signrpc_signer_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *InputScriptResp) String() string { @@ -639,7 +662,7 @@ func (*InputScriptResp) ProtoMessage() {} func (x *InputScriptResp) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[7] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -662,7 +685,10 @@ func (x *InputScriptResp) GetInputScripts() []*InputScript { } type SignMessageReq struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The message to be signed. When using REST, this field must be encoded as // base64. Msg []byte `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` @@ -681,16 +707,16 @@ type SignMessageReq struct { SchnorrSigTapTweak []byte `protobuf:"bytes,6,opt,name=schnorr_sig_tap_tweak,json=schnorrSigTapTweak,proto3" json:"schnorr_sig_tap_tweak,omitempty"` // An optional tag that can be provided when taking a tagged hash of a // message. This option can only be used when schnorr_sig is true. - Tag []byte `protobuf:"bytes,7,opt,name=tag,proto3" json:"tag,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Tag []byte `protobuf:"bytes,7,opt,name=tag,proto3" json:"tag,omitempty"` } func (x *SignMessageReq) Reset() { *x = SignMessageReq{} - mi := &file_signrpc_signer_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SignMessageReq) String() string { @@ -701,7 +727,7 @@ func (*SignMessageReq) ProtoMessage() {} func (x *SignMessageReq) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[8] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -766,18 +792,21 @@ func (x *SignMessageReq) GetTag() []byte { } type SignMessageResp struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The signature for the given message in the fixed-size LN wire format. - Signature []byte `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The signature for the given message in the fixed-size LN wire format. + Signature []byte `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` } func (x *SignMessageResp) Reset() { *x = SignMessageResp{} - mi := &file_signrpc_signer_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SignMessageResp) String() string { @@ -788,7 +817,7 @@ func (*SignMessageResp) ProtoMessage() {} func (x *SignMessageResp) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[9] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -811,7 +840,10 @@ func (x *SignMessageResp) GetSignature() []byte { } type VerifyMessageReq struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The message over which the signature is to be verified. When using // REST, this field must be encoded as base64. Msg []byte `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` @@ -827,16 +859,16 @@ type VerifyMessageReq struct { IsSchnorrSig bool `protobuf:"varint,4,opt,name=is_schnorr_sig,json=isSchnorrSig,proto3" json:"is_schnorr_sig,omitempty"` // An optional tag that can be provided when taking a tagged hash of a // message. This option can only be used when is_schnorr_sig is true. - Tag []byte `protobuf:"bytes,5,opt,name=tag,proto3" json:"tag,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Tag []byte `protobuf:"bytes,5,opt,name=tag,proto3" json:"tag,omitempty"` } func (x *VerifyMessageReq) Reset() { *x = VerifyMessageReq{} - mi := &file_signrpc_signer_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *VerifyMessageReq) String() string { @@ -847,7 +879,7 @@ func (*VerifyMessageReq) ProtoMessage() {} func (x *VerifyMessageReq) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[10] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -898,18 +930,21 @@ func (x *VerifyMessageReq) GetTag() []byte { } type VerifyMessageResp struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Whether the signature was valid over the given message. - Valid bool `protobuf:"varint,1,opt,name=valid,proto3" json:"valid,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Whether the signature was valid over the given message. + Valid bool `protobuf:"varint,1,opt,name=valid,proto3" json:"valid,omitempty"` } func (x *VerifyMessageResp) Reset() { *x = VerifyMessageResp{} - mi := &file_signrpc_signer_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *VerifyMessageResp) String() string { @@ -920,7 +955,7 @@ func (*VerifyMessageResp) ProtoMessage() {} func (x *VerifyMessageResp) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[11] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -943,7 +978,10 @@ func (x *VerifyMessageResp) GetValid() bool { } type SharedKeyRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The ephemeral public key to use for the DH key derivation. EphemeralPubkey []byte `protobuf:"bytes,1,opt,name=ephemeral_pubkey,json=ephemeralPubkey,proto3" json:"ephemeral_pubkey,omitempty"` // Deprecated. The optional key locator of the local key that should be used. @@ -955,16 +993,16 @@ type SharedKeyRequest struct { // A key descriptor describes the key used for performing ECDH. Either a key // locator or a raw public key is expected, if neither is supplied, defaults to // the node's identity private key. - KeyDesc *KeyDescriptor `protobuf:"bytes,3,opt,name=key_desc,json=keyDesc,proto3" json:"key_desc,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + KeyDesc *KeyDescriptor `protobuf:"bytes,3,opt,name=key_desc,json=keyDesc,proto3" json:"key_desc,omitempty"` } func (x *SharedKeyRequest) Reset() { *x = SharedKeyRequest{} - mi := &file_signrpc_signer_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SharedKeyRequest) String() string { @@ -975,7 +1013,7 @@ func (*SharedKeyRequest) ProtoMessage() {} func (x *SharedKeyRequest) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[12] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1013,18 +1051,21 @@ func (x *SharedKeyRequest) GetKeyDesc() *KeyDescriptor { } type SharedKeyResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The shared public key, hashed with sha256. - SharedKey []byte `protobuf:"bytes,1,opt,name=shared_key,json=sharedKey,proto3" json:"shared_key,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The shared public key, hashed with sha256. + SharedKey []byte `protobuf:"bytes,1,opt,name=shared_key,json=sharedKey,proto3" json:"shared_key,omitempty"` } func (x *SharedKeyResponse) Reset() { *x = SharedKeyResponse{} - mi := &file_signrpc_signer_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SharedKeyResponse) String() string { @@ -1035,7 +1076,7 @@ func (*SharedKeyResponse) ProtoMessage() {} func (x *SharedKeyResponse) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[13] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1058,22 +1099,25 @@ func (x *SharedKeyResponse) GetSharedKey() []byte { } type TweakDesc struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Tweak is the 32-byte value that will modify the public key. Tweak []byte `protobuf:"bytes,1,opt,name=tweak,proto3" json:"tweak,omitempty"` // Specifies if the target key should be converted to an x-only public key // before tweaking. If true, then the public key will be mapped to an x-only // key before the tweaking operation is applied. - IsXOnly bool `protobuf:"varint,2,opt,name=is_x_only,json=isXOnly,proto3" json:"is_x_only,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + IsXOnly bool `protobuf:"varint,2,opt,name=is_x_only,json=isXOnly,proto3" json:"is_x_only,omitempty"` } func (x *TweakDesc) Reset() { *x = TweakDesc{} - mi := &file_signrpc_signer_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *TweakDesc) String() string { @@ -1084,7 +1128,7 @@ func (*TweakDesc) ProtoMessage() {} func (x *TweakDesc) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[14] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1114,7 +1158,10 @@ func (x *TweakDesc) GetIsXOnly() bool { } type TaprootTweakDesc struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The root hash of the tapscript tree if a script path is committed to. If // the MuSig2 key put on chain doesn't also commit to a script path (BIP-0086 // key spend only), then this needs to be empty and the key_spend_only field @@ -1125,16 +1172,16 @@ type TaprootTweakDesc struct { // Indicates that the above script_root is expected to be empty because this // is a BIP-0086 key spend only commitment where only the internal key is // committed to instead of also including a script root hash. - KeySpendOnly bool `protobuf:"varint,2,opt,name=key_spend_only,json=keySpendOnly,proto3" json:"key_spend_only,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + KeySpendOnly bool `protobuf:"varint,2,opt,name=key_spend_only,json=keySpendOnly,proto3" json:"key_spend_only,omitempty"` } func (x *TaprootTweakDesc) Reset() { *x = TaprootTweakDesc{} - mi := &file_signrpc_signer_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *TaprootTweakDesc) String() string { @@ -1145,7 +1192,7 @@ func (*TaprootTweakDesc) ProtoMessage() {} func (x *TaprootTweakDesc) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[15] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1175,7 +1222,10 @@ func (x *TaprootTweakDesc) GetKeySpendOnly() bool { } type MuSig2CombineKeysRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // A list of all public keys (serialized in 32-byte x-only format for v0.4.0 // and 33-byte compressed format for v1.0.0rc2!) participating in the signing // session. The list will always be sorted lexicographically internally. This @@ -1192,16 +1242,16 @@ type MuSig2CombineKeysRequest struct { // differentiate between the changes that were made to the BIP while this // experimental RPC was already released. Some of those changes affect how the // combined key and nonces are created. - Version MuSig2Version `protobuf:"varint,4,opt,name=version,proto3,enum=signrpc.MuSig2Version" json:"version,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Version MuSig2Version `protobuf:"varint,4,opt,name=version,proto3,enum=signrpc.MuSig2Version" json:"version,omitempty"` } func (x *MuSig2CombineKeysRequest) Reset() { *x = MuSig2CombineKeysRequest{} - mi := &file_signrpc_signer_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *MuSig2CombineKeysRequest) String() string { @@ -1212,7 +1262,7 @@ func (*MuSig2CombineKeysRequest) ProtoMessage() {} func (x *MuSig2CombineKeysRequest) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[16] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1256,7 +1306,10 @@ func (x *MuSig2CombineKeysRequest) GetVersion() MuSig2Version { } type MuSig2CombineKeysResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The combined public key (in the 32-byte x-only format) with all tweaks // applied to it. If a taproot tweak is specified, this corresponds to the // taproot key that can be put into the on-chain output. @@ -1267,16 +1320,16 @@ type MuSig2CombineKeysResponse struct { // is used. TaprootInternalKey []byte `protobuf:"bytes,2,opt,name=taproot_internal_key,json=taprootInternalKey,proto3" json:"taproot_internal_key,omitempty"` // The version of the MuSig2 BIP that was used to combine the keys. - Version MuSig2Version `protobuf:"varint,4,opt,name=version,proto3,enum=signrpc.MuSig2Version" json:"version,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Version MuSig2Version `protobuf:"varint,4,opt,name=version,proto3,enum=signrpc.MuSig2Version" json:"version,omitempty"` } func (x *MuSig2CombineKeysResponse) Reset() { *x = MuSig2CombineKeysResponse{} - mi := &file_signrpc_signer_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *MuSig2CombineKeysResponse) String() string { @@ -1287,7 +1340,7 @@ func (*MuSig2CombineKeysResponse) ProtoMessage() {} func (x *MuSig2CombineKeysResponse) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[17] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1324,7 +1377,10 @@ func (x *MuSig2CombineKeysResponse) GetVersion() MuSig2Version { } type MuSig2SessionRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The key locator that identifies which key to use for signing. KeyLoc *KeyLocator `protobuf:"bytes,1,opt,name=key_loc,json=keyLoc,proto3" json:"key_loc,omitempty"` // A list of all public keys (serialized in 32-byte x-only format for v0.4.0 @@ -1354,15 +1410,15 @@ type MuSig2SessionRequest struct { // values and local public key used for signing as specified in the key_loc // field. PregeneratedLocalNonce []byte `protobuf:"bytes,7,opt,name=pregenerated_local_nonce,json=pregeneratedLocalNonce,proto3" json:"pregenerated_local_nonce,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *MuSig2SessionRequest) Reset() { *x = MuSig2SessionRequest{} - mi := &file_signrpc_signer_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *MuSig2SessionRequest) String() string { @@ -1373,7 +1429,7 @@ func (*MuSig2SessionRequest) ProtoMessage() {} func (x *MuSig2SessionRequest) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[18] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1438,7 +1494,10 @@ func (x *MuSig2SessionRequest) GetPregeneratedLocalNonce() []byte { } type MuSig2SessionResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The unique ID that represents this signing session. A session can be used // for producing a signature a single time. If the signing fails for any // reason, a new session with the same participants needs to be created. @@ -1460,16 +1519,16 @@ type MuSig2SessionResponse struct { // now. HaveAllNonces bool `protobuf:"varint,5,opt,name=have_all_nonces,json=haveAllNonces,proto3" json:"have_all_nonces,omitempty"` // The version of the MuSig2 BIP that was used to create the session. - Version MuSig2Version `protobuf:"varint,6,opt,name=version,proto3,enum=signrpc.MuSig2Version" json:"version,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Version MuSig2Version `protobuf:"varint,6,opt,name=version,proto3,enum=signrpc.MuSig2Version" json:"version,omitempty"` } func (x *MuSig2SessionResponse) Reset() { *x = MuSig2SessionResponse{} - mi := &file_signrpc_signer_proto_msgTypes[19] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *MuSig2SessionResponse) String() string { @@ -1480,7 +1539,7 @@ func (*MuSig2SessionResponse) ProtoMessage() {} func (x *MuSig2SessionResponse) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[19] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1538,21 +1597,24 @@ func (x *MuSig2SessionResponse) GetVersion() MuSig2Version { } type MuSig2RegisterNoncesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The unique ID of the signing session those nonces should be registered with. SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` // A list of all public nonces of other signing participants that should be // registered. OtherSignerPublicNonces [][]byte `protobuf:"bytes,3,rep,name=other_signer_public_nonces,json=otherSignerPublicNonces,proto3" json:"other_signer_public_nonces,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *MuSig2RegisterNoncesRequest) Reset() { *x = MuSig2RegisterNoncesRequest{} - mi := &file_signrpc_signer_proto_msgTypes[20] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *MuSig2RegisterNoncesRequest) String() string { @@ -1563,7 +1625,7 @@ func (*MuSig2RegisterNoncesRequest) ProtoMessage() {} func (x *MuSig2RegisterNoncesRequest) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[20] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1593,19 +1655,22 @@ func (x *MuSig2RegisterNoncesRequest) GetOtherSignerPublicNonces() [][]byte { } type MuSig2RegisterNoncesResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Indicates whether all nonces required to start the signing process are known // now. HaveAllNonces bool `protobuf:"varint,1,opt,name=have_all_nonces,json=haveAllNonces,proto3" json:"have_all_nonces,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *MuSig2RegisterNoncesResponse) Reset() { *x = MuSig2RegisterNoncesResponse{} - mi := &file_signrpc_signer_proto_msgTypes[21] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *MuSig2RegisterNoncesResponse) String() string { @@ -1616,7 +1681,7 @@ func (*MuSig2RegisterNoncesResponse) ProtoMessage() {} func (x *MuSig2RegisterNoncesResponse) ProtoReflect() protoreflect.Message { mi := &file_signrpc_signer_proto_msgTypes[21] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1638,191 +1703,11 @@ func (x *MuSig2RegisterNoncesResponse) GetHaveAllNonces() bool { return false } -type MuSig2RegisterCombinedNonceRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The unique ID of the signing session the combined nonce should be registered - // with. - SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` - // The 66-byte combined public nonce that was aggregated externally. This is a - // concatenation of two 33-byte compressed public keys (R1 || R2). - CombinedPublicNonce []byte `protobuf:"bytes,2,opt,name=combined_public_nonce,json=combinedPublicNonce,proto3" json:"combined_public_nonce,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *MuSig2RegisterCombinedNonceRequest) Reset() { - *x = MuSig2RegisterCombinedNonceRequest{} - mi := &file_signrpc_signer_proto_msgTypes[22] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *MuSig2RegisterCombinedNonceRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*MuSig2RegisterCombinedNonceRequest) ProtoMessage() {} - -func (x *MuSig2RegisterCombinedNonceRequest) ProtoReflect() protoreflect.Message { - mi := &file_signrpc_signer_proto_msgTypes[22] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use MuSig2RegisterCombinedNonceRequest.ProtoReflect.Descriptor instead. -func (*MuSig2RegisterCombinedNonceRequest) Descriptor() ([]byte, []int) { - return file_signrpc_signer_proto_rawDescGZIP(), []int{22} -} - -func (x *MuSig2RegisterCombinedNonceRequest) GetSessionId() []byte { - if x != nil { - return x.SessionId - } - return nil -} - -func (x *MuSig2RegisterCombinedNonceRequest) GetCombinedPublicNonce() []byte { - if x != nil { - return x.CombinedPublicNonce - } - return nil -} - -type MuSig2RegisterCombinedNonceResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *MuSig2RegisterCombinedNonceResponse) Reset() { - *x = MuSig2RegisterCombinedNonceResponse{} - mi := &file_signrpc_signer_proto_msgTypes[23] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *MuSig2RegisterCombinedNonceResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*MuSig2RegisterCombinedNonceResponse) ProtoMessage() {} - -func (x *MuSig2RegisterCombinedNonceResponse) ProtoReflect() protoreflect.Message { - mi := &file_signrpc_signer_proto_msgTypes[23] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use MuSig2RegisterCombinedNonceResponse.ProtoReflect.Descriptor instead. -func (*MuSig2RegisterCombinedNonceResponse) Descriptor() ([]byte, []int) { - return file_signrpc_signer_proto_rawDescGZIP(), []int{23} -} - -type MuSig2GetCombinedNonceRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The unique ID of the signing session to get the combined nonce for. - SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *MuSig2GetCombinedNonceRequest) Reset() { - *x = MuSig2GetCombinedNonceRequest{} - mi := &file_signrpc_signer_proto_msgTypes[24] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *MuSig2GetCombinedNonceRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*MuSig2GetCombinedNonceRequest) ProtoMessage() {} - -func (x *MuSig2GetCombinedNonceRequest) ProtoReflect() protoreflect.Message { - mi := &file_signrpc_signer_proto_msgTypes[24] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use MuSig2GetCombinedNonceRequest.ProtoReflect.Descriptor instead. -func (*MuSig2GetCombinedNonceRequest) Descriptor() ([]byte, []int) { - return file_signrpc_signer_proto_rawDescGZIP(), []int{24} -} - -func (x *MuSig2GetCombinedNonceRequest) GetSessionId() []byte { - if x != nil { - return x.SessionId - } - return nil -} - -type MuSig2GetCombinedNonceResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The 66-byte combined public nonce. This is a concatenation of two 33-byte - // compressed public keys (R1 || R2). - CombinedPublicNonce []byte `protobuf:"bytes,1,opt,name=combined_public_nonce,json=combinedPublicNonce,proto3" json:"combined_public_nonce,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *MuSig2GetCombinedNonceResponse) Reset() { - *x = MuSig2GetCombinedNonceResponse{} - mi := &file_signrpc_signer_proto_msgTypes[25] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *MuSig2GetCombinedNonceResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*MuSig2GetCombinedNonceResponse) ProtoMessage() {} - -func (x *MuSig2GetCombinedNonceResponse) ProtoReflect() protoreflect.Message { - mi := &file_signrpc_signer_proto_msgTypes[25] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use MuSig2GetCombinedNonceResponse.ProtoReflect.Descriptor instead. -func (*MuSig2GetCombinedNonceResponse) Descriptor() ([]byte, []int) { - return file_signrpc_signer_proto_rawDescGZIP(), []int{25} -} - -func (x *MuSig2GetCombinedNonceResponse) GetCombinedPublicNonce() []byte { - if x != nil { - return x.CombinedPublicNonce - } - return nil -} - type MuSig2SignRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The unique ID of the signing session to use for signing. SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` // The 32-byte SHA256 digest of the message to sign. @@ -1830,16 +1715,16 @@ type MuSig2SignRequest struct { // Cleanup indicates that after signing, the session state can be cleaned up, // since another participant is going to be responsible for combining the // partial signatures. - Cleanup bool `protobuf:"varint,3,opt,name=cleanup,proto3" json:"cleanup,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Cleanup bool `protobuf:"varint,3,opt,name=cleanup,proto3" json:"cleanup,omitempty"` } func (x *MuSig2SignRequest) Reset() { *x = MuSig2SignRequest{} - mi := &file_signrpc_signer_proto_msgTypes[26] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *MuSig2SignRequest) String() string { @@ -1849,8 +1734,8 @@ func (x *MuSig2SignRequest) String() string { func (*MuSig2SignRequest) ProtoMessage() {} func (x *MuSig2SignRequest) ProtoReflect() protoreflect.Message { - mi := &file_signrpc_signer_proto_msgTypes[26] - if x != nil { + mi := &file_signrpc_signer_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1862,7 +1747,7 @@ func (x *MuSig2SignRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MuSig2SignRequest.ProtoReflect.Descriptor instead. func (*MuSig2SignRequest) Descriptor() ([]byte, []int) { - return file_signrpc_signer_proto_rawDescGZIP(), []int{26} + return file_signrpc_signer_proto_rawDescGZIP(), []int{22} } func (x *MuSig2SignRequest) GetSessionId() []byte { @@ -1887,18 +1772,21 @@ func (x *MuSig2SignRequest) GetCleanup() bool { } type MuSig2SignResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The partial signature created by the local signer. LocalPartialSignature []byte `protobuf:"bytes,1,opt,name=local_partial_signature,json=localPartialSignature,proto3" json:"local_partial_signature,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *MuSig2SignResponse) Reset() { *x = MuSig2SignResponse{} - mi := &file_signrpc_signer_proto_msgTypes[27] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *MuSig2SignResponse) String() string { @@ -1908,8 +1796,8 @@ func (x *MuSig2SignResponse) String() string { func (*MuSig2SignResponse) ProtoMessage() {} func (x *MuSig2SignResponse) ProtoReflect() protoreflect.Message { - mi := &file_signrpc_signer_proto_msgTypes[27] - if x != nil { + mi := &file_signrpc_signer_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1921,7 +1809,7 @@ func (x *MuSig2SignResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MuSig2SignResponse.ProtoReflect.Descriptor instead. func (*MuSig2SignResponse) Descriptor() ([]byte, []int) { - return file_signrpc_signer_proto_rawDescGZIP(), []int{27} + return file_signrpc_signer_proto_rawDescGZIP(), []int{23} } func (x *MuSig2SignResponse) GetLocalPartialSignature() []byte { @@ -1932,21 +1820,24 @@ func (x *MuSig2SignResponse) GetLocalPartialSignature() []byte { } type MuSig2CombineSigRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The unique ID of the signing session to combine the signatures for. SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` // The list of all other participants' partial signatures to add to the current // session. OtherPartialSignatures [][]byte `protobuf:"bytes,2,rep,name=other_partial_signatures,json=otherPartialSignatures,proto3" json:"other_partial_signatures,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *MuSig2CombineSigRequest) Reset() { *x = MuSig2CombineSigRequest{} - mi := &file_signrpc_signer_proto_msgTypes[28] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *MuSig2CombineSigRequest) String() string { @@ -1956,8 +1847,8 @@ func (x *MuSig2CombineSigRequest) String() string { func (*MuSig2CombineSigRequest) ProtoMessage() {} func (x *MuSig2CombineSigRequest) ProtoReflect() protoreflect.Message { - mi := &file_signrpc_signer_proto_msgTypes[28] - if x != nil { + mi := &file_signrpc_signer_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1969,7 +1860,7 @@ func (x *MuSig2CombineSigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MuSig2CombineSigRequest.ProtoReflect.Descriptor instead. func (*MuSig2CombineSigRequest) Descriptor() ([]byte, []int) { - return file_signrpc_signer_proto_rawDescGZIP(), []int{28} + return file_signrpc_signer_proto_rawDescGZIP(), []int{24} } func (x *MuSig2CombineSigRequest) GetSessionId() []byte { @@ -1987,22 +1878,25 @@ func (x *MuSig2CombineSigRequest) GetOtherPartialSignatures() [][]byte { } type MuSig2CombineSigResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Indicates whether all partial signatures required to create a final, full // signature are known yet. If this is true, then the final_signature field is // set, otherwise it is empty. HaveAllSignatures bool `protobuf:"varint,1,opt,name=have_all_signatures,json=haveAllSignatures,proto3" json:"have_all_signatures,omitempty"` // The final, full signature that is valid for the combined public key. FinalSignature []byte `protobuf:"bytes,2,opt,name=final_signature,json=finalSignature,proto3" json:"final_signature,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *MuSig2CombineSigResponse) Reset() { *x = MuSig2CombineSigResponse{} - mi := &file_signrpc_signer_proto_msgTypes[29] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *MuSig2CombineSigResponse) String() string { @@ -2012,8 +1906,8 @@ func (x *MuSig2CombineSigResponse) String() string { func (*MuSig2CombineSigResponse) ProtoMessage() {} func (x *MuSig2CombineSigResponse) ProtoReflect() protoreflect.Message { - mi := &file_signrpc_signer_proto_msgTypes[29] - if x != nil { + mi := &file_signrpc_signer_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2025,7 +1919,7 @@ func (x *MuSig2CombineSigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MuSig2CombineSigResponse.ProtoReflect.Descriptor instead. func (*MuSig2CombineSigResponse) Descriptor() ([]byte, []int) { - return file_signrpc_signer_proto_rawDescGZIP(), []int{29} + return file_signrpc_signer_proto_rawDescGZIP(), []int{25} } func (x *MuSig2CombineSigResponse) GetHaveAllSignatures() bool { @@ -2043,18 +1937,21 @@ func (x *MuSig2CombineSigResponse) GetFinalSignature() []byte { } type MuSig2CleanupRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The unique ID of the signing session that should be removed/cleaned up. - SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The unique ID of the signing session that should be removed/cleaned up. + SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` } func (x *MuSig2CleanupRequest) Reset() { *x = MuSig2CleanupRequest{} - mi := &file_signrpc_signer_proto_msgTypes[30] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *MuSig2CleanupRequest) String() string { @@ -2064,8 +1961,8 @@ func (x *MuSig2CleanupRequest) String() string { func (*MuSig2CleanupRequest) ProtoMessage() {} func (x *MuSig2CleanupRequest) ProtoReflect() protoreflect.Message { - mi := &file_signrpc_signer_proto_msgTypes[30] - if x != nil { + mi := &file_signrpc_signer_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2077,7 +1974,7 @@ func (x *MuSig2CleanupRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MuSig2CleanupRequest.ProtoReflect.Descriptor instead. func (*MuSig2CleanupRequest) Descriptor() ([]byte, []int) { - return file_signrpc_signer_proto_rawDescGZIP(), []int{30} + return file_signrpc_signer_proto_rawDescGZIP(), []int{26} } func (x *MuSig2CleanupRequest) GetSessionId() []byte { @@ -2088,16 +1985,18 @@ func (x *MuSig2CleanupRequest) GetSessionId() []byte { } type MuSig2CleanupResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *MuSig2CleanupResponse) Reset() { *x = MuSig2CleanupResponse{} - mi := &file_signrpc_signer_proto_msgTypes[31] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_signrpc_signer_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *MuSig2CleanupResponse) String() string { @@ -2107,8 +2006,8 @@ func (x *MuSig2CleanupResponse) String() string { func (*MuSig2CleanupResponse) ProtoMessage() {} func (x *MuSig2CleanupResponse) ProtoReflect() protoreflect.Message { - mi := &file_signrpc_signer_proto_msgTypes[31] - if x != nil { + mi := &file_signrpc_signer_proto_msgTypes[27] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2120,221 +2019,358 @@ func (x *MuSig2CleanupResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MuSig2CleanupResponse.ProtoReflect.Descriptor instead. func (*MuSig2CleanupResponse) Descriptor() ([]byte, []int) { - return file_signrpc_signer_proto_rawDescGZIP(), []int{31} + return file_signrpc_signer_proto_rawDescGZIP(), []int{27} } var File_signrpc_signer_proto protoreflect.FileDescriptor -const file_signrpc_signer_proto_rawDesc = "" + - "\n" + - "\x14signrpc/signer.proto\x12\asignrpc\"H\n" + - "\n" + - "KeyLocator\x12\x1d\n" + - "\n" + - "key_family\x18\x01 \x01(\x05R\tkeyFamily\x12\x1b\n" + - "\tkey_index\x18\x02 \x01(\x05R\bkeyIndex\"a\n" + - "\rKeyDescriptor\x12\"\n" + - "\rraw_key_bytes\x18\x01 \x01(\fR\vrawKeyBytes\x12,\n" + - "\akey_loc\x18\x02 \x01(\v2\x13.signrpc.KeyLocatorR\x06keyLoc\":\n" + - "\x05TxOut\x12\x14\n" + - "\x05value\x18\x01 \x01(\x03R\x05value\x12\x1b\n" + - "\tpk_script\x18\x02 \x01(\fR\bpkScript\"\xe6\x02\n" + - "\x0eSignDescriptor\x121\n" + - "\bkey_desc\x18\x01 \x01(\v2\x16.signrpc.KeyDescriptorR\akeyDesc\x12!\n" + - "\fsingle_tweak\x18\x02 \x01(\fR\vsingleTweak\x12!\n" + - "\fdouble_tweak\x18\x03 \x01(\fR\vdoubleTweak\x12\x1b\n" + - "\ttap_tweak\x18\n" + - " \x01(\fR\btapTweak\x12%\n" + - "\x0ewitness_script\x18\x04 \x01(\fR\rwitnessScript\x12&\n" + - "\x06output\x18\x05 \x01(\v2\x0e.signrpc.TxOutR\x06output\x12\x18\n" + - "\asighash\x18\a \x01(\rR\asighash\x12\x1f\n" + - "\vinput_index\x18\b \x01(\x05R\n" + - "inputIndex\x124\n" + - "\vsign_method\x18\t \x01(\x0e2\x13.signrpc.SignMethodR\n" + - "signMethod\"\x96\x01\n" + - "\aSignReq\x12 \n" + - "\fraw_tx_bytes\x18\x01 \x01(\fR\n" + - "rawTxBytes\x126\n" + - "\n" + - "sign_descs\x18\x02 \x03(\v2\x17.signrpc.SignDescriptorR\tsignDescs\x121\n" + - "\fprev_outputs\x18\x03 \x03(\v2\x0e.signrpc.TxOutR\vprevOutputs\"%\n" + - "\bSignResp\x12\x19\n" + - "\braw_sigs\x18\x01 \x03(\fR\arawSigs\"F\n" + - "\vInputScript\x12\x18\n" + - "\awitness\x18\x01 \x03(\fR\awitness\x12\x1d\n" + - "\n" + - "sig_script\x18\x02 \x01(\fR\tsigScript\"L\n" + - "\x0fInputScriptResp\x129\n" + - "\rinput_scripts\x18\x01 \x03(\v2\x14.signrpc.InputScriptR\finputScripts\"\xf8\x01\n" + - "\x0eSignMessageReq\x12\x10\n" + - "\x03msg\x18\x01 \x01(\fR\x03msg\x12,\n" + - "\akey_loc\x18\x02 \x01(\v2\x13.signrpc.KeyLocatorR\x06keyLoc\x12\x1f\n" + - "\vdouble_hash\x18\x03 \x01(\bR\n" + - "doubleHash\x12\x1f\n" + - "\vcompact_sig\x18\x04 \x01(\bR\n" + - "compactSig\x12\x1f\n" + - "\vschnorr_sig\x18\x05 \x01(\bR\n" + - "schnorrSig\x121\n" + - "\x15schnorr_sig_tap_tweak\x18\x06 \x01(\fR\x12schnorrSigTapTweak\x12\x10\n" + - "\x03tag\x18\a \x01(\fR\x03tag\"/\n" + - "\x0fSignMessageResp\x12\x1c\n" + - "\tsignature\x18\x01 \x01(\fR\tsignature\"\x92\x01\n" + - "\x10VerifyMessageReq\x12\x10\n" + - "\x03msg\x18\x01 \x01(\fR\x03msg\x12\x1c\n" + - "\tsignature\x18\x02 \x01(\fR\tsignature\x12\x16\n" + - "\x06pubkey\x18\x03 \x01(\fR\x06pubkey\x12$\n" + - "\x0eis_schnorr_sig\x18\x04 \x01(\bR\fisSchnorrSig\x12\x10\n" + - "\x03tag\x18\x05 \x01(\fR\x03tag\")\n" + - "\x11VerifyMessageResp\x12\x14\n" + - "\x05valid\x18\x01 \x01(\bR\x05valid\"\xa2\x01\n" + - "\x10SharedKeyRequest\x12)\n" + - "\x10ephemeral_pubkey\x18\x01 \x01(\fR\x0fephemeralPubkey\x120\n" + - "\akey_loc\x18\x02 \x01(\v2\x13.signrpc.KeyLocatorB\x02\x18\x01R\x06keyLoc\x121\n" + - "\bkey_desc\x18\x03 \x01(\v2\x16.signrpc.KeyDescriptorR\akeyDesc\"2\n" + - "\x11SharedKeyResponse\x12\x1d\n" + - "\n" + - "shared_key\x18\x01 \x01(\fR\tsharedKey\"=\n" + - "\tTweakDesc\x12\x14\n" + - "\x05tweak\x18\x01 \x01(\fR\x05tweak\x12\x1a\n" + - "\tis_x_only\x18\x02 \x01(\bR\aisXOnly\"Y\n" + - "\x10TaprootTweakDesc\x12\x1f\n" + - "\vscript_root\x18\x01 \x01(\fR\n" + - "scriptRoot\x12$\n" + - "\x0ekey_spend_only\x18\x02 \x01(\bR\fkeySpendOnly\"\xe6\x01\n" + - "\x18MuSig2CombineKeysRequest\x12,\n" + - "\x12all_signer_pubkeys\x18\x01 \x03(\fR\x10allSignerPubkeys\x12*\n" + - "\x06tweaks\x18\x02 \x03(\v2\x12.signrpc.TweakDescR\x06tweaks\x12>\n" + - "\rtaproot_tweak\x18\x03 \x01(\v2\x19.signrpc.TaprootTweakDescR\ftaprootTweak\x120\n" + - "\aversion\x18\x04 \x01(\x0e2\x16.signrpc.MuSig2VersionR\aversion\"\xa2\x01\n" + - "\x19MuSig2CombineKeysResponse\x12!\n" + - "\fcombined_key\x18\x01 \x01(\fR\vcombinedKey\x120\n" + - "\x14taproot_internal_key\x18\x02 \x01(\fR\x12taprootInternalKey\x120\n" + - "\aversion\x18\x04 \x01(\x0e2\x16.signrpc.MuSig2VersionR\aversion\"\x87\x03\n" + - "\x14MuSig2SessionRequest\x12,\n" + - "\akey_loc\x18\x01 \x01(\v2\x13.signrpc.KeyLocatorR\x06keyLoc\x12,\n" + - "\x12all_signer_pubkeys\x18\x02 \x03(\fR\x10allSignerPubkeys\x12;\n" + - "\x1aother_signer_public_nonces\x18\x03 \x03(\fR\x17otherSignerPublicNonces\x12*\n" + - "\x06tweaks\x18\x04 \x03(\v2\x12.signrpc.TweakDescR\x06tweaks\x12>\n" + - "\rtaproot_tweak\x18\x05 \x01(\v2\x19.signrpc.TaprootTweakDescR\ftaprootTweak\x120\n" + - "\aversion\x18\x06 \x01(\x0e2\x16.signrpc.MuSig2VersionR\aversion\x128\n" + - "\x18pregenerated_local_nonce\x18\a \x01(\fR\x16pregeneratedLocalNonce\"\x95\x02\n" + - "\x15MuSig2SessionResponse\x12\x1d\n" + - "\n" + - "session_id\x18\x01 \x01(\fR\tsessionId\x12!\n" + - "\fcombined_key\x18\x02 \x01(\fR\vcombinedKey\x120\n" + - "\x14taproot_internal_key\x18\x03 \x01(\fR\x12taprootInternalKey\x12.\n" + - "\x13local_public_nonces\x18\x04 \x01(\fR\x11localPublicNonces\x12&\n" + - "\x0fhave_all_nonces\x18\x05 \x01(\bR\rhaveAllNonces\x120\n" + - "\aversion\x18\x06 \x01(\x0e2\x16.signrpc.MuSig2VersionR\aversion\"y\n" + - "\x1bMuSig2RegisterNoncesRequest\x12\x1d\n" + - "\n" + - "session_id\x18\x01 \x01(\fR\tsessionId\x12;\n" + - "\x1aother_signer_public_nonces\x18\x03 \x03(\fR\x17otherSignerPublicNonces\"F\n" + - "\x1cMuSig2RegisterNoncesResponse\x12&\n" + - "\x0fhave_all_nonces\x18\x01 \x01(\bR\rhaveAllNonces\"w\n" + - "\"MuSig2RegisterCombinedNonceRequest\x12\x1d\n" + - "\n" + - "session_id\x18\x01 \x01(\fR\tsessionId\x122\n" + - "\x15combined_public_nonce\x18\x02 \x01(\fR\x13combinedPublicNonce\"%\n" + - "#MuSig2RegisterCombinedNonceResponse\">\n" + - "\x1dMuSig2GetCombinedNonceRequest\x12\x1d\n" + - "\n" + - "session_id\x18\x01 \x01(\fR\tsessionId\"T\n" + - "\x1eMuSig2GetCombinedNonceResponse\x122\n" + - "\x15combined_public_nonce\x18\x01 \x01(\fR\x13combinedPublicNonce\"s\n" + - "\x11MuSig2SignRequest\x12\x1d\n" + - "\n" + - "session_id\x18\x01 \x01(\fR\tsessionId\x12%\n" + - "\x0emessage_digest\x18\x02 \x01(\fR\rmessageDigest\x12\x18\n" + - "\acleanup\x18\x03 \x01(\bR\acleanup\"L\n" + - "\x12MuSig2SignResponse\x126\n" + - "\x17local_partial_signature\x18\x01 \x01(\fR\x15localPartialSignature\"r\n" + - "\x17MuSig2CombineSigRequest\x12\x1d\n" + - "\n" + - "session_id\x18\x01 \x01(\fR\tsessionId\x128\n" + - "\x18other_partial_signatures\x18\x02 \x03(\fR\x16otherPartialSignatures\"s\n" + - "\x18MuSig2CombineSigResponse\x12.\n" + - "\x13have_all_signatures\x18\x01 \x01(\bR\x11haveAllSignatures\x12'\n" + - "\x0ffinal_signature\x18\x02 \x01(\fR\x0efinalSignature\"5\n" + - "\x14MuSig2CleanupRequest\x12\x1d\n" + - "\n" + - "session_id\x18\x01 \x01(\fR\tsessionId\"\x17\n" + - "\x15MuSig2CleanupResponse*\x9c\x01\n" + - "\n" + - "SignMethod\x12\x1a\n" + - "\x16SIGN_METHOD_WITNESS_V0\x10\x00\x12)\n" + - "%SIGN_METHOD_TAPROOT_KEY_SPEND_BIP0086\x10\x01\x12!\n" + - "\x1dSIGN_METHOD_TAPROOT_KEY_SPEND\x10\x02\x12$\n" + - " SIGN_METHOD_TAPROOT_SCRIPT_SPEND\x10\x03*b\n" + - "\rMuSig2Version\x12\x1c\n" + - "\x18MUSIG2_VERSION_UNDEFINED\x10\x00\x12\x17\n" + - "\x13MUSIG2_VERSION_V040\x10\x01\x12\x1a\n" + - "\x16MUSIG2_VERSION_V100RC2\x10\x022\xc0\b\n" + - "\x06Signer\x124\n" + - "\rSignOutputRaw\x12\x10.signrpc.SignReq\x1a\x11.signrpc.SignResp\x12@\n" + - "\x12ComputeInputScript\x12\x10.signrpc.SignReq\x1a\x18.signrpc.InputScriptResp\x12@\n" + - "\vSignMessage\x12\x17.signrpc.SignMessageReq\x1a\x18.signrpc.SignMessageResp\x12F\n" + - "\rVerifyMessage\x12\x19.signrpc.VerifyMessageReq\x1a\x1a.signrpc.VerifyMessageResp\x12H\n" + - "\x0fDeriveSharedKey\x12\x19.signrpc.SharedKeyRequest\x1a\x1a.signrpc.SharedKeyResponse\x12Z\n" + - "\x11MuSig2CombineKeys\x12!.signrpc.MuSig2CombineKeysRequest\x1a\".signrpc.MuSig2CombineKeysResponse\x12T\n" + - "\x13MuSig2CreateSession\x12\x1d.signrpc.MuSig2SessionRequest\x1a\x1e.signrpc.MuSig2SessionResponse\x12c\n" + - "\x14MuSig2RegisterNonces\x12$.signrpc.MuSig2RegisterNoncesRequest\x1a%.signrpc.MuSig2RegisterNoncesResponse\x12x\n" + - "\x1bMuSig2RegisterCombinedNonce\x12+.signrpc.MuSig2RegisterCombinedNonceRequest\x1a,.signrpc.MuSig2RegisterCombinedNonceResponse\x12i\n" + - "\x16MuSig2GetCombinedNonce\x12&.signrpc.MuSig2GetCombinedNonceRequest\x1a'.signrpc.MuSig2GetCombinedNonceResponse\x12E\n" + - "\n" + - "MuSig2Sign\x12\x1a.signrpc.MuSig2SignRequest\x1a\x1b.signrpc.MuSig2SignResponse\x12W\n" + - "\x10MuSig2CombineSig\x12 .signrpc.MuSig2CombineSigRequest\x1a!.signrpc.MuSig2CombineSigResponse\x12N\n" + - "\rMuSig2Cleanup\x12\x1d.signrpc.MuSig2CleanupRequest\x1a\x1e.signrpc.MuSig2CleanupResponseB/Z-github.com/lightningnetwork/lnd/lnrpc/signrpcb\x06proto3" +var file_signrpc_signer_proto_rawDesc = []byte{ + 0x0a, 0x14, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x22, + 0x48, 0x0a, 0x0a, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1d, 0x0a, + 0x0a, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x09, 0x6b, 0x65, 0x79, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x1b, 0x0a, 0x09, + 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x08, 0x6b, 0x65, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x61, 0x0a, 0x0d, 0x4b, 0x65, 0x79, + 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x61, + 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0b, 0x72, 0x61, 0x77, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2c, + 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, + 0x61, 0x74, 0x6f, 0x72, 0x52, 0x06, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x22, 0x3a, 0x0a, 0x05, + 0x54, 0x78, 0x4f, 0x75, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, + 0x6b, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, + 0x70, 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0xe6, 0x02, 0x0a, 0x0e, 0x53, 0x69, 0x67, + 0x6e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x31, 0x0a, 0x08, 0x6b, + 0x65, 0x79, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, + 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x6f, 0x72, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x12, 0x21, + 0x0a, 0x0c, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x5f, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x54, 0x77, 0x65, 0x61, + 0x6b, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x77, 0x65, 0x61, + 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x54, + 0x77, 0x65, 0x61, 0x6b, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x61, 0x70, 0x5f, 0x74, 0x77, 0x65, 0x61, + 0x6b, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x74, 0x61, 0x70, 0x54, 0x77, 0x65, 0x61, + 0x6b, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x5f, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x77, 0x69, 0x74, 0x6e, 0x65, + 0x73, 0x73, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x26, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x54, 0x78, 0x4f, 0x75, 0x74, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x12, 0x18, 0x0a, 0x07, 0x73, 0x69, 0x67, 0x68, 0x61, 0x73, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x07, 0x73, 0x69, 0x67, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, + 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x34, 0x0a, 0x0b, 0x73, + 0x69, 0x67, 0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x22, 0x96, 0x01, 0x0a, 0x07, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x12, 0x20, 0x0a, + 0x0c, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x61, 0x77, 0x54, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, + 0x36, 0x0a, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, + 0x67, 0x6e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x52, 0x09, 0x73, 0x69, + 0x67, 0x6e, 0x44, 0x65, 0x73, 0x63, 0x73, 0x12, 0x31, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x76, 0x5f, + 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, + 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x4f, 0x75, 0x74, 0x52, 0x0b, 0x70, + 0x72, 0x65, 0x76, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x22, 0x25, 0x0a, 0x08, 0x53, 0x69, + 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x61, 0x77, 0x5f, 0x73, 0x69, + 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x07, 0x72, 0x61, 0x77, 0x53, 0x69, 0x67, + 0x73, 0x22, 0x46, 0x0a, 0x0b, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x12, 0x18, 0x0a, 0x07, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0c, 0x52, 0x07, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x69, + 0x67, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, + 0x73, 0x69, 0x67, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x4c, 0x0a, 0x0f, 0x49, 0x6e, 0x70, + 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x39, 0x0a, 0x0d, + 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, + 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, + 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x22, 0xf8, 0x01, 0x0a, 0x0e, 0x53, 0x69, 0x67, 0x6e, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, + 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x2c, 0x0a, 0x07, + 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, + 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, + 0x6f, 0x72, 0x52, 0x06, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x6f, + 0x75, 0x62, 0x6c, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0a, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x63, + 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x5f, 0x73, 0x69, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x53, 0x69, 0x67, 0x12, 0x1f, 0x0a, 0x0b, + 0x73, 0x63, 0x68, 0x6e, 0x6f, 0x72, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0a, 0x73, 0x63, 0x68, 0x6e, 0x6f, 0x72, 0x72, 0x53, 0x69, 0x67, 0x12, 0x31, 0x0a, + 0x15, 0x73, 0x63, 0x68, 0x6e, 0x6f, 0x72, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x5f, 0x74, 0x61, 0x70, + 0x5f, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x73, 0x63, + 0x68, 0x6e, 0x6f, 0x72, 0x72, 0x53, 0x69, 0x67, 0x54, 0x61, 0x70, 0x54, 0x77, 0x65, 0x61, 0x6b, + 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x74, + 0x61, 0x67, 0x22, 0x2f, 0x0a, 0x0f, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x22, 0x92, 0x01, 0x0a, 0x10, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, + 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, + 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, + 0x12, 0x24, 0x0a, 0x0e, 0x69, 0x73, 0x5f, 0x73, 0x63, 0x68, 0x6e, 0x6f, 0x72, 0x72, 0x5f, 0x73, + 0x69, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x73, 0x53, 0x63, 0x68, 0x6e, + 0x6f, 0x72, 0x72, 0x53, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0x29, 0x0a, 0x11, 0x56, 0x65, 0x72, 0x69, + 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x69, 0x64, 0x22, 0xa2, 0x01, 0x0a, 0x10, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x65, 0x70, 0x68, 0x65, + 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0f, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x50, 0x75, 0x62, + 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, + 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x42, 0x02, 0x18, 0x01, 0x52, 0x06, 0x6b, + 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x12, 0x31, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x64, 0x65, 0x73, + 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x52, + 0x07, 0x6b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x22, 0x32, 0x0a, 0x11, 0x53, 0x68, 0x61, 0x72, + 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, + 0x0a, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x09, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x22, 0x3d, 0x0a, 0x09, + 0x54, 0x77, 0x65, 0x61, 0x6b, 0x44, 0x65, 0x73, 0x63, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x77, 0x65, + 0x61, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x12, + 0x1a, 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x78, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x58, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x59, 0x0a, 0x10, 0x54, + 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x44, 0x65, 0x73, 0x63, 0x12, + 0x1f, 0x0a, 0x0b, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x6f, 0x6f, 0x74, + 0x12, 0x24, 0x0a, 0x0e, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x6f, 0x6e, + 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6b, 0x65, 0x79, 0x53, 0x70, 0x65, + 0x6e, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0xe6, 0x01, 0x0a, 0x18, 0x4d, 0x75, 0x53, 0x69, 0x67, + 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x65, + 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, + 0x10, 0x61, 0x6c, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, + 0x73, 0x12, 0x2a, 0x0a, 0x06, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x12, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x77, 0x65, 0x61, + 0x6b, 0x44, 0x65, 0x73, 0x63, 0x52, 0x06, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x73, 0x12, 0x3e, 0x0a, + 0x0d, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, + 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x44, 0x65, 0x73, 0x63, 0x52, + 0x0c, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x12, 0x30, 0x0a, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, + 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, + 0xa2, 0x01, 0x0a, 0x19, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, + 0x65, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, + 0x0c, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x64, 0x4b, 0x65, 0x79, + 0x12, 0x30, 0x0a, 0x14, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, + 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, + 0x65, 0x79, 0x12, 0x30, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, + 0x53, 0x69, 0x67, 0x32, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x87, 0x03, 0x0a, 0x14, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, + 0x07, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, + 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, + 0x74, 0x6f, 0x72, 0x52, 0x06, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x12, 0x2c, 0x0a, 0x12, 0x61, + 0x6c, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x10, 0x61, 0x6c, 0x6c, 0x53, 0x69, 0x67, 0x6e, + 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x12, 0x3b, 0x0a, 0x1a, 0x6f, 0x74, 0x68, + 0x65, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x17, 0x6f, + 0x74, 0x68, 0x65, 0x72, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x06, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x73, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x44, 0x65, 0x73, 0x63, 0x52, 0x06, 0x74, 0x77, 0x65, 0x61, + 0x6b, 0x73, 0x12, 0x3e, 0x0a, 0x0d, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x74, 0x77, + 0x65, 0x61, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x69, 0x67, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x54, 0x77, 0x65, 0x61, 0x6b, + 0x44, 0x65, 0x73, 0x63, 0x52, 0x0c, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x54, 0x77, 0x65, + 0x61, 0x6b, 0x12, 0x30, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, + 0x53, 0x69, 0x67, 0x32, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x18, 0x70, 0x72, 0x65, 0x67, 0x65, 0x6e, 0x65, 0x72, + 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x16, 0x70, 0x72, 0x65, 0x67, 0x65, 0x6e, 0x65, 0x72, + 0x61, 0x74, 0x65, 0x64, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x22, 0x95, + 0x02, 0x0a, 0x15, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x62, 0x69, + 0x6e, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, + 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x74, 0x61, + 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, + 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, + 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x13, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6e, 0x6f, 0x6e, + 0x63, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x6c, 0x6f, 0x63, 0x61, 0x6c, + 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, + 0x68, 0x61, 0x76, 0x65, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x68, 0x61, 0x76, 0x65, 0x41, 0x6c, 0x6c, 0x4e, 0x6f, + 0x6e, 0x63, 0x65, 0x73, 0x12, 0x30, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x79, 0x0a, 0x1b, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x1a, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x73, 0x69, + 0x67, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, + 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x17, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x53, + 0x69, 0x67, 0x6e, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4e, 0x6f, 0x6e, 0x63, 0x65, + 0x73, 0x22, 0x46, 0x0a, 0x1c, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x68, 0x61, 0x76, 0x65, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x6e, 0x6f, + 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x68, 0x61, 0x76, 0x65, + 0x41, 0x6c, 0x6c, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x22, 0x73, 0x0a, 0x11, 0x4d, 0x75, 0x53, + 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x25, 0x0a, + 0x0e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x44, 0x69, + 0x67, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x22, 0x4c, + 0x0a, 0x12, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x61, + 0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x61, 0x72, 0x74, + 0x69, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x72, 0x0a, 0x17, + 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53, 0x69, 0x67, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x38, 0x0a, 0x18, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, + 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x16, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x50, + 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, + 0x22, 0x73, 0x0a, 0x18, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, + 0x65, 0x53, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x13, + 0x68, 0x61, 0x76, 0x65, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x68, 0x61, 0x76, 0x65, 0x41, + 0x6c, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x27, 0x0a, 0x0f, + 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x35, 0x0a, 0x14, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, + 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, + 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x17, 0x0a, 0x15, + 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x9c, 0x01, 0x0a, 0x0a, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x49, 0x47, 0x4e, 0x5f, 0x4d, 0x45, 0x54, + 0x48, 0x4f, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x56, 0x30, 0x10, 0x00, + 0x12, 0x29, 0x0a, 0x25, 0x53, 0x49, 0x47, 0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, + 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x53, 0x50, 0x45, 0x4e, + 0x44, 0x5f, 0x42, 0x49, 0x50, 0x30, 0x30, 0x38, 0x36, 0x10, 0x01, 0x12, 0x21, 0x0a, 0x1d, 0x53, + 0x49, 0x47, 0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, + 0x4f, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x24, + 0x0a, 0x20, 0x53, 0x49, 0x47, 0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x54, 0x41, + 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x53, 0x50, 0x45, + 0x4e, 0x44, 0x10, 0x03, 0x2a, 0x62, 0x0a, 0x0d, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x18, 0x4d, 0x55, 0x53, 0x49, 0x47, 0x32, 0x5f, + 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x44, 0x45, 0x46, 0x49, 0x4e, 0x45, + 0x44, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x55, 0x53, 0x49, 0x47, 0x32, 0x5f, 0x56, 0x45, + 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x56, 0x30, 0x34, 0x30, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, + 0x4d, 0x55, 0x53, 0x49, 0x47, 0x32, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x56, + 0x31, 0x30, 0x30, 0x52, 0x43, 0x32, 0x10, 0x02, 0x32, 0xdb, 0x06, 0x0a, 0x06, 0x53, 0x69, 0x67, + 0x6e, 0x65, 0x72, 0x12, 0x34, 0x0a, 0x0d, 0x53, 0x69, 0x67, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x52, 0x61, 0x77, 0x12, 0x10, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x12, 0x40, 0x0a, 0x12, 0x43, 0x6f, 0x6d, + 0x70, 0x75, 0x74, 0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, + 0x10, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, + 0x71, 0x1a, 0x18, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x70, 0x75, + 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x40, 0x0a, 0x0b, 0x53, + 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x17, 0x2e, 0x73, 0x69, 0x67, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, + 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x46, 0x0a, + 0x0d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, + 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x73, 0x69, 0x67, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x48, 0x0a, 0x0f, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x53, + 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, + 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x5a, 0x0a, 0x11, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, + 0x4b, 0x65, 0x79, 0x73, 0x12, 0x21, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, + 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x4b, 0x65, 0x79, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x4b, + 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x13, 0x4d, + 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x1d, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, + 0x69, 0x67, 0x32, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1e, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, + 0x67, 0x32, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x63, 0x0a, 0x14, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x24, 0x2e, 0x73, 0x69, 0x67, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x25, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x0a, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, + 0x53, 0x69, 0x67, 0x6e, 0x12, 0x1a, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, + 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1b, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, + 0x32, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, + 0x10, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53, 0x69, + 0x67, 0x12, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, + 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, + 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53, 0x69, 0x67, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0d, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, + 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x12, 0x1d, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, + 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, + 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} var ( file_signrpc_signer_proto_rawDescOnce sync.Once - file_signrpc_signer_proto_rawDescData []byte + file_signrpc_signer_proto_rawDescData = file_signrpc_signer_proto_rawDesc ) func file_signrpc_signer_proto_rawDescGZIP() []byte { file_signrpc_signer_proto_rawDescOnce.Do(func() { - file_signrpc_signer_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_signrpc_signer_proto_rawDesc), len(file_signrpc_signer_proto_rawDesc))) + file_signrpc_signer_proto_rawDescData = protoimpl.X.CompressGZIP(file_signrpc_signer_proto_rawDescData) }) return file_signrpc_signer_proto_rawDescData } var file_signrpc_signer_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_signrpc_signer_proto_msgTypes = make([]protoimpl.MessageInfo, 32) -var file_signrpc_signer_proto_goTypes = []any{ - (SignMethod)(0), // 0: signrpc.SignMethod - (MuSig2Version)(0), // 1: signrpc.MuSig2Version - (*KeyLocator)(nil), // 2: signrpc.KeyLocator - (*KeyDescriptor)(nil), // 3: signrpc.KeyDescriptor - (*TxOut)(nil), // 4: signrpc.TxOut - (*SignDescriptor)(nil), // 5: signrpc.SignDescriptor - (*SignReq)(nil), // 6: signrpc.SignReq - (*SignResp)(nil), // 7: signrpc.SignResp - (*InputScript)(nil), // 8: signrpc.InputScript - (*InputScriptResp)(nil), // 9: signrpc.InputScriptResp - (*SignMessageReq)(nil), // 10: signrpc.SignMessageReq - (*SignMessageResp)(nil), // 11: signrpc.SignMessageResp - (*VerifyMessageReq)(nil), // 12: signrpc.VerifyMessageReq - (*VerifyMessageResp)(nil), // 13: signrpc.VerifyMessageResp - (*SharedKeyRequest)(nil), // 14: signrpc.SharedKeyRequest - (*SharedKeyResponse)(nil), // 15: signrpc.SharedKeyResponse - (*TweakDesc)(nil), // 16: signrpc.TweakDesc - (*TaprootTweakDesc)(nil), // 17: signrpc.TaprootTweakDesc - (*MuSig2CombineKeysRequest)(nil), // 18: signrpc.MuSig2CombineKeysRequest - (*MuSig2CombineKeysResponse)(nil), // 19: signrpc.MuSig2CombineKeysResponse - (*MuSig2SessionRequest)(nil), // 20: signrpc.MuSig2SessionRequest - (*MuSig2SessionResponse)(nil), // 21: signrpc.MuSig2SessionResponse - (*MuSig2RegisterNoncesRequest)(nil), // 22: signrpc.MuSig2RegisterNoncesRequest - (*MuSig2RegisterNoncesResponse)(nil), // 23: signrpc.MuSig2RegisterNoncesResponse - (*MuSig2RegisterCombinedNonceRequest)(nil), // 24: signrpc.MuSig2RegisterCombinedNonceRequest - (*MuSig2RegisterCombinedNonceResponse)(nil), // 25: signrpc.MuSig2RegisterCombinedNonceResponse - (*MuSig2GetCombinedNonceRequest)(nil), // 26: signrpc.MuSig2GetCombinedNonceRequest - (*MuSig2GetCombinedNonceResponse)(nil), // 27: signrpc.MuSig2GetCombinedNonceResponse - (*MuSig2SignRequest)(nil), // 28: signrpc.MuSig2SignRequest - (*MuSig2SignResponse)(nil), // 29: signrpc.MuSig2SignResponse - (*MuSig2CombineSigRequest)(nil), // 30: signrpc.MuSig2CombineSigRequest - (*MuSig2CombineSigResponse)(nil), // 31: signrpc.MuSig2CombineSigResponse - (*MuSig2CleanupRequest)(nil), // 32: signrpc.MuSig2CleanupRequest - (*MuSig2CleanupResponse)(nil), // 33: signrpc.MuSig2CleanupResponse +var file_signrpc_signer_proto_msgTypes = make([]protoimpl.MessageInfo, 28) +var file_signrpc_signer_proto_goTypes = []interface{}{ + (SignMethod)(0), // 0: signrpc.SignMethod + (MuSig2Version)(0), // 1: signrpc.MuSig2Version + (*KeyLocator)(nil), // 2: signrpc.KeyLocator + (*KeyDescriptor)(nil), // 3: signrpc.KeyDescriptor + (*TxOut)(nil), // 4: signrpc.TxOut + (*SignDescriptor)(nil), // 5: signrpc.SignDescriptor + (*SignReq)(nil), // 6: signrpc.SignReq + (*SignResp)(nil), // 7: signrpc.SignResp + (*InputScript)(nil), // 8: signrpc.InputScript + (*InputScriptResp)(nil), // 9: signrpc.InputScriptResp + (*SignMessageReq)(nil), // 10: signrpc.SignMessageReq + (*SignMessageResp)(nil), // 11: signrpc.SignMessageResp + (*VerifyMessageReq)(nil), // 12: signrpc.VerifyMessageReq + (*VerifyMessageResp)(nil), // 13: signrpc.VerifyMessageResp + (*SharedKeyRequest)(nil), // 14: signrpc.SharedKeyRequest + (*SharedKeyResponse)(nil), // 15: signrpc.SharedKeyResponse + (*TweakDesc)(nil), // 16: signrpc.TweakDesc + (*TaprootTweakDesc)(nil), // 17: signrpc.TaprootTweakDesc + (*MuSig2CombineKeysRequest)(nil), // 18: signrpc.MuSig2CombineKeysRequest + (*MuSig2CombineKeysResponse)(nil), // 19: signrpc.MuSig2CombineKeysResponse + (*MuSig2SessionRequest)(nil), // 20: signrpc.MuSig2SessionRequest + (*MuSig2SessionResponse)(nil), // 21: signrpc.MuSig2SessionResponse + (*MuSig2RegisterNoncesRequest)(nil), // 22: signrpc.MuSig2RegisterNoncesRequest + (*MuSig2RegisterNoncesResponse)(nil), // 23: signrpc.MuSig2RegisterNoncesResponse + (*MuSig2SignRequest)(nil), // 24: signrpc.MuSig2SignRequest + (*MuSig2SignResponse)(nil), // 25: signrpc.MuSig2SignResponse + (*MuSig2CombineSigRequest)(nil), // 26: signrpc.MuSig2CombineSigRequest + (*MuSig2CombineSigResponse)(nil), // 27: signrpc.MuSig2CombineSigResponse + (*MuSig2CleanupRequest)(nil), // 28: signrpc.MuSig2CleanupRequest + (*MuSig2CleanupResponse)(nil), // 29: signrpc.MuSig2CleanupResponse } var file_signrpc_signer_proto_depIdxs = []int32{ 2, // 0: signrpc.KeyDescriptor.key_loc:type_name -> signrpc.KeyLocator @@ -2364,26 +2400,22 @@ var file_signrpc_signer_proto_depIdxs = []int32{ 18, // 24: signrpc.Signer.MuSig2CombineKeys:input_type -> signrpc.MuSig2CombineKeysRequest 20, // 25: signrpc.Signer.MuSig2CreateSession:input_type -> signrpc.MuSig2SessionRequest 22, // 26: signrpc.Signer.MuSig2RegisterNonces:input_type -> signrpc.MuSig2RegisterNoncesRequest - 24, // 27: signrpc.Signer.MuSig2RegisterCombinedNonce:input_type -> signrpc.MuSig2RegisterCombinedNonceRequest - 26, // 28: signrpc.Signer.MuSig2GetCombinedNonce:input_type -> signrpc.MuSig2GetCombinedNonceRequest - 28, // 29: signrpc.Signer.MuSig2Sign:input_type -> signrpc.MuSig2SignRequest - 30, // 30: signrpc.Signer.MuSig2CombineSig:input_type -> signrpc.MuSig2CombineSigRequest - 32, // 31: signrpc.Signer.MuSig2Cleanup:input_type -> signrpc.MuSig2CleanupRequest - 7, // 32: signrpc.Signer.SignOutputRaw:output_type -> signrpc.SignResp - 9, // 33: signrpc.Signer.ComputeInputScript:output_type -> signrpc.InputScriptResp - 11, // 34: signrpc.Signer.SignMessage:output_type -> signrpc.SignMessageResp - 13, // 35: signrpc.Signer.VerifyMessage:output_type -> signrpc.VerifyMessageResp - 15, // 36: signrpc.Signer.DeriveSharedKey:output_type -> signrpc.SharedKeyResponse - 19, // 37: signrpc.Signer.MuSig2CombineKeys:output_type -> signrpc.MuSig2CombineKeysResponse - 21, // 38: signrpc.Signer.MuSig2CreateSession:output_type -> signrpc.MuSig2SessionResponse - 23, // 39: signrpc.Signer.MuSig2RegisterNonces:output_type -> signrpc.MuSig2RegisterNoncesResponse - 25, // 40: signrpc.Signer.MuSig2RegisterCombinedNonce:output_type -> signrpc.MuSig2RegisterCombinedNonceResponse - 27, // 41: signrpc.Signer.MuSig2GetCombinedNonce:output_type -> signrpc.MuSig2GetCombinedNonceResponse - 29, // 42: signrpc.Signer.MuSig2Sign:output_type -> signrpc.MuSig2SignResponse - 31, // 43: signrpc.Signer.MuSig2CombineSig:output_type -> signrpc.MuSig2CombineSigResponse - 33, // 44: signrpc.Signer.MuSig2Cleanup:output_type -> signrpc.MuSig2CleanupResponse - 32, // [32:45] is the sub-list for method output_type - 19, // [19:32] is the sub-list for method input_type + 24, // 27: signrpc.Signer.MuSig2Sign:input_type -> signrpc.MuSig2SignRequest + 26, // 28: signrpc.Signer.MuSig2CombineSig:input_type -> signrpc.MuSig2CombineSigRequest + 28, // 29: signrpc.Signer.MuSig2Cleanup:input_type -> signrpc.MuSig2CleanupRequest + 7, // 30: signrpc.Signer.SignOutputRaw:output_type -> signrpc.SignResp + 9, // 31: signrpc.Signer.ComputeInputScript:output_type -> signrpc.InputScriptResp + 11, // 32: signrpc.Signer.SignMessage:output_type -> signrpc.SignMessageResp + 13, // 33: signrpc.Signer.VerifyMessage:output_type -> signrpc.VerifyMessageResp + 15, // 34: signrpc.Signer.DeriveSharedKey:output_type -> signrpc.SharedKeyResponse + 19, // 35: signrpc.Signer.MuSig2CombineKeys:output_type -> signrpc.MuSig2CombineKeysResponse + 21, // 36: signrpc.Signer.MuSig2CreateSession:output_type -> signrpc.MuSig2SessionResponse + 23, // 37: signrpc.Signer.MuSig2RegisterNonces:output_type -> signrpc.MuSig2RegisterNoncesResponse + 25, // 38: signrpc.Signer.MuSig2Sign:output_type -> signrpc.MuSig2SignResponse + 27, // 39: signrpc.Signer.MuSig2CombineSig:output_type -> signrpc.MuSig2CombineSigResponse + 29, // 40: signrpc.Signer.MuSig2Cleanup:output_type -> signrpc.MuSig2CleanupResponse + 30, // [30:41] is the sub-list for method output_type + 19, // [19:30] is the sub-list for method input_type 19, // [19:19] is the sub-list for extension type_name 19, // [19:19] is the sub-list for extension extendee 0, // [0:19] is the sub-list for field type_name @@ -2394,13 +2426,351 @@ func file_signrpc_signer_proto_init() { if File_signrpc_signer_proto != nil { return } + if !protoimpl.UnsafeEnabled { + file_signrpc_signer_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KeyLocator); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KeyDescriptor); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TxOut); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignDescriptor); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InputScript); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InputScriptResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignMessageReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignMessageResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VerifyMessageReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VerifyMessageResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SharedKeyRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SharedKeyResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TweakDesc); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TaprootTweakDesc); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2CombineKeysRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2CombineKeysResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2SessionRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2SessionResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2RegisterNoncesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2RegisterNoncesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2SignRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2SignResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2CombineSigRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2CombineSigResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2CleanupRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_signrpc_signer_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MuSig2CleanupResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_signrpc_signer_proto_rawDesc), len(file_signrpc_signer_proto_rawDesc)), + RawDescriptor: file_signrpc_signer_proto_rawDesc, NumEnums: 2, - NumMessages: 32, + NumMessages: 28, NumExtensions: 0, NumServices: 1, }, @@ -2410,6 +2780,7 @@ func file_signrpc_signer_proto_init() { MessageInfos: file_signrpc_signer_proto_msgTypes, }.Build() File_signrpc_signer_proto = out.File + file_signrpc_signer_proto_rawDesc = nil file_signrpc_signer_proto_goTypes = nil file_signrpc_signer_proto_depIdxs = nil } diff --git a/lnrpc/signrpc/signer.pb.gw.go b/lnrpc/signrpc/signer.pb.gw.go index 771ecc79b..20b8da69d 100644 --- a/lnrpc/signrpc/signer.pb.gw.go +++ b/lnrpc/signrpc/signer.pb.gw.go @@ -303,74 +303,6 @@ func local_request_Signer_MuSig2RegisterNonces_0(ctx context.Context, marshaler } -func request_Signer_MuSig2RegisterCombinedNonce_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq MuSig2RegisterCombinedNonceRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := client.MuSig2RegisterCombinedNonce(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) - return msg, metadata, err - -} - -func local_request_Signer_MuSig2RegisterCombinedNonce_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq MuSig2RegisterCombinedNonceRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.MuSig2RegisterCombinedNonce(ctx, &protoReq) - return msg, metadata, err - -} - -func request_Signer_MuSig2GetCombinedNonce_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq MuSig2GetCombinedNonceRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := client.MuSig2GetCombinedNonce(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) - return msg, metadata, err - -} - -func local_request_Signer_MuSig2GetCombinedNonce_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq MuSig2GetCombinedNonceRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.MuSig2GetCombinedNonce(ctx, &protoReq) - return msg, metadata, err - -} - func request_Signer_MuSig2Sign_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq MuSig2SignRequest var metadata runtime.ServerMetadata @@ -679,56 +611,6 @@ func RegisterSignerHandlerServer(ctx context.Context, mux *runtime.ServeMux, ser }) - mux.Handle("POST", pattern_Signer_MuSig2RegisterCombinedNonce_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - var stream runtime.ServerTransportStream - ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - var err error - var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/MuSig2RegisterCombinedNonce", runtime.WithHTTPPathPattern("/v2/signer/musig2/registercombinednonce")) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_Signer_MuSig2RegisterCombinedNonce_0(annotatedContext, inboundMarshaler, server, req, pathParams) - md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) - if err != nil { - runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) - return - } - - forward_Signer_MuSig2RegisterCombinedNonce_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_Signer_MuSig2GetCombinedNonce_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - var stream runtime.ServerTransportStream - ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - var err error - var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/MuSig2GetCombinedNonce", runtime.WithHTTPPathPattern("/v2/signer/musig2/getcombinednonce")) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_Signer_MuSig2GetCombinedNonce_0(annotatedContext, inboundMarshaler, server, req, pathParams) - md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) - if err != nil { - runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) - return - } - - forward_Signer_MuSig2GetCombinedNonce_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - mux.Handle("POST", pattern_Signer_MuSig2Sign_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -1021,50 +903,6 @@ func RegisterSignerHandlerClient(ctx context.Context, mux *runtime.ServeMux, cli }) - mux.Handle("POST", pattern_Signer_MuSig2RegisterCombinedNonce_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - var err error - var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/MuSig2RegisterCombinedNonce", runtime.WithHTTPPathPattern("/v2/signer/musig2/registercombinednonce")) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := request_Signer_MuSig2RegisterCombinedNonce_0(annotatedContext, inboundMarshaler, client, req, pathParams) - annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) - if err != nil { - runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) - return - } - - forward_Signer_MuSig2RegisterCombinedNonce_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_Signer_MuSig2GetCombinedNonce_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - var err error - var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/MuSig2GetCombinedNonce", runtime.WithHTTPPathPattern("/v2/signer/musig2/getcombinednonce")) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := request_Signer_MuSig2GetCombinedNonce_0(annotatedContext, inboundMarshaler, client, req, pathParams) - annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) - if err != nil { - runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) - return - } - - forward_Signer_MuSig2GetCombinedNonce_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - mux.Handle("POST", pattern_Signer_MuSig2Sign_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -1151,10 +989,6 @@ var ( pattern_Signer_MuSig2RegisterNonces_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "registernonces"}, "")) - pattern_Signer_MuSig2RegisterCombinedNonce_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "registercombinednonce"}, "")) - - pattern_Signer_MuSig2GetCombinedNonce_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "getcombinednonce"}, "")) - pattern_Signer_MuSig2Sign_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "sign"}, "")) pattern_Signer_MuSig2CombineSig_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "combinesig"}, "")) @@ -1179,10 +1013,6 @@ var ( forward_Signer_MuSig2RegisterNonces_0 = runtime.ForwardResponseMessage - forward_Signer_MuSig2RegisterCombinedNonce_0 = runtime.ForwardResponseMessage - - forward_Signer_MuSig2GetCombinedNonce_0 = runtime.ForwardResponseMessage - forward_Signer_MuSig2Sign_0 = runtime.ForwardResponseMessage forward_Signer_MuSig2CombineSig_0 = runtime.ForwardResponseMessage diff --git a/lnrpc/signrpc/signer.pb.json.go b/lnrpc/signrpc/signer.pb.json.go index 561d9913e..6adf032c5 100644 --- a/lnrpc/signrpc/signer.pb.json.go +++ b/lnrpc/signrpc/signer.pb.json.go @@ -221,56 +221,6 @@ func RegisterSignerJSONCallbacks(registry map[string]func(ctx context.Context, callback(string(respBytes), nil) } - registry["signrpc.Signer.MuSig2RegisterCombinedNonce"] = func(ctx context.Context, - conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { - - req := &MuSig2RegisterCombinedNonceRequest{} - err := marshaler.Unmarshal([]byte(reqJSON), req) - if err != nil { - callback("", err) - return - } - - client := NewSignerClient(conn) - resp, err := client.MuSig2RegisterCombinedNonce(ctx, req) - if err != nil { - callback("", err) - return - } - - respBytes, err := marshaler.Marshal(resp) - if err != nil { - callback("", err) - return - } - callback(string(respBytes), nil) - } - - registry["signrpc.Signer.MuSig2GetCombinedNonce"] = func(ctx context.Context, - conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { - - req := &MuSig2GetCombinedNonceRequest{} - err := marshaler.Unmarshal([]byte(reqJSON), req) - if err != nil { - callback("", err) - return - } - - client := NewSignerClient(conn) - resp, err := client.MuSig2GetCombinedNonce(ctx, req) - if err != nil { - callback("", err) - return - } - - respBytes, err := marshaler.Marshal(resp) - if err != nil { - callback("", err) - return - } - callback(string(respBytes), nil) - } - registry["signrpc.Signer.MuSig2Sign"] = func(ctx context.Context, conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { diff --git a/lnrpc/signrpc/signer.proto b/lnrpc/signrpc/signer.proto index 1dc128810..28a3295dd 100644 --- a/lnrpc/signrpc/signer.proto +++ b/lnrpc/signrpc/signer.proto @@ -106,34 +106,6 @@ service Signer { rpc MuSig2RegisterNonces (MuSig2RegisterNoncesRequest) returns (MuSig2RegisterNoncesResponse); - /* - MuSig2RegisterCombinedNonce (experimental!) registers a pre-aggregated - combined nonce for a signing session. This is an alternative to - MuSig2RegisterNonces and is used when a coordinator has already aggregated - all individual nonces and wants to distribute the combined nonce to - participants. - - NOTE: This method is mutually exclusive with MuSig2RegisterNonces for the - same session. The MuSig2 BIP is not final yet and therefore this API must - be considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming - releases. Backward compatibility is not guaranteed! - */ - rpc MuSig2RegisterCombinedNonce (MuSig2RegisterCombinedNonceRequest) - returns (MuSig2RegisterCombinedNonceResponse); - - /* - MuSig2GetCombinedNonce (experimental!) retrieves the combined nonce for a - signing session. This will be available after either all individual nonces - have been registered via MuSig2RegisterNonces, or a combined nonce has been - registered via MuSig2RegisterCombinedNonce. - - NOTE: The MuSig2 BIP is not final yet and therefore this API must be - considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming - releases. Backward compatibility is not guaranteed! - */ - rpc MuSig2GetCombinedNonce (MuSig2GetCombinedNonceRequest) - returns (MuSig2GetCombinedNonceResponse); - /* MuSig2Sign (experimental!) creates a partial signature using the local signing key that was specified when the session was created. This can only @@ -673,38 +645,6 @@ message MuSig2RegisterNoncesResponse { bool have_all_nonces = 1; } -message MuSig2RegisterCombinedNonceRequest { - /* - The unique ID of the signing session the combined nonce should be registered - with. - */ - bytes session_id = 1; - - /* - The 66-byte combined public nonce that was aggregated externally. This is a - concatenation of two 33-byte compressed public keys (R1 || R2). - */ - bytes combined_public_nonce = 2; -} - -message MuSig2RegisterCombinedNonceResponse { -} - -message MuSig2GetCombinedNonceRequest { - /* - The unique ID of the signing session to get the combined nonce for. - */ - bytes session_id = 1; -} - -message MuSig2GetCombinedNonceResponse { - /* - The 66-byte combined public nonce. This is a concatenation of two 33-byte - compressed public keys (R1 || R2). - */ - bytes combined_public_nonce = 1; -} - message MuSig2SignRequest { /* The unique ID of the signing session to use for signing. diff --git a/lnrpc/signrpc/signer.swagger.json b/lnrpc/signrpc/signer.swagger.json index 32412047c..1c6b6f02d 100644 --- a/lnrpc/signrpc/signer.swagger.json +++ b/lnrpc/signrpc/signer.swagger.json @@ -186,74 +186,6 @@ ] } }, - "/v2/signer/musig2/getcombinednonce": { - "post": { - "summary": "MuSig2GetCombinedNonce (experimental!) retrieves the combined nonce for a\nsigning session. This will be available after either all individual nonces\nhave been registered via MuSig2RegisterNonces, or a combined nonce has been\nregistered via MuSig2RegisterCombinedNonce.", - "description": "NOTE: The MuSig2 BIP is not final yet and therefore this API must be\nconsidered to be HIGHLY EXPERIMENTAL and subject to change in upcoming\nreleases. Backward compatibility is not guaranteed!", - "operationId": "Signer_MuSig2GetCombinedNonce", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/signrpcMuSig2GetCombinedNonceResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/rpcStatus" - } - } - }, - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/signrpcMuSig2GetCombinedNonceRequest" - } - } - ], - "tags": [ - "Signer" - ] - } - }, - "/v2/signer/musig2/registercombinednonce": { - "post": { - "summary": "MuSig2RegisterCombinedNonce (experimental!) registers a pre-aggregated\ncombined nonce for a signing session. This is an alternative to\nMuSig2RegisterNonces and is used when a coordinator has already aggregated\nall individual nonces and wants to distribute the combined nonce to\nparticipants.", - "description": "NOTE: This method is mutually exclusive with MuSig2RegisterNonces for the\nsame session. The MuSig2 BIP is not final yet and therefore this API must\nbe considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming\nreleases. Backward compatibility is not guaranteed!", - "operationId": "Signer_MuSig2RegisterCombinedNonce", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/signrpcMuSig2RegisterCombinedNonceResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/rpcStatus" - } - } - }, - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/signrpcMuSig2RegisterCombinedNonceRequest" - } - } - ], - "tags": [ - "Signer" - ] - } - }, "/v2/signer/musig2/registernonces": { "post": { "summary": "MuSig2RegisterNonces (experimental!) registers one or more public nonces of\nother signing participants for a session identified by its ID. This RPC can\nbe called multiple times until all nonces are registered.", @@ -640,44 +572,6 @@ } } }, - "signrpcMuSig2GetCombinedNonceRequest": { - "type": "object", - "properties": { - "session_id": { - "type": "string", - "format": "byte", - "description": "The unique ID of the signing session to get the combined nonce for." - } - } - }, - "signrpcMuSig2GetCombinedNonceResponse": { - "type": "object", - "properties": { - "combined_public_nonce": { - "type": "string", - "format": "byte", - "description": "The 66-byte combined public nonce. This is a concatenation of two 33-byte\ncompressed public keys (R1 || R2)." - } - } - }, - "signrpcMuSig2RegisterCombinedNonceRequest": { - "type": "object", - "properties": { - "session_id": { - "type": "string", - "format": "byte", - "description": "The unique ID of the signing session the combined nonce should be registered\nwith." - }, - "combined_public_nonce": { - "type": "string", - "format": "byte", - "description": "The 66-byte combined public nonce that was aggregated externally. This is a\nconcatenation of two 33-byte compressed public keys (R1 || R2)." - } - } - }, - "signrpcMuSig2RegisterCombinedNonceResponse": { - "type": "object" - }, "signrpcMuSig2RegisterNoncesRequest": { "type": "object", "properties": { diff --git a/lnrpc/signrpc/signer.yaml b/lnrpc/signrpc/signer.yaml index 20f53712a..57699de54 100644 --- a/lnrpc/signrpc/signer.yaml +++ b/lnrpc/signrpc/signer.yaml @@ -27,12 +27,6 @@ http: - selector: signrpc.Signer.MuSig2RegisterNonces post: "/v2/signer/musig2/registernonces" body: "*" - - selector: signrpc.Signer.MuSig2RegisterCombinedNonce - post: "/v2/signer/musig2/registercombinednonce" - body: "*" - - selector: signrpc.Signer.MuSig2GetCombinedNonce - post: "/v2/signer/musig2/getcombinednonce" - body: "*" - selector: signrpc.Signer.MuSig2Sign post: "/v2/signer/musig2/sign" body: "*" diff --git a/lnrpc/signrpc/signer_grpc.pb.go b/lnrpc/signrpc/signer_grpc.pb.go index 5e995c1d0..304b56aa8 100644 --- a/lnrpc/signrpc/signer_grpc.pb.go +++ b/lnrpc/signrpc/signer_grpc.pb.go @@ -90,26 +90,6 @@ type SignerClient interface { // considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming // releases. Backward compatibility is not guaranteed! MuSig2RegisterNonces(ctx context.Context, in *MuSig2RegisterNoncesRequest, opts ...grpc.CallOption) (*MuSig2RegisterNoncesResponse, error) - // MuSig2RegisterCombinedNonce (experimental!) registers a pre-aggregated - // combined nonce for a signing session. This is an alternative to - // MuSig2RegisterNonces and is used when a coordinator has already aggregated - // all individual nonces and wants to distribute the combined nonce to - // participants. - // - // NOTE: This method is mutually exclusive with MuSig2RegisterNonces for the - // same session. The MuSig2 BIP is not final yet and therefore this API must - // be considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming - // releases. Backward compatibility is not guaranteed! - MuSig2RegisterCombinedNonce(ctx context.Context, in *MuSig2RegisterCombinedNonceRequest, opts ...grpc.CallOption) (*MuSig2RegisterCombinedNonceResponse, error) - // MuSig2GetCombinedNonce (experimental!) retrieves the combined nonce for a - // signing session. This will be available after either all individual nonces - // have been registered via MuSig2RegisterNonces, or a combined nonce has been - // registered via MuSig2RegisterCombinedNonce. - // - // NOTE: The MuSig2 BIP is not final yet and therefore this API must be - // considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming - // releases. Backward compatibility is not guaranteed! - MuSig2GetCombinedNonce(ctx context.Context, in *MuSig2GetCombinedNonceRequest, opts ...grpc.CallOption) (*MuSig2GetCombinedNonceResponse, error) // MuSig2Sign (experimental!) creates a partial signature using the local // signing key that was specified when the session was created. This can only // be called when all public nonces of all participants are known and have been @@ -220,24 +200,6 @@ func (c *signerClient) MuSig2RegisterNonces(ctx context.Context, in *MuSig2Regis return out, nil } -func (c *signerClient) MuSig2RegisterCombinedNonce(ctx context.Context, in *MuSig2RegisterCombinedNonceRequest, opts ...grpc.CallOption) (*MuSig2RegisterCombinedNonceResponse, error) { - out := new(MuSig2RegisterCombinedNonceResponse) - err := c.cc.Invoke(ctx, "/signrpc.Signer/MuSig2RegisterCombinedNonce", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *signerClient) MuSig2GetCombinedNonce(ctx context.Context, in *MuSig2GetCombinedNonceRequest, opts ...grpc.CallOption) (*MuSig2GetCombinedNonceResponse, error) { - out := new(MuSig2GetCombinedNonceResponse) - err := c.cc.Invoke(ctx, "/signrpc.Signer/MuSig2GetCombinedNonce", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *signerClient) MuSig2Sign(ctx context.Context, in *MuSig2SignRequest, opts ...grpc.CallOption) (*MuSig2SignResponse, error) { out := new(MuSig2SignResponse) err := c.cc.Invoke(ctx, "/signrpc.Signer/MuSig2Sign", in, out, opts...) @@ -341,26 +303,6 @@ type SignerServer interface { // considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming // releases. Backward compatibility is not guaranteed! MuSig2RegisterNonces(context.Context, *MuSig2RegisterNoncesRequest) (*MuSig2RegisterNoncesResponse, error) - // MuSig2RegisterCombinedNonce (experimental!) registers a pre-aggregated - // combined nonce for a signing session. This is an alternative to - // MuSig2RegisterNonces and is used when a coordinator has already aggregated - // all individual nonces and wants to distribute the combined nonce to - // participants. - // - // NOTE: This method is mutually exclusive with MuSig2RegisterNonces for the - // same session. The MuSig2 BIP is not final yet and therefore this API must - // be considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming - // releases. Backward compatibility is not guaranteed! - MuSig2RegisterCombinedNonce(context.Context, *MuSig2RegisterCombinedNonceRequest) (*MuSig2RegisterCombinedNonceResponse, error) - // MuSig2GetCombinedNonce (experimental!) retrieves the combined nonce for a - // signing session. This will be available after either all individual nonces - // have been registered via MuSig2RegisterNonces, or a combined nonce has been - // registered via MuSig2RegisterCombinedNonce. - // - // NOTE: The MuSig2 BIP is not final yet and therefore this API must be - // considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming - // releases. Backward compatibility is not guaranteed! - MuSig2GetCombinedNonce(context.Context, *MuSig2GetCombinedNonceRequest) (*MuSig2GetCombinedNonceResponse, error) // MuSig2Sign (experimental!) creates a partial signature using the local // signing key that was specified when the session was created. This can only // be called when all public nonces of all participants are known and have been @@ -420,12 +362,6 @@ func (UnimplementedSignerServer) MuSig2CreateSession(context.Context, *MuSig2Ses func (UnimplementedSignerServer) MuSig2RegisterNonces(context.Context, *MuSig2RegisterNoncesRequest) (*MuSig2RegisterNoncesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method MuSig2RegisterNonces not implemented") } -func (UnimplementedSignerServer) MuSig2RegisterCombinedNonce(context.Context, *MuSig2RegisterCombinedNonceRequest) (*MuSig2RegisterCombinedNonceResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method MuSig2RegisterCombinedNonce not implemented") -} -func (UnimplementedSignerServer) MuSig2GetCombinedNonce(context.Context, *MuSig2GetCombinedNonceRequest) (*MuSig2GetCombinedNonceResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method MuSig2GetCombinedNonce not implemented") -} func (UnimplementedSignerServer) MuSig2Sign(context.Context, *MuSig2SignRequest) (*MuSig2SignResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method MuSig2Sign not implemented") } @@ -592,42 +528,6 @@ func _Signer_MuSig2RegisterNonces_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } -func _Signer_MuSig2RegisterCombinedNonce_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MuSig2RegisterCombinedNonceRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(SignerServer).MuSig2RegisterCombinedNonce(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/signrpc.Signer/MuSig2RegisterCombinedNonce", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(SignerServer).MuSig2RegisterCombinedNonce(ctx, req.(*MuSig2RegisterCombinedNonceRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Signer_MuSig2GetCombinedNonce_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MuSig2GetCombinedNonceRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(SignerServer).MuSig2GetCombinedNonce(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/signrpc.Signer/MuSig2GetCombinedNonce", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(SignerServer).MuSig2GetCombinedNonce(ctx, req.(*MuSig2GetCombinedNonceRequest)) - } - return interceptor(ctx, in, info, handler) -} - func _Signer_MuSig2Sign_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(MuSig2SignRequest) if err := dec(in); err != nil { @@ -721,14 +621,6 @@ var Signer_ServiceDesc = grpc.ServiceDesc{ MethodName: "MuSig2RegisterNonces", Handler: _Signer_MuSig2RegisterNonces_Handler, }, - { - MethodName: "MuSig2RegisterCombinedNonce", - Handler: _Signer_MuSig2RegisterCombinedNonce_Handler, - }, - { - MethodName: "MuSig2GetCombinedNonce", - Handler: _Signer_MuSig2GetCombinedNonce_Handler, - }, { MethodName: "MuSig2Sign", Handler: _Signer_MuSig2Sign_Handler, diff --git a/lnrpc/signrpc/signer_server.go b/lnrpc/signrpc/signer_server.go index 190822edc..a98e24d1b 100644 --- a/lnrpc/signrpc/signer_server.go +++ b/lnrpc/signrpc/signer_server.go @@ -14,9 +14,9 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -86,14 +86,6 @@ var ( Entity: "signer", Action: "generate", }}, - "/signrpc.Signer/MuSig2RegisterCombinedNonce": {{ - Entity: "signer", - Action: "generate", - }}, - "/signrpc.Signer/MuSig2GetCombinedNonce": {{ - Entity: "signer", - Action: "read", - }}, "/signrpc.Signer/MuSig2Sign": {{ Entity: "signer", Action: "generate", @@ -1088,62 +1080,6 @@ func (s *Server) MuSig2RegisterNonces(_ context.Context, return &MuSig2RegisterNoncesResponse{HaveAllNonces: haveAllNonces}, nil } -// MuSig2RegisterCombinedNonce registers a pre-aggregated combined nonce for a -// session identified by its ID. This is an alternative to MuSig2RegisterNonces -// and is used when a coordinator has already aggregated all individual nonces. -func (s *Server) MuSig2RegisterCombinedNonce(_ context.Context, - in *MuSig2RegisterCombinedNonceRequest) ( - *MuSig2RegisterCombinedNonceResponse, error) { - - // Check session ID length. - sessionID, err := parseMuSig2SessionID(in.SessionId) - if err != nil { - return nil, fmt.Errorf("error parsing session ID: %w", err) - } - - // Validate the combined nonce length. - if len(in.CombinedPublicNonce) != musig2.PubNonceSize { - return nil, fmt.Errorf("invalid combined nonce length, got "+ - "%d wanted %d", len(in.CombinedPublicNonce), - musig2.PubNonceSize) - } - - // Convert to the expected fixed-size array. - var combinedNonce [musig2.PubNonceSize]byte - copy(combinedNonce[:], in.CombinedPublicNonce) - - // Register the combined nonce. - err = s.cfg.Signer.MuSig2RegisterCombinedNonce(sessionID, combinedNonce) - if err != nil { - return nil, fmt.Errorf("error registering combined nonce: %w", - err) - } - - return &MuSig2RegisterCombinedNonceResponse{}, nil -} - -// MuSig2GetCombinedNonce retrieves the combined nonce for a signing session. -func (s *Server) MuSig2GetCombinedNonce(_ context.Context, - in *MuSig2GetCombinedNonceRequest) ( - *MuSig2GetCombinedNonceResponse, error) { - - // Check session ID length. - sessionID, err := parseMuSig2SessionID(in.SessionId) - if err != nil { - return nil, fmt.Errorf("error parsing session ID: %w", err) - } - - // Get the combined nonce from the signer. - combinedNonce, err := s.cfg.Signer.MuSig2GetCombinedNonce(sessionID) - if err != nil { - return nil, fmt.Errorf("error getting combined nonce: %w", err) - } - - return &MuSig2GetCombinedNonceResponse{ - CombinedPublicNonce: combinedNonce[:], - }, nil -} - // MuSig2Sign creates a partial signature using the local signing key that was // specified when the session was created. This can only be called when all // public nonces of all participants are known and have been registered with diff --git a/lnrpc/stateservice.pb.go b/lnrpc/stateservice.pb.go index 7090388f9..d6f6a23b2 100644 --- a/lnrpc/stateservice.pb.go +++ b/lnrpc/stateservice.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.33.0 // protoc v3.21.12 // source: stateservice.proto @@ -11,7 +11,6 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" - unsafe "unsafe" ) const ( @@ -89,16 +88,18 @@ func (WalletState) EnumDescriptor() ([]byte, []int) { } type SubscribeStateRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *SubscribeStateRequest) Reset() { *x = SubscribeStateRequest{} - mi := &file_stateservice_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_stateservice_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SubscribeStateRequest) String() string { @@ -109,7 +110,7 @@ func (*SubscribeStateRequest) ProtoMessage() {} func (x *SubscribeStateRequest) ProtoReflect() protoreflect.Message { mi := &file_stateservice_proto_msgTypes[0] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -125,17 +126,20 @@ func (*SubscribeStateRequest) Descriptor() ([]byte, []int) { } type SubscribeStateResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - State WalletState `protobuf:"varint,1,opt,name=state,proto3,enum=lnrpc.WalletState" json:"state,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + State WalletState `protobuf:"varint,1,opt,name=state,proto3,enum=lnrpc.WalletState" json:"state,omitempty"` } func (x *SubscribeStateResponse) Reset() { *x = SubscribeStateResponse{} - mi := &file_stateservice_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_stateservice_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SubscribeStateResponse) String() string { @@ -146,7 +150,7 @@ func (*SubscribeStateResponse) ProtoMessage() {} func (x *SubscribeStateResponse) ProtoReflect() protoreflect.Message { mi := &file_stateservice_proto_msgTypes[1] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -169,16 +173,18 @@ func (x *SubscribeStateResponse) GetState() WalletState { } type GetStateRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *GetStateRequest) Reset() { *x = GetStateRequest{} - mi := &file_stateservice_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_stateservice_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetStateRequest) String() string { @@ -189,7 +195,7 @@ func (*GetStateRequest) ProtoMessage() {} func (x *GetStateRequest) ProtoReflect() protoreflect.Message { mi := &file_stateservice_proto_msgTypes[2] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -205,17 +211,20 @@ func (*GetStateRequest) Descriptor() ([]byte, []int) { } type GetStateResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - State WalletState `protobuf:"varint,1,opt,name=state,proto3,enum=lnrpc.WalletState" json:"state,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + State WalletState `protobuf:"varint,1,opt,name=state,proto3,enum=lnrpc.WalletState" json:"state,omitempty"` } func (x *GetStateResponse) Reset() { *x = GetStateResponse{} - mi := &file_stateservice_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_stateservice_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetStateResponse) String() string { @@ -226,7 +235,7 @@ func (*GetStateResponse) ProtoMessage() {} func (x *GetStateResponse) ProtoReflect() protoreflect.Message { mi := &file_stateservice_proto_msgTypes[3] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -250,43 +259,57 @@ func (x *GetStateResponse) GetState() WalletState { var File_stateservice_proto protoreflect.FileDescriptor -const file_stateservice_proto_rawDesc = "" + - "\n" + - "\x12stateservice.proto\x12\x05lnrpc\"\x17\n" + - "\x15SubscribeStateRequest\"B\n" + - "\x16SubscribeStateResponse\x12(\n" + - "\x05state\x18\x01 \x01(\x0e2\x12.lnrpc.WalletStateR\x05state\"\x11\n" + - "\x0fGetStateRequest\"<\n" + - "\x10GetStateResponse\x12(\n" + - "\x05state\x18\x01 \x01(\x0e2\x12.lnrpc.WalletStateR\x05state*s\n" + - "\vWalletState\x12\x10\n" + - "\fNON_EXISTING\x10\x00\x12\n" + - "\n" + - "\x06LOCKED\x10\x01\x12\f\n" + - "\bUNLOCKED\x10\x02\x12\x0e\n" + - "\n" + - "RPC_ACTIVE\x10\x03\x12\x11\n" + - "\rSERVER_ACTIVE\x10\x04\x12\x15\n" + - "\x10WAITING_TO_START\x10\xff\x012\x95\x01\n" + - "\x05State\x12O\n" + - "\x0eSubscribeState\x12\x1c.lnrpc.SubscribeStateRequest\x1a\x1d.lnrpc.SubscribeStateResponse0\x01\x12;\n" + - "\bGetState\x12\x16.lnrpc.GetStateRequest\x1a\x17.lnrpc.GetStateResponseB'Z%github.com/lightningnetwork/lnd/lnrpcb\x06proto3" +var file_stateservice_proto_rawDesc = []byte{ + 0x0a, 0x12, 0x73, 0x74, 0x61, 0x74, 0x65, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x22, 0x17, 0x0a, 0x15, 0x53, + 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x22, 0x42, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, + 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, + 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x11, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3c, 0x0a, 0x10, 0x47, + 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x28, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2a, 0x73, 0x0a, 0x0b, 0x57, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x4e, 0x4f, 0x4e, 0x5f, + 0x45, 0x58, 0x49, 0x53, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x4f, + 0x43, 0x4b, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x4e, 0x4c, 0x4f, 0x43, 0x4b, + 0x45, 0x44, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x50, 0x43, 0x5f, 0x41, 0x43, 0x54, 0x49, + 0x56, 0x45, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x41, + 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x04, 0x12, 0x15, 0x0a, 0x10, 0x57, 0x41, 0x49, 0x54, 0x49, + 0x4e, 0x47, 0x5f, 0x54, 0x4f, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0xff, 0x01, 0x32, 0x95, + 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x4f, 0x0a, 0x0e, 0x53, 0x75, 0x62, 0x73, + 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x3b, 0x0a, 0x08, 0x47, 0x65, 0x74, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, + 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} var ( file_stateservice_proto_rawDescOnce sync.Once - file_stateservice_proto_rawDescData []byte + file_stateservice_proto_rawDescData = file_stateservice_proto_rawDesc ) func file_stateservice_proto_rawDescGZIP() []byte { file_stateservice_proto_rawDescOnce.Do(func() { - file_stateservice_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_stateservice_proto_rawDesc), len(file_stateservice_proto_rawDesc))) + file_stateservice_proto_rawDescData = protoimpl.X.CompressGZIP(file_stateservice_proto_rawDescData) }) return file_stateservice_proto_rawDescData } var file_stateservice_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_stateservice_proto_msgTypes = make([]protoimpl.MessageInfo, 4) -var file_stateservice_proto_goTypes = []any{ +var file_stateservice_proto_goTypes = []interface{}{ (WalletState)(0), // 0: lnrpc.WalletState (*SubscribeStateRequest)(nil), // 1: lnrpc.SubscribeStateRequest (*SubscribeStateResponse)(nil), // 2: lnrpc.SubscribeStateResponse @@ -312,11 +335,61 @@ func file_stateservice_proto_init() { if File_stateservice_proto != nil { return } + if !protoimpl.UnsafeEnabled { + file_stateservice_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SubscribeStateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_stateservice_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SubscribeStateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_stateservice_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetStateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_stateservice_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetStateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_stateservice_proto_rawDesc), len(file_stateservice_proto_rawDesc)), + RawDescriptor: file_stateservice_proto_rawDesc, NumEnums: 1, NumMessages: 4, NumExtensions: 0, @@ -328,6 +401,7 @@ func file_stateservice_proto_init() { MessageInfos: file_stateservice_proto_msgTypes, }.Build() File_stateservice_proto = out.File + file_stateservice_proto_rawDesc = nil file_stateservice_proto_goTypes = nil file_stateservice_proto_depIdxs = nil } diff --git a/lnrpc/verrpc/verrpc.pb.go b/lnrpc/verrpc/verrpc.pb.go index f7317771c..bff03d64b 100644 --- a/lnrpc/verrpc/verrpc.pb.go +++ b/lnrpc/verrpc/verrpc.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.33.0 // protoc v3.21.12 // source: verrpc/verrpc.proto @@ -11,7 +11,6 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" - unsafe "unsafe" ) const ( @@ -22,16 +21,18 @@ const ( ) type VersionRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *VersionRequest) Reset() { *x = VersionRequest{} - mi := &file_verrpc_verrpc_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_verrpc_verrpc_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *VersionRequest) String() string { @@ -42,7 +43,7 @@ func (*VersionRequest) ProtoMessage() {} func (x *VersionRequest) ProtoReflect() protoreflect.Message { mi := &file_verrpc_verrpc_proto_msgTypes[0] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -58,7 +59,10 @@ func (*VersionRequest) Descriptor() ([]byte, []int) { } type Version struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // A verbose description of the daemon's commit. Commit string `protobuf:"bytes,1,opt,name=commit,proto3" json:"commit,omitempty"` // The SHA1 commit hash that the daemon is compiled with. @@ -76,16 +80,16 @@ type Version struct { // The list of build tags that were supplied during compilation. BuildTags []string `protobuf:"bytes,8,rep,name=build_tags,json=buildTags,proto3" json:"build_tags,omitempty"` // The version of go that compiled the executable. - GoVersion string `protobuf:"bytes,9,opt,name=go_version,json=goVersion,proto3" json:"go_version,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + GoVersion string `protobuf:"bytes,9,opt,name=go_version,json=goVersion,proto3" json:"go_version,omitempty"` } func (x *Version) Reset() { *x = Version{} - mi := &file_verrpc_verrpc_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_verrpc_verrpc_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *Version) String() string { @@ -96,7 +100,7 @@ func (*Version) ProtoMessage() {} func (x *Version) ProtoReflect() protoreflect.Message { mi := &file_verrpc_verrpc_proto_msgTypes[1] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -176,41 +180,52 @@ func (x *Version) GetGoVersion() string { var File_verrpc_verrpc_proto protoreflect.FileDescriptor -const file_verrpc_verrpc_proto_rawDesc = "" + - "\n" + - "\x13verrpc/verrpc.proto\x12\x06verrpc\"\x10\n" + - "\x0eVersionRequest\"\x99\x02\n" + - "\aVersion\x12\x16\n" + - "\x06commit\x18\x01 \x01(\tR\x06commit\x12\x1f\n" + - "\vcommit_hash\x18\x02 \x01(\tR\n" + - "commitHash\x12\x18\n" + - "\aversion\x18\x03 \x01(\tR\aversion\x12\x1b\n" + - "\tapp_major\x18\x04 \x01(\rR\bappMajor\x12\x1b\n" + - "\tapp_minor\x18\x05 \x01(\rR\bappMinor\x12\x1b\n" + - "\tapp_patch\x18\x06 \x01(\rR\bappPatch\x12&\n" + - "\x0fapp_pre_release\x18\a \x01(\tR\rappPreRelease\x12\x1d\n" + - "\n" + - "build_tags\x18\b \x03(\tR\tbuildTags\x12\x1d\n" + - "\n" + - "go_version\x18\t \x01(\tR\tgoVersion2B\n" + - "\tVersioner\x125\n" + - "\n" + - "GetVersion\x12\x16.verrpc.VersionRequest\x1a\x0f.verrpc.VersionB.Z,github.com/lightningnetwork/lnd/lnrpc/verrpcb\x06proto3" +var file_verrpc_verrpc_proto_rawDesc = []byte{ + 0x0a, 0x13, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2f, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x22, 0x10, 0x0a, + 0x0e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, + 0x99, 0x02, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x63, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x68, 0x61, + 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x48, 0x61, 0x73, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1b, + 0x0a, 0x09, 0x61, 0x70, 0x70, 0x5f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x08, 0x61, 0x70, 0x70, 0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x61, + 0x70, 0x70, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, + 0x61, 0x70, 0x70, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x70, 0x70, 0x5f, + 0x70, 0x61, 0x74, 0x63, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x61, 0x70, 0x70, + 0x50, 0x61, 0x74, 0x63, 0x68, 0x12, 0x26, 0x0a, 0x0f, 0x61, 0x70, 0x70, 0x5f, 0x70, 0x72, 0x65, + 0x5f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x61, 0x70, 0x70, 0x50, 0x72, 0x65, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x12, 0x1d, 0x0a, + 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x54, 0x61, 0x67, 0x73, 0x12, 0x1d, 0x0a, 0x0a, + 0x67, 0x6f, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x67, 0x6f, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x32, 0x42, 0x0a, 0x09, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x35, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, + 0x2e, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, + 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, + 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, + 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} var ( file_verrpc_verrpc_proto_rawDescOnce sync.Once - file_verrpc_verrpc_proto_rawDescData []byte + file_verrpc_verrpc_proto_rawDescData = file_verrpc_verrpc_proto_rawDesc ) func file_verrpc_verrpc_proto_rawDescGZIP() []byte { file_verrpc_verrpc_proto_rawDescOnce.Do(func() { - file_verrpc_verrpc_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_verrpc_verrpc_proto_rawDesc), len(file_verrpc_verrpc_proto_rawDesc))) + file_verrpc_verrpc_proto_rawDescData = protoimpl.X.CompressGZIP(file_verrpc_verrpc_proto_rawDescData) }) return file_verrpc_verrpc_proto_rawDescData } var file_verrpc_verrpc_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_verrpc_verrpc_proto_goTypes = []any{ +var file_verrpc_verrpc_proto_goTypes = []interface{}{ (*VersionRequest)(nil), // 0: verrpc.VersionRequest (*Version)(nil), // 1: verrpc.Version } @@ -229,11 +244,37 @@ func file_verrpc_verrpc_proto_init() { if File_verrpc_verrpc_proto != nil { return } + if !protoimpl.UnsafeEnabled { + file_verrpc_verrpc_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VersionRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_verrpc_verrpc_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Version); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_verrpc_verrpc_proto_rawDesc), len(file_verrpc_verrpc_proto_rawDesc)), + RawDescriptor: file_verrpc_verrpc_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 0, @@ -244,6 +285,7 @@ func file_verrpc_verrpc_proto_init() { MessageInfos: file_verrpc_verrpc_proto_msgTypes, }.Build() File_verrpc_verrpc_proto = out.File + file_verrpc_verrpc_proto_rawDesc = nil file_verrpc_verrpc_proto_goTypes = nil file_verrpc_verrpc_proto_depIdxs = nil } diff --git a/lnrpc/walletrpc/config_active.go b/lnrpc/walletrpc/config_active.go index 33917b1c9..4636473f0 100644 --- a/lnrpc/walletrpc/config_active.go +++ b/lnrpc/walletrpc/config_active.go @@ -4,9 +4,9 @@ package walletrpc import ( - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcwallet/wallet" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" @@ -78,6 +78,6 @@ type Config struct { // coins when funding a transaction. CoinSelectionStrategy wallet.CoinSelectionStrategy - // ChanStateDB is the reference to the open channel store. - ChanStateDB chanstate.OpenChannelStore + // ChanStateDB is the reference to the channel db. + ChanStateDB *channeldb.ChannelStateDB } diff --git a/lnrpc/walletrpc/psbt.go b/lnrpc/walletrpc/psbt.go index 5a652aa4f..5d8472292 100644 --- a/lnrpc/walletrpc/psbt.go +++ b/lnrpc/walletrpc/psbt.go @@ -8,7 +8,7 @@ import ( "math" "time" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" base "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/wtxmgr" "github.com/lightningnetwork/lnd/lnwallet" diff --git a/lnrpc/walletrpc/walletkit.pb.go b/lnrpc/walletrpc/walletkit.pb.go index 40e9506e3..d89f3861e 100644 --- a/lnrpc/walletrpc/walletkit.pb.go +++ b/lnrpc/walletrpc/walletkit.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.33.0 // protoc v3.21.12 // source: walletrpc/walletkit.proto @@ -13,7 +13,6 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" - unsafe "unsafe" ) const ( @@ -218,35 +217,6 @@ const ( // A witness that allows us to sweep the settled output of a malicious // counterparty's who broadcasts a revoked taproot commitment transaction. WitnessType_TAPROOT_COMMITMENT_REVOKE WitnessType = 35 - // A witness type that allows us to spend our settled local commitment after a - // CSV delay when we force close a production taproot channel. - WitnessType_TAPROOT_LOCAL_COMMIT_SPEND_FINAL WitnessType = 36 - // A witness type that allows us to spend our settled local commitment after - // a CSV delay when the remote party has force closed a production taproot - // channel. - WitnessType_TAPROOT_REMOTE_COMMIT_SPEND_FINAL WitnessType = 37 - // A witness that allows us to timeout an HTLC we offered to the remote party - // on our production taproot commitment transaction. We use this when we need - // to go on chain to time out an HTLC. - WitnessType_TAPROOT_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL_FINAL WitnessType = 38 - // A witness type that allows us to sweep an HTLC we accepted on our - // production taproot commitment transaction after we go to the second level - // on chain. - WitnessType_TAPROOT_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL_FINAL WitnessType = 39 - // A witness that allows us to sweep an HTLC we offered to the remote party - // that lies on the production taproot commitment transaction for the remote - // party. We can spend this output after the absolute CLTV timeout of the - // HTLC as passed. - WitnessType_TAPROOT_HTLC_OFFERED_REMOTE_TIMEOUT_FINAL WitnessType = 40 - // A witness that allows us to sweep an HTLC that was offered to us by the - // remote party for a production taproot channel. We use this witness in the - // case that the remote party goes to chain, and we know the pre-image to the - // HTLC. We can sweep this without any additional timeout. - WitnessType_TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS_FINAL WitnessType = 41 - // A witness type that allows us to sweep the settled output of a malicious - // counterparty's who broadcasts a revoked production taproot commitment - // transaction. - WitnessType_TAPROOT_COMMITMENT_REVOKE_FINAL WitnessType = 42 ) // Enum value maps for WitnessType. @@ -288,13 +258,6 @@ var ( 33: "TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS", 34: "TAPROOT_HTLC_ACCEPTED_LOCAL_SUCCESS", 35: "TAPROOT_COMMITMENT_REVOKE", - 36: "TAPROOT_LOCAL_COMMIT_SPEND_FINAL", - 37: "TAPROOT_REMOTE_COMMIT_SPEND_FINAL", - 38: "TAPROOT_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL_FINAL", - 39: "TAPROOT_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL_FINAL", - 40: "TAPROOT_HTLC_OFFERED_REMOTE_TIMEOUT_FINAL", - 41: "TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS_FINAL", - 42: "TAPROOT_COMMITMENT_REVOKE_FINAL", } WitnessType_value = map[string]int32{ "UNKNOWN_WITNESS": 0, @@ -333,13 +296,6 @@ var ( "TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS": 33, "TAPROOT_HTLC_ACCEPTED_LOCAL_SUCCESS": 34, "TAPROOT_COMMITMENT_REVOKE": 35, - "TAPROOT_LOCAL_COMMIT_SPEND_FINAL": 36, - "TAPROOT_REMOTE_COMMIT_SPEND_FINAL": 37, - "TAPROOT_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL_FINAL": 38, - "TAPROOT_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL_FINAL": 39, - "TAPROOT_HTLC_OFFERED_REMOTE_TIMEOUT_FINAL": 40, - "TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS_FINAL": 41, - "TAPROOT_COMMITMENT_REVOKE_FINAL": 42, } ) @@ -428,7 +384,10 @@ func (ChangeAddressType) EnumDescriptor() ([]byte, []int) { } type ListUnspentRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The minimum number of confirmations to be included. MinConfs int32 `protobuf:"varint,1,opt,name=min_confs,json=minConfs,proto3" json:"min_confs,omitempty"` // The maximum number of confirmations to be included. @@ -440,15 +399,15 @@ type ListUnspentRequest struct { // zero. An error is returned if the value is true and both min_confs // and max_confs are non-zero. (default: false) UnconfirmedOnly bool `protobuf:"varint,4,opt,name=unconfirmed_only,json=unconfirmedOnly,proto3" json:"unconfirmed_only,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ListUnspentRequest) Reset() { *x = ListUnspentRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListUnspentRequest) String() string { @@ -459,7 +418,7 @@ func (*ListUnspentRequest) ProtoMessage() {} func (x *ListUnspentRequest) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[0] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -503,18 +462,21 @@ func (x *ListUnspentRequest) GetUnconfirmedOnly() bool { } type ListUnspentResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // A list of utxos satisfying the specified number of confirmations. - Utxos []*lnrpc.Utxo `protobuf:"bytes,1,rep,name=utxos,proto3" json:"utxos,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // A list of utxos satisfying the specified number of confirmations. + Utxos []*lnrpc.Utxo `protobuf:"bytes,1,rep,name=utxos,proto3" json:"utxos,omitempty"` } func (x *ListUnspentResponse) Reset() { *x = ListUnspentResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListUnspentResponse) String() string { @@ -525,7 +487,7 @@ func (*ListUnspentResponse) ProtoMessage() {} func (x *ListUnspentResponse) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[1] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -548,7 +510,10 @@ func (x *ListUnspentResponse) GetUtxos() []*lnrpc.Utxo { } type LeaseOutputRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // An ID of 32 random bytes that must be unique for each distinct application // using this RPC which will be used to bound the output lease to. Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` @@ -557,15 +522,15 @@ type LeaseOutputRequest struct { // The time in seconds before the lock expires. If set to zero, the default // lock duration is used. ExpirationSeconds uint64 `protobuf:"varint,3,opt,name=expiration_seconds,json=expirationSeconds,proto3" json:"expiration_seconds,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *LeaseOutputRequest) Reset() { *x = LeaseOutputRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *LeaseOutputRequest) String() string { @@ -576,7 +541,7 @@ func (*LeaseOutputRequest) ProtoMessage() {} func (x *LeaseOutputRequest) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[2] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -613,18 +578,21 @@ func (x *LeaseOutputRequest) GetExpirationSeconds() uint64 { } type LeaseOutputResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The absolute expiration of the output lease represented as a unix timestamp. - Expiration uint64 `protobuf:"varint,1,opt,name=expiration,proto3" json:"expiration,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The absolute expiration of the output lease represented as a unix timestamp. + Expiration uint64 `protobuf:"varint,1,opt,name=expiration,proto3" json:"expiration,omitempty"` } func (x *LeaseOutputResponse) Reset() { *x = LeaseOutputResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *LeaseOutputResponse) String() string { @@ -635,7 +603,7 @@ func (*LeaseOutputResponse) ProtoMessage() {} func (x *LeaseOutputResponse) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[3] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -658,20 +626,23 @@ func (x *LeaseOutputResponse) GetExpiration() uint64 { } type ReleaseOutputRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The unique ID that was used to lock the output. Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // The identifying outpoint of the output being released. - Outpoint *lnrpc.OutPoint `protobuf:"bytes,2,opt,name=outpoint,proto3" json:"outpoint,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Outpoint *lnrpc.OutPoint `protobuf:"bytes,2,opt,name=outpoint,proto3" json:"outpoint,omitempty"` } func (x *ReleaseOutputRequest) Reset() { *x = ReleaseOutputRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ReleaseOutputRequest) String() string { @@ -682,7 +653,7 @@ func (*ReleaseOutputRequest) ProtoMessage() {} func (x *ReleaseOutputRequest) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[4] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -712,18 +683,21 @@ func (x *ReleaseOutputRequest) GetOutpoint() *lnrpc.OutPoint { } type ReleaseOutputResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The status of the release operation. - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The status of the release operation. + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` } func (x *ReleaseOutputResponse) Reset() { *x = ReleaseOutputResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ReleaseOutputResponse) String() string { @@ -734,7 +708,7 @@ func (*ReleaseOutputResponse) ProtoMessage() {} func (x *ReleaseOutputResponse) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[5] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -757,23 +731,26 @@ func (x *ReleaseOutputResponse) GetStatus() string { } type KeyReq struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Is the key finger print of the root pubkey that this request is targeting. // This allows the WalletKit to possibly serve out keys for multiple HD chains // via public derivation. KeyFingerPrint int32 `protobuf:"varint,1,opt,name=key_finger_print,json=keyFingerPrint,proto3" json:"key_finger_print,omitempty"` // The target key family to derive a key from. In other contexts, this is // known as the "account". - KeyFamily int32 `protobuf:"varint,2,opt,name=key_family,json=keyFamily,proto3" json:"key_family,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + KeyFamily int32 `protobuf:"varint,2,opt,name=key_family,json=keyFamily,proto3" json:"key_family,omitempty"` } func (x *KeyReq) Reset() { *x = KeyReq{} - mi := &file_walletrpc_walletkit_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *KeyReq) String() string { @@ -784,7 +761,7 @@ func (*KeyReq) ProtoMessage() {} func (x *KeyReq) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[6] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -814,23 +791,26 @@ func (x *KeyReq) GetKeyFamily() int32 { } type AddrRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The name of the account to retrieve the next address of. If empty, the // default wallet account is used. Account string `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"` // The type of address to derive. Type AddressType `protobuf:"varint,2,opt,name=type,proto3,enum=walletrpc.AddressType" json:"type,omitempty"` // Whether a change address should be derived. - Change bool `protobuf:"varint,3,opt,name=change,proto3" json:"change,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Change bool `protobuf:"varint,3,opt,name=change,proto3" json:"change,omitempty"` } func (x *AddrRequest) Reset() { *x = AddrRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *AddrRequest) String() string { @@ -841,7 +821,7 @@ func (*AddrRequest) ProtoMessage() {} func (x *AddrRequest) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[7] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -878,18 +858,21 @@ func (x *AddrRequest) GetChange() bool { } type AddrResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The address encoded using a bech32 format. - Addr string `protobuf:"bytes,1,opt,name=addr,proto3" json:"addr,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The address encoded using a bech32 format. + Addr string `protobuf:"bytes,1,opt,name=addr,proto3" json:"addr,omitempty"` } func (x *AddrResponse) Reset() { *x = AddrResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *AddrResponse) String() string { @@ -900,7 +883,7 @@ func (*AddrResponse) ProtoMessage() {} func (x *AddrResponse) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[8] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -923,7 +906,10 @@ func (x *AddrResponse) GetAddr() string { } type Account struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The name used to identify the account. Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // The type of addresses the account supports. @@ -950,16 +936,16 @@ type Account struct { // single public keys are imported into. InternalKeyCount uint32 `protobuf:"varint,7,opt,name=internal_key_count,json=internalKeyCount,proto3" json:"internal_key_count,omitempty"` // Whether the wallet stores private keys for the account. - WatchOnly bool `protobuf:"varint,8,opt,name=watch_only,json=watchOnly,proto3" json:"watch_only,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + WatchOnly bool `protobuf:"varint,8,opt,name=watch_only,json=watchOnly,proto3" json:"watch_only,omitempty"` } func (x *Account) Reset() { *x = Account{} - mi := &file_walletrpc_walletkit_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *Account) String() string { @@ -970,7 +956,7 @@ func (*Account) ProtoMessage() {} func (x *Account) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[9] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1042,7 +1028,10 @@ func (x *Account) GetWatchOnly() bool { } type AddressProperty struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The address encoded using the appropriate format depending on the // address type (base58, bech32, bech32m). // @@ -1060,16 +1049,16 @@ type AddressProperty struct { // addresses. DerivationPath string `protobuf:"bytes,4,opt,name=derivation_path,json=derivationPath,proto3" json:"derivation_path,omitempty"` // The public key of the address. This will be empty for imported addresses. - PublicKey []byte `protobuf:"bytes,5,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + PublicKey []byte `protobuf:"bytes,5,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` } func (x *AddressProperty) Reset() { *x = AddressProperty{} - mi := &file_walletrpc_walletkit_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *AddressProperty) String() string { @@ -1080,7 +1069,7 @@ func (*AddressProperty) ProtoMessage() {} func (x *AddressProperty) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[10] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1131,7 +1120,10 @@ func (x *AddressProperty) GetPublicKey() []byte { } type AccountWithAddresses struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The name used to identify the account. Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // The type of addresses the account supports. @@ -1144,16 +1136,16 @@ type AccountWithAddresses struct { // Note that the order of addresses will be random and not according to the // derivation index, since that information is not stored by the underlying // wallet. - Addresses []*AddressProperty `protobuf:"bytes,4,rep,name=addresses,proto3" json:"addresses,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Addresses []*AddressProperty `protobuf:"bytes,4,rep,name=addresses,proto3" json:"addresses,omitempty"` } func (x *AccountWithAddresses) Reset() { *x = AccountWithAddresses{} - mi := &file_walletrpc_walletkit_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *AccountWithAddresses) String() string { @@ -1164,7 +1156,7 @@ func (*AccountWithAddresses) ProtoMessage() {} func (x *AccountWithAddresses) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[11] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1208,20 +1200,23 @@ func (x *AccountWithAddresses) GetAddresses() []*AddressProperty { } type ListAccountsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // An optional filter to only return accounts matching this name. Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // An optional filter to only return accounts matching this address type. - AddressType AddressType `protobuf:"varint,2,opt,name=address_type,json=addressType,proto3,enum=walletrpc.AddressType" json:"address_type,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + AddressType AddressType `protobuf:"varint,2,opt,name=address_type,json=addressType,proto3,enum=walletrpc.AddressType" json:"address_type,omitempty"` } func (x *ListAccountsRequest) Reset() { *x = ListAccountsRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListAccountsRequest) String() string { @@ -1232,7 +1227,7 @@ func (*ListAccountsRequest) ProtoMessage() {} func (x *ListAccountsRequest) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[12] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1262,17 +1257,20 @@ func (x *ListAccountsRequest) GetAddressType() AddressType { } type ListAccountsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Accounts []*Account `protobuf:"bytes,1,rep,name=accounts,proto3" json:"accounts,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Accounts []*Account `protobuf:"bytes,1,rep,name=accounts,proto3" json:"accounts,omitempty"` } func (x *ListAccountsResponse) Reset() { *x = ListAccountsResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListAccountsResponse) String() string { @@ -1283,7 +1281,7 @@ func (*ListAccountsResponse) ProtoMessage() {} func (x *ListAccountsResponse) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[13] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1306,18 +1304,21 @@ func (x *ListAccountsResponse) GetAccounts() []*Account { } type RequiredReserveRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The number of additional channels the user would like to open. AdditionalPublicChannels uint32 `protobuf:"varint,1,opt,name=additional_public_channels,json=additionalPublicChannels,proto3" json:"additional_public_channels,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *RequiredReserveRequest) Reset() { *x = RequiredReserveRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *RequiredReserveRequest) String() string { @@ -1328,7 +1329,7 @@ func (*RequiredReserveRequest) ProtoMessage() {} func (x *RequiredReserveRequest) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[14] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1351,18 +1352,21 @@ func (x *RequiredReserveRequest) GetAdditionalPublicChannels() uint32 { } type RequiredReserveResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The amount of reserve required. RequiredReserve int64 `protobuf:"varint,1,opt,name=required_reserve,json=requiredReserve,proto3" json:"required_reserve,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *RequiredReserveResponse) Reset() { *x = RequiredReserveResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *RequiredReserveResponse) String() string { @@ -1373,7 +1377,7 @@ func (*RequiredReserveResponse) ProtoMessage() {} func (x *RequiredReserveResponse) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[15] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1396,21 +1400,24 @@ func (x *RequiredReserveResponse) GetRequiredReserve() int64 { } type ListAddressesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // An optional filter to only return addresses matching this account. AccountName string `protobuf:"bytes,1,opt,name=account_name,json=accountName,proto3" json:"account_name,omitempty"` // An optional flag to return LND's custom accounts (Purpose=1017) // public key along with other addresses. ShowCustomAccounts bool `protobuf:"varint,2,opt,name=show_custom_accounts,json=showCustomAccounts,proto3" json:"show_custom_accounts,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ListAddressesRequest) Reset() { *x = ListAddressesRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListAddressesRequest) String() string { @@ -1421,7 +1428,7 @@ func (*ListAddressesRequest) ProtoMessage() {} func (x *ListAddressesRequest) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[16] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1451,18 +1458,21 @@ func (x *ListAddressesRequest) GetShowCustomAccounts() bool { } type ListAddressesResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // A list of all the accounts and their addresses. AccountWithAddresses []*AccountWithAddresses `protobuf:"bytes,1,rep,name=account_with_addresses,json=accountWithAddresses,proto3" json:"account_with_addresses,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ListAddressesResponse) Reset() { *x = ListAddressesResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListAddressesResponse) String() string { @@ -1473,7 +1483,7 @@ func (*ListAddressesResponse) ProtoMessage() {} func (x *ListAddressesResponse) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[17] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1496,18 +1506,21 @@ func (x *ListAddressesResponse) GetAccountWithAddresses() []*AccountWithAddresse } type GetTransactionRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The txid of the transaction. - Txid string `protobuf:"bytes,1,opt,name=txid,proto3" json:"txid,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The txid of the transaction. + Txid string `protobuf:"bytes,1,opt,name=txid,proto3" json:"txid,omitempty"` } func (x *GetTransactionRequest) Reset() { *x = GetTransactionRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetTransactionRequest) String() string { @@ -1518,7 +1531,7 @@ func (*GetTransactionRequest) ProtoMessage() {} func (x *GetTransactionRequest) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[18] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1541,22 +1554,25 @@ func (x *GetTransactionRequest) GetTxid() string { } type SignMessageWithAddrRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The message to be signed. When using REST, this field must be encoded as // base64. Msg []byte `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` // The address which will be used to look up the private key and sign the // corresponding message. - Addr string `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Addr string `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"` } func (x *SignMessageWithAddrRequest) Reset() { *x = SignMessageWithAddrRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[19] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SignMessageWithAddrRequest) String() string { @@ -1567,7 +1583,7 @@ func (*SignMessageWithAddrRequest) ProtoMessage() {} func (x *SignMessageWithAddrRequest) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[19] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1597,18 +1613,21 @@ func (x *SignMessageWithAddrRequest) GetAddr() string { } type SignMessageWithAddrResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The compact ECDSA signature for the given message encoded in base64. - Signature string `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The compact ECDSA signature for the given message encoded in base64. + Signature string `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` } func (x *SignMessageWithAddrResponse) Reset() { *x = SignMessageWithAddrResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[20] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SignMessageWithAddrResponse) String() string { @@ -1619,7 +1638,7 @@ func (*SignMessageWithAddrResponse) ProtoMessage() {} func (x *SignMessageWithAddrResponse) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[20] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1642,7 +1661,10 @@ func (x *SignMessageWithAddrResponse) GetSignature() string { } type VerifyMessageWithAddrRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The message to be signed. When using REST, this field must be encoded as // base64. Msg []byte `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` @@ -1651,16 +1673,16 @@ type VerifyMessageWithAddrRequest struct { Signature string `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` // The address which will be used to look up the public key and verify the // the signature. - Addr string `protobuf:"bytes,3,opt,name=addr,proto3" json:"addr,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Addr string `protobuf:"bytes,3,opt,name=addr,proto3" json:"addr,omitempty"` } func (x *VerifyMessageWithAddrRequest) Reset() { *x = VerifyMessageWithAddrRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[21] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *VerifyMessageWithAddrRequest) String() string { @@ -1671,7 +1693,7 @@ func (*VerifyMessageWithAddrRequest) ProtoMessage() {} func (x *VerifyMessageWithAddrRequest) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[21] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1708,20 +1730,23 @@ func (x *VerifyMessageWithAddrRequest) GetAddr() string { } type VerifyMessageWithAddrResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Whether the signature was valid over the given message. Valid bool `protobuf:"varint,1,opt,name=valid,proto3" json:"valid,omitempty"` // The pubkey recovered from the signature. - Pubkey []byte `protobuf:"bytes,2,opt,name=pubkey,proto3" json:"pubkey,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Pubkey []byte `protobuf:"bytes,2,opt,name=pubkey,proto3" json:"pubkey,omitempty"` } func (x *VerifyMessageWithAddrResponse) Reset() { *x = VerifyMessageWithAddrResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[22] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *VerifyMessageWithAddrResponse) String() string { @@ -1732,7 +1757,7 @@ func (*VerifyMessageWithAddrResponse) ProtoMessage() {} func (x *VerifyMessageWithAddrResponse) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[22] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1762,7 +1787,10 @@ func (x *VerifyMessageWithAddrResponse) GetPubkey() []byte { } type ImportAccountRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // A name to identify the account with. Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // A public key that corresponds to a wallet account represented as an extended @@ -1783,16 +1811,16 @@ type ImportAccountRequest struct { // by returning the first N addresses for the external and internal branches of // the account. If these addresses match as expected, then it should be safe to // import the account as is. - DryRun bool `protobuf:"varint,5,opt,name=dry_run,json=dryRun,proto3" json:"dry_run,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + DryRun bool `protobuf:"varint,5,opt,name=dry_run,json=dryRun,proto3" json:"dry_run,omitempty"` } func (x *ImportAccountRequest) Reset() { *x = ImportAccountRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[23] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ImportAccountRequest) String() string { @@ -1803,7 +1831,7 @@ func (*ImportAccountRequest) ProtoMessage() {} func (x *ImportAccountRequest) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[23] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1854,7 +1882,10 @@ func (x *ImportAccountRequest) GetDryRun() bool { } type ImportAccountResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The details of the imported account. Account *Account `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"` // The first N addresses that belong to the external branch of the account. @@ -1865,15 +1896,15 @@ type ImportAccountResponse struct { // The internal branch is typically used for change addresses. These are only // returned if a dry run was specified within the request. DryRunInternalAddrs []string `protobuf:"bytes,3,rep,name=dry_run_internal_addrs,json=dryRunInternalAddrs,proto3" json:"dry_run_internal_addrs,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ImportAccountResponse) Reset() { *x = ImportAccountResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[24] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ImportAccountResponse) String() string { @@ -1884,7 +1915,7 @@ func (*ImportAccountResponse) ProtoMessage() {} func (x *ImportAccountResponse) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[24] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1921,20 +1952,23 @@ func (x *ImportAccountResponse) GetDryRunInternalAddrs() []string { } type ImportPublicKeyRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // A compressed public key represented as raw bytes. PublicKey []byte `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` // The type of address that will be generated from the public key. - AddressType AddressType `protobuf:"varint,2,opt,name=address_type,json=addressType,proto3,enum=walletrpc.AddressType" json:"address_type,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + AddressType AddressType `protobuf:"varint,2,opt,name=address_type,json=addressType,proto3,enum=walletrpc.AddressType" json:"address_type,omitempty"` } func (x *ImportPublicKeyRequest) Reset() { *x = ImportPublicKeyRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[25] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ImportPublicKeyRequest) String() string { @@ -1945,7 +1979,7 @@ func (*ImportPublicKeyRequest) ProtoMessage() {} func (x *ImportPublicKeyRequest) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[25] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1975,18 +2009,21 @@ func (x *ImportPublicKeyRequest) GetAddressType() AddressType { } type ImportPublicKeyResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The status of the import operation. - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The status of the import operation. + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` } func (x *ImportPublicKeyResponse) Reset() { *x = ImportPublicKeyResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[26] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ImportPublicKeyResponse) String() string { @@ -1997,7 +2034,7 @@ func (*ImportPublicKeyResponse) ProtoMessage() {} func (x *ImportPublicKeyResponse) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[26] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2020,25 +2057,28 @@ func (x *ImportPublicKeyResponse) GetStatus() string { } type ImportTapscriptRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The internal public key, serialized as 32-byte x-only public key. InternalPublicKey []byte `protobuf:"bytes,1,opt,name=internal_public_key,json=internalPublicKey,proto3" json:"internal_public_key,omitempty"` - // Types that are valid to be assigned to Script: + // Types that are assignable to Script: // // *ImportTapscriptRequest_FullTree // *ImportTapscriptRequest_PartialReveal // *ImportTapscriptRequest_RootHashOnly // *ImportTapscriptRequest_FullKeyOnly - Script isImportTapscriptRequest_Script `protobuf_oneof:"script"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Script isImportTapscriptRequest_Script `protobuf_oneof:"script"` } func (x *ImportTapscriptRequest) Reset() { *x = ImportTapscriptRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[27] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ImportTapscriptRequest) String() string { @@ -2049,7 +2089,7 @@ func (*ImportTapscriptRequest) ProtoMessage() {} func (x *ImportTapscriptRequest) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[27] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2071,45 +2111,37 @@ func (x *ImportTapscriptRequest) GetInternalPublicKey() []byte { return nil } -func (x *ImportTapscriptRequest) GetScript() isImportTapscriptRequest_Script { - if x != nil { - return x.Script +func (m *ImportTapscriptRequest) GetScript() isImportTapscriptRequest_Script { + if m != nil { + return m.Script } return nil } func (x *ImportTapscriptRequest) GetFullTree() *TapscriptFullTree { - if x != nil { - if x, ok := x.Script.(*ImportTapscriptRequest_FullTree); ok { - return x.FullTree - } + if x, ok := x.GetScript().(*ImportTapscriptRequest_FullTree); ok { + return x.FullTree } return nil } func (x *ImportTapscriptRequest) GetPartialReveal() *TapscriptPartialReveal { - if x != nil { - if x, ok := x.Script.(*ImportTapscriptRequest_PartialReveal); ok { - return x.PartialReveal - } + if x, ok := x.GetScript().(*ImportTapscriptRequest_PartialReveal); ok { + return x.PartialReveal } return nil } func (x *ImportTapscriptRequest) GetRootHashOnly() []byte { - if x != nil { - if x, ok := x.Script.(*ImportTapscriptRequest_RootHashOnly); ok { - return x.RootHashOnly - } + if x, ok := x.GetScript().(*ImportTapscriptRequest_RootHashOnly); ok { + return x.RootHashOnly } return nil } func (x *ImportTapscriptRequest) GetFullKeyOnly() bool { - if x != nil { - if x, ok := x.Script.(*ImportTapscriptRequest_FullKeyOnly); ok { - return x.FullKeyOnly - } + if x, ok := x.GetScript().(*ImportTapscriptRequest_FullKeyOnly); ok { + return x.FullKeyOnly } return false } @@ -2155,18 +2187,21 @@ func (*ImportTapscriptRequest_RootHashOnly) isImportTapscriptRequest_Script() {} func (*ImportTapscriptRequest_FullKeyOnly) isImportTapscriptRequest_Script() {} type TapscriptFullTree struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The complete, ordered list of all tap leaves of the tree. - AllLeaves []*TapLeaf `protobuf:"bytes,1,rep,name=all_leaves,json=allLeaves,proto3" json:"all_leaves,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The complete, ordered list of all tap leaves of the tree. + AllLeaves []*TapLeaf `protobuf:"bytes,1,rep,name=all_leaves,json=allLeaves,proto3" json:"all_leaves,omitempty"` } func (x *TapscriptFullTree) Reset() { *x = TapscriptFullTree{} - mi := &file_walletrpc_walletkit_proto_msgTypes[28] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *TapscriptFullTree) String() string { @@ -2177,7 +2212,7 @@ func (*TapscriptFullTree) ProtoMessage() {} func (x *TapscriptFullTree) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[28] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2200,20 +2235,23 @@ func (x *TapscriptFullTree) GetAllLeaves() []*TapLeaf { } type TapLeaf struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The leaf version. Should be 0xc0 (192) in case of a SegWit v1 script. LeafVersion uint32 `protobuf:"varint,1,opt,name=leaf_version,json=leafVersion,proto3" json:"leaf_version,omitempty"` // The script of the tap leaf. - Script []byte `protobuf:"bytes,2,opt,name=script,proto3" json:"script,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Script []byte `protobuf:"bytes,2,opt,name=script,proto3" json:"script,omitempty"` } func (x *TapLeaf) Reset() { *x = TapLeaf{} - mi := &file_walletrpc_walletkit_proto_msgTypes[29] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *TapLeaf) String() string { @@ -2224,7 +2262,7 @@ func (*TapLeaf) ProtoMessage() {} func (x *TapLeaf) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[29] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2254,7 +2292,10 @@ func (x *TapLeaf) GetScript() []byte { } type TapscriptPartialReveal struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The tap leaf that is known and will be revealed. RevealedLeaf *TapLeaf `protobuf:"bytes,1,opt,name=revealed_leaf,json=revealedLeaf,proto3" json:"revealed_leaf,omitempty"` // The BIP-0341 serialized inclusion proof that is required to prove that @@ -2262,15 +2303,15 @@ type TapscriptPartialReveal struct { // bytes. If the tree only contained a single leaf (which is the revealed // leaf), this can be empty. FullInclusionProof []byte `protobuf:"bytes,2,opt,name=full_inclusion_proof,json=fullInclusionProof,proto3" json:"full_inclusion_proof,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *TapscriptPartialReveal) Reset() { *x = TapscriptPartialReveal{} - mi := &file_walletrpc_walletkit_proto_msgTypes[30] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *TapscriptPartialReveal) String() string { @@ -2281,7 +2322,7 @@ func (*TapscriptPartialReveal) ProtoMessage() {} func (x *TapscriptPartialReveal) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[30] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2311,19 +2352,22 @@ func (x *TapscriptPartialReveal) GetFullInclusionProof() []byte { } type ImportTapscriptResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The resulting pay-to-Taproot address that represents the imported internal // key with the script committed to it. - P2TrAddress string `protobuf:"bytes,1,opt,name=p2tr_address,json=p2trAddress,proto3" json:"p2tr_address,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + P2TrAddress string `protobuf:"bytes,1,opt,name=p2tr_address,json=p2trAddress,proto3" json:"p2tr_address,omitempty"` } func (x *ImportTapscriptResponse) Reset() { *x = ImportTapscriptResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[31] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ImportTapscriptResponse) String() string { @@ -2334,7 +2378,7 @@ func (*ImportTapscriptResponse) ProtoMessage() {} func (x *ImportTapscriptResponse) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[31] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2357,22 +2401,25 @@ func (x *ImportTapscriptResponse) GetP2TrAddress() string { } type Transaction struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The raw serialized transaction. Despite the field name, this does need to be // specified in raw bytes (or base64 encoded when using REST) and not in hex. // To not break existing software, the field can't simply be renamed. TxHex []byte `protobuf:"bytes,1,opt,name=tx_hex,json=txHex,proto3" json:"tx_hex,omitempty"` // An optional label to save with the transaction. Limited to 500 characters. - Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"` } func (x *Transaction) Reset() { *x = Transaction{} - mi := &file_walletrpc_walletkit_proto_msgTypes[32] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *Transaction) String() string { @@ -2383,7 +2430,7 @@ func (*Transaction) ProtoMessage() {} func (x *Transaction) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[32] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2413,22 +2460,25 @@ func (x *Transaction) GetLabel() string { } type PublishResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // If blank, then no error occurred and the transaction was successfully // published. If not the empty string, then a string representation of the // broadcast error. // // TODO(roasbeef): map to a proper enum type - PublishError string `protobuf:"bytes,1,opt,name=publish_error,json=publishError,proto3" json:"publish_error,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + PublishError string `protobuf:"bytes,1,opt,name=publish_error,json=publishError,proto3" json:"publish_error,omitempty"` } func (x *PublishResponse) Reset() { *x = PublishResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[33] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PublishResponse) String() string { @@ -2439,7 +2489,7 @@ func (*PublishResponse) ProtoMessage() {} func (x *PublishResponse) ProtoReflect() protoreflect.Message { mi := &file_walletrpc_walletkit_proto_msgTypes[33] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2461,205 +2511,22 @@ func (x *PublishResponse) GetPublishError() string { return "" } -type SubmitPackageRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The raw serialized transactions forming the package, topologically sorted - // with unconfirmed parents first and the child last. - RawTxs [][]byte `protobuf:"bytes,1,rep,name=raw_txs,json=rawTxs,proto3" json:"raw_txs,omitempty"` - // Optional per-transaction fee-rate ceiling in sat/vByte (mapped onto the - // submitpackage maxfeerate). When unset the node's default is used; an - // explicit 0 means no limit, which is required for a CPFP child whose - // standalone feerate is high. - SatPerVbyte *uint64 `protobuf:"varint,2,opt,name=sat_per_vbyte,json=satPerVbyte,proto3,oneof" json:"sat_per_vbyte,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *SubmitPackageRequest) Reset() { - *x = SubmitPackageRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[34] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *SubmitPackageRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SubmitPackageRequest) ProtoMessage() {} - -func (x *SubmitPackageRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[34] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SubmitPackageRequest.ProtoReflect.Descriptor instead. -func (*SubmitPackageRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{34} -} - -func (x *SubmitPackageRequest) GetRawTxs() [][]byte { - if x != nil { - return x.RawTxs - } - return nil -} - -func (x *SubmitPackageRequest) GetSatPerVbyte() uint64 { - if x != nil && x.SatPerVbyte != nil { - return *x.SatPerVbyte - } - return 0 -} - -type SubmitPackageTxResult struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The transaction id (txid) in hex. - Txid string `protobuf:"bytes,1,opt,name=txid,proto3" json:"txid,omitempty"` - // If non-empty, the reason this transaction was rejected. - Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` - // If non-empty, the wtxid (in hex) of a transaction with the same txid but a - // different witness that was already in the mempool; the submitted - // transaction was ignored as a duplicate (witness replacement). - OtherWtxid string `protobuf:"bytes,3,opt,name=other_wtxid,json=otherWtxid,proto3" json:"other_wtxid,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *SubmitPackageTxResult) Reset() { - *x = SubmitPackageTxResult{} - mi := &file_walletrpc_walletkit_proto_msgTypes[35] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *SubmitPackageTxResult) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SubmitPackageTxResult) ProtoMessage() {} - -func (x *SubmitPackageTxResult) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[35] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SubmitPackageTxResult.ProtoReflect.Descriptor instead. -func (*SubmitPackageTxResult) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{35} -} - -func (x *SubmitPackageTxResult) GetTxid() string { - if x != nil { - return x.Txid - } - return "" -} - -func (x *SubmitPackageTxResult) GetError() string { - if x != nil { - return x.Error - } - return "" -} - -func (x *SubmitPackageTxResult) GetOtherWtxid() string { - if x != nil { - return x.OtherWtxid - } - return "" -} - -type SubmitPackageResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // A summary message; "success" when the whole package was accepted. - PackageMsg string `protobuf:"bytes,1,opt,name=package_msg,json=packageMsg,proto3" json:"package_msg,omitempty"` - // Per-transaction results keyed by wtxid (hex). - TxResults map[string]*SubmitPackageTxResult `protobuf:"bytes,2,rep,name=tx_results,json=txResults,proto3" json:"tx_results,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - // The txids of transactions evicted via package RBF. - ReplacedTransactions []string `protobuf:"bytes,3,rep,name=replaced_transactions,json=replacedTransactions,proto3" json:"replaced_transactions,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *SubmitPackageResponse) Reset() { - *x = SubmitPackageResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[36] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *SubmitPackageResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SubmitPackageResponse) ProtoMessage() {} - -func (x *SubmitPackageResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[36] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SubmitPackageResponse.ProtoReflect.Descriptor instead. -func (*SubmitPackageResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{36} -} - -func (x *SubmitPackageResponse) GetPackageMsg() string { - if x != nil { - return x.PackageMsg - } - return "" -} - -func (x *SubmitPackageResponse) GetTxResults() map[string]*SubmitPackageTxResult { - if x != nil { - return x.TxResults - } - return nil -} - -func (x *SubmitPackageResponse) GetReplacedTransactions() []string { - if x != nil { - return x.ReplacedTransactions - } - return nil -} - type RemoveTransactionResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The status of the remove transaction operation. - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The status of the remove transaction operation. + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` } func (x *RemoveTransactionResponse) Reset() { *x = RemoveTransactionResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[37] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *RemoveTransactionResponse) String() string { @@ -2669,8 +2536,8 @@ func (x *RemoveTransactionResponse) String() string { func (*RemoveTransactionResponse) ProtoMessage() {} func (x *RemoveTransactionResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[37] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[34] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2682,7 +2549,7 @@ func (x *RemoveTransactionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveTransactionResponse.ProtoReflect.Descriptor instead. func (*RemoveTransactionResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{37} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{34} } func (x *RemoveTransactionResponse) GetStatus() string { @@ -2693,7 +2560,10 @@ func (x *RemoveTransactionResponse) GetStatus() string { } type SendOutputsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The number of satoshis per kilo weight that should be used when crafting // this transaction. SatPerKw int64 `protobuf:"varint,1,opt,name=sat_per_kw,json=satPerKw,proto3" json:"sat_per_kw,omitempty"` @@ -2708,15 +2578,15 @@ type SendOutputsRequest struct { SpendUnconfirmed bool `protobuf:"varint,5,opt,name=spend_unconfirmed,json=spendUnconfirmed,proto3" json:"spend_unconfirmed,omitempty"` // The strategy to use for selecting coins during sending the outputs. CoinSelectionStrategy lnrpc.CoinSelectionStrategy `protobuf:"varint,6,opt,name=coin_selection_strategy,json=coinSelectionStrategy,proto3,enum=lnrpc.CoinSelectionStrategy" json:"coin_selection_strategy,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *SendOutputsRequest) Reset() { *x = SendOutputsRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[38] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SendOutputsRequest) String() string { @@ -2726,8 +2596,8 @@ func (x *SendOutputsRequest) String() string { func (*SendOutputsRequest) ProtoMessage() {} func (x *SendOutputsRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[38] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[35] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2739,7 +2609,7 @@ func (x *SendOutputsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SendOutputsRequest.ProtoReflect.Descriptor instead. func (*SendOutputsRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{38} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{35} } func (x *SendOutputsRequest) GetSatPerKw() int64 { @@ -2785,18 +2655,21 @@ func (x *SendOutputsRequest) GetCoinSelectionStrategy() lnrpc.CoinSelectionStrat } type SendOutputsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The serialized transaction sent out on the network. - RawTx []byte `protobuf:"bytes,1,opt,name=raw_tx,json=rawTx,proto3" json:"raw_tx,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The serialized transaction sent out on the network. + RawTx []byte `protobuf:"bytes,1,opt,name=raw_tx,json=rawTx,proto3" json:"raw_tx,omitempty"` } func (x *SendOutputsResponse) Reset() { *x = SendOutputsResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[39] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SendOutputsResponse) String() string { @@ -2806,8 +2679,8 @@ func (x *SendOutputsResponse) String() string { func (*SendOutputsResponse) ProtoMessage() {} func (x *SendOutputsResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[39] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[36] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2819,7 +2692,7 @@ func (x *SendOutputsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SendOutputsResponse.ProtoReflect.Descriptor instead. func (*SendOutputsResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{39} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{36} } func (x *SendOutputsResponse) GetRawTx() []byte { @@ -2830,18 +2703,21 @@ func (x *SendOutputsResponse) GetRawTx() []byte { } type EstimateFeeRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The number of confirmations to shoot for when estimating the fee. - ConfTarget int32 `protobuf:"varint,1,opt,name=conf_target,json=confTarget,proto3" json:"conf_target,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The number of confirmations to shoot for when estimating the fee. + ConfTarget int32 `protobuf:"varint,1,opt,name=conf_target,json=confTarget,proto3" json:"conf_target,omitempty"` } func (x *EstimateFeeRequest) Reset() { *x = EstimateFeeRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[40] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *EstimateFeeRequest) String() string { @@ -2851,8 +2727,8 @@ func (x *EstimateFeeRequest) String() string { func (*EstimateFeeRequest) ProtoMessage() {} func (x *EstimateFeeRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[40] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[37] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2864,7 +2740,7 @@ func (x *EstimateFeeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EstimateFeeRequest.ProtoReflect.Descriptor instead. func (*EstimateFeeRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{40} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{37} } func (x *EstimateFeeRequest) GetConfTarget() int32 { @@ -2875,21 +2751,24 @@ func (x *EstimateFeeRequest) GetConfTarget() int32 { } type EstimateFeeResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The amount of satoshis per kw that should be used in order to reach the // confirmation target in the request. SatPerKw int64 `protobuf:"varint,1,opt,name=sat_per_kw,json=satPerKw,proto3" json:"sat_per_kw,omitempty"` // The current minimum relay fee based on our chain backend in sat/kw. MinRelayFeeSatPerKw int64 `protobuf:"varint,2,opt,name=min_relay_fee_sat_per_kw,json=minRelayFeeSatPerKw,proto3" json:"min_relay_fee_sat_per_kw,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *EstimateFeeResponse) Reset() { *x = EstimateFeeResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[41] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *EstimateFeeResponse) String() string { @@ -2899,8 +2778,8 @@ func (x *EstimateFeeResponse) String() string { func (*EstimateFeeResponse) ProtoMessage() {} func (x *EstimateFeeResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[41] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[38] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2912,7 +2791,7 @@ func (x *EstimateFeeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EstimateFeeResponse.ProtoReflect.Descriptor instead. func (*EstimateFeeResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{41} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{38} } func (x *EstimateFeeResponse) GetSatPerKw() int64 { @@ -2930,7 +2809,10 @@ func (x *EstimateFeeResponse) GetMinRelayFeeSatPerKw() int64 { } type PendingSweep struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The outpoint of the output we're attempting to sweep. Outpoint *lnrpc.OutPoint `protobuf:"bytes,1,opt,name=outpoint,proto3" json:"outpoint,omitempty"` // The witness type of the output we're attempting to sweep. @@ -2986,15 +2868,15 @@ type PendingSweep struct { // The block height which the input's locktime will expire at. Zero if the // input has no locktime. MaturityHeight uint32 `protobuf:"varint,15,opt,name=maturity_height,json=maturityHeight,proto3" json:"maturity_height,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *PendingSweep) Reset() { *x = PendingSweep{} - mi := &file_walletrpc_walletkit_proto_msgTypes[42] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[39] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PendingSweep) String() string { @@ -3004,8 +2886,8 @@ func (x *PendingSweep) String() string { func (*PendingSweep) ProtoMessage() {} func (x *PendingSweep) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[42] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[39] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3017,7 +2899,7 @@ func (x *PendingSweep) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingSweep.ProtoReflect.Descriptor instead. func (*PendingSweep) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{42} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{39} } func (x *PendingSweep) GetOutpoint() *lnrpc.OutPoint { @@ -3131,16 +3013,18 @@ func (x *PendingSweep) GetMaturityHeight() uint32 { } type PendingSweepsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *PendingSweepsRequest) Reset() { *x = PendingSweepsRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[43] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PendingSweepsRequest) String() string { @@ -3150,8 +3034,8 @@ func (x *PendingSweepsRequest) String() string { func (*PendingSweepsRequest) ProtoMessage() {} func (x *PendingSweepsRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[43] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[40] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3163,22 +3047,25 @@ func (x *PendingSweepsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingSweepsRequest.ProtoReflect.Descriptor instead. func (*PendingSweepsRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{43} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{40} } type PendingSweepsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The set of outputs currently being swept by lnd's central batching engine. PendingSweeps []*PendingSweep `protobuf:"bytes,1,rep,name=pending_sweeps,json=pendingSweeps,proto3" json:"pending_sweeps,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *PendingSweepsResponse) Reset() { *x = PendingSweepsResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[44] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PendingSweepsResponse) String() string { @@ -3188,8 +3075,8 @@ func (x *PendingSweepsResponse) String() string { func (*PendingSweepsResponse) ProtoMessage() {} func (x *PendingSweepsResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[44] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[41] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3201,7 +3088,7 @@ func (x *PendingSweepsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingSweepsResponse.ProtoReflect.Descriptor instead. func (*PendingSweepsResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{44} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{41} } func (x *PendingSweepsResponse) GetPendingSweeps() []*PendingSweep { @@ -3212,7 +3099,10 @@ func (x *PendingSweepsResponse) GetPendingSweeps() []*PendingSweep { } type BumpFeeRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The input we're attempting to bump the fee of. Outpoint *lnrpc.OutPoint `protobuf:"bytes,1,opt,name=outpoint,proto3" json:"outpoint,omitempty"` // Optional. The conf target the underlying fee estimator will use to @@ -3250,15 +3140,15 @@ type BumpFeeRequest struct { // fee function that the sweeper will use to bump the fee rate. When the // deadline is reached, ALL the budget will be spent as fees. DeadlineDelta uint32 `protobuf:"varint,8,opt,name=deadline_delta,json=deadlineDelta,proto3" json:"deadline_delta,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *BumpFeeRequest) Reset() { *x = BumpFeeRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[45] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[42] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *BumpFeeRequest) String() string { @@ -3268,8 +3158,8 @@ func (x *BumpFeeRequest) String() string { func (*BumpFeeRequest) ProtoMessage() {} func (x *BumpFeeRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[45] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[42] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3281,7 +3171,7 @@ func (x *BumpFeeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BumpFeeRequest.ProtoReflect.Descriptor instead. func (*BumpFeeRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{45} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{42} } func (x *BumpFeeRequest) GetOutpoint() *lnrpc.OutPoint { @@ -3343,18 +3233,21 @@ func (x *BumpFeeRequest) GetDeadlineDelta() uint32 { } type BumpFeeResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The status of the bump fee operation. - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The status of the bump fee operation. + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` } func (x *BumpFeeResponse) Reset() { *x = BumpFeeResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[46] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[43] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *BumpFeeResponse) String() string { @@ -3364,8 +3257,8 @@ func (x *BumpFeeResponse) String() string { func (*BumpFeeResponse) ProtoMessage() {} func (x *BumpFeeResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[46] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[43] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3377,7 +3270,7 @@ func (x *BumpFeeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BumpFeeResponse.ProtoReflect.Descriptor instead. func (*BumpFeeResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{46} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{43} } func (x *BumpFeeResponse) GetStatus() string { @@ -3388,7 +3281,10 @@ func (x *BumpFeeResponse) GetStatus() string { } type BumpForceCloseFeeRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The channel point which force close transaction we are attempting to // bump the fee rate for. ChanPoint *lnrpc.ChannelPoint `protobuf:"bytes,1,opt,name=chan_point,json=chanPoint,proto3" json:"chan_point,omitempty"` @@ -3414,16 +3310,16 @@ type BumpForceCloseFeeRequest struct { Budget uint64 `protobuf:"varint,5,opt,name=budget,proto3" json:"budget,omitempty"` // Optional. The conf target the underlying fee estimator will use to // estimate the starting fee rate for the fee function. - TargetConf uint32 `protobuf:"varint,6,opt,name=target_conf,json=targetConf,proto3" json:"target_conf,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + TargetConf uint32 `protobuf:"varint,6,opt,name=target_conf,json=targetConf,proto3" json:"target_conf,omitempty"` } func (x *BumpForceCloseFeeRequest) Reset() { *x = BumpForceCloseFeeRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[47] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[44] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *BumpForceCloseFeeRequest) String() string { @@ -3433,8 +3329,8 @@ func (x *BumpForceCloseFeeRequest) String() string { func (*BumpForceCloseFeeRequest) ProtoMessage() {} func (x *BumpForceCloseFeeRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[47] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[44] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3446,7 +3342,7 @@ func (x *BumpForceCloseFeeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BumpForceCloseFeeRequest.ProtoReflect.Descriptor instead. func (*BumpForceCloseFeeRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{47} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{44} } func (x *BumpForceCloseFeeRequest) GetChanPoint() *lnrpc.ChannelPoint { @@ -3492,18 +3388,21 @@ func (x *BumpForceCloseFeeRequest) GetTargetConf() uint32 { } type BumpForceCloseFeeResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The status of the force close fee bump operation. - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The status of the force close fee bump operation. + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` } func (x *BumpForceCloseFeeResponse) Reset() { *x = BumpForceCloseFeeResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[48] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[45] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *BumpForceCloseFeeResponse) String() string { @@ -3513,8 +3412,8 @@ func (x *BumpForceCloseFeeResponse) String() string { func (*BumpForceCloseFeeResponse) ProtoMessage() {} func (x *BumpForceCloseFeeResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[48] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[45] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3526,7 +3425,7 @@ func (x *BumpForceCloseFeeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BumpForceCloseFeeResponse.ProtoReflect.Descriptor instead. func (*BumpForceCloseFeeResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{48} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{45} } func (x *BumpForceCloseFeeResponse) GetStatus() string { @@ -3537,7 +3436,10 @@ func (x *BumpForceCloseFeeResponse) GetStatus() string { } type ListSweepsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Retrieve the full sweep transaction details. If false, only the sweep txids // will be returned. Note that some sweeps that LND publishes will have been // replaced-by-fee, so will not be included in this output. @@ -3545,16 +3447,16 @@ type ListSweepsRequest struct { // The start height to use when fetching sweeps. If not specified (0), the // result will start from the earliest sweep. If set to -1 the result will // only include unconfirmed sweeps (at the time of the call). - StartHeight int32 `protobuf:"varint,2,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + StartHeight int32 `protobuf:"varint,2,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty"` } func (x *ListSweepsRequest) Reset() { *x = ListSweepsRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[49] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[46] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListSweepsRequest) String() string { @@ -3564,8 +3466,8 @@ func (x *ListSweepsRequest) String() string { func (*ListSweepsRequest) ProtoMessage() {} func (x *ListSweepsRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[49] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[46] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3577,7 +3479,7 @@ func (x *ListSweepsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSweepsRequest.ProtoReflect.Descriptor instead. func (*ListSweepsRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{49} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{46} } func (x *ListSweepsRequest) GetVerbose() bool { @@ -3595,21 +3497,24 @@ func (x *ListSweepsRequest) GetStartHeight() int32 { } type ListSweepsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to Sweeps: + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Sweeps: // // *ListSweepsResponse_TransactionDetails // *ListSweepsResponse_TransactionIds - Sweeps isListSweepsResponse_Sweeps `protobuf_oneof:"sweeps"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Sweeps isListSweepsResponse_Sweeps `protobuf_oneof:"sweeps"` } func (x *ListSweepsResponse) Reset() { *x = ListSweepsResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[50] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[47] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListSweepsResponse) String() string { @@ -3619,8 +3524,8 @@ func (x *ListSweepsResponse) String() string { func (*ListSweepsResponse) ProtoMessage() {} func (x *ListSweepsResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[50] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[47] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3632,30 +3537,26 @@ func (x *ListSweepsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSweepsResponse.ProtoReflect.Descriptor instead. func (*ListSweepsResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{50} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{47} } -func (x *ListSweepsResponse) GetSweeps() isListSweepsResponse_Sweeps { - if x != nil { - return x.Sweeps +func (m *ListSweepsResponse) GetSweeps() isListSweepsResponse_Sweeps { + if m != nil { + return m.Sweeps } return nil } func (x *ListSweepsResponse) GetTransactionDetails() *lnrpc.TransactionDetails { - if x != nil { - if x, ok := x.Sweeps.(*ListSweepsResponse_TransactionDetails); ok { - return x.TransactionDetails - } + if x, ok := x.GetSweeps().(*ListSweepsResponse_TransactionDetails); ok { + return x.TransactionDetails } return nil } func (x *ListSweepsResponse) GetTransactionIds() *ListSweepsResponse_TransactionIDs { - if x != nil { - if x, ok := x.Sweeps.(*ListSweepsResponse_TransactionIds); ok { - return x.TransactionIds - } + if x, ok := x.GetSweeps().(*ListSweepsResponse_TransactionIds); ok { + return x.TransactionIds } return nil } @@ -3677,23 +3578,26 @@ func (*ListSweepsResponse_TransactionDetails) isListSweepsResponse_Sweeps() {} func (*ListSweepsResponse_TransactionIds) isListSweepsResponse_Sweeps() {} type LabelTransactionRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The txid of the transaction to label. Note: When using gRPC, the bytes // must be in little-endian (reverse) order. Txid []byte `protobuf:"bytes,1,opt,name=txid,proto3" json:"txid,omitempty"` // The label to add to the transaction, limited to 500 characters. Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"` // Whether to overwrite the existing label, if it is present. - Overwrite bool `protobuf:"varint,3,opt,name=overwrite,proto3" json:"overwrite,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Overwrite bool `protobuf:"varint,3,opt,name=overwrite,proto3" json:"overwrite,omitempty"` } func (x *LabelTransactionRequest) Reset() { *x = LabelTransactionRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[51] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[48] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *LabelTransactionRequest) String() string { @@ -3703,8 +3607,8 @@ func (x *LabelTransactionRequest) String() string { func (*LabelTransactionRequest) ProtoMessage() {} func (x *LabelTransactionRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[51] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[48] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3716,7 +3620,7 @@ func (x *LabelTransactionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LabelTransactionRequest.ProtoReflect.Descriptor instead. func (*LabelTransactionRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{51} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{48} } func (x *LabelTransactionRequest) GetTxid() []byte { @@ -3741,18 +3645,21 @@ func (x *LabelTransactionRequest) GetOverwrite() bool { } type LabelTransactionResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The status of the label operation. - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The status of the label operation. + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` } func (x *LabelTransactionResponse) Reset() { *x = LabelTransactionResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[52] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[49] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *LabelTransactionResponse) String() string { @@ -3762,8 +3669,8 @@ func (x *LabelTransactionResponse) String() string { func (*LabelTransactionResponse) ProtoMessage() {} func (x *LabelTransactionResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[52] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[49] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3775,7 +3682,7 @@ func (x *LabelTransactionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LabelTransactionResponse.ProtoReflect.Descriptor instead. func (*LabelTransactionResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{52} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{49} } func (x *LabelTransactionResponse) GetStatus() string { @@ -3786,14 +3693,17 @@ func (x *LabelTransactionResponse) GetStatus() string { } type FundPsbtRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to Template: + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Template: // // *FundPsbtRequest_Psbt // *FundPsbtRequest_Raw // *FundPsbtRequest_CoinSelect Template isFundPsbtRequest_Template `protobuf_oneof:"template"` - // Types that are valid to be assigned to Fees: + // Types that are assignable to Fees: // // *FundPsbtRequest_TargetConf // *FundPsbtRequest_SatPerVbyte @@ -3824,15 +3734,15 @@ type FundPsbtRequest struct { // specified duration. The lock duration is specified in seconds. If not // set, the default lock duration will be used. LockExpirationSeconds uint64 `protobuf:"varint,14,opt,name=lock_expiration_seconds,json=lockExpirationSeconds,proto3" json:"lock_expiration_seconds,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *FundPsbtRequest) Reset() { *x = FundPsbtRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[53] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[50] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *FundPsbtRequest) String() string { @@ -3842,8 +3752,8 @@ func (x *FundPsbtRequest) String() string { func (*FundPsbtRequest) ProtoMessage() {} func (x *FundPsbtRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[53] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[50] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3855,73 +3765,61 @@ func (x *FundPsbtRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FundPsbtRequest.ProtoReflect.Descriptor instead. func (*FundPsbtRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{53} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{50} } -func (x *FundPsbtRequest) GetTemplate() isFundPsbtRequest_Template { - if x != nil { - return x.Template +func (m *FundPsbtRequest) GetTemplate() isFundPsbtRequest_Template { + if m != nil { + return m.Template } return nil } func (x *FundPsbtRequest) GetPsbt() []byte { - if x != nil { - if x, ok := x.Template.(*FundPsbtRequest_Psbt); ok { - return x.Psbt - } + if x, ok := x.GetTemplate().(*FundPsbtRequest_Psbt); ok { + return x.Psbt } return nil } func (x *FundPsbtRequest) GetRaw() *TxTemplate { - if x != nil { - if x, ok := x.Template.(*FundPsbtRequest_Raw); ok { - return x.Raw - } + if x, ok := x.GetTemplate().(*FundPsbtRequest_Raw); ok { + return x.Raw } return nil } func (x *FundPsbtRequest) GetCoinSelect() *PsbtCoinSelect { - if x != nil { - if x, ok := x.Template.(*FundPsbtRequest_CoinSelect); ok { - return x.CoinSelect - } + if x, ok := x.GetTemplate().(*FundPsbtRequest_CoinSelect); ok { + return x.CoinSelect } return nil } -func (x *FundPsbtRequest) GetFees() isFundPsbtRequest_Fees { - if x != nil { - return x.Fees +func (m *FundPsbtRequest) GetFees() isFundPsbtRequest_Fees { + if m != nil { + return m.Fees } return nil } func (x *FundPsbtRequest) GetTargetConf() uint32 { - if x != nil { - if x, ok := x.Fees.(*FundPsbtRequest_TargetConf); ok { - return x.TargetConf - } + if x, ok := x.GetFees().(*FundPsbtRequest_TargetConf); ok { + return x.TargetConf } return 0 } func (x *FundPsbtRequest) GetSatPerVbyte() uint64 { - if x != nil { - if x, ok := x.Fees.(*FundPsbtRequest_SatPerVbyte); ok { - return x.SatPerVbyte - } + if x, ok := x.GetFees().(*FundPsbtRequest_SatPerVbyte); ok { + return x.SatPerVbyte } return 0 } func (x *FundPsbtRequest) GetSatPerKw() uint64 { - if x != nil { - if x, ok := x.Fees.(*FundPsbtRequest_SatPerKw); ok { - return x.SatPerKw - } + if x, ok := x.GetFees().(*FundPsbtRequest_SatPerKw); ok { + return x.SatPerKw } return 0 } @@ -4057,7 +3955,10 @@ func (*FundPsbtRequest_SatPerVbyte) isFundPsbtRequest_Fees() {} func (*FundPsbtRequest_SatPerKw) isFundPsbtRequest_Fees() {} type FundPsbtResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The funded but not yet signed PSBT packet. FundedPsbt []byte `protobuf:"bytes,1,opt,name=funded_psbt,json=fundedPsbt,proto3" json:"funded_psbt,omitempty"` // The index of the added change output or -1 if no change was left over. @@ -4065,16 +3966,16 @@ type FundPsbtResponse struct { // The list of lock leases that were acquired for the inputs in the funded PSBT // packet. Only inputs added to the PSBT by this RPC are locked, inputs that // were already present in the PSBT are not locked. - LockedUtxos []*UtxoLease `protobuf:"bytes,3,rep,name=locked_utxos,json=lockedUtxos,proto3" json:"locked_utxos,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + LockedUtxos []*UtxoLease `protobuf:"bytes,3,rep,name=locked_utxos,json=lockedUtxos,proto3" json:"locked_utxos,omitempty"` } func (x *FundPsbtResponse) Reset() { *x = FundPsbtResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[54] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[51] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *FundPsbtResponse) String() string { @@ -4084,8 +3985,8 @@ func (x *FundPsbtResponse) String() string { func (*FundPsbtResponse) ProtoMessage() {} func (x *FundPsbtResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[54] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[51] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4097,7 +3998,7 @@ func (x *FundPsbtResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FundPsbtResponse.ProtoReflect.Descriptor instead. func (*FundPsbtResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{54} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{51} } func (x *FundPsbtResponse) GetFundedPsbt() []byte { @@ -4122,7 +4023,10 @@ func (x *FundPsbtResponse) GetLockedUtxos() []*UtxoLease { } type TxTemplate struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // An optional list of inputs to use. Every input must be an UTXO known to the // wallet that has not been locked before. The sum of all inputs must be // sufficiently greater than the sum of all outputs to pay a miner fee with the @@ -4132,16 +4036,16 @@ type TxTemplate struct { // inputs of sufficient value will be added to the resulting PSBT. Inputs []*lnrpc.OutPoint `protobuf:"bytes,1,rep,name=inputs,proto3" json:"inputs,omitempty"` // A map of all addresses and the amounts to send to in the funded PSBT. - Outputs map[string]uint64 `protobuf:"bytes,2,rep,name=outputs,proto3" json:"outputs,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Outputs map[string]uint64 `protobuf:"bytes,2,rep,name=outputs,proto3" json:"outputs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` } func (x *TxTemplate) Reset() { *x = TxTemplate{} - mi := &file_walletrpc_walletkit_proto_msgTypes[55] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[52] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *TxTemplate) String() string { @@ -4151,8 +4055,8 @@ func (x *TxTemplate) String() string { func (*TxTemplate) ProtoMessage() {} func (x *TxTemplate) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[55] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[52] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4164,7 +4068,7 @@ func (x *TxTemplate) ProtoReflect() protoreflect.Message { // Deprecated: Use TxTemplate.ProtoReflect.Descriptor instead. func (*TxTemplate) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{55} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{52} } func (x *TxTemplate) GetInputs() []*lnrpc.OutPoint { @@ -4182,7 +4086,10 @@ func (x *TxTemplate) GetOutputs() map[string]uint64 { } type PsbtCoinSelect struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The template to use for the funded PSBT. The template must contain at least // one non-dust output. The amount to be funded is calculated by summing up the // amounts of all outputs in the template, subtracting all the input values of @@ -4193,20 +4100,20 @@ type PsbtCoinSelect struct { // PSBT must already be locked (if they belong to this node), only newly added // inputs will be locked by this RPC. Psbt []byte `protobuf:"bytes,1,opt,name=psbt,proto3" json:"psbt,omitempty"` - // Types that are valid to be assigned to ChangeOutput: + // Types that are assignable to ChangeOutput: // // *PsbtCoinSelect_ExistingOutputIndex // *PsbtCoinSelect_Add - ChangeOutput isPsbtCoinSelect_ChangeOutput `protobuf_oneof:"change_output"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + ChangeOutput isPsbtCoinSelect_ChangeOutput `protobuf_oneof:"change_output"` } func (x *PsbtCoinSelect) Reset() { *x = PsbtCoinSelect{} - mi := &file_walletrpc_walletkit_proto_msgTypes[56] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[53] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PsbtCoinSelect) String() string { @@ -4216,8 +4123,8 @@ func (x *PsbtCoinSelect) String() string { func (*PsbtCoinSelect) ProtoMessage() {} func (x *PsbtCoinSelect) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[56] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[53] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4229,7 +4136,7 @@ func (x *PsbtCoinSelect) ProtoReflect() protoreflect.Message { // Deprecated: Use PsbtCoinSelect.ProtoReflect.Descriptor instead. func (*PsbtCoinSelect) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{56} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{53} } func (x *PsbtCoinSelect) GetPsbt() []byte { @@ -4239,27 +4146,23 @@ func (x *PsbtCoinSelect) GetPsbt() []byte { return nil } -func (x *PsbtCoinSelect) GetChangeOutput() isPsbtCoinSelect_ChangeOutput { - if x != nil { - return x.ChangeOutput +func (m *PsbtCoinSelect) GetChangeOutput() isPsbtCoinSelect_ChangeOutput { + if m != nil { + return m.ChangeOutput } return nil } func (x *PsbtCoinSelect) GetExistingOutputIndex() int32 { - if x != nil { - if x, ok := x.ChangeOutput.(*PsbtCoinSelect_ExistingOutputIndex); ok { - return x.ExistingOutputIndex - } + if x, ok := x.GetChangeOutput().(*PsbtCoinSelect_ExistingOutputIndex); ok { + return x.ExistingOutputIndex } return 0 } func (x *PsbtCoinSelect) GetAdd() bool { - if x != nil { - if x, ok := x.ChangeOutput.(*PsbtCoinSelect_Add); ok { - return x.Add - } + if x, ok := x.GetChangeOutput().(*PsbtCoinSelect_Add); ok { + return x.Add } return false } @@ -4288,7 +4191,10 @@ func (*PsbtCoinSelect_ExistingOutputIndex) isPsbtCoinSelect_ChangeOutput() {} func (*PsbtCoinSelect_Add) isPsbtCoinSelect_ChangeOutput() {} type UtxoLease struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // A 32 byte random ID that identifies the lease. Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // The identifying outpoint of the output being leased. @@ -4298,16 +4204,16 @@ type UtxoLease struct { // The public key script of the leased output. PkScript []byte `protobuf:"bytes,4,opt,name=pk_script,json=pkScript,proto3" json:"pk_script,omitempty"` // The value of the leased output in satoshis. - Value uint64 `protobuf:"varint,5,opt,name=value,proto3" json:"value,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Value uint64 `protobuf:"varint,5,opt,name=value,proto3" json:"value,omitempty"` } func (x *UtxoLease) Reset() { *x = UtxoLease{} - mi := &file_walletrpc_walletkit_proto_msgTypes[57] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[54] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *UtxoLease) String() string { @@ -4317,8 +4223,8 @@ func (x *UtxoLease) String() string { func (*UtxoLease) ProtoMessage() {} func (x *UtxoLease) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[57] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[54] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4330,7 +4236,7 @@ func (x *UtxoLease) ProtoReflect() protoreflect.Message { // Deprecated: Use UtxoLease.ProtoReflect.Descriptor instead. func (*UtxoLease) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{57} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{54} } func (x *UtxoLease) GetId() []byte { @@ -4369,19 +4275,22 @@ func (x *UtxoLease) GetValue() uint64 { } type SignPsbtRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The PSBT that should be signed. The PSBT must contain all required inputs, // outputs, UTXO data and custom fields required to identify the signing key. - FundedPsbt []byte `protobuf:"bytes,1,opt,name=funded_psbt,json=fundedPsbt,proto3" json:"funded_psbt,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + FundedPsbt []byte `protobuf:"bytes,1,opt,name=funded_psbt,json=fundedPsbt,proto3" json:"funded_psbt,omitempty"` } func (x *SignPsbtRequest) Reset() { *x = SignPsbtRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[58] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[55] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SignPsbtRequest) String() string { @@ -4391,8 +4300,8 @@ func (x *SignPsbtRequest) String() string { func (*SignPsbtRequest) ProtoMessage() {} func (x *SignPsbtRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[58] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[55] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4404,7 +4313,7 @@ func (x *SignPsbtRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SignPsbtRequest.ProtoReflect.Descriptor instead. func (*SignPsbtRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{58} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{55} } func (x *SignPsbtRequest) GetFundedPsbt() []byte { @@ -4415,20 +4324,23 @@ func (x *SignPsbtRequest) GetFundedPsbt() []byte { } type SignPsbtResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The signed transaction in PSBT format. SignedPsbt []byte `protobuf:"bytes,1,opt,name=signed_psbt,json=signedPsbt,proto3" json:"signed_psbt,omitempty"` // The indices of signed inputs. - SignedInputs []uint32 `protobuf:"varint,2,rep,packed,name=signed_inputs,json=signedInputs,proto3" json:"signed_inputs,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + SignedInputs []uint32 `protobuf:"varint,2,rep,packed,name=signed_inputs,json=signedInputs,proto3" json:"signed_inputs,omitempty"` } func (x *SignPsbtResponse) Reset() { *x = SignPsbtResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[59] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[56] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *SignPsbtResponse) String() string { @@ -4438,8 +4350,8 @@ func (x *SignPsbtResponse) String() string { func (*SignPsbtResponse) ProtoMessage() {} func (x *SignPsbtResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[59] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[56] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4451,7 +4363,7 @@ func (x *SignPsbtResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SignPsbtResponse.ProtoReflect.Descriptor instead. func (*SignPsbtResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{59} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{56} } func (x *SignPsbtResponse) GetSignedPsbt() []byte { @@ -4469,23 +4381,26 @@ func (x *SignPsbtResponse) GetSignedInputs() []uint32 { } type FinalizePsbtRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // A PSBT that should be signed and finalized. The PSBT must contain all // required inputs, outputs, UTXO data and partial signatures of all other // signers. FundedPsbt []byte `protobuf:"bytes,1,opt,name=funded_psbt,json=fundedPsbt,proto3" json:"funded_psbt,omitempty"` // The name of the account to finalize the PSBT with. If empty, the default // wallet account is used. - Account string `protobuf:"bytes,5,opt,name=account,proto3" json:"account,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Account string `protobuf:"bytes,5,opt,name=account,proto3" json:"account,omitempty"` } func (x *FinalizePsbtRequest) Reset() { *x = FinalizePsbtRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[60] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[57] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *FinalizePsbtRequest) String() string { @@ -4495,8 +4410,8 @@ func (x *FinalizePsbtRequest) String() string { func (*FinalizePsbtRequest) ProtoMessage() {} func (x *FinalizePsbtRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[60] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[57] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4508,7 +4423,7 @@ func (x *FinalizePsbtRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FinalizePsbtRequest.ProtoReflect.Descriptor instead. func (*FinalizePsbtRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{60} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{57} } func (x *FinalizePsbtRequest) GetFundedPsbt() []byte { @@ -4526,20 +4441,23 @@ func (x *FinalizePsbtRequest) GetAccount() string { } type FinalizePsbtResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The fully signed and finalized transaction in PSBT format. SignedPsbt []byte `protobuf:"bytes,1,opt,name=signed_psbt,json=signedPsbt,proto3" json:"signed_psbt,omitempty"` // The fully signed and finalized transaction in the raw wire format. - RawFinalTx []byte `protobuf:"bytes,2,opt,name=raw_final_tx,json=rawFinalTx,proto3" json:"raw_final_tx,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + RawFinalTx []byte `protobuf:"bytes,2,opt,name=raw_final_tx,json=rawFinalTx,proto3" json:"raw_final_tx,omitempty"` } func (x *FinalizePsbtResponse) Reset() { *x = FinalizePsbtResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[61] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[58] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *FinalizePsbtResponse) String() string { @@ -4549,8 +4467,8 @@ func (x *FinalizePsbtResponse) String() string { func (*FinalizePsbtResponse) ProtoMessage() {} func (x *FinalizePsbtResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[61] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[58] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4562,7 +4480,7 @@ func (x *FinalizePsbtResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FinalizePsbtResponse.ProtoReflect.Descriptor instead. func (*FinalizePsbtResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{61} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{58} } func (x *FinalizePsbtResponse) GetSignedPsbt() []byte { @@ -4580,16 +4498,18 @@ func (x *FinalizePsbtResponse) GetRawFinalTx() []byte { } type ListLeasesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *ListLeasesRequest) Reset() { *x = ListLeasesRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[62] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[59] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListLeasesRequest) String() string { @@ -4599,8 +4519,8 @@ func (x *ListLeasesRequest) String() string { func (*ListLeasesRequest) ProtoMessage() {} func (x *ListLeasesRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[62] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[59] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4612,22 +4532,25 @@ func (x *ListLeasesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLeasesRequest.ProtoReflect.Descriptor instead. func (*ListLeasesRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{62} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{59} } type ListLeasesResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The list of currently leased utxos. - LockedUtxos []*UtxoLease `protobuf:"bytes,1,rep,name=locked_utxos,json=lockedUtxos,proto3" json:"locked_utxos,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The list of currently leased utxos. + LockedUtxos []*UtxoLease `protobuf:"bytes,1,rep,name=locked_utxos,json=lockedUtxos,proto3" json:"locked_utxos,omitempty"` } func (x *ListLeasesResponse) Reset() { *x = ListLeasesResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[63] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[60] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListLeasesResponse) String() string { @@ -4637,8 +4560,8 @@ func (x *ListLeasesResponse) String() string { func (*ListLeasesResponse) ProtoMessage() {} func (x *ListLeasesResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[63] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[60] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4650,7 +4573,7 @@ func (x *ListLeasesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLeasesResponse.ProtoReflect.Descriptor instead. func (*ListLeasesResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{63} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{60} } func (x *ListLeasesResponse) GetLockedUtxos() []*UtxoLease { @@ -4661,20 +4584,23 @@ func (x *ListLeasesResponse) GetLockedUtxos() []*UtxoLease { } type ListSweepsResponse_TransactionIDs struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Reversed, hex-encoded string representing the transaction ids of the // sweeps that our node has broadcast. Note that these transactions may // not have confirmed yet, we record sweeps on broadcast, not confirmation. TransactionIds []string `protobuf:"bytes,1,rep,name=transaction_ids,json=transactionIds,proto3" json:"transaction_ids,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ListSweepsResponse_TransactionIDs) Reset() { *x = ListSweepsResponse_TransactionIDs{} - mi := &file_walletrpc_walletkit_proto_msgTypes[65] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletrpc_walletkit_proto_msgTypes[61] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListSweepsResponse_TransactionIDs) String() string { @@ -4684,8 +4610,8 @@ func (x *ListSweepsResponse_TransactionIDs) String() string { func (*ListSweepsResponse_TransactionIDs) ProtoMessage() {} func (x *ListSweepsResponse_TransactionIDs) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[65] - if x != nil { + mi := &file_walletrpc_walletkit_proto_msgTypes[61] + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -4697,7 +4623,7 @@ func (x *ListSweepsResponse_TransactionIDs) ProtoReflect() protoreflect.Message // Deprecated: Use ListSweepsResponse_TransactionIDs.ProtoReflect.Descriptor instead. func (*ListSweepsResponse_TransactionIDs) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{50, 0} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{47, 0} } func (x *ListSweepsResponse_TransactionIDs) GetTransactionIds() []string { @@ -4709,396 +4635,760 @@ func (x *ListSweepsResponse_TransactionIDs) GetTransactionIds() []string { var File_walletrpc_walletkit_proto protoreflect.FileDescriptor -const file_walletrpc_walletkit_proto_rawDesc = "" + - "\n" + - "\x19walletrpc/walletkit.proto\x12\twalletrpc\x1a\x0flightning.proto\x1a\x14signrpc/signer.proto\"\x93\x01\n" + - "\x12ListUnspentRequest\x12\x1b\n" + - "\tmin_confs\x18\x01 \x01(\x05R\bminConfs\x12\x1b\n" + - "\tmax_confs\x18\x02 \x01(\x05R\bmaxConfs\x12\x18\n" + - "\aaccount\x18\x03 \x01(\tR\aaccount\x12)\n" + - "\x10unconfirmed_only\x18\x04 \x01(\bR\x0funconfirmedOnly\"8\n" + - "\x13ListUnspentResponse\x12!\n" + - "\x05utxos\x18\x01 \x03(\v2\v.lnrpc.UtxoR\x05utxos\"\x80\x01\n" + - "\x12LeaseOutputRequest\x12\x0e\n" + - "\x02id\x18\x01 \x01(\fR\x02id\x12+\n" + - "\boutpoint\x18\x02 \x01(\v2\x0f.lnrpc.OutPointR\boutpoint\x12-\n" + - "\x12expiration_seconds\x18\x03 \x01(\x04R\x11expirationSeconds\"5\n" + - "\x13LeaseOutputResponse\x12\x1e\n" + - "\n" + - "expiration\x18\x01 \x01(\x04R\n" + - "expiration\"S\n" + - "\x14ReleaseOutputRequest\x12\x0e\n" + - "\x02id\x18\x01 \x01(\fR\x02id\x12+\n" + - "\boutpoint\x18\x02 \x01(\v2\x0f.lnrpc.OutPointR\boutpoint\"/\n" + - "\x15ReleaseOutputResponse\x12\x16\n" + - "\x06status\x18\x01 \x01(\tR\x06status\"Q\n" + - "\x06KeyReq\x12(\n" + - "\x10key_finger_print\x18\x01 \x01(\x05R\x0ekeyFingerPrint\x12\x1d\n" + - "\n" + - "key_family\x18\x02 \x01(\x05R\tkeyFamily\"k\n" + - "\vAddrRequest\x12\x18\n" + - "\aaccount\x18\x01 \x01(\tR\aaccount\x12*\n" + - "\x04type\x18\x02 \x01(\x0e2\x16.walletrpc.AddressTypeR\x04type\x12\x16\n" + - "\x06change\x18\x03 \x01(\bR\x06change\"\"\n" + - "\fAddrResponse\x12\x12\n" + - "\x04addr\x18\x01 \x01(\tR\x04addr\"\xe2\x02\n" + - "\aAccount\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x129\n" + - "\faddress_type\x18\x02 \x01(\x0e2\x16.walletrpc.AddressTypeR\vaddressType\x12.\n" + - "\x13extended_public_key\x18\x03 \x01(\tR\x11extendedPublicKey\x124\n" + - "\x16master_key_fingerprint\x18\x04 \x01(\fR\x14masterKeyFingerprint\x12'\n" + - "\x0fderivation_path\x18\x05 \x01(\tR\x0ederivationPath\x12,\n" + - "\x12external_key_count\x18\x06 \x01(\rR\x10externalKeyCount\x12,\n" + - "\x12internal_key_count\x18\a \x01(\rR\x10internalKeyCount\x12\x1d\n" + - "\n" + - "watch_only\x18\b \x01(\bR\twatchOnly\"\xae\x01\n" + - "\x0fAddressProperty\x12\x18\n" + - "\aaddress\x18\x01 \x01(\tR\aaddress\x12\x1f\n" + - "\vis_internal\x18\x02 \x01(\bR\n" + - "isInternal\x12\x18\n" + - "\abalance\x18\x03 \x01(\x03R\abalance\x12'\n" + - "\x0fderivation_path\x18\x04 \x01(\tR\x0ederivationPath\x12\x1d\n" + - "\n" + - "public_key\x18\x05 \x01(\fR\tpublicKey\"\xc8\x01\n" + - "\x14AccountWithAddresses\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x129\n" + - "\faddress_type\x18\x02 \x01(\x0e2\x16.walletrpc.AddressTypeR\vaddressType\x12'\n" + - "\x0fderivation_path\x18\x03 \x01(\tR\x0ederivationPath\x128\n" + - "\taddresses\x18\x04 \x03(\v2\x1a.walletrpc.AddressPropertyR\taddresses\"d\n" + - "\x13ListAccountsRequest\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x129\n" + - "\faddress_type\x18\x02 \x01(\x0e2\x16.walletrpc.AddressTypeR\vaddressType\"F\n" + - "\x14ListAccountsResponse\x12.\n" + - "\baccounts\x18\x01 \x03(\v2\x12.walletrpc.AccountR\baccounts\"V\n" + - "\x16RequiredReserveRequest\x12<\n" + - "\x1aadditional_public_channels\x18\x01 \x01(\rR\x18additionalPublicChannels\"D\n" + - "\x17RequiredReserveResponse\x12)\n" + - "\x10required_reserve\x18\x01 \x01(\x03R\x0frequiredReserve\"k\n" + - "\x14ListAddressesRequest\x12!\n" + - "\faccount_name\x18\x01 \x01(\tR\vaccountName\x120\n" + - "\x14show_custom_accounts\x18\x02 \x01(\bR\x12showCustomAccounts\"n\n" + - "\x15ListAddressesResponse\x12U\n" + - "\x16account_with_addresses\x18\x01 \x03(\v2\x1f.walletrpc.AccountWithAddressesR\x14accountWithAddresses\"+\n" + - "\x15GetTransactionRequest\x12\x12\n" + - "\x04txid\x18\x01 \x01(\tR\x04txid\"B\n" + - "\x1aSignMessageWithAddrRequest\x12\x10\n" + - "\x03msg\x18\x01 \x01(\fR\x03msg\x12\x12\n" + - "\x04addr\x18\x02 \x01(\tR\x04addr\";\n" + - "\x1bSignMessageWithAddrResponse\x12\x1c\n" + - "\tsignature\x18\x01 \x01(\tR\tsignature\"b\n" + - "\x1cVerifyMessageWithAddrRequest\x12\x10\n" + - "\x03msg\x18\x01 \x01(\fR\x03msg\x12\x1c\n" + - "\tsignature\x18\x02 \x01(\tR\tsignature\x12\x12\n" + - "\x04addr\x18\x03 \x01(\tR\x04addr\"M\n" + - "\x1dVerifyMessageWithAddrResponse\x12\x14\n" + - "\x05valid\x18\x01 \x01(\bR\x05valid\x12\x16\n" + - "\x06pubkey\x18\x02 \x01(\fR\x06pubkey\"\xe4\x01\n" + - "\x14ImportAccountRequest\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x12.\n" + - "\x13extended_public_key\x18\x02 \x01(\tR\x11extendedPublicKey\x124\n" + - "\x16master_key_fingerprint\x18\x03 \x01(\fR\x14masterKeyFingerprint\x129\n" + - "\faddress_type\x18\x04 \x01(\x0e2\x16.walletrpc.AddressTypeR\vaddressType\x12\x17\n" + - "\adry_run\x18\x05 \x01(\bR\x06dryRun\"\xaf\x01\n" + - "\x15ImportAccountResponse\x12,\n" + - "\aaccount\x18\x01 \x01(\v2\x12.walletrpc.AccountR\aaccount\x123\n" + - "\x16dry_run_external_addrs\x18\x02 \x03(\tR\x13dryRunExternalAddrs\x123\n" + - "\x16dry_run_internal_addrs\x18\x03 \x03(\tR\x13dryRunInternalAddrs\"r\n" + - "\x16ImportPublicKeyRequest\x12\x1d\n" + - "\n" + - "public_key\x18\x01 \x01(\fR\tpublicKey\x129\n" + - "\faddress_type\x18\x02 \x01(\x0e2\x16.walletrpc.AddressTypeR\vaddressType\"1\n" + - "\x17ImportPublicKeyResponse\x12\x16\n" + - "\x06status\x18\x01 \x01(\tR\x06status\"\xa9\x02\n" + - "\x16ImportTapscriptRequest\x12.\n" + - "\x13internal_public_key\x18\x01 \x01(\fR\x11internalPublicKey\x12;\n" + - "\tfull_tree\x18\x02 \x01(\v2\x1c.walletrpc.TapscriptFullTreeH\x00R\bfullTree\x12J\n" + - "\x0epartial_reveal\x18\x03 \x01(\v2!.walletrpc.TapscriptPartialRevealH\x00R\rpartialReveal\x12&\n" + - "\x0eroot_hash_only\x18\x04 \x01(\fH\x00R\frootHashOnly\x12$\n" + - "\rfull_key_only\x18\x05 \x01(\bH\x00R\vfullKeyOnlyB\b\n" + - "\x06script\"F\n" + - "\x11TapscriptFullTree\x121\n" + - "\n" + - "all_leaves\x18\x01 \x03(\v2\x12.walletrpc.TapLeafR\tallLeaves\"D\n" + - "\aTapLeaf\x12!\n" + - "\fleaf_version\x18\x01 \x01(\rR\vleafVersion\x12\x16\n" + - "\x06script\x18\x02 \x01(\fR\x06script\"\x83\x01\n" + - "\x16TapscriptPartialReveal\x127\n" + - "\rrevealed_leaf\x18\x01 \x01(\v2\x12.walletrpc.TapLeafR\frevealedLeaf\x120\n" + - "\x14full_inclusion_proof\x18\x02 \x01(\fR\x12fullInclusionProof\"<\n" + - "\x17ImportTapscriptResponse\x12!\n" + - "\fp2tr_address\x18\x01 \x01(\tR\vp2trAddress\":\n" + - "\vTransaction\x12\x15\n" + - "\x06tx_hex\x18\x01 \x01(\fR\x05txHex\x12\x14\n" + - "\x05label\x18\x02 \x01(\tR\x05label\"6\n" + - "\x0fPublishResponse\x12#\n" + - "\rpublish_error\x18\x01 \x01(\tR\fpublishError\"j\n" + - "\x14SubmitPackageRequest\x12\x17\n" + - "\araw_txs\x18\x01 \x03(\fR\x06rawTxs\x12'\n" + - "\rsat_per_vbyte\x18\x02 \x01(\x04H\x00R\vsatPerVbyte\x88\x01\x01B\x10\n" + - "\x0e_sat_per_vbyte\"b\n" + - "\x15SubmitPackageTxResult\x12\x12\n" + - "\x04txid\x18\x01 \x01(\tR\x04txid\x12\x14\n" + - "\x05error\x18\x02 \x01(\tR\x05error\x12\x1f\n" + - "\vother_wtxid\x18\x03 \x01(\tR\n" + - "otherWtxid\"\x9d\x02\n" + - "\x15SubmitPackageResponse\x12\x1f\n" + - "\vpackage_msg\x18\x01 \x01(\tR\n" + - "packageMsg\x12N\n" + - "\n" + - "tx_results\x18\x02 \x03(\v2/.walletrpc.SubmitPackageResponse.TxResultsEntryR\ttxResults\x123\n" + - "\x15replaced_transactions\x18\x03 \x03(\tR\x14replacedTransactions\x1a^\n" + - "\x0eTxResultsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x126\n" + - "\x05value\x18\x02 \x01(\v2 .walletrpc.SubmitPackageTxResultR\x05value:\x028\x01\"3\n" + - "\x19RemoveTransactionResponse\x12\x16\n" + - "\x06status\x18\x01 \x01(\tR\x06status\"\x92\x02\n" + - "\x12SendOutputsRequest\x12\x1c\n" + - "\n" + - "sat_per_kw\x18\x01 \x01(\x03R\bsatPerKw\x12(\n" + - "\aoutputs\x18\x02 \x03(\v2\x0e.signrpc.TxOutR\aoutputs\x12\x14\n" + - "\x05label\x18\x03 \x01(\tR\x05label\x12\x1b\n" + - "\tmin_confs\x18\x04 \x01(\x05R\bminConfs\x12+\n" + - "\x11spend_unconfirmed\x18\x05 \x01(\bR\x10spendUnconfirmed\x12T\n" + - "\x17coin_selection_strategy\x18\x06 \x01(\x0e2\x1c.lnrpc.CoinSelectionStrategyR\x15coinSelectionStrategy\",\n" + - "\x13SendOutputsResponse\x12\x15\n" + - "\x06raw_tx\x18\x01 \x01(\fR\x05rawTx\"5\n" + - "\x12EstimateFeeRequest\x12\x1f\n" + - "\vconf_target\x18\x01 \x01(\x05R\n" + - "confTarget\"j\n" + - "\x13EstimateFeeResponse\x12\x1c\n" + - "\n" + - "sat_per_kw\x18\x01 \x01(\x03R\bsatPerKw\x125\n" + - "\x18min_relay_fee_sat_per_kw\x18\x02 \x01(\x03R\x13minRelayFeeSatPerKw\"\x90\x05\n" + - "\fPendingSweep\x12+\n" + - "\boutpoint\x18\x01 \x01(\v2\x0f.lnrpc.OutPointR\boutpoint\x129\n" + - "\fwitness_type\x18\x02 \x01(\x0e2\x16.walletrpc.WitnessTypeR\vwitnessType\x12\x1d\n" + - "\n" + - "amount_sat\x18\x03 \x01(\rR\tamountSat\x12$\n" + - "\fsat_per_byte\x18\x04 \x01(\rB\x02\x18\x01R\n" + - "satPerByte\x12-\n" + - "\x12broadcast_attempts\x18\x05 \x01(\rR\x11broadcastAttempts\x126\n" + - "\x15next_broadcast_height\x18\x06 \x01(\rB\x02\x18\x01R\x13nextBroadcastHeight\x12\x18\n" + - "\x05force\x18\a \x01(\bB\x02\x18\x01R\x05force\x126\n" + - "\x15requested_conf_target\x18\b \x01(\rB\x02\x18\x01R\x13requestedConfTarget\x127\n" + - "\x16requested_sat_per_byte\x18\t \x01(\rB\x02\x18\x01R\x13requestedSatPerByte\x12\"\n" + - "\rsat_per_vbyte\x18\n" + - " \x01(\x04R\vsatPerVbyte\x125\n" + - "\x17requested_sat_per_vbyte\x18\v \x01(\x04R\x14requestedSatPerVbyte\x12\x1c\n" + - "\timmediate\x18\f \x01(\bR\timmediate\x12\x16\n" + - "\x06budget\x18\r \x01(\x04R\x06budget\x12'\n" + - "\x0fdeadline_height\x18\x0e \x01(\rR\x0edeadlineHeight\x12'\n" + - "\x0fmaturity_height\x18\x0f \x01(\rR\x0ematurityHeight\"\x16\n" + - "\x14PendingSweepsRequest\"W\n" + - "\x15PendingSweepsResponse\x12>\n" + - "\x0epending_sweeps\x18\x01 \x03(\v2\x17.walletrpc.PendingSweepR\rpendingSweeps\"\x9f\x02\n" + - "\x0eBumpFeeRequest\x12+\n" + - "\boutpoint\x18\x01 \x01(\v2\x0f.lnrpc.OutPointR\boutpoint\x12\x1f\n" + - "\vtarget_conf\x18\x02 \x01(\rR\n" + - "targetConf\x12$\n" + - "\fsat_per_byte\x18\x03 \x01(\rB\x02\x18\x01R\n" + - "satPerByte\x12\x18\n" + - "\x05force\x18\x04 \x01(\bB\x02\x18\x01R\x05force\x12\"\n" + - "\rsat_per_vbyte\x18\x05 \x01(\x04R\vsatPerVbyte\x12\x1c\n" + - "\timmediate\x18\x06 \x01(\bR\timmediate\x12\x16\n" + - "\x06budget\x18\a \x01(\x04R\x06budget\x12%\n" + - "\x0edeadline_delta\x18\b \x01(\rR\rdeadlineDelta\")\n" + - "\x0fBumpFeeResponse\x12\x16\n" + - "\x06status\x18\x01 \x01(\tR\x06status\"\xf7\x01\n" + - "\x18BumpForceCloseFeeRequest\x122\n" + - "\n" + - "chan_point\x18\x01 \x01(\v2\x13.lnrpc.ChannelPointR\tchanPoint\x12%\n" + - "\x0edeadline_delta\x18\x02 \x01(\rR\rdeadlineDelta\x12)\n" + - "\x10starting_feerate\x18\x03 \x01(\x04R\x0fstartingFeerate\x12\x1c\n" + - "\timmediate\x18\x04 \x01(\bR\timmediate\x12\x16\n" + - "\x06budget\x18\x05 \x01(\x04R\x06budget\x12\x1f\n" + - "\vtarget_conf\x18\x06 \x01(\rR\n" + - "targetConf\"3\n" + - "\x19BumpForceCloseFeeResponse\x12\x16\n" + - "\x06status\x18\x01 \x01(\tR\x06status\"P\n" + - "\x11ListSweepsRequest\x12\x18\n" + - "\averbose\x18\x01 \x01(\bR\averbose\x12!\n" + - "\fstart_height\x18\x02 \x01(\x05R\vstartHeight\"\x80\x02\n" + - "\x12ListSweepsResponse\x12L\n" + - "\x13transaction_details\x18\x01 \x01(\v2\x19.lnrpc.TransactionDetailsH\x00R\x12transactionDetails\x12W\n" + - "\x0ftransaction_ids\x18\x02 \x01(\v2,.walletrpc.ListSweepsResponse.TransactionIDsH\x00R\x0etransactionIds\x1a9\n" + - "\x0eTransactionIDs\x12'\n" + - "\x0ftransaction_ids\x18\x01 \x03(\tR\x0etransactionIdsB\b\n" + - "\x06sweeps\"a\n" + - "\x17LabelTransactionRequest\x12\x12\n" + - "\x04txid\x18\x01 \x01(\fR\x04txid\x12\x14\n" + - "\x05label\x18\x02 \x01(\tR\x05label\x12\x1c\n" + - "\toverwrite\x18\x03 \x01(\bR\toverwrite\"2\n" + - "\x18LabelTransactionResponse\x12\x16\n" + - "\x06status\x18\x01 \x01(\tR\x06status\"\x88\x05\n" + - "\x0fFundPsbtRequest\x12\x14\n" + - "\x04psbt\x18\x01 \x01(\fH\x00R\x04psbt\x12)\n" + - "\x03raw\x18\x02 \x01(\v2\x15.walletrpc.TxTemplateH\x00R\x03raw\x12<\n" + - "\vcoin_select\x18\t \x01(\v2\x19.walletrpc.PsbtCoinSelectH\x00R\n" + - "coinSelect\x12!\n" + - "\vtarget_conf\x18\x03 \x01(\rH\x01R\n" + - "targetConf\x12$\n" + - "\rsat_per_vbyte\x18\x04 \x01(\x04H\x01R\vsatPerVbyte\x12\x1e\n" + - "\n" + - "sat_per_kw\x18\v \x01(\x04H\x01R\bsatPerKw\x12\x18\n" + - "\aaccount\x18\x05 \x01(\tR\aaccount\x12\x1b\n" + - "\tmin_confs\x18\x06 \x01(\x05R\bminConfs\x12+\n" + - "\x11spend_unconfirmed\x18\a \x01(\bR\x10spendUnconfirmed\x12=\n" + - "\vchange_type\x18\b \x01(\x0e2\x1c.walletrpc.ChangeAddressTypeR\n" + - "changeType\x12T\n" + - "\x17coin_selection_strategy\x18\n" + - " \x01(\x0e2\x1c.lnrpc.CoinSelectionStrategyR\x15coinSelectionStrategy\x12\"\n" + - "\rmax_fee_ratio\x18\f \x01(\x01R\vmaxFeeRatio\x12$\n" + - "\x0ecustom_lock_id\x18\r \x01(\fR\fcustomLockId\x126\n" + - "\x17lock_expiration_seconds\x18\x0e \x01(\x04R\x15lockExpirationSecondsB\n" + - "\n" + - "\btemplateB\x06\n" + - "\x04fees\"\x9c\x01\n" + - "\x10FundPsbtResponse\x12\x1f\n" + - "\vfunded_psbt\x18\x01 \x01(\fR\n" + - "fundedPsbt\x12.\n" + - "\x13change_output_index\x18\x02 \x01(\x05R\x11changeOutputIndex\x127\n" + - "\flocked_utxos\x18\x03 \x03(\v2\x14.walletrpc.UtxoLeaseR\vlockedUtxos\"\xaf\x01\n" + - "\n" + - "TxTemplate\x12'\n" + - "\x06inputs\x18\x01 \x03(\v2\x0f.lnrpc.OutPointR\x06inputs\x12<\n" + - "\aoutputs\x18\x02 \x03(\v2\".walletrpc.TxTemplate.OutputsEntryR\aoutputs\x1a:\n" + - "\fOutputsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\x04R\x05value:\x028\x01\"\x7f\n" + - "\x0ePsbtCoinSelect\x12\x12\n" + - "\x04psbt\x18\x01 \x01(\fR\x04psbt\x124\n" + - "\x15existing_output_index\x18\x02 \x01(\x05H\x00R\x13existingOutputIndex\x12\x12\n" + - "\x03add\x18\x03 \x01(\bH\x00R\x03addB\x0f\n" + - "\rchange_output\"\x9b\x01\n" + - "\tUtxoLease\x12\x0e\n" + - "\x02id\x18\x01 \x01(\fR\x02id\x12+\n" + - "\boutpoint\x18\x02 \x01(\v2\x0f.lnrpc.OutPointR\boutpoint\x12\x1e\n" + - "\n" + - "expiration\x18\x03 \x01(\x04R\n" + - "expiration\x12\x1b\n" + - "\tpk_script\x18\x04 \x01(\fR\bpkScript\x12\x14\n" + - "\x05value\x18\x05 \x01(\x04R\x05value\"2\n" + - "\x0fSignPsbtRequest\x12\x1f\n" + - "\vfunded_psbt\x18\x01 \x01(\fR\n" + - "fundedPsbt\"X\n" + - "\x10SignPsbtResponse\x12\x1f\n" + - "\vsigned_psbt\x18\x01 \x01(\fR\n" + - "signedPsbt\x12#\n" + - "\rsigned_inputs\x18\x02 \x03(\rR\fsignedInputs\"P\n" + - "\x13FinalizePsbtRequest\x12\x1f\n" + - "\vfunded_psbt\x18\x01 \x01(\fR\n" + - "fundedPsbt\x12\x18\n" + - "\aaccount\x18\x05 \x01(\tR\aaccount\"Y\n" + - "\x14FinalizePsbtResponse\x12\x1f\n" + - "\vsigned_psbt\x18\x01 \x01(\fR\n" + - "signedPsbt\x12 \n" + - "\fraw_final_tx\x18\x02 \x01(\fR\n" + - "rawFinalTx\"\x13\n" + - "\x11ListLeasesRequest\"M\n" + - "\x12ListLeasesResponse\x127\n" + - "\flocked_utxos\x18\x01 \x03(\v2\x14.walletrpc.UtxoLeaseR\vlockedUtxos*\x8e\x01\n" + - "\vAddressType\x12\v\n" + - "\aUNKNOWN\x10\x00\x12\x17\n" + - "\x13WITNESS_PUBKEY_HASH\x10\x01\x12\x1e\n" + - "\x1aNESTED_WITNESS_PUBKEY_HASH\x10\x02\x12%\n" + - "!HYBRID_NESTED_WITNESS_PUBKEY_HASH\x10\x03\x12\x12\n" + - "\x0eTAPROOT_PUBKEY\x10\x04*\xb7\f\n" + - "\vWitnessType\x12\x13\n" + - "\x0fUNKNOWN_WITNESS\x10\x00\x12\x18\n" + - "\x14COMMITMENT_TIME_LOCK\x10\x01\x12\x17\n" + - "\x13COMMITMENT_NO_DELAY\x10\x02\x12\x15\n" + - "\x11COMMITMENT_REVOKE\x10\x03\x12\x17\n" + - "\x13HTLC_OFFERED_REVOKE\x10\x04\x12\x18\n" + - "\x14HTLC_ACCEPTED_REVOKE\x10\x05\x12%\n" + - "!HTLC_OFFERED_TIMEOUT_SECOND_LEVEL\x10\x06\x12&\n" + - "\"HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL\x10\a\x12\x1f\n" + - "\x1bHTLC_OFFERED_REMOTE_TIMEOUT\x10\b\x12 \n" + - "\x1cHTLC_ACCEPTED_REMOTE_SUCCESS\x10\t\x12\x1c\n" + - "\x18HTLC_SECOND_LEVEL_REVOKE\x10\n" + - "\x12\x14\n" + - "\x10WITNESS_KEY_HASH\x10\v\x12\x1b\n" + - "\x17NESTED_WITNESS_KEY_HASH\x10\f\x12\x15\n" + - "\x11COMMITMENT_ANCHOR\x10\r\x12!\n" + - "\x1dCOMMITMENT_NO_DELAY_TWEAKLESS\x10\x0e\x12\"\n" + - "\x1eCOMMITMENT_TO_REMOTE_CONFIRMED\x10\x0f\x125\n" + - "1HTLC_OFFERED_TIMEOUT_SECOND_LEVEL_INPUT_CONFIRMED\x10\x10\x126\n" + - "2HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL_INPUT_CONFIRMED\x10\x11\x12\x1e\n" + - "\x1aLEASE_COMMITMENT_TIME_LOCK\x10\x12\x12(\n" + - "$LEASE_COMMITMENT_TO_REMOTE_CONFIRMED\x10\x13\x12+\n" + - "'LEASE_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL\x10\x14\x12,\n" + - "(LEASE_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL\x10\x15\x12\x19\n" + - "\x15TAPROOT_PUB_KEY_SPEND\x10\x16\x12\x1e\n" + - "\x1aTAPROOT_LOCAL_COMMIT_SPEND\x10\x17\x12\x1f\n" + - "\x1bTAPROOT_REMOTE_COMMIT_SPEND\x10\x18\x12\x1e\n" + - "\x1aTAPROOT_ANCHOR_SWEEP_SPEND\x10\x19\x12-\n" + - ")TAPROOT_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL\x10\x1a\x12.\n" + - "*TAPROOT_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL\x10\x1b\x12$\n" + - " TAPROOT_HTLC_SECOND_LEVEL_REVOKE\x10\x1c\x12 \n" + - "\x1cTAPROOT_HTLC_ACCEPTED_REVOKE\x10\x1d\x12\x1f\n" + - "\x1bTAPROOT_HTLC_OFFERED_REVOKE\x10\x1e\x12'\n" + - "#TAPROOT_HTLC_OFFERED_REMOTE_TIMEOUT\x10\x1f\x12&\n" + - "\"TAPROOT_HTLC_LOCAL_OFFERED_TIMEOUT\x10 \x12(\n" + - "$TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS\x10!\x12'\n" + - "#TAPROOT_HTLC_ACCEPTED_LOCAL_SUCCESS\x10\"\x12\x1d\n" + - "\x19TAPROOT_COMMITMENT_REVOKE\x10#\x12$\n" + - " TAPROOT_LOCAL_COMMIT_SPEND_FINAL\x10$\x12%\n" + - "!TAPROOT_REMOTE_COMMIT_SPEND_FINAL\x10%\x123\n" + - "/TAPROOT_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL_FINAL\x10&\x124\n" + - "0TAPROOT_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL_FINAL\x10'\x12-\n" + - ")TAPROOT_HTLC_OFFERED_REMOTE_TIMEOUT_FINAL\x10(\x12.\n" + - "*TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS_FINAL\x10)\x12#\n" + - "\x1fTAPROOT_COMMITMENT_REVOKE_FINAL\x10**V\n" + - "\x11ChangeAddressType\x12#\n" + - "\x1fCHANGE_ADDRESS_TYPE_UNSPECIFIED\x10\x00\x12\x1c\n" + - "\x18CHANGE_ADDRESS_TYPE_P2TR\x10\x012\xaa\x12\n" + - "\tWalletKit\x12L\n" + - "\vListUnspent\x12\x1d.walletrpc.ListUnspentRequest\x1a\x1e.walletrpc.ListUnspentResponse\x12L\n" + - "\vLeaseOutput\x12\x1d.walletrpc.LeaseOutputRequest\x1a\x1e.walletrpc.LeaseOutputResponse\x12R\n" + - "\rReleaseOutput\x12\x1f.walletrpc.ReleaseOutputRequest\x1a .walletrpc.ReleaseOutputResponse\x12I\n" + - "\n" + - "ListLeases\x12\x1c.walletrpc.ListLeasesRequest\x1a\x1d.walletrpc.ListLeasesResponse\x12:\n" + - "\rDeriveNextKey\x12\x11.walletrpc.KeyReq\x1a\x16.signrpc.KeyDescriptor\x128\n" + - "\tDeriveKey\x12\x13.signrpc.KeyLocator\x1a\x16.signrpc.KeyDescriptor\x12;\n" + - "\bNextAddr\x12\x16.walletrpc.AddrRequest\x1a\x17.walletrpc.AddrResponse\x12F\n" + - "\x0eGetTransaction\x12 .walletrpc.GetTransactionRequest\x1a\x12.lnrpc.Transaction\x12O\n" + - "\fListAccounts\x12\x1e.walletrpc.ListAccountsRequest\x1a\x1f.walletrpc.ListAccountsResponse\x12X\n" + - "\x0fRequiredReserve\x12!.walletrpc.RequiredReserveRequest\x1a\".walletrpc.RequiredReserveResponse\x12R\n" + - "\rListAddresses\x12\x1f.walletrpc.ListAddressesRequest\x1a .walletrpc.ListAddressesResponse\x12d\n" + - "\x13SignMessageWithAddr\x12%.walletrpc.SignMessageWithAddrRequest\x1a&.walletrpc.SignMessageWithAddrResponse\x12j\n" + - "\x15VerifyMessageWithAddr\x12'.walletrpc.VerifyMessageWithAddrRequest\x1a(.walletrpc.VerifyMessageWithAddrResponse\x12R\n" + - "\rImportAccount\x12\x1f.walletrpc.ImportAccountRequest\x1a .walletrpc.ImportAccountResponse\x12X\n" + - "\x0fImportPublicKey\x12!.walletrpc.ImportPublicKeyRequest\x1a\".walletrpc.ImportPublicKeyResponse\x12X\n" + - "\x0fImportTapscript\x12!.walletrpc.ImportTapscriptRequest\x1a\".walletrpc.ImportTapscriptResponse\x12H\n" + - "\x12PublishTransaction\x12\x16.walletrpc.Transaction\x1a\x1a.walletrpc.PublishResponse\x12R\n" + - "\rSubmitPackage\x12\x1f.walletrpc.SubmitPackageRequest\x1a .walletrpc.SubmitPackageResponse\x12[\n" + - "\x11RemoveTransaction\x12 .walletrpc.GetTransactionRequest\x1a$.walletrpc.RemoveTransactionResponse\x12L\n" + - "\vSendOutputs\x12\x1d.walletrpc.SendOutputsRequest\x1a\x1e.walletrpc.SendOutputsResponse\x12L\n" + - "\vEstimateFee\x12\x1d.walletrpc.EstimateFeeRequest\x1a\x1e.walletrpc.EstimateFeeResponse\x12R\n" + - "\rPendingSweeps\x12\x1f.walletrpc.PendingSweepsRequest\x1a .walletrpc.PendingSweepsResponse\x12@\n" + - "\aBumpFee\x12\x19.walletrpc.BumpFeeRequest\x1a\x1a.walletrpc.BumpFeeResponse\x12^\n" + - "\x11BumpForceCloseFee\x12#.walletrpc.BumpForceCloseFeeRequest\x1a$.walletrpc.BumpForceCloseFeeResponse\x12I\n" + - "\n" + - "ListSweeps\x12\x1c.walletrpc.ListSweepsRequest\x1a\x1d.walletrpc.ListSweepsResponse\x12[\n" + - "\x10LabelTransaction\x12\".walletrpc.LabelTransactionRequest\x1a#.walletrpc.LabelTransactionResponse\x12C\n" + - "\bFundPsbt\x12\x1a.walletrpc.FundPsbtRequest\x1a\x1b.walletrpc.FundPsbtResponse\x12C\n" + - "\bSignPsbt\x12\x1a.walletrpc.SignPsbtRequest\x1a\x1b.walletrpc.SignPsbtResponse\x12O\n" + - "\fFinalizePsbt\x12\x1e.walletrpc.FinalizePsbtRequest\x1a\x1f.walletrpc.FinalizePsbtResponseB1Z/github.com/lightningnetwork/lnd/lnrpc/walletrpcb\x06proto3" +var file_walletrpc_walletkit_proto_rawDesc = []byte{ + 0x0a, 0x19, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x6b, 0x69, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x1a, 0x0f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, + 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, + 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x93, 0x01, + 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, + 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x18, + 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x75, 0x6e, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4f, + 0x6e, 0x6c, 0x79, 0x22, 0x38, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x05, 0x75, 0x74, + 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x52, 0x05, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x22, 0x80, 0x01, + 0x0a, 0x12, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, + 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x12, 0x2d, 0x0a, 0x12, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, + 0x22, 0x35, 0x0a, 0x13, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x53, 0x0a, 0x14, 0x52, 0x65, 0x6c, 0x65, 0x61, + 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, + 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x2f, 0x0a, 0x15, + 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x51, 0x0a, + 0x06, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x12, 0x28, 0x0a, 0x10, 0x6b, 0x65, 0x79, 0x5f, 0x66, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x0e, 0x6b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x50, 0x72, 0x69, 0x6e, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6b, 0x65, 0x79, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, + 0x22, 0x6b, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x22, 0x0a, + 0x0c, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, + 0x72, 0x22, 0xe2, 0x02, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, + 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x13, + 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, + 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, 0x6e, + 0x64, 0x65, 0x64, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x34, 0x0a, 0x16, + 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x6d, 0x61, + 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x72, + 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2c, 0x0a, 0x12, 0x65, + 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x4b, 0x65, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, + 0x65, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x77, 0x61, 0x74, 0x63, 0x68, + 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x77, 0x61, 0x74, + 0x63, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0xae, 0x01, 0x0a, 0x0f, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x49, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, + 0x27, 0x0a, 0x0f, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, + 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, + 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0xc8, 0x01, 0x0a, 0x14, 0x41, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x27, 0x0a, 0x0f, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, + 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x12, 0x38, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x50, + 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x65, 0x73, 0x22, 0x64, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, + 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x22, 0x46, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x2e, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, + 0x22, 0x56, 0x0a, 0x16, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x1a, 0x61, 0x64, + 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x18, + 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0x44, 0x0a, 0x17, 0x52, 0x65, 0x71, 0x75, + 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, + 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, + 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x22, 0x6b, + 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x68, 0x6f, + 0x77, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x73, 0x68, 0x6f, 0x77, 0x43, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x6e, 0x0a, 0x15, 0x4c, + 0x69, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x16, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, + 0x77, 0x69, 0x74, 0x68, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x14, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x57, 0x69, + 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x2b, 0x0a, 0x15, 0x47, + 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x22, 0x42, 0x0a, 0x1a, 0x53, 0x69, 0x67, 0x6e, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x22, 0x3b, 0x0a, 0x1b, + 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, + 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x62, 0x0a, 0x1c, 0x56, 0x65, 0x72, + 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, + 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x73, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, + 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x22, 0x4d, 0x0a, + 0x1d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, + 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0xe4, 0x01, 0x0a, + 0x14, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x65, 0x78, 0x74, + 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, + 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x34, 0x0a, 0x16, 0x6d, 0x61, 0x73, + 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, + 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x6d, 0x61, 0x73, 0x74, 0x65, + 0x72, 0x4b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, + 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, + 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, 0x79, + 0x52, 0x75, 0x6e, 0x22, 0xaf, 0x01, 0x0a, 0x15, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, + 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x16, 0x64, + 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, + 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x64, 0x72, 0x79, + 0x52, 0x75, 0x6e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x73, + 0x12, 0x33, 0x0a, 0x16, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x13, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x41, 0x64, 0x64, 0x72, 0x73, 0x22, 0x72, 0x0a, 0x16, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x39, + 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x22, 0x31, 0x0a, 0x17, 0x49, 0x6d, 0x70, + 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xa9, 0x02, 0x0a, + 0x16, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x50, 0x75, + 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x3b, 0x0a, 0x09, 0x66, 0x75, 0x6c, 0x6c, 0x5f, + 0x74, 0x72, 0x65, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x46, 0x75, 0x6c, 0x6c, 0x54, 0x72, 0x65, 0x65, 0x48, 0x00, 0x52, 0x08, 0x66, 0x75, 0x6c, 0x6c, + 0x54, 0x72, 0x65, 0x65, 0x12, 0x4a, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f, + 0x72, 0x65, 0x76, 0x65, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x76, 0x65, 0x61, 0x6c, 0x48, + 0x00, 0x52, 0x0d, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x76, 0x65, 0x61, 0x6c, + 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x6f, 0x6e, + 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0c, 0x72, 0x6f, 0x6f, 0x74, + 0x48, 0x61, 0x73, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x66, 0x75, 0x6c, 0x6c, + 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x48, + 0x00, 0x52, 0x0b, 0x66, 0x75, 0x6c, 0x6c, 0x4b, 0x65, 0x79, 0x4f, 0x6e, 0x6c, 0x79, 0x42, 0x08, + 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x46, 0x0a, 0x11, 0x54, 0x61, 0x70, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x54, 0x72, 0x65, 0x65, 0x12, 0x31, 0x0a, + 0x0a, 0x61, 0x6c, 0x6c, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, + 0x70, 0x4c, 0x65, 0x61, 0x66, 0x52, 0x09, 0x61, 0x6c, 0x6c, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, + 0x22, 0x44, 0x0a, 0x07, 0x54, 0x61, 0x70, 0x4c, 0x65, 0x61, 0x66, 0x12, 0x21, 0x0a, 0x0c, 0x6c, + 0x65, 0x61, 0x66, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0b, 0x6c, 0x65, 0x61, 0x66, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x83, 0x01, 0x0a, 0x16, 0x54, 0x61, 0x70, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x76, 0x65, 0x61, + 0x6c, 0x12, 0x37, 0x0a, 0x0d, 0x72, 0x65, 0x76, 0x65, 0x61, 0x6c, 0x65, 0x64, 0x5f, 0x6c, 0x65, + 0x61, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, 0x70, 0x4c, 0x65, 0x61, 0x66, 0x52, 0x0c, 0x72, 0x65, + 0x76, 0x65, 0x61, 0x6c, 0x65, 0x64, 0x4c, 0x65, 0x61, 0x66, 0x12, 0x30, 0x0a, 0x14, 0x66, 0x75, + 0x6c, 0x6c, 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, + 0x6f, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x66, 0x75, 0x6c, 0x6c, 0x49, 0x6e, + 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0x3c, 0x0a, 0x17, + 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x32, 0x74, 0x72, 0x5f, + 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, + 0x32, 0x74, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x0a, 0x0b, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x0a, 0x06, 0x74, 0x78, 0x5f, + 0x68, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x74, 0x78, 0x48, 0x65, 0x78, + 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x36, 0x0a, 0x0f, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, + 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x75, 0x62, + 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x33, + 0x0a, 0x19, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x22, 0x92, 0x02, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x0a, 0x73, 0x61, + 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, + 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x28, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x73, 0x69, 0x67, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x4f, 0x75, 0x74, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, + 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, + 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, + 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, + 0x65, 0x64, 0x12, 0x54, 0x0a, 0x17, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x69, 0x6e, + 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, + 0x79, 0x52, 0x15, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x22, 0x2c, 0x0a, 0x13, 0x53, 0x65, 0x6e, 0x64, + 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x15, 0x0a, 0x06, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x05, 0x72, 0x61, 0x77, 0x54, 0x78, 0x22, 0x35, 0x0a, 0x12, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, + 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, + 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0x6a, 0x0a, + 0x13, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x0a, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, + 0x6b, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, + 0x4b, 0x77, 0x12, 0x35, 0x0a, 0x18, 0x6d, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, + 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x46, 0x65, + 0x65, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x22, 0x90, 0x05, 0x0a, 0x0c, 0x50, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, + 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, + 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x0c, 0x77, 0x69, 0x74, 0x6e, 0x65, + 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x6e, 0x65, 0x73, + 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x61, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x61, + 0x74, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, + 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x62, 0x72, 0x6f, 0x61, 0x64, + 0x63, 0x61, 0x73, 0x74, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x11, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x41, 0x74, + 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x12, 0x36, 0x0a, 0x15, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x62, + 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x13, 0x6e, 0x65, 0x78, 0x74, 0x42, + 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x18, + 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, + 0x01, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x15, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x13, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x12, 0x37, 0x0a, 0x16, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x61, + 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, + 0x42, 0x02, 0x18, 0x01, 0x52, 0x13, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x53, + 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, + 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x35, 0x0a, + 0x17, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, + 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, + 0x62, 0x79, 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, + 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, + 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x18, 0x0d, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x65, + 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0e, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x48, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, + 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6d, 0x61, + 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x16, 0x0a, 0x14, + 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x22, 0x57, 0x0a, 0x15, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, + 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, + 0x0e, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x77, 0x65, 0x65, 0x70, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x52, 0x0d, + 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x22, 0x9f, 0x02, + 0x0a, 0x0e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, + 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1f, 0x0a, + 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x24, + 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, + 0x42, 0x79, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x22, + 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, + 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x61, 0x64, + 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0d, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x22, + 0x29, 0x0a, 0x0f, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xf7, 0x01, 0x0a, 0x18, 0x42, + 0x75, 0x6d, 0x70, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x46, 0x65, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, + 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x64, + 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x44, 0x65, 0x6c, + 0x74, 0x61, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x74, 0x61, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x66, + 0x65, 0x65, 0x72, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x46, 0x65, 0x65, 0x72, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, + 0x09, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x09, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x62, + 0x75, 0x64, 0x67, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x62, 0x75, 0x64, + 0x67, 0x65, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, + 0x6e, 0x66, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x43, 0x6f, 0x6e, 0x66, 0x22, 0x33, 0x0a, 0x19, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x6f, 0x72, 0x63, + 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x50, 0x0a, 0x11, 0x4c, 0x69, 0x73, + 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, + 0x0a, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x80, 0x02, 0x0a, 0x12, + 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x13, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x48, 0x00, 0x52, 0x12, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, + 0x12, 0x57, 0x0a, 0x0f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x1a, 0x39, 0x0a, 0x0e, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x74, + 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x64, 0x73, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x77, 0x65, 0x65, 0x70, 0x73, 0x22, 0x61, + 0x0a, 0x17, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x14, 0x0a, + 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x76, 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6f, 0x76, 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, + 0x65, 0x22, 0x32, 0x0a, 0x18, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x88, 0x05, 0x0a, 0x0f, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, + 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x04, 0x70, 0x73, 0x62, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x04, 0x70, 0x73, 0x62, 0x74, 0x12, + 0x29, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x03, 0x72, 0x61, 0x77, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x6f, + 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x73, 0x62, 0x74, + 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x63, 0x6f, + 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x12, 0x21, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x01, 0x52, + 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0d, 0x73, + 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x04, 0x48, 0x01, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, + 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x08, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x4b, + 0x77, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, + 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, + 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, + 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x3d, 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x54, 0x0a, 0x17, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, + 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, + 0x65, 0x67, 0x79, 0x52, 0x15, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, + 0x78, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x01, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x46, 0x65, 0x65, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x12, 0x24, + 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x64, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4c, 0x6f, + 0x63, 0x6b, 0x49, 0x64, 0x12, 0x36, 0x0a, 0x17, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, + 0x0e, 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x6c, 0x6f, 0x63, 0x6b, 0x45, 0x78, 0x70, 0x69, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x42, 0x0a, 0x0a, 0x08, + 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x66, 0x65, 0x65, 0x73, + 0x22, 0x9c, 0x01, 0x0a, 0x10, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, + 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, + 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x11, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x37, 0x0a, 0x0c, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, + 0x5f, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, + 0x73, 0x65, 0x52, 0x0b, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x22, + 0xaf, 0x01, 0x0a, 0x0a, 0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x27, + 0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, + 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x3c, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x2e, + 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x22, 0x7f, 0x0a, 0x0e, 0x50, 0x73, 0x62, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x04, 0x70, 0x73, 0x62, 0x74, 0x12, 0x34, 0x0a, 0x15, 0x65, 0x78, 0x69, 0x73, 0x74, + 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x13, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, + 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x12, 0x0a, + 0x03, 0x61, 0x64, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x03, 0x61, 0x64, + 0x64, 0x42, 0x0f, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x6f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x22, 0x9b, 0x01, 0x0a, 0x09, 0x55, 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, 0x73, 0x65, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, + 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, + 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1e, 0x0a, + 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, + 0x09, 0x70, 0x6b, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x08, 0x70, 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x22, 0x32, 0x0a, 0x0f, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, + 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, + 0x50, 0x73, 0x62, 0x74, 0x22, 0x58, 0x0a, 0x10, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, + 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, + 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x69, 0x67, + 0x6e, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0d, + 0x52, 0x0c, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x22, 0x50, + 0x0a, 0x13, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, + 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, + 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x22, 0x59, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, + 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, + 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x61, 0x77, + 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0a, 0x72, 0x61, 0x77, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x54, 0x78, 0x22, 0x13, 0x0a, 0x11, 0x4c, + 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x22, 0x4d, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0c, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, + 0x5f, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, + 0x73, 0x65, 0x52, 0x0b, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x2a, + 0x8e, 0x01, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, + 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, + 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, + 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, + 0x41, 0x53, 0x48, 0x10, 0x02, 0x12, 0x25, 0x0a, 0x21, 0x48, 0x59, 0x42, 0x52, 0x49, 0x44, 0x5f, + 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, + 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, + 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x10, 0x04, + 0x2a, 0xfb, 0x09, 0x0a, 0x0b, 0x57, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x57, 0x49, 0x54, 0x4e, + 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, + 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x5f, 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x01, 0x12, + 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, + 0x5f, 0x44, 0x45, 0x4c, 0x41, 0x59, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x4d, + 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x03, 0x12, + 0x17, 0x0a, 0x13, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, + 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x48, 0x54, 0x4c, 0x43, + 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, + 0x10, 0x05, 0x12, 0x25, 0x0a, 0x21, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, + 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, + 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x06, 0x12, 0x26, 0x0a, 0x22, 0x48, 0x54, 0x4c, + 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, + 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, + 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, + 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, + 0x10, 0x08, 0x12, 0x20, 0x0a, 0x1c, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, + 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, + 0x53, 0x53, 0x10, 0x09, 0x12, 0x1c, 0x0a, 0x18, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x43, + 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, + 0x10, 0x0a, 0x12, 0x14, 0x0a, 0x10, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x4b, 0x45, + 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0b, 0x12, 0x1b, 0x0a, 0x17, 0x4e, 0x45, 0x53, 0x54, + 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x48, + 0x41, 0x53, 0x48, 0x10, 0x0c, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, + 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x10, 0x0d, 0x12, 0x21, 0x0a, 0x1d, + 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x5f, 0x44, 0x45, + 0x4c, 0x41, 0x59, 0x5f, 0x54, 0x57, 0x45, 0x41, 0x4b, 0x4c, 0x45, 0x53, 0x53, 0x10, 0x0e, 0x12, + 0x22, 0x0a, 0x1e, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x4f, + 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, + 0x44, 0x10, 0x0f, 0x12, 0x35, 0x0a, 0x31, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, + 0x52, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x5f, 0x53, 0x45, 0x43, 0x4f, + 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x43, + 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x10, 0x10, 0x12, 0x36, 0x0a, 0x32, 0x48, 0x54, + 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, + 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, + 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, + 0x10, 0x11, 0x12, 0x1e, 0x0a, 0x1a, 0x4c, 0x45, 0x41, 0x53, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, + 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x5f, 0x4c, 0x4f, 0x43, 0x4b, + 0x10, 0x12, 0x12, 0x28, 0x0a, 0x24, 0x4c, 0x45, 0x41, 0x53, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, + 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, + 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x10, 0x13, 0x12, 0x2b, 0x0a, 0x27, + 0x4c, 0x45, 0x41, 0x53, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, + 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, + 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x14, 0x12, 0x2c, 0x0a, 0x28, 0x4c, 0x45, 0x41, + 0x53, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, + 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, + 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x15, 0x12, 0x19, 0x0a, 0x15, 0x54, 0x41, 0x50, 0x52, 0x4f, + 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, + 0x10, 0x16, 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4c, 0x4f, + 0x43, 0x41, 0x4c, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, + 0x10, 0x17, 0x12, 0x1f, 0x0a, 0x1b, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x52, 0x45, + 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x5f, 0x53, 0x50, 0x45, 0x4e, + 0x44, 0x10, 0x18, 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x41, + 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x5f, 0x53, 0x57, 0x45, 0x45, 0x50, 0x5f, 0x53, 0x50, 0x45, 0x4e, + 0x44, 0x10, 0x19, 0x12, 0x2d, 0x0a, 0x29, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, + 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, + 0x4f, 0x55, 0x54, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, + 0x10, 0x1a, 0x12, 0x2e, 0x0a, 0x2a, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, + 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, + 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, + 0x10, 0x1b, 0x12, 0x24, 0x0a, 0x20, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, + 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, + 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x1c, 0x12, 0x20, 0x0a, 0x1c, 0x54, 0x41, 0x50, 0x52, + 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, + 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x1d, 0x12, 0x1f, 0x0a, 0x1b, 0x54, 0x41, + 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, + 0x45, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x1e, 0x12, 0x27, 0x0a, 0x23, 0x54, + 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, + 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, + 0x55, 0x54, 0x10, 0x1f, 0x12, 0x26, 0x0a, 0x22, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, + 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, + 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x20, 0x12, 0x28, 0x0a, 0x24, + 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, + 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x53, 0x55, 0x43, + 0x43, 0x45, 0x53, 0x53, 0x10, 0x21, 0x12, 0x27, 0x0a, 0x23, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, + 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, + 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x22, 0x12, + 0x1d, 0x0a, 0x19, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, + 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x23, 0x2a, 0x56, + 0x0a, 0x11, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x1f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x41, 0x44, + 0x44, 0x52, 0x45, 0x53, 0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, + 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x43, 0x48, 0x41, 0x4e, + 0x47, 0x45, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x45, 0x53, 0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x50, 0x32, 0x54, 0x52, 0x10, 0x01, 0x32, 0xd6, 0x11, 0x0a, 0x09, 0x57, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x4b, 0x69, 0x74, 0x12, 0x4c, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, + 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, + 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, + 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x52, 0x0a, 0x0d, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, + 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, + 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, + 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x3a, 0x0a, 0x0d, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x4e, 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, + 0x12, 0x11, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, + 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, + 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x38, 0x0a, 0x09, 0x44, + 0x65, 0x72, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x1a, 0x16, 0x2e, + 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x3b, 0x0a, 0x08, 0x4e, 0x65, 0x78, 0x74, 0x41, 0x64, 0x64, + 0x72, 0x12, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, + 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, + 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4f, 0x0a, 0x0c, 0x4c, 0x69, + 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x52, + 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x12, 0x21, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x69, + 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x13, 0x53, 0x69, 0x67, + 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, + 0x12, 0x25, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, + 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, + 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x6a, 0x0a, 0x15, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x12, 0x27, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x28, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, + 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, + 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x49, + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x58, 0x0a, 0x0f, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, + 0x65, 0x79, 0x12, 0x21, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x49, 0x6d, 0x70, + 0x6f, 0x72, 0x74, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x21, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, + 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, + 0x72, 0x74, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x12, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x1a, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, + 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, + 0x11, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, + 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x53, 0x65, + 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x45, 0x73, 0x74, 0x69, + 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, + 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x07, 0x42, 0x75, + 0x6d, 0x70, 0x46, 0x65, 0x65, 0x12, 0x19, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x6d, + 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, 0x11, + 0x42, 0x75, 0x6d, 0x70, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x46, 0x65, + 0x65, 0x12, 0x23, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, + 0x6d, 0x70, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x46, 0x65, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, + 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, + 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x12, 0x1c, 0x2e, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x10, 0x4c, 0x61, 0x62, 0x65, 0x6c, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x23, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65, + 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x08, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, + 0x12, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, + 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x08, 0x53, 0x69, 0x67, + 0x6e, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1b, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, + 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, + 0x0a, 0x0c, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1e, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, + 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, + 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, + 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, + 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, + 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, + 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} var ( file_walletrpc_walletkit_proto_rawDescOnce sync.Once - file_walletrpc_walletkit_proto_rawDescData []byte + file_walletrpc_walletkit_proto_rawDescData = file_walletrpc_walletkit_proto_rawDesc ) func file_walletrpc_walletkit_proto_rawDescGZIP() []byte { file_walletrpc_walletkit_proto_rawDescOnce.Do(func() { - file_walletrpc_walletkit_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_walletrpc_walletkit_proto_rawDesc), len(file_walletrpc_walletkit_proto_rawDesc))) + file_walletrpc_walletkit_proto_rawDescData = protoimpl.X.CompressGZIP(file_walletrpc_walletkit_proto_rawDescData) }) return file_walletrpc_walletkit_proto_rawDescData } var file_walletrpc_walletkit_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_walletrpc_walletkit_proto_msgTypes = make([]protoimpl.MessageInfo, 67) -var file_walletrpc_walletkit_proto_goTypes = []any{ +var file_walletrpc_walletkit_proto_msgTypes = make([]protoimpl.MessageInfo, 63) +var file_walletrpc_walletkit_proto_goTypes = []interface{}{ (AddressType)(0), // 0: walletrpc.AddressType (WitnessType)(0), // 1: walletrpc.WitnessType (ChangeAddressType)(0), // 2: walletrpc.ChangeAddressType @@ -5136,53 +5426,49 @@ var file_walletrpc_walletkit_proto_goTypes = []any{ (*ImportTapscriptResponse)(nil), // 34: walletrpc.ImportTapscriptResponse (*Transaction)(nil), // 35: walletrpc.Transaction (*PublishResponse)(nil), // 36: walletrpc.PublishResponse - (*SubmitPackageRequest)(nil), // 37: walletrpc.SubmitPackageRequest - (*SubmitPackageTxResult)(nil), // 38: walletrpc.SubmitPackageTxResult - (*SubmitPackageResponse)(nil), // 39: walletrpc.SubmitPackageResponse - (*RemoveTransactionResponse)(nil), // 40: walletrpc.RemoveTransactionResponse - (*SendOutputsRequest)(nil), // 41: walletrpc.SendOutputsRequest - (*SendOutputsResponse)(nil), // 42: walletrpc.SendOutputsResponse - (*EstimateFeeRequest)(nil), // 43: walletrpc.EstimateFeeRequest - (*EstimateFeeResponse)(nil), // 44: walletrpc.EstimateFeeResponse - (*PendingSweep)(nil), // 45: walletrpc.PendingSweep - (*PendingSweepsRequest)(nil), // 46: walletrpc.PendingSweepsRequest - (*PendingSweepsResponse)(nil), // 47: walletrpc.PendingSweepsResponse - (*BumpFeeRequest)(nil), // 48: walletrpc.BumpFeeRequest - (*BumpFeeResponse)(nil), // 49: walletrpc.BumpFeeResponse - (*BumpForceCloseFeeRequest)(nil), // 50: walletrpc.BumpForceCloseFeeRequest - (*BumpForceCloseFeeResponse)(nil), // 51: walletrpc.BumpForceCloseFeeResponse - (*ListSweepsRequest)(nil), // 52: walletrpc.ListSweepsRequest - (*ListSweepsResponse)(nil), // 53: walletrpc.ListSweepsResponse - (*LabelTransactionRequest)(nil), // 54: walletrpc.LabelTransactionRequest - (*LabelTransactionResponse)(nil), // 55: walletrpc.LabelTransactionResponse - (*FundPsbtRequest)(nil), // 56: walletrpc.FundPsbtRequest - (*FundPsbtResponse)(nil), // 57: walletrpc.FundPsbtResponse - (*TxTemplate)(nil), // 58: walletrpc.TxTemplate - (*PsbtCoinSelect)(nil), // 59: walletrpc.PsbtCoinSelect - (*UtxoLease)(nil), // 60: walletrpc.UtxoLease - (*SignPsbtRequest)(nil), // 61: walletrpc.SignPsbtRequest - (*SignPsbtResponse)(nil), // 62: walletrpc.SignPsbtResponse - (*FinalizePsbtRequest)(nil), // 63: walletrpc.FinalizePsbtRequest - (*FinalizePsbtResponse)(nil), // 64: walletrpc.FinalizePsbtResponse - (*ListLeasesRequest)(nil), // 65: walletrpc.ListLeasesRequest - (*ListLeasesResponse)(nil), // 66: walletrpc.ListLeasesResponse - nil, // 67: walletrpc.SubmitPackageResponse.TxResultsEntry - (*ListSweepsResponse_TransactionIDs)(nil), // 68: walletrpc.ListSweepsResponse.TransactionIDs - nil, // 69: walletrpc.TxTemplate.OutputsEntry - (*lnrpc.Utxo)(nil), // 70: lnrpc.Utxo - (*lnrpc.OutPoint)(nil), // 71: lnrpc.OutPoint - (*signrpc.TxOut)(nil), // 72: signrpc.TxOut - (lnrpc.CoinSelectionStrategy)(0), // 73: lnrpc.CoinSelectionStrategy - (*lnrpc.ChannelPoint)(nil), // 74: lnrpc.ChannelPoint - (*lnrpc.TransactionDetails)(nil), // 75: lnrpc.TransactionDetails - (*signrpc.KeyLocator)(nil), // 76: signrpc.KeyLocator - (*signrpc.KeyDescriptor)(nil), // 77: signrpc.KeyDescriptor - (*lnrpc.Transaction)(nil), // 78: lnrpc.Transaction + (*RemoveTransactionResponse)(nil), // 37: walletrpc.RemoveTransactionResponse + (*SendOutputsRequest)(nil), // 38: walletrpc.SendOutputsRequest + (*SendOutputsResponse)(nil), // 39: walletrpc.SendOutputsResponse + (*EstimateFeeRequest)(nil), // 40: walletrpc.EstimateFeeRequest + (*EstimateFeeResponse)(nil), // 41: walletrpc.EstimateFeeResponse + (*PendingSweep)(nil), // 42: walletrpc.PendingSweep + (*PendingSweepsRequest)(nil), // 43: walletrpc.PendingSweepsRequest + (*PendingSweepsResponse)(nil), // 44: walletrpc.PendingSweepsResponse + (*BumpFeeRequest)(nil), // 45: walletrpc.BumpFeeRequest + (*BumpFeeResponse)(nil), // 46: walletrpc.BumpFeeResponse + (*BumpForceCloseFeeRequest)(nil), // 47: walletrpc.BumpForceCloseFeeRequest + (*BumpForceCloseFeeResponse)(nil), // 48: walletrpc.BumpForceCloseFeeResponse + (*ListSweepsRequest)(nil), // 49: walletrpc.ListSweepsRequest + (*ListSweepsResponse)(nil), // 50: walletrpc.ListSweepsResponse + (*LabelTransactionRequest)(nil), // 51: walletrpc.LabelTransactionRequest + (*LabelTransactionResponse)(nil), // 52: walletrpc.LabelTransactionResponse + (*FundPsbtRequest)(nil), // 53: walletrpc.FundPsbtRequest + (*FundPsbtResponse)(nil), // 54: walletrpc.FundPsbtResponse + (*TxTemplate)(nil), // 55: walletrpc.TxTemplate + (*PsbtCoinSelect)(nil), // 56: walletrpc.PsbtCoinSelect + (*UtxoLease)(nil), // 57: walletrpc.UtxoLease + (*SignPsbtRequest)(nil), // 58: walletrpc.SignPsbtRequest + (*SignPsbtResponse)(nil), // 59: walletrpc.SignPsbtResponse + (*FinalizePsbtRequest)(nil), // 60: walletrpc.FinalizePsbtRequest + (*FinalizePsbtResponse)(nil), // 61: walletrpc.FinalizePsbtResponse + (*ListLeasesRequest)(nil), // 62: walletrpc.ListLeasesRequest + (*ListLeasesResponse)(nil), // 63: walletrpc.ListLeasesResponse + (*ListSweepsResponse_TransactionIDs)(nil), // 64: walletrpc.ListSweepsResponse.TransactionIDs + nil, // 65: walletrpc.TxTemplate.OutputsEntry + (*lnrpc.Utxo)(nil), // 66: lnrpc.Utxo + (*lnrpc.OutPoint)(nil), // 67: lnrpc.OutPoint + (*signrpc.TxOut)(nil), // 68: signrpc.TxOut + (lnrpc.CoinSelectionStrategy)(0), // 69: lnrpc.CoinSelectionStrategy + (*lnrpc.ChannelPoint)(nil), // 70: lnrpc.ChannelPoint + (*lnrpc.TransactionDetails)(nil), // 71: lnrpc.TransactionDetails + (*signrpc.KeyLocator)(nil), // 72: signrpc.KeyLocator + (*signrpc.KeyDescriptor)(nil), // 73: signrpc.KeyDescriptor + (*lnrpc.Transaction)(nil), // 74: lnrpc.Transaction } var file_walletrpc_walletkit_proto_depIdxs = []int32{ - 70, // 0: walletrpc.ListUnspentResponse.utxos:type_name -> lnrpc.Utxo - 71, // 1: walletrpc.LeaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint - 71, // 2: walletrpc.ReleaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint + 66, // 0: walletrpc.ListUnspentResponse.utxos:type_name -> lnrpc.Utxo + 67, // 1: walletrpc.LeaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint + 67, // 2: walletrpc.ReleaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint 0, // 3: walletrpc.AddrRequest.type:type_name -> walletrpc.AddressType 0, // 4: walletrpc.Account.address_type:type_name -> walletrpc.AddressType 0, // 5: walletrpc.AccountWithAddresses.address_type:type_name -> walletrpc.AddressType @@ -5197,89 +5483,85 @@ var file_walletrpc_walletkit_proto_depIdxs = []int32{ 33, // 14: walletrpc.ImportTapscriptRequest.partial_reveal:type_name -> walletrpc.TapscriptPartialReveal 32, // 15: walletrpc.TapscriptFullTree.all_leaves:type_name -> walletrpc.TapLeaf 32, // 16: walletrpc.TapscriptPartialReveal.revealed_leaf:type_name -> walletrpc.TapLeaf - 67, // 17: walletrpc.SubmitPackageResponse.tx_results:type_name -> walletrpc.SubmitPackageResponse.TxResultsEntry - 72, // 18: walletrpc.SendOutputsRequest.outputs:type_name -> signrpc.TxOut - 73, // 19: walletrpc.SendOutputsRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy - 71, // 20: walletrpc.PendingSweep.outpoint:type_name -> lnrpc.OutPoint - 1, // 21: walletrpc.PendingSweep.witness_type:type_name -> walletrpc.WitnessType - 45, // 22: walletrpc.PendingSweepsResponse.pending_sweeps:type_name -> walletrpc.PendingSweep - 71, // 23: walletrpc.BumpFeeRequest.outpoint:type_name -> lnrpc.OutPoint - 74, // 24: walletrpc.BumpForceCloseFeeRequest.chan_point:type_name -> lnrpc.ChannelPoint - 75, // 25: walletrpc.ListSweepsResponse.transaction_details:type_name -> lnrpc.TransactionDetails - 68, // 26: walletrpc.ListSweepsResponse.transaction_ids:type_name -> walletrpc.ListSweepsResponse.TransactionIDs - 58, // 27: walletrpc.FundPsbtRequest.raw:type_name -> walletrpc.TxTemplate - 59, // 28: walletrpc.FundPsbtRequest.coin_select:type_name -> walletrpc.PsbtCoinSelect - 2, // 29: walletrpc.FundPsbtRequest.change_type:type_name -> walletrpc.ChangeAddressType - 73, // 30: walletrpc.FundPsbtRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy - 60, // 31: walletrpc.FundPsbtResponse.locked_utxos:type_name -> walletrpc.UtxoLease - 71, // 32: walletrpc.TxTemplate.inputs:type_name -> lnrpc.OutPoint - 69, // 33: walletrpc.TxTemplate.outputs:type_name -> walletrpc.TxTemplate.OutputsEntry - 71, // 34: walletrpc.UtxoLease.outpoint:type_name -> lnrpc.OutPoint - 60, // 35: walletrpc.ListLeasesResponse.locked_utxos:type_name -> walletrpc.UtxoLease - 38, // 36: walletrpc.SubmitPackageResponse.TxResultsEntry.value:type_name -> walletrpc.SubmitPackageTxResult - 3, // 37: walletrpc.WalletKit.ListUnspent:input_type -> walletrpc.ListUnspentRequest - 5, // 38: walletrpc.WalletKit.LeaseOutput:input_type -> walletrpc.LeaseOutputRequest - 7, // 39: walletrpc.WalletKit.ReleaseOutput:input_type -> walletrpc.ReleaseOutputRequest - 65, // 40: walletrpc.WalletKit.ListLeases:input_type -> walletrpc.ListLeasesRequest - 9, // 41: walletrpc.WalletKit.DeriveNextKey:input_type -> walletrpc.KeyReq - 76, // 42: walletrpc.WalletKit.DeriveKey:input_type -> signrpc.KeyLocator - 10, // 43: walletrpc.WalletKit.NextAddr:input_type -> walletrpc.AddrRequest - 21, // 44: walletrpc.WalletKit.GetTransaction:input_type -> walletrpc.GetTransactionRequest - 15, // 45: walletrpc.WalletKit.ListAccounts:input_type -> walletrpc.ListAccountsRequest - 17, // 46: walletrpc.WalletKit.RequiredReserve:input_type -> walletrpc.RequiredReserveRequest - 19, // 47: walletrpc.WalletKit.ListAddresses:input_type -> walletrpc.ListAddressesRequest - 22, // 48: walletrpc.WalletKit.SignMessageWithAddr:input_type -> walletrpc.SignMessageWithAddrRequest - 24, // 49: walletrpc.WalletKit.VerifyMessageWithAddr:input_type -> walletrpc.VerifyMessageWithAddrRequest - 26, // 50: walletrpc.WalletKit.ImportAccount:input_type -> walletrpc.ImportAccountRequest - 28, // 51: walletrpc.WalletKit.ImportPublicKey:input_type -> walletrpc.ImportPublicKeyRequest - 30, // 52: walletrpc.WalletKit.ImportTapscript:input_type -> walletrpc.ImportTapscriptRequest - 35, // 53: walletrpc.WalletKit.PublishTransaction:input_type -> walletrpc.Transaction - 37, // 54: walletrpc.WalletKit.SubmitPackage:input_type -> walletrpc.SubmitPackageRequest - 21, // 55: walletrpc.WalletKit.RemoveTransaction:input_type -> walletrpc.GetTransactionRequest - 41, // 56: walletrpc.WalletKit.SendOutputs:input_type -> walletrpc.SendOutputsRequest - 43, // 57: walletrpc.WalletKit.EstimateFee:input_type -> walletrpc.EstimateFeeRequest - 46, // 58: walletrpc.WalletKit.PendingSweeps:input_type -> walletrpc.PendingSweepsRequest - 48, // 59: walletrpc.WalletKit.BumpFee:input_type -> walletrpc.BumpFeeRequest - 50, // 60: walletrpc.WalletKit.BumpForceCloseFee:input_type -> walletrpc.BumpForceCloseFeeRequest - 52, // 61: walletrpc.WalletKit.ListSweeps:input_type -> walletrpc.ListSweepsRequest - 54, // 62: walletrpc.WalletKit.LabelTransaction:input_type -> walletrpc.LabelTransactionRequest - 56, // 63: walletrpc.WalletKit.FundPsbt:input_type -> walletrpc.FundPsbtRequest - 61, // 64: walletrpc.WalletKit.SignPsbt:input_type -> walletrpc.SignPsbtRequest - 63, // 65: walletrpc.WalletKit.FinalizePsbt:input_type -> walletrpc.FinalizePsbtRequest - 4, // 66: walletrpc.WalletKit.ListUnspent:output_type -> walletrpc.ListUnspentResponse - 6, // 67: walletrpc.WalletKit.LeaseOutput:output_type -> walletrpc.LeaseOutputResponse - 8, // 68: walletrpc.WalletKit.ReleaseOutput:output_type -> walletrpc.ReleaseOutputResponse - 66, // 69: walletrpc.WalletKit.ListLeases:output_type -> walletrpc.ListLeasesResponse - 77, // 70: walletrpc.WalletKit.DeriveNextKey:output_type -> signrpc.KeyDescriptor - 77, // 71: walletrpc.WalletKit.DeriveKey:output_type -> signrpc.KeyDescriptor - 11, // 72: walletrpc.WalletKit.NextAddr:output_type -> walletrpc.AddrResponse - 78, // 73: walletrpc.WalletKit.GetTransaction:output_type -> lnrpc.Transaction - 16, // 74: walletrpc.WalletKit.ListAccounts:output_type -> walletrpc.ListAccountsResponse - 18, // 75: walletrpc.WalletKit.RequiredReserve:output_type -> walletrpc.RequiredReserveResponse - 20, // 76: walletrpc.WalletKit.ListAddresses:output_type -> walletrpc.ListAddressesResponse - 23, // 77: walletrpc.WalletKit.SignMessageWithAddr:output_type -> walletrpc.SignMessageWithAddrResponse - 25, // 78: walletrpc.WalletKit.VerifyMessageWithAddr:output_type -> walletrpc.VerifyMessageWithAddrResponse - 27, // 79: walletrpc.WalletKit.ImportAccount:output_type -> walletrpc.ImportAccountResponse - 29, // 80: walletrpc.WalletKit.ImportPublicKey:output_type -> walletrpc.ImportPublicKeyResponse - 34, // 81: walletrpc.WalletKit.ImportTapscript:output_type -> walletrpc.ImportTapscriptResponse - 36, // 82: walletrpc.WalletKit.PublishTransaction:output_type -> walletrpc.PublishResponse - 39, // 83: walletrpc.WalletKit.SubmitPackage:output_type -> walletrpc.SubmitPackageResponse - 40, // 84: walletrpc.WalletKit.RemoveTransaction:output_type -> walletrpc.RemoveTransactionResponse - 42, // 85: walletrpc.WalletKit.SendOutputs:output_type -> walletrpc.SendOutputsResponse - 44, // 86: walletrpc.WalletKit.EstimateFee:output_type -> walletrpc.EstimateFeeResponse - 47, // 87: walletrpc.WalletKit.PendingSweeps:output_type -> walletrpc.PendingSweepsResponse - 49, // 88: walletrpc.WalletKit.BumpFee:output_type -> walletrpc.BumpFeeResponse - 51, // 89: walletrpc.WalletKit.BumpForceCloseFee:output_type -> walletrpc.BumpForceCloseFeeResponse - 53, // 90: walletrpc.WalletKit.ListSweeps:output_type -> walletrpc.ListSweepsResponse - 55, // 91: walletrpc.WalletKit.LabelTransaction:output_type -> walletrpc.LabelTransactionResponse - 57, // 92: walletrpc.WalletKit.FundPsbt:output_type -> walletrpc.FundPsbtResponse - 62, // 93: walletrpc.WalletKit.SignPsbt:output_type -> walletrpc.SignPsbtResponse - 64, // 94: walletrpc.WalletKit.FinalizePsbt:output_type -> walletrpc.FinalizePsbtResponse - 66, // [66:95] is the sub-list for method output_type - 37, // [37:66] is the sub-list for method input_type - 37, // [37:37] is the sub-list for extension type_name - 37, // [37:37] is the sub-list for extension extendee - 0, // [0:37] is the sub-list for field type_name + 68, // 17: walletrpc.SendOutputsRequest.outputs:type_name -> signrpc.TxOut + 69, // 18: walletrpc.SendOutputsRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy + 67, // 19: walletrpc.PendingSweep.outpoint:type_name -> lnrpc.OutPoint + 1, // 20: walletrpc.PendingSweep.witness_type:type_name -> walletrpc.WitnessType + 42, // 21: walletrpc.PendingSweepsResponse.pending_sweeps:type_name -> walletrpc.PendingSweep + 67, // 22: walletrpc.BumpFeeRequest.outpoint:type_name -> lnrpc.OutPoint + 70, // 23: walletrpc.BumpForceCloseFeeRequest.chan_point:type_name -> lnrpc.ChannelPoint + 71, // 24: walletrpc.ListSweepsResponse.transaction_details:type_name -> lnrpc.TransactionDetails + 64, // 25: walletrpc.ListSweepsResponse.transaction_ids:type_name -> walletrpc.ListSweepsResponse.TransactionIDs + 55, // 26: walletrpc.FundPsbtRequest.raw:type_name -> walletrpc.TxTemplate + 56, // 27: walletrpc.FundPsbtRequest.coin_select:type_name -> walletrpc.PsbtCoinSelect + 2, // 28: walletrpc.FundPsbtRequest.change_type:type_name -> walletrpc.ChangeAddressType + 69, // 29: walletrpc.FundPsbtRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy + 57, // 30: walletrpc.FundPsbtResponse.locked_utxos:type_name -> walletrpc.UtxoLease + 67, // 31: walletrpc.TxTemplate.inputs:type_name -> lnrpc.OutPoint + 65, // 32: walletrpc.TxTemplate.outputs:type_name -> walletrpc.TxTemplate.OutputsEntry + 67, // 33: walletrpc.UtxoLease.outpoint:type_name -> lnrpc.OutPoint + 57, // 34: walletrpc.ListLeasesResponse.locked_utxos:type_name -> walletrpc.UtxoLease + 3, // 35: walletrpc.WalletKit.ListUnspent:input_type -> walletrpc.ListUnspentRequest + 5, // 36: walletrpc.WalletKit.LeaseOutput:input_type -> walletrpc.LeaseOutputRequest + 7, // 37: walletrpc.WalletKit.ReleaseOutput:input_type -> walletrpc.ReleaseOutputRequest + 62, // 38: walletrpc.WalletKit.ListLeases:input_type -> walletrpc.ListLeasesRequest + 9, // 39: walletrpc.WalletKit.DeriveNextKey:input_type -> walletrpc.KeyReq + 72, // 40: walletrpc.WalletKit.DeriveKey:input_type -> signrpc.KeyLocator + 10, // 41: walletrpc.WalletKit.NextAddr:input_type -> walletrpc.AddrRequest + 21, // 42: walletrpc.WalletKit.GetTransaction:input_type -> walletrpc.GetTransactionRequest + 15, // 43: walletrpc.WalletKit.ListAccounts:input_type -> walletrpc.ListAccountsRequest + 17, // 44: walletrpc.WalletKit.RequiredReserve:input_type -> walletrpc.RequiredReserveRequest + 19, // 45: walletrpc.WalletKit.ListAddresses:input_type -> walletrpc.ListAddressesRequest + 22, // 46: walletrpc.WalletKit.SignMessageWithAddr:input_type -> walletrpc.SignMessageWithAddrRequest + 24, // 47: walletrpc.WalletKit.VerifyMessageWithAddr:input_type -> walletrpc.VerifyMessageWithAddrRequest + 26, // 48: walletrpc.WalletKit.ImportAccount:input_type -> walletrpc.ImportAccountRequest + 28, // 49: walletrpc.WalletKit.ImportPublicKey:input_type -> walletrpc.ImportPublicKeyRequest + 30, // 50: walletrpc.WalletKit.ImportTapscript:input_type -> walletrpc.ImportTapscriptRequest + 35, // 51: walletrpc.WalletKit.PublishTransaction:input_type -> walletrpc.Transaction + 21, // 52: walletrpc.WalletKit.RemoveTransaction:input_type -> walletrpc.GetTransactionRequest + 38, // 53: walletrpc.WalletKit.SendOutputs:input_type -> walletrpc.SendOutputsRequest + 40, // 54: walletrpc.WalletKit.EstimateFee:input_type -> walletrpc.EstimateFeeRequest + 43, // 55: walletrpc.WalletKit.PendingSweeps:input_type -> walletrpc.PendingSweepsRequest + 45, // 56: walletrpc.WalletKit.BumpFee:input_type -> walletrpc.BumpFeeRequest + 47, // 57: walletrpc.WalletKit.BumpForceCloseFee:input_type -> walletrpc.BumpForceCloseFeeRequest + 49, // 58: walletrpc.WalletKit.ListSweeps:input_type -> walletrpc.ListSweepsRequest + 51, // 59: walletrpc.WalletKit.LabelTransaction:input_type -> walletrpc.LabelTransactionRequest + 53, // 60: walletrpc.WalletKit.FundPsbt:input_type -> walletrpc.FundPsbtRequest + 58, // 61: walletrpc.WalletKit.SignPsbt:input_type -> walletrpc.SignPsbtRequest + 60, // 62: walletrpc.WalletKit.FinalizePsbt:input_type -> walletrpc.FinalizePsbtRequest + 4, // 63: walletrpc.WalletKit.ListUnspent:output_type -> walletrpc.ListUnspentResponse + 6, // 64: walletrpc.WalletKit.LeaseOutput:output_type -> walletrpc.LeaseOutputResponse + 8, // 65: walletrpc.WalletKit.ReleaseOutput:output_type -> walletrpc.ReleaseOutputResponse + 63, // 66: walletrpc.WalletKit.ListLeases:output_type -> walletrpc.ListLeasesResponse + 73, // 67: walletrpc.WalletKit.DeriveNextKey:output_type -> signrpc.KeyDescriptor + 73, // 68: walletrpc.WalletKit.DeriveKey:output_type -> signrpc.KeyDescriptor + 11, // 69: walletrpc.WalletKit.NextAddr:output_type -> walletrpc.AddrResponse + 74, // 70: walletrpc.WalletKit.GetTransaction:output_type -> lnrpc.Transaction + 16, // 71: walletrpc.WalletKit.ListAccounts:output_type -> walletrpc.ListAccountsResponse + 18, // 72: walletrpc.WalletKit.RequiredReserve:output_type -> walletrpc.RequiredReserveResponse + 20, // 73: walletrpc.WalletKit.ListAddresses:output_type -> walletrpc.ListAddressesResponse + 23, // 74: walletrpc.WalletKit.SignMessageWithAddr:output_type -> walletrpc.SignMessageWithAddrResponse + 25, // 75: walletrpc.WalletKit.VerifyMessageWithAddr:output_type -> walletrpc.VerifyMessageWithAddrResponse + 27, // 76: walletrpc.WalletKit.ImportAccount:output_type -> walletrpc.ImportAccountResponse + 29, // 77: walletrpc.WalletKit.ImportPublicKey:output_type -> walletrpc.ImportPublicKeyResponse + 34, // 78: walletrpc.WalletKit.ImportTapscript:output_type -> walletrpc.ImportTapscriptResponse + 36, // 79: walletrpc.WalletKit.PublishTransaction:output_type -> walletrpc.PublishResponse + 37, // 80: walletrpc.WalletKit.RemoveTransaction:output_type -> walletrpc.RemoveTransactionResponse + 39, // 81: walletrpc.WalletKit.SendOutputs:output_type -> walletrpc.SendOutputsResponse + 41, // 82: walletrpc.WalletKit.EstimateFee:output_type -> walletrpc.EstimateFeeResponse + 44, // 83: walletrpc.WalletKit.PendingSweeps:output_type -> walletrpc.PendingSweepsResponse + 46, // 84: walletrpc.WalletKit.BumpFee:output_type -> walletrpc.BumpFeeResponse + 48, // 85: walletrpc.WalletKit.BumpForceCloseFee:output_type -> walletrpc.BumpForceCloseFeeResponse + 50, // 86: walletrpc.WalletKit.ListSweeps:output_type -> walletrpc.ListSweepsResponse + 52, // 87: walletrpc.WalletKit.LabelTransaction:output_type -> walletrpc.LabelTransactionResponse + 54, // 88: walletrpc.WalletKit.FundPsbt:output_type -> walletrpc.FundPsbtResponse + 59, // 89: walletrpc.WalletKit.SignPsbt:output_type -> walletrpc.SignPsbtResponse + 61, // 90: walletrpc.WalletKit.FinalizePsbt:output_type -> walletrpc.FinalizePsbtResponse + 63, // [63:91] is the sub-list for method output_type + 35, // [35:63] is the sub-list for method input_type + 35, // [35:35] is the sub-list for extension type_name + 35, // [35:35] is the sub-list for extension extendee + 0, // [0:35] is the sub-list for field type_name } func init() { file_walletrpc_walletkit_proto_init() } @@ -5287,18 +5569,763 @@ func file_walletrpc_walletkit_proto_init() { if File_walletrpc_walletkit_proto != nil { return } - file_walletrpc_walletkit_proto_msgTypes[27].OneofWrappers = []any{ + if !protoimpl.UnsafeEnabled { + file_walletrpc_walletkit_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListUnspentRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListUnspentResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LeaseOutputRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LeaseOutputResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReleaseOutputRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReleaseOutputResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KeyReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddrRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddrResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Account); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddressProperty); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AccountWithAddresses); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListAccountsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListAccountsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RequiredReserveRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RequiredReserveResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListAddressesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListAddressesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetTransactionRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignMessageWithAddrRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignMessageWithAddrResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VerifyMessageWithAddrRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VerifyMessageWithAddrResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ImportAccountRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ImportAccountResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ImportPublicKeyRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ImportPublicKeyResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ImportTapscriptRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TapscriptFullTree); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TapLeaf); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TapscriptPartialReveal); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ImportTapscriptResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Transaction); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PublishResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RemoveTransactionResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendOutputsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendOutputsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EstimateFeeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EstimateFeeResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PendingSweep); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PendingSweepsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PendingSweepsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BumpFeeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BumpFeeResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BumpForceCloseFeeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BumpForceCloseFeeResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[46].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListSweepsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[47].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListSweepsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[48].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LabelTransactionRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LabelTransactionResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[50].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FundPsbtRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[51].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FundPsbtResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[52].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TxTemplate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[53].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PsbtCoinSelect); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[54].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UtxoLease); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[55].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignPsbtRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[56].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SignPsbtResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[57].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FinalizePsbtRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[58].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FinalizePsbtResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[59].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListLeasesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[60].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListLeasesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletrpc_walletkit_proto_msgTypes[61].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListSweepsResponse_TransactionIDs); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_walletrpc_walletkit_proto_msgTypes[27].OneofWrappers = []interface{}{ (*ImportTapscriptRequest_FullTree)(nil), (*ImportTapscriptRequest_PartialReveal)(nil), (*ImportTapscriptRequest_RootHashOnly)(nil), (*ImportTapscriptRequest_FullKeyOnly)(nil), } - file_walletrpc_walletkit_proto_msgTypes[34].OneofWrappers = []any{} - file_walletrpc_walletkit_proto_msgTypes[50].OneofWrappers = []any{ + file_walletrpc_walletkit_proto_msgTypes[47].OneofWrappers = []interface{}{ (*ListSweepsResponse_TransactionDetails)(nil), (*ListSweepsResponse_TransactionIds)(nil), } - file_walletrpc_walletkit_proto_msgTypes[53].OneofWrappers = []any{ + file_walletrpc_walletkit_proto_msgTypes[50].OneofWrappers = []interface{}{ (*FundPsbtRequest_Psbt)(nil), (*FundPsbtRequest_Raw)(nil), (*FundPsbtRequest_CoinSelect)(nil), @@ -5306,7 +6333,7 @@ func file_walletrpc_walletkit_proto_init() { (*FundPsbtRequest_SatPerVbyte)(nil), (*FundPsbtRequest_SatPerKw)(nil), } - file_walletrpc_walletkit_proto_msgTypes[56].OneofWrappers = []any{ + file_walletrpc_walletkit_proto_msgTypes[53].OneofWrappers = []interface{}{ (*PsbtCoinSelect_ExistingOutputIndex)(nil), (*PsbtCoinSelect_Add)(nil), } @@ -5314,9 +6341,9 @@ func file_walletrpc_walletkit_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_walletrpc_walletkit_proto_rawDesc), len(file_walletrpc_walletkit_proto_rawDesc)), + RawDescriptor: file_walletrpc_walletkit_proto_rawDesc, NumEnums: 3, - NumMessages: 67, + NumMessages: 63, NumExtensions: 0, NumServices: 1, }, @@ -5326,6 +6353,7 @@ func file_walletrpc_walletkit_proto_init() { MessageInfos: file_walletrpc_walletkit_proto_msgTypes, }.Build() File_walletrpc_walletkit_proto = out.File + file_walletrpc_walletkit_proto_rawDesc = nil file_walletrpc_walletkit_proto_goTypes = nil file_walletrpc_walletkit_proto_depIdxs = nil } diff --git a/lnrpc/walletrpc/walletkit.pb.gw.go b/lnrpc/walletrpc/walletkit.pb.gw.go index 32cfdc35f..c4c4db99f 100644 --- a/lnrpc/walletrpc/walletkit.pb.gw.go +++ b/lnrpc/walletrpc/walletkit.pb.gw.go @@ -602,40 +602,6 @@ func local_request_WalletKit_PublishTransaction_0(ctx context.Context, marshaler } -func request_WalletKit_SubmitPackage_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq SubmitPackageRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := client.SubmitPackage(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) - return msg, metadata, err - -} - -func local_request_WalletKit_SubmitPackage_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq SubmitPackageRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.SubmitPackage(ctx, &protoReq) - return msg, metadata, err - -} - func request_WalletKit_RemoveTransaction_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetTransactionRequest var metadata runtime.ServerMetadata @@ -1445,31 +1411,6 @@ func RegisterWalletKitHandlerServer(ctx context.Context, mux *runtime.ServeMux, }) - mux.Handle("POST", pattern_WalletKit_SubmitPackage_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - var stream runtime.ServerTransportStream - ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - var err error - var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/SubmitPackage", runtime.WithHTTPPathPattern("/v2/wallet/tx/package")) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_WalletKit_SubmitPackage_0(annotatedContext, inboundMarshaler, server, req, pathParams) - md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) - if err != nil { - runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) - return - } - - forward_WalletKit_SubmitPackage_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - mux.Handle("POST", pattern_WalletKit_RemoveTransaction_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -2160,28 +2101,6 @@ func RegisterWalletKitHandlerClient(ctx context.Context, mux *runtime.ServeMux, }) - mux.Handle("POST", pattern_WalletKit_SubmitPackage_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - var err error - var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/SubmitPackage", runtime.WithHTTPPathPattern("/v2/wallet/tx/package")) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := request_WalletKit_SubmitPackage_0(annotatedContext, inboundMarshaler, client, req, pathParams) - annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) - if err != nil { - runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) - return - } - - forward_WalletKit_SubmitPackage_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - mux.Handle("POST", pattern_WalletKit_RemoveTransaction_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -2462,8 +2381,6 @@ var ( pattern_WalletKit_PublishTransaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "tx"}, "")) - pattern_WalletKit_SubmitPackage_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "tx", "package"}, "")) - pattern_WalletKit_RemoveTransaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "removetx"}, "")) pattern_WalletKit_SendOutputs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "send"}, "")) @@ -2522,8 +2439,6 @@ var ( forward_WalletKit_PublishTransaction_0 = runtime.ForwardResponseMessage - forward_WalletKit_SubmitPackage_0 = runtime.ForwardResponseMessage - forward_WalletKit_RemoveTransaction_0 = runtime.ForwardResponseMessage forward_WalletKit_SendOutputs_0 = runtime.ForwardResponseMessage diff --git a/lnrpc/walletrpc/walletkit.pb.json.go b/lnrpc/walletrpc/walletkit.pb.json.go index ba427b0e4..833780692 100644 --- a/lnrpc/walletrpc/walletkit.pb.json.go +++ b/lnrpc/walletrpc/walletkit.pb.json.go @@ -447,31 +447,6 @@ func RegisterWalletKitJSONCallbacks(registry map[string]func(ctx context.Context callback(string(respBytes), nil) } - registry["walletrpc.WalletKit.SubmitPackage"] = func(ctx context.Context, - conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { - - req := &SubmitPackageRequest{} - err := marshaler.Unmarshal([]byte(reqJSON), req) - if err != nil { - callback("", err) - return - } - - client := NewWalletKitClient(conn) - resp, err := client.SubmitPackage(ctx, req) - if err != nil { - callback("", err) - return - } - - respBytes, err := marshaler.Marshal(resp) - if err != nil { - callback("", err) - return - } - callback(string(respBytes), nil) - } - registry["walletrpc.WalletKit.RemoveTransaction"] = func(ctx context.Context, conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { diff --git a/lnrpc/walletrpc/walletkit.proto b/lnrpc/walletrpc/walletkit.proto index 540c571d3..b50d2d678 100644 --- a/lnrpc/walletrpc/walletkit.proto +++ b/lnrpc/walletrpc/walletkit.proto @@ -208,20 +208,6 @@ service WalletKit { */ rpc PublishTransaction (Transaction) returns (PublishResponse); - /* lncli: `wallet submitpackage` - SubmitPackage submits a package of related transactions (topologically - sorted, unconfirmed parents first and the child last) for atomic - validation and acceptance. Real package submission is only performed by - the bitcoind backend, via the node's submitpackage RPC, which lets a - zero-fee v3/TRUC parent be accepted via its fee-paying CPFP child. The - btcd backend does not support submitpackage and returns an error. A - neutrino light client has no mempool and cannot atomically accept a - package; as a best effort it broadcasts the transactions individually and - relies on a peer's 1p1c package relay, returning an unverified result - (not a package-accept verdict). - */ - rpc SubmitPackage (SubmitPackageRequest) returns (SubmitPackageResponse); - /* lncli: `wallet removetx` RemoveTransaction attempts to remove the provided transaction from the internal transaction store of the wallet. @@ -342,7 +328,7 @@ service WalletKit { */ rpc FundPsbt (FundPsbtRequest) returns (FundPsbtResponse); - /* lncli: `wallet psbt sign` + /* SignPsbt expects a partial transaction with all inputs and outputs fully declared and tries to sign all unsigned inputs that have all required fields (UTXO information, BIP32 derivation information, witness or sig scripts) @@ -815,48 +801,6 @@ message PublishResponse { string publish_error = 1; } -message SubmitPackageRequest { - /* - The raw serialized transactions forming the package, topologically sorted - with unconfirmed parents first and the child last. - */ - repeated bytes raw_txs = 1; - - /* - Optional per-transaction fee-rate ceiling in sat/vByte (mapped onto the - submitpackage maxfeerate). When unset the node's default is used; an - explicit 0 means no limit, which is required for a CPFP child whose - standalone feerate is high. - */ - optional uint64 sat_per_vbyte = 2; -} - -message SubmitPackageTxResult { - // The transaction id (txid) in hex. - string txid = 1; - - // If non-empty, the reason this transaction was rejected. - string error = 2; - - /* - If non-empty, the wtxid (in hex) of a transaction with the same txid but a - different witness that was already in the mempool; the submitted - transaction was ignored as a duplicate (witness replacement). - */ - string other_wtxid = 3; -} - -message SubmitPackageResponse { - // A summary message; "success" when the whole package was accepted. - string package_msg = 1; - - // Per-transaction results keyed by wtxid (hex). - map tx_results = 2; - - // The txids of transactions evicted via package RBF. - repeated string replaced_transactions = 3; -} - message RemoveTransactionResponse { // The status of the remove transaction operation. string status = 1; @@ -1154,56 +1098,6 @@ enum WitnessType { counterparty's who broadcasts a revoked taproot commitment transaction. */ TAPROOT_COMMITMENT_REVOKE = 35; - - /* - A witness type that allows us to spend our settled local commitment after a - CSV delay when we force close a production taproot channel. - */ - TAPROOT_LOCAL_COMMIT_SPEND_FINAL = 36; - - /* - A witness type that allows us to spend our settled local commitment after - a CSV delay when the remote party has force closed a production taproot - channel. - */ - TAPROOT_REMOTE_COMMIT_SPEND_FINAL = 37; - - /* - A witness that allows us to timeout an HTLC we offered to the remote party - on our production taproot commitment transaction. We use this when we need - to go on chain to time out an HTLC. - */ - TAPROOT_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL_FINAL = 38; - - /* - A witness type that allows us to sweep an HTLC we accepted on our - production taproot commitment transaction after we go to the second level - on chain. - */ - TAPROOT_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL_FINAL = 39; - - /* - A witness that allows us to sweep an HTLC we offered to the remote party - that lies on the production taproot commitment transaction for the remote - party. We can spend this output after the absolute CLTV timeout of the - HTLC as passed. - */ - TAPROOT_HTLC_OFFERED_REMOTE_TIMEOUT_FINAL = 40; - - /* - A witness that allows us to sweep an HTLC that was offered to us by the - remote party for a production taproot channel. We use this witness in the - case that the remote party goes to chain, and we know the pre-image to the - HTLC. We can sweep this without any additional timeout. - */ - TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS_FINAL = 41; - - /* - A witness type that allows us to sweep the settled output of a malicious - counterparty's who broadcasts a revoked production taproot commitment - transaction. - */ - TAPROOT_COMMITMENT_REVOKE_FINAL = 42; } message PendingSweep { diff --git a/lnrpc/walletrpc/walletkit.swagger.json b/lnrpc/walletrpc/walletkit.swagger.json index 90365c304..1e8e0741f 100644 --- a/lnrpc/walletrpc/walletkit.swagger.json +++ b/lnrpc/walletrpc/walletkit.swagger.json @@ -507,7 +507,7 @@ }, "/v2/wallet/psbt/sign": { "post": { - "summary": "lncli: `wallet psbt sign`\nSignPsbt expects a partial transaction with all inputs and outputs fully\ndeclared and tries to sign all unsigned inputs that have all required fields\n(UTXO information, BIP32 derivation information, witness or sig scripts)\nset.\nIf no error is returned, the PSBT is ready to be given to the next signer or\nto be finalized if lnd was the last signer.", + "summary": "SignPsbt expects a partial transaction with all inputs and outputs fully\ndeclared and tries to sign all unsigned inputs that have all required fields\n(UTXO information, BIP32 derivation information, witness or sig scripts)\nset.\nIf no error is returned, the PSBT is ready to be given to the next signer or\nto be finalized if lnd was the last signer.", "description": "NOTE: This RPC only signs inputs (and only those it can sign), it does not\nperform any other tasks (such as coin selection, UTXO locking or\ninput/output/fee value validation, PSBT finalization). Any input that is\nincomplete will be skipped.", "operationId": "WalletKit_SignPsbt", "responses": { @@ -832,39 +832,6 @@ ] } }, - "/v2/wallet/tx/package": { - "post": { - "summary": "lncli: `wallet submitpackage`\nSubmitPackage submits a package of related transactions (topologically\nsorted, unconfirmed parents first and the child last) for atomic\nvalidation and acceptance. Real package submission is only performed by\nthe bitcoind backend, via the node's submitpackage RPC, which lets a\nzero-fee v3/TRUC parent be accepted via its fee-paying CPFP child. The\nbtcd backend does not support submitpackage and returns an error. A\nneutrino light client has no mempool and cannot atomically accept a\npackage; as a best effort it broadcasts the transactions individually and\nrelies on a peer's 1p1c package relay, returning an unverified result\n(not a package-accept verdict).", - "operationId": "WalletKit_SubmitPackage", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/walletrpcSubmitPackageResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/rpcStatus" - } - } - }, - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/walletrpcSubmitPackageRequest" - } - } - ], - "tags": [ - "WalletKit" - ] - } - }, "/v2/wallet/utxos": { "post": { "summary": "ListUnspent returns a list of all utxos spendable by the wallet with a\nnumber of confirmations between the specified minimum and maximum. By\ndefault, all utxos are listed. To list only the unconfirmed utxos, set\nthe unconfirmed_only to true.", @@ -2213,64 +2180,6 @@ } } }, - "walletrpcSubmitPackageRequest": { - "type": "object", - "properties": { - "raw_txs": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - }, - "description": "The raw serialized transactions forming the package, topologically sorted\nwith unconfirmed parents first and the child last." - }, - "sat_per_vbyte": { - "type": "string", - "format": "uint64", - "description": "Optional per-transaction fee-rate ceiling in sat/vByte (mapped onto the\nsubmitpackage maxfeerate). When unset the node's default is used; an\nexplicit 0 means no limit, which is required for a CPFP child whose\nstandalone feerate is high." - } - } - }, - "walletrpcSubmitPackageResponse": { - "type": "object", - "properties": { - "package_msg": { - "type": "string", - "description": "A summary message; \"success\" when the whole package was accepted." - }, - "tx_results": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/walletrpcSubmitPackageTxResult" - }, - "description": "Per-transaction results keyed by wtxid (hex)." - }, - "replaced_transactions": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The txids of transactions evicted via package RBF." - } - } - }, - "walletrpcSubmitPackageTxResult": { - "type": "object", - "properties": { - "txid": { - "type": "string", - "description": "The transaction id (txid) in hex." - }, - "error": { - "type": "string", - "description": "If non-empty, the reason this transaction was rejected." - }, - "other_wtxid": { - "type": "string", - "description": "If non-empty, the wtxid (in hex) of a transaction with the same txid but a\ndifferent witness that was already in the mempool; the submitted\ntransaction was ignored as a duplicate (witness replacement)." - } - } - }, "walletrpcTapLeaf": { "type": "object", "properties": { @@ -2447,17 +2356,10 @@ "TAPROOT_HTLC_LOCAL_OFFERED_TIMEOUT", "TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS", "TAPROOT_HTLC_ACCEPTED_LOCAL_SUCCESS", - "TAPROOT_COMMITMENT_REVOKE", - "TAPROOT_LOCAL_COMMIT_SPEND_FINAL", - "TAPROOT_REMOTE_COMMIT_SPEND_FINAL", - "TAPROOT_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL_FINAL", - "TAPROOT_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL_FINAL", - "TAPROOT_HTLC_OFFERED_REMOTE_TIMEOUT_FINAL", - "TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS_FINAL", - "TAPROOT_COMMITMENT_REVOKE_FINAL" + "TAPROOT_COMMITMENT_REVOKE" ], "default": "UNKNOWN_WITNESS", - "description": " - COMMITMENT_TIME_LOCK: A witness that allows us to spend the output of a commitment transaction\nafter a relative lock-time lockout.\n - COMMITMENT_NO_DELAY: A witness that allows us to spend a settled no-delay output immediately on a\ncounterparty's commitment transaction.\n - COMMITMENT_REVOKE: A witness that allows us to sweep the settled output of a malicious\ncounterparty's who broadcasts a revoked commitment transaction.\n - HTLC_OFFERED_REVOKE: A witness that allows us to sweep an HTLC which we offered to the remote\nparty in the case that they broadcast a revoked commitment state.\n - HTLC_ACCEPTED_REVOKE: A witness that allows us to sweep an HTLC output sent to us in the case that\nthe remote party broadcasts a revoked commitment state.\n - HTLC_OFFERED_TIMEOUT_SECOND_LEVEL: A witness that allows us to sweep an HTLC output that we extended to a\nparty, but was never fulfilled. This HTLC output isn't directly on the\ncommitment transaction, but is the result of a confirmed second-level HTLC\ntransaction. As a result, we can only spend this after a CSV delay.\n - HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL: A witness that allows us to sweep an HTLC output that was offered to us, and\nfor which we have a payment preimage. This HTLC output isn't directly on our\ncommitment transaction, but is the result of confirmed second-level HTLC\ntransaction. As a result, we can only spend this after a CSV delay.\n - HTLC_OFFERED_REMOTE_TIMEOUT: A witness that allows us to sweep an HTLC that we offered to the remote\nparty which lies in the commitment transaction of the remote party. We can\nspend this output after the absolute CLTV timeout of the HTLC as passed.\n - HTLC_ACCEPTED_REMOTE_SUCCESS: A witness that allows us to sweep an HTLC that was offered to us by the\nremote party. We use this witness in the case that the remote party goes to\nchain, and we know the pre-image to the HTLC. We can sweep this without any\nadditional timeout.\n - HTLC_SECOND_LEVEL_REVOKE: A witness that allows us to sweep an HTLC from the remote party's commitment\ntransaction in the case that the broadcast a revoked commitment, but then\nalso immediately attempt to go to the second level to claim the HTLC.\n - WITNESS_KEY_HASH: A witness type that allows us to spend a regular p2wkh output that's sent to\nan output which is under complete control of the backing wallet.\n - NESTED_WITNESS_KEY_HASH: A witness type that allows us to sweep an output that sends to a nested P2SH\nscript that pays to a key solely under our control.\n - COMMITMENT_ANCHOR: A witness type that allows us to spend our anchor on the commitment\ntransaction.\n - COMMITMENT_NO_DELAY_TWEAKLESS: A witness type that is similar to the COMMITMENT_NO_DELAY type,\nbut it omits the tweak that randomizes the key we need to\nspend with a channel peer supplied set of randomness.\n - COMMITMENT_TO_REMOTE_CONFIRMED: A witness type that allows us to spend our output on the counterparty's\ncommitment transaction after a confirmation.\n - HTLC_OFFERED_TIMEOUT_SECOND_LEVEL_INPUT_CONFIRMED: A witness type that allows us to sweep an HTLC output that we extended\nto a party, but was never fulfilled. This _is_ the HTLC output directly\non our commitment transaction, and the input to the second-level HTLC\ntimeout transaction. It can only be spent after CLTV expiry, and\ncommitment confirmation.\n - HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL_INPUT_CONFIRMED: A witness type that allows us to sweep an HTLC output that was offered\nto us, and for which we have a payment preimage. This _is_ the HTLC\noutput directly on our commitment transaction, and the input to the\nsecond-level HTLC success transaction. It can only be spent after the\ncommitment has confirmed.\n - LEASE_COMMITMENT_TIME_LOCK: A witness type that allows us to spend our output on our local\ncommitment transaction after a relative and absolute lock-time lockout as\npart of the script enforced lease commitment type.\n - LEASE_COMMITMENT_TO_REMOTE_CONFIRMED: A witness type that allows us to spend our output on the counterparty's\ncommitment transaction after a confirmation and absolute locktime as part\nof the script enforced lease commitment type.\n - LEASE_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL: A witness type that allows us to sweep an HTLC output that we extended\nto a party, but was never fulfilled. This HTLC output isn't directly on\nthe commitment transaction, but is the result of a confirmed second-level\nHTLC transaction. As a result, we can only spend this after a CSV delay\nand CLTV locktime as part of the script enforced lease commitment type.\n - LEASE_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL: A witness type that allows us to sweep an HTLC output that was offered\nto us, and for which we have a payment preimage. This HTLC output isn't\ndirectly on our commitment transaction, but is the result of confirmed\nsecond-level HTLC transaction. As a result, we can only spend this after\na CSV delay and CLTV locktime as part of the script enforced lease\ncommitment type.\n - TAPROOT_PUB_KEY_SPEND: A witness type that allows us to spend a regular p2tr output that's sent\nto an output which is under complete control of the backing wallet.\n - TAPROOT_LOCAL_COMMIT_SPEND: A witness type that allows us to spend our settled local commitment after a\nCSV delay when we force close the channel.\n - TAPROOT_REMOTE_COMMIT_SPEND: A witness type that allows us to spend our settled local commitment after\na CSV delay when the remote party has force closed the channel.\n - TAPROOT_ANCHOR_SWEEP_SPEND: A witness type that we'll use for spending our own anchor output.\n - TAPROOT_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL: A witness that allows us to timeout an HTLC we offered to the remote party\non our commitment transaction. We use this when we need to go on chain to\ntime out an HTLC.\n - TAPROOT_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL: A witness type that allows us to sweep an HTLC we accepted on our commitment\ntransaction after we go to the second level on chain.\n - TAPROOT_HTLC_SECOND_LEVEL_REVOKE: A witness that allows us to sweep an HTLC on the revoked transaction of the\nremote party that goes to the second level.\n - TAPROOT_HTLC_ACCEPTED_REVOKE: A witness that allows us to sweep an HTLC sent to us by the remote party\nin the event that they broadcast a revoked state.\n - TAPROOT_HTLC_OFFERED_REVOKE: A witness that allows us to sweep an HTLC we offered to the remote party if\nthey broadcast a revoked commitment.\n - TAPROOT_HTLC_OFFERED_REMOTE_TIMEOUT: A witness that allows us to sweep an HTLC we offered to the remote party\nthat lies on the commitment transaction for the remote party. We can spend\nthis output after the absolute CLTV timeout of the HTLC as passed.\n - TAPROOT_HTLC_LOCAL_OFFERED_TIMEOUT: A witness type that allows us to sign the second level HTLC timeout\ntransaction when spending from an HTLC residing on our local commitment\ntransaction.\nThis is used by the sweeper to re-sign inputs if it needs to aggregate\nseveral second level HTLCs.\n - TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS: A witness that allows us to sweep an HTLC that was offered to us by the\nremote party for a taproot channels. We use this witness in the case that\nthe remote party goes to chain, and we know the pre-image to the HTLC. We\ncan sweep this without any additional timeout.\n - TAPROOT_HTLC_ACCEPTED_LOCAL_SUCCESS: A witness type that allows us to sweep the HTLC offered to us on our local\ncommitment transaction. We'll use this when we need to go on chain to sweep\nthe HTLC. In this case, this is the second level HTLC success transaction.\n - TAPROOT_COMMITMENT_REVOKE: A witness that allows us to sweep the settled output of a malicious\ncounterparty's who broadcasts a revoked taproot commitment transaction.\n - TAPROOT_LOCAL_COMMIT_SPEND_FINAL: A witness type that allows us to spend our settled local commitment after a\nCSV delay when we force close a production taproot channel.\n - TAPROOT_REMOTE_COMMIT_SPEND_FINAL: A witness type that allows us to spend our settled local commitment after\na CSV delay when the remote party has force closed a production taproot\nchannel.\n - TAPROOT_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL_FINAL: A witness that allows us to timeout an HTLC we offered to the remote party\non our production taproot commitment transaction. We use this when we need\nto go on chain to time out an HTLC.\n - TAPROOT_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL_FINAL: A witness type that allows us to sweep an HTLC we accepted on our\nproduction taproot commitment transaction after we go to the second level\non chain.\n - TAPROOT_HTLC_OFFERED_REMOTE_TIMEOUT_FINAL: A witness that allows us to sweep an HTLC we offered to the remote party\nthat lies on the production taproot commitment transaction for the remote\nparty. We can spend this output after the absolute CLTV timeout of the\nHTLC as passed.\n - TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS_FINAL: A witness that allows us to sweep an HTLC that was offered to us by the\nremote party for a production taproot channel. We use this witness in the\ncase that the remote party goes to chain, and we know the pre-image to the\nHTLC. We can sweep this without any additional timeout.\n - TAPROOT_COMMITMENT_REVOKE_FINAL: A witness type that allows us to sweep the settled output of a malicious\ncounterparty's who broadcasts a revoked production taproot commitment\ntransaction." + "description": " - COMMITMENT_TIME_LOCK: A witness that allows us to spend the output of a commitment transaction\nafter a relative lock-time lockout.\n - COMMITMENT_NO_DELAY: A witness that allows us to spend a settled no-delay output immediately on a\ncounterparty's commitment transaction.\n - COMMITMENT_REVOKE: A witness that allows us to sweep the settled output of a malicious\ncounterparty's who broadcasts a revoked commitment transaction.\n - HTLC_OFFERED_REVOKE: A witness that allows us to sweep an HTLC which we offered to the remote\nparty in the case that they broadcast a revoked commitment state.\n - HTLC_ACCEPTED_REVOKE: A witness that allows us to sweep an HTLC output sent to us in the case that\nthe remote party broadcasts a revoked commitment state.\n - HTLC_OFFERED_TIMEOUT_SECOND_LEVEL: A witness that allows us to sweep an HTLC output that we extended to a\nparty, but was never fulfilled. This HTLC output isn't directly on the\ncommitment transaction, but is the result of a confirmed second-level HTLC\ntransaction. As a result, we can only spend this after a CSV delay.\n - HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL: A witness that allows us to sweep an HTLC output that was offered to us, and\nfor which we have a payment preimage. This HTLC output isn't directly on our\ncommitment transaction, but is the result of confirmed second-level HTLC\ntransaction. As a result, we can only spend this after a CSV delay.\n - HTLC_OFFERED_REMOTE_TIMEOUT: A witness that allows us to sweep an HTLC that we offered to the remote\nparty which lies in the commitment transaction of the remote party. We can\nspend this output after the absolute CLTV timeout of the HTLC as passed.\n - HTLC_ACCEPTED_REMOTE_SUCCESS: A witness that allows us to sweep an HTLC that was offered to us by the\nremote party. We use this witness in the case that the remote party goes to\nchain, and we know the pre-image to the HTLC. We can sweep this without any\nadditional timeout.\n - HTLC_SECOND_LEVEL_REVOKE: A witness that allows us to sweep an HTLC from the remote party's commitment\ntransaction in the case that the broadcast a revoked commitment, but then\nalso immediately attempt to go to the second level to claim the HTLC.\n - WITNESS_KEY_HASH: A witness type that allows us to spend a regular p2wkh output that's sent to\nan output which is under complete control of the backing wallet.\n - NESTED_WITNESS_KEY_HASH: A witness type that allows us to sweep an output that sends to a nested P2SH\nscript that pays to a key solely under our control.\n - COMMITMENT_ANCHOR: A witness type that allows us to spend our anchor on the commitment\ntransaction.\n - COMMITMENT_NO_DELAY_TWEAKLESS: A witness type that is similar to the COMMITMENT_NO_DELAY type,\nbut it omits the tweak that randomizes the key we need to\nspend with a channel peer supplied set of randomness.\n - COMMITMENT_TO_REMOTE_CONFIRMED: A witness type that allows us to spend our output on the counterparty's\ncommitment transaction after a confirmation.\n - HTLC_OFFERED_TIMEOUT_SECOND_LEVEL_INPUT_CONFIRMED: A witness type that allows us to sweep an HTLC output that we extended\nto a party, but was never fulfilled. This _is_ the HTLC output directly\non our commitment transaction, and the input to the second-level HTLC\ntimeout transaction. It can only be spent after CLTV expiry, and\ncommitment confirmation.\n - HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL_INPUT_CONFIRMED: A witness type that allows us to sweep an HTLC output that was offered\nto us, and for which we have a payment preimage. This _is_ the HTLC\noutput directly on our commitment transaction, and the input to the\nsecond-level HTLC success transaction. It can only be spent after the\ncommitment has confirmed.\n - LEASE_COMMITMENT_TIME_LOCK: A witness type that allows us to spend our output on our local\ncommitment transaction after a relative and absolute lock-time lockout as\npart of the script enforced lease commitment type.\n - LEASE_COMMITMENT_TO_REMOTE_CONFIRMED: A witness type that allows us to spend our output on the counterparty's\ncommitment transaction after a confirmation and absolute locktime as part\nof the script enforced lease commitment type.\n - LEASE_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL: A witness type that allows us to sweep an HTLC output that we extended\nto a party, but was never fulfilled. This HTLC output isn't directly on\nthe commitment transaction, but is the result of a confirmed second-level\nHTLC transaction. As a result, we can only spend this after a CSV delay\nand CLTV locktime as part of the script enforced lease commitment type.\n - LEASE_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL: A witness type that allows us to sweep an HTLC output that was offered\nto us, and for which we have a payment preimage. This HTLC output isn't\ndirectly on our commitment transaction, but is the result of confirmed\nsecond-level HTLC transaction. As a result, we can only spend this after\na CSV delay and CLTV locktime as part of the script enforced lease\ncommitment type.\n - TAPROOT_PUB_KEY_SPEND: A witness type that allows us to spend a regular p2tr output that's sent\nto an output which is under complete control of the backing wallet.\n - TAPROOT_LOCAL_COMMIT_SPEND: A witness type that allows us to spend our settled local commitment after a\nCSV delay when we force close the channel.\n - TAPROOT_REMOTE_COMMIT_SPEND: A witness type that allows us to spend our settled local commitment after\na CSV delay when the remote party has force closed the channel.\n - TAPROOT_ANCHOR_SWEEP_SPEND: A witness type that we'll use for spending our own anchor output.\n - TAPROOT_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL: A witness that allows us to timeout an HTLC we offered to the remote party\non our commitment transaction. We use this when we need to go on chain to\ntime out an HTLC.\n - TAPROOT_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL: A witness type that allows us to sweep an HTLC we accepted on our commitment\ntransaction after we go to the second level on chain.\n - TAPROOT_HTLC_SECOND_LEVEL_REVOKE: A witness that allows us to sweep an HTLC on the revoked transaction of the\nremote party that goes to the second level.\n - TAPROOT_HTLC_ACCEPTED_REVOKE: A witness that allows us to sweep an HTLC sent to us by the remote party\nin the event that they broadcast a revoked state.\n - TAPROOT_HTLC_OFFERED_REVOKE: A witness that allows us to sweep an HTLC we offered to the remote party if\nthey broadcast a revoked commitment.\n - TAPROOT_HTLC_OFFERED_REMOTE_TIMEOUT: A witness that allows us to sweep an HTLC we offered to the remote party\nthat lies on the commitment transaction for the remote party. We can spend\nthis output after the absolute CLTV timeout of the HTLC as passed.\n - TAPROOT_HTLC_LOCAL_OFFERED_TIMEOUT: A witness type that allows us to sign the second level HTLC timeout\ntransaction when spending from an HTLC residing on our local commitment\ntransaction.\nThis is used by the sweeper to re-sign inputs if it needs to aggregate\nseveral second level HTLCs.\n - TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS: A witness that allows us to sweep an HTLC that was offered to us by the\nremote party for a taproot channels. We use this witness in the case that\nthe remote party goes to chain, and we know the pre-image to the HTLC. We\ncan sweep this without any additional timeout.\n - TAPROOT_HTLC_ACCEPTED_LOCAL_SUCCESS: A witness type that allows us to sweep the HTLC offered to us on our local\ncommitment transaction. We'll use this when we need to go on chain to sweep\nthe HTLC. In this case, this is the second level HTLC success transaction.\n - TAPROOT_COMMITMENT_REVOKE: A witness that allows us to sweep the settled output of a malicious\ncounterparty's who broadcasts a revoked taproot commitment transaction." } } } diff --git a/lnrpc/walletrpc/walletkit.yaml b/lnrpc/walletrpc/walletkit.yaml index 06c908923..b912fea23 100644 --- a/lnrpc/walletrpc/walletkit.yaml +++ b/lnrpc/walletrpc/walletkit.yaml @@ -34,9 +34,6 @@ http: - selector: walletrpc.WalletKit.PublishTransaction post: "/v2/wallet/tx" body: "*" - - selector: walletrpc.WalletKit.SubmitPackage - post: "/v2/wallet/tx/package" - body: "*" - selector: walletrpc.WalletKit.SendOutputs post: "/v2/wallet/send" body: "*" diff --git a/lnrpc/walletrpc/walletkit_grpc.pb.go b/lnrpc/walletrpc/walletkit_grpc.pb.go index 8cd991cee..579aa47bb 100644 --- a/lnrpc/walletrpc/walletkit_grpc.pb.go +++ b/lnrpc/walletrpc/walletkit_grpc.pb.go @@ -156,18 +156,6 @@ type WalletKitClient interface { // attempt to re-broadcast the transaction on start up, until it enters the // chain. PublishTransaction(ctx context.Context, in *Transaction, opts ...grpc.CallOption) (*PublishResponse, error) - // lncli: `wallet submitpackage` - // SubmitPackage submits a package of related transactions (topologically - // sorted, unconfirmed parents first and the child last) for atomic - // validation and acceptance. Real package submission is only performed by - // the bitcoind backend, via the node's submitpackage RPC, which lets a - // zero-fee v3/TRUC parent be accepted via its fee-paying CPFP child. The - // btcd backend does not support submitpackage and returns an error. A - // neutrino light client has no mempool and cannot atomically accept a - // package; as a best effort it broadcasts the transactions individually and - // relies on a peer's 1p1c package relay, returning an unverified result - // (not a package-accept verdict). - SubmitPackage(ctx context.Context, in *SubmitPackageRequest, opts ...grpc.CallOption) (*SubmitPackageResponse, error) // lncli: `wallet removetx` // RemoveTransaction attempts to remove the provided transaction from the // internal transaction store of the wallet. @@ -266,7 +254,6 @@ type WalletKitClient interface { // publishing the transaction) or to unlock/release the locked UTXOs in case of // an error on the caller's side. FundPsbt(ctx context.Context, in *FundPsbtRequest, opts ...grpc.CallOption) (*FundPsbtResponse, error) - // lncli: `wallet psbt sign` // SignPsbt expects a partial transaction with all inputs and outputs fully // declared and tries to sign all unsigned inputs that have all required fields // (UTXO information, BIP32 derivation information, witness or sig scripts) @@ -455,15 +442,6 @@ func (c *walletKitClient) PublishTransaction(ctx context.Context, in *Transactio return out, nil } -func (c *walletKitClient) SubmitPackage(ctx context.Context, in *SubmitPackageRequest, opts ...grpc.CallOption) (*SubmitPackageResponse, error) { - out := new(SubmitPackageResponse) - err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/SubmitPackage", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *walletKitClient) RemoveTransaction(ctx context.Context, in *GetTransactionRequest, opts ...grpc.CallOption) (*RemoveTransactionResponse, error) { out := new(RemoveTransactionResponse) err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/RemoveTransaction", in, out, opts...) @@ -703,18 +681,6 @@ type WalletKitServer interface { // attempt to re-broadcast the transaction on start up, until it enters the // chain. PublishTransaction(context.Context, *Transaction) (*PublishResponse, error) - // lncli: `wallet submitpackage` - // SubmitPackage submits a package of related transactions (topologically - // sorted, unconfirmed parents first and the child last) for atomic - // validation and acceptance. Real package submission is only performed by - // the bitcoind backend, via the node's submitpackage RPC, which lets a - // zero-fee v3/TRUC parent be accepted via its fee-paying CPFP child. The - // btcd backend does not support submitpackage and returns an error. A - // neutrino light client has no mempool and cannot atomically accept a - // package; as a best effort it broadcasts the transactions individually and - // relies on a peer's 1p1c package relay, returning an unverified result - // (not a package-accept verdict). - SubmitPackage(context.Context, *SubmitPackageRequest) (*SubmitPackageResponse, error) // lncli: `wallet removetx` // RemoveTransaction attempts to remove the provided transaction from the // internal transaction store of the wallet. @@ -813,7 +779,6 @@ type WalletKitServer interface { // publishing the transaction) or to unlock/release the locked UTXOs in case of // an error on the caller's side. FundPsbt(context.Context, *FundPsbtRequest) (*FundPsbtResponse, error) - // lncli: `wallet psbt sign` // SignPsbt expects a partial transaction with all inputs and outputs fully // declared and tries to sign all unsigned inputs that have all required fields // (UTXO information, BIP32 derivation information, witness or sig scripts) @@ -897,9 +862,6 @@ func (UnimplementedWalletKitServer) ImportTapscript(context.Context, *ImportTaps func (UnimplementedWalletKitServer) PublishTransaction(context.Context, *Transaction) (*PublishResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method PublishTransaction not implemented") } -func (UnimplementedWalletKitServer) SubmitPackage(context.Context, *SubmitPackageRequest) (*SubmitPackageResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method SubmitPackage not implemented") -} func (UnimplementedWalletKitServer) RemoveTransaction(context.Context, *GetTransactionRequest) (*RemoveTransactionResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RemoveTransaction not implemented") } @@ -1252,24 +1214,6 @@ func _WalletKit_PublishTransaction_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } -func _WalletKit_SubmitPackage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(SubmitPackageRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(WalletKitServer).SubmitPackage(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/walletrpc.WalletKit/SubmitPackage", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(WalletKitServer).SubmitPackage(ctx, req.(*SubmitPackageRequest)) - } - return interceptor(ctx, in, info, handler) -} - func _WalletKit_RemoveTransaction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetTransactionRequest) if err := dec(in); err != nil { @@ -1543,10 +1487,6 @@ var WalletKit_ServiceDesc = grpc.ServiceDesc{ MethodName: "PublishTransaction", Handler: _WalletKit_PublishTransaction_Handler, }, - { - MethodName: "SubmitPackage", - Handler: _WalletKit_SubmitPackage_Handler, - }, { MethodName: "RemoveTransaction", Handler: _WalletKit_RemoveTransaction_Handler, diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index 8480f62d0..9efc730a4 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -18,22 +18,20 @@ import ( "sort" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/btcutil/v2/hdkeychain" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/psbt/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" base "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/wtxmgr" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" @@ -95,10 +93,6 @@ var ( Entity: "onchain", Action: "write", }}, - "/walletrpc.WalletKit/SubmitPackage": {{ - Entity: "onchain", - Action: "write", - }}, "/walletrpc.WalletKit/SendOutputs": {{ Entity: "onchain", Action: "write", @@ -243,13 +237,6 @@ var ( input.TaprootHtlcAcceptedRemoteSuccess: WitnessType_TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS, input.TaprootHtlcAcceptedLocalSuccess: WitnessType_TAPROOT_HTLC_ACCEPTED_LOCAL_SUCCESS, input.TaprootCommitmentRevoke: WitnessType_TAPROOT_COMMITMENT_REVOKE, - input.TaprootLocalCommitSpendFinal: WitnessType_TAPROOT_LOCAL_COMMIT_SPEND_FINAL, - input.TaprootRemoteCommitSpendFinal: WitnessType_TAPROOT_REMOTE_COMMIT_SPEND_FINAL, - input.TaprootHtlcOfferedTimeoutSecondLevelFinal: WitnessType_TAPROOT_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL_FINAL, - input.TaprootHtlcAcceptedSuccessSecondLevelFinal: WitnessType_TAPROOT_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL_FINAL, - input.TaprootHtlcOfferedRemoteTimeoutFinal: WitnessType_TAPROOT_HTLC_OFFERED_REMOTE_TIMEOUT_FINAL, - input.TaprootHtlcAcceptedRemoteSuccessFinal: WitnessType_TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS_FINAL, - input.TaprootCommitmentRevokeFinal: WitnessType_TAPROOT_COMMITMENT_REVOKE_FINAL, } ) @@ -701,87 +688,6 @@ func (w *WalletKit) PublishTransaction(ctx context.Context, return &PublishResponse{}, nil } -// maxPackageTxns is the maximum number of transactions accepted in a single -// SubmitPackage request. It mirrors bitcoind's MAX_PACKAGE_COUNT (the limit -// its submitpackage RPC enforces), so any package the backend could accept -// fits, while bounding the work an authenticated caller can force from -// deserializing an arbitrarily long raw_txs list. -const maxPackageTxns = 25 - -// SubmitPackage submits a package of related transactions (topologically -// sorted, unconfirmed parents first and the child last) to the wallet's chain -// backend for atomic validation and acceptance. This lets a zero-fee v3/TRUC -// parent confirm via its fee-paying CPFP child without the caller needing a -// separate connection to the chain backend. -func (w *WalletKit) SubmitPackage(_ context.Context, - req *SubmitPackageRequest) (*SubmitPackageResponse, error) { - - if len(req.RawTxs) == 0 { - return nil, fmt.Errorf("must provide at least one transaction") - } - if len(req.RawTxs) > maxPackageTxns { - return nil, fmt.Errorf("package of %d transactions exceeds "+ - "the maximum of %d", len(req.RawTxs), maxPackageTxns) - } - - txns := make([]*wire.MsgTx, 0, len(req.RawTxs)) - for _, raw := range req.RawTxs { - tx := &wire.MsgTx{} - if err := tx.Deserialize(bytes.NewReader(raw)); err != nil { - return nil, fmt.Errorf("unable to decode tx: %w", err) - } - - txns = append(txns, tx) - } - - // Map the optional sat/vByte ceiling onto the backend. An unset value - // uses the node's default; an explicit value (including 0, meaning no - // limit) is passed through unchanged. - var maxFeeRate *chainfee.SatPerVByte - if req.SatPerVbyte != nil { - rate := chainfee.SatPerVByte(*req.SatPerVbyte) - maxFeeRate = &rate - } - - result, err := w.cfg.Wallet.SubmitPackage(txns, maxFeeRate) - if err != nil { - return nil, err - } - - // Some backends (e.g. the no-chain source or mocks) may return a nil - // result; guard against a nil dereference below. - if result == nil { - return nil, fmt.Errorf("nil result from wallet backend") - } - - numResults := len(result.TxResults) - resp := &SubmitPackageResponse{ - PackageMsg: result.PackageMsg, - TxResults: make(map[string]*SubmitPackageTxResult, numResults), - ReplacedTransactions: make( - []string, 0, len(result.ReplacedTransactions), - ), - } - for _, replaced := range result.ReplacedTransactions { - resp.ReplacedTransactions = append( - resp.ReplacedTransactions, replaced.String(), - ) - } - for wtxid, txResult := range result.TxResults { - entry := &SubmitPackageTxResult{Txid: txResult.TxID.String()} - if txResult.Error != nil { - entry.Error = *txResult.Error - } - if txResult.OtherWtxid != nil { - entry.OtherWtxid = txResult.OtherWtxid.String() - } - - resp.TxResults[wtxid] = entry - } - - return resp, nil -} - // RemoveTransaction attempts to remove the transaction and all of its // descendants resulting from further spends of the outputs of the provided // transaction id. @@ -1271,7 +1177,7 @@ func (w *WalletKit) BumpFee(ctx context.Context, // getWaitingCloseChannel returns the waiting close channel in case it does // exist in the underlying channel state database. func (w *WalletKit) getWaitingCloseChannel( - chanPoint wire.OutPoint) (*chanstate.OpenChannel, error) { + chanPoint wire.OutPoint) (*channeldb.OpenChannel, error) { // Fetch all channels, which still have their commitment transaction not // confirmed (waiting close channels). @@ -1280,7 +1186,7 @@ func (w *WalletKit) getWaitingCloseChannel( return nil, err } - channel := fn.Find(chans, func(c *chanstate.OpenChannel) bool { + channel := fn.Find(chans, func(c *channeldb.OpenChannel) bool { return c.FundingOutpoint == chanPoint }) @@ -1823,7 +1729,7 @@ func (w *WalletKit) FundPsbt(_ context.Context, txOut := make([]*wire.TxOut, 0, len(tpl.Outputs)) for addrStr, amt := range tpl.Outputs { - addr, err := address.DecodeAddress( + addr, err := btcutil.DecodeAddress( addrStr, w.cfg.ChainParams, ) if err != nil { @@ -2321,7 +2227,7 @@ func (w *WalletKit) handleChange(packet *psbt.Packet, changeIndex int32, // address, which is required for some protocols (such as Taproot // Assets). pOut := psbt.POutput{} - _, isTaprootChangeAddr := changeAddr.(*address.AddressTaproot) + _, isTaprootChangeAddr := changeAddr.(*btcutil.AddressTaproot) if isTaprootChangeAddr { changeAddrInfo, err := w.cfg.Wallet.AddressInfo(changeAddr) if err != nil { @@ -2360,6 +2266,7 @@ func (w *WalletKit) handleChange(packet *psbt.Packet, changeIndex int32, func marshallLeases(locks []*base.ListLeasedOutputResult) []*UtxoLease { rpcLocks := make([]*UtxoLease, len(locks)) for idx, lock := range locks { + lock := lock rpcLocks[idx] = &UtxoLease{ Id: lock.LockID[:], @@ -2812,7 +2719,7 @@ const msgSignaturePrefix = "Bitcoin Signed Message:\n" func (w *WalletKit) SignMessageWithAddr(_ context.Context, req *SignMessageWithAddrRequest) (*SignMessageWithAddrResponse, error) { - addr, err := address.DecodeAddress(req.Addr, w.cfg.ChainParams) + addr, err := btcutil.DecodeAddress(req.Addr, w.cfg.ChainParams) if err != nil { return nil, fmt.Errorf("unable to decode address: %w", err) } @@ -2899,65 +2806,65 @@ func (w *WalletKit) VerifyMessageWithAddr(_ context.Context, serializedPubkey = pk.SerializeUncompressed() } - decodedAddr, err := address.DecodeAddress(req.Addr, w.cfg.ChainParams) + addr, err := btcutil.DecodeAddress(req.Addr, w.cfg.ChainParams) if err != nil { return nil, fmt.Errorf("unable to decode address: %w", err) } - if !decodedAddr.IsForNet(w.cfg.ChainParams) { + if !addr.IsForNet(w.cfg.ChainParams) { return nil, fmt.Errorf("encoded address is for"+ "the wrong network %s", req.Addr) } var ( - addr address.Address - pubKeyHash = address.Hash160(serializedPubkey) + address btcutil.Address + pubKeyHash = btcutil.Hash160(serializedPubkey) ) // Ensure the address is one of the supported types. - switch decodedAddr.(type) { - case *address.AddressPubKeyHash: - addr, err = address.NewAddressPubKeyHash( + switch addr.(type) { + case *btcutil.AddressPubKeyHash: + address, err = btcutil.NewAddressPubKeyHash( pubKeyHash, w.cfg.ChainParams, ) if err != nil { return nil, err } - case *address.AddressWitnessPubKeyHash: - addr, err = address.NewAddressWitnessPubKeyHash( + case *btcutil.AddressWitnessPubKeyHash: + address, err = btcutil.NewAddressWitnessPubKeyHash( pubKeyHash, w.cfg.ChainParams, ) if err != nil { return nil, err } - case *address.AddressScriptHash: + case *btcutil.AddressScriptHash: // Check if address is a Nested P2WKH (NP2WKH). - addr, err = address.NewAddressWitnessPubKeyHash( + address, err = btcutil.NewAddressWitnessPubKeyHash( pubKeyHash, w.cfg.ChainParams, ) if err != nil { return nil, err } - witnessScript, err := txscript.PayToAddrScript(addr) + witnessScript, err := txscript.PayToAddrScript(address) if err != nil { return nil, err } - addr, err = address.NewAddressScriptHashFromHash( - address.Hash160(witnessScript), w.cfg.ChainParams, + address, err = btcutil.NewAddressScriptHashFromHash( + btcutil.Hash160(witnessScript), w.cfg.ChainParams, ) if err != nil { return nil, err } - case *address.AddressTaproot: + case *btcutil.AddressTaproot: // Only addresses without a tapscript are allowed because // the verification is using the internal key. tapKey := txscript.ComputeTaprootKeyNoScript(pk) - addr, err = address.NewAddressTaproot( + address, err = btcutil.NewAddressTaproot( schnorr.SerializePubKey(tapKey), w.cfg.ChainParams, ) @@ -2970,7 +2877,7 @@ func (w *WalletKit) VerifyMessageWithAddr(_ context.Context, } return &VerifyMessageWithAddrResponse{ - Valid: req.Addr == addr.EncodeAddress(), + Valid: req.Addr == address.EncodeAddress(), Pubkey: serializedPubkey, }, nil } diff --git a/lnrpc/walletrpc/walletkit_server_test.go b/lnrpc/walletrpc/walletkit_server_test.go index 803c4d1ba..40fb62ef2 100644 --- a/lnrpc/walletrpc/walletkit_server_test.go +++ b/lnrpc/walletrpc/walletkit_server_test.go @@ -11,11 +11,11 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/psbt/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/wallet" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntest/mock" @@ -43,6 +43,8 @@ func TestWitnessTypeMapping(t *testing.T) { for witnessType, witnessTypeProto := range allWitnessTypes { // Redeclare to avoid loop variables being captured // by func literal. + witnessType := witnessType + witnessTypeProto := witnessTypeProto t.Run(witnessType.String(), func(tt *testing.T) { tt.Parallel() @@ -627,6 +629,7 @@ func TestFundPsbtCoinSelect(t *testing.T) { }} for _, tc := range testCases { + tc := tc privKey, err := btcec.NewPrivateKey() require.NoError(t, err) diff --git a/lnrpc/walletrpc/walletkit_util.go b/lnrpc/walletrpc/walletkit_util.go index c6e9e998f..6f1c5c9ad 100644 --- a/lnrpc/walletrpc/walletkit_util.go +++ b/lnrpc/walletrpc/walletkit_util.go @@ -6,8 +6,8 @@ import ( "strconv" "strings" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnrpc" ) diff --git a/lnrpc/walletrpc/walletkit_util_test.go b/lnrpc/walletrpc/walletkit_util_test.go index 097c51315..fdc8d2e0a 100644 --- a/lnrpc/walletrpc/walletkit_util_test.go +++ b/lnrpc/walletrpc/walletkit_util_test.go @@ -55,6 +55,7 @@ func TestParseDerivationPath(t *testing.T) { }} for _, tc := range testCases { + tc := tc t.Run(tc.name, func(tt *testing.T) { result, err := parseDerivationPath(tc.path) diff --git a/lnrpc/walletunlocker.pb.go b/lnrpc/walletunlocker.pb.go index 9ac5719ec..33a2b3168 100644 --- a/lnrpc/walletunlocker.pb.go +++ b/lnrpc/walletunlocker.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.33.0 // protoc v3.21.12 // source: walletunlocker.proto @@ -11,7 +11,6 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" - unsafe "unsafe" ) const ( @@ -22,7 +21,10 @@ const ( ) type GenSeedRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // aezeed_passphrase is an optional user provided passphrase that will be used // to encrypt the generated aezeed cipher seed. When using REST, this field // must be encoded as base64. @@ -30,16 +32,16 @@ type GenSeedRequest struct { // seed_entropy is an optional 16-bytes generated via CSPRNG. If not // specified, then a fresh set of randomness will be used to create the seed. // When using REST, this field must be encoded as base64. - SeedEntropy []byte `protobuf:"bytes,2,opt,name=seed_entropy,json=seedEntropy,proto3" json:"seed_entropy,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + SeedEntropy []byte `protobuf:"bytes,2,opt,name=seed_entropy,json=seedEntropy,proto3" json:"seed_entropy,omitempty"` } func (x *GenSeedRequest) Reset() { *x = GenSeedRequest{} - mi := &file_walletunlocker_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletunlocker_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GenSeedRequest) String() string { @@ -50,7 +52,7 @@ func (*GenSeedRequest) ProtoMessage() {} func (x *GenSeedRequest) ProtoReflect() protoreflect.Message { mi := &file_walletunlocker_proto_msgTypes[0] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -80,7 +82,10 @@ func (x *GenSeedRequest) GetSeedEntropy() []byte { } type GenSeedResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed // cipher seed obtained by the user. This field is optional, as if not // provided, then the daemon will generate a new cipher seed for the user. @@ -90,15 +95,15 @@ type GenSeedResponse struct { // enciphered_seed are the raw aezeed cipher seed bytes. This is the raw // cipher text before run through our mnemonic encoding scheme. EncipheredSeed []byte `protobuf:"bytes,2,opt,name=enciphered_seed,json=encipheredSeed,proto3" json:"enciphered_seed,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *GenSeedResponse) Reset() { *x = GenSeedResponse{} - mi := &file_walletunlocker_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletunlocker_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GenSeedResponse) String() string { @@ -109,7 +114,7 @@ func (*GenSeedResponse) ProtoMessage() {} func (x *GenSeedResponse) ProtoReflect() protoreflect.Message { mi := &file_walletunlocker_proto_msgTypes[1] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -139,7 +144,10 @@ func (x *GenSeedResponse) GetEncipheredSeed() []byte { } type InitWalletRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // wallet_password is the passphrase that should be used to encrypt the // wallet. This MUST be at least 8 chars in length. After creation, this // password is required to unlock the daemon. When using REST, this field @@ -202,15 +210,15 @@ type InitWalletRequest struct { // provided when initializing the wallet rather than letting lnd generate one // on its own. MacaroonRootKey []byte `protobuf:"bytes,10,opt,name=macaroon_root_key,json=macaroonRootKey,proto3" json:"macaroon_root_key,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *InitWalletRequest) Reset() { *x = InitWalletRequest{} - mi := &file_walletunlocker_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletunlocker_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *InitWalletRequest) String() string { @@ -221,7 +229,7 @@ func (*InitWalletRequest) ProtoMessage() {} func (x *InitWalletRequest) ProtoReflect() protoreflect.Message { mi := &file_walletunlocker_proto_msgTypes[2] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -307,22 +315,25 @@ func (x *InitWalletRequest) GetMacaroonRootKey() []byte { } type InitWalletResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The binary serialized admin macaroon that can be used to access the daemon // after creating the wallet. If the stateless_init parameter was set to true, // this is the ONLY copy of the macaroon and MUST be stored safely by the // caller. Otherwise a copy of this macaroon is also persisted on disk by the // daemon, together with other macaroon files. AdminMacaroon []byte `protobuf:"bytes,1,opt,name=admin_macaroon,json=adminMacaroon,proto3" json:"admin_macaroon,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *InitWalletResponse) Reset() { *x = InitWalletResponse{} - mi := &file_walletunlocker_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletunlocker_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *InitWalletResponse) String() string { @@ -333,7 +344,7 @@ func (*InitWalletResponse) ProtoMessage() {} func (x *InitWalletResponse) ProtoReflect() protoreflect.Message { mi := &file_walletunlocker_proto_msgTypes[3] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -356,7 +367,10 @@ func (x *InitWalletResponse) GetAdminMacaroon() []byte { } type WatchOnly struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The unix timestamp in seconds of when the master key was created. lnd will // only start scanning for funds in blocks that are after the birthday which // can speed up the process significantly. If the birthday is not known, this @@ -373,16 +387,16 @@ type WatchOnly struct { // coin type is always 0, even for testnet/regtest) and lnd's internal key // scope (m/1017'/'/'), where account is the key family as // defined in `keychain/derivation.go` (currently indices 0 to 9). - Accounts []*WatchOnlyAccount `protobuf:"bytes,3,rep,name=accounts,proto3" json:"accounts,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Accounts []*WatchOnlyAccount `protobuf:"bytes,3,rep,name=accounts,proto3" json:"accounts,omitempty"` } func (x *WatchOnly) Reset() { *x = WatchOnly{} - mi := &file_walletunlocker_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletunlocker_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *WatchOnly) String() string { @@ -393,7 +407,7 @@ func (*WatchOnly) ProtoMessage() {} func (x *WatchOnly) ProtoReflect() protoreflect.Message { mi := &file_walletunlocker_proto_msgTypes[4] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -430,7 +444,10 @@ func (x *WatchOnly) GetAccounts() []*WatchOnlyAccount { } type WatchOnlyAccount struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Purpose is the first number in the derivation path, must be either 49, 84 // or 1017. Purpose uint32 `protobuf:"varint,1,opt,name=purpose,proto3" json:"purpose,omitempty"` @@ -445,16 +462,16 @@ type WatchOnlyAccount struct { // (currently indices 0 to 9) Account uint32 `protobuf:"varint,3,opt,name=account,proto3" json:"account,omitempty"` // The extended public key at depth 3 for the given account. - Xpub string `protobuf:"bytes,4,opt,name=xpub,proto3" json:"xpub,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Xpub string `protobuf:"bytes,4,opt,name=xpub,proto3" json:"xpub,omitempty"` } func (x *WatchOnlyAccount) Reset() { *x = WatchOnlyAccount{} - mi := &file_walletunlocker_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletunlocker_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *WatchOnlyAccount) String() string { @@ -465,7 +482,7 @@ func (*WatchOnlyAccount) ProtoMessage() {} func (x *WatchOnlyAccount) ProtoReflect() protoreflect.Message { mi := &file_walletunlocker_proto_msgTypes[5] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -509,7 +526,10 @@ func (x *WatchOnlyAccount) GetXpub() string { } type UnlockWalletRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // wallet_password should be the current valid passphrase for the daemon. This // will be required to decrypt on-disk material that the daemon requires to // function properly. When using REST, this field must be encoded as base64. @@ -530,15 +550,15 @@ type UnlockWalletRequest struct { // stateless_init is an optional argument instructing the daemon NOT to create // any *.macaroon files in its file system. StatelessInit bool `protobuf:"varint,4,opt,name=stateless_init,json=statelessInit,proto3" json:"stateless_init,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *UnlockWalletRequest) Reset() { *x = UnlockWalletRequest{} - mi := &file_walletunlocker_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletunlocker_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *UnlockWalletRequest) String() string { @@ -549,7 +569,7 @@ func (*UnlockWalletRequest) ProtoMessage() {} func (x *UnlockWalletRequest) ProtoReflect() protoreflect.Message { mi := &file_walletunlocker_proto_msgTypes[6] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -593,16 +613,18 @@ func (x *UnlockWalletRequest) GetStatelessInit() bool { } type UnlockWalletResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *UnlockWalletResponse) Reset() { *x = UnlockWalletResponse{} - mi := &file_walletunlocker_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletunlocker_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *UnlockWalletResponse) String() string { @@ -613,7 +635,7 @@ func (*UnlockWalletResponse) ProtoMessage() {} func (x *UnlockWalletResponse) ProtoReflect() protoreflect.Message { mi := &file_walletunlocker_proto_msgTypes[7] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -629,7 +651,10 @@ func (*UnlockWalletResponse) Descriptor() ([]byte, []int) { } type ChangePasswordRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // current_password should be the current valid passphrase used to unlock the // daemon. When using REST, this field must be encoded as base64. CurrentPassword []byte `protobuf:"bytes,1,opt,name=current_password,json=currentPassword,proto3" json:"current_password,omitempty"` @@ -645,15 +670,15 @@ type ChangePasswordRequest struct { // rotate the macaroon root key when set to true. This will invalidate all // previously generated macaroons. NewMacaroonRootKey bool `protobuf:"varint,4,opt,name=new_macaroon_root_key,json=newMacaroonRootKey,proto3" json:"new_macaroon_root_key,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ChangePasswordRequest) Reset() { *x = ChangePasswordRequest{} - mi := &file_walletunlocker_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletunlocker_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChangePasswordRequest) String() string { @@ -664,7 +689,7 @@ func (*ChangePasswordRequest) ProtoMessage() {} func (x *ChangePasswordRequest) ProtoReflect() protoreflect.Message { mi := &file_walletunlocker_proto_msgTypes[8] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -708,7 +733,10 @@ func (x *ChangePasswordRequest) GetNewMacaroonRootKey() bool { } type ChangePasswordResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The binary serialized admin macaroon that can be used to access the daemon // after rotating the macaroon root key. If both the stateless_init and // new_macaroon_root_key parameter were set to true, this is the ONLY copy of @@ -716,15 +744,15 @@ type ChangePasswordResponse struct { // safely by the caller. Otherwise a copy of this macaroon is also persisted on // disk by the daemon, together with other macaroon files. AdminMacaroon []byte `protobuf:"bytes,1,opt,name=admin_macaroon,json=adminMacaroon,proto3" json:"admin_macaroon,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ChangePasswordResponse) Reset() { *x = ChangePasswordResponse{} - mi := &file_walletunlocker_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_walletunlocker_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ChangePasswordResponse) String() string { @@ -735,7 +763,7 @@ func (*ChangePasswordResponse) ProtoMessage() {} func (x *ChangePasswordResponse) ProtoReflect() protoreflect.Message { mi := &file_walletunlocker_proto_msgTypes[9] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -759,73 +787,148 @@ func (x *ChangePasswordResponse) GetAdminMacaroon() []byte { var File_walletunlocker_proto protoreflect.FileDescriptor -const file_walletunlocker_proto_rawDesc = "" + - "\n" + - "\x14walletunlocker.proto\x12\x05lnrpc\x1a\x0flightning.proto\"`\n" + - "\x0eGenSeedRequest\x12+\n" + - "\x11aezeed_passphrase\x18\x01 \x01(\fR\x10aezeedPassphrase\x12!\n" + - "\fseed_entropy\x18\x02 \x01(\fR\vseedEntropy\"l\n" + - "\x0fGenSeedResponse\x120\n" + - "\x14cipher_seed_mnemonic\x18\x01 \x03(\tR\x12cipherSeedMnemonic\x12'\n" + - "\x0fenciphered_seed\x18\x02 \x01(\fR\x0eencipheredSeed\"\x90\x04\n" + - "\x11InitWalletRequest\x12'\n" + - "\x0fwallet_password\x18\x01 \x01(\fR\x0ewalletPassword\x120\n" + - "\x14cipher_seed_mnemonic\x18\x02 \x03(\tR\x12cipherSeedMnemonic\x12+\n" + - "\x11aezeed_passphrase\x18\x03 \x01(\fR\x10aezeedPassphrase\x12'\n" + - "\x0frecovery_window\x18\x04 \x01(\x05R\x0erecoveryWindow\x12B\n" + - "\x0fchannel_backups\x18\x05 \x01(\v2\x19.lnrpc.ChanBackupSnapshotR\x0echannelBackups\x12%\n" + - "\x0estateless_init\x18\x06 \x01(\bR\rstatelessInit\x12.\n" + - "\x13extended_master_key\x18\a \x01(\tR\x11extendedMasterKey\x12R\n" + - "&extended_master_key_birthday_timestamp\x18\b \x01(\x04R\"extendedMasterKeyBirthdayTimestamp\x12/\n" + - "\n" + - "watch_only\x18\t \x01(\v2\x10.lnrpc.WatchOnlyR\twatchOnly\x12*\n" + - "\x11macaroon_root_key\x18\n" + - " \x01(\fR\x0fmacaroonRootKey\";\n" + - "\x12InitWalletResponse\x12%\n" + - "\x0eadmin_macaroon\x18\x01 \x01(\fR\radminMacaroon\"\xb9\x01\n" + - "\tWatchOnly\x12A\n" + - "\x1dmaster_key_birthday_timestamp\x18\x01 \x01(\x04R\x1amasterKeyBirthdayTimestamp\x124\n" + - "\x16master_key_fingerprint\x18\x02 \x01(\fR\x14masterKeyFingerprint\x123\n" + - "\baccounts\x18\x03 \x03(\v2\x17.lnrpc.WatchOnlyAccountR\baccounts\"w\n" + - "\x10WatchOnlyAccount\x12\x18\n" + - "\apurpose\x18\x01 \x01(\rR\apurpose\x12\x1b\n" + - "\tcoin_type\x18\x02 \x01(\rR\bcoinType\x12\x18\n" + - "\aaccount\x18\x03 \x01(\rR\aaccount\x12\x12\n" + - "\x04xpub\x18\x04 \x01(\tR\x04xpub\"\xd2\x01\n" + - "\x13UnlockWalletRequest\x12'\n" + - "\x0fwallet_password\x18\x01 \x01(\fR\x0ewalletPassword\x12'\n" + - "\x0frecovery_window\x18\x02 \x01(\x05R\x0erecoveryWindow\x12B\n" + - "\x0fchannel_backups\x18\x03 \x01(\v2\x19.lnrpc.ChanBackupSnapshotR\x0echannelBackups\x12%\n" + - "\x0estateless_init\x18\x04 \x01(\bR\rstatelessInit\"\x16\n" + - "\x14UnlockWalletResponse\"\xbf\x01\n" + - "\x15ChangePasswordRequest\x12)\n" + - "\x10current_password\x18\x01 \x01(\fR\x0fcurrentPassword\x12!\n" + - "\fnew_password\x18\x02 \x01(\fR\vnewPassword\x12%\n" + - "\x0estateless_init\x18\x03 \x01(\bR\rstatelessInit\x121\n" + - "\x15new_macaroon_root_key\x18\x04 \x01(\bR\x12newMacaroonRootKey\"?\n" + - "\x16ChangePasswordResponse\x12%\n" + - "\x0eadmin_macaroon\x18\x01 \x01(\fR\radminMacaroon2\xa5\x02\n" + - "\x0eWalletUnlocker\x128\n" + - "\aGenSeed\x12\x15.lnrpc.GenSeedRequest\x1a\x16.lnrpc.GenSeedResponse\x12A\n" + - "\n" + - "InitWallet\x12\x18.lnrpc.InitWalletRequest\x1a\x19.lnrpc.InitWalletResponse\x12G\n" + - "\fUnlockWallet\x12\x1a.lnrpc.UnlockWalletRequest\x1a\x1b.lnrpc.UnlockWalletResponse\x12M\n" + - "\x0eChangePassword\x12\x1c.lnrpc.ChangePasswordRequest\x1a\x1d.lnrpc.ChangePasswordResponseB'Z%github.com/lightningnetwork/lnd/lnrpcb\x06proto3" +var file_walletunlocker_proto_rawDesc = []byte{ + 0x0a, 0x14, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x72, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x1a, 0x0f, 0x6c, + 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x60, + 0x0a, 0x0e, 0x47, 0x65, 0x6e, 0x53, 0x65, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x2b, 0x0a, 0x11, 0x61, 0x65, 0x7a, 0x65, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x70, + 0x68, 0x72, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x61, 0x65, 0x7a, + 0x65, 0x65, 0x64, 0x50, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x12, 0x21, 0x0a, + 0x0c, 0x73, 0x65, 0x65, 0x64, 0x5f, 0x65, 0x6e, 0x74, 0x72, 0x6f, 0x70, 0x79, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x73, 0x65, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x6f, 0x70, 0x79, + 0x22, 0x6c, 0x0a, 0x0f, 0x47, 0x65, 0x6e, 0x53, 0x65, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x5f, 0x73, 0x65, + 0x65, 0x64, 0x5f, 0x6d, 0x6e, 0x65, 0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x12, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x65, 0x65, 0x64, 0x4d, 0x6e, 0x65, + 0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x12, 0x27, 0x0a, 0x0f, 0x65, 0x6e, 0x63, 0x69, 0x70, 0x68, 0x65, + 0x72, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, + 0x65, 0x6e, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x65, 0x64, 0x53, 0x65, 0x65, 0x64, 0x22, 0x90, + 0x04, 0x0a, 0x11, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x70, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x30, 0x0a, + 0x14, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x65, 0x64, 0x5f, 0x6d, 0x6e, 0x65, + 0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x63, 0x69, 0x70, + 0x68, 0x65, 0x72, 0x53, 0x65, 0x65, 0x64, 0x4d, 0x6e, 0x65, 0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x12, + 0x2b, 0x0a, 0x11, 0x61, 0x65, 0x7a, 0x65, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, + 0x72, 0x61, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x61, 0x65, 0x7a, 0x65, + 0x65, 0x64, 0x50, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, + 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x57, + 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x12, 0x42, 0x0a, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, + 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x69, 0x74, + 0x12, 0x2e, 0x0a, 0x13, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x6d, 0x61, 0x73, + 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, + 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, + 0x12, 0x52, 0x0a, 0x26, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x6d, 0x61, 0x73, + 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x79, + 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x22, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, + 0x4b, 0x65, 0x79, 0x42, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x12, 0x2f, 0x0a, 0x0a, 0x77, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x6f, 0x6e, + 0x6c, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x52, 0x09, 0x77, 0x61, 0x74, 0x63, + 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, + 0x6e, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x6f, 0x6f, 0x74, 0x4b, 0x65, + 0x79, 0x22, 0x3b, 0x0a, 0x12, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x64, 0x6d, 0x69, 0x6e, + 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x22, 0xb9, + 0x01, 0x0a, 0x09, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x41, 0x0a, 0x1d, + 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x69, 0x72, 0x74, 0x68, + 0x64, 0x61, 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x1a, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x42, 0x69, + 0x72, 0x74, 0x68, 0x64, 0x61, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, + 0x34, 0x0a, 0x16, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, + 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x14, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x57, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x77, 0x0a, 0x10, 0x57, 0x61, + 0x74, 0x63, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x18, + 0x0a, 0x07, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x07, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x69, 0x6e, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x63, 0x6f, 0x69, + 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x12, 0x0a, 0x04, 0x78, 0x70, 0x75, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x78, + 0x70, 0x75, 0x62, 0x22, 0xd2, 0x01, 0x0a, 0x13, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x57, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, + 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, + 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x12, 0x42, 0x0a, + 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, + 0x74, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, + 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x5f, 0x69, + 0x6e, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x6c, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x69, 0x74, 0x22, 0x16, 0x0a, 0x14, 0x55, 0x6e, 0x6c, 0x6f, + 0x63, 0x6b, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0xbf, 0x01, 0x0a, 0x15, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, + 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x65, 0x77, 0x5f, 0x70, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6e, 0x65, 0x77, + 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, + 0x65, 0x6c, 0x65, 0x73, 0x73, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x69, 0x74, 0x12, + 0x31, 0x0a, 0x15, 0x6e, 0x65, 0x77, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x5f, + 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, + 0x6e, 0x65, 0x77, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x6f, 0x6f, 0x74, 0x4b, + 0x65, 0x79, 0x22, 0x3f, 0x0a, 0x16, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, + 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4d, 0x61, 0x63, 0x61, 0x72, + 0x6f, 0x6f, 0x6e, 0x32, 0xa5, 0x02, 0x0a, 0x0e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x55, 0x6e, + 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x12, 0x38, 0x0a, 0x07, 0x47, 0x65, 0x6e, 0x53, 0x65, 0x65, + 0x64, 0x12, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x53, 0x65, 0x65, + 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x47, 0x65, 0x6e, 0x53, 0x65, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x41, 0x0a, 0x0a, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x12, 0x18, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x57, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x6e, 0x6c, 0x6f, + 0x63, 0x6b, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x57, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e, + 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1c, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, + 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, + 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} var ( file_walletunlocker_proto_rawDescOnce sync.Once - file_walletunlocker_proto_rawDescData []byte + file_walletunlocker_proto_rawDescData = file_walletunlocker_proto_rawDesc ) func file_walletunlocker_proto_rawDescGZIP() []byte { file_walletunlocker_proto_rawDescOnce.Do(func() { - file_walletunlocker_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_walletunlocker_proto_rawDesc), len(file_walletunlocker_proto_rawDesc))) + file_walletunlocker_proto_rawDescData = protoimpl.X.CompressGZIP(file_walletunlocker_proto_rawDescData) }) return file_walletunlocker_proto_rawDescData } var file_walletunlocker_proto_msgTypes = make([]protoimpl.MessageInfo, 10) -var file_walletunlocker_proto_goTypes = []any{ +var file_walletunlocker_proto_goTypes = []interface{}{ (*GenSeedRequest)(nil), // 0: lnrpc.GenSeedRequest (*GenSeedResponse)(nil), // 1: lnrpc.GenSeedResponse (*InitWalletRequest)(nil), // 2: lnrpc.InitWalletRequest @@ -864,11 +967,133 @@ func file_walletunlocker_proto_init() { return } file_lightning_proto_init() + if !protoimpl.UnsafeEnabled { + file_walletunlocker_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GenSeedRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletunlocker_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GenSeedResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletunlocker_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InitWalletRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletunlocker_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InitWalletResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletunlocker_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WatchOnly); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletunlocker_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WatchOnlyAccount); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletunlocker_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UnlockWalletRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletunlocker_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UnlockWalletResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletunlocker_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChangePasswordRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletunlocker_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChangePasswordResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_walletunlocker_proto_rawDesc), len(file_walletunlocker_proto_rawDesc)), + RawDescriptor: file_walletunlocker_proto_rawDesc, NumEnums: 0, NumMessages: 10, NumExtensions: 0, @@ -879,6 +1104,7 @@ func file_walletunlocker_proto_init() { MessageInfos: file_walletunlocker_proto_msgTypes, }.Build() File_walletunlocker_proto = out.File + file_walletunlocker_proto_rawDesc = nil file_walletunlocker_proto_goTypes = nil file_walletunlocker_proto_depIdxs = nil } diff --git a/lnrpc/watchtowerrpc/watchtower.pb.go b/lnrpc/watchtowerrpc/watchtower.pb.go index 447f93a36..763f5fad2 100644 --- a/lnrpc/watchtowerrpc/watchtower.pb.go +++ b/lnrpc/watchtowerrpc/watchtower.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.33.0 // protoc v3.21.12 // source: watchtowerrpc/watchtower.proto @@ -11,7 +11,6 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" - unsafe "unsafe" ) const ( @@ -22,16 +21,18 @@ const ( ) type GetInfoRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *GetInfoRequest) Reset() { *x = GetInfoRequest{} - mi := &file_watchtowerrpc_watchtower_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_watchtowerrpc_watchtower_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetInfoRequest) String() string { @@ -42,7 +43,7 @@ func (*GetInfoRequest) ProtoMessage() {} func (x *GetInfoRequest) ProtoReflect() protoreflect.Message { mi := &file_watchtowerrpc_watchtower_proto_msgTypes[0] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -58,22 +59,25 @@ func (*GetInfoRequest) Descriptor() ([]byte, []int) { } type GetInfoResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The public key of the watchtower. Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` // The listening addresses of the watchtower. Listeners []string `protobuf:"bytes,2,rep,name=listeners,proto3" json:"listeners,omitempty"` // The URIs of the watchtower. - Uris []string `protobuf:"bytes,3,rep,name=uris,proto3" json:"uris,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Uris []string `protobuf:"bytes,3,rep,name=uris,proto3" json:"uris,omitempty"` } func (x *GetInfoResponse) Reset() { *x = GetInfoResponse{} - mi := &file_watchtowerrpc_watchtower_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_watchtowerrpc_watchtower_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetInfoResponse) String() string { @@ -84,7 +88,7 @@ func (*GetInfoResponse) ProtoMessage() {} func (x *GetInfoResponse) ProtoReflect() protoreflect.Message { mi := &file_watchtowerrpc_watchtower_proto_msgTypes[1] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -122,32 +126,43 @@ func (x *GetInfoResponse) GetUris() []string { var File_watchtowerrpc_watchtower_proto protoreflect.FileDescriptor -const file_watchtowerrpc_watchtower_proto_rawDesc = "" + - "\n" + - "\x1ewatchtowerrpc/watchtower.proto\x12\rwatchtowerrpc\"\x10\n" + - "\x0eGetInfoRequest\"[\n" + - "\x0fGetInfoResponse\x12\x16\n" + - "\x06pubkey\x18\x01 \x01(\fR\x06pubkey\x12\x1c\n" + - "\tlisteners\x18\x02 \x03(\tR\tlisteners\x12\x12\n" + - "\x04uris\x18\x03 \x03(\tR\x04uris2V\n" + - "\n" + - "Watchtower\x12H\n" + - "\aGetInfo\x12\x1d.watchtowerrpc.GetInfoRequest\x1a\x1e.watchtowerrpc.GetInfoResponseB5Z3github.com/lightningnetwork/lnd/lnrpc/watchtowerrpcb\x06proto3" +var file_watchtowerrpc_watchtower_proto_rawDesc = []byte{ + 0x0a, 0x1e, 0x77, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2f, + 0x77, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x0d, 0x77, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x72, 0x70, 0x63, 0x22, + 0x10, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x22, 0x5b, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, + 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x09, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x72, + 0x69, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x75, 0x72, 0x69, 0x73, 0x32, 0x56, + 0x0a, 0x0a, 0x57, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x48, 0x0a, 0x07, + 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x74, 0x63, 0x68, 0x74, + 0x6f, 0x77, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, + 0x77, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, + 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, + 0x77, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} var ( file_watchtowerrpc_watchtower_proto_rawDescOnce sync.Once - file_watchtowerrpc_watchtower_proto_rawDescData []byte + file_watchtowerrpc_watchtower_proto_rawDescData = file_watchtowerrpc_watchtower_proto_rawDesc ) func file_watchtowerrpc_watchtower_proto_rawDescGZIP() []byte { file_watchtowerrpc_watchtower_proto_rawDescOnce.Do(func() { - file_watchtowerrpc_watchtower_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_watchtowerrpc_watchtower_proto_rawDesc), len(file_watchtowerrpc_watchtower_proto_rawDesc))) + file_watchtowerrpc_watchtower_proto_rawDescData = protoimpl.X.CompressGZIP(file_watchtowerrpc_watchtower_proto_rawDescData) }) return file_watchtowerrpc_watchtower_proto_rawDescData } var file_watchtowerrpc_watchtower_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_watchtowerrpc_watchtower_proto_goTypes = []any{ +var file_watchtowerrpc_watchtower_proto_goTypes = []interface{}{ (*GetInfoRequest)(nil), // 0: watchtowerrpc.GetInfoRequest (*GetInfoResponse)(nil), // 1: watchtowerrpc.GetInfoResponse } @@ -166,11 +181,37 @@ func file_watchtowerrpc_watchtower_proto_init() { if File_watchtowerrpc_watchtower_proto != nil { return } + if !protoimpl.UnsafeEnabled { + file_watchtowerrpc_watchtower_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetInfoRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_watchtowerrpc_watchtower_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetInfoResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_watchtowerrpc_watchtower_proto_rawDesc), len(file_watchtowerrpc_watchtower_proto_rawDesc)), + RawDescriptor: file_watchtowerrpc_watchtower_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 0, @@ -181,6 +222,7 @@ func file_watchtowerrpc_watchtower_proto_init() { MessageInfos: file_watchtowerrpc_watchtower_proto_msgTypes, }.Build() File_watchtowerrpc_watchtower_proto = out.File + file_watchtowerrpc_watchtower_proto_rawDesc = nil file_watchtowerrpc_watchtower_proto_goTypes = nil file_watchtowerrpc_watchtower_proto_depIdxs = nil } diff --git a/lnrpc/wtclientrpc/wtclient.go b/lnrpc/wtclientrpc/wtclient.go index 551c223c5..5ddb99d37 100644 --- a/lnrpc/wtclientrpc/wtclient.go +++ b/lnrpc/wtclientrpc/wtclient.go @@ -585,9 +585,7 @@ func marshallTower(tower *wtclient.RegisteredTower, policyType PolicyType, func blobTypeToPolicyType(t blob.Type) (PolicyType, error) { switch t { - case blob.TypeAltruistTaprootCommit, - blob.TypeAltruistTaprootFinalCommit: - + case blob.TypeAltruistTaprootCommit: return PolicyType_TAPROOT, nil case blob.TypeAltruistAnchorCommit: diff --git a/lnrpc/wtclientrpc/wtclient.pb.go b/lnrpc/wtclientrpc/wtclient.pb.go index 0ef6462b5..452248e8a 100644 --- a/lnrpc/wtclientrpc/wtclient.pb.go +++ b/lnrpc/wtclientrpc/wtclient.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.33.0 // protoc v3.21.12 // source: wtclientrpc/wtclient.proto @@ -11,7 +11,6 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" - unsafe "unsafe" ) const ( @@ -74,20 +73,23 @@ func (PolicyType) EnumDescriptor() ([]byte, []int) { } type AddTowerRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The identifying public key of the watchtower to add. Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` // A network address the watchtower is reachable over. - Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` } func (x *AddTowerRequest) Reset() { *x = AddTowerRequest{} - mi := &file_wtclientrpc_wtclient_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_wtclientrpc_wtclient_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *AddTowerRequest) String() string { @@ -98,7 +100,7 @@ func (*AddTowerRequest) ProtoMessage() {} func (x *AddTowerRequest) ProtoReflect() protoreflect.Message { mi := &file_wtclientrpc_wtclient_proto_msgTypes[0] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -128,16 +130,18 @@ func (x *AddTowerRequest) GetAddress() string { } type AddTowerResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *AddTowerResponse) Reset() { *x = AddTowerResponse{} - mi := &file_wtclientrpc_wtclient_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_wtclientrpc_wtclient_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *AddTowerResponse) String() string { @@ -148,7 +152,7 @@ func (*AddTowerResponse) ProtoMessage() {} func (x *AddTowerResponse) ProtoReflect() protoreflect.Message { mi := &file_wtclientrpc_wtclient_proto_msgTypes[1] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -164,22 +168,25 @@ func (*AddTowerResponse) Descriptor() ([]byte, []int) { } type RemoveTowerRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The identifying public key of the watchtower to remove. Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` // If set, then the record for this address will be removed, indicating that is // is stale. Otherwise, the watchtower will no longer be used for future // session negotiations and backups. - Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` } func (x *RemoveTowerRequest) Reset() { *x = RemoveTowerRequest{} - mi := &file_wtclientrpc_wtclient_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_wtclientrpc_wtclient_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *RemoveTowerRequest) String() string { @@ -190,7 +197,7 @@ func (*RemoveTowerRequest) ProtoMessage() {} func (x *RemoveTowerRequest) ProtoReflect() protoreflect.Message { mi := &file_wtclientrpc_wtclient_proto_msgTypes[2] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -220,16 +227,18 @@ func (x *RemoveTowerRequest) GetAddress() string { } type RemoveTowerResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *RemoveTowerResponse) Reset() { *x = RemoveTowerResponse{} - mi := &file_wtclientrpc_wtclient_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_wtclientrpc_wtclient_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *RemoveTowerResponse) String() string { @@ -240,7 +249,7 @@ func (*RemoveTowerResponse) ProtoMessage() {} func (x *RemoveTowerResponse) ProtoReflect() protoreflect.Message { mi := &file_wtclientrpc_wtclient_proto_msgTypes[3] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -256,18 +265,21 @@ func (*RemoveTowerResponse) Descriptor() ([]byte, []int) { } type DeactivateTowerRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The identifying public key of the watchtower to deactivate. - Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The identifying public key of the watchtower to deactivate. + Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` } func (x *DeactivateTowerRequest) Reset() { *x = DeactivateTowerRequest{} - mi := &file_wtclientrpc_wtclient_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_wtclientrpc_wtclient_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *DeactivateTowerRequest) String() string { @@ -278,7 +290,7 @@ func (*DeactivateTowerRequest) ProtoMessage() {} func (x *DeactivateTowerRequest) ProtoReflect() protoreflect.Message { mi := &file_wtclientrpc_wtclient_proto_msgTypes[4] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -301,18 +313,21 @@ func (x *DeactivateTowerRequest) GetPubkey() []byte { } type DeactivateTowerResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // A string describing the action that took place. - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // A string describing the action that took place. + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` } func (x *DeactivateTowerResponse) Reset() { *x = DeactivateTowerResponse{} - mi := &file_wtclientrpc_wtclient_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_wtclientrpc_wtclient_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *DeactivateTowerResponse) String() string { @@ -323,7 +338,7 @@ func (*DeactivateTowerResponse) ProtoMessage() {} func (x *DeactivateTowerResponse) ProtoReflect() protoreflect.Message { mi := &file_wtclientrpc_wtclient_proto_msgTypes[5] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -346,18 +361,21 @@ func (x *DeactivateTowerResponse) GetStatus() string { } type TerminateSessionRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The ID of the session that should be terminated. - SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The ID of the session that should be terminated. + SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` } func (x *TerminateSessionRequest) Reset() { *x = TerminateSessionRequest{} - mi := &file_wtclientrpc_wtclient_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_wtclientrpc_wtclient_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *TerminateSessionRequest) String() string { @@ -368,7 +386,7 @@ func (*TerminateSessionRequest) ProtoMessage() {} func (x *TerminateSessionRequest) ProtoReflect() protoreflect.Message { mi := &file_wtclientrpc_wtclient_proto_msgTypes[6] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -391,18 +409,21 @@ func (x *TerminateSessionRequest) GetSessionId() []byte { } type TerminateSessionResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // A string describing the action that took place. - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // A string describing the action that took place. + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` } func (x *TerminateSessionResponse) Reset() { *x = TerminateSessionResponse{} - mi := &file_wtclientrpc_wtclient_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_wtclientrpc_wtclient_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *TerminateSessionResponse) String() string { @@ -413,7 +434,7 @@ func (*TerminateSessionResponse) ProtoMessage() {} func (x *TerminateSessionResponse) ProtoReflect() protoreflect.Message { mi := &file_wtclientrpc_wtclient_proto_msgTypes[7] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -436,7 +457,10 @@ func (x *TerminateSessionResponse) GetStatus() string { } type GetTowerInfoRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The identifying public key of the watchtower to retrieve information for. Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` // Whether we should include sessions with the watchtower in the response. @@ -444,15 +468,15 @@ type GetTowerInfoRequest struct { // Whether to exclude exhausted sessions in the response info. This option // is only meaningful if include_sessions is true. ExcludeExhaustedSessions bool `protobuf:"varint,3,opt,name=exclude_exhausted_sessions,json=excludeExhaustedSessions,proto3" json:"exclude_exhausted_sessions,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *GetTowerInfoRequest) Reset() { *x = GetTowerInfoRequest{} - mi := &file_wtclientrpc_wtclient_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_wtclientrpc_wtclient_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *GetTowerInfoRequest) String() string { @@ -463,7 +487,7 @@ func (*GetTowerInfoRequest) ProtoMessage() {} func (x *GetTowerInfoRequest) ProtoReflect() protoreflect.Message { mi := &file_wtclientrpc_wtclient_proto_msgTypes[8] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -500,7 +524,10 @@ func (x *GetTowerInfoRequest) GetExcludeExhaustedSessions() bool { } type TowerSession struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The total number of successful backups that have been made to the // watchtower session. NumBackups uint32 `protobuf:"varint,1,opt,name=num_backups,json=numBackups,proto3" json:"num_backups,omitempty"` @@ -519,16 +546,16 @@ type TowerSession struct { // the justice transaction in the event of a channel breach. SweepSatPerVbyte uint32 `protobuf:"varint,5,opt,name=sweep_sat_per_vbyte,json=sweepSatPerVbyte,proto3" json:"sweep_sat_per_vbyte,omitempty"` // The ID of the session. - Id []byte `protobuf:"bytes,6,opt,name=id,proto3" json:"id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Id []byte `protobuf:"bytes,6,opt,name=id,proto3" json:"id,omitempty"` } func (x *TowerSession) Reset() { *x = TowerSession{} - mi := &file_wtclientrpc_wtclient_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_wtclientrpc_wtclient_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *TowerSession) String() string { @@ -539,7 +566,7 @@ func (*TowerSession) ProtoMessage() {} func (x *TowerSession) ProtoReflect() protoreflect.Message { mi := &file_wtclientrpc_wtclient_proto_msgTypes[9] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -598,7 +625,10 @@ func (x *TowerSession) GetId() []byte { } type Tower struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The identifying public key of the watchtower. Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` // The list of addresses the watchtower is reachable over. @@ -622,16 +652,16 @@ type Tower struct { // Deprecated: Marked as deprecated in wtclientrpc/wtclient.proto. Sessions []*TowerSession `protobuf:"bytes,5,rep,name=sessions,proto3" json:"sessions,omitempty"` // A list sessions held with the tower. - SessionInfo []*TowerSessionInfo `protobuf:"bytes,6,rep,name=session_info,json=sessionInfo,proto3" json:"session_info,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + SessionInfo []*TowerSessionInfo `protobuf:"bytes,6,rep,name=session_info,json=sessionInfo,proto3" json:"session_info,omitempty"` } func (x *Tower) Reset() { *x = Tower{} - mi := &file_wtclientrpc_wtclient_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_wtclientrpc_wtclient_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *Tower) String() string { @@ -642,7 +672,7 @@ func (*Tower) ProtoMessage() {} func (x *Tower) ProtoReflect() protoreflect.Message { mi := &file_wtclientrpc_wtclient_proto_msgTypes[10] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -703,7 +733,10 @@ func (x *Tower) GetSessionInfo() []*TowerSessionInfo { } type TowerSessionInfo struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Whether the watchtower is currently a candidate for new sessions. ActiveSessionCandidate bool `protobuf:"varint,1,opt,name=active_session_candidate,json=activeSessionCandidate,proto3" json:"active_session_candidate,omitempty"` // The number of sessions that have been negotiated with the watchtower. @@ -711,16 +744,16 @@ type TowerSessionInfo struct { // The list of sessions that have been negotiated with the watchtower. Sessions []*TowerSession `protobuf:"bytes,3,rep,name=sessions,proto3" json:"sessions,omitempty"` // The session's policy type. - PolicyType PolicyType `protobuf:"varint,4,opt,name=policy_type,json=policyType,proto3,enum=wtclientrpc.PolicyType" json:"policy_type,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + PolicyType PolicyType `protobuf:"varint,4,opt,name=policy_type,json=policyType,proto3,enum=wtclientrpc.PolicyType" json:"policy_type,omitempty"` } func (x *TowerSessionInfo) Reset() { *x = TowerSessionInfo{} - mi := &file_wtclientrpc_wtclient_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_wtclientrpc_wtclient_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *TowerSessionInfo) String() string { @@ -731,7 +764,7 @@ func (*TowerSessionInfo) ProtoMessage() {} func (x *TowerSessionInfo) ProtoReflect() protoreflect.Message { mi := &file_wtclientrpc_wtclient_proto_msgTypes[11] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -775,21 +808,24 @@ func (x *TowerSessionInfo) GetPolicyType() PolicyType { } type ListTowersRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Whether we should include sessions with the watchtower in the response. IncludeSessions bool `protobuf:"varint,1,opt,name=include_sessions,json=includeSessions,proto3" json:"include_sessions,omitempty"` // Whether to exclude exhausted sessions in the response info. This option // is only meaningful if include_sessions is true. ExcludeExhaustedSessions bool `protobuf:"varint,2,opt,name=exclude_exhausted_sessions,json=excludeExhaustedSessions,proto3" json:"exclude_exhausted_sessions,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *ListTowersRequest) Reset() { *x = ListTowersRequest{} - mi := &file_wtclientrpc_wtclient_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_wtclientrpc_wtclient_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListTowersRequest) String() string { @@ -800,7 +836,7 @@ func (*ListTowersRequest) ProtoMessage() {} func (x *ListTowersRequest) ProtoReflect() protoreflect.Message { mi := &file_wtclientrpc_wtclient_proto_msgTypes[12] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -830,18 +866,21 @@ func (x *ListTowersRequest) GetExcludeExhaustedSessions() bool { } type ListTowersResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The list of watchtowers available for new backups. - Towers []*Tower `protobuf:"bytes,1,rep,name=towers,proto3" json:"towers,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The list of watchtowers available for new backups. + Towers []*Tower `protobuf:"bytes,1,rep,name=towers,proto3" json:"towers,omitempty"` } func (x *ListTowersResponse) Reset() { *x = ListTowersResponse{} - mi := &file_wtclientrpc_wtclient_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_wtclientrpc_wtclient_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *ListTowersResponse) String() string { @@ -852,7 +891,7 @@ func (*ListTowersResponse) ProtoMessage() {} func (x *ListTowersResponse) ProtoReflect() protoreflect.Message { mi := &file_wtclientrpc_wtclient_proto_msgTypes[13] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -875,16 +914,18 @@ func (x *ListTowersResponse) GetTowers() []*Tower { } type StatsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } func (x *StatsRequest) Reset() { *x = StatsRequest{} - mi := &file_wtclientrpc_wtclient_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_wtclientrpc_wtclient_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *StatsRequest) String() string { @@ -895,7 +936,7 @@ func (*StatsRequest) ProtoMessage() {} func (x *StatsRequest) ProtoReflect() protoreflect.Message { mi := &file_wtclientrpc_wtclient_proto_msgTypes[14] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -911,7 +952,10 @@ func (*StatsRequest) Descriptor() ([]byte, []int) { } type StatsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The total number of backups made to all active and exhausted watchtower // sessions. NumBackups uint32 `protobuf:"varint,1,opt,name=num_backups,json=numBackups,proto3" json:"num_backups,omitempty"` @@ -925,15 +969,15 @@ type StatsResponse struct { NumSessionsAcquired uint32 `protobuf:"varint,4,opt,name=num_sessions_acquired,json=numSessionsAcquired,proto3" json:"num_sessions_acquired,omitempty"` // The total number of watchtower sessions that have been exhausted. NumSessionsExhausted uint32 `protobuf:"varint,5,opt,name=num_sessions_exhausted,json=numSessionsExhausted,proto3" json:"num_sessions_exhausted,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *StatsResponse) Reset() { *x = StatsResponse{} - mi := &file_wtclientrpc_wtclient_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_wtclientrpc_wtclient_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *StatsResponse) String() string { @@ -944,7 +988,7 @@ func (*StatsResponse) ProtoMessage() {} func (x *StatsResponse) ProtoReflect() protoreflect.Message { mi := &file_wtclientrpc_wtclient_proto_msgTypes[15] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -995,18 +1039,21 @@ func (x *StatsResponse) GetNumSessionsExhausted() uint32 { } type PolicyRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The client type from which to retrieve the active offering policy. - PolicyType PolicyType `protobuf:"varint,1,opt,name=policy_type,json=policyType,proto3,enum=wtclientrpc.PolicyType" json:"policy_type,omitempty"` - unknownFields protoimpl.UnknownFields + state protoimpl.MessageState sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The client type from which to retrieve the active offering policy. + PolicyType PolicyType `protobuf:"varint,1,opt,name=policy_type,json=policyType,proto3,enum=wtclientrpc.PolicyType" json:"policy_type,omitempty"` } func (x *PolicyRequest) Reset() { *x = PolicyRequest{} - mi := &file_wtclientrpc_wtclient_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_wtclientrpc_wtclient_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PolicyRequest) String() string { @@ -1017,7 +1064,7 @@ func (*PolicyRequest) ProtoMessage() {} func (x *PolicyRequest) ProtoReflect() protoreflect.Message { mi := &file_wtclientrpc_wtclient_proto_msgTypes[16] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1040,7 +1087,10 @@ func (x *PolicyRequest) GetPolicyType() PolicyType { } type PolicyResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The maximum number of updates each session we negotiate with watchtowers // should allow. MaxUpdates uint32 `protobuf:"varint,1,opt,name=max_updates,json=maxUpdates,proto3" json:"max_updates,omitempty"` @@ -1053,15 +1103,15 @@ type PolicyResponse struct { // The fee rate, in satoshis per vbyte, that will be used by watchtowers for // justice transactions in response to channel breaches. SweepSatPerVbyte uint32 `protobuf:"varint,3,opt,name=sweep_sat_per_vbyte,json=sweepSatPerVbyte,proto3" json:"sweep_sat_per_vbyte,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache } func (x *PolicyResponse) Reset() { *x = PolicyResponse{} - mi := &file_wtclientrpc_wtclient_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + if protoimpl.UnsafeEnabled { + mi := &file_wtclientrpc_wtclient_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } func (x *PolicyResponse) String() string { @@ -1072,7 +1122,7 @@ func (*PolicyResponse) ProtoMessage() {} func (x *PolicyResponse) ProtoReflect() protoreflect.Message { mi := &file_wtclientrpc_wtclient_proto_msgTypes[17] - if x != nil { + if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1111,106 +1161,199 @@ func (x *PolicyResponse) GetSweepSatPerVbyte() uint32 { var File_wtclientrpc_wtclient_proto protoreflect.FileDescriptor -const file_wtclientrpc_wtclient_proto_rawDesc = "" + - "\n" + - "\x1awtclientrpc/wtclient.proto\x12\vwtclientrpc\"C\n" + - "\x0fAddTowerRequest\x12\x16\n" + - "\x06pubkey\x18\x01 \x01(\fR\x06pubkey\x12\x18\n" + - "\aaddress\x18\x02 \x01(\tR\aaddress\"\x12\n" + - "\x10AddTowerResponse\"F\n" + - "\x12RemoveTowerRequest\x12\x16\n" + - "\x06pubkey\x18\x01 \x01(\fR\x06pubkey\x12\x18\n" + - "\aaddress\x18\x02 \x01(\tR\aaddress\"\x15\n" + - "\x13RemoveTowerResponse\"0\n" + - "\x16DeactivateTowerRequest\x12\x16\n" + - "\x06pubkey\x18\x01 \x01(\fR\x06pubkey\"1\n" + - "\x17DeactivateTowerResponse\x12\x16\n" + - "\x06status\x18\x01 \x01(\tR\x06status\"8\n" + - "\x17TerminateSessionRequest\x12\x1d\n" + - "\n" + - "session_id\x18\x01 \x01(\fR\tsessionId\"2\n" + - "\x18TerminateSessionResponse\x12\x16\n" + - "\x06status\x18\x01 \x01(\tR\x06status\"\x96\x01\n" + - "\x13GetTowerInfoRequest\x12\x16\n" + - "\x06pubkey\x18\x01 \x01(\fR\x06pubkey\x12)\n" + - "\x10include_sessions\x18\x02 \x01(\bR\x0fincludeSessions\x12<\n" + - "\x1aexclude_exhausted_sessions\x18\x03 \x01(\bR\x18excludeExhaustedSessions\"\xf0\x01\n" + - "\fTowerSession\x12\x1f\n" + - "\vnum_backups\x18\x01 \x01(\rR\n" + - "numBackups\x12.\n" + - "\x13num_pending_backups\x18\x02 \x01(\rR\x11numPendingBackups\x12\x1f\n" + - "\vmax_backups\x18\x03 \x01(\rR\n" + - "maxBackups\x12/\n" + - "\x12sweep_sat_per_byte\x18\x04 \x01(\rB\x02\x18\x01R\x0fsweepSatPerByte\x12-\n" + - "\x13sweep_sat_per_vbyte\x18\x05 \x01(\rR\x10sweepSatPerVbyte\x12\x0e\n" + - "\x02id\x18\x06 \x01(\fR\x02id\"\x9f\x02\n" + - "\x05Tower\x12\x16\n" + - "\x06pubkey\x18\x01 \x01(\fR\x06pubkey\x12\x1c\n" + - "\taddresses\x18\x02 \x03(\tR\taddresses\x12<\n" + - "\x18active_session_candidate\x18\x03 \x01(\bB\x02\x18\x01R\x16activeSessionCandidate\x12%\n" + - "\fnum_sessions\x18\x04 \x01(\rB\x02\x18\x01R\vnumSessions\x129\n" + - "\bsessions\x18\x05 \x03(\v2\x19.wtclientrpc.TowerSessionB\x02\x18\x01R\bsessions\x12@\n" + - "\fsession_info\x18\x06 \x03(\v2\x1d.wtclientrpc.TowerSessionInfoR\vsessionInfo\"\xe0\x01\n" + - "\x10TowerSessionInfo\x128\n" + - "\x18active_session_candidate\x18\x01 \x01(\bR\x16activeSessionCandidate\x12!\n" + - "\fnum_sessions\x18\x02 \x01(\rR\vnumSessions\x125\n" + - "\bsessions\x18\x03 \x03(\v2\x19.wtclientrpc.TowerSessionR\bsessions\x128\n" + - "\vpolicy_type\x18\x04 \x01(\x0e2\x17.wtclientrpc.PolicyTypeR\n" + - "policyType\"|\n" + - "\x11ListTowersRequest\x12)\n" + - "\x10include_sessions\x18\x01 \x01(\bR\x0fincludeSessions\x12<\n" + - "\x1aexclude_exhausted_sessions\x18\x02 \x01(\bR\x18excludeExhaustedSessions\"@\n" + - "\x12ListTowersResponse\x12*\n" + - "\x06towers\x18\x01 \x03(\v2\x12.wtclientrpc.TowerR\x06towers\"\x0e\n" + - "\fStatsRequest\"\xf8\x01\n" + - "\rStatsResponse\x12\x1f\n" + - "\vnum_backups\x18\x01 \x01(\rR\n" + - "numBackups\x12.\n" + - "\x13num_pending_backups\x18\x02 \x01(\rR\x11numPendingBackups\x12,\n" + - "\x12num_failed_backups\x18\x03 \x01(\rR\x10numFailedBackups\x122\n" + - "\x15num_sessions_acquired\x18\x04 \x01(\rR\x13numSessionsAcquired\x124\n" + - "\x16num_sessions_exhausted\x18\x05 \x01(\rR\x14numSessionsExhausted\"I\n" + - "\rPolicyRequest\x128\n" + - "\vpolicy_type\x18\x01 \x01(\x0e2\x17.wtclientrpc.PolicyTypeR\n" + - "policyType\"\x91\x01\n" + - "\x0ePolicyResponse\x12\x1f\n" + - "\vmax_updates\x18\x01 \x01(\rR\n" + - "maxUpdates\x12/\n" + - "\x12sweep_sat_per_byte\x18\x02 \x01(\rB\x02\x18\x01R\x0fsweepSatPerByte\x12-\n" + - "\x13sweep_sat_per_vbyte\x18\x03 \x01(\rR\x10sweepSatPerVbyte*1\n" + - "\n" + - "PolicyType\x12\n" + - "\n" + - "\x06LEGACY\x10\x00\x12\n" + - "\n" + - "\x06ANCHOR\x10\x01\x12\v\n" + - "\aTAPROOT\x10\x022\x84\x05\n" + - "\x10WatchtowerClient\x12G\n" + - "\bAddTower\x12\x1c.wtclientrpc.AddTowerRequest\x1a\x1d.wtclientrpc.AddTowerResponse\x12P\n" + - "\vRemoveTower\x12\x1f.wtclientrpc.RemoveTowerRequest\x1a .wtclientrpc.RemoveTowerResponse\x12\\\n" + - "\x0fDeactivateTower\x12#.wtclientrpc.DeactivateTowerRequest\x1a$.wtclientrpc.DeactivateTowerResponse\x12_\n" + - "\x10TerminateSession\x12$.wtclientrpc.TerminateSessionRequest\x1a%.wtclientrpc.TerminateSessionResponse\x12M\n" + - "\n" + - "ListTowers\x12\x1e.wtclientrpc.ListTowersRequest\x1a\x1f.wtclientrpc.ListTowersResponse\x12D\n" + - "\fGetTowerInfo\x12 .wtclientrpc.GetTowerInfoRequest\x1a\x12.wtclientrpc.Tower\x12>\n" + - "\x05Stats\x12\x19.wtclientrpc.StatsRequest\x1a\x1a.wtclientrpc.StatsResponse\x12A\n" + - "\x06Policy\x12\x1a.wtclientrpc.PolicyRequest\x1a\x1b.wtclientrpc.PolicyResponseB3Z1github.com/lightningnetwork/lnd/lnrpc/wtclientrpcb\x06proto3" +var file_wtclientrpc_wtclient_proto_rawDesc = []byte{ + 0x0a, 0x1a, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x77, 0x74, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x77, 0x74, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x22, 0x43, 0x0a, 0x0f, 0x41, 0x64, 0x64, + 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, + 0x62, 0x6b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x12, + 0x0a, 0x10, 0x41, 0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x46, 0x0a, 0x12, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, + 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, + 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x15, 0x0a, 0x13, 0x52, 0x65, + 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x30, 0x0a, 0x16, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x54, + 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, + 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, + 0x6b, 0x65, 0x79, 0x22, 0x31, 0x0a, 0x17, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, + 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x38, 0x0a, 0x17, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, + 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x22, 0x32, 0x0a, 0x18, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x77, 0x65, + 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, + 0x62, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, + 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, + 0x3c, 0x0a, 0x1a, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x65, 0x78, 0x68, 0x61, 0x75, + 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x18, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x45, 0x78, 0x68, 0x61, + 0x75, 0x73, 0x74, 0x65, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xf0, 0x01, + 0x0a, 0x0c, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, + 0x0a, 0x0b, 0x6e, 0x75, 0x6d, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6e, 0x75, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, + 0x2e, 0x0a, 0x13, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x62, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6e, 0x75, + 0x6d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, + 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x78, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, + 0x12, 0x2f, 0x0a, 0x12, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, + 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, + 0x52, 0x0f, 0x73, 0x77, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, + 0x65, 0x12, 0x2d, 0x0a, 0x13, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, + 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, + 0x73, 0x77, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, + 0x22, 0x9f, 0x02, 0x0a, 0x05, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, + 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, + 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, + 0x12, 0x3c, 0x0a, 0x18, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x5f, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x16, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x53, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x12, 0x25, + 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x39, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, + 0x12, 0x40, 0x0a, 0x0c, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f, + 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0b, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x6e, + 0x66, 0x6f, 0x22, 0xe0, 0x01, 0x0a, 0x10, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x38, 0x0a, 0x18, 0x61, 0x63, 0x74, 0x69, 0x76, + 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, + 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x61, 0x63, 0x74, 0x69, 0x76, + 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, + 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x35, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x52, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x38, 0x0a, 0x0b, 0x70, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x17, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x70, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x54, 0x79, 0x70, 0x65, 0x22, 0x7c, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, + 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, + 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x53, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3c, 0x0a, 0x1a, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, + 0x5f, 0x65, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x65, 0x78, 0x63, 0x6c, 0x75, + 0x64, 0x65, 0x45, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x22, 0x40, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x74, 0x6f, 0x77, + 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x74, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x06, 0x74, + 0x6f, 0x77, 0x65, 0x72, 0x73, 0x22, 0x0e, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xf8, 0x01, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x75, 0x6d, 0x5f, 0x62, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6e, 0x75, + 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x75, 0x6d, 0x5f, + 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6e, 0x75, 0x6d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x6e, 0x75, 0x6d, 0x5f, + 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x6e, 0x75, 0x6d, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x42, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x61, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x16, 0x6e, 0x75, + 0x6d, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x65, 0x78, 0x68, 0x61, 0x75, + 0x73, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x6e, 0x75, 0x6d, 0x53, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64, + 0x22, 0x49, 0x0a, 0x0d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x38, 0x0a, 0x0b, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, + 0x0a, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x22, 0x91, 0x01, 0x0a, 0x0e, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, + 0x0a, 0x0b, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, + 0x2f, 0x0a, 0x12, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, + 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, + 0x0f, 0x73, 0x77, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, + 0x12, 0x2d, 0x0a, 0x13, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, + 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x73, + 0x77, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x2a, + 0x31, 0x0a, 0x0a, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, + 0x06, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x43, + 0x48, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, + 0x10, 0x02, 0x32, 0x84, 0x05, 0x0a, 0x10, 0x57, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, + 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x08, 0x41, 0x64, 0x64, 0x54, 0x6f, + 0x77, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x41, 0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1d, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x41, 0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x50, 0x0a, 0x0b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, + 0x1f, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, + 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x20, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, + 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x0f, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, + 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x23, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x54, 0x6f, + 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x77, 0x74, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, + 0x61, 0x74, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x5f, 0x0a, 0x10, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x77, 0x74, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, + 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x4d, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x12, + 0x1e, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1f, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x44, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, + 0x12, 0x20, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, + 0x65, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, + 0x19, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, + 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x77, 0x74, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x12, 0x1a, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x77, + 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, + 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2f, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} var ( file_wtclientrpc_wtclient_proto_rawDescOnce sync.Once - file_wtclientrpc_wtclient_proto_rawDescData []byte + file_wtclientrpc_wtclient_proto_rawDescData = file_wtclientrpc_wtclient_proto_rawDesc ) func file_wtclientrpc_wtclient_proto_rawDescGZIP() []byte { file_wtclientrpc_wtclient_proto_rawDescOnce.Do(func() { - file_wtclientrpc_wtclient_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_wtclientrpc_wtclient_proto_rawDesc), len(file_wtclientrpc_wtclient_proto_rawDesc))) + file_wtclientrpc_wtclient_proto_rawDescData = protoimpl.X.CompressGZIP(file_wtclientrpc_wtclient_proto_rawDescData) }) return file_wtclientrpc_wtclient_proto_rawDescData } var file_wtclientrpc_wtclient_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_wtclientrpc_wtclient_proto_msgTypes = make([]protoimpl.MessageInfo, 18) -var file_wtclientrpc_wtclient_proto_goTypes = []any{ +var file_wtclientrpc_wtclient_proto_goTypes = []interface{}{ (PolicyType)(0), // 0: wtclientrpc.PolicyType (*AddTowerRequest)(nil), // 1: wtclientrpc.AddTowerRequest (*AddTowerResponse)(nil), // 2: wtclientrpc.AddTowerResponse @@ -1266,11 +1409,229 @@ func file_wtclientrpc_wtclient_proto_init() { if File_wtclientrpc_wtclient_proto != nil { return } + if !protoimpl.UnsafeEnabled { + file_wtclientrpc_wtclient_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddTowerRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_wtclientrpc_wtclient_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddTowerResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_wtclientrpc_wtclient_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RemoveTowerRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_wtclientrpc_wtclient_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RemoveTowerResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_wtclientrpc_wtclient_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeactivateTowerRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_wtclientrpc_wtclient_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeactivateTowerResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_wtclientrpc_wtclient_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TerminateSessionRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_wtclientrpc_wtclient_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TerminateSessionResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_wtclientrpc_wtclient_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetTowerInfoRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_wtclientrpc_wtclient_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TowerSession); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_wtclientrpc_wtclient_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Tower); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_wtclientrpc_wtclient_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TowerSessionInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_wtclientrpc_wtclient_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListTowersRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_wtclientrpc_wtclient_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListTowersResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_wtclientrpc_wtclient_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_wtclientrpc_wtclient_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_wtclientrpc_wtclient_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PolicyRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_wtclientrpc_wtclient_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PolicyResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_wtclientrpc_wtclient_proto_rawDesc), len(file_wtclientrpc_wtclient_proto_rawDesc)), + RawDescriptor: file_wtclientrpc_wtclient_proto_rawDesc, NumEnums: 1, NumMessages: 18, NumExtensions: 0, @@ -1282,6 +1643,7 @@ func file_wtclientrpc_wtclient_proto_init() { MessageInfos: file_wtclientrpc_wtclient_proto_msgTypes, }.Build() File_wtclientrpc_wtclient_proto = out.File + file_wtclientrpc_wtclient_proto_rawDesc = nil file_wtclientrpc_wtclient_proto_goTypes = nil file_wtclientrpc_wtclient_proto_depIdxs = nil } diff --git a/lntest/bitcoind.go b/lntest/bitcoind.go index eeb19e3ea..646716482 100644 --- a/lntest/bitcoind.go +++ b/lntest/bitcoind.go @@ -4,7 +4,7 @@ package lntest import ( - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" ) // NewBackend starts a bitcoind node with the txindex enabled and returns a diff --git a/lntest/bitcoind_common.go b/lntest/bitcoind_common.go index b1244b562..6d5cacc43 100644 --- a/lntest/bitcoind_common.go +++ b/lntest/bitcoind_common.go @@ -4,20 +4,17 @@ package lntest import ( - "encoding/json" "errors" "fmt" "os" "os/exec" "path/filepath" - "strings" "time" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/rpcclient" "github.com/lightningnetwork/lnd/lntest/node" "github.com/lightningnetwork/lnd/lntest/port" - "github.com/lightningnetwork/lnd/lntest/wait" ) // logDirPattern is the pattern of the name of the temporary log directory. @@ -70,43 +67,12 @@ func (b BitcoindBackendConfig) GenArgs() []string { // ConnectMiner is called to establish a connection to the test miner. func (b BitcoindBackendConfig) ConnectMiner() error { - err := b.rpcClient.AddNode(b.minerAddr, rpcclient.ANOneTry) - if err != nil { - return err - } - - return wait.NoError(func() error { - peerInfo, err := b.rpcClient.GetPeerInfo() - if err != nil { - return err - } - - for _, peer := range peerInfo { - if strings.HasPrefix(peer.Addr, b.minerAddr) { - return nil - } - } - - return fmt.Errorf("peer %s not connected", b.minerAddr) - }, wait.DefaultTimeout) + return b.rpcClient.AddNode(b.minerAddr, rpcclient.ANAdd) } // DisconnectMiner is called to disconnect the miner. func (b BitcoindBackendConfig) DisconnectMiner() error { - // `addnode remove` removes from the addnode list, but doesn't reliably - // disconnect an existing connection. Use `disconnectnode` first. - _, err := b.rpcClient.RawRequest( - "disconnectnode", - []json.RawMessage{ - []byte(fmt.Sprintf("%q", b.minerAddr)), - }, - ) - if err != nil { - return err - } - - _ = b.rpcClient.AddNode(b.minerAddr, rpcclient.ANRemove) - return nil + return b.rpcClient.AddNode(b.minerAddr, rpcclient.ANRemove) } // Credentials returns the rpc username, password and host for the backend. @@ -158,8 +124,7 @@ func newBackend(miner string, netParams *chaincfg.Params, extraArgs []string, cmdArgs := []string{ "-datadir=" + tempBitcoindDir, - // Whitelist localhost to speed up relay. - "-whitelist=127.0.0.1", + "-whitelist=127.0.0.1", // whitelist localhost to speed up relay "-rpcauth=weks:469e9bb14ab2360f8e226efed5ca6f" + "d$507c670e800a95284294edb5773b05544b" + "220110063096c221be9933c82d38e1", @@ -173,22 +138,6 @@ func newBackend(miner string, netParams *chaincfg.Params, extraArgs []string, "-debuglogfile=" + logFile, "-blockfilterindex", "-peerblockfilters", - // Disable v2 transport since the miner is btcd, which - // doesn't support v2 yet. Without this, bitcoind - // attempts a v2 handshake that hangs for 30s before - // falling back to v1, causing test flakes whenever a - // test reconnects to the miner under a timeout. - // - // TODO: Remove once btcd supports v2 P2P transport. - "-v2transport=0", - // Pin the pre-v30 mempool policy defaults (1 sat/vB) - // so the itest suite keeps exercising the fee math it - // was written against. v30 lowered minrelaytxfee and - // incrementalrelayfee to 100 sat/kvB, which breaks - // integer sat/vByte assertions and alters RBF bump - // thresholds across the sweeper/bumpfee tests. - "-minrelaytxfee=0.00001", - "-incrementalrelayfee=0.00001", } cmdArgs = append(cmdArgs, extraArgs...) bitcoind := exec.Command("bitcoind", cmdArgs...) diff --git a/lntest/bitcoind_notxindex.go b/lntest/bitcoind_notxindex.go index 094249f3e..611b89f5e 100644 --- a/lntest/bitcoind_notxindex.go +++ b/lntest/bitcoind_notxindex.go @@ -4,7 +4,7 @@ package lntest import ( - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" ) // NewBackend starts a bitcoind node without the txindex enabled and returns a diff --git a/lntest/bitcoind_rpcpolling.go b/lntest/bitcoind_rpcpolling.go index be48aee31..1280e6f2f 100644 --- a/lntest/bitcoind_rpcpolling.go +++ b/lntest/bitcoind_rpcpolling.go @@ -4,7 +4,7 @@ package lntest import ( - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" ) // NewBackend starts a bitcoind node without the txindex enabled and returns a diff --git a/lntest/btcd.go b/lntest/btcd.go index 736c692d1..21c343243 100644 --- a/lntest/btcd.go +++ b/lntest/btcd.go @@ -11,7 +11,7 @@ import ( "strings" "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcd/rpcclient" "github.com/lightningnetwork/lnd/lntest/miner" diff --git a/lntest/channels/channels.go b/lntest/channels/channels.go index 9312cc15f..fbe3487e9 100644 --- a/lntest/channels/channels.go +++ b/lntest/channels/channels.go @@ -1,8 +1,8 @@ package channels import ( - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" ) var ( diff --git a/lntest/harness.go b/lntest/harness.go index 7a7dc4923..21c32a531 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -1,7 +1,6 @@ package lntest import ( - "bytes" "context" "fmt" "runtime/debug" @@ -9,13 +8,12 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/kvdb/etcd" @@ -56,18 +54,15 @@ const ( // mining blocks. maxBlocksAllowed = 100 - // finalCltvDelta is the min CLTV delta used by the router. - finalCltvDelta = routing.MinCLTVDelta - - // thawHeightDelta defines how far in the future we pick thaw heights. - thawHeightDelta = finalCltvDelta * 2 + finalCltvDelta = routing.MinCLTVDelta // 18. + thawHeightDelta = finalCltvDelta * 2 // 36. ) var ( // MaxBlocksMinedPerTest is the maximum number of blocks that we allow // a test to mine. This is an exported global variable so it can be // overwritten by other projects that don't have the same constraints. - MaxBlocksMinedPerTest = 70 + MaxBlocksMinedPerTest = 50 ) // TestCase defines a test case that's been used in the integration test. @@ -103,8 +98,7 @@ type HarnessTest struct { // runCtx is a context with cancel method. It's used to signal when the // node needs to quit, and used as the parent context when spawning // children contexts for RPC requests. - //nolint:containedctx - runCtx context.Context + runCtx context.Context //nolint:containedctx cancel context.CancelFunc // stopChainBackend points to the cleanup function returned by the @@ -415,13 +409,13 @@ func (h *HarnessTest) checkAndLimitBlocksMined(startHeight int32) { desc += "1. break test into smaller individual tests, especially if " + "this is a table-drive test.\n" + "2. use smaller CSV via `--bitcoin.defaultremotedelay=1.`\n" + - "3. use smaller CLTV via `--bitcoin.timelockdelta=24.`\n" + + "3. use smaller CLTV via `--bitcoin.timelockdelta=18.`\n" + "4. remove unnecessary CloseChannel when test ends.\n" + "5. use `CreateSimpleNetwork` for efficient channel creation.\n" h.Log(desc) // We enforce that the test should not mine more than - // MaxBlocksMinedPerTest (70 by default) blocks, which is more than + // MaxBlocksMinedPerTest (50 by default) blocks, which is more than // enough to test a multi hop force close scenario. require.LessOrEqualf( h, int(blocksMined), MaxBlocksMinedPerTest, @@ -512,7 +506,8 @@ func (h *HarnessTest) NewNode(name string, require.NoError(h, err, "failed to start node %s", node.Name()) // Get the miner's best block hash. - bestBlock, _ := h.miner.GetBestBlock() + bestBlock, err := h.miner.Client.GetBestBlockHash() + require.NoError(h, err, "unable to get best block hash") // Wait until the node's chain backend is synced to the miner's best // block. @@ -1338,8 +1333,7 @@ func (h *HarnessTest) CloseChannelAssertPending(hn *node.HarnessNode, return nil, nil } - //nolint:ll - pendingClose, ok := event.Update.(*lnrpc.CloseStatusUpdate_ClosePending) + pendingClose, ok := event.Update.(*lnrpc.CloseStatusUpdate_ClosePending) //nolint:ll require.Truef(h, ok, "expected channel close "+ "update, instead got %v", pendingClose) @@ -1439,24 +1433,6 @@ func (h *HarnessTest) IsNeutrinoBackend() bool { return h.manager.chainBackend.Name() == NeutrinoBackendName } -// IsPostgresBackend returns true if the test harness is configured to use a -// Postgres database backend. -func (h *HarnessTest) IsPostgresBackend() bool { - return h.manager.dbBackend == node.BackendPostgres -} - -// UsesClosedChanTombstones reports whether the test harness's database -// backend closes channels via tombstone markers rather than cascading the -// nested-bucket delete. This is true on the KV-over-SQL backends (sqlite, -// postgres) and false on bbolt. Tests that observe forwarding-package or -// revocation-log deletion immediately after a channel close should consult -// this predicate; on tombstone backends the bulk state remains on disk -// until the upcoming native-SQL channel-state migration reclaims it. -func (h *HarnessTest) UsesClosedChanTombstones() bool { - return h.manager.dbBackend == node.BackendSqlite || - h.manager.dbBackend == node.BackendPostgres -} - // fundCoins attempts to send amt satoshis from the internal mining node to the // targeted lightning node. The confirmed boolean indicates whether the // transaction that pays to the target should confirm. For neutrino backend, @@ -1643,9 +1619,8 @@ func (h *HarnessTest) CompletePaymentRequests(hn *node.HarnessNode, } // CompletePaymentRequestsNoWait sends payments from a node to complete all -// payment requests without waiting for the results. Instead, it waits for -// all HTLCs to be locked in on the sender's channel by checking the number -// of pending HTLCs. +// payment requests without waiting for the results. Instead, it checks the +// number of updates in the specified channel has increased. func (h *HarnessTest) CompletePaymentRequestsNoWait(hn *node.HarnessNode, paymentRequests []string, chanPoint *lnrpc.ChannelPoint) { @@ -1654,50 +1629,31 @@ func (h *HarnessTest) CompletePaymentRequestsNoWait(hn *node.HarnessNode, // we return. oldResp := h.GetChannelByChanPoint(hn, chanPoint) - // countOutgoing counts the number of outgoing HTLCs in the given list. - countOutgoing := func(htlcs []*lnrpc.HTLC) int { - count := 0 - for _, htlc := range htlcs { - if !htlc.Incoming { - count++ - } - } - - return count - } - - // Count existing outgoing HTLCs before sending. - oldOutgoingCount := countOutgoing(oldResp.PendingHtlcs) - - numPayments := len(paymentRequests) - // Send payments and assert they are in-flight. h.completePaymentRequestsAssertStatus( hn, paymentRequests, lnrpc.Payment_IN_FLIGHT, ) - // Wait for all HTLCs to be locked in. We check that the number of - // outgoing pending HTLCs has increased by exactly the number of - // payments sent. This ensures all HTLCs are committed on the sender's - // side. + // We are not waiting for feedback in the form of a response, but we + // should still wait long enough for the server to receive and handle + // the send before cancelling the request. We wait for the number of + // updates to one of our channels has increased before we return. err := wait.NoError(func() error { newResp := h.GetChannelByChanPoint(hn, chanPoint) - // Count current outgoing HTLCs. - newOutgoingCount := countOutgoing(newResp.PendingHtlcs) - - htlcsAdded := newOutgoingCount - oldOutgoingCount - - // Verify all HTLCs are locked in. - if htlcsAdded == numPayments { + // If this channel has an increased number of updates, we + // assume the payments are committed, and we can return. + if newResp.NumUpdates > oldResp.NumUpdates { return nil } - return fmt.Errorf("%s: channel:%v waiting for HTLCs, "+ - "added: %d/%d", hn.Name(), chanPoint, - htlcsAdded, numPayments) + // Otherwise return an error as the NumUpdates are not + // increased. + return fmt.Errorf("%s: channel:%v not updated after sending "+ + "payments, old updates: %v, new updates: %v", hn.Name(), + chanPoint, oldResp.NumUpdates, newResp.NumUpdates) }, DefaultTimeout) - require.NoError(h, err, "timeout while waiting for HTLCs to lock in") + require.NoError(h, err, "timeout while checking for channel updates") } // OpenChannelPsbt attempts to open a channel between srcNode and destNode with @@ -1737,20 +1693,18 @@ func (h *HarnessTest) OpenChannelPsbt(srcNode, destNode *node.HarnessNode, // Make sure the channel funding address has the correct type for the // given commitment type. - fundingAddr, err := address.DecodeAddress( + fundingAddr, err := btcutil.DecodeAddress( upd.PsbtFund.FundingAddress, miner.HarnessNetParams, ) require.NoError(h, err) switch p.CommitmentType { - case lnrpc.CommitmentType_SIMPLE_TAPROOT, - lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL: - - require.IsType(h, &address.AddressTaproot{}, fundingAddr) + case lnrpc.CommitmentType_SIMPLE_TAPROOT: + require.IsType(h, &btcutil.AddressTaproot{}, fundingAddr) default: require.IsType( - h, &address.AddressWitnessScriptHash{}, fundingAddr, + h, &btcutil.AddressWitnessScriptHash{}, fundingAddr, ) } @@ -2311,14 +2265,10 @@ func (h *HarnessTest) GetOutputIndex(txid chainhash.Hash, addr string) int { p2trOutputIndex := -1 for i, txOut := range tx.MsgTx().TxOut { _, addrs, _, err := txscript.ExtractPkScriptAddrs( - txOut.PkScript, miner.HarnessNetParams, + txOut.PkScript, h.miner.ActiveNet, ) require.NoError(h, err) - if len(addrs) == 0 { - continue - } - if addrs[0].String() == addr { p2trOutputIndex = i } @@ -2601,7 +2551,6 @@ func (h *HarnessTest) DeriveFundingShim(alice, bob *node.HarnessNode, ) if commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT || - commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL || commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY { var carolKey, daveKey *btcec.PublicKey @@ -2626,43 +2575,14 @@ func (h *HarnessTest) DeriveFundingShim(alice, bob *node.HarnessNode, } var txid *chainhash.Hash - var outputIndex uint32 targetOutputs := []*wire.TxOut{fundingOutput} - - findFundingOutputIndex := func(tx *wire.MsgTx) uint32 { - for i, out := range tx.TxOut { - if out.Value != fundingOutput.Value { - continue - } - if !bytes.Equal(out.PkScript, fundingOutput.PkScript) { - continue - } - - return uint32(i) - } - - require.Failf( - h, "funding output not found", - "funding output not found in tx %v", txid, - ) - - return 0 - } - if publish { txid = h.SendOutputsWithoutChange(targetOutputs, 5) - - // If we published the funding transaction, then we need to - // look it up in the mempool to locate the actual output - // index. - tx := h.GetRawTransaction(*txid).MsgTx() - outputIndex = findFundingOutputIndex(tx) } else { tx := h.CreateTransaction(targetOutputs, 5) txHash := tx.TxHash() txid = &txHash - outputIndex = findFundingOutputIndex(tx) } // At this point, we can being our external channel funding workflow. @@ -2677,7 +2597,6 @@ func (h *HarnessTest) DeriveFundingShim(alice, bob *node.HarnessNode, FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{ FundingTxidBytes: txid[:], }, - OutputIndex: outputIndex, } chanPointShim := &lnrpc.ChanPointShim{ Amt: int64(chanSize), diff --git a/lntest/harness_assertion.go b/lntest/harness_assertion.go index 03c1819ff..544576bef 100644 --- a/lntest/harness_assertion.go +++ b/lntest/harness_assertion.go @@ -12,14 +12,12 @@ import ( "strings" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/davecgh/go-spew/spew" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" @@ -426,20 +424,6 @@ func (h *HarnessTest) assertChannelStatus(hn *node.HarnessNode, return channel } -// AssertChannelEventType consumes one event from a client and asserts the event -// type is matched. -func (h *HarnessTest) AssertChannelEventType(sub rpc.ChannelEventsClient, - updateType lnrpc.ChannelEventUpdate_UpdateType, -) *lnrpc.ChannelEventUpdate { - - update := h.ReceiveChannelEvent(sub) - - require.Equalf(h, updateType, update.Type, "wrong event type, "+ - "want %v got %v", updateType, update.Type) - - return update -} - // AssertOutputScriptClass checks that the specified transaction output has the // expected script class. func (h *HarnessTest) AssertOutputScriptClass(tx *btcutil.Tx, @@ -568,10 +552,8 @@ func (h HarnessTest) WaitForChannelCloseEvent( require.NoError(h, err) resp, ok := event.Update.(*lnrpc.CloseStatusUpdate_ChanClose) - require.Truef( - h, ok, "expected channel close update, instead got %T: %v", - event.Update, spew.Sdump(event.Update), - ) + require.Truef(h, ok, "expected channel close update, instead got %v", + event.Update) txid, err := chainhash.NewHash(resp.ChanClose.ClosingTxid) require.NoErrorf(h, err, "wrong format found in closing txid: %v", @@ -958,8 +940,8 @@ func (h *HarnessTest) RandomPreimage() lntypes.Preimage { } // DecodeAddress decodes a given address and asserts there's no error. -func (h *HarnessTest) DecodeAddress(addr string) address.Address { - resp, err := address.DecodeAddress(addr, miner.HarnessNetParams) +func (h *HarnessTest) DecodeAddress(addr string) btcutil.Address { + resp, err := btcutil.DecodeAddress(addr, miner.HarnessNetParams) require.NoError(h, err, "DecodeAddress failed") return resp @@ -967,7 +949,7 @@ func (h *HarnessTest) DecodeAddress(addr string) address.Address { // PayToAddrScript creates a new script from the given address and asserts // there's no error. -func (h *HarnessTest) PayToAddrScript(addr address.Address) []byte { +func (h *HarnessTest) PayToAddrScript(addr btcutil.Address) []byte { addrScript, err := txscript.PayToAddrScript(addr) require.NoError(h, err, "PayToAddrScript failed") @@ -1666,40 +1648,6 @@ func (h *HarnessTest) AssertPaymentFailureReason( return payment } -// AssertPaymentFailureReasonAny asserts that the given node lists a payment -// with the given preimage which has one of the expected failure reasons. -func (h *HarnessTest) AssertPaymentFailureReasonAny( - hn *node.HarnessNode, preimage lntypes.Preimage, - reasons ...lnrpc.PaymentFailureReason) *lnrpc.Payment { - - var payment *lnrpc.Payment - - payHash := preimage.Hash() - err := wait.NoError(func() error { - p, err := h.findPayment(hn, payHash.String()) - if err != nil { - return err - } - - payment = p - - // Check if the payment failure reason matches any of the - // expected reasons. - for _, reason := range reasons { - if reason == p.FailureReason { - return nil - } - } - - return fmt.Errorf("payment: %v failure reason not match, "+ - "want one of %v, got %s(%d)", payHash, reasons, - p.FailureReason, p.FailureReason) - }, DefaultTimeout) - require.NoError(h, err, "timeout checking payment failure reason") - - return payment -} - // AssertActiveNodesSynced asserts all active nodes have synced to the chain. func (h *HarnessTest) AssertActiveNodesSynced() { for _, node := range h.manager.activeNodes { @@ -2182,7 +2130,7 @@ func (h *HarnessTest) AssertNumChannelUpdates(hn *node.HarnessNode, // CreateBurnAddr creates a random burn address of the given type. func (h *HarnessTest) CreateBurnAddr(addrType lnrpc.AddressType) ([]byte, - address.Address) { + btcutil.Address) { randomPrivKey, err := btcec.NewPrivateKey() require.NoError(h, err) @@ -2190,29 +2138,29 @@ func (h *HarnessTest) CreateBurnAddr(addrType lnrpc.AddressType) ([]byte, randomKeyBytes := randomPrivKey.PubKey().SerializeCompressed() harnessNetParams := miner.HarnessNetParams - var addr address.Address + var addr btcutil.Address switch addrType { case lnrpc.AddressType_WITNESS_PUBKEY_HASH: - addr, err = address.NewAddressWitnessPubKeyHash( - address.Hash160(randomKeyBytes), harnessNetParams, + addr, err = btcutil.NewAddressWitnessPubKeyHash( + btcutil.Hash160(randomKeyBytes), harnessNetParams, ) case lnrpc.AddressType_TAPROOT_PUBKEY: taprootKey := txscript.ComputeTaprootKeyNoScript( randomPrivKey.PubKey(), ) - addr, err = address.NewAddressPubKey( + addr, err = btcutil.NewAddressPubKey( schnorr.SerializePubKey(taprootKey), harnessNetParams, ) case lnrpc.AddressType_NESTED_PUBKEY_HASH: - var witnessAddr address.Address - witnessAddr, err = address.NewAddressWitnessPubKeyHash( - address.Hash160(randomKeyBytes), harnessNetParams, + var witnessAddr btcutil.Address + witnessAddr, err = btcutil.NewAddressWitnessPubKeyHash( + btcutil.Hash160(randomKeyBytes), harnessNetParams, ) require.NoError(h, err) - addr, err = address.NewAddressScriptHash( + addr, err = btcutil.NewAddressScriptHash( h.PayToAddrScript(witnessAddr), harnessNetParams, ) @@ -2553,6 +2501,38 @@ func (h *HarnessTest) AssertNumInvoices(hn *node.HarnessNode, return invoices } +// ReceiveSendToRouteUpdate waits until a message is received on the +// SendToRoute client stream or the timeout is reached. +func (h *HarnessTest) ReceiveSendToRouteUpdate( + stream rpc.SendToRouteClient) (*lnrpc.SendResponse, error) { + + chanMsg := make(chan *lnrpc.SendResponse, 1) + errChan := make(chan error, 1) + go func() { + // Consume one message. This will block until the message is + // received. + resp, err := stream.Recv() + if err != nil { + errChan <- err + + return + } + chanMsg <- resp + }() + + select { + case <-time.After(DefaultTimeout): + require.Fail(h, "timeout", "timeout waiting for send resp") + return nil, nil + + case err := <-errChan: + return nil, err + + case updateMsg := <-chanMsg: + return updateMsg, nil + } +} + // AssertInvoiceEqual asserts that two lnrpc.Invoices are equivalent. A custom // comparison function is defined for these tests, since proto message returned // from unary and streaming RPCs (as of protobuf 1.23.0 and grpc 1.29.1) aren't diff --git a/lntest/harness_miner.go b/lntest/harness_miner.go index 540ed18c0..010c3f8ca 100644 --- a/lntest/harness_miner.go +++ b/lntest/harness_miner.go @@ -3,11 +3,10 @@ package lntest import ( "fmt" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lntest/miner" "github.com/lightningnetwork/lnd/lntest/node" "github.com/lightningnetwork/lnd/lntest/wait" @@ -122,66 +121,6 @@ func (h *HarnessTest) MineBlocksAndAssertNumTxes(num uint32, return blocks } -// MineBlockAndAssertOutpointSpent mines a block and asserts the given outpoint -// was spent in it. Unlike MineBlocksAndAssertNumTxes, it does not require the -// txids seen in the mempool before mining to be the txids that confirm. This is -// useful for RBF-sensitive flows where a transaction may be replaced between -// the mempool check and block generation. -func (h *HarnessTest) MineBlockAndAssertOutpointSpent(numTxs int, - outpoint wire.OutPoint) *wire.MsgBlock { - - // Update the harness's current height. - defer h.updateCurrentHeight() - - // If we expect transactions to be included in the blocks we'll mine, - // wait until they are seen in the miner's mempool. - h.AssertNumTxsInMempool(numTxs) - - // Mine a block. - block := h.miner.MineBlocks(1)[0] - - // Assert that the expected number of non-coinbase transactions were - // included in the block. - require.Len(h, block.Transactions, numTxs+1) - - // Assert that the expected outpoint was spent in the block. - h.assertOutpointSpentInBlock(block, outpoint) - - // Finally, make sure all the active nodes are synced. - h.AssertActiveNodesSyncedTo(block.BlockHash()) - - return block -} - -// assertOutpointSpentInBlock asserts that the given outpoint is spent by a -// transaction in the passed block. -func (h *HarnessTest) assertOutpointSpentInBlock(block *wire.MsgBlock, - outpoint wire.OutPoint) { - - var txids []chainhash.Hash - var prevouts []wire.OutPoint - - for _, tx := range block.Transactions { - if blockchain.IsCoinBaseTx(tx) { - continue - } - - txids = append(txids, tx.TxHash()) - - for _, txIn := range tx.TxIn { - prevouts = append(prevouts, txIn.PreviousOutPoint) - - if txIn.PreviousOutPoint == outpoint { - return - } - } - } - - require.Failf(h, "outpoint was not spent in block", - "outpoint:%v, block:%v, txids:%v, prevouts:%v", - outpoint, block.BlockHash(), txids, prevouts) -} - // ConnectMiner connects the miner with the chain backend in the network. func (h *HarnessTest) ConnectMiner() { err := h.manager.chainBackend.ConnectMiner() @@ -342,7 +281,7 @@ func (h *HarnessTest) GetRawTransaction(txid chainhash.Hash) *btcutil.Tx { } // NewMinerAddress creates a new address for the miner and asserts. -func (h *HarnessTest) NewMinerAddress() address.Address { +func (h *HarnessTest) NewMinerAddress() btcutil.Address { return h.miner.NewMinerAddress() } @@ -382,9 +321,7 @@ func (h *HarnessTest) AssertMinerBlockHeightDelta( func (h *HarnessTest) SendRawTransaction(tx *wire.MsgTx, allowHighFees bool) (chainhash.Hash, error) { - // Use the miner's SendRawTransaction method which handles both - // btcd and bitcoind backends. - txid, err := h.miner.SendRawTransaction(tx, allowHighFees) + txid, err := h.miner.Client.SendRawTransaction(tx, allowHighFees) require.NoError(h, err) return *txid, nil diff --git a/lntest/harness_setup.go b/lntest/harness_setup.go index bbea5c4b0..166880bae 100644 --- a/lntest/harness_setup.go +++ b/lntest/harness_setup.go @@ -22,18 +22,6 @@ import ( func SetupHarness(t *testing.T, binaryPath, dbBackendName string, nativeSQL bool, feeService WebFeeService) *HarnessTest { - return SetupHarnessWithMinerConfig( - t, binaryPath, dbBackendName, nativeSQL, feeService, nil, - ) -} - -// SetupHarnessWithMinerConfig is identical to SetupHarness, but allows callers -// to supply a miner configuration. This can be used to select alternative miner -// backends (e.g. bitcoind) without relying on environment variables. -func SetupHarnessWithMinerConfig(t *testing.T, binaryPath, - dbBackendName string, nativeSQL bool, feeService WebFeeService, - minerCfg *miner.MinerConfig) *HarnessTest { - t.Log("Setting up HarnessTest...") // Parse testing flags that influence our test execution. @@ -48,7 +36,7 @@ func SetupHarnessWithMinerConfig(t *testing.T, binaryPath, // Init the miner. t.Log("Prepare the miner and mine blocks to activate segwit...") - miner := prepareMiner(ht.runCtx, ht.T, minerCfg) + miner := prepareMiner(ht.runCtx, ht.T) // Start a chain backend. chainBackend, cleanUp := prepareChainBackend(t, miner.P2PAddress()) @@ -71,39 +59,27 @@ func SetupHarnessWithMinerConfig(t *testing.T, binaryPath, return ht } -// prepareMiner creates an instance of the miner that will act as the miner -// for all tests. This will be used to fund the wallets of the nodes within -// the test network and to drive blockchain related events within the network. -func prepareMiner(ctxt context.Context, t *testing.T, - minerCfg *miner.MinerConfig) *miner.HarnessMiner { +// prepareMiner creates an instance of the btcd's rpctest.Harness that will act +// as the miner for all tests. This will be used to fund the wallets of the +// nodes within the test network and to drive blockchain related events within +// the network. Revert the default setting of accepting non-standard +// transactions on simnet to reject them. Transactions on the lightning network +// should always be standard to get better guarantees of getting included in to +// blocks. +func prepareMiner(ctxt context.Context, t *testing.T) *miner.HarnessMiner { + m := miner.NewMiner(ctxt, t) - var m *miner.HarnessMiner - switch { - case minerCfg != nil: - t.Logf("Using miner backend=%s", minerCfg.Backend) - m = miner.NewMinerWithConfig(ctxt, t, minerCfg) + // Before we start anything, we want to overwrite some of the + // connection settings to make the tests more robust. We might need to + // restart the miner while there are already blocks present, which will + // take a bit longer than the 1 second the default settings amount to. + // Doubling both values will give us retries up to 4 seconds. + m.MaxConnRetries = rpctest.DefaultMaxConnectionRetries * 2 + m.ConnectionRetryTimeout = rpctest.DefaultConnectionRetryTimeout * 2 - default: - // Default to btcd for backward compatibility. - t.Log("Using miner backend=btcd") - m = miner.NewMiner(ctxt, t) - } - - // For btcd, we can optimize connection settings. - // - // Before we start anything, we want to overwrite some of the connection - // settings to make the tests more robust. We might need to restart the - // miner while there are already blocks present, which will take a bit - // longer than the 1 second the default settings amount to. Doubling - // both values will give us retries up to 4 seconds. - m.SetBtcdConnectionRetryParams( - rpctest.DefaultMaxConnectionRetries*2, - rpctest.DefaultConnectionRetryTimeout*2, - ) - - // Start the miner. - require.NoError(t, m.Start(true, 50)) - require.NoError(t, m.NotifyNewTransactions(false)) + // Set up miner and connect chain backend to it. + require.NoError(t, m.SetUp(true, 50)) + require.NoError(t, m.Client.NotifyNewTransactions(false)) // Next mine enough blocks in order for segwit and the CSV package // soft-fork to activate on SimNet. diff --git a/lntest/miner/bitcoind_miner.go b/lntest/miner/bitcoind_miner.go deleted file mode 100644 index bab2b7e3c..000000000 --- a/lntest/miner/bitcoind_miner.go +++ /dev/null @@ -1,850 +0,0 @@ -package miner - -import ( - "bytes" - "context" - "encoding/hex" - "encoding/json" - "fmt" - "math" - "os" - "os/exec" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/btcsuite/btcd/address/v2" - "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/lntest/node" - "github.com/lightningnetwork/lnd/lntest/port" - "github.com/lightningnetwork/lnd/lntest/wait" -) - -// BitcoindMinerBackend implements MinerBackend using bitcoind. -type BitcoindMinerBackend struct { - *testing.T - - // runCtx is a context with cancel method. - //nolint:containedctx - runCtx context.Context - cancel context.CancelFunc - - // bitcoind process and configuration. - cmd *exec.Cmd - dataDir string - rpcClient *rpcclient.Client - rpcHost string - rpcUser string - rpcPass string - p2pPort int - logPath string - logFilename string - extraArgs []string -} - -type fundRawTransactionResp struct { - Hex string `json:"hex"` -} - -type signRawTransactionResp struct { - Hex string `json:"hex"` - Complete bool `json:"complete"` -} - -type generateBlockResp struct { - Hash string `json:"hash"` -} - -type mempoolInfoResp struct { - MempoolMinFee float64 `json:"mempoolminfee"` -} - -type networkInfoResp struct { - RelayFee float64 `json:"relayfee"` -} - -type blockchainInfoResp struct { - BestBlockHash string `json:"bestblockhash"` - Blocks int32 `json:"blocks"` -} - -func btcStringFromSats(sats int64) string { - sign := "" - if sats < 0 { - sign = "-" - sats = -sats - } - - whole := sats / 1e8 - frac := sats % 1e8 - - return fmt.Sprintf("%s%d.%08d", sign, whole, frac) -} - -// NewBitcoindMinerBackend creates a new bitcoind miner backend. -func NewBitcoindMinerBackend(ctxb context.Context, t *testing.T, - config *MinerConfig) *BitcoindMinerBackend { - - t.Helper() - - logDir := config.LogDir - if logDir == "" { - logDir = minerLogDir - } - - logFilename := config.LogFilename - if logFilename == "" { - logFilename = "output_bitcoind_miner.log" - } - - baseLogPath := fmt.Sprintf("%s/%s", node.GetLogDir(), logDir) - - ctxt, cancel := context.WithCancel(ctxb) - - return &BitcoindMinerBackend{ - T: t, - runCtx: ctxt, - cancel: cancel, - logPath: baseLogPath, - logFilename: logFilename, - rpcUser: "miner", - rpcPass: "minerpass", - extraArgs: append([]string(nil), config.ExtraArgs...), - } -} - -// Start starts the bitcoind miner backend. -func (b *BitcoindMinerBackend) Start(setupChain bool, - numMatureOutputs uint32) error { - // Create temporary directory for bitcoind data. - tempDir, err := os.MkdirTemp("", "bitcoind-miner") - if err != nil { - return fmt.Errorf("unable to create temp directory: %w", err) - } - b.dataDir = tempDir - - // Create log directory if it doesn't exist. - if err := os.MkdirAll(b.logPath, 0700); err != nil { - return fmt.Errorf("unable to create log directory: %w", err) - } - - logFile, err := filepath.Abs(b.logPath + "/bitcoind.log") - if err != nil { - return fmt.Errorf("unable to get absolute log path: %w", err) - } - - // Generate ports. - rpcPort := port.NextAvailablePort() - b.p2pPort = port.NextAvailablePort() - b.rpcHost = fmt.Sprintf("127.0.0.1:%d", rpcPort) - - // Build bitcoind command arguments. - cmdArgs := []string{ - "-datadir=" + b.dataDir, - "-regtest", - "-txindex", - // Whitelist localhost to speed up relay. - "-whitelist=127.0.0.1", - fmt.Sprintf("-rpcuser=%s", b.rpcUser), - fmt.Sprintf("-rpcpassword=%s", b.rpcPass), - fmt.Sprintf("-rpcport=%d", rpcPort), - fmt.Sprintf("-bind=127.0.0.1:%d", b.p2pPort), - "-rpcallowip=127.0.0.1", - "-server", - // Run in foreground for easier process management. - "-daemon=0", - // 0x20000002 signals SegWit activation (BIP141 bit 1). - "-blockversion=536870914", - "-debug", - "-debuglogfile=" + logFile, - // Set fallback fee for transaction creation. - "-fallbackfee=0.00001", - // Disable v2 transport since this backend may peer with - // btcd nodes that don't support v2 yet. Without this, - // bitcoind attempts a v2 handshake that hangs for 30s - // before falling back to v1. - // - // TODO: Remove once btcd supports v2 P2P transport. - "-v2transport=0", - } - - cmdArgs = append(cmdArgs, b.extraArgs...) - - // Start bitcoind process. - b.cmd = exec.Command("bitcoind", cmdArgs...) - - // Discard stdout and stderr to prevent output noise in tests. - // All debug output goes to the log file via -debuglogfile. - b.cmd.Stdout = nil - b.cmd.Stderr = nil - - if err := b.cmd.Start(); err != nil { - _ = b.cleanup() - return fmt.Errorf("couldn't start bitcoind: %w", err) - } - - // Create RPC client config. - rpcCfg := rpcclient.ConnConfig{ - Host: b.rpcHost, - User: b.rpcUser, - Pass: b.rpcPass, - DisableConnectOnNew: true, - DisableAutoReconnect: false, - DisableTLS: true, - HTTPPostMode: true, - } - - client, err := rpcclient.New(&rpcCfg, nil) - if err != nil { - _ = b.stopProcess() - _ = b.cleanup() - return fmt.Errorf("unable to create rpc client: %w", err) - } - b.rpcClient = client - - // Wait for bitcoind to be ready (with retries). Use GetBlockCount which - // is more universally supported. Bitcoind can take a while to start, - // especially on first run, so we give it up to 2 minutes. - maxRetries := 120 - retryDelay := 1 * time.Second - - for i := 0; i < maxRetries; i++ { - _, err = b.rpcClient.GetBlockCount() - if err == nil { - // Successfully connected! - break - } - - if i < maxRetries-1 { - time.Sleep(retryDelay) - } - } - - if err != nil { - _ = b.stopProcess() - _ = b.cleanup() - return fmt.Errorf("unable to connect to bitcoind after %d "+ - "retries: %w", maxRetries, err) - } - - // Create a default wallet for the miner using raw RPC. - _, err = b.rpcClient.RawRequest("createwallet", []json.RawMessage{ - []byte(`"miner"`), - }) - if err != nil { - _ = b.stopProcess() - _ = b.cleanup() - return fmt.Errorf("unable to create wallet: %w", err) - } - - if !setupChain { - return nil - } - - // Generate initial blocks to fund the wallet with mature coinbase - // outputs. - // - // Coinbase outputs mature after 100 confirmations. In order to have - // numMatureOutputs mature outputs available, we mine: - // 100 + numMatureOutputs - // blocks. - // - // Use legacy addresses to ensure compatibility with btcd before SegWit - // is fully activated. - // - // Params: label, address_type. - addrResult, err := b.rpcClient.RawRequest( - "getnewaddress", []json.RawMessage{ - []byte(`""`), - []byte(`"legacy"`), - }, - ) - if err != nil { - _ = b.stopProcess() - _ = b.cleanup() - return fmt.Errorf("unable to get new address: %w", err) - } - - var addrStr string - if err := json.Unmarshal(addrResult, &addrStr); err != nil { - _ = b.stopProcess() - _ = b.cleanup() - return fmt.Errorf("unable to parse address: %w", err) - } - - addr, err := address.DecodeAddress(addrStr, HarnessNetParams) - if err != nil { - _ = b.stopProcess() - _ = b.cleanup() - return fmt.Errorf("unable to decode address: %w", err) - } - - initialBlocks := int64(100 + numMatureOutputs) - _, err = b.rpcClient.GenerateToAddress(initialBlocks, addr, nil) - if err != nil { - _ = b.stopProcess() - _ = b.cleanup() - return fmt.Errorf("unable to generate initial blocks: %w", err) - } - - return nil -} - -func (b *BitcoindMinerBackend) minRelayFeeBTCPerKVb() float64 { - // If we fail to query the min relay fee, return 0 so callers can - // continue with their requested fee rate. - if b.rpcClient == nil { - return 0 - } - - var ( - relayFeeBTCPerKVb float64 - mempoolMinFeeBTCPerKVb float64 - ) - - networkInfoJSON, err := b.rpcClient.RawRequest("getnetworkinfo", nil) - if err == nil { - var ni networkInfoResp - if json.Unmarshal(networkInfoJSON, &ni) == nil { - relayFeeBTCPerKVb = ni.RelayFee - } - } - - mempoolInfoJSON, err := b.rpcClient.RawRequest("getmempoolinfo", nil) - if err == nil { - var mi mempoolInfoResp - if json.Unmarshal(mempoolInfoJSON, &mi) == nil { - mempoolMinFeeBTCPerKVb = mi.MempoolMinFee - } - } - - if relayFeeBTCPerKVb > mempoolMinFeeBTCPerKVb { - return relayFeeBTCPerKVb - } - - return mempoolMinFeeBTCPerKVb -} - -func txFromHex(hexStr string) (*wire.MsgTx, error) { - rawTxBytes, err := hex.DecodeString(hexStr) - if err != nil { - return nil, fmt.Errorf("decode tx hex: %w", err) - } - - tx := &wire.MsgTx{} - err = tx.Deserialize(bytes.NewReader(rawTxBytes)) - if err != nil { - return nil, fmt.Errorf("deserialize tx: %w", err) - } - - return tx, nil -} - -// Stop stops the bitcoind miner backend and performs cleanup. -func (b *BitcoindMinerBackend) Stop() error { - b.cancel() - - // Close RPC client. - if b.rpcClient != nil { - b.rpcClient.Disconnect() - } - - // Stop bitcoind process. - _ = b.stopProcess() - - // Copy logs and cleanup. - b.saveLogs() - - return b.cleanup() -} - -// stopProcess stops the bitcoind process gracefully or forcefully. -func (b *BitcoindMinerBackend) stopProcess() error { - if b.cmd == nil || b.cmd.Process == nil { - return nil - } - - // Try to stop bitcoind gracefully via RPC if client is available. - if b.rpcClient != nil { - _, _ = b.rpcClient.RawRequest("stop", nil) - // Give it a moment to shutdown gracefully. - time.Sleep(500 * time.Millisecond) - } - - // Kill the process if it's still running. - _ = b.cmd.Process.Kill() - - // Wait for the process to exit to ensure it releases file handles. - _ = b.cmd.Wait() - - return nil -} - -// cleanup removes temporary directories. -func (b *BitcoindMinerBackend) cleanup() error { - if b.dataDir != "" { - if err := os.RemoveAll(b.dataDir); err != nil { - return fmt.Errorf("cannot remove data dir %s: %w", - b.dataDir, err) - } - } - - return nil -} - -// saveLogs copies the bitcoind log file. -func (b *BitcoindMinerBackend) saveLogs() { - logFile := b.logPath + "/bitcoind.log" - logDestination := fmt.Sprintf("%s/../%s", b.logPath, b.logFilename) - - err := node.CopyFile(logDestination, logFile) - if err != nil { - // Log error but don't fail. - b.Logf("Unable to copy log file: %v", err) - } - - err = os.RemoveAll(b.logPath) - if err != nil { - // Log error but don't fail. - b.Logf("Cannot remove log dir %s: %v", b.logPath, err) - } -} - -// GetBestBlock returns the hash and height of the best block. -func (b *BitcoindMinerBackend) GetBestBlock() (*chainhash.Hash, int32, error) { - infoJSON, err := b.rpcClient.RawRequest("getblockchaininfo", nil) - if err != nil { - return nil, 0, err - } - - var info blockchainInfoResp - if err := json.Unmarshal(infoJSON, &info); err != nil { - return nil, 0, fmt.Errorf( - "parse getblockchaininfo resp: %w", err, - ) - } - - hash, err := chainhash.NewHashFromStr(info.BestBlockHash) - if err != nil { - return nil, 0, fmt.Errorf("invalid best block hash %q: %w", - info.BestBlockHash, err) - } - - return hash, info.Blocks, nil -} - -// GetRawMempool returns all transaction hashes in the mempool. -func (b *BitcoindMinerBackend) GetRawMempool() ([]*chainhash.Hash, error) { - return b.rpcClient.GetRawMempool() -} - -// Generate mines a specified number of blocks. -func (b *BitcoindMinerBackend) Generate(blocks uint32) ([]*chainhash.Hash, - error) { - - // First create an address to mine to. - addr, err := b.NewAddress() - if err != nil { - return nil, fmt.Errorf("unable to get new address: %w", err) - } - - return b.rpcClient.GenerateToAddress(int64(blocks), addr, nil) -} - -// GetBlock returns the block for the given block hash. -func (b *BitcoindMinerBackend) GetBlock(blockHash *chainhash.Hash) ( - *wire.MsgBlock, error) { - - return b.rpcClient.GetBlock(blockHash) -} - -// GetRawTransaction returns the raw transaction for the given txid. -func (b *BitcoindMinerBackend) GetRawTransaction(txid *chainhash.Hash) ( - *btcutil.Tx, error) { - - return b.rpcClient.GetRawTransaction(txid) -} - -// GetRawTransactionVerbose returns verbose information about the given txid. -func (b *BitcoindMinerBackend) GetRawTransactionVerbose( - txid *chainhash.Hash) (*btcjson.TxRawResult, error) { - - return b.rpcClient.GetRawTransactionVerbose(txid) -} - -// InvalidateBlock marks a block as invalid, triggering a reorg. -func (b *BitcoindMinerBackend) InvalidateBlock( - blockHash *chainhash.Hash) error { - - _, err := b.rpcClient.RawRequest( - "invalidateblock", - []json.RawMessage{ - []byte(fmt.Sprintf("%q", blockHash.String())), - }, - ) - - return err -} - -// NotifyNewTransactions registers for new transaction notifications. Bitcoind -// doesn't expose btcd-style tx notifications through the btcd rpcclient -// wrapper, so this is a no-op. -func (b *BitcoindMinerBackend) NotifyNewTransactions(_ bool) error { - return nil -} - -// SendOutputsWithoutChange creates and broadcasts a transaction with the given -// outputs using the specified fee rate. -func (b *BitcoindMinerBackend) SendOutputsWithoutChange(outputs []*wire.TxOut, - feeRate btcutil.Amount) (*chainhash.Hash, error) { - - tx, err := b.CreateTransaction(outputs, feeRate) - if err != nil { - return nil, err - } - - return b.SendRawTransaction(tx, true) -} - -// CreateTransaction creates a transaction with the given outputs. -func (b *BitcoindMinerBackend) CreateTransaction(outputs []*wire.TxOut, - feeRate btcutil.Amount) (*wire.MsgTx, error) { - - tx := wire.NewMsgTx(2) - for _, output := range outputs { - tx.AddTxOut(output) - } - - var rawTx bytes.Buffer - if err := tx.Serialize(&rawTx); err != nil { - return nil, fmt.Errorf("serialize raw transaction: %w", err) - } - - rawHex := hex.EncodeToString(rawTx.Bytes()) - - // Fund the tx using the miner wallet without broadcasting. - // - // The fee rate coming from lntest is in sat/kw. Bitcoind expects - // BTC/kvB. - // - // sat/kw -> sat/kvB: multiply by 4 (1000 weight units is 250 vbytes). - feeRateSatPerKVb := int64(feeRate) * 4 - minFeeRateBTCPerKVb := b.minRelayFeeBTCPerKVb() - minFeeSatPerKVb := int64(math.Ceil(minFeeRateBTCPerKVb * 1e8)) - if feeRateSatPerKVb < minFeeSatPerKVb { - feeRateSatPerKVb = minFeeSatPerKVb - } - - feeRateOpt := btcStringFromSats(feeRateSatPerKVb) - fundOpts, err := json.Marshal(map[string]interface{}{ - // Bitcoin Core supports two distinct fee rate options: - // - fee_rate: sat/vB - // - feeRate: BTC/kvB - // - // We use feeRate (BTC/kvB) because lntest uses btcutil.Amount - // and we already clamp against getnetworkinfo/getmempoolinfo - // which are expressed in BTC/kvB. - "feeRate": json.RawMessage(feeRateOpt), - "changePosition": len(outputs), - "lockUnspents": true, - }) - if err != nil { - return nil, fmt.Errorf("marshal fundrawtransaction opts: %w", - err) - } - - fundResp, err := b.rpcClient.RawRequest( - "fundrawtransaction", - []json.RawMessage{ - []byte(fmt.Sprintf("%q", rawHex)), fundOpts, - }, - ) - if err != nil { - return nil, fmt.Errorf("fundrawtransaction (fee_rate=%s, "+ - "changePosition=%d): %w", feeRateOpt, len(outputs), err) - } - - var funded fundRawTransactionResp - if err := json.Unmarshal(fundResp, &funded); err != nil { - return nil, fmt.Errorf("parse fundrawtransaction resp: %w", - err) - } - - // Sign the funded tx using the miner wallet. - signResp, err := b.rpcClient.RawRequest( - "signrawtransactionwithwallet", - []json.RawMessage{ - []byte(fmt.Sprintf("%q", funded.Hex)), - }, - ) - if err != nil { - return nil, fmt.Errorf("signrawtransactionwithwallet: %w", err) - } - - var signed signRawTransactionResp - if err := json.Unmarshal(signResp, &signed); err != nil { - return nil, fmt.Errorf("parse signrawtransaction resp: %w", - err) - } - if !signed.Complete { - return nil, fmt.Errorf("signrawtransactionwithwallet " + - "incomplete") - } - - return txFromHex(signed.Hex) -} - -// SendOutputs creates and broadcasts a transaction with the given outputs. -func (b *BitcoindMinerBackend) SendOutputs(outputs []*wire.TxOut, - feeRate btcutil.Amount) (*chainhash.Hash, error) { - - return b.SendOutputsWithoutChange(outputs, feeRate) -} - -// GenerateAndSubmitBlock generates a block with the given transactions. -func (b *BitcoindMinerBackend) GenerateAndSubmitBlock(txes []*btcutil.Tx, - blockVersion int32, blockTime time.Time) (*btcutil.Block, error) { - - _ = blockVersion - _ = blockTime - - // Generate a block that includes only the specified transactions. - addr, err := b.NewAddress() - if err != nil { - return nil, fmt.Errorf("unable to get new address: %w", err) - } - - // `generateblock` is available on Bitcoin Core regtest, and lets us - // mine blocks without pulling in arbitrary mempool transactions. - // - // `generateblock` has existed in multiple forms across Bitcoin Core - // versions. We try the following strategies, in order: - // - // 1. Pass raw tx hex strings (doesn't require mempool acceptance, - // avoids policy issues like RBF replacement checks). - // 2. Pass txids after submitting to mempool. - // 3. Fallback to `generatetoaddress`. - var ( - resp json.RawMessage - generateErrHex error - generateErrID error - ) - - // Strategy 1: try `generateblock` with raw tx hex strings. - rawTxs := make([]string, 0, len(txes)) - for _, tx := range txes { - var buf bytes.Buffer - if err := tx.MsgTx().Serialize(&buf); err != nil { - return nil, fmt.Errorf("serialize tx %s: %w", - tx.Hash(), err) - } - rawTxs = append(rawTxs, hex.EncodeToString(buf.Bytes())) - } - - rawTxsJSON, err := json.Marshal(rawTxs) - if err != nil { - return nil, fmt.Errorf("marshal raw txs: %w", err) - } - - resp, generateErrHex = b.rpcClient.RawRequest( - "generateblock", - []json.RawMessage{ - []byte(fmt.Sprintf("%q", addr.EncodeAddress())), - rawTxsJSON, - }, - ) - - if generateErrHex != nil { - // Strategy 2: submit to mempool, then try `generateblock` with - // txids. - txids := make([]string, 0, len(txes)) - for _, tx := range txes { - txid := tx.Hash().String() - txids = append(txids, txid) - - _, err := b.rpcClient.SendRawTransaction( - tx.MsgTx(), true, - ) - if err != nil { - // Ignore already-in-mempool errors. - if !strings.Contains(err.Error(), "already") && - !strings.Contains( - err.Error(), - "txn-already-known", - ) { - - return nil, fmt.Errorf("unable to "+ - "send tx %s to mempool: %w", - txid, err) - } - } - } - - txidsJSON, err := json.Marshal(txids) - if err != nil { - return nil, fmt.Errorf("marshal txids: %w", err) - } - - resp, generateErrID = b.rpcClient.RawRequest( - "generateblock", - []json.RawMessage{ - []byte(fmt.Sprintf("%q", addr.EncodeAddress())), - txidsJSON, - }, - ) - } - - if generateErrHex != nil && generateErrID != nil { - // Fall back to `generatetoaddress` for older bitcoind versions. - // - // Note: this fallback may include additional mempool - // transactions. - blockHashes, genErr := b.rpcClient.GenerateToAddress( - 1, addr, nil, - ) - if genErr != nil { - return nil, fmt.Errorf("generateblock (hex): %v; "+ - "generateblock (txid): %v; fallback "+ - "generatetoaddress: %v", generateErrHex, - generateErrID, genErr) - } - if len(blockHashes) == 0 { - return nil, fmt.Errorf("no block generated") - } - - block, getErr := b.rpcClient.GetBlock(blockHashes[0]) - if getErr != nil { - return nil, fmt.Errorf("unable to get generated "+ - "block: %w", getErr) - } - - return btcutil.NewBlock(block), nil - } - - // `generateblock` returns either a hash string or an object with a - // `hash` field, depending on the version. - var blockHashStr string - if unmarshalErr := json.Unmarshal( - resp, &blockHashStr, - ); unmarshalErr != nil { - var respObj generateBlockResp - if unmarshalErr2 := json.Unmarshal( - resp, &respObj, - ); unmarshalErr2 != nil { - return nil, fmt.Errorf("parse generateblock resp: "+ - "%v; %v", unmarshalErr, unmarshalErr2) - } - blockHashStr = respObj.Hash - } - - blockHash, err := chainhash.NewHashFromStr(blockHashStr) - if err != nil { - return nil, fmt.Errorf("invalid generateblock hash %q: %w", - blockHashStr, err) - } - - block, err := b.rpcClient.GetBlock(blockHash) - if err != nil { - return nil, fmt.Errorf("unable to get generated block: %w", err) - } - - return btcutil.NewBlock(block), nil -} - -// NewAddress generates a new address. -func (b *BitcoindMinerBackend) NewAddress() (address.Address, error) { - // Use legacy addresses for compatibility with btcd. - // - // Params: label, address_type. - addrResult, err := b.rpcClient.RawRequest( - "getnewaddress", []json.RawMessage{ - []byte(`""`), - []byte(`"legacy"`), - }, - ) - if err != nil { - return nil, fmt.Errorf("unable to get new address: %w", err) - } - - var addrStr string - if err := json.Unmarshal(addrResult, &addrStr); err != nil { - return nil, fmt.Errorf("unable to parse address: %w", err) - } - - return address.DecodeAddress(addrStr, HarnessNetParams) -} - -// P2PAddress returns the P2P address of the miner. -func (b *BitcoindMinerBackend) P2PAddress() string { - return fmt.Sprintf("127.0.0.1:%d", b.p2pPort) -} - -// Name returns the name of the backend implementation. -func (b *BitcoindMinerBackend) Name() string { - return "bitcoind" -} - -// ConnectMiner connects this miner to another node. -func (b *BitcoindMinerBackend) ConnectMiner(address string) error { - // Use "onetry" so we don't persist peer connections across tests. - _, err := b.rpcClient.RawRequest( - "addnode", - []json.RawMessage{ - []byte(fmt.Sprintf("%q", address)), - []byte(`"onetry"`), - }, - ) - - if err != nil { - return err - } - - return wait.NoError(func() error { - peerInfo, err := b.rpcClient.GetPeerInfo() - if err != nil { - return err - } - - for _, peer := range peerInfo { - if strings.HasPrefix(peer.Addr, address) { - return nil - } - } - - return fmt.Errorf("peer %s not connected", address) - }, wait.DefaultTimeout) -} - -// DisconnectMiner disconnects this miner from another node. -func (b *BitcoindMinerBackend) DisconnectMiner(address string) error { - // `addnode remove` removes from the addnode list, but doesn't reliably - // disconnect an existing connection. Use `disconnectnode` first. - _, err := b.rpcClient.RawRequest( - "disconnectnode", - []json.RawMessage{ - []byte(fmt.Sprintf("%q", address)), - }, - ) - if err != nil { - return err - } - - // Best-effort cleanup of any persistent addnode state. `ConnectMiner` - // uses "onetry", so `addnode remove` can return an error if the peer - // was never added to the addnode list. - _ = b.rpcClient.AddNode(address, rpcclient.ANRemove) - - return nil -} - -// SendRawTransaction sends a raw transaction to the network. -func (b *BitcoindMinerBackend) SendRawTransaction(tx *wire.MsgTx, - allowHighFees bool) (*chainhash.Hash, error) { - - return b.rpcClient.SendRawTransaction(tx, allowHighFees) -} diff --git a/lntest/miner/btcd_miner.go b/lntest/miner/btcd_miner.go deleted file mode 100644 index 090a581c3..000000000 --- a/lntest/miner/btcd_miner.go +++ /dev/null @@ -1,242 +0,0 @@ -package miner - -import ( - "context" - "fmt" - "os" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/btcsuite/btcd/address/v2" - "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/integration/rpctest" - "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/lntest/node" - "github.com/stretchr/testify/require" -) - -// BtcdMinerBackend implements MinerBackend using btcd. -type BtcdMinerBackend struct { - *testing.T - *rpctest.Harness - - // runCtx is a context with cancel method. It's used to signal when the - // node needs to quit, and used as the parent context when spawning - // children contexts for RPC requests. - //nolint:containedctx - runCtx context.Context - cancel context.CancelFunc - - // logPath is the directory path of the miner's logs. - logPath string - - // logFilename is the saved log filename of the miner node. - logFilename string -} - -// NewBtcdMinerBackend creates a new btcd miner backend. -func NewBtcdMinerBackend(ctxb context.Context, t *testing.T, - config *MinerConfig) *BtcdMinerBackend { - - t.Helper() - - logDir := config.LogDir - if logDir == "" { - logDir = minerLogDir - } - - logFilename := config.LogFilename - if logFilename == "" { - logFilename = minerLogFilename - } - - handler := &rpcclient.NotificationHandlers{} - btcdBinary := node.GetBtcdBinary() - baseLogPath := fmt.Sprintf("%s/%s", node.GetLogDir(), logDir) - - args := []string{ - "--rejectnonstd", - "--txindex", - "--nowinservice", - "--nobanning", - "--debuglevel=debug", - "--logdir=" + baseLogPath, - "--trickleinterval=100ms", - // Don't disconnect if a reply takes too long. - "--nostalldetect", - } - - // Add any extra args from config. - if config.ExtraArgs != nil { - args = append(args, config.ExtraArgs...) - } - - miner, err := rpctest.New(HarnessNetParams, handler, args, btcdBinary) - require.NoError(t, err, "unable to create mining node") - - ctxt, cancel := context.WithCancel(ctxb) - - return &BtcdMinerBackend{ - T: t, - Harness: miner, - runCtx: ctxt, - cancel: cancel, - logPath: baseLogPath, - logFilename: logFilename, - } -} - -// Start starts the btcd miner backend. -func (b *BtcdMinerBackend) Start(setupChain bool, - numMatureOutputs uint32) error { - - return b.SetUp(setupChain, numMatureOutputs) -} - -// Stop stops the btcd miner backend and saves logs. -func (b *BtcdMinerBackend) Stop() error { - b.cancel() - if err := b.TearDown(); err != nil { - return fmt.Errorf("tear down miner got error: %w", err) - } - b.saveLogs() - - return nil -} - -// saveLogs copies the node logs and save it to the file specified by -// b.logFilename. -func (b *BtcdMinerBackend) saveLogs() { - // After shutting down the miner, we'll make a copy of the log files - // before deleting the temporary log dir. - path := fmt.Sprintf("%s/%s", b.logPath, HarnessNetParams.Name) - files, err := os.ReadDir(path) - require.NoError(b, err, "unable to read log directory") - - for _, file := range files { - newFilename := strings.Replace( - file.Name(), "btcd.log", b.logFilename, 1, - ) - copyPath := fmt.Sprintf("%s/../%s", b.logPath, newFilename) - - logFile := fmt.Sprintf("%s/%s", path, file.Name()) - err := node.CopyFile(filepath.Clean(copyPath), logFile) - require.NoError(b, err, "unable to copy file") - } - - err = os.RemoveAll(b.logPath) - require.NoErrorf(b, err, "cannot remove dir %s", b.logPath) -} - -// GetBestBlock returns the hash and height of the best block. -func (b *BtcdMinerBackend) GetBestBlock() (*chainhash.Hash, int32, error) { - return b.Client.GetBestBlock() -} - -// GetRawMempool returns all transaction hashes in the mempool. -func (b *BtcdMinerBackend) GetRawMempool() ([]*chainhash.Hash, error) { - return b.Client.GetRawMempool() -} - -// Generate mines a specified number of blocks. -func (b *BtcdMinerBackend) Generate(blocks uint32) ([]*chainhash.Hash, error) { - return b.Client.Generate(blocks) -} - -// GetBlock returns the block for the given block hash. -func (b *BtcdMinerBackend) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, - error) { - - return b.Client.GetBlock(blockHash) -} - -// GetRawTransaction returns the raw transaction for the given txid. -func (b *BtcdMinerBackend) GetRawTransaction(txid *chainhash.Hash) (*btcutil.Tx, - error) { - - return b.Client.GetRawTransaction(txid) -} - -// GetRawTransactionVerbose returns verbose information about the given txid. -func (b *BtcdMinerBackend) GetRawTransactionVerbose( - txid *chainhash.Hash) (*btcjson.TxRawResult, error) { - - return b.Client.GetRawTransactionVerbose(txid) -} - -// InvalidateBlock marks a block as invalid, triggering a reorg. -func (b *BtcdMinerBackend) InvalidateBlock(blockHash *chainhash.Hash) error { - return b.Client.InvalidateBlock(blockHash) -} - -// SendRawTransaction sends a raw transaction to the backend. -func (b *BtcdMinerBackend) SendRawTransaction(tx *wire.MsgTx, - allowHighFees bool) (*chainhash.Hash, error) { - - return b.Client.SendRawTransaction(tx, allowHighFees) -} - -// NotifyNewTransactions registers for new transaction notifications. -func (b *BtcdMinerBackend) NotifyNewTransactions(verbose bool) error { - return b.Client.NotifyNewTransactions(verbose) -} - -// SendOutputsWithoutChange creates and broadcasts a transaction with -// the given outputs using the specified fee rate. -func (b *BtcdMinerBackend) SendOutputsWithoutChange(outputs []*wire.TxOut, - feeRate btcutil.Amount) (*chainhash.Hash, error) { - - return b.Harness.SendOutputsWithoutChange(outputs, feeRate) -} - -// CreateTransaction creates a transaction with the given outputs. -func (b *BtcdMinerBackend) CreateTransaction(outputs []*wire.TxOut, - feeRate btcutil.Amount) (*wire.MsgTx, error) { - - return b.Harness.CreateTransaction(outputs, feeRate, false) -} - -// SendOutputs creates and broadcasts a transaction with the given outputs. -func (b *BtcdMinerBackend) SendOutputs(outputs []*wire.TxOut, - feeRate btcutil.Amount) (*chainhash.Hash, error) { - - return b.Harness.SendOutputs(outputs, feeRate) -} - -// GenerateAndSubmitBlock generates a block with the given transactions. -func (b *BtcdMinerBackend) GenerateAndSubmitBlock(txes []*btcutil.Tx, - blockVersion int32, - blockTime time.Time) (*btcutil.Block, error) { - - return b.Harness.GenerateAndSubmitBlock(txes, blockVersion, blockTime) -} - -// NewAddress generates a new address. -func (b *BtcdMinerBackend) NewAddress() (address.Address, error) { - return b.Harness.NewAddress() -} - -// P2PAddress returns the P2P address of the miner. -func (b *BtcdMinerBackend) P2PAddress() string { - return b.Harness.P2PAddress() -} - -// Name returns the name of the backend implementation. -func (b *BtcdMinerBackend) Name() string { - return "btcd" -} - -// ConnectMiner connects this miner to another node using btcjson commands. -func (b *BtcdMinerBackend) ConnectMiner(address string) error { - return b.Client.Node(btcjson.NConnect, address, &Temp) -} - -// DisconnectMiner disconnects this miner from another node. -func (b *BtcdMinerBackend) DisconnectMiner(address string) error { - return b.Client.Node(btcjson.NDisconnect, address, &Temp) -} diff --git a/lntest/miner/interface.go b/lntest/miner/interface.go deleted file mode 100644 index f2e6153df..000000000 --- a/lntest/miner/interface.go +++ /dev/null @@ -1,111 +0,0 @@ -package miner - -import ( - "time" - - "github.com/btcsuite/btcd/address/v2" - "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" -) - -// MinerBackend defines the interface for different miner backend -// implementations (btcd vs bitcoind). -// -// We keep a single interface to preserve the existing lntest APIs while -// supporting multiple backends. -// -//nolint:interfacebloat -type MinerBackend interface { - // Start starts the miner backend. - // - // If setupChain is true, the backend should perform any initial chain - // setup needed for tests. numMatureOutputs specifies how many mature - // coinbase outputs should be available after startup. - Start(setupChain bool, numMatureOutputs uint32) error - - // Stop stops the miner backend and performs cleanup. - Stop() error - - // GetBestBlock returns the hash and height of the best block. - GetBestBlock() (*chainhash.Hash, int32, error) - - // GetRawMempool returns all transaction hashes in the mempool. - GetRawMempool() ([]*chainhash.Hash, error) - - // Generate mines a specified number of blocks. - Generate(blocks uint32) ([]*chainhash.Hash, error) - - // GetBlock returns the block for the given block hash. - GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) - - // GetRawTransaction returns the raw transaction for the given txid. - GetRawTransaction(txid *chainhash.Hash) (*btcutil.Tx, error) - - // GetRawTransactionVerbose returns verbose information about the given - // transaction. - GetRawTransactionVerbose( - txid *chainhash.Hash) (*btcjson.TxRawResult, error) - - // InvalidateBlock marks a block as invalid, triggering a reorg. - InvalidateBlock(blockHash *chainhash.Hash) error - - // SendRawTransaction sends a raw transaction to the backend. - SendRawTransaction(tx *wire.MsgTx, allowHighFees bool) (*chainhash.Hash, - error) - - // NotifyNewTransactions registers for new transaction notifications. - // For backends that don't support this, it should be a no-op. - NotifyNewTransactions(verbose bool) error - - // SendOutputsWithoutChange creates and broadcasts a transaction with - // the given outputs using the specified fee rate. - SendOutputsWithoutChange(outputs []*wire.TxOut, - feeRate btcutil.Amount) (*chainhash.Hash, error) - - // CreateTransaction creates a transaction with the given outputs. - CreateTransaction(outputs []*wire.TxOut, - feeRate btcutil.Amount) (*wire.MsgTx, error) - - // SendOutputs creates and broadcasts a transaction with the given - // outputs. - SendOutputs(outputs []*wire.TxOut, - feeRate btcutil.Amount) (*chainhash.Hash, error) - - // GenerateAndSubmitBlock generates a block with the given transactions. - GenerateAndSubmitBlock(txes []*btcutil.Tx, blockVersion int32, - blockTime time.Time) (*btcutil.Block, error) - - // NewAddress generates a new address. - NewAddress() (address.Address, error) - - // P2PAddress returns the P2P address of the miner. - P2PAddress() string - - // ConnectMiner connects this backend to a miner peer at the given - // address (host:port). - ConnectMiner(address string) error - - // DisconnectMiner disconnects this backend from a miner peer at the - // given address (host:port). - DisconnectMiner(address string) error - - // Name returns the name of the backend implementation. - Name() string -} - -// MinerConfig holds configuration for creating different miner backends. -type MinerConfig struct { - // Backend specifies which backend to use ("btcd" or "bitcoind"). - Backend string - - // LogDir specifies the directory for log files. - LogDir string - - // LogFilename specifies the log filename. - LogFilename string - - // ExtraArgs contains additional command-line arguments for the backend. - ExtraArgs []string -} diff --git a/lntest/miner/miner.go b/lntest/miner/miner.go index de486e715..0229d6a47 100644 --- a/lntest/miner/miner.go +++ b/lntest/miner/miner.go @@ -4,17 +4,21 @@ import ( "bytes" "context" "fmt" + "os" + "path/filepath" + "strings" "testing" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/integration/rpctest" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/rpcclient" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" + "github.com/lightningnetwork/lnd/lntest/node" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/stretchr/testify/require" ) @@ -42,32 +46,19 @@ var ( type HarnessMiner struct { *testing.T - - // backend is the underlying miner backend implementation (btcd or - // bitcoind). This is always set. - backend MinerBackend - - // harness is the btcd-specific harness. This is only set when using - // the btcd backend, and is nil when using bitcoind. - harness *rpctest.Harness - - // ActiveNet is the miner network parameters used by lntest. - // - // This field exists for backwards compatibility with existing itests - // that reference `ht.Miner().ActiveNet`. When using the bitcoind miner - // backend, the embedded btcd rpctest harness is nil, but callers still - // expect this value to be set. - ActiveNet *chaincfg.Params + *rpctest.Harness // runCtx is a context with cancel method. It's used to signal when the // node needs to quit, and used as the parent context when spawning // children contexts for RPC requests. - // - // NOTE: The backend owns lifecycle management; these fields are kept - // for compatibility with existing patterns in lntest. - //nolint:containedctx - runCtx context.Context + runCtx context.Context //nolint:containedctx cancel context.CancelFunc + + // logPath is the directory path of the miner's logs. + logPath string + + // logFilename is the saved log filename of the miner node. + logFilename string } // NewMiner creates a new miner using btcd backend with the default log file @@ -87,179 +78,77 @@ func NewTempMiner(ctxt context.Context, t *testing.T, return newMiner(ctxt, t, tempDir, tempLogFilename) } -// NewBitcoindMiner creates a new miner using bitcoind backend with the -// default log file dir and name. -func NewBitcoindMiner(ctxt context.Context, t *testing.T) *HarnessMiner { - t.Helper() - return newBitcoindMiner( - ctxt, t, minerLogDir, "output_bitcoind_miner.log", - ) -} - -// NewBitcoindTempMiner creates a new miner using bitcoind backend with the -// specified log file dir and name. -func NewBitcoindTempMiner(ctxt context.Context, t *testing.T, - tempDir, tempLogFilename string) *HarnessMiner { - - t.Helper() - return newBitcoindMiner(ctxt, t, tempDir, tempLogFilename) -} - -// NewMinerWithConfig creates a new miner with the specified configuration. -// The Backend field in the config determines which backend to use ("btcd" or -// "bitcoind"). -func NewMinerWithConfig(ctxt context.Context, t *testing.T, - config *MinerConfig) *HarnessMiner { - - t.Helper() - - // Set defaults if not specified. - logDir := config.LogDir - if logDir == "" { - logDir = minerLogDir - } - - logFilename := config.LogFilename - if logFilename == "" { - if config.Backend == "bitcoind" { - logFilename = "output_bitcoind_miner.log" - } else { - logFilename = minerLogFilename - } - } - - // Choose backend based on config. - switch config.Backend { - case "bitcoind": - return newBitcoindMinerWithConfig(ctxt, t, config, logDir, - logFilename) - case "btcd", "": - // Default to btcd for backward compatibility. - return newBtcdMinerWithConfig(ctxt, t, config, logDir, - logFilename) - default: - require.Failf(t, "unknown backend", - "backend %s not supported", config.Backend) - return nil - } -} - // newMiner creates a new miner using btcd's rpctest. func newMiner(ctxb context.Context, t *testing.T, minerDirName, logFilename string) *HarnessMiner { t.Helper() - config := &MinerConfig{ - Backend: "btcd", - LogDir: minerDirName, - LogFilename: logFilename, + handler := &rpcclient.NotificationHandlers{} + btcdBinary := node.GetBtcdBinary() + baseLogPath := fmt.Sprintf("%s/%s", node.GetLogDir(), minerDirName) + + args := []string{ + "--rejectnonstd", + "--txindex", + "--nowinservice", + "--nobanning", + "--debuglevel=debug", + "--logdir=" + baseLogPath, + "--trickleinterval=100ms", + // Don't disconnect if a reply takes too long. + "--nostalldetect", } - return newBtcdMinerWithConfig(ctxb, t, config, minerDirName, - logFilename) -} + miner, err := rpctest.New(HarnessNetParams, handler, args, btcdBinary) + require.NoError(t, err, "unable to create mining node") -// newBitcoindMiner creates a new miner using bitcoind. -func newBitcoindMiner(ctxb context.Context, t *testing.T, minerDirName, - logFilename string) *HarnessMiner { - - t.Helper() - - config := &MinerConfig{ - Backend: "bitcoind", - LogDir: minerDirName, - LogFilename: logFilename, - } - - return newBitcoindMinerWithConfig(ctxb, t, config, minerDirName, - logFilename) -} - -// newBtcdMinerWithConfig creates a new miner using btcd with the given config. -func newBtcdMinerWithConfig(ctxb context.Context, t *testing.T, - config *MinerConfig, minerDirName, logFilename string) *HarnessMiner { - - t.Helper() - - // Create the btcd backend wrapper. Note that we don't start the backend - // here, as callers (lntest harness) often tweak harness settings (e.g. - // connection retries) before calling SetUp/Start. - btcdBackend := NewBtcdMinerBackend(ctxb, t, &MinerConfig{ - Backend: "btcd", - LogDir: minerDirName, - LogFilename: logFilename, - ExtraArgs: config.ExtraArgs, - }) + ctxt, cancel := context.WithCancel(ctxb) return &HarnessMiner{ - T: t, - backend: btcdBackend, - harness: btcdBackend.Harness, - ActiveNet: HarnessNetParams, - runCtx: btcdBackend.runCtx, - cancel: btcdBackend.cancel, + T: t, + Harness: miner, + runCtx: ctxt, + cancel: cancel, + logPath: baseLogPath, + logFilename: logFilename, } } -// newBitcoindMinerWithConfig creates a new miner using bitcoind with the -// given config. -func newBitcoindMinerWithConfig(ctxb context.Context, t *testing.T, - config *MinerConfig, minerDirName, logFilename string) *HarnessMiner { +// saveLogs copies the node logs and save it to the file specified by +// h.logFilename. +func (h *HarnessMiner) saveLogs() { + // After shutting down the miner, we'll make a copy of the log files + // before deleting the temporary log dir. + path := fmt.Sprintf("%s/%s", h.logPath, HarnessNetParams.Name) + files, err := os.ReadDir(path) + require.NoError(h, err, "unable to read log directory") - t.Helper() + for _, file := range files { + newFilename := strings.Replace( + file.Name(), "btcd.log", h.logFilename, 1, + ) + copyPath := fmt.Sprintf("%s/../%s", h.logPath, newFilename) - // Create the bitcoind backend. - bitcoindBackend := NewBitcoindMinerBackend(ctxb, t, &MinerConfig{ - Backend: "bitcoind", - LogDir: minerDirName, - LogFilename: logFilename, - ExtraArgs: config.ExtraArgs, - }) - - return &HarnessMiner{ - T: t, - backend: bitcoindBackend, - // No btcd harness when using bitcoind. - harness: nil, - ActiveNet: HarnessNetParams, - runCtx: bitcoindBackend.runCtx, - cancel: bitcoindBackend.cancel, - } -} - -// SetBtcdConnectionRetryParams updates the underlying btcd harness connection -// retry parameters when this miner is backed by btcd. -func (h *HarnessMiner) SetBtcdConnectionRetryParams(maxRetries int, - retryTimeout time.Duration) { - - if h.harness == nil { - return + logFile := fmt.Sprintf("%s/%s", path, file.Name()) + err := node.CopyFile(filepath.Clean(copyPath), logFile) + require.NoError(h, err, "unable to copy file") } - h.harness.MaxConnRetries = maxRetries - h.harness.ConnectionRetryTimeout = retryTimeout -} - -// Start starts the miner backend. -func (h *HarnessMiner) Start(setupChain bool, numMatureOutputs uint32) error { - return h.backend.Start(setupChain, numMatureOutputs) -} - -// NotifyNewTransactions registers for new transaction notifications. -func (h *HarnessMiner) NotifyNewTransactions(verbose bool) error { - return h.backend.NotifyNewTransactions(verbose) + err = os.RemoveAll(h.logPath) + require.NoErrorf(h, err, "cannot remove dir %s", h.logPath) } // Stop shuts down the miner and saves its logs. func (h *HarnessMiner) Stop() { - err := h.backend.Stop() - require.NoError(h, err, "failed to stop miner backend") + h.cancel() + require.NoError(h, h.TearDown(), "tear down miner got error") + h.saveLogs() } // GetBestBlock makes a RPC request to miner and asserts. func (h *HarnessMiner) GetBestBlock() (*chainhash.Hash, int32) { - blockHash, height, err := h.backend.GetBestBlock() + blockHash, height, err := h.Client.GetBestBlock() require.NoError(h, err, "failed to GetBestBlock") return blockHash, height @@ -268,7 +157,7 @@ func (h *HarnessMiner) GetBestBlock() (*chainhash.Hash, int32) { // GetRawMempool makes a RPC call to the miner's GetRawMempool and // asserts. func (h *HarnessMiner) GetRawMempool() []chainhash.Hash { - mempool, err := h.backend.GetRawMempool() + mempool, err := h.Client.GetRawMempool() require.NoError(h, err, "unable to get mempool") txns := make([]chainhash.Hash, 0, len(mempool)) @@ -281,7 +170,7 @@ func (h *HarnessMiner) GetRawMempool() []chainhash.Hash { // GenerateBlocks mine 'num' of blocks and returns them. func (h *HarnessMiner) GenerateBlocks(num uint32) []*chainhash.Hash { - blockHashes, err := h.backend.Generate(num) + blockHashes, err := h.Client.Generate(num) require.NoError(h, err, "unable to generate blocks") require.Len(h, blockHashes, int(num), "wrong num of blocks generated") @@ -290,7 +179,7 @@ func (h *HarnessMiner) GenerateBlocks(num uint32) []*chainhash.Hash { // GetBlock gets a block using its block hash. func (h *HarnessMiner) GetBlock(blockHash *chainhash.Hash) *wire.MsgBlock { - block, err := h.backend.GetBlock(blockHash) + block, err := h.Client.GetBlock(blockHash) require.NoError(h, err, "unable to get block") return block @@ -381,30 +270,17 @@ func (h *HarnessMiner) MineBlocksAndAssertNumTxes(num uint32, // GetRawTransaction makes a RPC call to the miner's GetRawTransaction and // asserts. func (h *HarnessMiner) GetRawTransaction(txid chainhash.Hash) *btcutil.Tx { - tx, err := h.backend.GetRawTransaction(&txid) + tx, err := h.Client.GetRawTransaction(&txid) require.NoErrorf(h, err, "failed to get raw tx: %v", txid) return tx } -// GetRawTransactionNoAssert makes a RPC call to the miner's GetRawTransaction -// and returns the error to the caller. -func (h *HarnessMiner) GetRawTransactionNoAssert( - txid chainhash.Hash) (*btcutil.Tx, error) { - - return h.backend.GetRawTransaction(&txid) -} - -// InvalidateBlock marks a block as invalid, triggering a reorg. -func (h *HarnessMiner) InvalidateBlock(blockHash *chainhash.Hash) error { - return h.backend.InvalidateBlock(blockHash) -} - // GetRawTransactionVerbose makes a RPC call to the miner's // GetRawTransactionVerbose and asserts. func (h *HarnessMiner) GetRawTransactionVerbose( txid chainhash.Hash) *btcjson.TxRawResult { - tx, err := h.backend.GetRawTransactionVerbose(&txid) + tx, err := h.Client.GetRawTransactionVerbose(&txid) require.NoErrorf(h, err, "failed to get raw tx verbose: %v", txid) return tx } @@ -494,7 +370,9 @@ func (h *HarnessMiner) AssertTxNotInMempool(txid chainhash.Hash) { func (h *HarnessMiner) SendOutputsWithoutChange(outputs []*wire.TxOut, feeRate btcutil.Amount) *chainhash.Hash { - txid, err := h.backend.SendOutputsWithoutChange(outputs, feeRate) + txid, err := h.Harness.SendOutputsWithoutChange( + outputs, feeRate, + ) require.NoErrorf(h, err, "failed to send output") return txid @@ -505,7 +383,7 @@ func (h *HarnessMiner) SendOutputsWithoutChange(outputs []*wire.TxOut, func (h *HarnessMiner) CreateTransaction(outputs []*wire.TxOut, feeRate btcutil.Amount) *wire.MsgTx { - tx, err := h.backend.CreateTransaction(outputs, feeRate) + tx, err := h.Harness.CreateTransaction(outputs, feeRate, false) require.NoErrorf(h, err, "failed to create transaction") return tx @@ -516,7 +394,7 @@ func (h *HarnessMiner) CreateTransaction(outputs []*wire.TxOut, func (h *HarnessMiner) SendOutput(newOutput *wire.TxOut, feeRate btcutil.Amount) *chainhash.Hash { - hash, err := h.backend.SendOutputs([]*wire.TxOut{newOutput}, feeRate) + hash, err := h.Harness.SendOutputs([]*wire.TxOut{newOutput}, feeRate) require.NoErrorf(h, err, "failed to send outputs") return hash @@ -536,7 +414,7 @@ func (h *HarnessMiner) MineBlocksSlow(num uint32) []*wire.MsgBlock { } for i, blockHash := range blockHashes { - block, err := h.backend.GetBlock(blockHash) + block, err := h.Client.GetBlock(blockHash) require.NoError(h, err, "get blocks") blocks[i] = block @@ -566,7 +444,7 @@ func (h *HarnessMiner) AssertOutpointInMempool(op wire.OutPoint) *wire.MsgTx { // found. For instance, the aggregation logic used in // sweeping HTLC outputs will update the mempool by // replacing the HTLC spending txes with a single one. - tx, err := h.backend.GetRawTransaction(&txid) + tx, err := h.Client.GetRawTransaction(&txid) if err != nil { return err } @@ -590,36 +468,20 @@ func (h *HarnessMiner) AssertOutpointInMempool(op wire.OutPoint) *wire.MsgTx { // GetNumTxsFromMempool polls until finding the desired number of transactions // in the miner's mempool and returns the full transactions to the caller. func (h *HarnessMiner) GetNumTxsFromMempool(n int) []*wire.MsgTx { + txids := h.AssertNumTxsInMempool(n) + var txes []*wire.MsgTx - - err := wait.NoError(func() error { - txids := h.AssertNumTxsInMempool(n) - - txes = nil - for _, txid := range txids { - // The mempool can change between listing its txids - // and fetching a transaction. For example, sweep - // tests may RBF-replace a tx while we iterate over - // the snapshot. Retry with a fresh snapshot when - // that happens. - tx, err := h.backend.GetRawTransaction(&txid) - if err != nil { - return err - } - - txes = append(txes, tx.MsgTx()) - } - - return nil - }, wait.MinerMempoolTimeout) - require.NoError(h, err, "get txs from mempool") + for _, txid := range txids { + tx := h.GetRawTransaction(txid) + txes = append(txes, tx.MsgTx()) + } return txes } // NewMinerAddress creates a new address for the miner and asserts. -func (h *HarnessMiner) NewMinerAddress() address.Address { - addr, err := h.backend.NewAddress() +func (h *HarnessMiner) NewMinerAddress() btcutil.Address { + addr, err := h.NewAddress() require.NoError(h, err, "failed to create new miner address") return addr } @@ -630,10 +492,10 @@ func (h *HarnessMiner) MineBlockWithTxes(txes []*btcutil.Tx) *wire.MsgBlock { var emptyTime time.Time // Generate a block. - b, err := h.backend.GenerateAndSubmitBlock(txes, -1, emptyTime) + b, err := h.GenerateAndSubmitBlock(txes, -1, emptyTime) require.NoError(h, err, "unable to mine block") - block, err := h.backend.GetBlock(b.Hash()) + block, err := h.Client.GetBlock(b.Hash()) require.NoError(h, err, "unable to get block") // Make sure the mempool has been updated. @@ -651,10 +513,10 @@ func (h *HarnessMiner) MineBlockWithTx(tx *wire.MsgTx) *wire.MsgBlock { txes := []*btcutil.Tx{btcutil.NewTx(tx)} // Generate a block. - b, err := h.backend.GenerateAndSubmitBlock(txes, -1, emptyTime) + b, err := h.GenerateAndSubmitBlock(txes, -1, emptyTime) require.NoError(h, err, "unable to mine block") - block, err := h.backend.GetBlock(b.Hash()) + block, err := h.Client.GetBlock(b.Hash()) require.NoError(h, err, "unable to get block") // Make sure the mempool has been updated. @@ -670,7 +532,7 @@ func (h *HarnessMiner) MineEmptyBlocks(num int) []*wire.MsgBlock { blocks := make([]*wire.MsgBlock, num) for i := 0; i < num; i++ { // Generate an empty block. - b, err := h.backend.GenerateAndSubmitBlock(nil, -1, emptyTime) + b, err := h.GenerateAndSubmitBlock(nil, -1, emptyTime) require.NoError(h, err, "unable to mine empty block") block := h.GetBlock(b.Hash()) @@ -689,43 +551,29 @@ func (h *HarnessMiner) SpawnTempMiner() *HarnessMiner { // Setup a temp miner. tempLogDir := ".tempminerlogs" logFilename := "output-temp_miner.log" - var tempMiner *HarnessMiner - switch h.BackendName() { - case "bitcoind": - tempMiner = NewBitcoindTempMiner( - h.runCtx, h.T, tempLogDir, logFilename, - ) - case "btcd": - tempMiner = NewTempMiner(h.runCtx, h.T, tempLogDir, logFilename) - default: - require.Failf("unknown miner backend", - "backend %s not supported", h.BackendName()) - return nil - } + tempMiner := NewTempMiner(h.runCtx, h.T, tempLogDir, logFilename) // Make sure to clean the miner when the test ends. h.T.Cleanup(tempMiner.Stop) - // Start the miner. - require.NoError(tempMiner.Start(false, 0), "unable to start miner") + // Setup the miner. + require.NoError(tempMiner.SetUp(false, 0), "unable to setup miner") // Connect the temp miner to the original miner. - err := h.backend.ConnectMiner(tempMiner.P2PAddress()) + err := h.Client.Node(btcjson.NConnect, tempMiner.P2PAddress(), &Temp) require.NoError(err, "unable to connect node") // Sync the blocks. - if h.harness != nil && tempMiner.harness != nil { - nodeSlice := []*rpctest.Harness{h.harness, tempMiner.harness} - err = rpctest.JoinNodes(nodeSlice, rpctest.Blocks) - require.NoError(err, "unable to join node on blocks") - } + nodeSlice := []*rpctest.Harness{h.Harness, tempMiner.Harness} + err = rpctest.JoinNodes(nodeSlice, rpctest.Blocks) + require.NoError(err, "unable to join node on blocks") // The two miners should be on the same block height. h.AssertMinerBlockHeightDelta(tempMiner, 0) // Once synced, we now disconnect the temp miner so it'll be // independent from the original miner. - err = h.backend.DisconnectMiner(tempMiner.P2PAddress()) + err = h.Client.Node(btcjson.NDisconnect, tempMiner.P2PAddress(), &Temp) require.NoError(err, "unable to disconnect miners") return tempMiner @@ -736,21 +584,17 @@ func (h *HarnessMiner) ConnectMiner(tempMiner *HarnessMiner) { require := require.New(h.T) // Connect the current miner to the temporary miner. - err := h.backend.ConnectMiner(tempMiner.P2PAddress()) + err := h.Client.Node(btcjson.NConnect, tempMiner.P2PAddress(), &Temp) require.NoError(err, "unable to connect temp miner") - if h.harness != nil && tempMiner.harness != nil { - nodes := []*rpctest.Harness{tempMiner.harness, h.harness} - err = rpctest.JoinNodes(nodes, rpctest.Blocks) - require.NoError(err, "unable to join node on blocks") - } else { - h.AssertMinerBlockHeightDelta(tempMiner, 0) - } + nodes := []*rpctest.Harness{tempMiner.Harness, h.Harness} + err = rpctest.JoinNodes(nodes, rpctest.Blocks) + require.NoError(err, "unable to join node on blocks") } // DisconnectMiner disconnects the miner from the temp miner. func (h *HarnessMiner) DisconnectMiner(tempMiner *HarnessMiner) { - err := h.backend.DisconnectMiner(tempMiner.P2PAddress()) + err := h.Client.Node(btcjson.NDisconnect, tempMiner.P2PAddress(), &Temp) require.NoError(h.T, err, "unable to disconnect temp miner") } @@ -761,13 +605,13 @@ func (h *HarnessMiner) AssertMinerBlockHeightDelta(tempMiner *HarnessMiner, // Ensure the chain lengths are what we expect. err := wait.NoError(func() error { - _, tempMinerHeight, err := tempMiner.backend.GetBestBlock() + _, tempMinerHeight, err := tempMiner.Client.GetBestBlock() if err != nil { return fmt.Errorf("unable to get current "+ "blockheight %v", err) } - _, minerHeight, err := h.backend.GetBestBlock() + _, minerHeight, err := h.Client.GetBestBlock() if err != nil { return fmt.Errorf("unable to get current "+ "blockheight %v", err) @@ -783,20 +627,3 @@ func (h *HarnessMiner) AssertMinerBlockHeightDelta(tempMiner *HarnessMiner, }, wait.DefaultTimeout) require.NoError(h.T, err, "failed to assert block height delta") } - -// P2PAddress returns the P2P address of the miner. -func (h *HarnessMiner) P2PAddress() string { - return h.backend.P2PAddress() -} - -// BackendName returns the name of the backend implementation. -func (h *HarnessMiner) BackendName() string { - return h.backend.Name() -} - -// SendRawTransaction sends a raw transaction with optional high fee allowance. -func (h *HarnessMiner) SendRawTransaction(tx *wire.MsgTx, - allowHighFees bool) (*chainhash.Hash, error) { - - return h.backend.SendRawTransaction(tx, allowHighFees) -} diff --git a/lntest/mock/chainio.go b/lntest/mock/chainio.go index 486d9acbd..4dd0868b2 100644 --- a/lntest/mock/chainio.go +++ b/lntest/mock/chainio.go @@ -1,9 +1,9 @@ package mock import ( - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" ) // ChainIO is a mock implementation of the BlockChainIO interface. diff --git a/lntest/mock/chainnotifier.go b/lntest/mock/chainnotifier.go index a84898f2c..ddce8defa 100644 --- a/lntest/mock/chainnotifier.go +++ b/lntest/mock/chainnotifier.go @@ -1,20 +1,16 @@ package mock import ( - "testing" - "time" - - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" ) // ChainNotifier is a mock implementation of the ChainNotifier interface. type ChainNotifier struct { - SpendChan chan *chainntnfs.SpendDetail - EpochChan chan *chainntnfs.BlockEpoch - ConfChan chan *chainntnfs.TxConfirmation - ConfRegistered chan struct{} + SpendChan chan *chainntnfs.SpendDetail + EpochChan chan *chainntnfs.BlockEpoch + ConfChan chan *chainntnfs.TxConfirmation } // RegisterConfirmationsNtfn returns a ConfirmationEvent that contains a channel @@ -23,14 +19,6 @@ func (c *ChainNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, pkScript []byte, numConfs, heightHint uint32, opts ...chainntnfs.NotifierOption) (*chainntnfs.ConfirmationEvent, error) { - // Signal that a confirmation registration occurred. - if c.ConfRegistered != nil { - select { - case c.ConfRegistered <- struct{}{}: - default: - } - } - return &chainntnfs.ConfirmationEvent{ Confirmed: c.ConfChan, Cancel: func() {}, @@ -73,25 +61,3 @@ func (c *ChainNotifier) Started() bool { func (c *ChainNotifier) Stop() error { return nil } - -// WaitForConfRegistrationAndSend waits for a confirmation registration to -// occur and then sends a confirmation notification. This is a helper function -// for tests that need to ensure the chain watcher has registered for -// confirmations before sending the confirmation. -func (c *ChainNotifier) WaitForConfRegistrationAndSend(t *testing.T) { - t.Helper() - - // Wait for the chain watcher to register for confirmations. - select { - case <-c.ConfRegistered: - case <-time.After(time.Second * 2): - t.Fatalf("timeout waiting for conf registration") - } - - // Send the confirmation to satisfy the confirmation requirement. - select { - case c.ConfChan <- &chainntnfs.TxConfirmation{}: - case <-time.After(time.Second * 1): - t.Fatalf("unable to send confirmation") - } -} diff --git a/lntest/mock/secretkeyring.go b/lntest/mock/secretkeyring.go index 8ee76ad2f..a5a39cc72 100644 --- a/lntest/mock/secretkeyring.go +++ b/lntest/mock/secretkeyring.go @@ -4,8 +4,8 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" "github.com/lightningnetwork/lnd/keychain" ) diff --git a/lntest/mock/signer.go b/lntest/mock/signer.go index 367bc27a8..1d30204ea 100644 --- a/lntest/mock/signer.go +++ b/lntest/mock/signer.go @@ -8,9 +8,9 @@ import ( "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" ) @@ -72,22 +72,6 @@ func (d *DummySigner) MuSig2RegisterNonces(input.MuSig2SessionID, return false, nil } -// MuSig2RegisterCombinedNonce registers a pre-aggregated combined nonce for a -// session identified by its ID. -func (d *DummySigner) MuSig2RegisterCombinedNonce(input.MuSig2SessionID, - [musig2.PubNonceSize]byte) error { - - return nil -} - -// MuSig2GetCombinedNonce retrieves the combined nonce for a session identified -// by its ID. -func (d *DummySigner) MuSig2GetCombinedNonce(input.MuSig2SessionID) ( - [musig2.PubNonceSize]byte, error) { - - return [musig2.PubNonceSize]byte{}, nil -} - // MuSig2Sign creates a partial signature using the local signing key // that was specified when the session was created. This can only be // called when all public nonces of all participants are known and have diff --git a/lntest/mock/spendnotifier.go b/lntest/mock/spendnotifier.go index 2d15a2536..04c861d91 100644 --- a/lntest/mock/spendnotifier.go +++ b/lntest/mock/spendnotifier.go @@ -3,7 +3,7 @@ package mock import ( "sync" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" ) diff --git a/lntest/mock/walletcontroller.go b/lntest/mock/walletcontroller.go index 90a3310dd..fa623bf84 100644 --- a/lntest/mock/walletcontroller.go +++ b/lntest/mock/walletcontroller.go @@ -5,15 +5,13 @@ import ( "sync/atomic" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/btcutil/v2/hdkeychain" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/psbt/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" base "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/wallet/txauthor" @@ -78,28 +76,28 @@ func (w *WalletController) ConfirmedBalance(int32, string) (btcutil.Amount, // NewAddress is called to get new addresses for delivery, change etc. func (w *WalletController) NewAddress(lnwallet.AddressType, bool, - string) (address.Address, error) { + string) (btcutil.Address, error) { - pkh := address.Hash160(w.RootKey.PubKey().SerializeCompressed()) - addr, _ := address.NewAddressPubKeyHash(pkh, &chaincfg.MainNetParams) + pkh := btcutil.Hash160(w.RootKey.PubKey().SerializeCompressed()) + addr, _ := btcutil.NewAddressPubKeyHash(pkh, &chaincfg.MainNetParams) return addr, nil } // LastUnusedAddress currently returns dummy values. func (w *WalletController) LastUnusedAddress(lnwallet.AddressType, - string) (address.Address, error) { + string) (btcutil.Address, error) { return nil, nil } // IsOurAddress currently returns a dummy value. -func (w *WalletController) IsOurAddress(address.Address) bool { +func (w *WalletController) IsOurAddress(btcutil.Address) bool { return false } // AddressInfo currently returns a dummy value. func (w *WalletController) AddressInfo( - address.Address) (waddrmgr.ManagedAddress, error) { + btcutil.Address) (waddrmgr.ManagedAddress, error) { return nil, nil } @@ -126,7 +124,7 @@ func (w *WalletController) ListAddresses(string, // ImportAccount currently returns a dummy value. func (w *WalletController) ImportAccount(string, *hdkeychain.ExtendedKey, uint32, *waddrmgr.AddressType, bool) (*waddrmgr.AccountProperties, - []address.Address, []address.Address, error) { + []btcutil.Address, []btcutil.Address, error) { return nil, nil, nil, nil } @@ -242,18 +240,6 @@ func (w *WalletController) PublishTransaction(tx *wire.MsgTx, _ string) error { return nil } -// SubmitPackage publishes each transaction in the package individually, -// mirroring PublishTransaction. -func (w *WalletController) SubmitPackage(txns []*wire.MsgTx, - _ *chainfee.SatPerVByte) (*btcjson.SubmitPackageResult, error) { - - for _, tx := range txns { - w.PublishedTransactions <- tx - } - - return &btcjson.SubmitPackageResult{}, nil -} - // GetTransactionDetails currently does nothing. func (w *WalletController) GetTransactionDetails( txHash *chainhash.Hash) (*lnwallet.TransactionDetail, error) { diff --git a/lntest/neutrino.go b/lntest/neutrino.go index 055daaa3c..568007097 100644 --- a/lntest/neutrino.go +++ b/lntest/neutrino.go @@ -6,7 +6,7 @@ package lntest import ( "fmt" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" "github.com/lightningnetwork/lnd/lntest/node" ) diff --git a/lntest/node/config.go b/lntest/node/config.go index d6b9cf361..7f3b1df7f 100644 --- a/lntest/node/config.go +++ b/lntest/node/config.go @@ -9,7 +9,7 @@ import ( "path/filepath" "time" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/integration/rpctest" "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/chanbackup" diff --git a/lntest/node/harness_node.go b/lntest/node/harness_node.go index 59080bf7a..62419d687 100644 --- a/lntest/node/harness_node.go +++ b/lntest/node/harness_node.go @@ -15,7 +15,7 @@ import ( "testing" "time" - "github.com/jackc/pgx/v5/pgxpool" + "github.com/jackc/pgx/v4/pgxpool" "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntest/rpc" @@ -949,7 +949,7 @@ func createTempPgDB(ctx context.Context) (string, error) { // executePgQuery executes a SQL statement in a postgres db. func executePgQuery(ctx context.Context, query string) error { - pool, err := pgxpool.New(ctx, postgresDatabaseDsn("postgres")) + pool, err := pgxpool.Connect(ctx, postgresDatabaseDsn("postgres")) if err != nil { return fmt.Errorf("unable to connect to database: %w", err) } diff --git a/lntest/node/state.go b/lntest/node/state.go index eaec3230f..38f02f3a4 100644 --- a/lntest/node/state.go +++ b/lntest/node/state.go @@ -6,7 +6,7 @@ import ( "math" "time" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" diff --git a/lntest/node/watcher.go b/lntest/node/watcher.go index 1f4d08b0f..08f1facfe 100644 --- a/lntest/node/watcher.go +++ b/lntest/node/watcher.go @@ -10,7 +10,7 @@ import ( "sync" "time" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntest/rpc" "github.com/lightningnetwork/lnd/lntest/wait" diff --git a/lntest/rpc/harness_rpc.go b/lntest/rpc/harness_rpc.go index 31de96966..2e08a8494 100644 --- a/lntest/rpc/harness_rpc.go +++ b/lntest/rpc/harness_rpc.go @@ -4,7 +4,7 @@ import ( "context" "testing" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/chainrpc" "github.com/lightningnetwork/lnd/lnrpc/devrpc" diff --git a/lntest/rpc/lnd.go b/lntest/rpc/lnd.go index a9dc742e6..265aab91a 100644 --- a/lntest/rpc/lnd.go +++ b/lntest/rpc/lnd.go @@ -560,6 +560,32 @@ func (h *HarnessRPC) QueryRoutes( return routes } +type SendToRouteClient lnrpc.Lightning_SendToRouteClient + +// SendToRoute makes a RPC call to SendToRoute and asserts. +func (h *HarnessRPC) SendToRoute() SendToRouteClient { + // SendToRoute needs to have the context alive for the entire test case + // as the returned client will be used for send and receive payment + // stream. Thus we use runCtx here instead of a timeout context. + client, err := h.LN.SendToRoute(h.runCtx) + h.NoError(err, "SendToRoute") + + return client +} + +// SendToRouteSync makes a RPC call to SendToRouteSync and asserts. +func (h *HarnessRPC) SendToRouteSync( + req *lnrpc.SendToRouteRequest) *lnrpc.SendResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.LN.SendToRouteSync(ctxt, req) + h.NoError(err, "SendToRouteSync") + + return resp +} + // UpdateChannelPolicy makes a RPC call to UpdateChannelPolicy and asserts. func (h *HarnessRPC) UpdateChannelPolicy( req *lnrpc.PolicyUpdateRequest) *lnrpc.PolicyUpdateResponse { @@ -700,8 +726,6 @@ func (h *HarnessRPC) SubscribeChannelEvents() ChannelEventsClient { type CustomMessageClient lnrpc.Lightning_SubscribeCustomMessagesClient -type OnionMessageClient lnrpc.Lightning_SubscribeOnionMessagesClient - // SubscribeCustomMessages creates a subscription client for custom messages. func (h *HarnessRPC) SubscribeCustomMessages() (CustomMessageClient, context.CancelFunc) { @@ -734,38 +758,6 @@ func (h *HarnessRPC) SendCustomMessage( return resp } -// SendOnionMessage makes a RPC call to the node's SendOnionMessage and -// returns the response. -func (h *HarnessRPC) SendOnionMessage( - req *lnrpc.SendOnionMessageRequest) *lnrpc.SendOnionMessageResponse { - - ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) - defer cancel() - - resp, err := h.LN.SendOnionMessage(ctxt, req) - h.NoError(err, "SendOnionMessage") - - return resp -} - -// SubscribeOnionMessages creates a subscription client for onion messages. -func (h *HarnessRPC) SubscribeOnionMessages() (OnionMessageClient, - context.CancelFunc) { - - ctxt, cancel := context.WithCancel(h.runCtx) - - req := &lnrpc.SubscribeOnionMessagesRequest{} - - // SubscribeCustomMessages needs to have the context alive for the - // entire test case as the returned client will be used for send and - // receive events stream. Thus we use runCtx here instead of a timeout - // context. - stream, err := h.LN.SubscribeOnionMessages(ctxt, req) - h.NoError(err, "SubscribeOnionMessages") - - return stream, cancel -} - // GetChanInfo makes a RPC call to the node's GetChanInfo and returns the // response. func (h *HarnessRPC) GetChanInfo( diff --git a/lntest/rpc/router.go b/lntest/rpc/router.go index d368e6604..ccc7b0ef6 100644 --- a/lntest/rpc/router.go +++ b/lntest/rpc/router.go @@ -283,19 +283,3 @@ func (h *HarnessRPC) TrackPaymentV2(payHash []byte) TrackPaymentClient { return client } - -// DeleteForwardingHistory makes a RPC call to the node's RouterClient and -// asserts. -// -//nolint:ll -func (h *HarnessRPC) DeleteForwardingHistory( - req *routerrpc.DeleteForwardingHistoryRequest) *routerrpc.DeleteForwardingHistoryResponse { - - ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) - defer cancel() - - resp, err := h.Router.DeleteForwardingHistory(ctxt, req) - h.NoError(err, "DeleteForwardingHistory") - - return resp -} diff --git a/lntest/rpc/signer.go b/lntest/rpc/signer.go index 5c3c1f6d0..62b9ac06d 100644 --- a/lntest/rpc/signer.go +++ b/lntest/rpc/signer.go @@ -130,80 +130,6 @@ func (h *HarnessRPC) MuSig2RegisterNonces( return resp } -// MuSig2RegisterNoncesErr makes a RPC call to the node's SignerClient and -// asserts an error is returned. -func (h *HarnessRPC) MuSig2RegisterNoncesErr( - req *signrpc.MuSig2RegisterNoncesRequest) error { - - ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) - defer cancel() - - _, err := h.Signer.MuSig2RegisterNonces(ctxt, req) - require.Error(h, err, "expected error from MuSig2RegisterNonces") - - return err -} - -// MuSig2RegisterCombinedNonce makes a RPC call to the node's SignerClient and -// asserts. -// -//nolint:ll -func (h *HarnessRPC) MuSig2RegisterCombinedNonce( - req *signrpc.MuSig2RegisterCombinedNonceRequest) *signrpc.MuSig2RegisterCombinedNonceResponse { - - ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) - defer cancel() - - resp, err := h.Signer.MuSig2RegisterCombinedNonce(ctxt, req) - h.NoError(err, "MuSig2RegisterCombinedNonce") - - return resp -} - -// MuSig2RegisterCombinedNonceErr makes a RPC call to the node's SignerClient -// and asserts an error is returned. -func (h *HarnessRPC) MuSig2RegisterCombinedNonceErr( - req *signrpc.MuSig2RegisterCombinedNonceRequest) error { - - ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) - defer cancel() - - _, err := h.Signer.MuSig2RegisterCombinedNonce(ctxt, req) - require.Error(h, err, "expected error from MuSig2RegisterCombinedNonce") - - return err -} - -// MuSig2GetCombinedNonce makes a RPC call to the node's SignerClient and -// asserts. -// -//nolint:ll -func (h *HarnessRPC) MuSig2GetCombinedNonce( - req *signrpc.MuSig2GetCombinedNonceRequest) *signrpc.MuSig2GetCombinedNonceResponse { - - ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) - defer cancel() - - resp, err := h.Signer.MuSig2GetCombinedNonce(ctxt, req) - h.NoError(err, "MuSig2GetCombinedNonce") - - return resp -} - -// MuSig2GetCombinedNonceErr makes a RPC call to the node's SignerClient and -// asserts an error is returned. -func (h *HarnessRPC) MuSig2GetCombinedNonceErr( - req *signrpc.MuSig2GetCombinedNonceRequest) error { - - ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) - defer cancel() - - _, err := h.Signer.MuSig2GetCombinedNonce(ctxt, req) - require.Error(h, err, "expected error from MuSig2GetCombinedNonce") - - return err -} - // MuSig2Sign makes a RPC call to the node's SignerClient and asserts. func (h *HarnessRPC) MuSig2Sign( req *signrpc.MuSig2SignRequest) *signrpc.MuSig2SignResponse { diff --git a/lntest/rpc/wallet_kit.go b/lntest/rpc/wallet_kit.go index 1251555c2..ffa5acdd0 100644 --- a/lntest/rpc/wallet_kit.go +++ b/lntest/rpc/wallet_kit.go @@ -208,19 +208,6 @@ func (h *HarnessRPC) PublishTransaction( return resp } -// SubmitPackage makes a RPC call to the node's WalletKitClient and asserts. -func (h *HarnessRPC) SubmitPackage( - req *walletrpc.SubmitPackageRequest) *walletrpc.SubmitPackageResponse { - - ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) - defer cancel() - - resp, err := h.WalletKit.SubmitPackage(ctxt, req) - h.NoError(err, "SubmitPackage") - - return resp -} - // GetTransaction makes a RPC call to the node's WalletKitClient and asserts. func (h *HarnessRPC) GetTransaction( req *walletrpc.GetTransactionRequest) *lnrpc.Transaction { diff --git a/lntest/unittest/backend.go b/lntest/unittest/backend.go index ff7795afc..0c93fdb15 100644 --- a/lntest/unittest/backend.go +++ b/lntest/unittest/backend.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcwallet/chain" @@ -349,7 +349,7 @@ func NewNeutrinoBackend(t *testing.T, netParams *chaincfg.Params, // We'll also wait for the instance to sync up fully to the chain // generated by the btcd instance. - _ = spvNode.Start(t.Context()) + _ = spvNode.Start() for !spvNode.IsCurrent() { time.Sleep(time.Millisecond * 100) } diff --git a/lntest/utils.go b/lntest/utils.go index f2b5b1ccd..ab998ecf8 100644 --- a/lntest/utils.go +++ b/lntest/utils.go @@ -7,9 +7,11 @@ import ( "os" "strconv" "strings" + "time" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntest/wait" @@ -133,8 +135,7 @@ func channelPointStr(chanPoint *lnrpc.ChannelPoint) string { // CommitTypeHasTaproot returns whether commitType is a taproot commitment. func CommitTypeHasTaproot(commitType lnrpc.CommitmentType) bool { switch commitType { - case lnrpc.CommitmentType_SIMPLE_TAPROOT, - lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL: + case lnrpc.CommitmentType_SIMPLE_TAPROOT: return true default: return false @@ -146,7 +147,6 @@ func CommitTypeHasAnchors(commitType lnrpc.CommitmentType) bool { switch commitType { case lnrpc.CommitmentType_ANCHORS, lnrpc.CommitmentType_SIMPLE_TAPROOT, - lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL, lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: return true default: @@ -169,8 +169,7 @@ func NodeArgsForCommitType(commitType lnrpc.CommitmentType) []string { "--protocol.anchors", "--protocol.script-enforced-lease", } - case lnrpc.CommitmentType_SIMPLE_TAPROOT, - lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL: + case lnrpc.CommitmentType_SIMPLE_TAPROOT: return []string{ "--protocol.anchors", "--protocol.simple-taproot-chans", @@ -286,18 +285,33 @@ func CalcStaticFeeBuffer(c lnrpc.CommitmentType, numHTLCs int) btcutil.Amount { return feeBuffer.ToSatoshis() } -// CustomRecordsWithUnaccountable copies the map of custom records and adds an -// accountable signal (replacing in the case of conflict) for assertion in -// tests. -func CustomRecordsWithUnaccountable( +// CustomRecordsWithUnendorsed copies the map of custom records and adds an +// endorsed signal (replacing in the case of conflict) for assertion in tests. +func CustomRecordsWithUnendorsed( originalRecords lnwire.CustomRecords) map[uint64][]byte { + + if !ExperimentalEndorsementActive() { + // Return nil if there are no records, to match wire encoding. + if len(originalRecords) == 0 { + return nil + } + + return originalRecords.Copy() + } + return originalRecords.MergedCopy(map[uint64][]byte{ - uint64(lnwire.ExperimentalAccountableType): { - lnwire.ExperimentalUnaccountable, + uint64(lnwire.ExperimentalEndorsementType): { + lnwire.ExperimentalUnendorsed, }}, ) } +// ExperimentalEndorsementActive returns true if the experimental endorsement +// window is still open. +func ExperimentalEndorsementActive() bool { + return time.Now().Before(lnd.EndorsementExperimentEnd) +} + // LnrpcOutpointToStr returns a string representation of an lnrpc.OutPoint. func LnrpcOutpointToStr(outpoint *lnrpc.OutPoint) string { return fmt.Sprintf("%s:%d", outpoint.TxidStr, outpoint.OutputIndex) diff --git a/lnutils/context.go b/lnutils/context.go deleted file mode 100644 index 5deeb7714..000000000 --- a/lnutils/context.go +++ /dev/null @@ -1,22 +0,0 @@ -package lnutils - -import "context" - -// ContextFromQuit returns a context that is cancelled when the provided quit -// channel is closed. The returned cancel function MUST be called to avoid -// goroutine leaks. -func ContextFromQuit(quit <-chan struct{}) (context.Context, - context.CancelFunc) { - - ctx, cancel := context.WithCancel(context.Background()) - - go func() { - select { - case <-quit: - cancel() - case <-ctx.Done(): - } - }() - - return ctx, cancel -} diff --git a/lnutils/context_test.go b/lnutils/context_test.go deleted file mode 100644 index 85092fbe3..000000000 --- a/lnutils/context_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package lnutils - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -// TestContextFromQuitQuitCancels verifies that closing the quit channel -// cancels the derived context. -func TestContextFromQuitQuitCancels(t *testing.T) { - t.Parallel() - - quit := make(chan struct{}) - ctx, cancel := ContextFromQuit(quit) - defer cancel() - - // The context should not be done yet. - select { - case <-ctx.Done(): - t.Fatal("context cancelled before quit was closed") - default: - } - - // Closing the quit channel should cancel the context. - close(quit) - - select { - case <-ctx.Done(): - case <-time.After(time.Second): - t.Fatal("context was not cancelled after quit was closed") - } - - require.ErrorIs(t, ctx.Err(), context.Canceled) -} - -// TestContextFromQuitCancelCleansUp verifies that calling the returned cancel -// function cancels the context and allows the internal goroutine to exit -// cleanly, preventing a goroutine leak. -func TestContextFromQuitCancelCleansUp(t *testing.T) { - t.Parallel() - - // Use a quit channel that is never closed to ensure the goroutine - // exits via the cancel path, not the quit path. - quit := make(chan struct{}) - ctx, cancel := ContextFromQuit(quit) - - // The context should not be done yet. - select { - case <-ctx.Done(): - t.Fatal("context cancelled before cancel was called") - default: - } - - // Calling cancel should cancel the context. The internal goroutine - // exits via <-ctx.Done(). - cancel() - - select { - case <-ctx.Done(): - case <-time.After(time.Second): - t.Fatal("context was not cancelled after cancel() was called") - } - - require.ErrorIs(t, ctx.Err(), context.Canceled) -} diff --git a/lnutils/fs_test.go b/lnutils/fs_test.go index c23250441..3e96d4faf 100644 --- a/lnutils/fs_test.go +++ b/lnutils/fs_test.go @@ -66,6 +66,7 @@ func TestCreateDir(t *testing.T) { } for _, tc := range tests { + tc := tc t.Run(tc.name, func(t *testing.T) { dir := tc.setup() defer os.RemoveAll(dir) diff --git a/lnutils/sync_map.go b/lnutils/sync_map.go index 1ffd6135e..881572885 100644 --- a/lnutils/sync_map.go +++ b/lnutils/sync_map.go @@ -96,24 +96,3 @@ func (m *SyncMap[K, V]) LoadOrStore(key K, value V) (V, bool) { return item, loaded } - -// Swap stores value for the given key and returns the previously stored -// value (if any). The second return value reports whether a previous -// value was present. It is a thin typed wrapper around sync.Map.Swap so -// callers that need to atomically read-modify-write a map entry — for -// example, to update an atomic counter that shadows the map's -// membership — can do so without dropping down to untyped interface{} -// assertions. -func (m *SyncMap[K, V]) Swap(key K, value V) (V, bool) { - prev, loaded := m.Map.Swap(key, value) - if !loaded { - return *new(V), false - } - - item, ok := prev.(V) - if !ok { - return *new(V), false - } - - return item, true -} diff --git a/lnutils/sync_map_test.go b/lnutils/sync_map_test.go index e672e309e..948d38424 100644 --- a/lnutils/sync_map_test.go +++ b/lnutils/sync_map_test.go @@ -202,46 +202,3 @@ func TestSyncMapLoadOrStore(t *testing.T) { require.True(t, loaded) require.Equal(t, "two", item) } - -// TestSyncMapSwap tests the Swap method of the SyncMap type. -func TestSyncMapSwap(t *testing.T) { - t.Parallel() - - // Create a new SyncMap of string keys and integer values. - m := &lnutils.SyncMap[string, int]{} - - // Swapping into an empty key should store the value and report no - // previous entry. - prev, loaded := m.Swap("foo", 42) - require.False(t, loaded) - require.Equal(t, 0, prev) - - // The value should now be retrievable via Load. - value, ok := m.Load("foo") - require.True(t, ok) - require.Equal(t, 42, value) - - // Swapping an existing key should return the previous value and - // report that it was present. - prev, loaded = m.Swap("foo", 99) - require.True(t, loaded) - require.Equal(t, 42, prev) - - // Load should now return the new value. - value, ok = m.Load("foo") - require.True(t, ok) - require.Equal(t, 99, value) - - // Swapping a second key should not affect the first. - prev, loaded = m.Swap("bar", 7) - require.False(t, loaded) - require.Equal(t, 0, prev) - - value, ok = m.Load("foo") - require.True(t, ok) - require.Equal(t, 99, value) - - value, ok = m.Load("bar") - require.True(t, ok) - require.Equal(t, 7, value) -} diff --git a/lnwallet/aux_leaf_store.go b/lnwallet/aux_leaf_store.go index e4c22335f..0a8505037 100644 --- a/lnwallet/aux_leaf_store.go +++ b/lnwallet/aux_leaf_store.go @@ -1,11 +1,10 @@ package lnwallet import ( - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntypes" @@ -56,7 +55,7 @@ type CommitAuxLeaves struct { } // AuxChanState is a struct that holds certain fields of the -// chanstate.OpenChannel struct that are used by the aux components. The data +// channeldb.OpenChannel struct that are used by the aux components. The data // is copied over to prevent accidental mutation of the original channel state. type AuxChanState struct { // ChanType denotes which type of channel this is. @@ -111,7 +110,7 @@ type AuxChanState struct { } // NewAuxChanState creates a new AuxChanState from the given channel state. -func NewAuxChanState(chanState *chanstate.OpenChannel) AuxChanState { +func NewAuxChanState(chanState *channeldb.OpenChannel) AuxChanState { peerPub := chanState.IdentityPub.SerializeCompressed() return AuxChanState{ @@ -203,7 +202,7 @@ type AuxLeafStore interface { // auxLeavesFromView is used to derive the set of commit aux leaves (if any), // that are needed to create a new commitment transaction using the original // (unfiltered) htlc view. -func auxLeavesFromView(leafStore AuxLeafStore, chanState *chanstate.OpenChannel, +func auxLeavesFromView(leafStore AuxLeafStore, chanState *channeldb.OpenChannel, prevBlob fn.Option[tlv.Blob], originalView *HtlcView, whoseCommit lntypes.ChannelParty, ourBalance, theirBalance lnwire.MilliSatoshi, @@ -226,7 +225,7 @@ func auxLeavesFromView(leafStore AuxLeafStore, chanState *chanstate.OpenChannel, // updateAuxBlob is a helper function that attempts to update the aux blob // given the prior and current state information. -func updateAuxBlob(leafStore AuxLeafStore, chanState *chanstate.OpenChannel, +func updateAuxBlob(leafStore AuxLeafStore, chanState *channeldb.OpenChannel, prevBlob fn.Option[tlv.Blob], nextViewUnfiltered *HtlcView, whoseCommit lntypes.ChannelParty, ourBalance, theirBalance lnwire.MilliSatoshi, diff --git a/lnwallet/aux_resolutions.go b/lnwallet/aux_resolutions.go index 8891da7d6..14802c57c 100644 --- a/lnwallet/aux_resolutions.go +++ b/lnwallet/aux_resolutions.go @@ -1,8 +1,8 @@ package lnwallet import ( - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" diff --git a/lnwallet/aux_signer.go b/lnwallet/aux_signer.go index 38f540454..79a7ca1dc 100644 --- a/lnwallet/aux_signer.go +++ b/lnwallet/aux_signer.go @@ -1,7 +1,7 @@ package lnwallet import ( - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntypes" diff --git a/lnwallet/aux_test_utils.go b/lnwallet/aux_test_utils.go deleted file mode 100644 index 1e76a522e..000000000 --- a/lnwallet/aux_test_utils.go +++ /dev/null @@ -1,31 +0,0 @@ -package lnwallet - -import ( - "github.com/lightningnetwork/lnd/lnwire" -) - -// NewTestAuxHtlcDescriptor creates an AuxHtlcDescriptor for testing purposes. -// This function allows tests to create descriptors with specific commit heights -// and entry types, which are normally unexported fields. -func NewTestAuxHtlcDescriptor(chanID lnwire.ChannelID, - rHash PaymentHash, timeout uint32, - amount lnwire.MilliSatoshi, htlcIndex, parentIndex uint64, - entryType uint8, customRecords lnwire.CustomRecords, - addHeightLocal, addHeightRemote, removeHeightLocal, - removeHeightRemote uint64) AuxHtlcDescriptor { - - return AuxHtlcDescriptor{ - ChanID: chanID, - RHash: rHash, - Timeout: timeout, - Amount: amount, - HtlcIndex: htlcIndex, - ParentIndex: parentIndex, - EntryType: updateType(entryType), - CustomRecords: customRecords, - addCommitHeightLocal: addHeightLocal, - addCommitHeightRemote: addHeightRemote, - removeCommitHeightLocal: removeHeightLocal, - removeCommitHeightRemote: removeHeightRemote, - } -} diff --git a/lnwallet/btcwallet/blockchain.go b/lnwallet/btcwallet/blockchain.go index f5abda372..25b51d5e0 100644 --- a/lnwallet/btcwallet/blockchain.go +++ b/lnwallet/btcwallet/blockchain.go @@ -5,9 +5,9 @@ import ( "errors" "fmt" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/chain" "github.com/lightninglabs/neutrino" "github.com/lightninglabs/neutrino/headerfs" diff --git a/lnwallet/btcwallet/btcwallet.go b/lnwallet/btcwallet/btcwallet.go index ecb7b57e0..a29139dba 100644 --- a/lnwallet/btcwallet/btcwallet.go +++ b/lnwallet/btcwallet/btcwallet.go @@ -2,7 +2,6 @@ package btcwallet import ( "bytes" - "context" "encoding/hex" "errors" "fmt" @@ -10,16 +9,14 @@ import ( "sync" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/btcutil/v2/hdkeychain" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/waddrmgr" base "github.com/btcsuite/btcwallet/wallet" @@ -385,7 +382,7 @@ func (b *BtcWallet) Start() error { // Establish an RPC connection in addition to starting the goroutines // in the underlying wallet. - if err := b.chain.Start(context.Background()); err != nil { + if err := b.chain.Start(); err != nil { return err } @@ -483,7 +480,7 @@ func (b *BtcWallet) keyScopeForAccountAddr(accountName string, // // This is a part of the WalletController interface. func (b *BtcWallet) NewAddress(t lnwallet.AddressType, change bool, - accountName string) (address.Address, error) { + accountName string) (btcutil.Address, error) { // Addresses cannot be derived from the catch-all imported accounts. if accountName == waddrmgr.ImportedAddrAccountName { @@ -509,7 +506,7 @@ func (b *BtcWallet) NewAddress(t lnwallet.AddressType, change bool, // change address. The account parameter must be non-empty as it determines // which account the address should be generated from. func (b *BtcWallet) LastUnusedAddress(addrType lnwallet.AddressType, - accountName string) (address.Address, error) { + accountName string) (btcutil.Address, error) { // Addresses cannot be derived from the catch-all imported accounts. if accountName == waddrmgr.ImportedAddrAccountName { @@ -527,7 +524,7 @@ func (b *BtcWallet) LastUnusedAddress(addrType lnwallet.AddressType, // IsOurAddress checks if the passed address belongs to this wallet // // This is a part of the WalletController interface. -func (b *BtcWallet) IsOurAddress(a address.Address) bool { +func (b *BtcWallet) IsOurAddress(a btcutil.Address) bool { result, err := b.wallet.HaveAddress(a) return result && (err == nil) } @@ -536,7 +533,7 @@ func (b *BtcWallet) IsOurAddress(a address.Address) bool { // wallet. // // NOTE: This is a part of the WalletController interface. -func (b *BtcWallet) AddressInfo(a address.Address) (waddrmgr.ManagedAddress, +func (b *BtcWallet) AddressInfo(a btcutil.Address) (waddrmgr.ManagedAddress, error) { return b.wallet.AddressInfo(a) @@ -620,6 +617,7 @@ func (b *BtcWallet) ListAccounts(name string, return nil, err } for _, account := range accounts.Accounts { + account := account res = append(res, &account.AccountProperties) } @@ -632,6 +630,7 @@ func (b *BtcWallet) ListAccounts(name string, return nil, err } for _, account := range accounts.Accounts { + account := account res = append(res, &account.AccountProperties) } } @@ -644,6 +643,7 @@ func (b *BtcWallet) ListAccounts(name string, return nil, err } for _, account := range accounts.Accounts { + account := account res = append(res, &account.AccountProperties) } } @@ -809,8 +809,8 @@ func (b *BtcWallet) ListAddresses(name string, // This is a part of the WalletController interface. func (b *BtcWallet) ImportAccount(name string, accountPubKey *hdkeychain.ExtendedKey, masterKeyFingerprint uint32, addrType *waddrmgr.AddressType, - dryRun bool) (*waddrmgr.AccountProperties, []address.Address, - []address.Address, error) { + dryRun bool) (*waddrmgr.AccountProperties, []btcutil.Address, + []btcutil.Address, error) { // For custom accounts, we first check if there is no existing account // with the same name. @@ -849,12 +849,12 @@ func (b *BtcWallet) ImportAccount(name string, accountPubKey *hdkeychain.Extende return nil, nil, nil, err } - externalAddrs := make([]address.Address, len(extAddrs)) + externalAddrs := make([]btcutil.Address, len(extAddrs)) for i := 0; i < len(extAddrs); i++ { externalAddrs[i] = extAddrs[i].Address() } - internalAddrs := make([]address.Address, len(intAddrs)) + internalAddrs := make([]btcutil.Address, len(intAddrs)) for i := 0; i < len(intAddrs); i++ { internalAddrs[i] = intAddrs[i].Address() } @@ -1232,91 +1232,6 @@ func (b *BtcWallet) PublishTransaction(tx *wire.MsgTx, label string) error { return mapRpcclientError(err) } -// neutrinoBroadcastMsg is the PackageMsg returned by the neutrino best-effort -// path. It is deliberately not "success": a neutrino light client has no -// mempool, so it cannot confirm the package was accepted. It broadcasts the -// transactions and reports them as broadcast-but-unverified, which callers -// must treat as an unverified relay attempt, not a package-accept verdict. -const neutrinoBroadcastMsg = "broadcast-unverified" - -// SubmitPackage submits a package of related transactions (topologically -// sorted, parents first and child last) for atomic validation and acceptance. -// -// Only the bitcoind backend performs real package submission, via the node's -// submitpackage RPC, which lets a zero-fee v3/TRUC parent be accepted via its -// fee-paying CPFP child (which sendrawtransaction rejects on its own). The -// btcd backend has no submitpackage handler and returns ErrUnimplemented. -// -// A neutrino light client has no mempool and cannot validate or atomically -// accept a package. As a best effort it broadcasts each transaction -// individually over the P2P network and relies on a peer's 1p1c package relay -// to assemble them. The returned PackageMsg is deliberately not "success": a -// light client cannot confirm acceptance, so callers must treat the result as -// an unverified broadcast rather than a package-accept verdict. -func (b *BtcWallet) SubmitPackage(txns []*wire.MsgTx, - maxFeeRate *chainfee.SatPerVByte) (*btcjson.SubmitPackageResult, - error) { - - if b.chain.BackEnd() == "neutrino" { - // The best-effort neutrino broadcast goes through plain - // SendRawTransaction, which cannot enforce a fee-rate ceiling, - // so reject a caller-provided limit rather than silently - // ignoring it and giving a false sense of protection. - if maxFeeRate != nil { - return nil, fmt.Errorf("max fee rate is not " + - "supported for neutrino package broadcast") - } - - for i, tx := range txns { - if err := b.PublishTransaction(tx, ""); err != nil { - return nil, fmt.Errorf("unable to "+ - "broadcast package tx %d (%v): %w", - i, tx.TxHash(), err) - } - } - - results := make( - map[string]btcjson.SubmitPackageTxResult, len(txns), - ) - for _, tx := range txns { - results[tx.WitnessHash().String()] = - btcjson.SubmitPackageTxResult{TxID: tx.TxHash()} - } - - return &btcjson.SubmitPackageResult{ - PackageMsg: neutrinoBroadcastMsg, - TxResults: results, - }, nil - } - - // bitcoind's submitpackage maxfeerate is expressed in BTC/kvB, so map - // the optional sat/vByte ceiling onto it. A nil ceiling leaves the node - // default unchanged; an explicit 0 disables the limit. - var maxFeeRateBTCPerKvB *float64 - if maxFeeRate != nil { - btcPerKvB := satPerVByteToBTCPerKvB(*maxFeeRate) - maxFeeRateBTCPerKvB = &btcPerKvB - } - - return b.chain.SubmitPackage(txns, maxFeeRateBTCPerKvB) -} - -// vBytesPerKvB is the number of virtual bytes in a kilo-virtual-byte, used to -// convert a sat/vByte fee rate into the per-kvB unit bitcoind expects. -const vBytesPerKvB = 1000 - -// satPerVByteToBTCPerKvB converts a sat/vByte fee rate into the BTC/kvB unit -// expected by bitcoind's submitpackage maxfeerate argument: 1 sat/vByte is -// 1000 sat/kvB, and SatoshiPerBitcoin sats make a BTC, so -// BTC/kvB = sat/vByte * 1000 / SatoshiPerBitcoin. -// -// NOTE: the sat/vByte input is integer, so only whole-sat/vByte ceilings are -// expressible, and very large values lose precision once the float64 product -// exceeds 2^53. -func satPerVByteToBTCPerKvB(rate chainfee.SatPerVByte) float64 { - return float64(rate) * vBytesPerKvB / btcutil.SatoshiPerBitcoin -} - // LabelTransaction adds a label to a transaction. If the tx already // has a label, this call will fail unless the overwrite parameter // is set. Labels must not be empty, and they are limited to 500 chars. @@ -1442,7 +1357,7 @@ func minedTransactionsToDetails( var outputDetails []lnwallet.OutputDetail for i, txOut := range wireTx.TxOut { - var addresses []address.Address + var addresses []btcutil.Address sc, outAddresses, _, err := txscript.ExtractPkScriptAddrs( txOut.PkScript, chainParams, ) @@ -1514,7 +1429,7 @@ func unminedTransactionsToDetail( var outputDetails []lnwallet.OutputDetail for i, txOut := range wireTx.TxOut { - var addresses []address.Address + var addresses []btcutil.Address sc, outAddresses, _, err := txscript.ExtractPkScriptAddrs( txOut.PkScript, chainParams, ) diff --git a/lnwallet/btcwallet/btcwallet_test.go b/lnwallet/btcwallet/btcwallet_test.go index fd7c4b02c..c5bd8905a 100644 --- a/lnwallet/btcwallet/btcwallet_test.go +++ b/lnwallet/btcwallet/btcwallet_test.go @@ -5,7 +5,7 @@ import ( "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/wallet" "github.com/lightningnetwork/lnd/lnmock" diff --git a/lnwallet/btcwallet/config.go b/lnwallet/btcwallet/config.go index 15c717a11..45abdafce 100644 --- a/lnwallet/btcwallet/config.go +++ b/lnwallet/btcwallet/config.go @@ -4,8 +4,8 @@ import ( "path/filepath" "time" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/wallet" ) diff --git a/lnwallet/btcwallet/psbt.go b/lnwallet/btcwallet/psbt.go index df41336ec..ec88cd92f 100644 --- a/lnwallet/btcwallet/psbt.go +++ b/lnwallet/btcwallet/psbt.go @@ -8,11 +8,11 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/btcutil/v2/hdkeychain" - "github.com/btcsuite/btcd/psbt/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/wtxmgr" diff --git a/lnwallet/btcwallet/psbt_test.go b/lnwallet/btcwallet/psbt_test.go index fe2325126..694a8c04f 100644 --- a/lnwallet/btcwallet/psbt_test.go +++ b/lnwallet/btcwallet/psbt_test.go @@ -7,13 +7,13 @@ import ( "fmt" "testing" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/psbt/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -100,26 +100,26 @@ func (i testInputType) output(t *testing.T, privKey *btcec.PrivateKey) (*wire.TxOut, []byte) { var ( - addr address.Address + addr btcutil.Address witnessScript []byte err error ) switch i { case plainP2WKH: - h := address.Hash160(privKey.PubKey().SerializeCompressed()) - addr, err = address.NewAddressWitnessPubKeyHash(h, netParams) + h := btcutil.Hash160(privKey.PubKey().SerializeCompressed()) + addr, err = btcutil.NewAddressWitnessPubKeyHash(h, netParams) require.NoError(t, err) case tweakedP2WKH: privKey = input.TweakPrivKey(privKey, testTweakSingle) - h := address.Hash160(privKey.PubKey().SerializeCompressed()) - addr, err = address.NewAddressWitnessPubKeyHash(h, netParams) + h := btcutil.Hash160(privKey.PubKey().SerializeCompressed()) + addr, err = btcutil.NewAddressWitnessPubKeyHash(h, netParams) require.NoError(t, err) case nestedP2WKH: - h := address.Hash160(privKey.PubKey().SerializeCompressed()) - witnessAddr, err := address.NewAddressWitnessPubKeyHash( + h := btcutil.Hash160(privKey.PubKey().SerializeCompressed()) + witnessAddr, err := btcutil.NewAddressWitnessPubKeyHash( h, netParams, ) require.NoError(t, err) @@ -127,7 +127,7 @@ func (i testInputType) output(t *testing.T, witnessProgram, err := txscript.PayToAddrScript(witnessAddr) require.NoError(t, err) - addr, err = address.NewAddressScriptHash( + addr, err = btcutil.NewAddressScriptHash( witnessProgram, netParams, ) require.NoError(t, err) @@ -145,7 +145,7 @@ func (i testInputType) output(t *testing.T, require.NoError(t, err) h := sha256.Sum256(witnessScript) - addr, err = address.NewAddressWitnessScriptHash(h[:], netParams) + addr, err = btcutil.NewAddressWitnessScriptHash(h[:], netParams) require.NoError(t, err) case singleKeyDoubleTweakedP2WSH: @@ -163,7 +163,7 @@ func (i testInputType) output(t *testing.T, require.NoError(t, err) h := sha256.Sum256(witnessScript) - addr, err = address.NewAddressWitnessScriptHash(h[:], netParams) + addr, err = btcutil.NewAddressWitnessScriptHash(h[:], netParams) require.NoError(t, err) default: @@ -189,8 +189,8 @@ func (i testInputType) decorateInput(t *testing.T, privKey *btcec.PrivateKey, }} case nestedP2WKH: - h := address.Hash160(privKey.PubKey().SerializeCompressed()) - witnessAddr, err := address.NewAddressWitnessPubKeyHash( + h := btcutil.Hash160(privKey.PubKey().SerializeCompressed()) + witnessAddr, err := btcutil.NewAddressWitnessPubKeyHash( h, netParams, ) require.NoError(t, err) @@ -277,6 +277,7 @@ func TestSignPsbt(t *testing.T) { }} for _, tc := range testCases { + tc := tc // This is the private key we're going to sign with. privKey, err := w.deriveKeyByBIP32Path(tc.inputType.keyPath()) @@ -464,6 +465,7 @@ func TestEstimateInputWeight(t *testing.T) { input.WitnessHeaderSize for _, tc := range testCases { + tc := tc t.Run(tc.name, func(tt *testing.T) { estimator := input.TxWeightEstimator{} @@ -549,6 +551,7 @@ func TestBip32DerivationFromKeyDesc(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(tt *testing.T) { d, trD, path := Bip32DerivationFromKeyDesc( @@ -604,6 +607,7 @@ func TestBip32DerivationFromAddress(t *testing.T) { w, _ := newTestWallet(t, netParams, seedBytes) for _, tc := range testCases { + tc := tc addr, err := w.NewAddress( tc.addrType, false, lnwallet.DefaultAccountName, diff --git a/lnwallet/btcwallet/signer.go b/lnwallet/btcwallet/signer.go index d26a38394..69f7ab609 100644 --- a/lnwallet/btcwallet/signer.go +++ b/lnwallet/btcwallet/signer.go @@ -3,16 +3,15 @@ package btcwallet import ( "fmt" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/btcutil/v2/hdkeychain" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/psbt/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -211,8 +210,8 @@ func (b *BtcWallet) fetchPrivKey( return b.deriveKeyByLocator(keyDesc.KeyLocator) } - hash160 := address.Hash160(keyDesc.PubKey.SerializeCompressed()) - addr, err := address.NewAddressWitnessPubKeyHash(hash160, b.netParams) + hash160 := btcutil.Hash160(keyDesc.PubKey.SerializeCompressed()) + addr, err := btcutil.NewAddressWitnessPubKeyHash(hash160, b.netParams) if err != nil { return nil, err } @@ -239,32 +238,20 @@ func (b *BtcWallet) fetchPrivKey( // maybeTweakPrivKey examines the single and double tweak parameters on the // passed sign descriptor and may perform a mapping on the passed private key -// in order to utilize the tweaks, if populated. If both tweak parameters are -// set, then both are applied in the following order: -// -// a) double tweak -// b) single tweak +// in order to utilize the tweaks, if populated. func maybeTweakPrivKey(signDesc *input.SignDescriptor, privKey *btcec.PrivateKey) (*btcec.PrivateKey, error) { var retPriv *btcec.PrivateKey - switch { - // If both tweak parameters are set, apply the double tweak first - // (revocation), then the single tweak (HTLC index). - case signDesc.DoubleTweak != nil && signDesc.SingleTweak != nil: - retPriv = input.DeriveRevocationPrivKey( - privKey, signDesc.DoubleTweak, - ) - retPriv = input.TweakPrivKey(retPriv, signDesc.SingleTweak) case signDesc.SingleTweak != nil: - retPriv = input.TweakPrivKey(privKey, signDesc.SingleTweak) + retPriv = input.TweakPrivKey(privKey, + signDesc.SingleTweak) case signDesc.DoubleTweak != nil: - retPriv = input.DeriveRevocationPrivKey( - privKey, signDesc.DoubleTweak, - ) + retPriv = input.DeriveRevocationPrivKey(privKey, + signDesc.DoubleTweak) default: retPriv = privKey diff --git a/lnwallet/btcwallet/signer_test.go b/lnwallet/btcwallet/signer_test.go index a1cbdb758..e45211d82 100644 --- a/lnwallet/btcwallet/signer_test.go +++ b/lnwallet/btcwallet/signer_test.go @@ -1,23 +1,20 @@ package btcwallet import ( - "crypto/sha256" "encoding/hex" "fmt" "math" "testing" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/btcutil/v2/hdkeychain" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/integration/rpctest" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/lightningnetwork/lnd/blockcache" @@ -189,6 +186,7 @@ func TestBip32KeyDerivation(t *testing.T) { // Let's go through the test cases now that we know our wallet is ready. for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { privKey, err := w.deriveKeyByBIP32Path(tc.path) @@ -230,7 +228,7 @@ func TestScriptImport(t *testing.T) { builder := txscript.NewScriptBuilder() builder.AddOp(txscript.OP_DUP) builder.AddOp(txscript.OP_HASH160) - builder.AddData(address.Hash160([]byte("foobar"))) + builder.AddData(btcutil.Hash160([]byte("foobar"))) builder.AddOp(txscript.OP_EQUALVERIFY) script1, err := builder.Script() require.NoError(t, err) @@ -352,181 +350,3 @@ func getChainBackend(t *testing.T, netParams *chaincfg.Params) (chain.Interface, func hardenedKey(part uint32) uint32 { return part + hdkeychain.HardenedKeyStart } - -// TestMaybeTweakPrivKey tests the maybeTweakPrivKey function to ensure it -// correctly applies single tweaks, double tweaks, both tweaks combined, and -// handles the case where no tweaks are applied. -func TestMaybeTweakPrivKey(t *testing.T) { - // Create a test private key. - privKeyBytes, err := hex.DecodeString( - "e68abc8e2a7a5b9f0e4a3c7d8b9e6f5" + - "a4b3c2d1e0f9e8d7c6b5a4938271605", - ) - require.NoError(t, err) - privKey, pubKey := btcec.PrivKeyFromBytes(privKeyBytes) - - // Create test tweak values. - singleTweakBytes, err := hex.DecodeString( - "1234567890abcdef1234567890abcdef" + - "1234567890abcdef1234567890abcdef", - ) - require.NoError(t, err) - - doubleTweakBytes, err := hex.DecodeString( - "fedcba0987654321fedcba0987654321" + - "fedcba0987654321fedcba0987654321", - ) - require.NoError(t, err) - doubleTweak, _ := btcec.PrivKeyFromBytes(doubleTweakBytes) - - testCases := []struct { - name string - singleTweak []byte - doubleTweak *btcec.PrivateKey - validate func(*testing.T, *btcec.PrivateKey, - *btcec.PublicKey) - }{ - { - name: "no tweaks applied", - singleTweak: nil, - doubleTweak: nil, - validate: func(t *testing.T, result *btcec.PrivateKey, - _ *btcec.PublicKey) { - - // Should return the original private key - // unchanged. - require.Equal( - t, privKey.Serialize(), - result.Serialize(), - "expected private key to be unchanged", - ) - }, - }, - { - name: "single tweak only", - singleTweak: singleTweakBytes, - doubleTweak: nil, - validate: func(t *testing.T, result *btcec.PrivateKey, - _ *btcec.PublicKey) { - - // Manually apply single tweak to verify. - expected := input.TweakPrivKey( - privKey, singleTweakBytes, - ) - require.Equal( - t, expected.Serialize(), - result.Serialize(), - "single tweak not applied correctly", - ) - // Ensure it's different from the original. - require.NotEqual( - t, privKey.Serialize(), - result.Serialize(), - "tweaked key should differ from "+ - "original", - ) - }, - }, - { - name: "double tweak only", - singleTweak: nil, - doubleTweak: doubleTweak, - validate: func(t *testing.T, result *btcec.PrivateKey, - _ *btcec.PublicKey) { - - // Manually apply double tweak to verify. - expected := input.DeriveRevocationPrivKey( - privKey, doubleTweak, - ) - require.Equal( - t, expected.Serialize(), - result.Serialize(), - "double tweak not applied correctly", - ) - // Ensure it's different from the original. - require.NotEqual( - t, privKey.Serialize(), - result.Serialize(), - "tweaked key should differ from "+ - "original", - ) - }, - }, - { - name: "both tweaks combined", - singleTweak: singleTweakBytes, - doubleTweak: doubleTweak, - validate: func(t *testing.T, result *btcec.PrivateKey, - origPubKey *btcec.PublicKey) { - - // Manually apply both tweaks in order: double - // first, then single. - afterDouble := input.DeriveRevocationPrivKey( - privKey, doubleTweak, - ) - expected := input.TweakPrivKey( - afterDouble, singleTweakBytes, - ) - require.Equal( - t, expected.Serialize(), - result.Serialize(), - "combined tweaks not applied correctly", - ) - // Ensure it's different from single tweak only. - singleOnly := input.TweakPrivKey( - privKey, singleTweakBytes, - ) - require.NotEqual( - t, singleOnly.Serialize(), - result.Serialize(), - "combined tweak should differ from "+ - "single tweak only", - ) - - // Calculate the expected tweaked public key by - // applying the same tweaks to the original - // public key. - doubleTweakPub := doubleTweak.PubKey() - afterDoublePub := input.DeriveRevocationPubkey( - origPubKey, doubleTweakPub, - ) - finalPK := input.TweakPubKeyWithTweak( - afterDoublePub, singleTweakBytes, - ) - - // Verify that the tweaked private key can sign - // for the expected tweaked public key. - testMsg := []byte("test message for signing") - msgHash := sha256.Sum256(testMsg) - signature := ecdsa.Sign(result, msgHash[:]) - - // Verify the signature with the tweaked public - // key. - require.True( - t, - signature.Verify(msgHash[:], finalPK), - "signature verification failed for "+ - "combined tweaked key", - ) - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Create a sign descriptor with the test tweaks. - signDesc := &input.SignDescriptor{ - SingleTweak: tc.singleTweak, - DoubleTweak: tc.doubleTweak, - } - - // Call the function under test. - result, err := maybeTweakPrivKey(signDesc, privKey) - require.NoError(t, err) - require.NotNil(t, result) - - // Validate the result. - tc.validate(t, result, pubKey) - }) - } -} diff --git a/lnwallet/btcwallet/submitpackage_test.go b/lnwallet/btcwallet/submitpackage_test.go deleted file mode 100644 index 63286f011..000000000 --- a/lnwallet/btcwallet/submitpackage_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package btcwallet - -import ( - "testing" - - "github.com/lightningnetwork/lnd/lnwallet/chainfee" - "github.com/stretchr/testify/require" -) - -// TestSatPerVByteToBTCPerKvB checks the sat/vByte -> BTC/kvB conversion used to -// map an lnd fee-rate ceiling onto bitcoind's submitpackage maxfeerate -// argument. A regression here would silently relax or tighten the user's fee -// ceiling, so the known reference points are pinned. -func TestSatPerVByteToBTCPerKvB(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - rate chainfee.SatPerVByte - want float64 - }{ - {name: "zero", rate: 0, want: 0}, - {name: "1 sat/vByte", rate: 1, want: 0.00001}, - {name: "10 sat/vByte", rate: 10, want: 0.0001}, - {name: "250 sat/vByte", rate: 250, want: 0.0025}, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - require.InDelta( - t, tc.want, satPerVByteToBTCPerKvB(tc.rate), - 1e-12, - ) - }) - } -} diff --git a/lnwallet/chainfee/estimator.go b/lnwallet/chainfee/estimator.go index 5f8cb3254..f83cce285 100644 --- a/lnwallet/chainfee/estimator.go +++ b/lnwallet/chainfee/estimator.go @@ -13,7 +13,7 @@ import ( "sync/atomic" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/rpcclient" "github.com/lightningnetwork/lnd/lnutils" ) diff --git a/lnwallet/chainfee/estimator_test.go b/lnwallet/chainfee/estimator_test.go index fa37712e1..3d355d503 100644 --- a/lnwallet/chainfee/estimator_test.go +++ b/lnwallet/chainfee/estimator_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/stretchr/testify/require" ) @@ -261,6 +261,7 @@ func TestWebAPIFeeEstimator(t *testing.T) { require.NoError(t, estimator.Start(), "unable to start fee estimator") for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { est, err := estimator.EstimateFeePerKW(tc.target) @@ -360,6 +361,7 @@ func TestGetCachedFee(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { cachedFee, err := estimator.getCachedFee(tc.confTarget) diff --git a/lnwallet/chainfee/filtermanager.go b/lnwallet/chainfee/filtermanager.go index dfed4bcf0..2d6fd0a2e 100644 --- a/lnwallet/chainfee/filtermanager.go +++ b/lnwallet/chainfee/filtermanager.go @@ -7,7 +7,7 @@ import ( "sync" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/rpcclient" "github.com/lightningnetwork/lnd/fn/v2" ) diff --git a/lnwallet/chainfee/filtermanager_test.go b/lnwallet/chainfee/filtermanager_test.go index 0271c81e7..085814d96 100644 --- a/lnwallet/chainfee/filtermanager_test.go +++ b/lnwallet/chainfee/filtermanager_test.go @@ -35,6 +35,7 @@ func TestFeeFilterMedian(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { cb := func() ([]SatPerKWeight, error) { return nil, nil diff --git a/lnwallet/chainfee/rates.go b/lnwallet/chainfee/rates.go index 8763a9781..735cbd20c 100644 --- a/lnwallet/chainfee/rates.go +++ b/lnwallet/chainfee/rates.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lntypes" ) diff --git a/lnwallet/chancloser/aux_closer.go b/lnwallet/chancloser/aux_closer.go index 5ee01d4ae..62f475dd4 100644 --- a/lnwallet/chancloser/aux_closer.go +++ b/lnwallet/chancloser/aux_closer.go @@ -1,13 +1,78 @@ package chancloser import ( - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwallet" - "github.com/lightningnetwork/lnd/lnwallet/types" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/tlv" ) +// CloseOutput represents an output that should be included in the close +// transaction. +type CloseOutput struct { + // Amt is the amount of the output. + Amt btcutil.Amount + + // DustLimit is the dust limit for the local node. + DustLimit btcutil.Amount + + // PkScript is the script that should be used to pay to the output. + PkScript []byte + + // ShutdownRecords is the set of custom records that may result in + // extra close outputs being added. + ShutdownRecords lnwire.CustomRecords +} + +// AuxShutdownReq is used to request a set of extra custom records to include +// in the shutdown message. +type AuxShutdownReq struct { + // ChanPoint is the channel point of the channel that is being shut + // down. + ChanPoint wire.OutPoint + + // ShortChanID is the short channel ID of the channel that is being + // closed. + ShortChanID lnwire.ShortChannelID + + // Initiator is true if the local node is the initiator of the channel. + Initiator bool + + // InternalKey is the internal key for the shutdown addr. This will + // only be set for taproot shutdown addrs. + InternalKey fn.Option[btcec.PublicKey] + + // CommitBlob is the blob that was included in the last commitment. + CommitBlob fn.Option[tlv.Blob] + + // FundingBlob is the blob that was included in the funding state. + FundingBlob fn.Option[tlv.Blob] +} + +// AuxCloseDesc is used to describe the channel close that is being performed. +type AuxCloseDesc struct { + AuxShutdownReq + + // CloseFee is the closing fee to be paid for this state. + CloseFee btcutil.Amount + + // CommitFee is the fee that was paid for the last commitment. + CommitFee btcutil.Amount + + // LocalCloseOutput is the output that the local node should be paid + // to. This is None if the local party will not have an output on the + // co-op close transaction. + LocalCloseOutput fn.Option[CloseOutput] + + // RemoteCloseOutput is the output that the remote node should be paid + // to. This will be None if the remote party will not have an output on + // the co-op close transaction. + RemoteCloseOutput fn.Option[CloseOutput] +} + // AuxCloseOutputs is used to specify extra outputs that should be used when // constructing the co-op close transaction. type AuxCloseOutputs struct { @@ -26,15 +91,14 @@ type AuxCloseOutputs struct { type AuxChanCloser interface { // ShutdownBlob returns the set of custom records that should be // included in the shutdown message. - ShutdownBlob(req types.AuxShutdownReq) (fn.Option[lnwire.CustomRecords], + ShutdownBlob(req AuxShutdownReq) (fn.Option[lnwire.CustomRecords], error) // AuxCloseOutputs returns the set of custom outputs that should be used // to construct the co-op close transaction. - AuxCloseOutputs(desc types.AuxCloseDesc) (fn.Option[AuxCloseOutputs], - error) + AuxCloseOutputs(desc AuxCloseDesc) (fn.Option[AuxCloseOutputs], error) // FinalizeClose is called after the close transaction has been agreed // upon. - FinalizeClose(desc types.AuxCloseDesc, closeTx *wire.MsgTx) error + FinalizeClose(desc AuxCloseDesc, closeTx *wire.MsgTx) error } diff --git a/lnwallet/chancloser/chancloser.go b/lnwallet/chancloser/chancloser.go index 85d1319e2..cc6ccffa8 100644 --- a/lnwallet/chancloser/chancloser.go +++ b/lnwallet/chancloser/chancloser.go @@ -4,13 +4,12 @@ import ( "bytes" "fmt" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/htlcswitch" @@ -20,7 +19,6 @@ import ( "github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" - "github.com/lightningnetwork/lnd/lnwallet/types" "github.com/lightningnetwork/lnd/lnwire" ) @@ -164,12 +162,6 @@ type ChanCloseCfg struct { // procedure. This includes shutting down a channel, marking it ineligible for // routing HTLC's, negotiating fees with the remote party, and finally // broadcasting the fully signed closure transaction to the network. -// -// NOTE: The state machine takes no locks of its own. Nearly every method reads -// and writes the same fields, so all of them MUST be driven from a single -// goroutine. In production that's the peer's channelManager, which is the one -// place the close messages from the wire, the local close requests, and the -// link's flush notification all meet. type ChanCloser struct { // state is the current state of the state machine. state closeState @@ -247,12 +239,12 @@ type ChanCloser struct { // localCloseOutput is the local output on the closing transaction that // the local party should be paid to. This will only be populated if the // local balance isn't dust. - localCloseOutput fn.Option[types.CloseOutput] + localCloseOutput fn.Option[CloseOutput] // remoteCloseOutput is the remote output on the closing transaction // that the remote party should be paid to. This will only be populated // if the remote balance isn't dust. - remoteCloseOutput fn.Option[types.CloseOutput] + remoteCloseOutput fn.Option[CloseOutput] // auxOutputs are the optional additional outputs that might be added to // the closing transaction. @@ -386,17 +378,14 @@ func (c *ChanCloser) initChanShutdown() (*lnwire.Shutdown, error) { // At this point, we'll check to see if we have any custom records to // add to the shutdown message. err := fn.MapOptionZ(c.cfg.AuxCloser, func(a AuxChanCloser) error { - channel := c.cfg.Channel - shutdownCustomRecords, err := a.ShutdownBlob( - types.AuxShutdownReq{ - ChanPoint: c.chanPoint, - ShortChanID: channel.ShortChanID(), - Initiator: channel.IsInitiator(), - InternalKey: c.localInternalKey, - CommitBlob: channel.LocalCommitmentBlob(), - FundingBlob: channel.FundingBlob(), - }, - ) + shutdownCustomRecords, err := a.ShutdownBlob(AuxShutdownReq{ + ChanPoint: c.chanPoint, + ShortChanID: c.cfg.Channel.ShortChanID(), + Initiator: c.cfg.Channel.IsInitiator(), + InternalKey: c.localInternalKey, + CommitBlob: c.cfg.Channel.LocalCommitmentBlob(), + FundingBlob: c.cfg.Channel.FundingBlob(), + }) if err != nil { return err } @@ -453,7 +442,7 @@ func (c *ChanCloser) initChanShutdown() (*lnwire.Shutdown, error) { // it might still carry value in custom channel terms. _, dustAmt := c.cfg.Channel.LocalBalanceDust() localBalance, _ := c.cfg.Channel.CommitBalances() - c.localCloseOutput = fn.Some(types.CloseOutput{ + c.localCloseOutput = fn.Some(CloseOutput{ Amt: localBalance, DustLimit: dustAmt, PkScript: c.localDeliveryScript, @@ -530,12 +519,12 @@ func (c *ChanCloser) NegotiationHeight() uint32 { } // LocalCloseOutput returns the local close output. -func (c *ChanCloser) LocalCloseOutput() fn.Option[types.CloseOutput] { +func (c *ChanCloser) LocalCloseOutput() fn.Option[CloseOutput] { return c.localCloseOutput } // RemoteCloseOutput returns the remote close output. -func (c *ChanCloser) RemoteCloseOutput() fn.Option[types.CloseOutput] { +func (c *ChanCloser) RemoteCloseOutput() fn.Option[CloseOutput] { return c.remoteCloseOutput } @@ -598,13 +587,10 @@ func (c *ChanCloser) ReceiveShutdown(msg lnwire.Shutdown) ( noShutdown := fn.None[lnwire.Shutdown]() // We'll track their remote close output, even if it's dust in BTC - // terms, it might still carry value in custom channel terms. We only - // commit it to our state in the branches below that go on to accept the - // message: a Shutdown that shows up at a point where we can't act on it - // has no business overwriting an output we already settled on. + // terms, it might still carry value in custom channel terms. _, dustAmt := c.cfg.Channel.RemoteBalanceDust() _, remoteBalance := c.cfg.Channel.CommitBalances() - remoteCloseOutput := fn.Some(types.CloseOutput{ + c.remoteCloseOutput = fn.Some(CloseOutput{ Amt: remoteBalance, DustLimit: dustAmt, PkScript: msg.Address, @@ -651,7 +637,6 @@ func (c *ChanCloser) ReceiveShutdown(msg lnwire.Shutdown) ( // address. We'll use this when we craft the closure // transaction. c.remoteDeliveryScript = msg.Address - c.remoteCloseOutput = remoteCloseOutput // We'll generate a shutdown message of our own to send across // the wire. @@ -701,7 +686,6 @@ func (c *ChanCloser) ReceiveShutdown(msg lnwire.Shutdown) ( // address, we'll record their preferred delivery closing // script. c.remoteDeliveryScript = msg.Address - c.remoteCloseOutput = remoteCloseOutput // At this point, we can now start the fee negotiation state, by // constructing and sending our initial signature for what we @@ -754,6 +738,18 @@ func (c *ChanCloser) BeginNegotiation() (fn.Option[lnwire.ClosingSigned], // compute what our max/ideal fee will be. c.initFeeBaseline() + // Before continuing, mark the channel as cooperatively closed + // with a nil txn. Even though we haven't negotiated the final + // txn, this guarantees that our listchannels rpc will be + // externally consistent, and reflect that the channel is being + // shutdown by the time the closing request returns. + err := c.cfg.Channel.MarkCoopBroadcasted( + nil, c.closer, + ) + if err != nil { + return noClosingSigned, err + } + // At this point, we can now start the fee negotiation state, by // constructing and sending our initial signature for what we // think the closing transaction should look like. @@ -764,7 +760,7 @@ func (c *ChanCloser) BeginNegotiation() (fn.Option[lnwire.ClosingSigned], // to check if we have a cached remote offer to process. // If we do, we'll process it here. res := noClosingSigned - var err error + err = nil c.cachedClosingSigned.WhenSome( func(cs lnwire.ClosingSigned) { res, err = c.ReceiveClosingSigned(cs) @@ -974,6 +970,33 @@ func (c *ChanCloser) ReceiveClosingSigned( //nolint:funlen } c.closingTx = closeTx + // If there's an aux chan closer, then we'll finalize with it + // before we write to disk. + err = fn.MapOptionZ( + c.cfg.AuxCloser, func(aux AuxChanCloser) error { + channel := c.cfg.Channel + //nolint:ll + req := AuxShutdownReq{ + ChanPoint: c.chanPoint, + ShortChanID: c.cfg.Channel.ShortChanID(), + InternalKey: c.localInternalKey, + Initiator: channel.IsInitiator(), + CommitBlob: channel.LocalCommitmentBlob(), + FundingBlob: channel.FundingBlob(), + } + desc := AuxCloseDesc{ + AuxShutdownReq: req, + LocalCloseOutput: c.localCloseOutput, + RemoteCloseOutput: c.remoteCloseOutput, + } + + return aux.FinalizeClose(desc, closeTx) + }, + ) + if err != nil { + return noClosing, err + } + // Before publishing the closing tx, we persist it to the // database, such that it can be republished if something goes // wrong. @@ -1030,7 +1053,7 @@ func (c *ChanCloser) auxCloseOutputs( var closeOuts fn.Option[AuxCloseOutputs] err := fn.MapOptionZ(c.cfg.AuxCloser, func(aux AuxChanCloser) error { - req := types.AuxShutdownReq{ + req := AuxShutdownReq{ ChanPoint: c.chanPoint, ShortChanID: c.cfg.Channel.ShortChanID(), InternalKey: c.localInternalKey, @@ -1038,7 +1061,7 @@ func (c *ChanCloser) auxCloseOutputs( CommitBlob: c.cfg.Channel.LocalCommitmentBlob(), FundingBlob: c.cfg.Channel.FundingBlob(), } - outs, err := aux.AuxCloseOutputs(types.AuxCloseDesc{ + outs, err := aux.AuxCloseOutputs(AuxCloseDesc{ AuxShutdownReq: req, CloseFee: closeFee, CommitFee: c.cfg.Channel.CommitFee(), @@ -1260,14 +1283,16 @@ func calcCompromiseFee(chanPoint wire.OutPoint, ourIdealFee, lastSentFee, // ParseUpfrontShutdownAddress attempts to parse an upfront shutdown address. // If the address is empty, it returns nil. If it successfully decoded the // address, it returns a script that pays out to the address. -func ParseUpfrontShutdownAddress(strAddr string, +func ParseUpfrontShutdownAddress(address string, params *chaincfg.Params) (lnwire.DeliveryAddress, error) { - if len(strAddr) == 0 { + if len(address) == 0 { return nil, nil } - addr, err := address.DecodeAddress(strAddr, params) + addr, err := btcutil.DecodeAddress( + address, params, + ) if err != nil { return nil, fmt.Errorf("invalid address: %w", err) } diff --git a/lnwallet/chancloser/chancloser_test.go b/lnwallet/chancloser/chancloser_test.go index a13115d60..0f16356e2 100644 --- a/lnwallet/chancloser/chancloser_test.go +++ b/lnwallet/chancloser/chancloser_test.go @@ -8,11 +8,11 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" @@ -123,6 +123,7 @@ func TestMaybeMatchScript(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -146,8 +147,6 @@ type mockChannel struct { chanType channeldb.ChannelType localKey keychain.KeyDescriptor remoteKey keychain.KeyDescriptor - - coopBroadcastTxns []*wire.MsgTx } func (m *mockChannel) ChannelPoint() wire.OutPoint { @@ -162,10 +161,8 @@ func (m *mockChannel) FundingBlob() fn.Option[tlv.Blob] { return fn.None[tlv.Blob]() } -func (m *mockChannel) MarkCoopBroadcasted(tx *wire.MsgTx, - _ lntypes.ChannelParty) error { - - m.coopBroadcastTxns = append(m.coopBroadcastTxns, tx) +func (m *mockChannel) MarkCoopBroadcasted(*wire.MsgTx, + lntypes.ChannelParty) error { return nil } @@ -276,8 +273,6 @@ func newMockTaprootChan(t *testing.T, initiator bool) *mockChannel { } type mockMusigSession struct { - remoteNonceInited bool - remoteNonce musig2.Nonces } func newMockMusigSession() *mockMusigSession { @@ -298,17 +293,11 @@ func (m *mockMusigSession) CombineClosingOpts(localSig, nil } -func (m *mockMusigSession) InitRemoteNonce(nonce *musig2.Nonces) { - m.remoteNonceInited = true - m.remoteNonce = *nonce +func (m *mockMusigSession) ClosingNonce() (*musig2.Nonces, error) { + return &musig2.Nonces{}, nil } -func (m *mockMusigSession) InvalidateNonce() {} - -func (m *mockMusigSession) ClosingNonce() (*musig2.Nonces, error) { - return &musig2.Nonces{ - PubNonce: [66]byte{1, 2, 3}, - }, nil +func (m *mockMusigSession) InitRemoteNonce(nonce *musig2.Nonces) { } type mockCoopFeeEstimator struct { @@ -360,6 +349,7 @@ func TestMaxFeeClamp(t *testing.T) { }, } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -396,6 +386,7 @@ func TestMaxFeeBailOut(t *testing.T) { ) for _, isInitiator := range []bool{true, false} { + isInitiator := isInitiator t.Run(fmt.Sprintf("initiator=%v", isInitiator), func(t *testing.T) { t.Parallel() @@ -491,6 +482,7 @@ func TestParseUpfrontShutdownAddress(t *testing.T) { } for _, tc := range tests { + tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() @@ -643,23 +635,4 @@ func TestTaprootFastClose(t *testing.T) { tx, _ = bobCloser.ClosingTx() require.NotNil(t, tx) require.True(t, oClosingSigned.IsNone()) - - // Every MarkCoopBroadcasted call must have a real close tx. - // A nil tx would set ChanStatusCoopBroadcasted without a - // stored transaction, creating the limbo state described in - // https://github.com/lightninglabs/taproot-assets/issues/2108. - require.NotEmpty(t, aliceChan.coopBroadcastTxns, - "expected at least one MarkCoopBroadcasted call "+ - "from alice") - require.NotEmpty(t, bobChan.coopBroadcastTxns, - "expected at least one MarkCoopBroadcasted call "+ - "from bob") - for i, broadcastTx := range aliceChan.coopBroadcastTxns { - require.NotNilf(t, broadcastTx, - "alice MarkCoopBroadcasted call %d had nil tx", i) - } - for i, broadcastTx := range bobChan.coopBroadcastTxns { - require.NotNilf(t, broadcastTx, - "bob MarkCoopBroadcasted call %d had nil tx", i) - } } diff --git a/lnwallet/chancloser/interface.go b/lnwallet/chancloser/interface.go index 99e3ceb07..74f096973 100644 --- a/lnwallet/chancloser/interface.go +++ b/lnwallet/chancloser/interface.go @@ -2,8 +2,8 @@ package chancloser import ( "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" @@ -41,10 +41,8 @@ type Channel interface { //nolint:interfacebloat // funding details for the channel. FundingBlob() fn.Option[tlv.Blob] - // MarkCoopBroadcasted persistently marks that the channel - // close transaction has been broadcast. The tx MUST be - // non-nil; callers must not invoke this until a concrete - // close tx has been constructed. + // MarkCoopBroadcasted persistently marks that the channel close + // transaction has been broadcast. MarkCoopBroadcasted(*wire.MsgTx, lntypes.ChannelParty) error // MarkShutdownSent persists the given ShutdownInfo. The existence of @@ -134,10 +132,4 @@ type MusigSession interface { // shutdown message so it can be used later to generate and verify // signatures. InitRemoteNonce(nonce *musig2.Nonces) - - // InvalidateNonce clears the cached local nonce, forcing a fresh - // nonce to be generated on the next call to ClosingNonce. This - // must be called after each RBF round completes to prevent nonce - // reuse across iterations. - InvalidateNonce() } diff --git a/lnwallet/chancloser/mock.go b/lnwallet/chancloser/mock.go index 6f1fd669f..970c6b036 100644 --- a/lnwallet/chancloser/mock.go +++ b/lnwallet/chancloser/mock.go @@ -5,9 +5,9 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" diff --git a/lnwallet/chancloser/rbf_close.md b/lnwallet/chancloser/rbf_close.md index 926578e02..ac532c5d5 100644 --- a/lnwallet/chancloser/rbf_close.md +++ b/lnwallet/chancloser/rbf_close.md @@ -178,43 +178,6 @@ The `CloseErr` state provides recovery paths when protocol violations occur: Recovery typically involves restarting the negotiation with a new closing offer. -### RBF Nonce Flow Example - -Here's how nonces flow through an RBF cooperative close with taproot: - -1. **Initial Shutdown**: - - Alice sends `shutdown` with her closee nonce `NA` - - Bob sends `shutdown` with his closee nonce `NB` - -2. **First Close Attempt** (Alice as closer): - - Alice sends `closing_complete`: - - Uses Bob's closee nonce NB (from his shutdown) as the closee nonce - - Generates her own closer nonce NC locally - - Signs with aggregate nonce R = NB + NC - - Includes `PartialSigWithNonce` = partial_sig (32 bytes) + closer nonce NC (66 bytes) - - Bob sends `closing_sig`: - - Extracts Alice's closer nonce NC from `PartialSigWithNonce` - - Uses his own closee nonce NB (from his shutdown) - - Signs with aggregate nonce R = NC + NB - - Includes `PartialSig` (32 bytes) + `NextCloseeNonce` NB2 for future RBF - -3. **RBF Iteration** (Bob as closer): - - Bob sends `closing_complete`: - - Uses Alice's next closee nonce NA2 (from her previous `NextCloseeNonce`) as closee nonce - - Generates his own closer nonce NC2 locally - - Signs with aggregate nonce R = NA2 + NC2 - - Includes `PartialSigWithNonce` = partial_sig + closer nonce NC2 - - Alice sends `closing_sig`: - - Extracts Bob's closer nonce NC2 from `PartialSigWithNonce` - - Uses her own closee nonce NA2 (from her previous `NextCloseeNonce`) - - Signs with aggregate nonce R = NC2 + NA2 - - Includes `PartialSig` + `NextCloseeNonce` NA3 for future RBF - -The pattern continues: the closer always uses the peer's closee nonce (from -shutdown or previous NextCloseeNonce) combined with a fresh local closer nonce. -The closee extracts the closer nonce from PartialSigWithNonce and combines it -with their own closee nonce. - ## Example Scenarios ### Standard Cooperative Close @@ -248,135 +211,9 @@ with their own closee nonce. 5. When agreement is reached on new fees: `ClosePending` → `CloseFin` (via `txn_confirmation`) -## Taproot Channel Support - -### MuSig2 Nonce Handling - -For taproot channels, the cooperative close process requires coordination for -MuSig2 signature creation using a JIT (Just-In-Time) nonce pattern: - -#### Nonce Exchange During Shutdown - -For taproot channels using the modern RBF cooperative close flow: -- The `shutdown` message includes a single nonce field: - - `shutdown_nonce` (TLV type 8): The sender's "closee nonce" used when they - send `closing_sig` -- This simplified approach works because nonces are sent JIT with signatures - -#### JIT (Just-In-Time) Nonce Pattern - -The protocol uses an asymmetric signature pattern for taproot channels that -optimizes nonce delivery: - -**Asymmetric Roles**: -- **Closer**: The party proposing a fee (sends `closing_complete`) -- **Closee**: The party accepting the fee (sends `closing_sig`) - -**ClosingComplete (from Closer)**: -- Uses `PartialSigWithNonce` (98 bytes total): - - The partial signature (32 bytes) - - The sender's closer nonce (66 bytes) -- Bundles the closer nonce because the closee hasn't seen it yet -- TLV types 5, 6, 7 (distinct from non-taproot types 1, 2, 3) - -**ClosingSig (from Closee)**: -- Uses `PartialSig` (32 bytes) + separate `NextCloseeNonce`: - - The partial signature in TLV types 5, 6, 7 - - The next closee nonce in TLV type 22 (66 bytes) -- Separates the nonce because the closer already knows the current nonce from - shutdown or previous `PartialSigWithNonce` - -This asymmetric pattern minimizes redundancy while ensuring both parties always -have the nonces they need for signing. - -#### Nonce State Management - -The state machine maintains a simplified `NonceState` structure with only 2 fields: -- `LocalCloseeNonce`: Our closee nonce sent in our shutdown message -- `RemoteCloseeNonce`: The peer's closee nonce from their shutdown message - -The JIT pattern eliminates complex nonce rotation: -- New nonces arrive with signatures, not pre-generated -- Remote nonces are updated automatically from `PartialSigWithNonce` in - `closing_complete` -- Local nonces are generated on-demand when creating signatures - -### Wire Message Extensions - -The following messages have been extended with optional TLV fields for taproot: - -**shutdown**: -- Type 8: `shutdown_nonce` - Sender's closee nonce for cooperative close signing - -**closing_complete**: -- Types 5, 6, 7: `PartialSigWithNonce` - Partial signature with embedded closer nonce - - Type 5: `closer_no_closee` (closer has output, closee is dust) - - Type 6: `no_closer_closee` (closer is dust, closee has output) - - Type 7: `closer_and_closee` (both have outputs) - -**closing_sig**: -- Types 5, 6, 7: `PartialSig` - Just the partial signature (32 bytes) - - Same TLV type meanings as above -- Type 22: `NextCloseeNonce` - Next closee nonce for RBF iterations (66 bytes) - -### Validation Requirements - -For taproot channels: -- Shutdown messages MUST include the sender's closee nonce -- ClosingComplete messages MUST use PartialSigWithNonce (includes next nonce - bundled with signature) -- ClosingSig messages MUST use PartialSig with separate NextCloseeNonce field -- ClosingSig messages MUST always include NextCloseeNonce for RBF readiness - -### Implementation Notes for Nonce Handling - -The MuSig2 session's `InitRemoteNonce` method is called at specific times -depending on our role: - -**When we're the Closer (LocalMusigSession)**: -1. During `ShutdownReceived`: Store their closee nonce in `NonceState.RemoteCloseeNonce` -2. During `SendOfferEvent`: Call `initLocalMusigCloseeNonce` with stored closee nonce, - generate JIT closer nonce via `ClosingNonce()`, sign via `ProposalClosingOpts()` -3. Store the full `MusigPartialSig` in `LocalOfferSent` state to avoid re-signing -4. During `LocalSigReceived` (when receiving their ClosingSig): - - Use the stored `MusigPartialSig` via `CombineClosingOpts()` — no second signing - - AFTER `CompleteCooperativeClose` succeeds, call `InvalidateNonce()` to force - fresh nonce generation for the next RBF round - - Update `NonceState.RemoteCloseeNonce` with `NextCloseeNonce` for future RBF - -**When we're the Closee (RemoteMusigSession)**: -1. Our closee nonce was generated during shutdown and sent in the `Shutdown` message -2. During `OfferReceivedEvent`: Receive their JIT closer nonce in `ClosingComplete`, - call `initRemoteMusigCloserNonce`, then sign via `ProposalClosingOpts()` -3. After signing, call `InvalidateNonce()` before generating the next closee nonce -4. `createClosingSigMessage` calls `ClosingNonce()` which generates a fresh nonce - for `NextCloseeNonce` in `ClosingSig` - -**Nonce Safety**: Each RBF round uses a unique nonce. The `InvalidateNonce()` call -clears the cached nonce after signing, ensuring `ClosingNonce()` generates fresh -on the next round. The closer's `MusigPartialSig` is stored in state so -`LocalOfferSent` can combine signatures without calling `CreateCloseProposal` a -second time (which would reuse the nonce). - -### Helper Function Reference - -The following helper functions manage nonce initialization: - -| Function | Session | Sets | Called When | -|----------|---------|------|-------------| -| `initLocalMusigCloseeNonce` | LocalMusigSession | Remote's closee nonce | We're closer, preparing to sign | -| `initRemoteMusigCloserNonce` | RemoteMusigSession | Remote's closer nonce | We're closee, received ClosingComplete | - -Note: The function names now correctly reflect what nonce is being set: -- `initLocalMusigCloseeNonce`: Sets remote's **closee** nonce (from their shutdown) -- `initRemoteMusigCloserNonce`: Sets remote's **closer** nonce (from their JIT nonce in ClosingComplete) - ## Implementation Notes -- This state machine is implemented in `rbf_coop_transitions.go` and - `rbf_coop_states.go` within the `lnwallet/chancloser` package -- The `MusigChanCloser` adapter in `peer/musig_chan_closer.go` implements the - `MusigSession` interface for managing MuSig2 nonces +- This state machine is implemented in the `peer.go` and `channel.go` files +within the lnd codebase - State transitions are logged at the debug level - The `ChanCloser` interface manages the state machine execution -- Taproot support requires the `MusigSession` interface for nonce coordination diff --git a/lnwallet/chancloser/rbf_coop_msg_mapper.go b/lnwallet/chancloser/rbf_coop_msg_mapper.go index 2e4079a49..a66cf78cc 100644 --- a/lnwallet/chancloser/rbf_coop_msg_mapper.go +++ b/lnwallet/chancloser/rbf_coop_msg_mapper.go @@ -11,11 +11,10 @@ import ( // rbf-coop close state machine. This enables the state machine to be used with // protofsm. type RbfMsgMapper struct { - // bestHeight returns the current best block height. This is used - // instead of a static height so that thaw height checks reflect the - // actual chain state when messages are received, not the height at - // FSM creation time. - bestHeight func() uint32 + // blockHeight is the height of the block when the co-op close request + // was initiated. This is used to validate conditions related to the + // thaw height. + blockHeight uint32 // chanID is the channel ID of the channel being closed. chanID lnwire.ChannelID @@ -25,15 +24,15 @@ type RbfMsgMapper struct { peerPub btcec.PublicKey } -// NewRbfMsgMapper creates a new RbfMsgMapper instance given a function that -// returns the current best block height. -func NewRbfMsgMapper(bestHeight func() uint32, +// NewRbfMsgMapper creates a new RbfMsgMapper instance given the current block +// height when the co-op close request was initiated. +func NewRbfMsgMapper(blockHeight uint32, chanID lnwire.ChannelID, peerPub btcec.PublicKey) *RbfMsgMapper { return &RbfMsgMapper{ - bestHeight: bestHeight, - chanID: chanID, - peerPub: peerPub, + blockHeight: blockHeight, + chanID: chanID, + peerPub: peerPub, } } @@ -59,15 +58,9 @@ func (r *RbfMsgMapper) MapMsg(wireMsg msgmux.PeerMsg) fn.Option[ProtocolEvent] { return fn.None[ProtocolEvent]() } - var remoteShutdownNonce fn.Option[lnwire.Musig2Nonce] - msg.ShutdownNonce.WhenSomeV(func(nonce lnwire.Musig2Nonce) { - remoteShutdownNonce = fn.Some(nonce) - }) - return someEvent(&ShutdownReceived{ - BlockHeight: r.bestHeight(), - ShutdownScript: msg.Address, - RemoteShutdownNonce: remoteShutdownNonce, + BlockHeight: r.blockHeight, + ShutdownScript: msg.Address, }) case *lnwire.ClosingComplete: diff --git a/lnwallet/chancloser/rbf_coop_states.go b/lnwallet/chancloser/rbf_coop_states.go index 48e998121..8c7a26513 100644 --- a/lnwallet/chancloser/rbf_coop_states.go +++ b/lnwallet/chancloser/rbf_coop_states.go @@ -4,9 +4,9 @@ import ( "fmt" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" @@ -54,11 +54,6 @@ var ( // ClosingComplete message that doesn't carry our last local script // sent. ErrWrongLocalScript = fmt.Errorf("wrong local script") - - // ErrTaprootShutdownNonceMissing is returned when a taproot channel - // receives a shutdown message without the required nonce. - ErrTaprootShutdownNonceMissing = fmt.Errorf("shutdown nonce " + - "required for taproot channel RBF flow") ) // ProtocolEvent is a special interface used to create the equivalent of a @@ -106,11 +101,6 @@ type SendShutdown struct { // IdealFeeRate is the ideal fee rate we'd like to use for the closing // attempt. IdealFeeRate chainfee.SatPerVByte - - // CloseeNonce is the nonce we'll send in the shutdown message. The - // remote party will use this when they create their closing transaction - // (when they act as closer). Only present for taproot channels. - CloseeNonce fn.Option[lnwire.Musig2Nonce] } // protocolSealed indicates that this struct is a ProtocolEvent instance. @@ -131,11 +121,6 @@ type ShutdownReceived struct { // received. This is used for channel leases to determine if a co-op // close can occur. BlockHeight uint32 - - // RemoteShutdownNonce is the closee nonce from the remote party's - // shutdown message. We'll use this when signing our closing transaction - // (when we act as closer). Only present for taproot channels. - RemoteShutdownNonce fn.Option[lnwire.Musig2Nonce] } // protocolSealed indicates that this struct is a ProtocolEvent instance. @@ -175,6 +160,10 @@ var unknownBalance = ShutdownBalances{} // - fromState: ChannelFlushing // - toState: ClosingNegotiation type ChannelFlushed struct { + // FreshFlush indicates if this is the first time the channel has been + // flushed, or if this is a flush as part of an RBF iteration. + FreshFlush bool + // ShutdownBalances is the balances of the channel once it has been // flushed. We tie this to the ChannelFlushed state as this may not be // the same as the starting value. @@ -270,10 +259,8 @@ type ChanStateObserver interface { // channel. DisableChannel() error - // MarkCoopBroadcasted persistently marks that the channel - // close transaction has been broadcast. The tx MUST be - // non-nil; callers must not invoke this until a concrete - // close tx has been constructed. + // MarkCoopBroadcasted persistently marks that the channel close + // transaction has been broadcast. MarkCoopBroadcasted(*wire.MsgTx, bool) error // MarkShutdownSent persists the given ShutdownInfo. The existence of @@ -351,16 +338,6 @@ type Environment struct { // we'll be signing can only be determined once the channel has been // flushed. CloseSigner CloseSigner - - // LocalMusigSession is the MuSig2 session used when we're creating our - // own closing transaction (acting as the closer) in the RBF flow. This - // is optional and only used for taproot channels. - LocalMusigSession MusigSession - - // RemoteMusigSession is the MuSig2 session used when we're creating the - // remote party's closing transaction (acting as the closee) in the RBF - // flow. This is optional and only used for taproot channels. - RemoteMusigSession MusigSession } // Name returns the name of the environment. This is used to uniquely identify @@ -370,12 +347,6 @@ func (e *Environment) Name() string { return fmt.Sprintf("rbf_chan_closer(%v)", e.ChanPoint) } -// IsTaproot returns true if this is a taproot channel. A channel is considered -// taproot if both the LocalMusigSession and RemoteMusigSession are set. -func (e *Environment) IsTaproot() bool { - return e.LocalMusigSession != nil && e.RemoteMusigSession != nil -} - // CloseStateTransition is the StateTransition type specific to the coop close // state machine. // @@ -488,10 +459,6 @@ type ShutdownPending struct { // before we received their shutdown message. We'll stash it to process // later. EarlyRemoteOffer fn.Option[OfferReceivedEvent] - - // NonceState tracks the nonces exchanged during shutdown for taproot - // channels. - NonceState NonceState } // String returns the name of the state for ShutdownPending. @@ -532,10 +499,6 @@ type ChannelFlushing struct { // transaction. Once the channel has been flushed, we'll use this as // our target fee rate. IdealFeeRate fn.Option[chainfee.SatPerVByte] - - // NonceState tracks the nonces exchanged during shutdown for taproot - // channels. - NonceState NonceState } // String returns the name of the state for ChannelFlushing. @@ -646,20 +609,6 @@ func (e *ErrStateCantPayForFee) String() string { "attempted_fee=%v)", e.localBalance, e.attemptedFee) } -// NonceState stores the nonces for taproot channel closing using the simplified -// JIT (just-in-time) nonce pattern. With this pattern, shutdown messages only -// contain the sender's closee nonce, and subsequent nonces are sent alongside -// signatures in PartialSigWithNonce fields. -type NonceState struct { - // LocalCloseeNonce is the nonce we sent in our shutdown message. - // The remote party will use this when they act as closer. - LocalCloseeNonce fn.Option[lnwire.Musig2Nonce] - - // RemoteCloseeNonce is the nonce from the remote party's shutdown - // message. We'll use this when we act as closer. - RemoteCloseeNonce fn.Option[lnwire.Musig2Nonce] -} - // CloseChannelTerms is a set of terms that we'll use to close the channel. This // includes the balances of the channel, and the scripts we'll use to send each // party's funds to. @@ -667,9 +616,6 @@ type CloseChannelTerms struct { ShutdownScripts ShutdownBalances - - // NonceState tracks nonces for taproot channels across RBF iterations. - NonceState NonceState } // DeriveCloseTxOuts takes the close terms, and returns the local and remote tx @@ -790,12 +736,6 @@ type LocalOfferSent struct { // LocalSig is the signature we sent to the remote party. LocalSig lnwire.Sig - - // LocalMusigSig is the full musig partial signature from when we - // signed as closer. Stored here so LocalOfferSent can combine - // signatures without re-signing, which prevents nonce reuse across - // RBF iterations. Only set for taproot channels. - LocalMusigSig fn.Option[lnwallet.MusigPartialSig] } // String returns the name of the state for LocalOfferSent, including proposed. @@ -1024,7 +964,3 @@ type RbfEvent = protofsm.EmittedEvent[ProtocolEvent] // RbfStateSub is a type alias for the state subscription type of the RBF chan // closer. type RbfStateSub = protofsm.StateSubscriber[ProtocolEvent, *Environment] - -// ChanCloserActorMsg is an adapter to enable the state machine executor that -// runs this state machine to be passed around as an actor. -type ChanCloserActorMsg = protofsm.ActorMessage[ProtocolEvent] diff --git a/lnwallet/chancloser/rbf_coop_test.go b/lnwallet/chancloser/rbf_coop_test.go index e3f0e88e8..088f7f4e1 100644 --- a/lnwallet/chancloser/rbf_coop_test.go +++ b/lnwallet/chancloser/rbf_coop_test.go @@ -12,18 +12,15 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/mempool" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/protofsm" @@ -52,19 +49,18 @@ var ( remoteSigBytes = fromHex("304502210082235e21a2300022738dabb8e1bbd9d1" + "9cfb1e7ab8c30a23b0afbb8d178abcf3022024bf68e256c534ddfaf966b" + "f908deb944305596f7bdcc38d69acad7f9c868724") - remoteSig = sigMustParse(remoteSigBytes) - remoteWireSig = mustWireSig(&remoteSig) - - localSchnorrSigBytes = bytes.Repeat([]byte{0x01}, 64) - localSchnorrSig, _ = lnwire.NewSigFromSchnorrRawSignature( - localSchnorrSigBytes, - ) + remoteSig = sigMustParse(remoteSigBytes) + remoteWireSig = mustWireSig(&remoteSig) + remoteSigRecordType3 = newSigTlv[tlv.TlvType3](remoteWireSig) + remoteSigRecordType1 = newSigTlv[tlv.TlvType1](remoteWireSig) localTx = wire.MsgTx{Version: 2} closeTx = wire.NewMsgTx(2) - defaultTimeout = wait.DefaultTimeout + defaultTimeout = 500 * time.Millisecond + longTimeout = 3 * time.Second + defaultPoll = 50 * time.Millisecond ) func sigMustParse(sigBytes []byte) ecdsa.Signature { @@ -121,13 +117,19 @@ func assertStateTransitions[Event any, Env protofsm.Environment]( for _, expectedState := range expectedStates { newState, err := fn.RecvOrTimeout( - stateSub.NewItemCreated.ChanOut(), - defaultTimeout, + stateSub.NewItemCreated.ChanOut(), defaultTimeout, ) require.NoError(t, err, "expected state: %T", expectedState) require.IsType(t, expectedState, newState) } + + // We should have no more states. + select { + case newState := <-stateSub.NewItemCreated.ChanOut(): + t.Fatalf("unexpected state transition: %v", newState) + case <-time.After(defaultPoll): + } } // unknownEvent is a dummy event that is used to test that the state machine @@ -185,9 +187,6 @@ type harnessCfg struct { localUpfrontAddr fn.Option[lnwire.DeliveryAddress] remoteUpfrontAddr fn.Option[lnwire.DeliveryAddress] - - localMusigSession fn.Option[MusigSession] - remoteMusigSession fn.Option[MusigSession] } // rbfCloserTestHarness is a test harness for the RBF closer. @@ -273,10 +272,6 @@ func (r *rbfCloserTestHarness) stopAndAssert() { defer r.chanCloser.RemoveStateSub(r.stateSub) r.chanCloser.Stop() - // After Stop(), no further state transitions should be produced. - // Wait for a short quiet window to catch any unexpected stragglers. - r.assertNoStateTransitions() - r.assertExpectations() } @@ -296,10 +291,12 @@ func (r *rbfCloserTestHarness) assertStartupAssertions() { } func (r *rbfCloserTestHarness) assertNoStateTransitions() { + r.T.Helper() + select { case newState := <-r.stateSub.NewItemCreated.ChanOut(): r.T.Fatalf("unexpected state transition: %T", newState) - case <-time.After(10 * time.Millisecond): + case <-time.After(defaultPoll): } } @@ -436,33 +433,10 @@ func (r *rbfCloserTestHarness) expectNewCloseSig( r.T.Helper() - // For taproot channels, we'll return a musig2 partial signature instead - // of the normal schnorr sig. - switch { - case r.env.LocalMusigSession != nil: - var s btcec.ModNScalar - s.SetInt(1) - - privKey, _ := btcec.NewPrivateKey() - rPoint := privKey.PubKey() - - partislSig := musig2.NewPartialSignature(&s, rPoint) - musigSig := lnwallet.NewMusigPartialSig( - &partislSig, lnwire.Musig2Nonce{}, lnwire.Musig2Nonce{}, - nil, fn.None[chainhash.Hash](), - ) - r.signer.On( - "CreateCloseProposal", fee, localScript, remoteScript, - mock.Anything, - ).Return(musigSig, &localTx, closeBalance, nil) - - // For non-taproot channels, return regular ECDSA signature. - default: - r.signer.On( - "CreateCloseProposal", fee, localScript, remoteScript, - mock.Anything, - ).Return(&localSig, &localTx, closeBalance, nil) - } + r.signer.On( + "CreateCloseProposal", fee, localScript, remoteScript, + mock.Anything, + ).Return(&localSig, &localTx, closeBalance, nil) } func (r *rbfCloserTestHarness) waitForMsgSent() { @@ -470,7 +444,7 @@ func (r *rbfCloserTestHarness) waitForMsgSent() { err := wait.Predicate(func() bool { return r.daemonAdapters.msgSent.Load() - }, time.Second*3) + }, longTimeout) require.NoError(r.T, err) } @@ -496,22 +470,11 @@ func (r *rbfCloserTestHarness) expectCloseFinalized( remoteScript []byte, fee btcutil.Amount, balanceAfterClose btcutil.Amount, isLocal bool) { - // For taproot, we expect the CompleteCooperativeClose to be called with - // musig signatures. We need to match on any signature type since the - // exact types will differ. - switch { - case r.env.LocalMusigSession != nil: - r.signer.On("CompleteCooperativeClose", - mock.Anything, mock.Anything, localScript, - remoteScript, fee, mock.Anything, - ).Return(closeTx, balanceAfterClose, nil) - default: - // The caller should obtain the final signature. - r.signer.On("CompleteCooperativeClose", - localCoopSig, remoteCoopSig, localScript, - remoteScript, fee, mock.Anything, - ).Return(closeTx, balanceAfterClose, nil) - } + // The caller should obtain the final signature. + r.signer.On("CompleteCooperativeClose", + localCoopSig, remoteCoopSig, localScript, + remoteScript, fee, mock.Anything, + ).Return(closeTx, balanceAfterClose, nil) // The caller should also mark the transaction as broadcast on disk. r.chanObserver.On("MarkCoopBroadcasted", closeTx, isLocal).Return(nil) @@ -523,6 +486,11 @@ func (r *rbfCloserTestHarness) expectCloseFinalized( ).Return(nil) } +func (r *rbfCloserTestHarness) expectChanPendingClose() { + var nilTx *wire.MsgTx + r.chanObserver.On("MarkCoopBroadcasted", nilTx, true).Return(nil) +} + func (r *rbfCloserTestHarness) assertLocalClosePending() { // We should then remain in the outer close negotiation state. r.assertStateTransitions(&ClosingNegotiation{}) @@ -569,9 +537,9 @@ func (d dustExpectation) String() string { // message to the remote party, and all the other intermediate steps. func (r *rbfCloserTestHarness) expectHalfSignerIteration( initEvent ProtocolEvent, balanceAfterClose, absoluteFee btcutil.Amount, - dustExpect dustExpectation, expectExtraTransition bool) { + dustExpect dustExpectation, iteration bool) { - ctx := context.Background() + ctx := r.T.Context() numFeeCalls := 2 // If we're using the SendOfferEvent as a trigger, we only need to call @@ -598,40 +566,13 @@ func (r *rbfCloserTestHarness) expectHalfSignerIteration( msgExpect := singleMsgMatcher(func(m *lnwire.ClosingComplete) bool { r.T.Helper() - // For taproot channels, check TaprootClosingSigs, as we'll be - // sending musig signatures over. - if r.env.LocalMusigSession != nil { - switch { - case m.TaprootClosingSigs.CloserNoClosee.IsSome(): - r.T.Logf("taproot closer no closee field "+ - "set, expected: %v", - dustExpect) - - return dustExpect == remoteDustExpect - case m.TaprootClosingSigs.NoCloserClosee.IsSome(): - r.T.Logf("taproot no close closee "+ - "field set, expected: %v", - dustExpect) - - return dustExpect == localDustExpect - default: - r.T.Logf("taproot no dust field set, "+ - "expected: %v", dustExpect) - - //nolint:ll - return (m.TaprootClosingSigs.CloserAndClosee.IsSome() && - dustExpect == noDustExpect) - } - } - - // For non-taproot channels, check regular ClosingSigs switch { - case m.ClosingSigs.CloserNoClosee.IsSome(): + case m.CloserNoClosee.IsSome(): r.T.Logf("closer no closee field set, expected: %v", dustExpect) return dustExpect == remoteDustExpect - case m.ClosingSigs.NoCloserClosee.IsSome(): + case m.NoCloserClosee.IsSome(): r.T.Logf("no close closee field set, expected: %v", dustExpect) @@ -639,7 +580,7 @@ func (r *rbfCloserTestHarness) expectHalfSignerIteration( default: r.T.Logf("no dust field set, expected: %v", dustExpect) - return (m.ClosingSigs.CloserAndClosee.IsSome() && + return (m.CloserAndClosee.IsSome() && dustExpect == noDustExpect) } }) @@ -659,10 +600,9 @@ func (r *rbfCloserTestHarness) expectHalfSignerIteration( case *SendOfferEvent: expectedStates = []RbfState{&ClosingNegotiation{}} - // If we expect an extra transition (e.g. restarting from - // ClosePending or CloseErr), then we'll see an additional - // ClosingNegotiation emission from the internal requeue. - if expectExtraTransition { + // If we're in the middle of an iteration, then we expect a + // transition from ClosePending -> LocalCloseStart. + if iteration { expectedStates = append( expectedStates, &ClosingNegotiation{}, ) @@ -698,36 +638,20 @@ func (r *rbfCloserTestHarness) expectHalfSignerIteration( // The proposed fee, as well as our local signature should be // properly stashed in the state. require.Equal(r.T, absoluteFee, offerSentState.ProposedFee) - - switch { - case r.env.LocalMusigSession != nil: - // For taproot, we verify that we have a schnorr signature - // stored. - require.NotNil(r.T, offerSentState.LocalSig) - - // The signature should be marked as schnorr type - sigBytes := offerSentState.LocalSig.RawBytes() - require.Len(r.T, sigBytes, 64) - - // Verify it's not a zero signature - require.NotEqual(r.T, make([]byte, 64), sigBytes) - default: - // For non-taproot channels, we expect the exact ECDSA signature - require.Equal(r.T, localSigWire, offerSentState.LocalSig) - } + require.Equal(r.T, localSigWire, offerSentState.LocalSig) } func (r *rbfCloserTestHarness) assertSingleRbfIteration( initEvent ProtocolEvent, balanceAfterClose, absoluteFee btcutil.Amount, - dustExpect dustExpectation, expectExtraTransition bool) { + dustExpect dustExpectation, iteration bool) { - ctx := context.Background() + ctx := r.T.Context() // We'll now send in the send offer event, which should trigger 1/2 of // the RBF loop, ending us in the LocalOfferSent state. r.expectHalfSignerIteration( - initEvent, balanceAfterClose, absoluteFee, noDustExpect, - expectExtraTransition, + initEvent, balanceAfterClose, absoluteFee, dustExpect, + iteration, ) // Now that we're in the local offer sent state, we'll send the @@ -757,81 +681,12 @@ func (r *rbfCloserTestHarness) assertSingleRbfIteration( r.assertLocalClosePending() } -// newNonceTlv is a helper function that returns a new optional TLV nonce field. -// -//nolint:ll -func newNonceTlv(nonce lnwire.Musig2Nonce) tlv.OptionalRecordT[tlv.TlvType22, lnwire.Musig2Nonce] { - return tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType22](nonce)) -} - -// newPartialSigTlv is a helper function that returns a new optional TLV partial -// sig field. -// -//nolint:ll -func newPartialSigTlv[T tlv.TlvType](ps lnwire.PartialSig) tlv.OptionalRecordT[T, lnwire.PartialSig] { - return tlv.SomeRecordT(tlv.NewRecordT[T](ps)) -} - -// newPartialSigWithNonceTlv is a helper function that returns a new optional -// TLV partial sig with nonce field. -func newPartialSigWithNonceTlv[T tlv.TlvType](psn lnwire.PartialSigWithNonce, -) tlv.OptionalRecordT[T, lnwire.PartialSigWithNonce] { - - return tlv.SomeRecordT(tlv.NewRecordT[T](psn)) -} - -// assertSingleRbfIterationWithNonce is a variant of assertSingleRbfIteration -// that includes nonce handling for taproot channels. -func (r *rbfCloserTestHarness) assertSingleRbfIterationWithNonce( - initEvent ProtocolEvent, balanceAfterClose, absoluteFee btcutil.Amount, - dustExpect dustExpectation, expectExtraTransition bool, - nextCloseeNonce lnwire.Musig2Nonce) { - - ctx := context.Background() - - // We'll now send in the send offer event, which should trigger 1/2 of - // the RBF loop, ending us in the LocalOfferSent state. - r.expectHalfSignerIteration( - initEvent, balanceAfterClose, absoluteFee, noDustExpect, - expectExtraTransition, - ) - - // Now that we're in the local offer sent state, we'll send the response - // of the remote party, which completes one iteration - localSigEvent := &LocalSigReceived{ - SigMsg: lnwire.ClosingSig{ - CloserScript: localAddr, - CloseeScript: remoteAddr, - TaprootPartialSigs: lnwire.TaprootPartialSigs{ - CloserAndClosee: newPartialSigTlv[tlv.TlvType7]( - lnwire.PartialSig{ - Sig: btcec.ModNScalar{}, - }, - ), - }, - NextCloseeNonce: newNonceTlv(nextCloseeNonce), - }, - } - - // Before we send the event, we expect the close the final signature to - // be combined/obtained, and for the close to finalized on disk. - r.expectCloseFinalized( - &localSig, &remoteSig, localAddr, remoteAddr, absoluteFee, - balanceAfterClose, true, - ) - - r.chanCloser.SendEvent(ctx, localSigEvent) - - // We should transition to the pending closing state now. - r.assertLocalClosePending() -} - func (r *rbfCloserTestHarness) assertSingleRemoteRbfIteration( initEvent *OfferReceivedEvent, balanceAfterClose, - absoluteFee btcutil.Amount, sequence uint32, - expectExtraTransition bool, sendInit bool) { + absoluteFee btcutil.Amount, sequence uint32, iteration bool, + sendInit bool) { - ctx := context.Background() + ctx := r.T.Context() // When we receive the signature below, our local state machine should // move to finalize the close. @@ -846,17 +701,21 @@ func (r *rbfCloserTestHarness) assertSingleRemoteRbfIteration( } // Our outer state should transition to ClosingNegotiation state. - // When restarting from ClosePending or CloseErr, the internal - // requeue produces two ClosingNegotiation transitions. We consume - // them in a single call to keep assertions deterministic. - if expectExtraTransition { - r.assertStateTransitions( - &ClosingNegotiation{}, &ClosingNegotiation{}, - ) - } else { - r.assertStateTransitions(&ClosingNegotiation{}) + transitions := []RbfState{ + &ClosingNegotiation{}, } + // If this is an iteration, then we'll go from ClosePending -> + // RemoteCloseStart -> ClosePending. So we'll assert an extra transition + // here. + if iteration { + transitions = append(transitions, &ClosingNegotiation{}) + } + + // Now that we know how many state transitions to expect, we'll wait + // for them. + r.assertStateTransitions(transitions...) + // If we examine the final resting state, we should see that the we're // now in the ClosePending state for the remote peer. currentState := assertStateT[*ClosingNegotiation](r) @@ -872,62 +731,6 @@ func (r *rbfCloserTestHarness) assertSingleRemoteRbfIteration( require.Equal(r.T, closeTx, pendingState.CloseTx) } -// TestSelectTaprootPartialSigWithNonce tests the selection logic for taproot -// partial signatures with nonces. -func TestSelectTaprootPartialSigWithNonce(t *testing.T) { - var ( - nonceNoClosee lnwire.Musig2Nonce - nonceWithClosee lnwire.Musig2Nonce - nonceNoCloser lnwire.Musig2Nonce - emptyPartialSig lnwire.PartialSig - closerNoCloseePS lnwire.PartialSigWithNonce - withCloseePS lnwire.PartialSigWithNonce - noCloserPS lnwire.PartialSigWithNonce - ) - - nonceNoClosee[0] = 0x01 - nonceWithClosee[0] = 0x02 - nonceNoCloser[0] = 0x03 - - closerNoCloseePS = lnwire.PartialSigWithNonce{ - PartialSig: emptyPartialSig, - Nonce: nonceNoClosee, - } - withCloseePS = lnwire.PartialSigWithNonce{ - PartialSig: emptyPartialSig, - Nonce: nonceWithClosee, - } - noCloserPS = lnwire.PartialSigWithNonce{ - PartialSig: emptyPartialSig, - Nonce: nonceNoCloser, - } - - sigsBoth := lnwire.TaprootClosingSigs{ - CloserNoClosee: newPartialSigWithNonceTlv[tlv.TlvType5]( - closerNoCloseePS, - ), - CloserAndClosee: newPartialSigWithNonceTlv[tlv.TlvType7]( - withCloseePS, - ), - } - - selected, err := selectTaprootPartialSigWithNonce(sigsBoth, false) - require.NoError(t, err) - require.Equal(t, nonceWithClosee, selected.Nonce) - - selected, err = selectTaprootPartialSigWithNonce(sigsBoth, true) - require.NoError(t, err) - require.Equal(t, nonceNoClosee, selected.Nonce) - - sigsNoCloser := lnwire.TaprootClosingSigs{ - NoCloserClosee: newPartialSigWithNonceTlv[tlv.TlvType6](noCloserPS), //nolint:ll - } - - selected, err = selectTaprootPartialSigWithNonce(sigsNoCloser, false) - require.NoError(t, err) - require.Equal(t, nonceNoCloser, selected.Nonce) -} - func assertStateT[T ProtocolState](h *rbfCloserTestHarness) T { h.T.Helper() @@ -952,10 +755,7 @@ func newRbfCloserTestHarness(t *testing.T, peerPub := randPubKey(t) - msgMapper := NewRbfMsgMapper( - func() uint32 { return uint32(startingHeight) }, - chanID, *peerPub, - ) + msgMapper := NewRbfMsgMapper(uint32(startingHeight), chanID, *peerPub) initialState := cfg.initialState.UnwrapOr(&ChannelActive{}) @@ -992,15 +792,6 @@ func newRbfCloserTestHarness(t *testing.T, ChanObserver: mockObserver, CloseSigner: mockSigner, } - - // If musig sessions are provided, we set them in the environment. - cfg.localMusigSession.WhenSome(func(session MusigSession) { - env.LocalMusigSession = session - }) - cfg.remoteMusigSession.WhenSome(func(session MusigSession) { - env.RemoteMusigSession = session - }) - harness.env = env var pkScript []byte @@ -1025,7 +816,7 @@ func newRbfCloserTestHarness(t *testing.T, MsgMapper: fn.Some[protofsm.MsgMapper[ProtocolEvent]]( msgMapper, ), - CustomPollInterval: fn.Some(time.Nanosecond), + CustomPollInterval: fn.Some(defaultPoll), } // Before we start we always expect an initial spend event. @@ -1035,8 +826,8 @@ func newRbfCloserTestHarness(t *testing.T, chanCloser := protofsm.NewStateMachine(protoCfg) - // Register the state subscriber before Start() to avoid racing - // with the initial state notification emitted by driveMachine. + // We register our subscriber before starting the state machine, to make + // sure we don't miss any events. harness.stateSub = chanCloser.RegisterStateEvents() chanCloser.Start(ctx) @@ -1056,315 +847,6 @@ func newCloser(t *testing.T, cfg *harnessCfg) *rbfCloserTestHarness { return chanCloser } -// testInitiatorShutdownRecvOkNonTap tests the initiator shutdown received -// scenario for non-taproot channels in the ShutdownPending state. -func testInitiatorShutdownRecvOkNonTap(t *testing.T, ctx context.Context, - startingState *ShutdownPending) { - - t.Run("non_taproot", func(t *testing.T) { - firstState := *startingState - firstState.IdealFeeRate = fn.Some( - chainfee.FeePerKwFloor.FeePerVByte(), - ) - firstState.ShutdownScripts = ShutdownScripts{ - LocalDeliveryScript: localAddr, - RemoteDeliveryScript: remoteAddr, - } - - cfg := &harnessCfg{ - initialState: fn.Some[ProtocolState]( - &firstState, - ), - localUpfrontAddr: fn.Some(localAddr), - remoteUpfrontAddr: fn.Some(remoteAddr), - } - - closeHarness := newCloser(t, cfg) - defer closeHarness.stopAndAssert() - - // We should disable the outgoing adds for the channel at this - // point as well. - closeHarness.expectFinalBalances(fn.None[ShutdownBalances]()) - closeHarness.expectIncomingAddsDisabled() - - // Create shutdown event. - shutdownEvent := &ShutdownReceived{ - ShutdownScript: remoteAddr, - } - - // We'll send in a shutdown received event, with the expected - // co-op close addr. - closeHarness.chanCloser.SendEvent(ctx, shutdownEvent) - - // We should transition to the channel flushing state. - closeHarness.assertStateTransitions(&ChannelFlushing{}) - - // Now we'll ensure that the flushing state has the proper - // co-op close state. - currentState := assertStateT[*ChannelFlushing](closeHarness) - - require.Equal( - t, localAddr, currentState.LocalDeliveryScript, - ) - require.Equal( - t, remoteAddr, currentState.RemoteDeliveryScript, - ) - require.Equal( - t, firstState.IdealFeeRate, currentState.IdealFeeRate, - ) - }) -} - -// testInitiatorShutdownRecvOkTaproot tests the initiator shutdown received -// scenario for taproot channels in the ShutdownPending state. -func testInitiatorShutdownRecvOkTaproot(t *testing.T, ctx context.Context, - startingState *ShutdownPending) { - - t.Run("taproot", func(t *testing.T) { - firstState := *startingState - firstState.IdealFeeRate = fn.Some( - chainfee.FeePerKwFloor.FeePerVByte(), - ) - firstState.ShutdownScripts = ShutdownScripts{ - LocalDeliveryScript: localAddr, - RemoteDeliveryScript: remoteAddr, - } - - localCloseeNonce := lnwire.Musig2Nonce{1, 2, 3} - remoteCloseeNonce := lnwire.Musig2Nonce{4, 5, 6} - - firstState.NonceState = NonceState{ - LocalCloseeNonce: fn.Some(localCloseeNonce), - RemoteCloseeNonce: fn.None[lnwire.Musig2Nonce](), - } - - mockLocalMusig := newMockMusigSession() - mockRemoteMusig := newMockMusigSession() - - cfg := &harnessCfg{ - initialState: fn.Some[ProtocolState]( - &firstState, - ), - localUpfrontAddr: fn.Some(localAddr), - remoteUpfrontAddr: fn.Some(remoteAddr), - localMusigSession: fn.Some[MusigSession]( - mockLocalMusig, - ), - remoteMusigSession: fn.Some[MusigSession]( - mockRemoteMusig, - ), - } - - closeHarness := newCloser(t, cfg) - defer closeHarness.stopAndAssert() - - // We should disable the outgoing adds for the channel at this - // point as well. - closeHarness.expectFinalBalances(fn.None[ShutdownBalances]()) - closeHarness.expectIncomingAddsDisabled() - - // Create shutdown event with nonce for taproot channel. - shutdownEvent := &ShutdownReceived{ - ShutdownScript: remoteAddr, - RemoteShutdownNonce: fn.Some( - remoteCloseeNonce, - ), - } - - // We'll send in a shutdown received event, with the expected - // co-op close addr. - closeHarness.chanCloser.SendEvent(ctx, shutdownEvent) - - // We should transition to the channel flushing state. - closeHarness.assertStateTransitions(&ChannelFlushing{}) - - // Now we'll ensure that the flushing state has the proper - // co-op close state. - currentState := assertStateT[*ChannelFlushing](closeHarness) - - require.Equal( - t, localAddr, currentState.LocalDeliveryScript, - ) - require.Equal( - t, remoteAddr, currentState.RemoteDeliveryScript, - ) - require.Equal( - t, firstState.IdealFeeRate, currentState.IdealFeeRate, - ) - - // Verify nonce state was updated with remote's closee nonce. - require.True( - t, currentState.NonceState.RemoteCloseeNonce.IsSome(), - ) - require.Equal( - t, remoteCloseeNonce, - currentState.NonceState.RemoteCloseeNonce.UnwrapOr( - lnwire.Musig2Nonce{}, - ), - ) - - // Verify musig sessions were set up. - require.NotNil( - t, closeHarness.env.LocalMusigSession, - "LocalMusigSession should not be nil", - ) - require.NotNil( - t, closeHarness.env.RemoteMusigSession, - "RemoteMusigSession should not be nil", - ) - - // Verify InitRemoteNonce was called on LocalMusigSession with - // remote's nonce. This prepares the LocalMusigSession for when - // we act as closer. - require.True( - t, mockLocalMusig.remoteNonceInited, - "LocalMusigSession.InitRemoteNonce "+ - "should have been called", - ) - expectedRemoteNonce := musig2.Nonces{ - PubNonce: remoteCloseeNonce, - } - require.Equal( - t, expectedRemoteNonce, - mockLocalMusig.remoteNonce, - ) - }) -} - -// testRemoteInitiatedCloseOkNonTap tests the remote initiated close scenario -// for non-taproot channels. -func testRemoteInitiatedCloseOkNonTap(t *testing.T, ctx context.Context) { - t.Run("non_taproot", func(t *testing.T) { - cfg := &harnessCfg{ - localUpfrontAddr: fn.Some(localAddr), - } - - closeHarness := newCloser(t, cfg) - defer closeHarness.stopAndAssert() - - // We assert our shutdown events, and also that we eventually - // send a shutdown to the remote party. We'll hold back the - // send in this case though, as we should only send once there - // are no updates dangling. - closeHarness.expectShutdownEvents(shutdownExpect{ - isInitiator: false, - allowSend: false, - recvShutdown: true, - }) - - // Create shutdown event. - shutdownEvent := &ShutdownReceived{ - ShutdownScript: remoteAddr, - } - - // Next, we'll emit the recv event, with the addr of the remote - // party. - closeHarness.chanCloser.SendEvent(ctx, shutdownEvent) - - // We should transition to the shutdown pending state. - closeHarness.assertStateTransitions(&ShutdownPending{}) - - currentState := assertStateT[*ShutdownPending](closeHarness) - - // Both the local and remote shutdown scripts should be set. - require.Equal( - t, localAddr, - currentState.ShutdownScripts.LocalDeliveryScript, - ) - require.Equal( - t, remoteAddr, - currentState.ShutdownScripts.RemoteDeliveryScript, - ) - }) -} - -// testRemoteInitiatedCloseOkTaproot tests the remote initiated close scenario -// for taproot channels. -func testRemoteInitiatedCloseOkTaproot(t *testing.T, ctx context.Context) { - t.Run("taproot", func(t *testing.T) { - remoteCloseeNonce := lnwire.Musig2Nonce{4, 5, 6} - - mockLocalMusig := newMockMusigSession() - mockRemoteMusig := newMockMusigSession() - - cfg := &harnessCfg{ - localUpfrontAddr: fn.Some(localAddr), - localMusigSession: fn.Some[MusigSession]( - mockLocalMusig, - ), - remoteMusigSession: fn.Some[MusigSession]( - mockRemoteMusig, - ), - } - - closeHarness := newCloser(t, cfg) - defer closeHarness.stopAndAssert() - - // We assert our shutdown events, and also that we eventually - // send a shutdown to the remote party. We'll hold back the - // send in this case though, as we should only send once there - // are no updates dangling. - closeHarness.expectShutdownEvents(shutdownExpect{ - isInitiator: false, - allowSend: false, - recvShutdown: true, - }) - - // Create shutdown event with nonce for taproot channel. - shutdownEvent := &ShutdownReceived{ - ShutdownScript: remoteAddr, - RemoteShutdownNonce: fn.Some( - remoteCloseeNonce, - ), - } - - // Next, we'll emit the recv event, with the addr of the remote - // party. - closeHarness.chanCloser.SendEvent(ctx, shutdownEvent) - - // We should transition to the shutdown pending state. - closeHarness.assertStateTransitions(&ShutdownPending{}) - - currentState := assertStateT[*ShutdownPending](closeHarness) - - // Both the local and remote shutdown scripts should be set. - require.Equal( - t, localAddr, - currentState.ShutdownScripts.LocalDeliveryScript, - ) - require.Equal( - t, remoteAddr, - currentState.ShutdownScripts.RemoteDeliveryScript, - ) - - // Verify nonce state was set with remote's closee nonce. - require.True( - t, currentState.NonceState.RemoteCloseeNonce.IsSome(), - ) - require.Equal( - t, remoteCloseeNonce, - currentState.NonceState.RemoteCloseeNonce.UnwrapOr( - lnwire.Musig2Nonce{}, - ), - ) - - // Verify InitRemoteNonce was called on LocalMusigSession. - require.True(t, mockLocalMusig.remoteNonceInited) - expectedRemoteNonce := musig2.Nonces{ - PubNonce: remoteCloseeNonce, - } - require.Equal( - t, expectedRemoteNonce, - mockLocalMusig.remoteNonce, - ) - - // Also verify we generated and stored our local closee nonce. - require.True( - t, currentState.NonceState.LocalCloseeNonce.IsSome(), - ) - }) -} - // TestRbfChannelActiveTransitions tests the transitions of from the // ChannelActive state. func TestRbfChannelActiveTransitions(t *testing.T) { @@ -1466,126 +948,44 @@ func TestRbfChannelActiveTransitions(t *testing.T) { ) }) - // Even when the remote party never committed to an upfront shutdown - // script, we should still validate the delivery script they send, and - // reject one that isn't a well-formed delivery script. - name := "remote_initiated_bad_script_no_upfront_fail" - t.Run(name, func(t *testing.T) { - // The spec dropped p2pkh and p2sh for co-op closes to keep the - // dust calculations uniform, and a delivery script has to be - // something we can actually pay to, so none of these are - // acceptable even though some of them are perfectly valid - // scripts in their own right. - badScripts := []struct { - name string - script lnwire.DeliveryAddress - }{ - { - name: "empty", - script: lnwire.DeliveryAddress{}, - }, - { - name: "garbage", - script: lnwire.DeliveryAddress( - bytes.Repeat([]byte{0xff}, 5), - ), - }, - { - // Provably unspendable: paying a close output - // here would burn the remote party's balance. - name: "op_return", - script: lnwire.DeliveryAddress(append( - []byte{txscript.OP_RETURN, 32}, - bytes.Repeat([]byte{0xAB}, 32)..., - )), - }, - { - name: "bare_op_return", - script: lnwire.DeliveryAddress( - []byte{txscript.OP_RETURN}, - ), - }, - { - name: "p2pkh", - script: lnwire.DeliveryAddress(append(append( - []byte{ - txscript.OP_DUP, - txscript.OP_HASH160, 20, - }, - bytes.Repeat([]byte{0xAB}, 20)..., - ), - txscript.OP_EQUALVERIFY, - txscript.OP_CHECKSIG, - )), - }, - { - name: "p2sh", - script: lnwire.DeliveryAddress(append(append( - []byte{txscript.OP_HASH160, 20}, - bytes.Repeat([]byte{0xAB}, 20)..., - ), txscript.OP_EQUAL)), - }, - } - - for _, badScript := range badScripts { - t.Run(badScript.name, func(t *testing.T) { - // Note the config carries no remoteUpfrontAddr, - // so the only thing standing between the peer's - // script and the rest of the close flow is the - // delivery-script validation itself. - closeHarness := newCloser(t, &harnessCfg{ - localUpfrontAddr: fn.Some(localAddr), - }) - defer closeHarness.stopAndAssert() - - event := &ShutdownReceived{ - ShutdownScript: badScript.script, - } - closeHarness.sendEventAndExpectFailure( - ctx, event, ErrInvalidShutdownScript, - ) - closeHarness.assertNoStateTransitions() - }) - } - }) - // When we receive a shutdown, we should transition to the shutdown // pending state, with the local+remote shutdown addrs known. t.Run("remote_initiated_close_ok", func(t *testing.T) { - // Test both non-taproot and taproot channels. - testRemoteInitiatedCloseOkNonTap(t, ctx) - testRemoteInitiatedCloseOkTaproot(t, ctx) - }) - - // If the remote party sends a shutdown for a taproot channel without a - // nonce, we should reject it. - t.Run("remote_initiated_taproot_no_nonce_fail", func(t *testing.T) { - mockLocalMusig := newMockMusigSession() - mockRemoteMusig := newMockMusigSession() - - cfg := &harnessCfg{ + closeHarness := newCloser(t, &harnessCfg{ localUpfrontAddr: fn.Some(localAddr), - localMusigSession: fn.Some[MusigSession]( - mockLocalMusig, - ), - remoteMusigSession: fn.Some[MusigSession]( - mockRemoteMusig, - ), - } - - closeHarness := newCloser(t, cfg) + }) defer closeHarness.stopAndAssert() - // We'll now create then send a shutdown that is missing their - // shutdown nonce. This should result in an error. - shutdownEvent := &ShutdownReceived{ - ShutdownScript: remoteAddr, - RemoteShutdownNonce: fn.None[lnwire.Musig2Nonce](), - } - closeHarness.sendEventAndExpectFailure( - ctx, shutdownEvent, ErrTaprootShutdownNonceMissing, + // We assert our shutdown events, and also that we eventually + // send a shutdown to the remote party. We'll hold back the + // send in this case though, as we should only send once the no + // updates are dangling. + closeHarness.expectShutdownEvents(shutdownExpect{ + isInitiator: false, + allowSend: false, + recvShutdown: true, + }) + + // Next, we'll emit the recv event, with the addr of the remote + // party. + closeHarness.chanCloser.SendEvent( + ctx, &ShutdownReceived{ShutdownScript: remoteAddr}, + ) + + // We should transition to the shutdown pending state. + closeHarness.assertStateTransitions(&ShutdownPending{}) + + currentState := assertStateT[*ShutdownPending](closeHarness) + + // Both the local and remote shutdown scripts should be set. + require.Equal( + t, localAddr, + currentState.ShutdownScripts.LocalDeliveryScript, + ) + require.Equal( + t, remoteAddr, + currentState.ShutdownScripts.RemoteDeliveryScript, ) - closeHarness.assertNoStateTransitions() }) // Any other event should be ignored. @@ -1648,14 +1048,6 @@ func TestRbfShutdownPendingTransitions(t *testing.T) { // Otherwise, if the shutdown is well composed, then we should // transition to the ChannelFlushing state. t.Run("initiator_shutdown_recv_ok", func(t *testing.T) { - // Test both non-taproot and taproot channels. - testInitiatorShutdownRecvOkNonTap(t, ctx, startingState) - testInitiatorShutdownRecvOkTaproot(t, ctx, startingState) - }) - - // If the remote party sends a shutdown for a taproot channel without - // a nonce in the ShutdownPending state, we should reject it. - t.Run("initiator_shutdown_recv_taproot_no_nonce_fail", func(t *testing.T) { //nolint:ll firstState := *startingState firstState.IdealFeeRate = fn.Some( chainfee.FeePerKwFloor.FeePerVByte(), @@ -1665,43 +1057,38 @@ func TestRbfShutdownPendingTransitions(t *testing.T) { RemoteDeliveryScript: remoteAddr, } - // Set up taproot channel with nonce state - mockLocalMusig := newMockMusigSession() - mockRemoteMusig := newMockMusigSession() - localCloseeNonce := lnwire.Musig2Nonce{1, 2, 3} - - firstState.NonceState = NonceState{ - LocalCloseeNonce: fn.Some(localCloseeNonce), - RemoteCloseeNonce: fn.None[lnwire.Musig2Nonce](), - } - - cfg := &harnessCfg{ + closeHarness := newCloser(t, &harnessCfg{ initialState: fn.Some[ProtocolState]( &firstState, ), localUpfrontAddr: fn.Some(localAddr), remoteUpfrontAddr: fn.Some(remoteAddr), - localMusigSession: fn.Some[MusigSession]( - mockLocalMusig, - ), - remoteMusigSession: fn.Some[MusigSession]( - mockRemoteMusig, - ), - } - - closeHarness := newCloser(t, cfg) + }) defer closeHarness.stopAndAssert() - // Create shutdown event WITHOUT nonce for taproot channel, this - // should fail. - shutdownEvent := &ShutdownReceived{ - ShutdownScript: remoteAddr, - RemoteShutdownNonce: fn.None[lnwire.Musig2Nonce](), - } - closeHarness.sendEventAndExpectFailure( - ctx, shutdownEvent, ErrTaprootShutdownNonceMissing, + // We should disable the outgoing adds for the channel at this + // point as well. + closeHarness.expectFinalBalances(fn.None[ShutdownBalances]()) + closeHarness.expectIncomingAddsDisabled() + + // We'll send in a shutdown received event, with the expected + // co-op close addr. + closeHarness.chanCloser.SendEvent( + ctx, &ShutdownReceived{ShutdownScript: remoteAddr}, + ) + + // We should transition to the channel flushing state. + closeHarness.assertStateTransitions(&ChannelFlushing{}) + + // Now we'll ensure that the flushing state has the proper + // co-op close state. + currentState := assertStateT[*ChannelFlushing](closeHarness) + + require.Equal(t, localAddr, currentState.LocalDeliveryScript) + require.Equal(t, remoteAddr, currentState.RemoteDeliveryScript) + require.Equal( + t, firstState.IdealFeeRate, currentState.IdealFeeRate, ) - closeHarness.assertNoStateTransitions() }) // If we received the shutdown event, then we'll rely on the external @@ -1814,12 +1201,8 @@ func TestRbfShutdownPendingTransitions(t *testing.T) { // This will cause a self transition back to ShutdownPending. closeHarness.assertStateTransitions(&ShutdownPending{}) - // Next, we'll send in a shutdown complete event. The script is - // incidental to what this test exercises, but a shutdown always - // carries one, so we supply the remote party's. - closeHarness.chanCloser.SendEvent(ctx, &ShutdownReceived{ - ShutdownScript: remoteAddr, - }) + // Next, we'll send in a shutdown complete event. + closeHarness.chanCloser.SendEvent(ctx, &ShutdownReceived{}) // We should transition to the channel flushing state, then the // self event to have this state cache he early offer should @@ -1867,74 +1250,96 @@ func TestRbfChannelFlushingTransitions(t *testing.T) { }, } - // When the channel is flushed but the local party cannot cover - // the closing fee, we should transition directly to - // ClosingNegotiation without any further intermediate state - // transitions. - t.Run("local_cannot_pay_for_fee", func(t *testing.T) { - firstState := *startingState + // If send in the channel flushed event, but the local party can't pay + // for fees, then we should just head to the negotiation state. + for _, isFreshFlush := range []bool{true, false} { chanFlushedEvent := *flushTemplate + chanFlushedEvent.FreshFlush = isFreshFlush - closeHarness := newCloser(t, &harnessCfg{ - initialState: fn.Some[ProtocolState]( - &firstState, - ), + testName := fmt.Sprintf("local_cannot_pay_for_fee/"+ + "fresh_flush=%v", isFreshFlush) + + t.Run(testName, func(t *testing.T) { + firstState := *startingState + + closeHarness := newCloser(t, &harnessCfg{ + initialState: fn.Some[ProtocolState]( + &firstState, + ), + }) + defer closeHarness.stopAndAssert() + + // As part of the set up for this state, we'll have the + // final absolute fee required be greater than the + // balance of the local party. + closeHarness.expectFeeEstimate(absoluteFee, 1) + + // If this is a fresh flush, then we expect the state + // to be marked on disk. + if isFreshFlush { + closeHarness.expectChanPendingClose() + } + + // We'll now send in the event which should trigger + // this code path. + closeHarness.chanCloser.SendEvent( + ctx, &chanFlushedEvent, + ) + + // With the event sent, we should now transition + // straight to the ClosingNegotiation state, with no + // further state transitions. + closeHarness.assertStateTransitions( + &ClosingNegotiation{}, + ) }) - defer closeHarness.stopAndAssert() + } - // As part of the set up for this state, we'll have the - // final absolute fee required be greater than the - // balance of the local party. - closeHarness.expectFeeEstimate(absoluteFee, 1) - - // We'll now send in the event which should trigger - // this code path. - closeHarness.chanCloser.SendEvent( - ctx, &chanFlushedEvent, - ) - - // With the event sent, we should now transition - // straight to the ClosingNegotiation state, with no - // further state transitions. - closeHarness.assertStateTransitions( - &ClosingNegotiation{}, - ) - }) - - // When the local party can cover the closing fee, - // ChannelFlushed drives a normal half-signer iteration: we - // move to ClosingNegotiation and send a ClosingComplete - // message. - t.Run("local_can_pay_for_fee", func(t *testing.T) { - firstState := *startingState + for _, isFreshFlush := range []bool{true, false} { flushEvent := *flushTemplate + flushEvent.FreshFlush = isFreshFlush - // We'll modify the starting balance to be 3x the required - // fee to ensure that we can pay for the fee. - flushEvent.ShutdownBalances.LocalBalance = lnwire.NewMSatFromSatoshis( //nolint:ll - absoluteFee * 3, - ) + // We'll modify the starting balance to be 3x the required fee + // to ensure that we can pay for the fee. + localBalanceMSat := lnwire.NewMSatFromSatoshis(absoluteFee * 3) + flushEvent.ShutdownBalances.LocalBalance = localBalanceMSat - closeHarness := newCloser(t, &harnessCfg{ - initialState: fn.Some[ProtocolState]( - &firstState, - ), + testName := fmt.Sprintf("local_can_pay_for_fee/"+ + "fresh_flush=%v", isFreshFlush) + + // This scenario, we'll have the local party be able to pay for + // the fees, which will trigger additional state transitions. + t.Run(testName, func(t *testing.T) { + firstState := *startingState + + closeHarness := newCloser(t, &harnessCfg{ + initialState: fn.Some[ProtocolState]( + &firstState, + ), + }) + defer closeHarness.stopAndAssert() + + localBalance := flushEvent.ShutdownBalances.LocalBalance + balanceAfterClose := localBalance.ToSatoshis() - + absoluteFee + + // If this is a fresh flush, then we expect the state + // to be marked on disk. + if isFreshFlush { + closeHarness.expectChanPendingClose() + } + + // From here, we expect the state transition to go + // back to closing negotiated, for a ClosingComplete + // message to be sent and then for us to terminate at + // that state. This is 1/2 of the normal RBF signer + // flow. + closeHarness.expectHalfSignerIteration( + &flushEvent, balanceAfterClose, absoluteFee, + noDustExpect, false, + ) }) - defer closeHarness.stopAndAssert() - - localBalance := flushEvent.ShutdownBalances.LocalBalance - balanceAfterClose := localBalance.ToSatoshis() - absoluteFee - - // From here, we expect the state transition to go - // back to closing negotiated, for a ClosingComplete - // message to be sent and then for us to terminate at - // that state. This is 1/2 of the normal RBF signer - // flow. - closeHarness.expectHalfSignerIteration( - &flushEvent, balanceAfterClose, absoluteFee, - noDustExpect, false, - ) - }) + } // This tests that if we receive an `OfferReceivedEvent` while in the // flushing state, then we'll cache that, and once we receive @@ -1965,9 +1370,7 @@ func TestRbfChannelFlushingTransitions(t *testing.T) { CloserScript: remoteAddr, CloseeScript: localAddr, ClosingSigs: lnwire.ClosingSigs{ - CloserAndClosee: newSigTlv[tlv.TlvType3]( //nolint:ll - remoteWireSig, - ), + CloserAndClosee: remoteSigRecordType3, }, }, } @@ -1997,370 +1400,9 @@ func TestRbfChannelFlushingTransitions(t *testing.T) { assertSpendEventCloseFin(t, startingState) } -// testSendOfferRbfIterationLoopNonTap tests the RBF iteration loop for -// non-taproot channels. -func testSendOfferRbfIterationLoopNonTap(t *testing.T, - closeTerms *CloseChannelTerms, - sendOfferEvent *SendOfferEvent, - balanceAfterClose btcutil.Amount, - absoluteFee btcutil.Amount) { - - t.Run("non_taproot", func(t *testing.T) { - firstState := &ClosingNegotiation{ - PeerState: lntypes.Dual[AsymmetricPeerState]{ - Local: &LocalCloseStart{ - CloseChannelTerms: closeTerms, - }, - }, - CloseChannelTerms: closeTerms, - } - - cfg := &harnessCfg{ - initialState: fn.Some[ProtocolState]( - firstState, - ), - localUpfrontAddr: fn.Some(localAddr), - } - - closeHarness := newCloser(t, cfg) - defer closeHarness.stopAndAssert() - - closeHarness.assertSingleRbfIteration( - sendOfferEvent, balanceAfterClose, absoluteFee, - noDustExpect, false, - ) - - rbfFeeBump := chainfee.FeePerKwFloor.FeePerVByte() * 10 - localOffer := &SendOfferEvent{ - TargetFeeRate: rbfFeeBump, - } - - closeHarness.assertSingleRbfIteration( - localOffer, balanceAfterClose, absoluteFee, - noDustExpect, true, - ) - }) -} - -func testSendOfferRbfIterationLoopTaproot(t *testing.T, - closeTerms *CloseChannelTerms, - sendOfferEvent *SendOfferEvent, - balanceAfterClose btcutil.Amount, - absoluteFee btcutil.Amount) { - - t.Run("taproot", func(t *testing.T) { - firstState := &ClosingNegotiation{ - PeerState: lntypes.Dual[AsymmetricPeerState]{ - Local: &LocalCloseStart{ - CloseChannelTerms: closeTerms, - }, - }, - CloseChannelTerms: closeTerms, - } - - firstState.CloseChannelTerms.NonceState = NonceState{ - LocalCloseeNonce: fn.Some( - lnwire.Musig2Nonce{1, 2, 3}, - ), - RemoteCloseeNonce: fn.Some( - lnwire.Musig2Nonce{4, 5, 6}, - ), - } - localState, ok := firstState.PeerState.Local.(*LocalCloseStart) - require.True(t, ok) - localState.CloseChannelTerms.NonceState = - firstState.CloseChannelTerms.NonceState - - cfg := &harnessCfg{ - initialState: fn.Some[ProtocolState]( - firstState, - ), - localUpfrontAddr: fn.Some(localAddr), - localMusigSession: fn.Some[MusigSession]( - newMockMusigSession(), - ), - remoteMusigSession: fn.Some[MusigSession]( - newMockMusigSession(), - ), - } - - closeHarness := newCloser(t, cfg) - defer closeHarness.stopAndAssert() - - closeHarness.assertSingleRbfIterationWithNonce( - sendOfferEvent, balanceAfterClose, absoluteFee, - noDustExpect, false, - lnwire.Musig2Nonce{7, 8, 9}, - ) - - rbfFeeBump := chainfee.FeePerKwFloor.FeePerVByte() * 10 - localOffer := &SendOfferEvent{ - TargetFeeRate: rbfFeeBump, - } - - closeHarness.assertSingleRbfIterationWithNonce( - localOffer, balanceAfterClose, absoluteFee, - noDustExpect, true, - lnwire.Musig2Nonce{10, 11, 12}, - ) - }) -} - -// testRecvOfferRbfLoopIterationsNonTap tests the receive offer RBF loop -// iteration scenario for non-taproot channels. -func testRecvOfferRbfLoopIterationsNonTap(t *testing.T, - closeTerms *CloseChannelTerms, - absoluteFee btcutil.Amount) { - - t.Run("non_taproot", func(t *testing.T) { - closingTerms := *closeTerms - closingTerms.ShutdownBalances.LocalBalance = - lnwire.NewMSatFromSatoshis(9000) - - firstState := &ClosingNegotiation{ - PeerState: lntypes.Dual[AsymmetricPeerState]{ - Local: &LocalCloseStart{ - CloseChannelTerms: &closingTerms, - }, - Remote: &RemoteCloseStart{ - CloseChannelTerms: &closingTerms, - }, - }, - CloseChannelTerms: &closingTerms, - } - - cfg := &harnessCfg{ - initialState: fn.Some[ProtocolState]( - firstState, - ), - localUpfrontAddr: fn.Some(localAddr), - } - - closeHarness := newCloser(t, cfg) - defer closeHarness.stopAndAssert() - - balanceAfterClose := closingTerms.ShutdownBalances.RemoteBalance.ToSatoshis() - absoluteFee //nolint:ll - sequence := uint32(mempool.MaxRBFSequence) - - feeOffer := &OfferReceivedEvent{ - SigMsg: lnwire.ClosingComplete{ - CloserScript: remoteAddr, - CloseeScript: localAddr, - FeeSatoshis: absoluteFee, - LockTime: 1, - ClosingSigs: lnwire.ClosingSigs{ - CloserAndClosee: newSigTlv[tlv.TlvType3]( //nolint:ll - remoteWireSig, - ), - }, - }, - } - - closeHarness.assertSingleRemoteRbfIteration( - feeOffer, balanceAfterClose, absoluteFee, - sequence, false, true, - ) - - feeOffer.SigMsg.FeeSatoshis += 1000 - absoluteFee = feeOffer.SigMsg.FeeSatoshis - closeHarness.assertSingleRemoteRbfIteration( - feeOffer, balanceAfterClose, absoluteFee, - sequence, true, true, - ) - - closeHarness.assertNoStateTransitions() - }) -} - -// testRecvOfferRbfLoopIterationsTaproot tests the receive offer RBF loop -// iteration scenario for taproot channels. -func testRecvOfferRbfLoopIterationsTaproot(t *testing.T, - closeTerms *CloseChannelTerms, - absoluteFee btcutil.Amount) { - - t.Run("taproot", func(t *testing.T) { - closingTerms := *closeTerms - closingTerms.ShutdownBalances.LocalBalance = - lnwire.NewMSatFromSatoshis(9000) - - firstState := &ClosingNegotiation{ - PeerState: lntypes.Dual[AsymmetricPeerState]{ - Local: &LocalCloseStart{ - CloseChannelTerms: &closingTerms, - }, - Remote: &RemoteCloseStart{ - CloseChannelTerms: &closingTerms, - }, - }, - CloseChannelTerms: &closingTerms, - } - - nonceState := NonceState{ - LocalCloseeNonce: fn.Some( - lnwire.Musig2Nonce{1, 2, 3}, - ), - RemoteCloseeNonce: fn.Some( - lnwire.Musig2Nonce{4, 5, 6}, - ), - } - firstState.CloseChannelTerms.NonceState = nonceState - - localState, ok := firstState.PeerState.Local.(*LocalCloseStart) - require.True(t, ok) - localState.CloseChannelTerms.NonceState = nonceState - - remoteState, ok := firstState.PeerState.Remote.(*RemoteCloseStart) //nolint:ll - require.True(t, ok) - remoteState.CloseChannelTerms.NonceState = nonceState - - cfg := &harnessCfg{ - initialState: fn.Some[ProtocolState]( - firstState, - ), - localUpfrontAddr: fn.Some(localAddr), - localMusigSession: fn.Some[MusigSession]( - newMockMusigSession(), - ), - remoteMusigSession: fn.Some[MusigSession]( - newMockMusigSession(), - ), - } - - closeHarness := newCloser(t, cfg) - defer closeHarness.stopAndAssert() - - balanceAfterClose := closingTerms.ShutdownBalances.RemoteBalance.ToSatoshis() - absoluteFee //nolint:ll - sequence := uint32(mempool.MaxRBFSequence) - - closingSigs := lnwire.TaprootClosingSigs{ - CloserAndClosee: newPartialSigWithNonceTlv[tlv.TlvType7]( //nolint:ll - lnwire.PartialSigWithNonce{ - PartialSig: lnwire.PartialSig{ - Sig: btcec.ModNScalar{}, - }, - Nonce: lnwire.Musig2Nonce{ - 10, 11, 12, - }, - }, - ), - } - feeOffer := &OfferReceivedEvent{ - SigMsg: lnwire.ClosingComplete{ - CloserScript: remoteAddr, - CloseeScript: localAddr, - FeeSatoshis: absoluteFee, - LockTime: 1, - TaprootClosingSigs: closingSigs, - }, - } - - closeHarness.assertSingleRemoteRbfIteration( - feeOffer, balanceAfterClose, absoluteFee, - sequence, false, true, - ) - - feeOffer.SigMsg.FeeSatoshis += 1000 - absoluteFee = feeOffer.SigMsg.FeeSatoshis - closeHarness.assertSingleRemoteRbfIteration( - feeOffer, balanceAfterClose, absoluteFee, - sequence, true, true, - ) - - closeHarness.assertNoStateTransitions() - }) -} - -// testSendOfferIterationNoDustNonTap tests the send offer iteration -// scenario for non-taproot channels. -func testSendOfferIterationNoDustNonTap(t *testing.T, - startingState *ClosingNegotiation, - sendOfferEvent *SendOfferEvent, - balanceAfterClose btcutil.Amount, - absoluteFee btcutil.Amount) { - - t.Run("non_taproot", func(t *testing.T) { - testStartingState := *startingState - - cfg := &harnessCfg{ - initialState: fn.Some[ProtocolState]( - &testStartingState, - ), - } - - closeHarness := newCloser(t, cfg) - defer closeHarness.stopAndAssert() - - closeHarness.assertSingleRbfIteration( - sendOfferEvent, balanceAfterClose, absoluteFee, - noDustExpect, false, - ) - }) -} - -// testSendOfferIterationNoDustTaproot tests the send offer iteration -// scenario for taproot channels. -func testSendOfferIterationNoDustTaproot(t *testing.T, - startingState *ClosingNegotiation, - sendOfferEvent *SendOfferEvent, - balanceAfterClose btcutil.Amount, - absoluteFee btcutil.Amount) { - - t.Run("taproot", func(t *testing.T) { - nextCloseeNonce := lnwire.Musig2Nonce{7, 8, 9} - - testStartingState := *startingState - testStartingState.CloseChannelTerms.NonceState = NonceState{ - LocalCloseeNonce: fn.Some( - lnwire.Musig2Nonce{1, 2, 3}, - ), - RemoteCloseeNonce: fn.Some( - lnwire.Musig2Nonce{4, 5, 6}, - ), - } - - localState, ok := testStartingState.PeerState.Local.(*LocalCloseStart) //nolint:ll - require.True(t, ok) - localState.CloseChannelTerms.NonceState = - testStartingState.CloseChannelTerms.NonceState - - cfg := &harnessCfg{ - initialState: fn.Some[ProtocolState]( - &testStartingState, - ), - localMusigSession: fn.Some[MusigSession]( - newMockMusigSession(), - ), - remoteMusigSession: fn.Some[MusigSession]( - newMockMusigSession(), - ), - } - - closeHarness := newCloser(t, cfg) - defer closeHarness.stopAndAssert() - - closeHarness.assertSingleRbfIterationWithNonce( - sendOfferEvent, balanceAfterClose, absoluteFee, - noDustExpect, false, nextCloseeNonce, - ) - - // Verify nonce state was updated with new closee nonce. - currentState := assertStateT[*ClosingNegotiation]( - closeHarness, - ) - require.True( - t, - currentState.CloseChannelTerms.NonceState.RemoteCloseeNonce.IsSome(), //nolint:ll - ) - require.Equal( - t, nextCloseeNonce, - currentState.CloseChannelTerms.NonceState.RemoteCloseeNonce.UnwrapOr(lnwire.Musig2Nonce{}), //nolint:ll - ) - }) -} - // TestRbfCloseClosingNegotiationLocal tests the local portion of the primary // RBF close loop. We should be able to transition to a close state, get a sig, -// then restart all over again to re-request a signature at a new higher fee +// then restart all over again to re-request a signature of at new higher fee // rate. func TestRbfCloseClosingNegotiationLocal(t *testing.T) { t.Parallel() @@ -2401,13 +1443,17 @@ func TestRbfCloseClosingNegotiationLocal(t *testing.T) { // In this state, we'll simulate deciding that we need to send a new // offer to the remote party. t.Run("send_offer_iteration_no_dust", func(t *testing.T) { - testSendOfferIterationNoDustNonTap( - t, startingState, sendOfferEvent, - balanceAfterClose, absoluteFee, - ) - testSendOfferIterationNoDustTaproot( - t, startingState, sendOfferEvent, - balanceAfterClose, absoluteFee, + closeHarness := newCloser(t, &harnessCfg{ + initialState: fn.Some[ProtocolState](startingState), + }) + defer closeHarness.stopAndAssert() + + // We'll now send in the initial sender offer event, which + // should then trigger a single RBF iteration, ending at the + // pending state. + closeHarness.assertSingleRbfIteration( + sendOfferEvent, balanceAfterClose, absoluteFee, + noDustExpect, false, ) }) @@ -2436,9 +1482,7 @@ func TestRbfCloseClosingNegotiationLocal(t *testing.T) { CloserNoClosee: newSigTlv[tlv.TlvType1]( remoteWireSig, ), - CloserAndClosee: newSigTlv[tlv.TlvType3]( //nolint:ll - remoteWireSig, - ), + CloserAndClosee: remoteSigRecordType3, }, }, } @@ -2533,9 +1577,7 @@ func TestRbfCloseClosingNegotiationLocal(t *testing.T) { CloserScript: remoteAddr, CloseeScript: remoteAddr, ClosingSigs: lnwire.ClosingSigs{ - CloserAndClosee: newSigTlv[tlv.TlvType3]( //nolint:ll - remoteWireSig, - ), + CloserAndClosee: remoteSigRecordType3, }, }, } @@ -2547,13 +1589,41 @@ func TestRbfCloseClosingNegotiationLocal(t *testing.T) { // In this test, we'll assert that we're able to restart the RBF loop // to trigger additional signature iterations. t.Run("send_offer_rbf_iteration_loop", func(t *testing.T) { - testSendOfferRbfIterationLoopNonTap( - t, closeTerms, sendOfferEvent, - balanceAfterClose, absoluteFee, + firstState := &ClosingNegotiation{ + PeerState: lntypes.Dual[AsymmetricPeerState]{ + Local: &LocalCloseStart{ + CloseChannelTerms: closeTerms, + }, + }, + CloseChannelTerms: closeTerms, + } + + closeHarness := newCloser(t, &harnessCfg{ + initialState: fn.Some[ProtocolState](firstState), + localUpfrontAddr: fn.Some(localAddr), + }) + defer closeHarness.stopAndAssert() + + // We'll start out by first triggering a routine iteration, + // assuming we start in this negotiation state. + closeHarness.assertSingleRbfIteration( + sendOfferEvent, balanceAfterClose, absoluteFee, + noDustExpect, false, ) - testSendOfferRbfIterationLoopTaproot( - t, closeTerms, sendOfferEvent, - balanceAfterClose, absoluteFee, + + // Next, we'll send in a new SendOfferEvent event which + // simulates the user requesting a RBF fee bump. We'll use 10x + // the fee we used in the last iteration. + rbfFeeBump := chainfee.FeePerKwFloor.FeePerVByte() * 10 + localOffer := &SendOfferEvent{ + TargetFeeRate: rbfFeeBump, + } + + // Now we expect that another full RBF iteration takes place (we + // initiate a new local sig). + closeHarness.assertSingleRbfIteration( + localOffer, balanceAfterClose, absoluteFee, + noDustExpect, true, ) }) @@ -2611,99 +1681,6 @@ func TestRbfCloseClosingNegotiationLocal(t *testing.T) { assertSpendEventCloseFin(t, startingState) } -// TestValidateSigTypeMatchesChannelType tests that taproot channels reject -// regular signatures and non-taproot channels reject taproot signatures. -func TestValidateSigTypeMatchesChannelType(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - isTaproot bool - sendTaproot bool - expectedError string - }{ - { - name: "taproot channel with regular sig", - isTaproot: true, - sendTaproot: false, - expectedError: "taproot channel requires taproot " + - "signature", - }, - { - name: "regular channel with taproot sig", - isTaproot: false, - sendTaproot: true, - expectedError: "non-taproot channel requires regular " + - "signatures", - }, - { - name: "taproot channel with taproot sig", - isTaproot: true, - sendTaproot: true, - }, - { - name: "regular channel with regular sig", - isTaproot: false, - sendTaproot: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Create a message with mismatched signature type - var sigMsg lnwire.ClosingSig - if tc.sendTaproot { - // Send taproot signature using - // TaprootPartialSigs. Create a - // dummy partial sig. - var scalar btcec.ModNScalar - scalar.SetByteSlice(localSchnorrSigBytes[:32]) - partialSig := lnwire.PartialSig{Sig: scalar} - - sigMsg.TaprootPartialSigs.CloserAndClosee = tlv.SomeRecordT( //nolint:ll - tlv.NewRecordT[tlv.TlvType7]( - partialSig, - ), - ) - - testNonce := lnwire.Musig2Nonce{7, 8, 9} - sigMsg.NextCloseeNonce = tlv.SomeRecordT( //nolint:ll - tlv.NewRecordT[tlv.TlvType22]( - testNonce, - ), - ) - } else { - sigs := &sigMsg.ClosingSigs - sigs.CloserAndClosee = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType3]( - localSigWire, - ), - ) - } - - sigResult, nonce := validateAndExtractSigAndNonce( - sigMsg, tc.isTaproot, - ) - - if tc.expectedError != "" { - _, err := sigResult.Unpack() - require.Error(t, err) - require.Contains( - t, err.Error(), tc.expectedError, - ) - } else { - sig, err := sigResult.Unpack() - require.NoError(t, err) - require.NotNil(t, sig) - - if tc.isTaproot { - require.True(t, nonce.IsSome()) - } - } - }) - } -} - // TestRbfCloseClosingNegotiationRemote tests that state machine is able to // handle RBF iterations to sign for the closing transaction of the remote // party. @@ -2766,7 +1743,7 @@ func TestRbfCloseClosingNegotiationRemote(t *testing.T) { closeHarness.assertNoStateTransitions() }) - // If our balance, is dust, then the remote party should send a + // If our balance is dust, then the remote party should send a // signature that doesn't include our output. t.Run("recv_offer_err_closer_no_closee", func(t *testing.T) { // We'll modify our local balance to be dust. @@ -2798,9 +1775,7 @@ func TestRbfCloseClosingNegotiationRemote(t *testing.T) { CloserScript: remoteAddr, CloseeScript: localAddr, ClosingSigs: lnwire.ClosingSigs{ - CloserAndClosee: newSigTlv[tlv.TlvType3]( //nolint:ll - remoteWireSig, - ), + CloserAndClosee: remoteSigRecordType3, }, }, } @@ -2826,9 +1801,7 @@ func TestRbfCloseClosingNegotiationRemote(t *testing.T) { CloserScript: remoteAddr, CloseeScript: localAddr, ClosingSigs: lnwire.ClosingSigs{ - CloserNoClosee: newSigTlv[tlv.TlvType1]( //nolint:ll - remoteWireSig, - ), + CloserNoClosee: remoteSigRecordType1, }, }, } @@ -2838,58 +1811,66 @@ func TestRbfCloseClosingNegotiationRemote(t *testing.T) { closeHarness.assertNoStateTransitions() }) - // When both CloserNoClosee AND CloserAndClosee are present (which is - // spec-compliant), the closee should select CloserAndClosee when local - // output is not dust. - t.Run("recv_offer_both_sigs_present", func(t *testing.T) { - closeHarness := newCloser(t, &harnessCfg{ - initialState: fn.Some[ProtocolState](startingState), - }) - defer closeHarness.stopAndAssert() - - // Per BOLT spec, when closee's output is not dust, sender MUST - // send both CloserNoClosee and CloserAndClosee sigs. The - // receiver should select CloserAndClosee. - event := &OfferReceivedEvent{ - SigMsg: lnwire.ClosingComplete{ - FeeSatoshis: absoluteFee, - CloserScript: remoteAddr, - CloseeScript: localAddr, - ClosingSigs: lnwire.ClosingSigs{ - CloserNoClosee: newSigTlv[tlv.TlvType1]( //nolint:ll - remoteWireSig, - ), - CloserAndClosee: newSigTlv[tlv.TlvType3]( //nolint:ll - remoteWireSig, - ), - }, - }, - } - - balanceAfterClose := localBalance.ToSatoshis() - absoluteFee - closeHarness.expectRemoteCloseFinalized( - &localSig, &remoteSig, localAddr, remoteAddr, - absoluteFee, balanceAfterClose, false, - ) - - closeHarness.chanCloser.SendEvent(ctx, event) - - // We should remain in ClosingNegotiation (outer state doesn't - // change when receiving an offer). We also shouldn't have - // errored out. - closeHarness.assertStateTransitions(&ClosingNegotiation{}) - }) - // If everything lines up, then we should be able to do multiple RBF // loops to enable the remote party to sign.new versions of the co-op // close transaction. t.Run("recv_offer_rbf_loop_iterations", func(t *testing.T) { - testRecvOfferRbfLoopIterationsNonTap( - t, closeTerms, absoluteFee, + // We'll modify our balance s.t we're unable to pay for fees, + // but aren't yet dust. + closingTerms := *closeTerms + closingTerms.ShutdownBalances.LocalBalance = lnwire.NewMSatFromSatoshis( //nolint:ll + 9000, ) - testRecvOfferRbfLoopIterationsTaproot( - t, closeTerms, absoluteFee, + + firstState := &ClosingNegotiation{ + PeerState: lntypes.Dual[AsymmetricPeerState]{ + Local: &LocalCloseStart{ + CloseChannelTerms: &closingTerms, + }, + Remote: &RemoteCloseStart{ + CloseChannelTerms: &closingTerms, + }, + }, + CloseChannelTerms: &closingTerms, + } + + closeHarness := newCloser(t, &harnessCfg{ + initialState: fn.Some[ProtocolState](firstState), + localUpfrontAddr: fn.Some(localAddr), + }) + defer closeHarness.stopAndAssert() + + feeOffer := &OfferReceivedEvent{ + SigMsg: lnwire.ClosingComplete{ + CloserScript: remoteAddr, + CloseeScript: localAddr, + FeeSatoshis: absoluteFee, + LockTime: 1, + ClosingSigs: lnwire.ClosingSigs{ + CloserAndClosee: remoteSigRecordType3, + }, + }, + } + + // As we're already in the negotiation phase, we'll now trigger + // a new iteration by having the remote party send a new offer + // sig. + closeHarness.assertSingleRemoteRbfIteration( + feeOffer, balanceAfterClose, absoluteFee, sequence, + false, true, ) + + // Next, we'll receive an offer from the remote party, and drive + // another RBF iteration. This time, we'll increase the absolute + // fee by 1k sats. + feeOffer.SigMsg.FeeSatoshis += 1000 + absoluteFee = feeOffer.SigMsg.FeeSatoshis + closeHarness.assertSingleRemoteRbfIteration( + feeOffer, balanceAfterClose, absoluteFee, sequence, + true, true, + ) + + closeHarness.assertNoStateTransitions() }) // This tests that if we get an offer that has the wrong local script, @@ -2910,9 +1891,7 @@ func TestRbfCloseClosingNegotiationRemote(t *testing.T) { CloserScript: remoteAddr, CloseeScript: remoteAddr, ClosingSigs: lnwire.ClosingSigs{ - CloserNoClosee: newSigTlv[tlv.TlvType1]( //nolint:ll - remoteWireSig, - ), + CloserNoClosee: remoteSigRecordType1, }, }, } @@ -2961,9 +1940,7 @@ func TestRbfCloseClosingNegotiationRemote(t *testing.T) { FeeSatoshis: absoluteFee, LockTime: 1, ClosingSigs: lnwire.ClosingSigs{ - CloserAndClosee: newSigTlv[tlv.TlvType3]( //nolint:ll - remoteWireSig, - ), + CloserAndClosee: remoteSigRecordType3, }, }, } @@ -3025,10 +2002,8 @@ func TestRbfCloseErr(t *testing.T) { TargetFeeRate: rbfFeeBump, } - // Now we expect that another full RBF iteration takes place - // (we initiate a new local sig). Restarting from CloseErr - // produces an extra ClosingNegotiation transition from the - // internal requeue, same as a regular iteration. + // Now we expect that another full RBF iteration takes place (we + // initiate a new local sig). closeHarness.assertSingleRbfIteration( localOffer, balanceAfterClose, absoluteFee, noDustExpect, true, @@ -3061,20 +2036,16 @@ func TestRbfCloseErr(t *testing.T) { FeeSatoshis: absoluteFee, LockTime: 1, ClosingSigs: lnwire.ClosingSigs{ - CloserAndClosee: newSigTlv[tlv.TlvType3]( //nolint:ll - remoteWireSig, - ), + CloserAndClosee: remoteSigRecordType3, }, }, } sequence := uint32(mempool.MaxRBFSequence) - // As we're already in the negotiation phase, we'll now - // trigger a new iteration by having the remote party send - // a new offer sig. Restarting from CloseErr produces an - // extra ClosingNegotiation transition from the internal - // requeue, same as a regular iteration. + // As we're already in the negotiation phase, we'll now trigger + // a new iteration by having the remote party send a new offer + // sig. closeHarness.assertSingleRemoteRbfIteration( feeOffer, balanceAfterClose, absoluteFee, sequence, true, true, @@ -3084,332 +2055,3 @@ func TestRbfCloseErr(t *testing.T) { // Sending a Spend event should transition to CloseFin. assertSpendEventCloseFin(t, startingState) } - -// generateTestNonce creates a test musig2 nonce for testing. -func generateTestNonce(t *testing.T) *musig2.Nonces { - t.Helper() - - // Generate a dummy private key for nonce generation. - privKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - nonce, err := musig2.GenNonces(musig2.WithPublicKey(privKey.PubKey())) - require.NoError(t, err) - - return nonce -} - -// TestTaprootNonceHandling tests the taproot nonce handling functionality -// in the RBF cooperative close state machine. -func TestTaprootNonceHandling(t *testing.T) { - t.Parallel() - - closeHarness := newCloser(t, &harnessCfg{ - localUpfrontAddr: fn.Some(localAddr), - }) - defer closeHarness.stopAndAssert() - - // Set up mock MusigSessions to indicate this is a taproot channel. - mockLocalSession := newMockMusigSession() - mockRemoteSession := newMockMusigSession() - closeHarness.env.LocalMusigSession = mockLocalSession - closeHarness.env.RemoteMusigSession = mockRemoteSession - - closeHarness.expectShutdownEvents(shutdownExpect{ - isInitiator: false, - allowSend: false, - recvShutdown: true, - }) - - remoteNonce := generateTestNonce(t) - shutdownEvent := &ShutdownReceived{ - ShutdownScript: remoteAddr, - BlockHeight: 100, - RemoteShutdownNonce: fn.Some(lnwire.Musig2Nonce( - remoteNonce.PubNonce, - )), - } - - // Send the shutdown event and verify state transition. We should - // transition to ShutdownPending. - closeHarness.chanCloser.SendEvent( - t.Context(), shutdownEvent, - ) - - closeHarness.assertStateTransitions(&ShutdownPending{}) - - // Verify the state transition occurred and the nonce was stored. - currentState := assertStateT[*ShutdownPending](closeHarness) - require.True(t, currentState.NonceState.RemoteCloseeNonce.IsSome(), - "remote closee nonce should be stored") - - storedNonce := currentState.NonceState.RemoteCloseeNonce.UnwrapOrFail(t) - require.Equal( - t, lnwire.Musig2Nonce(remoteNonce.PubNonce), storedNonce, - "stored nonce should match received nonce", - ) -} - -// TestNextCloseeNonceStorageFromClosingSig tests that -// updateAndValidateCloseTerms does NOT modify RemoteCloseeNonce. The nonce -// rotation is handled by LocalOfferSent.ProcessEvent, keeping close term -// validation separate from nonce state management. -func TestNextCloseeNonceStorageFromClosingSig(t *testing.T) { - t.Parallel() - - // Create a closing negotiation state with taproot. - originalNonce := lnwire.Musig2Nonce{4, 5, 6} - closeTerms := &CloseChannelTerms{ - ShutdownScripts: ShutdownScripts{ - LocalDeliveryScript: localAddr, - RemoteDeliveryScript: remoteAddr, - }, - NonceState: NonceState{ - LocalCloseeNonce: fn.Some(lnwire.Musig2Nonce{1, 2, 3}), - RemoteCloseeNonce: fn.Some(originalNonce), - }, - } - - negotiation := &ClosingNegotiation{ - CloseChannelTerms: closeTerms, - } - - // Create a LocalSigReceived event with NextCloseeNonce for the next - // round. - nextCloseeNonce := lnwire.Musig2Nonce{10, 11, 12} - sigEvent := &LocalSigReceived{ - SigMsg: lnwire.ClosingSig{ - CloserScript: localAddr, - CloseeScript: remoteAddr, - FeeSatoshis: btcutil.Amount(1000), - LockTime: 1, - TaprootPartialSigs: lnwire.TaprootPartialSigs{ - CloserAndClosee: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType7]( - lnwire.PartialSig{ - Sig: btcec.ModNScalar{}, - }, - ), - ), - }, - NextCloseeNonce: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType22](nextCloseeNonce), - ), - }, - } - - // updateAndValidateCloseTerms should only validate close terms, not - // update the nonce. The nonce rotation happens in - // LocalOfferSent.ProcessEvent. - env := &Environment{ChainParams: chaincfg.RegressionNetParams} - err := negotiation.updateAndValidateCloseTerms(sigEvent, env) - require.NoError(t, err) - - // Verify the RemoteCloseeNonce was NOT modified — it should still - // hold the original nonce from shutdown. - storedNonce := negotiation.NonceState.RemoteCloseeNonce.UnwrapOrFail(t) - require.Equal( - t, originalNonce, storedNonce, - "updateAndValidateCloseTerms should not modify "+ - "RemoteCloseeNonce", - ) -} - -// TestProcessRemoteTaprootSigWithSignerNonce tests that processRemoteTaprootSig -// properly initializes the musig session with the nonce from -// PartialSigWithNonce. -func TestProcessRemoteTaprootSigWithSignerNonce(t *testing.T) { - t.Parallel() - - // Create a mock musig session that tracks InitRemoteNonce calls - mockRemoteMusig := newMockMusigSession() - - // The session should already be initialized from shutdown - mockRemoteMusig.remoteNonceInited = true - mockRemoteMusig.remoteNonce = musig2.Nonces{ - PubNonce: lnwire.Musig2Nonce{4, 5, 6}, - } - - env := &Environment{ - RemoteMusigSession: mockRemoteMusig, - } - - // Create a ClosingComplete message with signer nonce. - signerNonce := lnwire.Musig2Nonce{10, 11, 12} - jitNonce := lnwire.Musig2Nonce{20, 21, 22} - msg := lnwire.ClosingComplete{ - TaprootClosingSigs: lnwire.TaprootClosingSigs{ - CloserAndClosee: newPartialSigWithNonceTlv[tlv.TlvType7]( //nolint:ll - lnwire.PartialSigWithNonce{ - PartialSig: lnwire.PartialSig{ - Sig: btcec.ModNScalar{}, - }, - Nonce: signerNonce, - }, - ), - }, - } - - _, err := processRemoteTaprootSig( - env, msg, fn.Some(jitNonce), false, - ) - require.NoError(t, err) - - // Verify the musig session was re-initialized with the JIT nonce - // parameter (the nonce they used to sign as the closer) - require.True( - t, mockRemoteMusig.remoteNonceInited, - "InitRemoteNonce should be called", - ) - - // The session should have the JIT nonce, not the signer nonce from - // PartialSigWithNonce. - require.Equal( - t, musig2.Nonces{PubNonce: jitNonce}, - mockRemoteMusig.remoteNonce, - "musig session should be updated with JIT closer nonce", - ) -} - -// strictNonceMusigSession is a mock that enforces the correct ordering: -// InitRemoteNonce must be called before ProposalClosingOpts. -type strictNonceMusigSession struct { - remoteNonceInited bool - remoteNonce musig2.Nonces - - proposalOptsCalledBeforeInit bool -} - -func newStrictNonceMusigSession() *strictNonceMusigSession { - return &strictNonceMusigSession{} -} - -//nolint:ll -func (m *strictNonceMusigSession) ProposalClosingOpts() ([]lnwallet.ChanCloseOpt, error) { - // Track if ProposalClosingOpts was called before InitRemoteNonce. - if !m.remoteNonceInited { - m.proposalOptsCalledBeforeInit = true - return nil, fmt.Errorf("ProposalClosingOpts called before " + - "InitRemoteNonce") - } - - return nil, nil -} - -func (m *strictNonceMusigSession) CombineClosingOpts(localSig, - remoteSig lnwire.PartialSig, -) (input.Signature, input.Signature, []lnwallet.ChanCloseOpt, error) { - - return &lnwallet.MusigPartialSig{}, &lnwallet.MusigPartialSig{}, nil, - nil -} - -func (m *strictNonceMusigSession) InitRemoteNonce(nonce *musig2.Nonces) { - m.remoteNonceInited = true - m.remoteNonce = *nonce -} - -func (m *strictNonceMusigSession) InvalidateNonce() {} - -func (m *strictNonceMusigSession) ClosingNonce() (*musig2.Nonces, error) { - return &musig2.Nonces{ - PubNonce: [66]byte{1, 2, 3}, - }, nil -} - -// TestLocalOfferSentUsesStoredSig verifies that when processing a -// LocalSigReceived event in the LocalOfferSent state, the stored -// LocalMusigSig is used for combining rather than re-signing. This -// prevents nonce reuse across RBF iterations. -func TestLocalOfferSentUsesStoredSig(t *testing.T) { - t.Parallel() - - // Create a strict mock that will fail if ProposalClosingOpts is - // called — it should NOT be called since we use the stored sig. - strictLocalMusig := newStrictNonceMusigSession() - - // The remote's closee nonce from shutdown. - remoteCloseeNonceFromShutdown := lnwire.Musig2Nonce{4, 5, 6} - - // Set up the environment with the strict mock. - closeTerms := &CloseChannelTerms{ - ShutdownScripts: ShutdownScripts{ - LocalDeliveryScript: localAddr, - RemoteDeliveryScript: remoteAddr, - }, - NonceState: NonceState{ - LocalCloseeNonce: fn.Some(lnwire.Musig2Nonce{1, 2, 3}), - RemoteCloseeNonce: fn.Some( - remoteCloseeNonceFromShutdown, - ), - }, - ShutdownBalances: ShutdownBalances{ - LocalBalance: lnwire.NewMSatFromSatoshis(500_000), - RemoteBalance: lnwire.NewMSatFromSatoshis(500_000), - }, - } - - // Create the LocalOfferSent state with a stored musig sig, - // simulating what LocalCloseStart would have stored. - localOfferSent := &LocalOfferSent{ - CloseChannelTerms: closeTerms, - ProposedFee: btcutil.Amount(1000), - ProposedFeeRate: chainfee.FeePerKwFloor.FeePerVByte(), - LocalSig: localSchnorrSig, - LocalMusigSig: fn.Some(lnwallet.MusigPartialSig{}), - } - - // The environment needs both musig sessions set for taproot path. - env := &Environment{ - ChanPoint: randOutPoint(t), - LocalMusigSession: strictLocalMusig, - RemoteMusigSession: newMockMusigSession(), - } - - // Create a LocalSigReceived event with NextCloseeNonce. - nextCloseeNonce := lnwire.Musig2Nonce{10, 11, 12} - localSigEvent := &LocalSigReceived{ - SigMsg: lnwire.ClosingSig{ - CloserScript: localAddr, - CloseeScript: remoteAddr, - FeeSatoshis: btcutil.Amount(1000), - LockTime: 1, - TaprootPartialSigs: lnwire.TaprootPartialSigs{ - CloserAndClosee: newPartialSigTlv[tlv.TlvType7]( - lnwire.PartialSig{ - Sig: btcec.ModNScalar{}, - }, - ), - }, - NextCloseeNonce: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType22](nextCloseeNonce), - ), - }, - } - - // Process the event. We expect it to use CombineClosingOpts with the - // stored sig, NOT ProposalClosingOpts + CreateCloseProposal. - func() { - defer func() { - _ = recover() - }() - - _, _ = localOfferSent.ProcessEvent(localSigEvent, env) - }() - - // ProposalClosingOpts should NOT have been called — we use the stored - // sig directly via CombineClosingOpts. - require.False( - t, strictLocalMusig.proposalOptsCalledBeforeInit, - "ProposalClosingOpts should not be called when using "+ - "stored MusigPartialSig", - ) - - // Verify that the NextCloseeNonce was stored for the next RBF round. - require.Equal( - t, fn.Some(nextCloseeNonce), - localOfferSent.NonceState.RemoteCloseeNonce, - "InitRemoteNonce should be called with RemoteCloseeNonce from "+ - "NonceState (not NextCloseeNonce from ClosingSig)", - ) -} diff --git a/lnwallet/chancloser/rbf_coop_transitions.go b/lnwallet/chancloser/rbf_coop_transitions.go index 505f678f5..ac9432a5c 100644 --- a/lnwallet/chancloser/rbf_coop_transitions.go +++ b/lnwallet/chancloser/rbf_coop_transitions.go @@ -5,13 +5,10 @@ import ( "fmt" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/mempool" - "github.com/btcsuite/btcd/wire/v2" - "github.com/davecgh/go-spew/spew" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/labels" @@ -25,60 +22,28 @@ import ( ) var ( - // ErrInvalidStateTransition is returned if the remote party tries to + // ErrThawHeightNotReached is returned if the remote party tries to // close, but the thaw height hasn't been matched yet. ErrThawHeightNotReached = fmt.Errorf("thaw height not reached") ) -// sendShutdownEvents is a helper function that returns a set of daemon -// events we need to emit when we decide that we should send a shutdown -// message. We'll also mark the channel as borked as well, as at this -// point, we no longer want to continue with normal operation. This -// function also returns the actual closee nonce used (either provided -// or auto-generated) for taproot channels. +// sendShutdownEvents is a helper function that returns a set of daemon events +// we need to emit when we decide that we should send a shutdown message. We'll +// also mark the channel as borked as well, as at this point, we no longer want +// to continue with normal operation. func sendShutdownEvents(chanID lnwire.ChannelID, chanPoint wire.OutPoint, deliveryAddr lnwire.DeliveryAddress, peerPub btcec.PublicKey, - postSendEvent fn.Option[ProtocolEvent], chanState ChanStateObserver, - env *Environment, localCloseeNonce fn.Option[lnwire.Musig2Nonce], -) (protofsm.DaemonEventSet, fn.Option[lnwire.Musig2Nonce], error) { + postSendEvent fn.Option[ProtocolEvent], + chanState ChanStateObserver) (protofsm.DaemonEventSet, error) { - // Create the shutdown message. - shutdownMsg := &lnwire.Shutdown{ - ChannelID: chanID, - Address: deliveryAddr, - } - - none := fn.None[lnwire.Musig2Nonce]() - - // For taproot channels using modern RBF flow, auto-generate closee - // nonce if not provided. The shutdown message only contains our closee - // nonce - the nonce the remote party will use when they act as closer. - if env.IsTaproot() && localCloseeNonce.IsNone() { - // Generate closee nonce now. Note how we generate it using the - // RemoteMusigSession, as that'll set our localNonce, we'll - // receive their remoteNonce for this session once we get their - // ClosingComplete message. - closeeNonces, err := env.RemoteMusigSession.ClosingNonce() - if err != nil { - return nil, none, fmt.Errorf("unable to generate "+ - "closee nonce: %w", err) - } - localCloseeNonce = fn.Some( - lnwire.Musig2Nonce(closeeNonces.PubNonce), - ) - } - - // If we have a closee nonce, then make sure to include it in the - // shutdown message. - localCloseeNonce.WhenSome(func(nonce lnwire.Musig2Nonce) { - shutdownMsg.ShutdownNonce = lnwire.SomeShutdownNonce(nonce) - }) - - // We'll emit a daemon event that instructs the daemon to send out a new - // shutdown message to the remote peer. + // We'll emit a daemon event that instructs the daemon to send out a + // new shutdown message to the remote peer. msgsToSend := &protofsm.SendMsgEvent[ProtocolEvent]{ TargetPeer: peerPub, - Msgs: []lnwire.Message{shutdownMsg}, + Msgs: []lnwire.Message{&lnwire.Shutdown{ + ChannelID: chanID, + Address: deliveryAddr, + }}, SendWhen: fn.Some(func() bool { ok := chanState.NoDanglingUpdates() if ok { @@ -95,16 +60,14 @@ func sendShutdownEvents(chanID lnwire.ChannelID, chanPoint wire.OutPoint, // If a close is already in process (we're in the RBF loop), then we // can skip everything below, and just send out the shutdown message. if chanState.FinalBalances().IsSome() { - return protofsm.DaemonEventSet{msgsToSend}, - localCloseeNonce, nil + return protofsm.DaemonEventSet{msgsToSend}, nil } // Before closing, we'll attempt to send a disable update for the // channel. We do so before closing the channel as otherwise the // current edge policy won't be retrievable from the graph. if err := chanState.DisableChannel(); err != nil { - return nil, none, fmt.Errorf("unable to disable "+ - "channel: %w", err) + return nil, fmt.Errorf("unable to disable channel: %w", err) } // If we have a post-send event, then this means that we're the @@ -117,53 +80,21 @@ func sendShutdownEvents(chanID lnwire.ChannelID, chanPoint wire.OutPoint, // As we're about to send a shutdown, we'll disable adds in the // outgoing direction. if err := chanState.DisableOutgoingAdds(); err != nil { - return nil, none, fmt.Errorf("unable to disable "+ - "outgoing adds: %w", err) + return nil, fmt.Errorf("unable to disable outgoing "+ + "adds: %w", err) } // To be able to survive a restart, we'll also write to disk // information about the shutdown we're about to send out. err := chanState.MarkShutdownSent(deliveryAddr, isInitiator) if err != nil { - return nil, none, fmt.Errorf("unable to mark "+ - "shutdown sent: %w", err) + return nil, fmt.Errorf("unable to mark shutdown sent: %w", err) } chancloserLog.Debugf("ChannelPoint(%v): marking channel as borked", chanPoint) - return protofsm.DaemonEventSet{msgsToSend}, localCloseeNonce, nil -} - -// initLocalMusigCloseeNonce initializes the LocalMusigSession with the remote's -// closee nonce. This is used when we act as the closer to create a closing -// transaction. -func initLocalMusigCloseeNonce(env *Environment, - remoteCloseeNonce fn.Option[lnwire.Musig2Nonce]) { - - if env.LocalMusigSession != nil { - remoteCloseeNonce.WhenSome(func(nonce lnwire.Musig2Nonce) { - remoteMusigNonce := musig2.Nonces{PubNonce: nonce} - env.LocalMusigSession.InitRemoteNonce(&remoteMusigNonce) - }) - } -} - -// initRemoteMusigCloserNonce initializes the RemoteMusigSession with the -// remote party's closer nonce. This is called when we receive ClosingComplete -// and we're acting as closee. The nonce passed in is the remote's JIT closer -// nonce from their ClosingComplete message. -func initRemoteMusigCloserNonce(env *Environment, - remoteCloserNonce fn.Option[lnwire.Musig2Nonce]) { - - if env.RemoteMusigSession != nil { - remoteCloserNonce.WhenSome(func(nonce lnwire.Musig2Nonce) { - remoteMusigNonce := musig2.Nonces{PubNonce: nonce} - env.RemoteMusigSession.InitRemoteNonce( - &remoteMusigNonce, - ) - }) - } + return protofsm.DaemonEventSet{msgsToSend}, nil } // validateShutdown is a helper function that validates that the shutdown has a @@ -172,7 +103,7 @@ func initRemoteMusigCloserNonce(env *Environment, func validateShutdown(chanThawHeight fn.Option[uint32], upfrontAddr fn.Option[lnwire.DeliveryAddress], msg *ShutdownReceived, chanPoint wire.OutPoint, - chainParams chaincfg.Params, isTaproot bool) error { + chainParams chaincfg.Params) error { // If we've received a shutdown message, and we have a thaw height, // then we need to make sure that the channel can now be co-op closed. @@ -194,46 +125,21 @@ func validateShutdown(chanThawHeight fn.Option[uint32], return err } - // For taproot channels, validate that the shutdown message includes - // the required nonce for the RBF cooperative close flow. - if isTaproot && !msg.RemoteShutdownNonce.IsSome() { - return ErrTaprootShutdownNonceMissing - } - - // Finally, verify the remote party's delivery script. We validate it in - // all cases (mirroring the negotiation closer), rather than only when - // an upfront shutdown script is on record: passing a nil upfront script - // still runs the well-formedness check on the peer's script, and a - // non-nil upfront script additionally enforces the exact match. - return validateRemoteDeliveryScript( - upfrontAddr, msg.ShutdownScript, chainParams, - ) -} - -// validateRemoteDeliveryScript checks a delivery script the remote party sent -// us, against any upfront shutdown script we have on record for them. We end up -// paying to this script, so it has to be present, and it has to be one of the -// delivery forms we accept. An absent script is rejected here rather than -// treated as nothing to check. -func validateRemoteDeliveryScript(upfrontAddr fn.Option[lnwire.DeliveryAddress], - script lnwire.DeliveryAddress, chainParams chaincfg.Params) error { - - if len(script) == 0 { - return fmt.Errorf("%w: no delivery script", - ErrInvalidShutdownScript) - } - - return validateShutdownScript( - upfrontAddr.UnwrapOr(nil), script, &chainParams, - ) + // Next, we'll verify that the remote party is sending the expected + // shutdown script. + return fn.MapOption(func(addr lnwire.DeliveryAddress) error { + return validateShutdownScript( + addr, msg.ShutdownScript, &chainParams, + ) + })(upfrontAddr).UnwrapOr(nil) } // ProcessEvent takes a protocol event, and implements a state transition for // the state. From this state, we can receive two possible incoming events: // SendShutdown and ShutdownReceived. Both of these will transition us to the // ChannelFlushing state. -func (c *ChannelActive) ProcessEvent(event ProtocolEvent, env *Environment, -) (*CloseStateTransition, error) { +func (c *ChannelActive) ProcessEvent(event ProtocolEvent, + env *Environment) (*CloseStateTransition, error) { switch msg := event.(type) { // If we get a confirmation, then a prior transaction we broadcasted @@ -263,10 +169,10 @@ func (c *ChannelActive) ProcessEvent(event ProtocolEvent, env *Environment, // and disable the channel on the network level. In this case, // we don't need a post send event as receive their shutdown is // what'll move us beyond the ShutdownPending state. - daemonEvents, closeeNonce, err := sendShutdownEvents( + daemonEvents, err := sendShutdownEvents( env.ChanID, env.ChanPoint, shutdownScript, env.ChanPeer, fn.None[ProtocolEvent](), - env.ChanObserver, env, msg.CloseeNonce, + env.ChanObserver, ) if err != nil { return nil, err @@ -284,9 +190,6 @@ func (c *ChannelActive) ProcessEvent(event ProtocolEvent, env *Environment, ShutdownScripts: ShutdownScripts{ LocalDeliveryScript: shutdownScript, }, - NonceState: NonceState{ - LocalCloseeNonce: closeeNonce, - }, }, NewEvents: fn.Some(RbfEvent{ ExternalEvents: daemonEvents, @@ -306,7 +209,7 @@ func (c *ChannelActive) ProcessEvent(event ProtocolEvent, env *Environment, // shutdown addr. err := validateShutdown( env.ThawHeight, env.RemoteUpfrontShutdown, msg, - env.ChanPoint, env.ChainParams, env.IsTaproot(), + env.ChanPoint, env.ChainParams, ) if err != nil { chancloserLog.Errorf("ChannelPoint(%v): rejecting "+ @@ -331,11 +234,11 @@ func (c *ChannelActive) ProcessEvent(event ProtocolEvent, env *Environment, // the set of daemon events we need to emit. We'll also specify // that once the message has actually been sent, that we // generate receive an input event of a ShutdownComplete. - daemonEvents, closeeNonce, err := sendShutdownEvents( + daemonEvents, err := sendShutdownEvents( env.ChanID, env.ChanPoint, shutdownAddr, env.ChanPeer, fn.Some[ProtocolEvent](&ShutdownComplete{}), - env.ChanObserver, env, fn.None[lnwire.Musig2Nonce](), + env.ChanObserver, ) if err != nil { return nil, err @@ -353,20 +256,12 @@ func (c *ChannelActive) ProcessEvent(event ProtocolEvent, env *Environment, remoteAddr := msg.ShutdownScript - // Initialize our LocalMusigSession with their closee nonce. - // This prepares the session for when we act as closer. - initLocalMusigCloseeNonce(env, msg.RemoteShutdownNonce) - return &CloseStateTransition{ NextState: &ShutdownPending{ ShutdownScripts: ShutdownScripts{ LocalDeliveryScript: shutdownAddr, RemoteDeliveryScript: remoteAddr, }, - NonceState: NonceState{ - RemoteCloseeNonce: msg.RemoteShutdownNonce, //nolint:ll - LocalCloseeNonce: closeeNonce, - }, }, NewEvents: fn.Some(protofsm.EmittedEvent[ProtocolEvent]{ ExternalEvents: daemonEvents, @@ -388,8 +283,8 @@ func (c *ChannelActive) ProcessEvent(event ProtocolEvent, env *Environment, // forward once we receive the ShutdownComplete event. Receiving // ShutdownComplete means that we've sent our shutdown, as this was specified // as a post send event. -func (s *ShutdownPending) ProcessEvent(event ProtocolEvent, env *Environment, -) (*CloseStateTransition, error) { +func (s *ShutdownPending) ProcessEvent(event ProtocolEvent, + env *Environment) (*CloseStateTransition, error) { switch msg := event.(type) { // If we get a confirmation, then a prior transaction we broadcasted @@ -428,7 +323,7 @@ func (s *ShutdownPending) ProcessEvent(event ProtocolEvent, env *Environment, // shutdown addr. err := validateShutdown( env.ThawHeight, env.RemoteUpfrontShutdown, msg, - env.ChanPoint, env.ChainParams, env.IsTaproot(), + env.ChanPoint, env.ChainParams, ) if err != nil { chancloserLog.Errorf("ChannelPoint(%v): rejecting "+ @@ -451,10 +346,6 @@ func (s *ShutdownPending) ProcessEvent(event ProtocolEvent, env *Environment, eventsToEmit = append(eventsToEmit, channelFlushed) } - // Initialize our LocalMusigSession with their closee nonce. - // This prepares the session for when we act as closer. - initLocalMusigCloseeNonce(env, msg.RemoteShutdownNonce) - chancloserLog.Infof("ChannelPoint(%v): disabling incoming adds", env.ChanPoint) @@ -481,11 +372,6 @@ func (s *ShutdownPending) ProcessEvent(event ProtocolEvent, env *Environment, }) } - // Make sure that we stash their closee nonce, so we can make a - // sig if needed in the next state transition. - updatedNonceState := s.NonceState - updatedNonceState.RemoteCloseeNonce = msg.RemoteShutdownNonce - // We transition to the ChannelFlushing state, where we await // the ChannelFlushed event. return &CloseStateTransition{ @@ -495,7 +381,6 @@ func (s *ShutdownPending) ProcessEvent(event ProtocolEvent, env *Environment, LocalDeliveryScript: s.LocalDeliveryScript, //nolint:ll RemoteDeliveryScript: msg.ShutdownScript, //nolint:ll }, - NonceState: updatedNonceState, }, NewEvents: newEvents, }, nil @@ -540,7 +425,6 @@ func (s *ShutdownPending) ProcessEvent(event ProtocolEvent, env *Environment, NextState: &ChannelFlushing{ IdealFeeRate: s.IdealFeeRate, ShutdownScripts: s.ShutdownScripts, - NonceState: s.NonceState, }, NewEvents: newEvents, }, nil @@ -558,8 +442,8 @@ func (s *ShutdownPending) ProcessEvent(event ProtocolEvent, env *Environment, // a ShutdownReceived event, then we'll stay in the ChannelFlushing state, as // we haven't yet fully cleared the channel. Otherwise, we can move to the // CloseReady state which'll being the channel closing process. -func (c *ChannelFlushing) ProcessEvent(event ProtocolEvent, env *Environment, -) (*CloseStateTransition, error) { +func (c *ChannelFlushing) ProcessEvent(event ProtocolEvent, + env *Environment) (*CloseStateTransition, error) { switch msg := event.(type) { // If we get a confirmation, then a prior transaction we broadcasted @@ -598,12 +482,23 @@ func (c *ChannelFlushing) ProcessEvent(event ProtocolEvent, env *Environment, closeTerms := CloseChannelTerms{ ShutdownScripts: c.ShutdownScripts, ShutdownBalances: msg.ShutdownBalances, - NonceState: c.NonceState, } chancloserLog.Infof("ChannelPoint(%v): channel flushed! "+ "proceeding with co-op close", env.ChanPoint) + // Now that the channel has been flushed, we'll mark on disk + // that we're approaching the point of no return where we'll + // send a new signature to the remote party. + // + // TODO(roasbeef): doesn't actually matter if initiator here? + if msg.FreshFlush { + err := env.ChanObserver.MarkCoopBroadcasted(nil, true) + if err != nil { + return nil, err + } + } + // If an ideal fee rate was specified, then we'll use that, // otherwise we'll fall back to the default value given in the // env. @@ -682,8 +577,8 @@ func (c *ChannelFlushing) ProcessEvent(event ProtocolEvent, env *Environment, // processNegotiateEvent is a helper function that processes a new event to // local channel state once we're in the ClosingNegotiation state. func processNegotiateEvent(c *ClosingNegotiation, event ProtocolEvent, - env *Environment, chanPeer lntypes.ChannelParty, -) (*CloseStateTransition, error) { + env *Environment, + chanPeer lntypes.ChannelParty) (*CloseStateTransition, error) { targetPeerState := c.PeerState.GetForParty(chanPeer) @@ -712,216 +607,11 @@ func processNegotiateEvent(c *ClosingNegotiation, event ProtocolEvent, }, nil } -// partialSigToWireSig converts a PartialSig to a wire Sig format for taproot. -func partialSigToWireSig(partialSig lnwire.PartialSig) lnwire.Sig { - var wireSig lnwire.Sig - sigBytes := partialSig.Sig.Bytes() - copy(wireSig.RawBytes()[:32], sigBytes[:]) - wireSig.ForceSchnorr() - - return wireSig -} - -// extractTaprootSigAndNonce extracts the partial signature and closee nonce -// from a taproot ClosingSig message. -func extractTaprootSigAndNonce( - msg lnwire.ClosingSig) (fn.Result[lnwire.Sig], - fn.Option[lnwire.Musig2Nonce]) { - - // Count how many taproot sig fields are populated. - taprootSigInts := []bool{ - msg.TaprootPartialSigs.CloserNoClosee.IsSome(), - msg.TaprootPartialSigs.NoCloserClosee.IsSome(), - msg.TaprootPartialSigs.CloserAndClosee.IsSome(), - } - numTaprootSigs := fn.Foldl( - 0, taprootSigInts, - func(acc int, sigInt bool) int { - if sigInt { - return acc + 1 - } - - return acc - }, - ) - - // Validate exactly one sig is set. - if numTaprootSigs != 1 { - return fn.Errf[lnwire.Sig]( - "%w: only one sig should be set, got %v", - ErrTooManySigs, numTaprootSigs, - ), fn.None[lnwire.Musig2Nonce]() - } - - tapSigs := msg.TaprootPartialSigs - - // Extract the partial signature from whichever field has it. - var extractedSig lnwire.Sig - switch { - case msg.TaprootPartialSigs.CloserNoClosee.IsSome(): - tapSigs.CloserNoClosee.WhenSomeV(func(ps lnwire.PartialSig) { - extractedSig = partialSigToWireSig(ps) - }) - - case msg.TaprootPartialSigs.NoCloserClosee.IsSome(): - tapSigs.NoCloserClosee.WhenSomeV(func(ps lnwire.PartialSig) { - extractedSig = partialSigToWireSig(ps) - }) - - case msg.TaprootPartialSigs.CloserAndClosee.IsSome(): - tapSigs.CloserAndClosee.WhenSomeV(func(ps lnwire.PartialSig) { - extractedSig = partialSigToWireSig(ps) - }) - } - - // Extract the closee nonce, for taproot channels, we expect this to - // always be present. - var nextCloseeNonce fn.Option[lnwire.Musig2Nonce] - msg.NextCloseeNonce.WhenSomeV(func(nonce lnwire.Musig2Nonce) { - nextCloseeNonce = fn.Some(nonce) - }) - - // Validate that NextCloseeNonce is always set for taproot channels. - if nextCloseeNonce.IsNone() { - return fn.Errf[lnwire.Sig]("NextCloseeNonce must be set for " + - "taproot channels"), fn.None[lnwire.Musig2Nonce]() - } - - return fn.Ok(extractedSig), nextCloseeNonce -} - -// extractRegularSig extracts the signature from a non-taproot ClosingSig -// message. -func extractRegularSig(msg lnwire.ClosingSig) fn.Result[lnwire.Sig] { - // Count how many regular sig fields are populated - regularSigInts := []bool{ - msg.ClosingSigs.CloserNoClosee.IsSome(), - msg.ClosingSigs.NoCloserClosee.IsSome(), - msg.ClosingSigs.CloserAndClosee.IsSome(), - } - numRegularSigs := fn.Foldl( - 0, regularSigInts, - func(acc int, sigInt bool) int { - if sigInt { - return acc + 1 - } - - return acc - }, - ) - - // Validate exactly one sig is set - if numRegularSigs != 1 { - return fn.Errf[lnwire.Sig]("%w: only one sig should be "+ - "set, got %v", ErrTooManySigs, numRegularSigs) - } - - // Extract the signature from the appropriate field - switch { - case msg.ClosingSigs.CloserNoClosee.IsSome(): - var sig lnwire.Sig - msg.ClosingSigs.CloserNoClosee.WhenSomeV( - func(s lnwire.Sig) { - sig = s - }, - ) - - return fn.Ok(sig) - - case msg.ClosingSigs.NoCloserClosee.IsSome(): - var sig lnwire.Sig - msg.ClosingSigs.NoCloserClosee.WhenSomeV( - func(s lnwire.Sig) { - sig = s - }, - ) - - return fn.Ok(sig) - - case msg.ClosingSigs.CloserAndClosee.IsSome(): - var sig lnwire.Sig - msg.ClosingSigs.CloserAndClosee.WhenSomeV( - func(s lnwire.Sig) { - sig = s - }, - ) - - return fn.Ok(sig) - - default: - return fn.Errf[lnwire.Sig]("no signature found") - } -} - -// extractSigAndNonceFromClosingSig validates that the signature type in the -// ClosingSig message matches the channel type (taproot vs non-taproot), then -// extracts the partial signature and the NextCloseeNonce for the next RBF -// round. This is used by the closer when receiving the closee's response. -func extractSigAndNonceFromClosingSig( - msg lnwire.ClosingSig, -) (fn.Result[lnwire.Sig], fn.Option[lnwire.Musig2Nonce]) { - - // Check if this is a taproot or regular signature. - hasTaprootSigs := msg.TaprootPartialSigs.CloserNoClosee.IsSome() || - msg.TaprootPartialSigs.NoCloserClosee.IsSome() || - msg.TaprootPartialSigs.CloserAndClosee.IsSome() - - hasRegularSigs := msg.ClosingSigs.CloserNoClosee.IsSome() || - msg.ClosingSigs.NoCloserClosee.IsSome() || - msg.ClosingSigs.CloserAndClosee.IsSome() - - // Make sure that only a single set of signatures is present. - if hasTaprootSigs && hasRegularSigs { - return fn.Errf[lnwire.Sig]("both taproot and regular " + - "sigs present"), fn.None[lnwire.Musig2Nonce]() - } - - // If it's a taproot sig, then we may need to also extract the nonce. - if hasTaprootSigs { - return extractTaprootSigAndNonce(msg) - } - - return extractRegularSig(msg), fn.None[lnwire.Musig2Nonce]() -} - -// validateAndExtractSigAndNonce validates that the signature type matches the -// channel type and then extracts the signature and nonce. -func validateAndExtractSigAndNonce( - msg lnwire.ClosingSig, isTaproot bool, -) (fn.Result[lnwire.Sig], fn.Option[lnwire.Musig2Nonce]) { - - // Check if this is a taproot or regular signature. - hasTaprootSigs := msg.TaprootPartialSigs.CloserNoClosee.IsSome() || - msg.TaprootPartialSigs.NoCloserClosee.IsSome() || - msg.TaprootPartialSigs.CloserAndClosee.IsSome() - - hasRegularSigs := msg.ClosingSigs.CloserNoClosee.IsSome() || - msg.ClosingSigs.NoCloserClosee.IsSome() || - msg.ClosingSigs.CloserAndClosee.IsSome() - - // Assert that the signature type matches the channel type. - switch { - case isTaproot && !hasTaprootSigs && hasRegularSigs: - return fn.Errf[lnwire.Sig]("taproot channel requires " + - "taproot signatures, got regular signatures"), - fn.None[lnwire.Musig2Nonce]() - - case !isTaproot && hasTaprootSigs && !hasRegularSigs: - return fn.Errf[lnwire.Sig]("non-taproot channel requires " + - "regular signatures, got taproot signatures"), - fn.None[lnwire.Musig2Nonce]() - } - - // If everything is clear, then we'll go ahead and extract the - // signatures. - return extractSigAndNonceFromClosingSig(msg) -} - // updateAndValidateCloseTerms is a helper function that validates examines the // incoming event, and decide if we need to update the remote party's address, // or reject it if it doesn't include our latest address. -func (c *ClosingNegotiation) updateAndValidateCloseTerms(event ProtocolEvent, - env *Environment) error { +func (c *ClosingNegotiation) updateAndValidateCloseTerms( + event ProtocolEvent) error { assertLocalScriptMatches := func(localScriptInMsg []byte) error { if !bytes.Equal( @@ -952,19 +642,9 @@ func (c *ClosingNegotiation) updateAndValidateCloseTerms(event ProtocolEvent, oldRemoteAddr := c.RemoteDeliveryScript newRemoteAddr := msg.SigMsg.CloserScript - // If they're sending a new script, then we'll make sure it's - // well-formed (and matches any upfront script on record) before - // we update to the new one, just as we do for the initial - // shutdown script. + // If they're sending a new script, then we'll update to the new + // one. if !bytes.Equal(oldRemoteAddr, newRemoteAddr) { - err := validateRemoteDeliveryScript( - env.RemoteUpfrontShutdown, newRemoteAddr, - env.ChainParams, - ) - if err != nil { - return err - } - c.RemoteDeliveryScript = newRemoteAddr } @@ -988,8 +668,8 @@ func (c *ClosingNegotiation) updateAndValidateCloseTerms(event ProtocolEvent, // party in response to new events. From this state, we'll continue to drive // forward the local and remote states until we arrive at the StateFin stage, // or we loop back up to the ShutdownPending state. -func (c *ClosingNegotiation) ProcessEvent(event ProtocolEvent, env *Environment, -) (*CloseStateTransition, error) { +func (c *ClosingNegotiation) ProcessEvent(event ProtocolEvent, + env *Environment) (*CloseStateTransition, error) { // There're two classes of events that can break us out of this state: // we receive a confirmation event, or we receive a signal to restart @@ -1015,8 +695,7 @@ func (c *ClosingNegotiation) ProcessEvent(event ProtocolEvent, env *Environment, // At this point, we know its a new signature message. We'll validate, // and maybe update the set of close terms based on what we receive. We // might update the remote party's address for example. - err := c.updateAndValidateCloseTerms(event, env) - if err != nil { + if err := c.updateAndValidateCloseTerms(event); err != nil { return nil, fmt.Errorf("event violates close terms: %w", err) } @@ -1042,8 +721,8 @@ func (c *ClosingNegotiation) ProcessEvent(event ProtocolEvent, env *Environment, case shouldRouteTo(lntypes.Remote): chancloserLog.Infof("ChannelPoint(%v): routing %T to remote "+ - "chan state", env.ChanPoint, event) + // Drive forward the remote state based on the next event. return processNegotiateEvent(c, event, env, lntypes.Remote) } @@ -1058,73 +737,10 @@ func newSigTlv[T tlv.TlvType](s lnwire.Sig) tlv.OptionalRecordT[T, lnwire.Sig] { return tlv.SomeRecordT(tlv.NewRecordT[T](s)) } -// encodeClosingSignatures is a helper function that creates the appropriate -// signature structures for the closing_complete message based on the channel -// type and dust status. -func encodeClosingSignatures(env *Environment, wireSig lnwire.Sig, - musigPartialSig *lnwallet.MusigPartialSig, noCloser, noClosee bool, -) (lnwire.ClosingSigs, lnwire.TaprootClosingSigs, error) { - - var ( - closingSigs lnwire.ClosingSigs - taprootClosingSigs lnwire.TaprootClosingSigs - ) - - // If this is a taproot channel, then we'll return the taproot specific - // closing sigs variant. - if env.IsTaproot() { - if musigPartialSig == nil { - return closingSigs, taprootClosingSigs, - fmt.Errorf("missing partial signature for " + - "taproot channel") - } - - // Convert the musig partial sig to wire format. - // This already includes our JIT closer nonce that we - // used to sign. - partialSigWithNonce := musigPartialSig.ToWireSig() - - switch { - case noCloser: - taprootClosingSigs.NoCloserClosee = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType6]( - *partialSigWithNonce, - ), - ) - case noClosee: - taprootClosingSigs.CloserNoClosee = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType5]( - *partialSigWithNonce, - ), - ) - default: - taprootClosingSigs.CloserAndClosee = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType7]( - *partialSigWithNonce, - ), - ) - } - - return closingSigs, taprootClosingSigs, nil - } - - // For non-taproot channels, we'll populate the normal ECDSA signatures. - switch { - case noClosee: - closingSigs.CloserNoClosee = newSigTlv[tlv.TlvType1](wireSig) - case noCloser: - closingSigs.NoCloserClosee = newSigTlv[tlv.TlvType2](wireSig) - default: - closingSigs.CloserAndClosee = newSigTlv[tlv.TlvType3](wireSig) - } - - return closingSigs, taprootClosingSigs, nil -} - // ProcessEvent implements the event processing to kick off the process of // obtaining a new (possibly RBF'd) signature for our commitment transaction. -func (l *LocalCloseStart) ProcessEvent(event ProtocolEvent, env *Environment, -) (*CloseStateTransition, error) { +func (l *LocalCloseStart) ProcessEvent(event ProtocolEvent, + env *Environment) (*CloseStateTransition, error) { switch msg := event.(type) { //nolint:gocritic // If we receive a SendOfferEvent, then we'll use the specified fee @@ -1164,82 +780,17 @@ func (l *LocalCloseStart) ProcessEvent(event ProtocolEvent, env *Environment, // proposals, we'll just always use the known RBF sequence // value. localScript := l.LocalDeliveryScript - - var closeOpts []lnwallet.ChanCloseOpt - closeOpts = append(closeOpts, + rawSig, closeTx, closeBalance, err := env.CloseSigner.CreateCloseProposal( //nolint:ll + absoluteFee, localScript, l.RemoteDeliveryScript, lnwallet.WithCustomSequence(mempool.MaxRBFSequence), lnwallet.WithCustomPayer(lntypes.Local), ) - - // For taproot channels, we need to use the LocalMusigSession - // for signing when we're the closer (sending closing_complete). - if env.IsTaproot() { - // Initialize with the remote's closee nonce for - // signing. This may be using the very first nonce they - // send in shutdown, or the nonce they sent in - // ClosingSig after responding to our prior offer. - initLocalMusigCloseeNonce( - env, l.NonceState.RemoteCloseeNonce, - ) - - // Generate our JIT closer nonce. This sets the internal - // localNonce field in LocalMusigSession. - _, err := env.LocalMusigSession.ClosingNonce() - if err != nil { - return nil, fmt.Errorf("failed to generate "+ - "JIT closer nonce: %w", err) - } - - //nolint:ll - musigOpts, err := env.LocalMusigSession.ProposalClosingOpts() - if err != nil { - return nil, fmt.Errorf("failed to get musig "+ - "closing opts: %w", err) - } - closeOpts = append(closeOpts, musigOpts...) - } - - rawSig, closeTx, closeBalance, err := env.CloseSigner.CreateCloseProposal( //nolint:ll - absoluteFee, localScript, l.RemoteDeliveryScript, - closeOpts..., - ) if err != nil { - return nil, fmt.Errorf("unable to create close "+ - "proposal: %w", err) + return nil, err } - - // Depending on the channel type, we'll be encoding a normal - // sig, or a musig2 partial sig. - var ( - wireSig lnwire.Sig - musigPartialSig *lnwallet.MusigPartialSig - ) - - // Depending on the channel type, we'll either have a partial - // signature, or a regular signature. - switch { - case env.IsTaproot(): - var ok bool - musigPartialSig, ok = rawSig.(*lnwallet.MusigPartialSig) - if !ok { - return nil, fmt.Errorf("expected "+ - "MusigPartialSig for taproot "+ - "channel, got %T", rawSig) - } - - // Convert to schnorr shell format for wire sig. - schnorrSig := musigPartialSig.ToSchnorrShell() - wireSig, err = lnwire.NewSigFromSignature(schnorrSig) - if err != nil { - return nil, err - } - default: - // For non-taproot channels, use regular signature - // conversion. - wireSig, err = lnwire.NewSigFromSignature(rawSig) - if err != nil { - return nil, err - } + wireSig, err := lnwire.NewSigFromSignature(rawSig) + if err != nil { + return nil, err } chancloserLog.Infof("closing w/ local_addr=%x, "+ @@ -1247,58 +798,62 @@ func (l *LocalCloseStart) ProcessEvent(event ProtocolEvent, env *Environment, l.RemoteDeliveryScript[:], absoluteFee) chancloserLog.Infof("proposing closing_tx=%v", - spew.Sdump(closeTx)) + lnutils.SpewLogClosure(closeTx)) - var noClosee, noCloser bool + // Now that we have our signature, we'll set the proper + // closingSigs field based on if the remote party's output is + // dust or not. + var closingSigs lnwire.ClosingSigs switch { + // If the remote party's output is dust, then we'll set the + // CloserNoClosee field. case remoteTxOut == nil: - noClosee = true + closingSigs.CloserNoClosee = newSigTlv[tlv.TlvType1]( + wireSig, + ) + + // If after paying for fees, our balance is below dust, then + // we'll set the NoCloserClosee field. case closeBalance < lnwallet.DustLimitForSize(len(localScript)): - noCloser = true - } - - // Create the appropriate signature structures based on channel - // type. - closingSigs, taprootClosingSigs, err := encodeClosingSignatures( - env, wireSig, musigPartialSig, noCloser, noClosee, - ) - if err != nil { - return nil, err - } - - closingCompleteMsg := &lnwire.ClosingComplete{ - ChannelID: env.ChanID, - CloserScript: l.LocalDeliveryScript, - CloseeScript: l.RemoteDeliveryScript, - FeeSatoshis: absoluteFee, - LockTime: env.BlockHeight, - ClosingSigs: closingSigs, - TaprootClosingSigs: taprootClosingSigs, + closingSigs.NoCloserClosee = newSigTlv[tlv.TlvType2]( + wireSig, + ) + + // Otherwise, we'll set the CloserAndClosee field. + // + // TODO(roasbeef): should actually set both?? + default: + closingSigs.CloserAndClosee = newSigTlv[tlv.TlvType3]( + wireSig, + ) } + // Now that we have our sig, we'll emit a daemon event to send + // it to the remote party, then transition to the + // LocalOfferSent state. + // // TODO(roasbeef): type alias for protocol event sendEvent := protofsm.DaemonEventSet{&protofsm.SendMsgEvent[ProtocolEvent]{ //nolint:ll TargetPeer: env.ChanPeer, - Msgs: []lnwire.Message{closingCompleteMsg}, + Msgs: []lnwire.Message{&lnwire.ClosingComplete{ + ChannelID: env.ChanID, + CloserScript: l.LocalDeliveryScript, + CloseeScript: l.RemoteDeliveryScript, + FeeSatoshis: absoluteFee, + LockTime: env.BlockHeight, + ClosingSigs: closingSigs, + }}, }} chancloserLog.Infof("ChannelPoint(%v): sending closing sig "+ "to remote party, fee_sats=%v", env.ChanPoint, absoluteFee) - // For taproot channels, stash the full MusigPartialSig so - // LocalOfferSent can combine signatures without re-signing. - var localMusigSig fn.Option[lnwallet.MusigPartialSig] - if musigPartialSig != nil { - localMusigSig = fn.Some(*musigPartialSig) - } - return &CloseStateTransition{ NextState: &LocalOfferSent{ ProposedFee: absoluteFee, ProposedFeeRate: msg.TargetFeeRate, LocalSig: wireSig, - LocalMusigSig: localMusigSig, CloseChannelTerms: l.CloseChannelTerms, }, NewEvents: fn.Some(RbfEvent{ @@ -1311,300 +866,77 @@ func (l *LocalCloseStart) ProcessEvent(event ProtocolEvent, env *Environment, ErrInvalidStateTransition, event) } -// selectTaprootPartialSigWithNonce selects the PartialSigWithNonce to use from -// TaprootClosingSigs based on whether the closee output is omitted. -func selectTaprootPartialSigWithNonce( - sigs lnwire.TaprootClosingSigs, - noClosee bool) (lnwire.PartialSigWithNonce, error) { - - var ps lnwire.PartialSigWithNonce - - if noClosee { - if sigs.CloserNoClosee.IsNone() { - return ps, ErrCloserNoClosee +// extractSig extracts the expected signature from the closing sig message. +// Only one of them should actually be populated as the closing sig message is +// sent in response to a ClosingComplete message, it should only sign the same +// version of the co-op close tx as the sender did. +func extractSig(msg lnwire.ClosingSig) fn.Result[lnwire.Sig] { + // First, we'll validate that only one signature is included in their + // response to our initial offer. If not, then we'll exit here, and + // trigger a recycle of the connection. + sigInts := []bool{ + msg.CloserNoClosee.IsSome(), msg.NoCloserClosee.IsSome(), + msg.CloserAndClosee.IsSome(), + } + numSigs := fn.Foldl(0, sigInts, func(acc int, sigInt bool) int { + if sigInt { + return acc + 1 } - sigs.CloserNoClosee.WhenSomeV( - func(p lnwire.PartialSigWithNonce) { - ps = p - }, - ) - - return ps, nil + return acc + }) + if numSigs != 1 { + return fn.Errf[lnwire.Sig]("%w: only one sig should be set, "+ + "got %v", ErrTooManySigs, numSigs) } - if sigs.CloserAndClosee.IsSome() { - sigs.CloserAndClosee.WhenSomeV( - func(p lnwire.PartialSigWithNonce) { - ps = p - }, - ) - - return ps, nil - } - - if sigs.NoCloserClosee.IsSome() { - sigs.NoCloserClosee.WhenSomeV( - func(p lnwire.PartialSigWithNonce) { - ps = p - }, - ) - - return ps, nil - } - - return ps, ErrNoSig -} - -// createClosingSigMessage creates the ClosingSig message response for the -// closee role. -func createClosingSigMessage(env *Environment, wireSig lnwire.Sig, - localSig input.Signature, - localScript, remoteScript lnwire.DeliveryAddress, fee btcutil.Amount, - lockTime uint32, noClosee bool) (*lnwire.ClosingSig, error) { - - var ( - closingSigs lnwire.ClosingSigs - taprootPartialSigs lnwire.TaprootPartialSigs - nextCloseeNonce tlv.OptionalRecordT[ - tlv.TlvType22, lnwire.Musig2Nonce, - ] + // The final sig is the one that's actually set. + sig := msg.CloserAndClosee.ValOpt().Alt( + msg.NoCloserClosee.ValOpt(), + ).Alt( + msg.CloserNoClosee.ValOpt(), ) - // For taproot channels, use PartialSig (no nonce) since receiver knows - // our nonce. - if env.IsTaproot() { - // We already have the MusigPartialSig from earlier. - musigSig, ok := localSig.(*lnwallet.MusigPartialSig) - if !ok { - return nil, fmt.Errorf("expected "+ - "MusigPartialSig for taproot channel, "+ - "got %T", localSig) - } - wireSigWithNonce := musigSig.ToWireSig() - partialSig := wireSigWithNonce.PartialSig - - if noClosee { - taprootPartialSigs.CloserNoClosee = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType5](partialSig), - ) - } else { - taprootPartialSigs.CloserAndClosee = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType7](partialSig), - ) - } - - // Generate our next closee nonce for the next RBF iteration. - // This is the nonce the closer should use for our closee - // signature in the next RBF round. We always include this since - // RBF could occur. - nextNonces, err := env.RemoteMusigSession.ClosingNonce() - if err != nil { - return nil, fmt.Errorf("failed to generate next "+ - "closee nonce: %w", err) - } - nextCloseeNonce = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType22]( - lnwire.Musig2Nonce(nextNonces.PubNonce), - ), - ) - } else { - // Non-taproot: use regular signatures. - if noClosee { - closingSigs.CloserNoClosee = newSigTlv[tlv.TlvType1]( - wireSig, - ) - } else { - closingSigs.CloserAndClosee = newSigTlv[tlv.TlvType3]( - wireSig, - ) - } - } - - return &lnwire.ClosingSig{ - ChannelID: env.ChanID, - CloserScript: remoteScript, - CloseeScript: localScript, - FeeSatoshis: fee, - LockTime: lockTime, - ClosingSigs: closingSigs, - TaprootPartialSigs: taprootPartialSigs, - NextCloseeNonce: nextCloseeNonce, - }, nil -} - -// extractTaprootPartialSig extracts just the PartialSig from -// TaprootPartialSigs. This is useful when we need the actual -// partial sig for combining. -func extractTaprootPartialSig( - sigs lnwire.TaprootPartialSigs, -) fn.Option[lnwire.PartialSig] { - - if sigs.CloserNoClosee.IsSome() { - var ps lnwire.PartialSig - sigs.CloserNoClosee.WhenSomeV( - func(p lnwire.PartialSig) { - ps = p - }, - ) - - return fn.Some(ps) - } - - if sigs.NoCloserClosee.IsSome() { - var ps lnwire.PartialSig - sigs.NoCloserClosee.WhenSomeV( - func(p lnwire.PartialSig) { - ps = p - }, - ) - - return fn.Some(ps) - } - - if sigs.CloserAndClosee.IsSome() { - var ps lnwire.PartialSig - sigs.CloserAndClosee.WhenSomeV( - func(p lnwire.PartialSig) { - ps = p - }, - ) - - return fn.Some(ps) - } - - return fn.None[lnwire.PartialSig]() -} - -// prepareClosingSignatures prepares the local and remote signatures for the -// closing transaction. For taproot channels, it handles musig signature -// combination. For non-taproot channels, it converts wire signatures to regular -// signatures. -func prepareClosingSignatures(env *Environment, - l *LocalOfferSent, msg *LocalSigReceived, - sig lnwire.Sig, - closeOpts []lnwallet.ChanCloseOpt, -) (input.Signature, input.Signature, - []lnwallet.ChanCloseOpt, error) { - - if env.IsTaproot() { - // Use the stored MusigPartialSig from LocalCloseStart rather - // than re-signing. This prevents nonce reuse across the two - // state transitions within a closer round. - storedSig, err := l.LocalMusigSig.UnwrapOrErr( - fmt.Errorf("missing stored musig partial sig " + - "for taproot channel"), - ) - if err != nil { - return nil, nil, nil, err - } - - localPartialSig := storedSig.ToWireSig().PartialSig - - // Extract the remote's partial sig from their ClosingSig. - remotePartialSigOpt := extractTaprootPartialSig( - msg.SigMsg.TaprootPartialSigs, - ) - if remotePartialSigOpt.IsNone() { - return nil, nil, nil, fmt.Errorf("no taproot " + - "partial sig found in message") - } - - remotePartialSig := remotePartialSigOpt.UnwrapOr( - lnwire.PartialSig{}, - ) - - // Combine both partial signatures using the musig session - // from the original ProposalClosingOpts call. - //nolint:ll - localCombined, remoteCombined, combinedOpts, err := env.LocalMusigSession.CombineClosingOpts( - localPartialSig, remotePartialSig, - ) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to "+ - "combine closing opts: %w", err) - } - - return localCombined, remoteCombined, combinedOpts, nil - } - - // For non-taproot channels, convert wire signatures to regular - // signatures. - remoteSig, err := sig.ToSignature() - if err != nil { - return nil, nil, nil, err - } - localSig, err := l.LocalSig.ToSignature() - if err != nil { - return nil, nil, nil, err - } - - return localSig, remoteSig, nil, nil + return fn.NewResult(sig.UnwrapOrErr(ErrNoSig)) } // ProcessEvent implements the state transition function for the // LocalOfferSent state. In this state, we'll wait for the remote party to // send a close_signed message which gives us the ability to broadcast a new // co-op close transaction. -func (l *LocalOfferSent) ProcessEvent(event ProtocolEvent, env *Environment, -) (*CloseStateTransition, error) { +func (l *LocalOfferSent) ProcessEvent(event ProtocolEvent, + env *Environment) (*CloseStateTransition, error) { switch msg := event.(type) { //nolint:gocritic // If we receive a LocalSigReceived event, then we'll attempt to // validate the signature from the remote party. If valid, then we can // broadcast the transaction, and transition to the ClosePending state. case *LocalSigReceived: - // Extract and validate that only one sig field is set. For - // taproot channels, we also extract the NextCloseeNonce. - sigResult, nextCloseeNonce := validateAndExtractSigAndNonce( - msg.SigMsg, env.IsTaproot(), - ) - sig, err := sigResult.Unpack() + // Extract and validate that only one sig field is set. + sig, err := extractSig(msg.SigMsg).Unpack() if err != nil { return nil, err } - var closeOpts []lnwallet.ChanCloseOpt - closeOpts = append(closeOpts, - lnwallet.WithCustomSequence(mempool.MaxRBFSequence), - lnwallet.WithCustomPayer(lntypes.Local), - ) - - // For taproot channels, update NonceState with the new nonce - // from ClosingSig for potential future RBF iterations. - if env.IsTaproot() { - l.NonceState.RemoteCloseeNonce = nextCloseeNonce - } - - // Prepare the closing signatures. For taproot, this uses the - // stored MusigPartialSig from LocalCloseStart (no re-signing). - // The returned musigOpts contain the musig session needed by - // CompleteCooperativeClose. - localSig, remoteSig, musigOpts, err := prepareClosingSignatures( - env, l, msg, sig, closeOpts, - ) + remoteSig, err := sig.ToSignature() if err != nil { - return nil, fmt.Errorf("LocalOfferSent: unable "+ - "to prepare closing sigs: %w", err) + return nil, err + } + localSig, err := l.LocalSig.ToSignature() + if err != nil { + return nil, err } - closeOpts = append(closeOpts, musigOpts...) // Now that we have their signature, we'll attempt to validate // it, then extract a valid closing signature from it. closeTx, _, err := env.CloseSigner.CompleteCooperativeClose( localSig, remoteSig, l.LocalDeliveryScript, - l.RemoteDeliveryScript, l.ProposedFee, closeOpts..., + l.RemoteDeliveryScript, l.ProposedFee, + lnwallet.WithCustomSequence(mempool.MaxRBFSequence), + lnwallet.WithCustomPayer(lntypes.Local), ) if err != nil { - return nil, fmt.Errorf("LocalOfferSent: unable "+ - "to complete coop close: %w", err) - } - - // Invalidate the closer nonce now that the round is complete. - // The next RBF round will generate a fresh nonce in - // LocalCloseStart. - if env.IsTaproot() { - env.LocalMusigSession.InvalidateNonce() + return nil, err } // As we're about to broadcast a new version of the co-op close @@ -1644,347 +976,12 @@ func (l *LocalOfferSent) ProcessEvent(event ProtocolEvent, env *Environment, ErrInvalidStateTransition, event) } -// processRemoteTaprootSig handles the extraction and processing of a remote -// taproot signature for the closee role. It extracts the partial sig with -// nonce, initializes the musig session, and returns the remote signature. -func processRemoteTaprootSig(env *Environment, msg lnwire.ClosingComplete, - jitNonce fn.Option[lnwire.Musig2Nonce], noClosee bool) (input.Signature, - error) { - - // Initialize the RemoteMusigSession with their JIT closer nonce. We - // already added our local nonce either during shutdown, or with our - // last ClosingSig message. - initRemoteMusigCloserNonce(env, jitNonce) - - remotePartialSig, err := selectTaprootPartialSigWithNonce( - msg.TaprootClosingSigs, noClosee, - ) - if err != nil { - return nil, err - } - - // Create a MusigPartialSig from the wire format. The nonce in - // PartialSigWithNonce is their JIT closer nonce used for the current - // session verification. - remoteSig := lnwallet.NewMusigPartialSig( - &musig2.PartialSignature{ - S: &remotePartialSig.PartialSig.Sig, - }, - remotePartialSig.Nonce, lnwire.Musig2Nonce{}, nil, - fn.None[chainhash.Hash](), - ) - - return remoteSig, nil -} - -// createLocalCloseeSignature creates our local signature for the closee role. -// It returns both the wire format signature and the input.Signature. -func createLocalCloseeSignature(env *Environment, fee btcutil.Amount, - localScript, remoteScript lnwire.DeliveryAddress, - chanOpts []lnwallet.ChanCloseOpt) (lnwire.Sig, input.Signature, error) { - - rawSig, _, _, err := env.CloseSigner.CreateCloseProposal( - fee, localScript, remoteScript, chanOpts..., - ) - if err != nil { - return lnwire.Sig{}, nil, fmt.Errorf("failed to "+ - "create close proposal: %w", err) - } - - var ( - wireSig lnwire.Sig - localSig input.Signature - ) - - if env.IsTaproot() { - musigSig, ok := rawSig.(*lnwallet.MusigPartialSig) - if !ok { - return lnwire.Sig{}, nil, fmt.Errorf("expected "+ - "MusigPartialSig for taproot channel, got %T", - rawSig) - } - - // Convert to schnorr shell format for wire sig encoding. - schnorrSig := musigSig.ToSchnorrShell() - wireSig, err = lnwire.NewSigFromSignature(schnorrSig) - if err != nil { - return lnwire.Sig{}, nil, err - } - - localSig = musigSig - } else { - wireSig, err = lnwire.NewSigFromSignature(rawSig) - if err != nil { - return lnwire.Sig{}, nil, err - } - - localSig, err = wireSig.ToSignature() - if err != nil { - return lnwire.Sig{}, nil, err - } - } - - return wireSig, localSig, nil -} - -// SigType represents either a regular or taproot signature. -// Left = regular signature, Right = taproot signature with nonce. -type SigType = fn.Either[lnwire.Sig, lnwire.PartialSigWithNonce] - -// NewRegularSigType creates a SigType for a regular (non-taproot) signature. -func NewRegularSigType(sig lnwire.Sig) SigType { - return fn.NewLeft[lnwire.Sig, lnwire.PartialSigWithNonce](sig) -} - -// NewTaprootSigType creates a SigType for a taproot signature with nonce. -func NewTaprootSigType(ps lnwire.PartialSigWithNonce) SigType { - return fn.NewRight[lnwire.Sig](ps) -} - -// SigFieldSet represents which signature fields are present in a -// ClosingComplete message. -type SigFieldSet struct { - // CloserNoClosee contains the signature for a transaction with only - // the closer's output (closee's output is dust/excluded). - CloserNoClosee fn.Option[SigType] - - // NoCloserClosee contains the signature for a transaction with only - // the closee's output (closer's output is dust/excluded). - NoCloserClosee fn.Option[SigType] - - // CloserAndClosee contains the signature for a transaction with both - // outputs present. - CloserAndClosee fn.Option[SigType] -} - -// IsTaproot returns true if any taproot signatures are present in the field -// set. -func (s SigFieldSet) IsTaproot() bool { - checkTaproot := func(opt fn.Option[SigType]) bool { - return fn.MapOptionZ(opt, func(sig SigType) bool { - return sig.IsRight() - }) - } - - return checkTaproot(s.CloserNoClosee) || - checkTaproot(s.NoCloserClosee) || - checkTaproot(s.CloserAndClosee) -} - -// HasAnySig returns true if at least one signature field is present. -func (s SigFieldSet) HasAnySig() bool { - return s.CloserNoClosee.IsSome() || - s.NoCloserClosee.IsSome() || - s.CloserAndClosee.IsSome() -} - -// parseSigFields extracts signature fields from a ClosingComplete message and -// returns a structured representation of which fields are present. -func parseSigFields(msg lnwire.ClosingComplete) SigFieldSet { - var fields SigFieldSet - - // createSigType is a helper function that creates a SigType based on - // field otpions. - createSigType := func( - taprootOpt fn.Option[lnwire.PartialSigWithNonce], - regularOpt fn.Option[lnwire.Sig], - ) fn.Option[SigType] { - - // The taproot takes precedence if present. - if taprootOpt.IsSome() { - var ps lnwire.PartialSigWithNonce - taprootOpt.WhenSome(func(p lnwire.PartialSigWithNonce) { - ps = p - }) - - return fn.Some(NewTaprootSigType(ps)) - } - - // Otherwise, check for a regular signature. - if regularOpt.IsSome() { - var sig lnwire.Sig - regularOpt.WhenSome(func(s lnwire.Sig) { - sig = s - }) - - return fn.Some(NewRegularSigType(sig)) - } - - return fn.None[SigType]() - } - - fields.CloserNoClosee = createSigType( - msg.TaprootClosingSigs.CloserNoClosee.ValOpt(), - msg.ClosingSigs.CloserNoClosee.ValOpt(), - ) - - fields.NoCloserClosee = createSigType( - msg.TaprootClosingSigs.NoCloserClosee.ValOpt(), - msg.ClosingSigs.NoCloserClosee.ValOpt(), - ) - - fields.CloserAndClosee = createSigType( - msg.TaprootClosingSigs.CloserAndClosee.ValOpt(), - msg.ClosingSigs.CloserAndClosee.ValOpt(), - ) - - return fields -} - -// validateSigFields validates that the signature field set conforms to BOLT -// spec requirements based on the receiver's (closee's) output dust status. -func validateSigFields(sigFields SigFieldSet, localIsDust bool) error { - // Check if any signature is present at all, if not then this is a - // terminal error. - if !sigFields.HasAnySig() { - return ErrNoSig - } - - // Per BOLT spec for the receiver (closee) of closing_complete: - // - // "Select a signature for validation: - // 1. If the local output amount is dust: MUST use closer_output_only - // (CloserNoClosee). - // 3. Otherwise, if closer_and_closee_outputs is present: MUST use - // closer_and_closee_outputs (CloserAndClosee). - // 4. Otherwise: MUST use closee_output_only (NoCloserClosee)." - // - // We validate that the required signature field is present. - if localIsDust { - // Local output is dust, we need CloserNoClosee. - if sigFields.CloserNoClosee.IsNone() { - return fmt.Errorf("local output is dust but "+ - "CloserNoClosee sig missing: %w", - ErrCloserNoClosee) - } - } else { - // Local output is not dust, we prefer CloserAndClosee, but can - // fall back to NoCloserClosee per spec step 4. - if sigFields.CloserAndClosee.IsNone() && - sigFields.NoCloserClosee.IsNone() { - - return fmt.Errorf("local output is not dust "+ - "but no valid sig field present: %w", - ErrCloserAndClosee) - } - } - - return nil -} - -// selectAndExtractSig selects the appropriate signature field based on BOLT -// spec priority and extracts the signature and nonce. -func selectAndExtractSig( - fields SigFieldSet, localIsDust bool, -) (lnwire.Sig, fn.Option[lnwire.Musig2Nonce], bool, - error) { - - // Select which field to use based on BOLT spec priority. - var ( - selectedField fn.Option[SigType] - isNoClosee bool - ) - - if localIsDust { - // Spec step 1: Local output is dust, use - // CloserNoClosee. - selectedField = fields.CloserNoClosee - isNoClosee = true - } else { - // Spec step 3: Prefer CloserAndClosee if present. - if fields.CloserAndClosee.IsSome() { - selectedField = fields.CloserAndClosee - isNoClosee = false - } else { - // Spec step 4: Fallback to NoCloserClosee. - selectedField = fields.NoCloserClosee - isNoClosee = false - } - } - - // If the selected field is none, this is an error. - sigType, err := selectedField.UnwrapOrErr(ErrNoSig) - if err != nil { - return lnwire.Sig{}, fn.None[lnwire.Musig2Nonce](), - false, err - } - - // Check if this is a taproot signature (Right side of - // Either) or regular (Left side). - var sig lnwire.Sig - nonce := fn.None[lnwire.Musig2Nonce]() - - // If this is a regular signature, extract it directly. - sigType.WhenLeft(func(regularSig lnwire.Sig) { - sig = regularSig - }) - - // Otherwise, for taproot, extract the partial sig and - // nonce. - sigType.WhenRight(func(ps lnwire.PartialSigWithNonce) { - nonce = fn.Some(ps.Nonce) - sig = partialSigToWireSig(ps.PartialSig) - }) - - return sig, nonce, isNoClosee, nil -} - -// extractSigAndNonceFromClosingComplete extracts signature and optional -// nonce from ClosingComplete using a three-phase approach: parse, -// validate, and select. -// -// This function implements the BOLT spec requirements for the receiver (closee) -// of a closing_complete message. -func extractSigAndNonceFromClosingComplete( - msg lnwire.ClosingComplete, - localIsDust, isTaproot bool, -) (lnwire.Sig, fn.Option[lnwire.Musig2Nonce], bool, - error) { - - // First, parse the message to extract which signature fields are - // present. - fields := parseSigFields(msg) - - // Validate that the signature type matches the channel type. Taproot - // channels must have taproot signatures, and non-taproot channels must - // have regular signatures. - switch { - case isTaproot && !fields.IsTaproot() && fields.HasAnySig(): - return lnwire.Sig{}, fn.None[lnwire.Musig2Nonce](), false, - fmt.Errorf("taproot channel requires taproot " + - "signatures, got regular signatures") - - case !isTaproot && fields.IsTaproot(): - return lnwire.Sig{}, fn.None[lnwire.Musig2Nonce](), false, - fmt.Errorf("non-taproot channel requires regular " + - "signatures, got taproot signatures") - } - - // Next, validate that the parsed fields conform to BOLT spec - // requirements based on our (closee's) output dust status. - if err := validateSigFields(fields, localIsDust); err != nil { - return lnwire.Sig{}, fn.None[lnwire.Musig2Nonce](), false, err - } - - // Finally, select and extract the appropriate signature - // based on BOLT spec priority. - sig, nonce, isNoClosee, err := selectAndExtractSig( - fields, localIsDust, - ) - if err != nil { - return lnwire.Sig{}, fn.None[lnwire.Musig2Nonce](), - false, err - } - - return sig, nonce, isNoClosee, nil -} - // ProcessEvent implements the state transition function for the // RemoteCloseStart. In this state, we'll wait for the remote party to send a // closing_complete message. Assuming they can pay for the fees, we'll sign it // ourselves, then transition to the next state of ClosePending. -func (l *RemoteCloseStart) ProcessEvent(event ProtocolEvent, env *Environment, -) (*CloseStateTransition, error) { +func (l *RemoteCloseStart) ProcessEvent(event ProtocolEvent, + env *Environment) (*CloseStateTransition, error) { switch msg := event.(type) { //nolint:gocritic // If we receive a OfferReceived event, we'll make sure they can @@ -2001,14 +998,35 @@ func (l *RemoteCloseStart) ProcessEvent(event ProtocolEvent, env *Environment, l.RemoteBalance.ToSatoshis()) } - // Extract the signature and JIT nonce from the ClosingComplete - // message. This function parses, validates, and selects the - // appropriate signature per BOLT spec. - sig, jitNonce, noClosee, err := extractSigAndNonceFromClosingComplete( //nolint:ll - msg.SigMsg, l.LocalAmtIsDust(), env.IsTaproot(), + // With the basic sanity checks out of the way, we'll now + // figure out which signature that we'll attempt to sign + // against. + var ( + remoteSig input.Signature + noClosee bool ) - if err != nil { - return nil, err + switch { + // If our balance is dust, then we expect the CloserNoClosee + // sig to be set. + case l.LocalAmtIsDust(): + if msg.SigMsg.CloserNoClosee.IsNone() { + return nil, ErrCloserNoClosee + } + msg.SigMsg.CloserNoClosee.WhenSomeV(func(s lnwire.Sig) { + remoteSig, _ = s.ToSignature() + noClosee = true + }) + + // Otherwise, we'll assume that CloseAndClosee is set. + // + // TODO(roasbeef): NoCloserClosee, but makes no sense? + default: + if msg.SigMsg.CloserAndClosee.IsNone() { + return nil, ErrCloserAndClosee + } + msg.SigMsg.CloserAndClosee.WhenSomeV(func(s lnwire.Sig) { //nolint:ll + remoteSig, _ = s.ToSignature() + }) } chanOpts := []lnwallet.ChanCloseOpt{ @@ -2017,44 +1035,10 @@ func (l *RemoteCloseStart) ProcessEvent(event ProtocolEvent, env *Environment, lnwallet.WithCustomPayer(lntypes.Remote), } - var remoteSig input.Signature - - // For taproot channels, add MusigSession options if available. - // When we're the closee (sending closing_sig), we use - // RemoteMusigSession. - switch { - case env.IsTaproot(): - // First, process the remote taproot signature which - // initializes the remote nonce via InitRemoteNonce(). - // This must happen before ProposalClosingOpts() which - // requires the nonce to be set. - remoteSig, err = processRemoteTaprootSig( - env, msg.SigMsg, jitNonce, noClosee, - ) - if err != nil { - return nil, err - } - - // Now that the nonce is initialized, get the musig - // closing options. - session := env.RemoteMusigSession - musigOpts, err := session.ProposalClosingOpts() - if err != nil { - return nil, fmt.Errorf("failed to get musig "+ - "closing opts: %w", err) - } - chanOpts = append(chanOpts, musigOpts...) - default: - remoteSig, err = sig.ToSignature() - if err != nil { - return nil, err - } - } - - chancloserLog.Infof("RemoteCloseStart: responding to close w/ "+ - "local_addr=%x, remote_addr=%x, fee=%v, locktime=%v", + chancloserLog.Infof("responding to close w/ local_addr=%x, "+ + "remote_addr=%x, fee=%v", l.LocalDeliveryScript[:], l.RemoteDeliveryScript[:], - msg.SigMsg.FeeSatoshis, msg.SigMsg.LockTime) + msg.SigMsg.FeeSatoshis) // Now that we have the remote sig, we'll sign the version they // signed, then attempt to complete the cooperative close @@ -2062,13 +1046,21 @@ func (l *RemoteCloseStart) ProcessEvent(event ProtocolEvent, env *Environment, // // TODO(roasbeef): need to be able to omit an output when // signing based on the above, as closing opt - wireSig, localSig, err := createLocalCloseeSignature( - env, msg.SigMsg.FeeSatoshis, l.LocalDeliveryScript, - l.RemoteDeliveryScript, chanOpts, + rawSig, _, _, err := env.CloseSigner.CreateCloseProposal( + msg.SigMsg.FeeSatoshis, l.LocalDeliveryScript, + l.RemoteDeliveryScript, chanOpts..., ) if err != nil { - return nil, fmt.Errorf("RemoteCloseStart: unable "+ - "to create closee sig: %w", err) + return nil, err + } + wireSig, err := lnwire.NewSigFromSignature(rawSig) + if err != nil { + return nil, err + } + + localSig, err := wireSig.ToSignature() + if err != nil { + return nil, err } // With our signature created, we'll now attempt to finalize the @@ -2079,8 +1071,7 @@ func (l *RemoteCloseStart) ProcessEvent(event ProtocolEvent, env *Environment, chanOpts..., ) if err != nil { - return nil, fmt.Errorf("RemoteCloseStart: unable "+ - "to complete coop close: %w", err) + return nil, err } chancloserLog.Infof("ChannelPoint(%v): received sig (fee=%v "+ @@ -2089,20 +1080,15 @@ func (l *RemoteCloseStart) ProcessEvent(event ProtocolEvent, env *Environment, lnutils.SpewLogClosure(closeTx), ) - // Invalidate the closee nonce that was consumed for signing. - // This forces createClosingSigMessage to generate a fresh - // nonce for NextCloseeNonce in the next RBF round. - if env.IsTaproot() { - env.RemoteMusigSession.InvalidateNonce() - } - - closingSigMsg, err := createClosingSigMessage( - env, wireSig, localSig, l.LocalDeliveryScript, - l.RemoteDeliveryScript, msg.SigMsg.FeeSatoshis, - msg.SigMsg.LockTime, noClosee, - ) - if err != nil { - return nil, err + var closingSigs lnwire.ClosingSigs + if noClosee { + closingSigs.CloserNoClosee = newSigTlv[tlv.TlvType1]( + wireSig, + ) + } else { + closingSigs.CloserAndClosee = newSigTlv[tlv.TlvType3]( + wireSig, + ) } // As we're about to broadcast a new version of the co-op close @@ -2115,9 +1101,19 @@ func (l *RemoteCloseStart) ProcessEvent(event ProtocolEvent, env *Environment, return nil, err } + // As we transition, we'll omit two events: one to broadcast + // the transaction, and the other to send our ClosingSig + // message to the remote party. sendEvent := &protofsm.SendMsgEvent[ProtocolEvent]{ TargetPeer: env.ChanPeer, - Msgs: []lnwire.Message{closingSigMsg}, + Msgs: []lnwire.Message{&lnwire.ClosingSig{ + ChannelID: env.ChanID, + CloserScript: l.RemoteDeliveryScript, + CloseeScript: l.LocalDeliveryScript, + FeeSatoshis: msg.SigMsg.FeeSatoshis, + LockTime: msg.SigMsg.LockTime, + ClosingSigs: closingSigs, + }}, } broadcastEvent := &protofsm.BroadcastTxn{ Tx: closeTx, @@ -2159,8 +1155,8 @@ func (l *RemoteCloseStart) ProcessEvent(event ProtocolEvent, env *Environment, // ProcessEvent is a semi-terminal state in the rbf-coop close state machine. // In this state, we're waiting for either a confirmation, or for either side // to attempt to create a new RBF'd co-op close transaction. -func (c *ClosePending) ProcessEvent(event ProtocolEvent, env *Environment, -) (*CloseStateTransition, error) { +func (c *ClosePending) ProcessEvent(event ProtocolEvent, + _ *Environment) (*CloseStateTransition, error) { switch msg := event.(type) { // If we can a spend while waiting for the close, then we'll go to our @@ -2208,8 +1204,8 @@ func (c *ClosePending) ProcessEvent(event ProtocolEvent, env *Environment, // ProcessEvent is the event processing for out terminal state. In this state, // we just keep looping back on ourselves. -func (c *CloseFin) ProcessEvent(event ProtocolEvent, env *Environment, -) (*CloseStateTransition, error) { +func (c *CloseFin) ProcessEvent(_ ProtocolEvent, + _ *Environment) (*CloseStateTransition, error) { return &CloseStateTransition{ NextState: c, @@ -2220,8 +1216,8 @@ func (c *CloseFin) ProcessEvent(event ProtocolEvent, env *Environment, // In this state, we hit a validation error in an earlier state, so we'll remain // in this state for the user to examine. We may also process new requests to // continue the state machine. -func (c *CloseErr) ProcessEvent(event ProtocolEvent, env *Environment, -) (*CloseStateTransition, error) { +func (c *CloseErr) ProcessEvent(event ProtocolEvent, + _ *Environment) (*CloseStateTransition, error) { switch msg := event.(type) { // If we get a send offer event in this state, then we're doing a state @@ -2249,6 +1245,7 @@ func (c *CloseErr) ProcessEvent(event ProtocolEvent, env *Environment, InternalEvent: []ProtocolEvent{msg}, }), }, nil + default: return &CloseStateTransition{ NextState: c, diff --git a/lnwallet/chanfunding/canned_assembler.go b/lnwallet/chanfunding/canned_assembler.go index 1a5f03103..e28cbb96d 100644 --- a/lnwallet/chanfunding/canned_assembler.go +++ b/lnwallet/chanfunding/canned_assembler.go @@ -5,9 +5,9 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" diff --git a/lnwallet/chanfunding/coin_select.go b/lnwallet/chanfunding/coin_select.go index f4cab3c66..26fe9b92b 100644 --- a/lnwallet/chanfunding/coin_select.go +++ b/lnwallet/chanfunding/coin_select.go @@ -4,8 +4,8 @@ import ( "errors" "fmt" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/txscript/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcwallet/wallet" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet/chainfee" diff --git a/lnwallet/chanfunding/coin_select_test.go b/lnwallet/chanfunding/coin_select_test.go index a742cda2e..e20259c86 100644 --- a/lnwallet/chanfunding/coin_select_test.go +++ b/lnwallet/chanfunding/coin_select_test.go @@ -5,8 +5,8 @@ import ( "regexp" "testing" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/wallet" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet/chainfee" @@ -123,6 +123,7 @@ func TestCalculateFees(t *testing.T) { fundingOutputEstimate.AddP2WSHOutput() for _, test := range testCases { + test := test t.Run(test.name, func(t *testing.T) { feeNoChange, feeWithChange, err := calculateFees( test.utxos, feeRate, fundingOutputEstimate, @@ -308,6 +309,7 @@ func TestCoinSelect(t *testing.T) { fundingOutputEstimate.AddP2WSHOutput() for _, test := range testCases { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -449,6 +451,7 @@ func TestCalculateChangeAmount(t *testing.T) { }} for _, tc := range testCases { + tc := tc t.Run(tc.name, func(tt *testing.T) { changeAmt, needMore, err := CalculateChangeAmount( tc.totalInputAmt, tc.requiredAmt, @@ -641,6 +644,7 @@ func TestCoinSelectSubtractFees(t *testing.T) { fundingOutputEstimate.AddP2WSHOutput() for _, test := range testCases { + test := test t.Run(test.name, func(t *testing.T) { feeRate := feeRate @@ -889,6 +893,7 @@ func TestCoinSelectUpToAmount(t *testing.T) { fundingOutputEstimate.AddP2WSHOutput() for _, test := range testCases { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/lnwallet/chanfunding/interface.go b/lnwallet/chanfunding/interface.go index 993a0b0b6..e40c4a115 100644 --- a/lnwallet/chanfunding/interface.go +++ b/lnwallet/chanfunding/interface.go @@ -3,10 +3,9 @@ package chanfunding import ( "time" - "github.com/btcsuite/btcd/address/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/wtxmgr" "github.com/lightningnetwork/lnd/fn/v2" @@ -116,7 +115,7 @@ type Request struct { // ChangeAddr is a closure that will provide the Assembler with a // change address for the funding transaction if needed. - ChangeAddr func() (address.Address, error) + ChangeAddr func() (btcutil.Address, error) // Musig2 if true, then musig2 will be used to generate the funding // output. By definition, this'll also use segwit v1 (taproot) for the diff --git a/lnwallet/chanfunding/psbt_assembler.go b/lnwallet/chanfunding/psbt_assembler.go index 13cd9416b..dd1bedd05 100644 --- a/lnwallet/chanfunding/psbt_assembler.go +++ b/lnwallet/chanfunding/psbt_assembler.go @@ -5,15 +5,14 @@ import ( "fmt" "sync" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/psbt/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -177,7 +176,7 @@ func (i *PsbtIntent) BindTapscriptRoot(root fn.Option[chainhash.Hash]) { // channel output this intent was created for. It returns the P2WSH funding // address, the exact funding amount and a PSBT packet that contains exactly one // output that encodes the previous two parameters. -func (i *PsbtIntent) FundingParams() (address.Address, int64, *psbt.Packet, +func (i *PsbtIntent) FundingParams() (btcutil.Address, int64, *psbt.Packet, error) { if i.State != PsbtOutputKnown { diff --git a/lnwallet/chanfunding/psbt_assembler_test.go b/lnwallet/chanfunding/psbt_assembler_test.go index 0906d95bd..461b83f5a 100644 --- a/lnwallet/chanfunding/psbt_assembler_test.go +++ b/lnwallet/chanfunding/psbt_assembler_test.go @@ -10,13 +10,12 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/psbt/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -70,7 +69,7 @@ func TestPsbtIntent(t *testing.T) { ) require.NoError(t, err, "error calculating script") witnessScriptHash := sha256.Sum256(script) - addr, err := address.NewAddressWitnessScriptHash( + addr, err := btcutil.NewAddressWitnessScriptHash( witnessScriptHash[:], ¶ms, ) require.NoError(t, err, "unable to encode address") @@ -191,7 +190,7 @@ func TestPsbtIntentBasePsbt(t *testing.T) { ) require.NoError(t, err, "error calculating script") witnessScriptHash := sha256.Sum256(script) - addr, err := address.NewAddressWitnessScriptHash( + addr, err := btcutil.NewAddressWitnessScriptHash( witnessScriptHash[:], ¶ms, ) require.NoError(t, err, "unable to encode address") @@ -457,6 +456,7 @@ func TestPsbtVerify(t *testing.T) { // Loop through all our test cases. for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { // Reset the state from a previous test and create a new // pending PSBT that we can manipulate. @@ -622,6 +622,7 @@ func TestPsbtFinalize(t *testing.T) { // Loop through all our test cases. for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { // Reset the state from a previous test and create a new // pending PSBT that we can manipulate. @@ -738,6 +739,7 @@ func TestVerifyAllInputsSegWit(t *testing.T) { }} for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { r := strings.NewReader(tc.packet) diff --git a/lnwallet/chanfunding/wallet_assembler.go b/lnwallet/chanfunding/wallet_assembler.go index 161840d9c..ce2b406c6 100644 --- a/lnwallet/chanfunding/wallet_assembler.go +++ b/lnwallet/chanfunding/wallet_assembler.go @@ -6,10 +6,10 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/btcutil/v2/txsort" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/txsort" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/wtxmgr" "github.com/lightningnetwork/lnd/input" diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 436bff1e4..484a019da 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -7,7 +7,6 @@ import ( "crypto/sha256" "errors" "fmt" - "io" "slices" "sync" @@ -15,16 +14,15 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/btcutil/v2/txsort" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/txsort" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/mempool" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btclog/v2" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/input" @@ -604,6 +602,8 @@ func (lc *LightningChannel) extractPayDescs(feeRate chainfee.SatPerKWeight, // persist state w.r.t to if forwarded or not, or can // inadvertently trigger replays + htlc := htlc + auxLeaf := fn.FlatMapOption( func(l CommitAuxLeaves) input.AuxTapLeaf { leaves := l.OutgoingHtlcLeaves @@ -794,7 +794,7 @@ type LightningChannel struct { // state, which we are able to broadcast safely. commitChains lntypes.Dual[*commitmentChain] - channelState *chanstate.OpenChannel + channelState *channeldb.OpenChannel commitBuilder *CommitmentBuilder @@ -833,20 +833,6 @@ type LightningChannel struct { // is created. type ChannelOpt func(*channelOpts) -// AuxHtlcValidator is an interface for validating whether an HTLC can be added -// to a custom channel. It is called during HTLC validation with the current -// channel state and HTLC details. This allows external components (like the -// traffic shaper) to perform final validation checks against the most -// up-to-date channel state before the HTLC is committed. -type AuxHtlcValidator interface { - // ValidateHtlc checks whether the given HTLC can be added to the - // channel given the current link bandwidth, custom records, and HTLC - // view. - ValidateHtlc(amount, linkBandwidth lnwire.MilliSatoshi, - customRecords lnwire.CustomRecords, - view AuxHtlcView) error -} - // channelOpts is the set of options used to create a new channel. type channelOpts struct { localNonce *musig2.Nonces @@ -856,19 +842,7 @@ type channelOpts struct { auxSigner fn.Option[AuxSigner] auxResolver fn.Option[AuxContractResolver] - // auxHtlcValidator is an optional validator that performs custom - // validation on HTLCs before they are added to the channel state. - auxHtlcValidator fn.Option[AuxHtlcValidator] - skipNonceInit bool - - // customSigningRand is an optional custom random source for generating - // deterministic JIT signing nonces in MuSig2 sessions. - // - // WARNING: This MUST only be used for test vector generation. Setting - // this in production will produce deterministic nonces, enabling - // private key extraction via nonce reuse. - customSigningRand fn.Option[io.Reader] } // WithLocalMusigNonces is used to bind an existing verification/local nonce to @@ -920,27 +894,6 @@ func WithAuxResolver(resolver AuxContractResolver) ChannelOpt { } } -// WithCustomSigningRand is used to provide a custom random source for -// generating deterministic JIT signing nonces in MuSig2 sessions. -// -// WARNING: This MUST only be used for test vector generation. Setting this in -// production will produce deterministic nonces, enabling private key extraction -// via nonce reuse. -func WithCustomSigningRand(rand io.Reader) ChannelOpt { - return func(o *channelOpts) { - o.customSigningRand = fn.Some[io.Reader](rand) - } -} - -// WithAuxHtlcValidator is used to specify a custom HTLC validator for the -// channel. This allows external components to perform additional validation on -// HTLCs before they are added to the channel state. -func WithAuxHtlcValidator(validator AuxHtlcValidator) ChannelOpt { - return func(o *channelOpts) { - o.auxHtlcValidator = fn.Some(validator) - } -} - // defaultChannelOpts returns the set of default options for a new channel. func defaultChannelOpts() *channelOpts { return &channelOpts{} @@ -952,7 +905,7 @@ func defaultChannelOpts() *channelOpts { // automatically persist pertinent state to the database in an efficient // manner. func NewLightningChannel(signer input.Signer, - state *chanstate.OpenChannel, + state *channeldb.OpenChannel, sigPool *SigPool, chanOpts ...ChannelOpt) (*LightningChannel, error) { opts := defaultChannelOpts() @@ -1773,6 +1726,7 @@ func (lc *LightningChannel) restorePendingRemoteUpdates( len(unsignedAckedUpdates)) for _, logUpdate := range unsignedAckedUpdates { + logUpdate := logUpdate payDesc, err := lc.remoteLogUpdateToPayDesc( &logUpdate, lc.updateLogs.Local, localCommitmentHeight, @@ -1852,6 +1806,7 @@ func (lc *LightningChannel) restorePeerLocalUpdates(updates []channeldb.LogUpdat len(updates)) for _, logUpdate := range updates { + logUpdate := logUpdate payDesc, err := lc.localLogUpdateToPayDesc( &logUpdate, lc.updateLogs.Remote, @@ -1905,6 +1860,7 @@ func (lc *LightningChannel) restorePendingLocalUpdates( // If we did have a dangling commit, then we'll examine which updates // we included in that state and re-insert them into our update log. for _, logUpdate := range pendingRemoteCommitDiff.LogUpdates { + logUpdate := logUpdate payDesc, err := lc.logUpdateToPayDesc( &logUpdate, lc.updateLogs.Remote, pendingHeight, @@ -2080,11 +2036,6 @@ type BreachRetribution struct { // RemoteResolutionBlob is a blob used for aux channels that permits an // honest party to sweep the remote commitment output. RemoteResolutionBlob fn.Option[tlv.Blob] - - // ChanType is the channel type of the breached channel, used to - // determine whether production taproot scripts should be used when - // constructing justice transactions. - ChanType channeldb.ChannelType } // NewBreachRetribution creates a new fully populated BreachRetribution for the @@ -2094,9 +2045,7 @@ type BreachRetribution struct { // nil, then the revocation log will be checked to see if it contains the info // required to construct the BreachRetribution. If the revocation log is missing // the required fields then ErrRevLogDataMissing will be returned. -// -//nolint:funlen -func NewBreachRetribution(chanState *chanstate.OpenChannel, stateNum uint64, +func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, breachHeight uint32, spendTx *wire.MsgTx, leafStore fn.Option[AuxLeafStore], auxResolver fn.Option[AuxContractResolver]) (*BreachRetribution, @@ -2396,7 +2345,7 @@ func NewBreachRetribution(chanState *chanstate.OpenChannel, stateNum uint64, // createHtlcRetribution is a helper function to construct an HtlcRetribution // based on the passed params. -func createHtlcRetribution(chanState *chanstate.OpenChannel, +func createHtlcRetribution(chanState *channeldb.OpenChannel, keyRing *CommitmentKeyRing, commitHash chainhash.Hash, commitmentSecret *btcec.PrivateKey, leaseExpiry uint32, htlc *channeldb.HTLCEntry, @@ -2523,7 +2472,7 @@ func createHtlcRetribution(chanState *chanstate.OpenChannel, // see if these fields are present there. If they are not, then // ErrRevLogDataMissing is returned. func createBreachRetribution(revokedLog *channeldb.RevocationLog, - spendTx *wire.MsgTx, chanState *chanstate.OpenChannel, + spendTx *wire.MsgTx, chanState *channeldb.OpenChannel, keyRing *CommitmentKeyRing, commitmentSecret *btcec.PrivateKey, leaseExpiry uint32, auxLeaves fn.Option[CommitAuxLeaves]) (*BreachRetribution, int64, int64, @@ -2632,7 +2581,6 @@ func createBreachRetribution(revokedLog *channeldb.RevocationLog, RemoteOutpoint: theirOutpoint, HtlcRetributions: htlcRetributions, KeyRing: keyRing, - ChanType: chanState.ChanType, }, ourAmt, theirAmt, nil } @@ -2640,7 +2588,7 @@ func createBreachRetribution(revokedLog *channeldb.RevocationLog, // BreachRetribution using a ChannelCommitment. Returns the constructed // retribution, our amount, their amount, and a possible non-nil error. func createBreachRetributionLegacy(revokedLog *channeldb.ChannelCommitment, - chanState *chanstate.OpenChannel, keyRing *CommitmentKeyRing, + chanState *channeldb.OpenChannel, keyRing *CommitmentKeyRing, commitmentSecret *btcec.PrivateKey, ourScript, theirScript input.ScriptDescriptor, leaseExpiry uint32) (*BreachRetribution, int64, int64, error) { @@ -2708,7 +2656,6 @@ func createBreachRetributionLegacy(revokedLog *channeldb.ChannelCommitment, RemoteOutpoint: theirOutpoint, HtlcRetributions: htlcRetributions, KeyRing: keyRing, - ChanType: chanState.ChanType, }, ourAmt, theirAmt, nil } @@ -2791,21 +2738,9 @@ func (lc *LightningChannel) FetchLatestAuxHTLCView() AuxHtlcView { lc.RLock() defer lc.RUnlock() - nextHeight := lc.commitChains.Local.tip().height + 1 - - // We use the remote ACKed index from the last signed local commitment - // (tail) rather than the remote's latest log index. This ensures we - // only include remote HTLCs that have been locked into a signed - // commitment, giving the aux validator a stable, consistent view that - // matches the actual commitment state used for balance calculations. - remoteACKedIndex := lc.commitChains.Local.tail().messageIndices.Remote - view := lc.fetchHTLCView( - remoteACKedIndex, lc.updateLogs.Local.logIndex, - ) - - view.NextHeight = nextHeight - - return newAuxHtlcView(view) + return newAuxHtlcView(lc.fetchHTLCView( + lc.updateLogs.Remote.logIndex, lc.updateLogs.Local.logIndex, + )) } // fetchHTLCView returns all the candidate HTLC updates which should be @@ -2993,7 +2928,7 @@ func (lc *LightningChannel) fetchCommitmentView( // fundingTxIn returns the funding output as a transaction input. The input // returned by this function uses a max sequence number, so it isn't able to be // used with RBF by default. -func fundingTxIn(chanState *chanstate.OpenChannel) wire.TxIn { +func fundingTxIn(chanState *channeldb.OpenChannel) wire.TxIn { return *wire.NewTxIn(&chanState.FundingOutpoint, nil, nil) } @@ -3249,7 +3184,7 @@ func (lc *LightningChannel) fetchParent(entry *paymentDescriptor, // configured reserve. It also uses the balance delta for the party, to account // for entry amounts that have been processed already. func balanceAboveReserve(party lntypes.ChannelParty, delta int64, - channel *chanstate.OpenChannel) bool { + channel *channeldb.OpenChannel) bool { // We're going to access the channel state, so let's make sure we're // holding the lock. @@ -3338,7 +3273,7 @@ func (lc *LightningChannel) evaluateNoOpHtlc(entry *paymentDescriptor, // signature can be submitted to the sigPool to generate all the signatures // asynchronously and in parallel. func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, - chanState *chanstate.OpenChannel, leaseExpiry uint32, + chanState *channeldb.OpenChannel, leaseExpiry uint32, remoteCommitView *commitment, leafStore fn.Option[AuxLeafStore]) ([]SignJob, []AuxSigJob, chan struct{}, error) { @@ -4455,40 +4390,11 @@ func (lc *LightningChannel) ProcessChanSyncMsg(ctx context.Context, } } - // If this is a taproot channel, then we expect the remote party to - // have sent the next verification nonce. We prioritize the new - // LocalNonces field over the legacy LocalNonce field for backwards - // compatibility. If no nonce is present, we'll bail out. + // If this is a taproot channel, then we expect that the remote party + // has sent the next verification nonce. If they haven't, then we'll + // bail out, otherwise we'll init our local session then continue as + // normal. switch { - case lc.channelState.ChanType.IsTaproot() && msg.LocalNonces.IsSome(): - // The IsSome() guard above guarantees this unwrap succeeds. - noncesData := msg.LocalNonces.UnsafeFromSome() - - // Extract the nonce for the main commitment by looking up the - // funding TXID, as the commitment tx spends the funding - // outpoint. - fundingTxid := lc.channelState.FundingOutpoint.Hash - commitNonce, ok := noncesData.NoncesMap[fundingTxid] - if !ok { - return nil, nil, nil, fmt.Errorf( - "remote LocalNonces missing nonce "+ - "for funding txid %v", fundingTxid, - ) - } - - if lc.opts.skipNonceInit { - break - } - - initErr := lc.InitRemoteMusigNonces(&musig2.Nonces{ - PubNonce: commitNonce, - }) - if initErr != nil { - return nil, nil, nil, fmt.Errorf( - "unable to init remote nonce: %w", initErr, - ) - } - case lc.channelState.ChanType.IsTaproot() && msg.LocalNonce.IsNone(): return nil, nil, nil, fmt.Errorf("remote verification nonce " + "not sent") @@ -4968,7 +4874,7 @@ func (lc *LightningChannel) recordSettlement( // directly into the pool of workers. // //nolint:funlen -func genHtlcSigValidationJobs(chanState *chanstate.OpenChannel, +func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel, localCommitmentView *commitment, keyRing *CommitmentKeyRing, htlcSigs []lnwire.Sig, leaseExpiry uint32, leafStore fn.Option[AuxLeafStore], auxSigner fn.Option[AuxSigner], @@ -5830,47 +5736,6 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck, return revocationMsg, newCommitment.Htlcs, finalHtlcs, nil } -// extractRevokeAndAckNonce extracts the next verification nonce from a -// RevokeAndAck message. It prioritizes the new LocalNonces field over the -// legacy LocalNonce field for backwards compatibility. The fundingTxid is used -// to validate the nonce map key per the spec (bolts#995). If neither field is -// present, an error is returned. -func extractRevokeAndAckNonce(revMsg *lnwire.RevokeAndAck, - fundingTxid chainhash.Hash) (lnwire.Musig2Nonce, error) { - - switch { - case revMsg.LocalNonces.IsSome(): - noncesData, err := revMsg.LocalNonces.UnwrapOrErr( - fmt.Errorf("invalid LocalNonces"), - ) - if err != nil { - return lnwire.Musig2Nonce{}, err - } - - // Per the spec, the nonce map key must match the channel's - // funding txid. Validate this before using the nonce. - nonce, ok := noncesData.NoncesMap[fundingTxid] - if ok { - return nonce, nil - } - - return lnwire.Musig2Nonce{}, fmt.Errorf("no nonce for "+ - "funding txid %v in revoke_and_ack", fundingTxid) - - case revMsg.LocalNonce.IsSome(): - localNonce, err := revMsg.LocalNonce.UnwrapOrErrV(errNoNonce) - if err != nil { - return lnwire.Musig2Nonce{}, err - } - - return localNonce, nil - - default: - return lnwire.Musig2Nonce{}, fmt.Errorf("remote " + - "verification nonce not sent") - } -} - // ReceiveRevocation processes a revocation sent by the remote party for the // lowest unrevoked commitment within their commitment chain. We receive a // revocation either during the initial session negotiation wherein revocation @@ -6056,16 +5921,15 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ( // Now that we have a new verification nonce from them, we can refresh // our remote musig2 session which allows us to create another state. if lc.channelState.ChanType.IsTaproot() { - fundingTxid := lc.channelState.FundingOutpoint.Hash - localNonce, err := extractRevokeAndAckNonce( - revMsg, fundingTxid, - ) + localNonce, err := revMsg.LocalNonce.UnwrapOrErrV(errNoNonce) if err != nil { return nil, nil, err } session, err := lc.musigSessions.RemoteSession.Refresh( - &musig2.Nonces{PubNonce: localNonce}, + &musig2.Nonces{ + PubNonce: localNonce, + }, ) if err != nil { return nil, nil, err @@ -6201,52 +6065,6 @@ func (lc *LightningChannel) addHTLC(htlc *lnwire.UpdateAddHTLC, return 0, err } - // If an auxiliary HTLC validator is configured, call it now to perform - // custom validation checks against the current channel state. This is - // the final validation point before the HTLC is added to the update - // log, ensuring that the validator sees the most up-to-date state - // including all previously validated HTLCs in this batch. - // - // NOTE: This is called after the standard commitment sanity checks to - // ensure we only perform (potentially) expensive custom validation on - // HTLCs that have already passed the basic Lightning protocol - // constraints. - err := fn.MapOptionZ( - lc.opts.auxHtlcValidator, - func(validator AuxHtlcValidator) error { - // Fetch the current HTLC view which includes all - // pending HTLCs that haven't been committed yet. This - // provides the validator with the most accurate state. - commitChain := lc.commitChains.Local - remoteIndex := commitChain.tail().messageIndices.Remote - view := lc.fetchHTLCView( - remoteIndex, lc.updateLogs.Local.logIndex, - ) - - nextHeight := lc.commitChains.Local.tip().height + 1 - view.NextHeight = nextHeight - - lc.log.Debugf("Setting view nextheight=%v", nextHeight) - - auxView := newAuxHtlcView(view) - - // Get the current available balance for the link - // bandwidth check. This is needed for the balance - // validation in the traffic shaper. We use NoBuffer - // since the buffer check was already performed earlier, - // and assets don't pay on-chain fees. - linkBandwidth, _ := lc.availableBalance(NoBuffer) - - return validator.ValidateHtlc( - pd.Amount, linkBandwidth, pd.CustomRecords, - auxView, - ) - }, - ) - if err != nil { - return 0, fmt.Errorf("aux HTLC validation failed: %w", err) - } - lc.updateLogs.Local.appendHtlc(pd) return pd.HtlcIndex, nil @@ -6398,7 +6216,6 @@ func (lc *LightningChannel) htlcAddDescriptor(htlc *lnwire.UpdateAddHTLC, // remote commitments. func (lc *LightningChannel) validateAddHtlc(pd *paymentDescriptor, buffer BufferType) error { - // Make sure adding this HTLC won't violate any of the constraints we // must keep on the commitment transactions. remoteACKedIndex := lc.commitChains.Local.tail().messageIndices.Remote @@ -6759,13 +6576,6 @@ func (lc *LightningChannel) ChannelPoint() wire.OutPoint { return lc.channelState.FundingOutpoint } -// ChannelState returns a copy of the internal chanstate.OpenChannel state -// struct. Modifications to the returned struct will not be reflected within -// the LightningChannel. -func (lc *LightningChannel) ChannelState() *chanstate.OpenChannel { - return lc.channelState.Copy() -} - // ChannelID returns the ChannelID of this LightningChannel. This is the same // ChannelID that is used in update messages for this channel. func (lc *LightningChannel) ChannelID() lnwire.ChannelID { @@ -6893,7 +6703,7 @@ func GetSignedCommitTx(inputs SignedCommitTxInputs, musigSession := NewPartialMusigSession( *localNonce, inputs.OurKey, inputs.TheirKey, signer, inputs.SignDesc.Output, LocalMusigCommit, - tapscriptTweak, fn.None[io.Reader](), + tapscriptTweak, ) var remoteSig lnwire.PartialSigWithNonce @@ -7072,7 +6882,7 @@ type UnilateralCloseSummary struct { // happen in case we have lost state) it should be set to an empty struct, in // which case we will attempt to sweep the non-HTLC output using the passed // commitPoint. -func NewUnilateralCloseSummary(chanState *chanstate.OpenChannel, +func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, //nolint:funlen signer input.Signer, commitSpend *chainntnfs.SpendDetail, remoteCommit channeldb.ChannelCommitment, commitPoint *btcec.PublicKey, leafStore fn.Option[AuxLeafStore], @@ -7413,7 +7223,7 @@ func newOutgoingHtlcResolution(signer input.Signer, commitTxHeight uint32, htlc *channeldb.HTLC, keyRing *CommitmentKeyRing, feePerKw chainfee.SatPerKWeight, csvDelay, leaseExpiry uint32, whoseCommit lntypes.ChannelParty, isCommitFromInitiator bool, - chanType channeldb.ChannelType, chanState *chanstate.OpenChannel, + chanType channeldb.ChannelType, chanState *channeldb.OpenChannel, auxLeaves fn.Option[CommitAuxLeaves], auxResolver fn.Option[AuxContractResolver], ) (*OutgoingHtlcResolution, error) { @@ -7637,16 +7447,10 @@ func newOutgoingHtlcResolution(signer input.Signer, return nil, err } } else { - // Determine script options based on channel type. - var scriptOpts []input.TaprootScriptOpt - if chanType.IsTaprootFinal() { - scriptOpts = append(scriptOpts, input.WithProdScripts()) - } - //nolint:ll secondLevelScriptTree, err := input.TaprootSecondLevelScriptTree( keyRing.RevocationKey, keyRing.ToLocalKey, csvDelay, - secondLevelAuxLeaf, scriptOpts..., + secondLevelAuxLeaf, ) if err != nil { return nil, err @@ -7787,7 +7591,7 @@ func newIncomingHtlcResolution(signer input.Signer, commitTxHeight uint32, htlc *channeldb.HTLC, keyRing *CommitmentKeyRing, feePerKw chainfee.SatPerKWeight, csvDelay, leaseExpiry uint32, whoseCommit lntypes.ChannelParty, isCommitFromInitiator bool, - chanType channeldb.ChannelType, chanState *chanstate.OpenChannel, + chanType channeldb.ChannelType, chanState *channeldb.OpenChannel, auxLeaves fn.Option[CommitAuxLeaves], auxResolver fn.Option[AuxContractResolver], ) (*IncomingHtlcResolution, error) { @@ -8007,16 +7811,10 @@ func newIncomingHtlcResolution(signer input.Signer, return nil, err } } else { - // Determine script options based on channel type. - var scriptOpts []input.TaprootScriptOpt - if chanType.IsTaprootFinal() { - scriptOpts = append(scriptOpts, input.WithProdScripts()) - } - //nolint:ll secondLevelScriptTree, err := input.TaprootSecondLevelScriptTree( keyRing.RevocationKey, keyRing.ToLocalKey, csvDelay, - secondLevelAuxLeaf, scriptOpts..., + secondLevelAuxLeaf, ) if err != nil { return nil, err @@ -8172,7 +7970,7 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, localChanCfg, remoteChanCfg *channeldb.ChannelConfig, commitTx *wire.MsgTx, commitTxHeight uint32, chanType channeldb.ChannelType, isCommitFromInitiator bool, - leaseExpiry uint32, chanState *chanstate.OpenChannel, + leaseExpiry uint32, chanState *channeldb.OpenChannel, auxLeaves fn.Option[CommitAuxLeaves], auxResolver fn.Option[AuxContractResolver]) (*HtlcResolutions, error) { @@ -8187,6 +7985,7 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, incomingResolutions := make([]IncomingHtlcResolution, 0, len(htlcs)) outgoingResolutions := make([]OutgoingHtlcResolution, 0, len(htlcs)) for _, htlc := range htlcs { + htlc := htlc // We'll skip any HTLC's which were dust on the commitment // transaction, as these don't have a corresponding output @@ -8386,7 +8185,7 @@ func (lc *LightningChannel) ForceClose(opts ...ForceCloseOpt) ( // NewLocalForceCloseSummary generates a LocalForceCloseSummary from the given // channel state. The passed commitTx must be a fully signed commitment // transaction corresponding to localCommit. -func NewLocalForceCloseSummary(chanState *chanstate.OpenChannel, +func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, signer input.Signer, commitTx *wire.MsgTx, commitTxHeight uint32, stateNum uint64, leafStore fn.Option[AuxLeafStore], auxResolver fn.Option[AuxContractResolver]) (*LocalForceCloseSummary, @@ -9041,7 +8840,7 @@ func (lc *LightningChannel) NewAnchorResolutions() (*AnchorResolutions, // NewAnchorResolution returns the information that is required to sweep the // local anchor. -func NewAnchorResolution(chanState *chanstate.OpenChannel, +func NewAnchorResolution(chanState *channeldb.OpenChannel, commitTx *wire.MsgTx, keyRing *CommitmentKeyRing, whoseCommit lntypes.ChannelParty) (*AnchorResolution, error) { @@ -9062,7 +8861,8 @@ func NewAnchorResolution(chanState *chanstate.OpenChannel, return nil, err } if chanState.ChanType.IsTaproot() && whoseCommit.IsRemote() { - localAnchor = remoteAnchor + //nolint:ineffassign + localAnchor, remoteAnchor = remoteAnchor, localAnchor } // TODO(roasbeef): remote anchor not needed above @@ -9431,7 +9231,7 @@ func (lc *LightningChannel) UpdateFee(feePerKw chainfee.SatPerKWeight) error { EntryType: FeeUpdate, } - lc.updateLogs.Local.appendFeeUpdate(pd) + lc.updateLogs.Local.appendUpdate(pd) return nil } @@ -9504,7 +9304,7 @@ func (lc *LightningChannel) ReceiveUpdateFee(feePerKw chainfee.SatPerKWeight) er EntryType: FeeUpdate, } - lc.updateLogs.Remote.appendFeeUpdate(pd) + lc.updateLogs.Remote.appendUpdate(pd) return nil } @@ -9558,25 +9358,9 @@ func (lc *LightningChannel) generateRevocation(height uint64) (*lnwire.RevokeAnd if err != nil { return nil, err } - - fundingTxid := lc.channelState.FundingOutpoint.Hash - nonce := nextVerificationNonce.PubNonce - - // Set the appropriate nonce field based on the channel type. - // Final taproot channels use the map-based LocalNonces field, - // while staging taproot channels use the legacy single - // LocalNonce field. - if lc.channelState.ChanType.IsTaprootFinal() { - noncesMap := make(map[chainhash.Hash]lnwire.Musig2Nonce) - noncesMap[fundingTxid] = nonce - revocationMsg.LocalNonces = lnwire.SomeLocalNonces( - lnwire.LocalNoncesData{ - NoncesMap: noncesMap, - }, - ) - } else { - revocationMsg.LocalNonce = lnwire.SomeMusig2Nonce(nonce) - } + revocationMsg.LocalNonce = lnwire.SomeMusig2Nonce( + nextVerificationNonce.PubNonce, + ) } return revocationMsg, nil @@ -10084,7 +9868,7 @@ func (lc *LightningChannel) IsPending() bool { } // State provides access to the channel's internal state. -func (lc *LightningChannel) State() *chanstate.OpenChannel { +func (lc *LightningChannel) State() *channeldb.OpenChannel { return lc.channelState } @@ -10267,14 +10051,13 @@ func (lc *LightningChannel) InitRemoteMusigNonces(remoteNonce *musig2.Nonces, // TODO(roasbeef): propagate rename of signing and verification nonces sessionCfg := &MusigSessionCfg{ - LocalKey: localChanCfg.MultiSigKey, - RemoteKey: remoteChanCfg.MultiSigKey, - LocalNonce: *localNonce, - RemoteNonce: *remoteNonce, - Signer: lc.Signer, - InputTxOut: &lc.fundingOutput, - TapscriptTweak: lc.channelState.TapscriptRoot, - CustomNonceRand: lc.opts.customSigningRand, + LocalKey: localChanCfg.MultiSigKey, + RemoteKey: remoteChanCfg.MultiSigKey, + LocalNonce: *localNonce, + RemoteNonce: *remoteNonce, + Signer: lc.Signer, + InputTxOut: &lc.fundingOutput, + TapscriptTweak: lc.channelState.TapscriptRoot, } lc.musigSessions = NewMusigPairSession( sessionCfg, diff --git a/lnwallet/channel_revoke_nonces_test.go b/lnwallet/channel_revoke_nonces_test.go deleted file mode 100644 index d0654278d..000000000 --- a/lnwallet/channel_revoke_nonces_test.go +++ /dev/null @@ -1,236 +0,0 @@ -package lnwallet - -import ( - "testing" - - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/stretchr/testify/require" -) - -// extractRevocationNonce is a helper function to extract the nonce from a -// RevokeAndAck message, preferring LocalNonces over LocalNonce. -func extractRevocationNonce(t *testing.T, - msg *lnwire.RevokeAndAck) lnwire.Musig2Nonce { - - if msg.LocalNonces.IsSome() { - noncesData := msg.LocalNonces.UnwrapOrFail(t) - - for _, nonce := range noncesData.NoncesMap { - return nonce - } - - // If map is empty, fall back to LocalNonce. - } - - return msg.LocalNonce.UnwrapOrFailV(t) -} - -// revokeModifier is a functional option to modify a RevokeAndAck message. -type revokeModifier func(*lnwire.RevokeAndAck) - -// generateAndProcessRevocation creates fresh channels, performs a state -// transition to generate a RevokeAndAck message, optionally modifies it, and -// processes it. Returns the revocation message and channels for further -// testing. -func generateAndProcessRevocation(t *testing.T, chanType channeldb.ChannelType, - modifier revokeModifier) ( - *lnwire.RevokeAndAck, *LightningChannel, *LightningChannel, error) { - - aliceChannel, bobChannel, err := CreateTestChannels(t, chanType) - require.NoError(t, err) - - aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) - if err != nil { - return nil, nil, nil, err - } - err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) - if err != nil { - return nil, nil, nil, err - } - - bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() - if err != nil { - return nil, nil, nil, err - } - - // Apply the modifier if provided, we'll use this to mutate things to - // test our logic. - if modifier != nil { - modifier(bobRevocation) - } - - _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) - - return bobRevocation, aliceChannel, bobChannel, err -} - -// TestRevokeAndAckTaprootLocalNonces tests that the RevokeAndAck message -// properly populates the nonce fields based on the channel type. -// Staging taproot channels populate only LocalNonce (legacy behavior). -// Final taproot channels populate only LocalNonces (map-based). -// This ensures backwards compatibility while supporting production peers. -func TestRevokeAndAckTaprootLocalNonces(t *testing.T) { - t.Parallel() - - chanType := channeldb.SimpleTaprootFeatureBit - - t.Run("legacy nonce type only populates LocalNonce", - func(t *testing.T) { - t.Parallel() - - // Staging taproot channels populate only the - // LocalNonce field (legacy behavior). - revMsg, _, _, err := generateAndProcessRevocation( - t, chanType, nil, - ) - require.NoError(t, err) - - // Verify only LocalNonce is populated (legacy - // behavior). - require.True( - t, revMsg.LocalNonce.IsSome(), - "LocalNonce should be populated for legacy "+ - "nonce type", - ) - require.True( - t, revMsg.LocalNonces.IsNone(), - "LocalNonces should NOT be populated for "+ - "legacy nonce type", - ) - }) - - t.Run("extracted nonce from legacy field", func(t *testing.T) { - t.Parallel() - - revMsg, _, _, err := generateAndProcessRevocation( - t, chanType, nil, - ) - require.NoError(t, err) - - // Verify we can extract the nonce from the legacy field. - legacyNonce := revMsg.LocalNonce.UnwrapOrFailV(t) - extractedNonce := extractRevocationNonce(t, revMsg) - require.Equal( - t, legacyNonce, extractedNonce, - "Extracted nonce should match legacy nonce", - ) - }) - - t.Run("receive with only LocalNonces field", func(t *testing.T) { - t.Parallel() - - // We need to know the funding txid to use as the map key, - // so we first create channels to get it, then use a - // modifier that moves the nonce to the correct map key. - aliceChannel, bobChannel, err := CreateTestChannels( - t, chanType, - ) - require.NoError(t, err) - - fundingTxid := aliceChannel.channelState.FundingOutpoint.Hash - - aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) - require.NoError(t, err) - - err = bobChannel.ReceiveNewCommitment( - aliceNewCommit.CommitSigs, - ) - require.NoError(t, err) - - bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() - require.NoError(t, err) - - // Move the nonce from LocalNonce to LocalNonces map, - // keyed by the actual funding txid. - legacyNonce := bobRevocation.LocalNonce.UnwrapOrFailV(t) - noncesMap := make( - map[chainhash.Hash]lnwire.Musig2Nonce, - ) - noncesMap[fundingTxid] = legacyNonce - bobRevocation.LocalNonces = lnwire.SomeLocalNonces( - lnwire.LocalNoncesData{NoncesMap: noncesMap}, - ) - bobRevocation.LocalNonce = lnwire.OptMusig2NonceTLV{} - - _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) - require.NoError( - t, err, - "should successfully process revocation "+ - "with only LocalNonces", - ) - }) - - t.Run("receive with only LocalNonce field (legacy peer)", - func(t *testing.T) { - t.Parallel() - - // Modify the message to clear the LocalNonces field. - clearLocalNonces := func(rev *lnwire.RevokeAndAck) { - rev.LocalNonces = lnwire.OptLocalNonces{} - } - - // Processing should still succeed with only LocalNonce - // (backwards compat). - _, _, _, err := generateAndProcessRevocation( - t, chanType, clearLocalNonces, - ) - require.NoError( - t, err, - "successfully process "+ - "revocation with only LocalNonce for "+ - "backwards compatibility", - ) - }) - - t.Run("error when LocalNonces map is empty", func(t *testing.T) { - t.Parallel() - - // Modify the message to have empty LocalNonces map and no - // LocalNonce. - emptyMap := func(rev *lnwire.RevokeAndAck) { - rev.LocalNonce = lnwire.OptMusig2NonceTLV{} - - emptyNonces := make( - map[chainhash.Hash]lnwire.Musig2Nonce, - ) - rev.LocalNonces = lnwire.SomeLocalNonces( - lnwire.LocalNoncesData{ - NoncesMap: emptyNonces, - }, - ) - } - - // We should get an error when the LocalNonces map is empty. - _, _, _, err := generateAndProcessRevocation( - t, chanType, emptyMap, - ) - require.Error( - t, err, "Should error when LocalNonces map is empty", - ) - require.Contains( - t, err.Error(), "no nonce for funding txid", - ) - }) - - t.Run("error when both fields missing", func(t *testing.T) { - t.Parallel() - - clearBoth := func(rev *lnwire.RevokeAndAck) { - rev.LocalNonce = lnwire.OptMusig2NonceTLV{} - rev.LocalNonces = lnwire.OptLocalNonces{} - } - - // If both fields are missing, we should get an error. - _, _, _, err := generateAndProcessRevocation( - t, chanType, clearBoth, - ) - require.Error( - t, err, "Should error when both fields are missing", - ) - require.Contains( - t, err.Error(), "remote verification nonce not sent", - ) - }) -} diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 6a6e12079..6e175ba73 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -17,16 +17,15 @@ import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/btcutil/v2/txsort" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/txsort" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/mempool" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/input" @@ -388,6 +387,7 @@ func TestSimpleAddSettleWorkflow(t *testing.T) { t.Parallel() for _, tweakless := range []bool{true, false} { + tweakless := tweakless t.Run(fmt.Sprintf("tweakless=%v", tweakless), func(t *testing.T) { testAddSettleWorkflow(t, tweakless, 0, false) @@ -1385,6 +1385,7 @@ func TestForceCloseDustOutput(t *testing.T) { htlcAmount := lnwire.NewMSatFromSatoshis(500) + aliceAmount := aliceChannel.channelState.LocalCommitment.LocalBalance bobAmount := bobChannel.channelState.LocalCommitment.LocalBalance // Have Bobs' to-self output be below her dust limit and check @@ -1409,7 +1410,8 @@ func TestForceCloseDustOutput(t *testing.T) { t.Fatalf("Can't update the channel state: %v", err) } - aliceAmount := aliceChannel.channelState.LocalCommitment.LocalBalance + aliceAmount = aliceChannel.channelState.LocalCommitment.LocalBalance + bobAmount = bobChannel.channelState.LocalCommitment.RemoteBalance closeSummary, err := aliceChannel.ForceClose() require.NoError(t, err, "unable to force close channel") @@ -3068,29 +3070,6 @@ func TestAddHTLCNegativeBalance(t *testing.T) { require.ErrorIs(t, err, ErrBelowChanReserve) } -// extractCommitmentNonce extracts the commitment nonce from a -// ChannelReestablish message, prioritizing LocalNonces over the legacy -// LocalNonce field. The fundingTxid is used to look up the correct nonce -// in the LocalNonces map. -func extractCommitmentNonce(t *testing.T, - msg *lnwire.ChannelReestablish, - fundingTxid chainhash.Hash) lnwire.Musig2Nonce { - - // Prefer LocalNonces if present, doing a keyed lookup by funding - // TXID. - if msg.LocalNonces.IsSome() { - noncesData := msg.LocalNonces.UnwrapOrFail(t) - - nonce, ok := noncesData.NoncesMap[fundingTxid] - require.True(t, ok, "LocalNonces missing funding txid") - - return nonce - } - - // Fall back to legacy LocalNonce field. - return msg.LocalNonce.UnwrapOrFailV(t) -} - // assertNoChanSyncNeeded is a helper function that asserts that upon restart, // two channels conclude that they're fully synchronized and don't need to // retransmit any new messages. @@ -3111,19 +3090,13 @@ func assertNoChanSyncNeeded(t *testing.T, aliceChannel *LightningChannel, } // For taproot channels, simulate the link/peer binding the generated - // nonces. Use helper to extract nonces from either LocalNonces or - // LocalNonce. + // nonces. if aliceChannel.channelState.ChanType.IsTaproot() { - fundingTxid := aliceChannel.channelState.FundingOutpoint.Hash aliceChannel.pendingVerificationNonce = &musig2.Nonces{ - PubNonce: extractCommitmentNonce( - t, aliceChanSyncMsg, fundingTxid, - ), + PubNonce: aliceChanSyncMsg.LocalNonce.UnwrapOrFailV(t), } bobChannel.pendingVerificationNonce = &musig2.Nonces{ - PubNonce: extractCommitmentNonce( - t, bobChanSyncMsg, fundingTxid, - ), + PubNonce: bobChanSyncMsg.LocalNonce.UnwrapOrFailV(t), } } @@ -3584,187 +3557,6 @@ func testChanSyncOweCommitment(t *testing.T, } } -// TestChanSyncTaprootLocalNonces tests the nonce synchronization behavior for -// taproot channels. The nonce field populated is auto-detected from the -// channel type: -// - Staging taproot: only LocalNonce is populated (legacy format). -// - Final taproot: only LocalNonces map is populated (map format). -func TestChanSyncTaprootLocalNonces(t *testing.T) { - t.Parallel() - - // Staging taproot channels use the legacy single nonce field. - t.Run( - "staging channel populates LocalNonce", - func(t *testing.T) { - chanType := channeldb.SimpleTaprootFeatureBit - aliceChannel, bobChannel, err := CreateTestChannels( - t, chanType, - ) - require.NoError(t, err) - - assertNoChanSyncNeeded(t, aliceChannel, bobChannel) - - aliceChanSyncMsg, err := - aliceChannel.channelState.ChanSyncMsg() - require.NoError(t, err) - bobChanSyncMsg, err := - bobChannel.channelState.ChanSyncMsg() - require.NoError(t, err) - - // Only LocalNonce should be populated. - require.True(t, aliceChanSyncMsg.LocalNonce.IsSome()) - require.True(t, aliceChanSyncMsg.LocalNonces.IsNone()) - require.True(t, bobChanSyncMsg.LocalNonce.IsSome()) - require.True(t, bobChanSyncMsg.LocalNonces.IsNone()) - }, - ) - - // Final taproot channels use the map-based nonce field. - t.Run("final channel populates LocalNonces", func(t *testing.T) { - chanType := channeldb.SimpleTaprootFeatureBit | - channeldb.TaprootFinalBit - aliceChannel, _, err := CreateTestChannels(t, chanType) - require.NoError(t, err) - - aliceChanSyncMsg, err := aliceChannel.channelState.ChanSyncMsg() - require.NoError(t, err) - - // Only LocalNonces should be populated. - require.True(t, aliceChanSyncMsg.LocalNonce.IsNone()) - require.True(t, aliceChanSyncMsg.LocalNonces.IsSome()) - - noncesData := aliceChanSyncMsg.LocalNonces.UnwrapOrFail(t) - require.Len(t, noncesData.NoncesMap, 1) - }) - - t.Run("sync with final channel LocalNonces", func(t *testing.T) { - chanType := channeldb.SimpleTaprootFeatureBit | - channeldb.TaprootFinalBit - aliceChannel, bobChannel, err := CreateTestChannels( - t, chanType, - ) - require.NoError(t, err) - - fundingTxid := aliceChannel.channelState.FundingOutpoint.Hash - - // Both channels are final, so both use map nonces. - aliceChanSyncMsg, err := aliceChannel.channelState.ChanSyncMsg() - require.NoError(t, err) - bobChanSyncMsg, err := bobChannel.channelState.ChanSyncMsg() - require.NoError(t, err) - - bobChannel.pendingVerificationNonce = &musig2.Nonces{ - PubNonce: extractCommitmentNonce( - t, bobChanSyncMsg, fundingTxid, - ), - } - - // Bob should be able to process Alice's message with only - // LocalNonces. - bobMsgsToSend, _, _, err := bobChannel.ProcessChanSyncMsg( - ctxb, aliceChanSyncMsg, - ) - require.NoError(t, err) - require.Empty(t, bobMsgsToSend) - }) - - t.Run("sync with only legacy LocalNonce field", func(t *testing.T) { - chanType := channeldb.SimpleTaprootFeatureBit - aliceChan, bobChan, err := CreateTestChannels(t, chanType) - require.NoError(t, err) - - fundTxid := aliceChan.channelState.FundingOutpoint.Hash - - aliceChanSyncMsg, err := aliceChan.channelState.ChanSyncMsg() - require.NoError(t, err) - bobChanSyncMsg, err := bobChan.channelState.ChanSyncMsg() - require.NoError(t, err) - - // Simulate an older peer that only sends LocalNonce. - aliceModifiedMsg := *aliceChanSyncMsg - aliceModifiedMsg.LocalNonces = lnwire.OptLocalNonces{} - - bobChan.pendingVerificationNonce = &musig2.Nonces{ - PubNonce: extractCommitmentNonce( - t, bobChanSyncMsg, fundTxid, - ), - } - - bobMsgsToSend, _, _, err := bobChan.ProcessChanSyncMsg( - ctxb, &aliceModifiedMsg, - ) - require.NoError(t, err) - require.Empty(t, bobMsgsToSend) - }) - - t.Run("error when LocalNonces missing txid", func(t *testing.T) { - chanType := channeldb.SimpleTaprootFeatureBit - aliceChan, bobChan, err := CreateTestChannels(t, chanType) - require.NoError(t, err) - - fundTxid := aliceChan.channelState.FundingOutpoint.Hash - - aliceChanSyncMsg, err := aliceChan.channelState.ChanSyncMsg() - require.NoError(t, err) - bobChanSyncMsg, err := bobChan.channelState.ChanSyncMsg() - require.NoError(t, err) - - // Use a wrong txid in the LocalNonces map. - wrongTxid := chainhash.Hash{0xff, 0xff} - nonce := extractCommitmentNonce( - t, aliceChanSyncMsg, fundTxid, - ) - aliceModifiedMsg := *aliceChanSyncMsg - noncesMap := map[chainhash.Hash]lnwire.Musig2Nonce{ - wrongTxid: nonce, - } - aliceModifiedMsg.LocalNonces = lnwire.SomeLocalNonces( - lnwire.LocalNoncesData{NoncesMap: noncesMap}, - ) - - bobChan.pendingVerificationNonce = &musig2.Nonces{ - PubNonce: extractCommitmentNonce( - t, bobChanSyncMsg, fundTxid, - ), - } - - _, _, _, err = bobChan.ProcessChanSyncMsg( - ctxb, &aliceModifiedMsg, - ) - require.Error(t, err) - require.Contains( - t, err.Error(), - "missing nonce for funding txid", - ) - }) - - t.Run("error when both fields missing", func(t *testing.T) { - chanType := channeldb.SimpleTaprootFeatureBit - aliceChan, _, err := CreateTestChannels(t, chanType) - require.NoError(t, err) - - aliceChanSyncMsg, err := aliceChan.channelState.ChanSyncMsg() - require.NoError(t, err) - - aliceEmptyMsg := *aliceChanSyncMsg - aliceEmptyMsg.LocalNonce = lnwire.OptMusig2NonceTLV{} - aliceEmptyMsg.LocalNonces = lnwire.OptLocalNonces{} - - // Create a bob to process against. - _, bobChan, err := CreateTestChannels(t, chanType) - require.NoError(t, err) - - _, _, _, err = bobChan.ProcessChanSyncMsg( - ctxb, &aliceEmptyMsg, - ) - require.Error(t, err) - require.Contains( - t, err.Error(), - "remote verification nonce not sent", - ) - }) -} - // TestChanSyncOweCommitment tests that if Bob restarts (and then Alice) before // he receives Alice's CommitSig message, then Alice concludes that she needs // to re-send the CommitDiff. After the diff has been sent, both nodes should @@ -5375,9 +5167,8 @@ func TestFeeUpdateOldDiskFormat(t *testing.T) { err) } } - // Replacement semantics retain the final pending fee value alongside - // all of the HTLCs. - expFee := 1 + // Check that the expected number of items is found in the logs. + expFee := numHTLCs / 5 assertLogItems(expFee, numHTLCs) // Now, Alice will send a new commitment to Bob, but we'll simulate a @@ -7105,6 +6896,7 @@ func TestChanReserve(t *testing.T) { // Bob: 5.0 htlcAmt := lnwire.NewMSatFromSatoshis(0.5 * btcutil.SatoshiPerBitcoin) htlc, _ := createHTLC(aliceIndex, htlcAmt) + aliceIndex++ addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) // Force a state transition, making sure this HTLC is considered valid @@ -7126,6 +6918,7 @@ func TestChanReserve(t *testing.T) { // Alice: 4.5 // Bob: 5.0 htlc, _ = createHTLC(bobIndex, htlcAmt) + bobIndex++ _, err := bobChannel.AddHTLC(htlc, nil) require.ErrorIs(t, err, ErrBelowChanReserve) @@ -7138,6 +6931,7 @@ func TestChanReserve(t *testing.T) { aliceChannel, bobChannel = setupChannels() aliceIndex = 0 + bobIndex = 0 // Now we'll add HTLC of 3.5 BTC to Alice's commitment, this should put // Alice's balance at 1.5 BTC. @@ -7158,6 +6952,7 @@ func TestChanReserve(t *testing.T) { // balance dip below. htlcAmt = lnwire.NewMSatFromSatoshis(1 * btcutil.SatoshiPerBitcoin) htlc, _ = createHTLC(aliceIndex, htlcAmt) + aliceIndex++ _, err = aliceChannel.AddHTLC(htlc, nil) require.ErrorIs(t, err, ErrBelowChanReserve) @@ -7178,6 +6973,7 @@ func TestChanReserve(t *testing.T) { // Bob: 7.0 htlcAmt = lnwire.NewMSatFromSatoshis(2 * btcutil.SatoshiPerBitcoin) htlc, preimage := createHTLC(aliceIndex, htlcAmt) + aliceIndex++ aliceHtlcIndex, err := aliceChannel.AddHTLC(htlc, nil) require.NoError(t, err, "unable to add htlc") bobHtlcIndex, err := bobChannel.ReceiveHTLC(htlc) @@ -7213,6 +7009,7 @@ func TestChanReserve(t *testing.T) { // the fee this is okay. htlcAmt = lnwire.NewMSatFromSatoshis(1 * btcutil.SatoshiPerBitcoin) htlc, _ = createHTLC(bobIndex, htlcAmt) + bobIndex++ addAndReceiveHTLC(t, bobChannel, aliceChannel, htlc, nil) // Do a last state transition, which should succeed. @@ -7616,7 +7413,7 @@ func TestChannelRestoreUpdateLogs(t *testing.T) { // and remote commit chains are updated in an async fashion. Since the // remote chain was updated with the latest state (since Bob sent the // revocation earlier) we can keep advancing the remote commit chain. - _, err = aliceChannel.SignNextCommitment(ctxb) + aliceNewCommit, err = aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commitment") // After Alice has signed this commitment, her local commitment will @@ -8920,6 +8717,7 @@ func TestFetchParent(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { // Create a lightning channel with newly initialized @@ -9266,11 +9064,12 @@ func TestEvaluateView(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { isInitiator := test.channelInitiator == lntypes.Local lc := LightningChannel{ - channelState: &chanstate.OpenChannel{ + channelState: &channeldb.OpenChannel{ IsInitiator: isInitiator, TotalMSatSent: 0, TotalMSatReceived: 0, @@ -10062,7 +9861,7 @@ func testGetDustSum(t *testing.T, chantype channeldb.ChannelType) { // deriveDummyRetributionParams is a helper function that derives a list of // dummy params to assist retribution creation related tests. -func deriveDummyRetributionParams(chanState *chanstate.OpenChannel) (uint32, +func deriveDummyRetributionParams(chanState *channeldb.OpenChannel) (uint32, *CommitmentKeyRing, chainhash.Hash) { config := chanState.RemoteChanCfg @@ -10303,6 +10102,7 @@ func TestCreateBreachRetribution(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { tx := spendTx if tc.noSpendTx { @@ -10731,6 +10531,7 @@ func TestApplyCommitmentFee(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { //nolint:ll balance, bufferAmt, commitFee, err := tc.channel.applyCommitFee( diff --git a/lnwallet/chanvalidate/validate.go b/lnwallet/chanvalidate/validate.go index b36eda8af..5cf5bbf10 100644 --- a/lnwallet/chanvalidate/validate.go +++ b/lnwallet/chanvalidate/validate.go @@ -4,9 +4,9 @@ import ( "bytes" "fmt" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnwire" ) diff --git a/lnwallet/chanvalidate/validate_test.go b/lnwallet/chanvalidate/validate_test.go index 22597cebd..a1be34a92 100644 --- a/lnwallet/chanvalidate/validate_test.go +++ b/lnwallet/chanvalidate/validate_test.go @@ -6,10 +6,10 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" diff --git a/lnwallet/close_test.go b/lnwallet/close_test.go index bc290a029..7ece3fc45 100644 --- a/lnwallet/close_test.go +++ b/lnwallet/close_test.go @@ -4,7 +4,7 @@ import ( "strconv" "testing" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lntypes" diff --git a/lnwallet/commit_sort.go b/lnwallet/commit_sort.go index 05838297b..6fc931515 100644 --- a/lnwallet/commit_sort.go +++ b/lnwallet/commit_sort.go @@ -4,8 +4,8 @@ import ( "bytes" "sort" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" ) // InPlaceCommitSort performs an in-place sort of a commitment transaction, diff --git a/lnwallet/commit_sort_test.go b/lnwallet/commit_sort_test.go index 512a43799..fd4ca6b44 100644 --- a/lnwallet/commit_sort_test.go +++ b/lnwallet/commit_sort_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnwallet" ) diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index 78b82125b..ab20d9afa 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -6,12 +6,11 @@ import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntypes" @@ -237,14 +236,8 @@ func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool, // // Our "redeem" script here is just the taproot witness program. case chanType.IsTaproot(): - // Determine script options based on channel type. - var scriptOpts []input.TaprootScriptOpt - if chanType.IsTaprootFinal() { - scriptOpts = append(scriptOpts, input.WithProdScripts()) - } - return input.NewLocalCommitScriptTree( - csvDelay, selfKey, revokeKey, auxLeaf, scriptOpts..., + csvDelay, selfKey, revokeKey, auxLeaf, ) // If we are the initiator of a leased channel, then we have an @@ -327,14 +320,8 @@ func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool, // we use a NUMS key to force the remote party to take a script path, // with the sole tap leaf enforcing the 1 CSV delay. case chanType.IsTaproot(): - // Determine script options based on channel type. - var scriptOpts []input.TaprootScriptOpt - if chanType.IsTaprootFinal() { - scriptOpts = append(scriptOpts, input.WithProdScripts()) - } - toRemoteScriptTree, err := input.NewRemoteCommitScriptTree( - remoteKey, auxLeaf, scriptOpts..., + remoteKey, auxLeaf, ) if err != nil { return nil, 0, err @@ -439,15 +426,8 @@ func SecondLevelHtlcScript(chanType channeldb.ChannelType, initiator bool, switch { // For taproot channels, the pkScript is a segwit v1 p2tr output. case chanType.IsTaproot(): - // Determine script options based on channel type. - var scriptOpts []input.TaprootScriptOpt - if chanType.IsTaprootFinal() { - scriptOpts = append(scriptOpts, input.WithProdScripts()) - } - return input.TaprootSecondLevelScriptTree( revocationKey, delayKey, csvDelay, auxLeaf, - scriptOpts..., ) // If we are the initiator of a leased channel, then we have an @@ -636,7 +616,7 @@ type CommitmentBuilder struct { // chanState is the underlying channel's state struct, used to // determine the type of channel we are dealing with, and relevant // parameters. - chanState *chanstate.OpenChannel + chanState *channeldb.OpenChannel // obfuscator is a 48-bit state hint that's used to obfuscate the // current state number on the commitment transactions. @@ -648,7 +628,7 @@ type CommitmentBuilder struct { } // NewCommitmentBuilder creates a new CommitmentBuilder from chanState. -func NewCommitmentBuilder(chanState *chanstate.OpenChannel, +func NewCommitmentBuilder(chanState *channeldb.OpenChannel, leafStore fn.Option[AuxLeafStore]) *CommitmentBuilder { // The anchor channel type MUST be tweakless. @@ -666,9 +646,7 @@ func NewCommitmentBuilder(chanState *chanstate.OpenChannel, // createStateHintObfuscator derives and assigns the state hint obfuscator for // the channel, which is used to encode the commitment height in the sequence // number of commitment transaction inputs. -func createStateHintObfuscator( - state *chanstate.OpenChannel) [StateHintSize]byte { - +func createStateHintObfuscator(state *channeldb.OpenChannel) [StateHintSize]byte { if state.IsInitiator { return DeriveStateHintObfuscator( state.LocalChanCfg.PaymentBasePoint.PubKey, @@ -1187,8 +1165,7 @@ func genSegwitV0HtlcScript(chanType channeldb.ChannelType, // channel. func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty, timeout uint32, rHash [32]byte, keyRing *CommitmentKeyRing, - auxLeaf input.AuxTapLeaf, - opts ...input.TaprootScriptOpt) (*input.HtlcScriptTree, error) { + auxLeaf input.AuxTapLeaf) (*input.HtlcScriptTree, error) { var ( htlcScriptTree *input.HtlcScriptTree @@ -1206,7 +1183,6 @@ func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty, htlcScriptTree, err = input.ReceiverHTLCScriptTaproot( timeout, keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, keyRing.RevocationKey, rHash[:], whoseCommit, auxLeaf, - opts..., ) // We're being paid via an HTLC by the remote party, and the HTLC is @@ -1216,7 +1192,6 @@ func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty, htlcScriptTree, err = input.SenderHTLCScriptTaproot( keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, keyRing.RevocationKey, rHash[:], whoseCommit, auxLeaf, - opts..., ) // We're sending an HTLC which is being added to our commitment @@ -1226,7 +1201,6 @@ func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty, htlcScriptTree, err = input.SenderHTLCScriptTaproot( keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey, keyRing.RevocationKey, rHash[:], whoseCommit, auxLeaf, - opts..., ) // Finally, we're paying the remote party via an HTLC, which is being @@ -1236,7 +1210,6 @@ func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty, htlcScriptTree, err = input.ReceiverHTLCScriptTaproot( timeout, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey, keyRing.RevocationKey, rHash[:], whoseCommit, auxLeaf, - opts..., ) } @@ -1261,15 +1234,8 @@ func genHtlcScript(chanType channeldb.ChannelType, isIncoming bool, ) } - // Determine script options based on channel type. - var scriptOpts []input.TaprootScriptOpt - if chanType.IsTaprootFinal() { - scriptOpts = append(scriptOpts, input.WithProdScripts()) - } - return GenTaprootHtlcScript( isIncoming, whoseCommit, timeout, rHash, keyRing, auxLeaf, - scriptOpts..., ) } @@ -1323,7 +1289,7 @@ func addHTLC(commitTx *wire.MsgTx, whoseCommit lntypes.ChannelParty, // output scripts and compares them against the outputs inside the commitment // to find the match. func findOutputIndexesFromRemote(revocationPreimage *chainhash.Hash, - chanState *chanstate.OpenChannel, + chanState *channeldb.OpenChannel, leafStore fn.Option[AuxLeafStore]) (uint32, uint32, error) { // Init the output indexes as empty. diff --git a/lnwallet/config.go b/lnwallet/config.go index c2ccc99c3..c60974be6 100644 --- a/lnwallet/config.go +++ b/lnwallet/config.go @@ -1,7 +1,7 @@ package lnwallet import ( - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcwallet/wallet" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" diff --git a/lnwallet/confscale.go b/lnwallet/confscale.go deleted file mode 100644 index 5b58f6f54..000000000 --- a/lnwallet/confscale.go +++ /dev/null @@ -1,58 +0,0 @@ -package lnwallet - -import ( - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/lightningnetwork/lnd/lnwire" -) - -const ( - // minRequiredConfs is the minimum number of confirmations we'll - // require for channel operations. - minRequiredConfs = 1 - - // maxRequiredConfs is the maximum number of confirmations we'll - // require for channel operations. - maxRequiredConfs = 6 - - // maxChannelSize is the maximum expected channel size in satoshis. - // This matches MaxBtcFundingAmount (0.16777215 BTC). - maxChannelSize = 16777215 -) - -// ScaleNumConfs returns a linearly scaled number of confirmations based on the -// provided channel amount and push amount (for funding transactions). The push -// amount represents additional risk when receiving funds. -func ScaleNumConfs(chanAmt btcutil.Amount, pushAmt lnwire.MilliSatoshi) uint16 { - // For wumbo channels, always require maximum confirmations. - if chanAmt > maxChannelSize { - return maxRequiredConfs - } - - // Calculate total stake: channel amount + push amount. The push amount - // represents value at risk for the receiver. - maxChannelSizeMsat := lnwire.NewMSatFromSatoshis(maxChannelSize) - stake := lnwire.NewMSatFromSatoshis(chanAmt) + pushAmt - - // Scale confirmations linearly based on stake. - conf := uint64(maxRequiredConfs) * uint64(stake) / - uint64(maxChannelSizeMsat) - - // Bound the result between minRequiredConfs and maxRequiredConfs. - if conf < minRequiredConfs { - conf = minRequiredConfs - } - if conf > maxRequiredConfs { - conf = maxRequiredConfs - } - - return uint16(conf) -} - -// FundingConfsForAmounts returns the number of confirmations to wait for a -// funding transaction, taking into account both the channel amount and any -// pushed amount (which represents additional risk). -func FundingConfsForAmounts(chanAmt btcutil.Amount, - pushAmt lnwire.MilliSatoshi) uint16 { - - return ScaleNumConfs(chanAmt, pushAmt) -} diff --git a/lnwallet/confscale_integration.go b/lnwallet/confscale_integration.go deleted file mode 100644 index 99d7938eb..000000000 --- a/lnwallet/confscale_integration.go +++ /dev/null @@ -1,13 +0,0 @@ -//go:build integration -// +build integration - -package lnwallet - -import "github.com/btcsuite/btcd/btcutil/v2" - -// CloseConfsForCapacity returns the number of confirmations to wait -// before signaling a cooperative close. Under integration tests, we -// always return 1 to keep tests fast and deterministic. -func CloseConfsForCapacity(capacity btcutil.Amount) uint32 { //nolint:revive - return 1 -} diff --git a/lnwallet/confscale_prod.go b/lnwallet/confscale_prod.go deleted file mode 100644 index 378af19c9..000000000 --- a/lnwallet/confscale_prod.go +++ /dev/null @@ -1,25 +0,0 @@ -//go:build !integration -// +build !integration - -package lnwallet - -import "github.com/btcsuite/btcd/btcutil/v2" - -// CloseConfsForCapacity returns the number of confirmations to wait before -// signaling a channel close, scaled by channel capacity. This is used for both -// cooperative and force closes. We enforce a minimum of 3 confirmations to -// provide better reorg protection, even for small channels. -func CloseConfsForCapacity(capacity btcutil.Amount) uint32 { - // For cooperative closes, we don't have a push amount to consider, - // so we pass 0 for the pushAmt parameter. - scaledConfs := uint32(ScaleNumConfs(capacity, 0)) - - // Enforce a minimum of 3 confirmations for reorg safety. - // This protects against shallow reorgs which are more common. - const minCloseConfs = 3 - if scaledConfs < minCloseConfs { - return minCloseConfs - } - - return scaledConfs -} diff --git a/lnwallet/confscale_test.go b/lnwallet/confscale_test.go deleted file mode 100644 index 377b29e0b..000000000 --- a/lnwallet/confscale_test.go +++ /dev/null @@ -1,339 +0,0 @@ -package lnwallet - -import ( - "testing" - - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/stretchr/testify/require" - "pgregory.net/rapid" -) - -// TestScaleNumConfsProperties tests various properties that ScaleNumConfs -// should satisfy using property-based testing. -func TestScaleNumConfsProperties(t *testing.T) { - t.Parallel() - - // The result should always be bounded between the minimum and maximum - // number of confirmations regardless of input values. - t.Run("bounded_result", func(t *testing.T) { - rapid.Check(t, func(t *rapid.T) { - // Generate random channel amount and push amount. - chanAmt := rapid.Uint64Range( - 0, maxChannelSize*10, - ).Draw(t, "chanAmt") - pushAmtSats := rapid.Uint64Range( - 0, chanAmt, - ).Draw(t, "pushAmtSats") - pushAmt := lnwire.NewMSatFromSatoshis( - btcutil.Amount(pushAmtSats), - ) - - result := ScaleNumConfs( - btcutil.Amount(chanAmt), pushAmt, - ) - - // Check bounds - require.GreaterOrEqual( - t, result, uint16(minRequiredConfs), - "result should be >= minRequiredConfs", - ) - require.LessOrEqual( - t, result, uint16(maxRequiredConfs), - "result should be <= maxRequiredConfs", - ) - }) - }) - - // Larger channel amounts and push amounts should require equal or more - // confirmations, ensuring the function is monotonically increasing. - t.Run("monotonicity", func(t *testing.T) { - rapid.Check(t, func(t *rapid.T) { - // Generate two channel amounts where amt1 <= amt2. - amt1 := rapid.Uint64Range( - 0, maxChannelSize, - ).Draw(t, "amt1") - amt2 := rapid.Uint64Range( - amt1, maxChannelSize, - ).Draw(t, "amt2") - - // Generate push amounts proportional to channel size. - pushAmt1Sats := rapid.Uint64Range( - 0, amt1, - ).Draw(t, "pushAmt1") - pushAmt2Sats := rapid.Uint64Range( - pushAmt1Sats, amt2, - ).Draw(t, "pushAmt2") - - pushAmt1 := lnwire.NewMSatFromSatoshis( - btcutil.Amount(pushAmt1Sats), - ) - pushAmt2 := lnwire.NewMSatFromSatoshis( - btcutil.Amount(pushAmt2Sats), - ) - - confs1 := ScaleNumConfs(btcutil.Amount(amt1), pushAmt1) - confs2 := ScaleNumConfs(btcutil.Amount(amt2), pushAmt2) - - // Larger or equal stake should require equal or more - // confirmations. - require.GreaterOrEqual( - t, confs2, confs1, - "larger amount should require equal or "+ - "more confirmations", - ) - }) - }) - - // Wumbo channels (those exceeding the max standard channel size) should - // always require the maximum number of confirmations for safety. - t.Run("wumbo_max_confs", func(t *testing.T) { - rapid.Check(t, func(t *rapid.T) { - // Generate wumbo channel amount (above maxChannelSize). - wumboAmt := rapid.Uint64Range( - maxChannelSize+1, maxChannelSize*100, - ).Draw(t, "wumboAmt") - pushAmtSats := rapid.Uint64Range( - 0, wumboAmt, - ).Draw(t, "pushAmtSats") - pushAmt := lnwire.NewMSatFromSatoshis( - btcutil.Amount(pushAmtSats), - ) - - result := ScaleNumConfs( - btcutil.Amount(wumboAmt), pushAmt, - ) - - require.Equal( - t, uint16(maxRequiredConfs), result, - "wumbo channels should always get "+ - "max confirmations", - ) - }) - }) - - // Zero channel amounts should always result in the minimum number of - // confirmations since there's no value at risk. - t.Run("zero_gets_min", func(t *testing.T) { - result := ScaleNumConfs(0, 0) - require.Equal( - t, uint16(minRequiredConfs), result, - "zero amount should get minimum confirmations", - ) - }) - - // The function should be deterministic, always returning the same - // output for the same input values. - t.Run("determinism", func(t *testing.T) { - rapid.Check(t, func(t *rapid.T) { - chanAmt := rapid.Uint64Range( - 0, maxChannelSize*2, - ).Draw(t, "chanAmt") - pushAmtSats := rapid.Uint64Range( - 0, chanAmt, - ).Draw(t, "pushAmtSats") - pushAmt := lnwire.NewMSatFromSatoshis( - btcutil.Amount(pushAmtSats), - ) - - // Call multiple times with same inputs. - result1 := ScaleNumConfs( - btcutil.Amount(chanAmt), pushAmt, - ) - result2 := ScaleNumConfs( - btcutil.Amount(chanAmt), pushAmt, - ) - result3 := ScaleNumConfs( - btcutil.Amount(chanAmt), pushAmt, - ) - - require.Equal( - t, result1, result2, - "function should be deterministic", - ) - require.Equal( - t, result2, result3, - "function should be deterministic", - ) - }) - }) - - // Adding a push amount to a channel should require equal or more - // confirmations compared to the same channel without a push amount. - t.Run("push_amount_effect", func(t *testing.T) { - rapid.Check(t, func(t *rapid.T) { - // Fix channel amount, vary push amount - chanAmt := rapid.Uint64Range( - 1, maxChannelSize, - ).Draw(t, "chanAmt") - pushAmt1Sats := rapid.Uint64Range( - 0, chanAmt/2, - ).Draw(t, "pushAmt1") - pushAmt2Sats := rapid.Uint64Range( - pushAmt1Sats, chanAmt, - ).Draw(t, "pushAmt2") - - pushAmt1 := lnwire.NewMSatFromSatoshis( - btcutil.Amount(pushAmt1Sats), - ) - pushAmt2 := lnwire.NewMSatFromSatoshis( - btcutil.Amount(pushAmt2Sats), - ) - - confs1 := ScaleNumConfs( - btcutil.Amount(chanAmt), pushAmt1, - ) - confs2 := ScaleNumConfs( - btcutil.Amount(chanAmt), pushAmt2, - ) - - // More push amount should require equal or more - // confirmations. - require.GreaterOrEqual( - t, confs2, confs1, - "larger push amount should "+ - "require equal or more confirmations", - ) - }) - }) -} - -// TestScaleNumConfsKnownValues tests ScaleNumConfs with specific known values -// to ensure the scaling formula works as expected. -func TestScaleNumConfsKnownValues(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - chanAmt btcutil.Amount - pushAmt lnwire.MilliSatoshi - expected uint16 - }{ - { - name: "zero amounts", - chanAmt: 0, - pushAmt: 0, - expected: minRequiredConfs, - }, - { - name: "tiny channel", - chanAmt: 1000, - pushAmt: 0, - expected: minRequiredConfs, - }, - { - name: "small channel no push", - chanAmt: 100_000, - pushAmt: 0, - expected: minRequiredConfs, - }, - { - name: "half max channel no push", - chanAmt: maxChannelSize / 2, - pushAmt: 0, - expected: 2, - }, - { - name: "max channel no push", - chanAmt: maxChannelSize, - pushAmt: 0, - expected: maxRequiredConfs, - }, - { - name: "wumbo channel", - chanAmt: maxChannelSize * 2, - pushAmt: 0, - expected: maxRequiredConfs, - }, - { - name: "small channel with push", - chanAmt: 100_000, - pushAmt: lnwire.NewMSatFromSatoshis(50_000), - expected: minRequiredConfs, - }, - { - name: "medium channel with significant push", - chanAmt: maxChannelSize / 4, - pushAmt: lnwire.NewMSatFromSatoshis( - maxChannelSize / 4, - ), - expected: 2, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - result := ScaleNumConfs(tc.chanAmt, tc.pushAmt) - - require.Equal( - t, tc.expected, result, - "chanAmt=%d, pushAmt=%d", tc.chanAmt, - tc.pushAmt, - ) - }) - } -} - -// TestFundingConfsForAmounts verifies that FundingConfsForAmounts is a simple -// wrapper around ScaleNumConfs. -func TestFundingConfsForAmounts(t *testing.T) { - t.Parallel() - - rapid.Check(t, func(t *rapid.T) { - chanAmt := rapid.Uint64Range( - 0, maxChannelSize*2, - ).Draw(t, "chanAmt") - pushAmtSats := rapid.Uint64Range( - 0, chanAmt, - ).Draw(t, "pushAmtSats") - pushAmt := lnwire.NewMSatFromSatoshis( - btcutil.Amount(pushAmtSats), - ) - - // Both functions should return the same result. - scaleResult := ScaleNumConfs(btcutil.Amount(chanAmt), pushAmt) - fundingResult := FundingConfsForAmounts( - btcutil.Amount(chanAmt), pushAmt, - ) - - require.Equal( - t, scaleResult, fundingResult, - "FundingConfsForAmounts should return "+ - "same result as ScaleNumConfs", - ) - }) -} - -// TestCloseConfsForCapacity verifies that CloseConfsForCapacity correctly -// wraps ScaleNumConfs with zero push amount and enforces a minimum of 3 -// confirmations for reorg safety. -func TestCloseConfsForCapacity(t *testing.T) { - t.Parallel() - - rapid.Check(t, func(t *rapid.T) { - capacity := rapid.Uint64Range( - 0, maxChannelSize*2, - ).Draw(t, "capacity") - - // CloseConfsForCapacity should be equivalent to ScaleNumConfs - // with 0 push, but with a minimum of 3 confirmations enforced - // for reorg safety. - closeConfs := CloseConfsForCapacity(btcutil.Amount(capacity)) - scaleConfs := ScaleNumConfs(btcutil.Amount(capacity), 0) - - // The result should be at least the scaled value, but with a - // minimum of 3 confirmations. - const minCloseConfs = 3 - expectedConfs := uint32(scaleConfs) - if expectedConfs < minCloseConfs { - expectedConfs = minCloseConfs - } - - require.Equal( - t, expectedConfs, closeConfs, - "CloseConfsForCapacity should match "+ - "ScaleNumConfs with 0 push amount, "+ - "but with minimum of 3 confs", - ) - }) -} diff --git a/lnwallet/errors.go b/lnwallet/errors.go index fc78d0bda..b2d1a5c42 100644 --- a/lnwallet/errors.go +++ b/lnwallet/errors.go @@ -4,8 +4,8 @@ import ( "errors" "fmt" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/lnwire" ) @@ -85,18 +85,6 @@ func ErrNonZeroPushAmount() ReservationError { return ReservationError{errors.New("non-zero push amounts are disabled")} } -// ErrPushAmountTooLarge is returned when the push amount exceeds the channel's -// funding amount, which violates BOLT-02 (push_msat MUST be <= -// 1000 * funding_satoshis). -func ErrPushAmountTooLarge(pushAmt lnwire.MilliSatoshi, - fundingAmt btcutil.Amount) ReservationError { - - return ReservationError{ - fmt.Errorf("push amount %v exceeds funding amount %v", - pushAmt, lnwire.NewMSatFromSatoshis(fundingAmt)), - } -} - // ErrMinHtlcTooLarge returns an error indicating that the MinHTLC value the // remote required is too large to be accepted. func ErrMinHtlcTooLarge(minHtlc, diff --git a/lnwallet/interface.go b/lnwallet/interface.go index d2adfd2ee..f5a717d3f 100644 --- a/lnwallet/interface.go +++ b/lnwallet/interface.go @@ -6,17 +6,15 @@ import ( "sync" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/btcutil/v2/hdkeychain" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/psbt/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" base "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/wallet/txauthor" @@ -137,7 +135,7 @@ type Utxo struct { // OutputDetail contains additional information on a destination address. type OutputDetail struct { OutputType txscript.ScriptClass - Addresses []address.Address + Addresses []btcutil.Address PkScript []byte OutputIndex int Value btcutil.Amount @@ -269,7 +267,7 @@ type WalletController interface { // p2wsh, etc. The account parameter must be non-empty as it determines // which account the address should be generated from. NewAddress(addrType AddressType, change bool, - account string) (address.Address, error) + account string) (btcutil.Address, error) // LastUnusedAddress returns the last *unused* address known by the // wallet. An address is unused if it hasn't received any payments. @@ -280,14 +278,14 @@ type WalletController interface { // The account parameter must be non-empty as it determines which // account the address should be generated from. LastUnusedAddress(addrType AddressType, - account string) (address.Address, error) + account string) (btcutil.Address, error) // IsOurAddress checks if the passed address belongs to this wallet - IsOurAddress(a address.Address) bool + IsOurAddress(a btcutil.Address) bool // AddressInfo returns the information about an address, if it's known // to this wallet. - AddressInfo(a address.Address) (waddrmgr.ManagedAddress, error) + AddressInfo(a btcutil.Address) (waddrmgr.ManagedAddress, error) // ListAccounts retrieves all accounts belonging to the wallet by // default. A name and key scope filter can be provided to filter @@ -327,8 +325,8 @@ type WalletController interface { // (nested pubkeys externally, witness pubkeys internally). ImportAccount(name string, accountPubKey *hdkeychain.ExtendedKey, masterKeyFingerprint uint32, addrType *waddrmgr.AddressType, - dryRun bool) (*waddrmgr.AccountProperties, []address.Address, - []address.Address, error) + dryRun bool) (*waddrmgr.AccountProperties, []btcutil.Address, + []btcutil.Address, error) // ImportPublicKey imports a single derived public key into the wallet. // The address type can usually be inferred from the key's version, but @@ -442,19 +440,6 @@ type WalletController interface { // published transaction. PublishTransaction(tx *wire.MsgTx, label string) error - // SubmitPackage submits a package of related transactions - // (topologically sorted, unconfirmed parents first and the child - // last) to the chain backend for atomic validation and acceptance. - // This lets a zero-fee v3/TRUC parent be accepted via its fee-paying - // CPFP child, which a standalone broadcast rejects. maxFeeRate is an - // optional per-transaction fee-rate ceiling in sat/vByte (nil leaves - // the node default unchanged). Backends without a mempool (e.g. - // neutrino) broadcast each transaction individually and rely on P2P - // 1p1c package relay instead. - SubmitPackage(txns []*wire.MsgTx, - maxFeeRate *chainfee.SatPerVByte) (*btcjson.SubmitPackageResult, - error) - // LabelTransaction adds a label to a transaction. If the tx already // has a label, this call will fail unless the overwrite parameter // is set. Labels must not be empty, and they are limited to 500 chars. @@ -646,7 +631,7 @@ func InternalKeyForAddr(wallet WalletController, netParams *chaincfg.Params, // If it's not a taproot address, we don't require to know the internal // key in the first place. So we don't return an error here, but also no // internal key. - _, isTaproot := addr.(*address.AddressTaproot) + _, isTaproot := addr.(*btcutil.AddressTaproot) if !isTaproot { return none, nil } diff --git a/lnwallet/mock.go b/lnwallet/mock.go index 5583073e6..39e520d27 100644 --- a/lnwallet/mock.go +++ b/lnwallet/mock.go @@ -5,15 +5,13 @@ import ( "sync/atomic" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/btcutil/v2/hdkeychain" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/psbt/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" base "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/wallet/txauthor" @@ -85,9 +83,9 @@ func (w *mockWalletController) ConfirmedBalance(int32, string) (btcutil.Amount, // NewAddress is called to get new addresses for delivery, change etc. func (w *mockWalletController) NewAddress(AddressType, bool, - string) (address.Address, error) { + string) (btcutil.Address, error) { - addr, _ := address.NewAddressPubKey( + addr, _ := btcutil.NewAddressPubKey( w.RootKey.PubKey().SerializeCompressed(), &chaincfg.MainNetParams, ) @@ -97,19 +95,19 @@ func (w *mockWalletController) NewAddress(AddressType, bool, // LastUnusedAddress currently returns dummy values. func (w *mockWalletController) LastUnusedAddress(AddressType, - string) (address.Address, error) { + string) (btcutil.Address, error) { return nil, nil } // IsOurAddress currently returns a dummy value. -func (w *mockWalletController) IsOurAddress(address.Address) bool { +func (w *mockWalletController) IsOurAddress(btcutil.Address) bool { return false } // AddressInfo currently returns a dummy value. func (w *mockWalletController) AddressInfo( - address.Address) (waddrmgr.ManagedAddress, error) { + btcutil.Address) (waddrmgr.ManagedAddress, error) { return nil, nil } @@ -136,7 +134,7 @@ func (w *mockWalletController) ListAddresses(string, // ImportAccount currently returns a dummy value. func (w *mockWalletController) ImportAccount(string, *hdkeychain.ExtendedKey, uint32, *waddrmgr.AddressType, bool) (*waddrmgr.AccountProperties, - []address.Address, []address.Address, error) { + []btcutil.Address, []btcutil.Address, error) { return nil, nil, nil, nil } @@ -257,19 +255,6 @@ func (w *mockWalletController) PublishTransaction(tx *wire.MsgTx, return nil } -// SubmitPackage publishes each transaction in the package individually, -// mirroring PublishTransaction. The mock has no real chain backend so it -// returns an empty result. -func (w *mockWalletController) SubmitPackage(txns []*wire.MsgTx, - _ *chainfee.SatPerVByte) (*btcjson.SubmitPackageResult, error) { - - for _, tx := range txns { - w.PublishedTransactions <- tx - } - - return &btcjson.SubmitPackageResult{}, nil -} - // GetTransactionDetails currently does nothing. func (w *mockWalletController) GetTransactionDetails(*chainhash.Hash) ( *TransactionDetail, error) { diff --git a/lnwallet/musig_session.go b/lnwallet/musig_session.go index f2f4e8435..748e5fa95 100644 --- a/lnwallet/musig_session.go +++ b/lnwallet/musig_session.go @@ -8,9 +8,9 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -237,17 +237,6 @@ type MusigSession struct { // instead of the normal BIP 86 tweak when creating the MuSig2 // aggregate key and session. tapscriptTweak fn.Option[input.MuSig2Tweaks] - - // customNonceRand is an optional custom random source used to generate - // deterministic JIT signing nonces. This should only be set in tests - // that need reproducible MuSig2 signatures. - customNonceRand fn.Option[io.Reader] - - // lastSecNonce holds the secret nonce from the most recent JIT nonce - // generation. This is only populated when customNonceRand is set - // (test vector generation), allowing test code to extract the raw - // 97-byte secret nonces for inclusion in interop test vectors. - lastSecNonce fn.Option[[musig2.SecNonceSize]byte] } // NewPartialMusigSession creates a new musig2 session given only the @@ -256,8 +245,7 @@ type MusigSession struct { func NewPartialMusigSession(verificationNonce musig2.Nonces, localKey, remoteKey keychain.KeyDescriptor, signer input.MuSig2Signer, inputTxOut *wire.TxOut, commitType MusigCommitType, - tapscriptTweak fn.Option[input.MuSig2Tweaks], - customNonceRand fn.Option[io.Reader]) *MusigSession { + tapscriptTweak fn.Option[input.MuSig2Tweaks]) *MusigSession { signerKeys := []*btcec.PublicKey{localKey.PubKey, remoteKey.PubKey} @@ -266,15 +254,14 @@ func NewPartialMusigSession(verificationNonce musig2.Nonces, } return &MusigSession{ - nonces: nonces, - remoteKey: remoteKey, - localKey: localKey, - inputTxOut: inputTxOut, - signerKeys: signerKeys, - signer: signer, - commitType: commitType, - tapscriptTweak: tapscriptTweak, - customNonceRand: customNonceRand, + nonces: nonces, + remoteKey: remoteKey, + localKey: localKey, + inputTxOut: inputTxOut, + signerKeys: signerKeys, + signer: signer, + commitType: commitType, + tapscriptTweak: tapscriptTweak, } } @@ -328,7 +315,7 @@ func (m *MusigSession) FinalizeSession(signingNonce musig2.Nonces) error { m.nonces.VerificationNonce.PubNonce, }) if err != nil { - return err + return nil } m.combinedNonce = aggNonce @@ -364,28 +351,13 @@ func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, error) { // a fresh nonce that'll be sent along side our signature. With // the nonce in hand, we can finalize the session. txHash := tx.TxHash() - nonceOpts := []musig2.NonceGenOption{ + signingNonce, err := musig2.GenNonces( musig2.WithPublicKey(m.localKey.PubKey), musig2.WithNonceAuxInput(txHash[:]), - } - m.customNonceRand.WhenSome(func(r io.Reader) { - nonceOpts = append( - nonceOpts, - musig2.WithCustomRand(r), - ) - }) - signingNonce, err := musig2.GenNonces(nonceOpts...) + ) if err != nil { return nil, err } - - // When using deterministic nonce generation (test vector - // mode), stash the secret nonce so it can be extracted - // for inclusion in interop test vectors. - m.customNonceRand.WhenSome(func(_ io.Reader) { - m.lastSecNonce = fn.Some(signingNonce.SecNonce) - }) - if err := m.FinalizeSession(*signingNonce); err != nil { return nil, err } @@ -437,7 +409,6 @@ func (m *MusigSession) Refresh(verificationNonce *musig2.Nonces, return NewPartialMusigSession( *verificationNonce, m.localKey, m.remoteKey, m.signer, m.inputTxOut, m.commitType, m.tapscriptTweak, - m.customNonceRand, ), nil } @@ -446,16 +417,6 @@ func (m *MusigSession) VerificationNonce() *musig2.Nonces { return &m.nonces.VerificationNonce } -// lastSigningSecNonce returns the secret nonce from the most recent JIT nonce -// generation, if available. This is only populated when customNonceRand is set -// (test vector generation mode). The value is cleared after being read to -// prevent accidental nonce reuse. -func (m *MusigSession) lastSigningSecNonce() fn.Option[[musig2.SecNonceSize]byte] { //nolint:ll - nonce := m.lastSecNonce - m.lastSecNonce = fn.None[[musig2.SecNonceSize]byte]() - return nonce -} - // musigSessionOpts is a set of options that can be used to modify calls to the // musig session. type musigSessionOpts struct { @@ -626,11 +587,6 @@ type MusigSessionCfg struct { // TapscriptTweak is an optional tweak that can be used to modify the // MuSig2 public key used in the session. TapscriptTweak fn.Option[chainhash.Hash] - - // CustomNonceRand is an optional custom random source for generating - // deterministic JIT signing nonces. This should only be set in tests - // that need reproducible MuSig2 signatures. - CustomNonceRand fn.Option[io.Reader] } // MusigPairSession houses the two musig2 sessions needed to do funding and @@ -659,12 +615,10 @@ func NewMusigPairSession(cfg *MusigSessionCfg) *MusigPairSession { localSession := NewPartialMusigSession( cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer, cfg.InputTxOut, LocalMusigCommit, tapscriptTweak, - cfg.CustomNonceRand, ) remoteSession := NewPartialMusigSession( cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer, cfg.InputTxOut, RemoteMusigCommit, tapscriptTweak, - cfg.CustomNonceRand, ) return &MusigPairSession{ diff --git a/lnwallet/musig_session_test.go b/lnwallet/musig_session_test.go index 9ecc3127a..52de69b4d 100644 --- a/lnwallet/musig_session_test.go +++ b/lnwallet/musig_session_test.go @@ -5,7 +5,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/stretchr/testify/require" diff --git a/lnwallet/parameters.go b/lnwallet/parameters.go index dba9893ba..41509ef9a 100644 --- a/lnwallet/parameters.go +++ b/lnwallet/parameters.go @@ -1,9 +1,9 @@ package lnwallet import ( - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/mempool" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwire" ) @@ -41,10 +41,8 @@ func DefaultRoutingFeeLimitForAmount(a lnwire.MilliSatoshi) lnwire.MilliSatoshi // DustLimitForSize retrieves the dust limit for a given pkscript size. Given // the size, it automatically determines whether the script is a witness script -// or not. It calls btcd's GetDustThreshold method under the hood. Any size that -// doesn't map to one of the well-known templates is treated as a generic -// witness output, so the helper stays well-defined for arbitrary (including -// future witness-version) script lengths. +// or not. It calls btcd's GetDustThreshold method under the hood. It must be +// called with a proper size parameter or else a panic occurs. func DustLimitForSize(scriptSize int) btcutil.Amount { var ( dustlimit btcutil.Amount @@ -68,11 +66,11 @@ func DustLimitForSize(scriptSize int) btcutil.Amount { case input.P2PKHSize: pkscript, _ = input.GenerateP2PKH([]byte{}) - // Any other length (the explicit UnknownWitnessSize, or an otherwise - // unrecognized size) is priced as a generic witness output rather than - // treated as a hard error. - default: + case input.UnknownWitnessSize: pkscript, _ = input.GenerateUnknownWitness() + + default: + panic("invalid script size") } // Call GetDustThreshold with a TxOut containing the generated diff --git a/lnwallet/parameters_test.go b/lnwallet/parameters_test.go index a67434b49..3cee8f3e6 100644 --- a/lnwallet/parameters_test.go +++ b/lnwallet/parameters_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" @@ -38,6 +38,7 @@ func TestDefaultRoutingFeeLimitForAmount(t *testing.T) { } for _, test := range tests { + test := test t.Run(fmt.Sprintf("%d sats", test.amount), func(t *testing.T) { feeLimit := DefaultRoutingFeeLimitForAmount(test.amount) @@ -81,24 +82,10 @@ func TestDustLimitForSize(t *testing.T) { size: input.UnknownWitnessSize, expectedLimit: btcutil.Amount(354), }, - { - // An arbitrary short length that matches no known - // template is priced as a generic witness output - // rather than treated as an error. - name: "arbitrary small size", - size: 7, - expectedLimit: btcutil.Amount(354), - }, - { - // The largest witness program length is also handled - // as a generic witness output. - name: "arbitrary large witness size", - size: 42, - expectedLimit: btcutil.Amount(354), - }, } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { dustlimit := DustLimitForSize(test.size) diff --git a/lnwallet/rebroadcaster.go b/lnwallet/rebroadcaster.go index fdd296168..bb139f0a6 100644 --- a/lnwallet/rebroadcaster.go +++ b/lnwallet/rebroadcaster.go @@ -1,8 +1,8 @@ package lnwallet import ( - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" ) // Rebroadcaster is an abstract rebroadcaster instance that'll continually diff --git a/lnwallet/rebroadcaster_test.go b/lnwallet/rebroadcaster_test.go index 927c96177..2e6f1bbfc 100644 --- a/lnwallet/rebroadcaster_test.go +++ b/lnwallet/rebroadcaster_test.go @@ -5,8 +5,8 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/lnutils" "github.com/stretchr/testify/require" diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index a2c116566..a8a0cacd4 100644 --- a/lnwallet/reservation.go +++ b/lnwallet/reservation.go @@ -8,11 +8,10 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -50,17 +49,9 @@ const ( // CommitmentTypeSimpleTaproot is the base commitment type for the // channels that use a musig2 funding output and the tapscript tree - // where relevant for the commitment transaction pk scripts. This is - // the staging version using feature bits 180/181. + // where relevant for the commitment transaction pk scripts. CommitmentTypeSimpleTaproot - // CommitmentTypeSimpleTaprootFinal is the production commitment type - // for taproot channels that use a musig2 funding output and the - // tapscript tree where relevant for the commitment transaction pk - // scripts. This uses the final feature bits 80/81 and production - // scripts. - CommitmentTypeSimpleTaprootFinal - // CommitmentTypeSimpleTaprootOverlay builds on the existing // CommitmentTypeSimpleTaproot type but layers on a special overlay // protocol. @@ -75,7 +66,6 @@ func (c CommitmentType) HasStaticRemoteKey() bool { CommitmentTypeAnchorsZeroFeeHtlcTx, CommitmentTypeScriptEnforcedLease, CommitmentTypeSimpleTaproot, - CommitmentTypeSimpleTaprootFinal, CommitmentTypeSimpleTaprootOverlay: return true @@ -91,7 +81,6 @@ func (c CommitmentType) HasAnchors() bool { case CommitmentTypeAnchorsZeroFeeHtlcTx, CommitmentTypeScriptEnforcedLease, CommitmentTypeSimpleTaproot, - CommitmentTypeSimpleTaprootFinal, CommitmentTypeSimpleTaprootOverlay: return true @@ -104,7 +93,6 @@ func (c CommitmentType) HasAnchors() bool { // IsTaproot returns true if the channel type is a taproot channel. func (c CommitmentType) IsTaproot() bool { return c == CommitmentTypeSimpleTaproot || - c == CommitmentTypeSimpleTaprootFinal || c == CommitmentTypeSimpleTaprootOverlay } @@ -121,8 +109,6 @@ func (c CommitmentType) String() string { return "script-enforced-lease" case CommitmentTypeSimpleTaproot: return "simple-taproot" - case CommitmentTypeSimpleTaprootFinal: - return "simple-taproot-final" case CommitmentTypeSimpleTaprootOverlay: return "simple-taproot-overlay" default: @@ -249,7 +235,7 @@ type ChannelReservation struct { ourContribution *ChannelContribution theirContribution *ChannelContribution - partialState *chanstate.OpenChannel + partialState *channeldb.OpenChannel nodeAddr net.Addr // The ID of this reservation, used to uniquely track the reservation @@ -455,11 +441,6 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, if req.CommitType.IsTaproot() { chanType |= channeldb.SimpleTaprootFeatureBit - - // Set the final bit if this is the production taproot version. - if req.CommitType == CommitmentTypeSimpleTaprootFinal { - chanType |= channeldb.TaprootFinalBit - } } if req.ZeroConf { @@ -495,7 +476,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, FundingAmount: theirBalance.ToSatoshis(), ChannelConfig: &channeldb.ChannelConfig{}, }, - partialState: &chanstate.OpenChannel{ + partialState: &channeldb.OpenChannel{ ChanType: chanType, ChainHash: *chainHash, IsPending: true, @@ -778,11 +759,11 @@ func (r *ChannelReservation) OurSignatures() ([]*input.Script, // confirmations. Once the method unblocks, a LightningChannel instance is // returned, marking the channel available for updates. func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*input.Script, - commitmentSig input.Signature) (*chanstate.OpenChannel, error) { + commitmentSig input.Signature) (*channeldb.OpenChannel, error) { // TODO(roasbeef): add flag for watch or not? errChan := make(chan error, 1) - completeChan := make(chan *chanstate.OpenChannel, 1) + completeChan := make(chan *channeldb.OpenChannel, 1) r.wallet.msgChan <- &addCounterPartySigsMsg{ pendingFundingID: r.reservationID, @@ -806,11 +787,11 @@ func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*input.Sc // will be populated. func (r *ChannelReservation) CompleteReservationSingle( fundingPoint *wire.OutPoint, commitSig input.Signature, - auxFundingDesc fn.Option[AuxFundingDesc]) (*chanstate.OpenChannel, + auxFundingDesc fn.Option[AuxFundingDesc]) (*channeldb.OpenChannel, error) { errChan := make(chan error, 1) - completeChan := make(chan *chanstate.OpenChannel, 1) + completeChan := make(chan *channeldb.OpenChannel, 1) r.wallet.msgChan <- &addSingleFunderSigsMsg{ pendingFundingID: r.reservationID, @@ -904,7 +885,7 @@ func (r *ChannelReservation) Cancel() error { } // ChanState the current open channel state. -func (r *ChannelReservation) ChanState() *chanstate.OpenChannel { +func (r *ChannelReservation) ChanState() *channeldb.OpenChannel { r.RLock() defer r.RUnlock() diff --git a/lnwallet/revocation_producer_itest.go b/lnwallet/revocation_producer_itest.go index 0b64fd44e..a1b6a0551 100644 --- a/lnwallet/revocation_producer_itest.go +++ b/lnwallet/revocation_producer_itest.go @@ -3,7 +3,7 @@ package lnwallet import ( - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/shachain" diff --git a/lnwallet/rpcwallet/rpcwallet.go b/lnwallet/rpcwallet/rpcwallet.go index 8aea3cf0f..426712b59 100644 --- a/lnwallet/rpcwallet/rpcwallet.go +++ b/lnwallet/rpcwallet/rpcwallet.go @@ -10,16 +10,16 @@ import ( "os" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/btcutil/v2/hdkeychain" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/psbt/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" basewallet "github.com/btcsuite/btcwallet/wallet" "github.com/lightningnetwork/lnd/fn/v2" @@ -107,7 +107,7 @@ func NewRPCKeyRing(watchOnlyKeyRing keychain.SecretKeyRing, // p2wsh, etc. The account parameter must be non-empty as it determines // which account the address should be generated from. func (r *RPCKeyRing) NewAddress(addrType lnwallet.AddressType, change bool, - account string) (address.Address, error) { + account string) (btcutil.Address, error) { return r.WalletController.NewAddress(addrType, change, account) } @@ -780,59 +780,6 @@ func (r *RPCKeyRing) MuSig2RegisterNonces(sessionID input.MuSig2SessionID, return resp.HaveAllNonces, nil } -// MuSig2RegisterCombinedNonce registers a pre-aggregated combined nonce for a -// session identified by its ID. This is an alternative to MuSig2RegisterNonces -// and is used when a coordinator has already aggregated all individual nonces. -func (r *RPCKeyRing) MuSig2RegisterCombinedNonce( - sessionID input.MuSig2SessionID, - combinedNonce [musig2.PubNonceSize]byte) error { - - req := &signrpc.MuSig2RegisterCombinedNonceRequest{ - SessionId: sessionID[:], - CombinedPublicNonce: combinedNonce[:], - } - - ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) - defer cancel() - - _, err := r.signerClient.MuSig2RegisterCombinedNonce(ctxt, req) - if err != nil { - considerShutdown(err) - - return fmt.Errorf("error registering MuSig2 combined nonce "+ - "in remote signer instance: %v", err) - } - - return nil -} - -// MuSig2GetCombinedNonce retrieves the combined nonce for a session identified -// by its ID. -func (r *RPCKeyRing) MuSig2GetCombinedNonce(sessionID input.MuSig2SessionID) ( - [musig2.PubNonceSize]byte, error) { - - req := &signrpc.MuSig2GetCombinedNonceRequest{ - SessionId: sessionID[:], - } - - ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) - defer cancel() - - resp, err := r.signerClient.MuSig2GetCombinedNonce(ctxt, req) - if err != nil { - considerShutdown(err) - - return [musig2.PubNonceSize]byte{}, fmt.Errorf("error getting "+ - "MuSig2 combined nonce from remote signer instance: %v", - err) - } - - var combinedNonce [musig2.PubNonceSize]byte - copy(combinedNonce[:], resp.CombinedPublicNonce) - - return combinedNonce, nil -} - // MuSig2Sign creates a partial signature using the local signing key // that was specified when the session was created. This can only be // called when all public nonces of all participants are known and have @@ -951,9 +898,42 @@ func (r *RPCKeyRing) remoteSign(tx *wire.MsgTx, signDesc *input.SignDescriptor, // We need to add witness information for all inputs! Otherwise, we'll // have a problem when attempting to sign a taproot input! - populateNonSignedInputWitnessUtxos( - packet, tx, signDesc, r.WalletController.FetchOutpointInfo, - ) + for idx := range packet.Inputs { + // Skip the input we're signing for, that will get a special + // treatment later on. + if idx == signDesc.InputIndex { + continue + } + + txIn := tx.TxIn[idx] + info, err := r.WalletController.FetchOutpointInfo( + &txIn.PreviousOutPoint, + ) + if err != nil { + // Maybe we have an UTXO in the previous output fetcher? + if signDesc.PrevOutputFetcher != nil { + utxo := signDesc.PrevOutputFetcher.FetchPrevOutput( + txIn.PreviousOutPoint, + ) + if utxo != nil && utxo.Value != 0 && + len(utxo.PkScript) > 0 { + + packet.Inputs[idx].WitnessUtxo = utxo + continue + } + } + + log.Warnf("No UTXO info found for index %d "+ + "(prev_outpoint=%v), won't be able to sign "+ + "for taproot output!", idx, + txIn.PreviousOutPoint) + continue + } + packet.Inputs[idx].WitnessUtxo = &wire.TxOut{ + Value: int64(info.Value), + PkScript: info.PkScript, + } + } // Catch incorrect signing input index, just in case. if signDesc.InputIndex < 0 || signDesc.InputIndex >= len(packet.Inputs) { @@ -984,8 +964,8 @@ func (r *RPCKeyRing) remoteSign(tx *wire.MsgTx, signDesc *input.SignDescriptor, // internally stored as p2wkh addresses. case signDesc.KeyDesc.PubKey != nil && signDesc.KeyDesc.IsEmpty(): pubKeyBytes := signDesc.KeyDesc.PubKey.SerializeCompressed() - addr, err := address.NewAddressWitnessPubKeyHash( - address.Hash160(pubKeyBytes), r.netParams, + addr, err := btcutil.NewAddressWitnessPubKeyHash( + btcutil.Hash160(pubKeyBytes), r.netParams, ) if err != nil { return nil, fmt.Errorf("error deriving address from "+ @@ -1339,72 +1319,6 @@ func connectRPC(hostPort, tlsCertPath, macaroonPath string, return conn, nil } -// fetchOutpointInfoFn looks up the wallet's local knowledge of an outpoint. -// Mirrors lnwallet.WalletController.FetchOutpointInfo so the helper below can -// be exercised in unit tests without standing up a full wallet. -type fetchOutpointInfoFn func(*wire.OutPoint) (*lnwallet.Utxo, error) - -// populateNonSignedInputWitnessUtxos walks every non-signed PSBT input and -// fills in its WitnessUtxo. The signing path that ultimately ships this PSBT -// to the remote signer is walletkit.SignPsbt, which rejects any PSBT that -// has an input without a WitnessUtxo or NonWitnessUtxo set — even when the -// remote signer is only being asked to sign one of the inputs — because -// taproot sighash computation requires all prev outputs. -// -// Resolution order for each non-signed input: -// -// 1. fetchInfo (the local wallet's knowledge of the outpoint). -// 2. signDesc.PrevOutputFetcher, when the local wallet does not own or -// track the outpoint (e.g. funding-flow channel TXs that haven't been -// published yet, or BIP-322 virtual to_spend outputs). -// -// A zero Value is accepted from the fetcher. BIP-322 mandates that the -// to_spend output is exactly value=0 with the message commitment as -// pk_script, and that output is referenced as input 0 of every BIP-322 -// to_sign transaction. Refusing zero-value fetched outputs would silently -// leave that input's WitnessUtxo unpopulated, causing walletkit.SignPsbt -// to later reject the resulting PSBT with "input (index=N) doesn't specify -// any UTXO info". -func populateNonSignedInputWitnessUtxos(packet *psbt.Packet, tx *wire.MsgTx, - signDesc *input.SignDescriptor, fetchInfo fetchOutpointInfoFn) { - - for idx := range packet.Inputs { - // Skip the input we're signing for, that will get a special - // treatment by the caller. - if idx == signDesc.InputIndex { - continue - } - - txIn := tx.TxIn[idx] - info, err := fetchInfo(&txIn.PreviousOutPoint) - if err != nil { - // The wallet doesn't know about this outpoint. Fall - // back to the caller-supplied PrevOutputFetcher. - if signDesc.PrevOutputFetcher != nil { - fetcher := signDesc.PrevOutputFetcher - utxo := fetcher.FetchPrevOutput( - txIn.PreviousOutPoint, - ) - if utxo != nil && len(utxo.PkScript) > 0 { - packet.Inputs[idx].WitnessUtxo = utxo - continue - } - } - - log.Warnf("No UTXO info found for index %d "+ - "(prev_outpoint=%v), won't be able to sign "+ - "for taproot output!", idx, - txIn.PreviousOutPoint) - - continue - } - packet.Inputs[idx].WitnessUtxo = &wire.TxOut{ - Value: int64(info.Value), - PkScript: info.PkScript, - } - } -} - // packetFromTx creates a PSBT from a tx that potentially already contains // signed inputs. func packetFromTx(original *wire.MsgTx) (*psbt.Packet, error) { diff --git a/lnwallet/rpcwallet/rpcwallet_test.go b/lnwallet/rpcwallet/rpcwallet_test.go deleted file mode 100644 index 63dadf326..000000000 --- a/lnwallet/rpcwallet/rpcwallet_test.go +++ /dev/null @@ -1,231 +0,0 @@ -package rpcwallet - -import ( - "bytes" - "errors" - "testing" - - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/psbt/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/input" - "github.com/lightningnetwork/lnd/lnwallet" - "github.com/stretchr/testify/require" -) - -// errNotMine mirrors lnwallet.ErrNotMine for the parts of these tests that -// just need *some* "wallet doesn't know this outpoint" sentinel. -var errNotMine = errors.New("not mine") - -// makeOutPoint returns a wire.OutPoint with a unique, deterministic hash so -// each test case can build inputs without colliding. -func makeOutPoint(t *testing.T, idx uint32) wire.OutPoint { - t.Helper() - var h chainhash.Hash - h[0] = byte(idx + 1) - - return wire.OutPoint{Hash: h, Index: idx} -} - -// makeTxAndPacket builds a wire tx with the given outpoints and the matching -// empty PSBT skeleton ready for WitnessUtxo to be filled in per input. -func makeTxAndPacket(t *testing.T, - outpoints []wire.OutPoint) (*wire.MsgTx, *psbt.Packet) { - - t.Helper() - tx := wire.NewMsgTx(2) - for _, op := range outpoints { - tx.AddTxIn(&wire.TxIn{PreviousOutPoint: op}) - } - // At least one output is required by psbt.NewFromUnsignedTx. - tx.AddTxOut(&wire.TxOut{Value: 1000, PkScript: []byte{0x51}}) - - packet, err := psbt.NewFromUnsignedTx(tx) - require.NoError(t, err) - - return tx, packet -} - -// TestPopulateNonSignedInputWitnessUtxosFromWallet verifies that an input -// whose outpoint is known to the wallet is annotated with the wallet's -// view of the prev output. -func TestPopulateNonSignedInputWitnessUtxosFromWallet(t *testing.T) { - t.Parallel() - - walletOp := makeOutPoint(t, 0) - signingOp := makeOutPoint(t, 1) - - tx, packet := makeTxAndPacket( - t, []wire.OutPoint{walletOp, signingOp}, - ) - - walletPkScript := []byte{0x51, 0x20, 0xaa, 0xbb} - fetchInfo := func(op *wire.OutPoint) (*lnwallet.Utxo, error) { - if *op == walletOp { - return &lnwallet.Utxo{ - Value: 12345, - PkScript: walletPkScript, - }, nil - } - - return nil, errNotMine - } - - signDesc := &input.SignDescriptor{InputIndex: 1} - populateNonSignedInputWitnessUtxos(packet, tx, signDesc, fetchInfo) - - require.NotNil(t, packet.Inputs[0].WitnessUtxo) - require.Equal(t, int64(12345), packet.Inputs[0].WitnessUtxo.Value) - require.Equal(t, walletPkScript, packet.Inputs[0].WitnessUtxo.PkScript) - - // The signed input must be untouched. - require.Nil(t, packet.Inputs[1].WitnessUtxo) -} - -// TestPopulateNonSignedInputWitnessUtxosFromFetcher verifies that when the -// wallet does not know about an outpoint, the helper falls back to the -// sign descriptor's PrevOutputFetcher. -func TestPopulateNonSignedInputWitnessUtxosFromFetcher(t *testing.T) { - t.Parallel() - - externalOp := makeOutPoint(t, 0) - signingOp := makeOutPoint(t, 1) - - tx, packet := makeTxAndPacket( - t, []wire.OutPoint{externalOp, signingOp}, - ) - - externalUtxo := &wire.TxOut{ - Value: 50000, - PkScript: []byte{0x51, 0x20, 0xcc, 0xdd}, - } - prevFetcher := txscript.NewMultiPrevOutFetcher( - map[wire.OutPoint]*wire.TxOut{externalOp: externalUtxo}, - ) - - fetchInfo := func(*wire.OutPoint) (*lnwallet.Utxo, error) { - return nil, errNotMine - } - - signDesc := &input.SignDescriptor{ - InputIndex: 1, - PrevOutputFetcher: prevFetcher, - } - populateNonSignedInputWitnessUtxos(packet, tx, signDesc, fetchInfo) - - require.NotNil(t, packet.Inputs[0].WitnessUtxo) - require.Equal( - t, externalUtxo.Value, packet.Inputs[0].WitnessUtxo.Value, - ) - require.True(t, bytes.Equal( - externalUtxo.PkScript, packet.Inputs[0].WitnessUtxo.PkScript, - )) -} - -// TestPopulateNonSignedInputWitnessUtxosZeroValueFromFetcher is the -// regression test for the BIP-322 case. The to_spend output is mandated -// by BIP-322 to have value=0 with the message commitment as its pk_script, -// and that output is referenced as input 0 of every BIP-322 to_sign tx. -// The helper must accept that zero-value entry — otherwise the resulting -// PSBT is rejected by walletkit.SignPsbt with "input (index=N) doesn't -// specify any UTXO info". -func TestPopulateNonSignedInputWitnessUtxosZeroValueFromFetcher(t *testing.T) { - t.Parallel() - - bip322ToSpendOp := makeOutPoint(t, 0) - signingOp := makeOutPoint(t, 1) - - tx, packet := makeTxAndPacket( - t, []wire.OutPoint{bip322ToSpendOp, signingOp}, - ) - - // The BIP-322 to_spend output: value=0, pk_script is the message - // commitment script (P2WSH of OP_0 ). For the purposes of - // this test the exact script bytes don't matter; only that we have - // a non-empty pk_script with a zero Value. - msgCommitment := []byte{0x00, 0x20, 0xde, 0xad, 0xbe, 0xef} - zeroValueUtxo := &wire.TxOut{Value: 0, PkScript: msgCommitment} - - prevFetcher := txscript.NewMultiPrevOutFetcher( - map[wire.OutPoint]*wire.TxOut{bip322ToSpendOp: zeroValueUtxo}, - ) - - fetchInfo := func(*wire.OutPoint) (*lnwallet.Utxo, error) { - return nil, errNotMine - } - - signDesc := &input.SignDescriptor{ - InputIndex: 1, - PrevOutputFetcher: prevFetcher, - } - populateNonSignedInputWitnessUtxos(packet, tx, signDesc, fetchInfo) - - require.NotNil(t, packet.Inputs[0].WitnessUtxo, - "BIP-322 to_spend output must populate WitnessUtxo "+ - "despite its zero Value") - require.Equal(t, int64(0), packet.Inputs[0].WitnessUtxo.Value) - require.Equal( - t, msgCommitment, packet.Inputs[0].WitnessUtxo.PkScript, - ) -} - -// TestPopulateNonSignedInputWitnessUtxosNoFallback verifies the helper -// leaves an input bare when neither the wallet nor a fetcher can resolve -// the outpoint. The caller logs a warning; the unsigned PSBT will still -// fail downstream validation, but that failure should be exposed to the -// caller rather than silently masked. -func TestPopulateNonSignedInputWitnessUtxosNoFallback(t *testing.T) { - t.Parallel() - - unknownOp := makeOutPoint(t, 0) - signingOp := makeOutPoint(t, 1) - - tx, packet := makeTxAndPacket( - t, []wire.OutPoint{unknownOp, signingOp}, - ) - - fetchInfo := func(*wire.OutPoint) (*lnwallet.Utxo, error) { - return nil, errNotMine - } - // No PrevOutputFetcher on the sign descriptor. - signDesc := &input.SignDescriptor{InputIndex: 1} - - populateNonSignedInputWitnessUtxos(packet, tx, signDesc, fetchInfo) - - require.Nil(t, packet.Inputs[0].WitnessUtxo) -} - -// TestPopulateNonSignedInputWitnessUtxosEmptyPkScript guards the helper -// against fetchers that return a non-nil TxOut with an empty PkScript. -// Such an entry is not a usable WitnessUtxo (a PSBT WitnessUtxo with an -// empty PkScript is malformed at serialization), so the helper should -// treat it as unknown and skip the input. -func TestPopulateNonSignedInputWitnessUtxosEmptyPkScript(t *testing.T) { - t.Parallel() - - bogusOp := makeOutPoint(t, 0) - signingOp := makeOutPoint(t, 1) - - tx, packet := makeTxAndPacket( - t, []wire.OutPoint{bogusOp, signingOp}, - ) - - prevFetcher := txscript.NewMultiPrevOutFetcher( - map[wire.OutPoint]*wire.TxOut{ - bogusOp: {Value: 100, PkScript: nil}, - }, - ) - - fetchInfo := func(*wire.OutPoint) (*lnwallet.Utxo, error) { - return nil, errNotMine - } - signDesc := &input.SignDescriptor{ - InputIndex: 1, - PrevOutputFetcher: prevFetcher, - } - - populateNonSignedInputWitnessUtxos(packet, tx, signDesc, fetchInfo) - - require.Nil(t, packet.Inputs[0].WitnessUtxo) -} diff --git a/lnwallet/sigpool.go b/lnwallet/sigpool.go index e76a465af..2296e1703 100644 --- a/lnwallet/sigpool.go +++ b/lnwallet/sigpool.go @@ -5,7 +5,7 @@ import ( "sync" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwire" ) diff --git a/lnwallet/taproot_test_vectors_test.go b/lnwallet/taproot_test_vectors_test.go deleted file mode 100644 index 57b702769..000000000 --- a/lnwallet/taproot_test_vectors_test.go +++ /dev/null @@ -1,1772 +0,0 @@ -package lnwallet - -import ( - "bytes" - "crypto/sha256" - "encoding/hex" - "encoding/json" - "flag" - "fmt" - "net" - "os" - "sort" - "testing" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/input" - "github.com/lightningnetwork/lnd/keychain" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/lnwallet/chainfee" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/shachain" - "github.com/stretchr/testify/require" -) - -// bip340Signer wraps a Signer and overrides SignOutputRaw for taproot script -// spends to use BIP-340 standard nonce derivation (with zero auxrand) instead -// of RFC6979. This ensures HTLC signatures in test vectors are deterministic -// and reproducible across MuSig2/Schnorr implementations (e.g. libsecp256k1). -type bip340Signer struct { - input.Signer - - // privKeys maps compressed pubkey bytes to private keys, allowing the - // wrapper to look up the signing key for BIP-340 re-signing. - privKeys map[[33]byte]*btcec.PrivateKey -} - -// newBIP340Signer creates a bip340Signer that wraps the given signer. The -// provided private keys are indexed by their compressed public key for lookup -// during taproot script spend signing. -func newBIP340Signer(inner input.Signer, - keys []*btcec.PrivateKey) *bip340Signer { - - keyMap := make(map[[33]byte]*btcec.PrivateKey, len(keys)) - for _, k := range keys { - var pub [33]byte - copy(pub[:], k.PubKey().SerializeCompressed()) - keyMap[pub] = k - } - - return &bip340Signer{ - Signer: inner, - privKeys: keyMap, - } -} - -// SignOutputRaw overrides the embedded signer's method. For taproot script -// path spends, it computes the sighash and signs using schnorr.CustomNonce -// with zero auxrand (BIP-340 deterministic). All other signing paths delegate -// to the wrapped signer unchanged. -func (s *bip340Signer) SignOutputRaw(tx *wire.MsgTx, - signDesc *input.SignDescriptor) (input.Signature, error) { - - // Only override taproot script spends. Everything else (including - // MuSig2 key spends) delegates to the inner signer. - if signDesc.SignMethod != input.TaprootScriptSpendSignMethod { - return s.Signer.SignOutputRaw(tx, signDesc) - } - - // Resolve the private key. The SignDescriptor's KeyDesc.PubKey is - // the base key before any tweaking. We apply the same single-tweak - // that MockSigner uses to derive the actual signing key. - basePub := signDesc.KeyDesc.PubKey - if basePub == nil { - return nil, fmt.Errorf("bip340Signer: no pubkey in " + - "sign descriptor") - } - - var pubBytes [33]byte - copy(pubBytes[:], basePub.SerializeCompressed()) - basePriv, ok := s.privKeys[pubBytes] - if !ok { - return nil, fmt.Errorf("bip340Signer: unknown key %x", - pubBytes) - } - - // Apply the single tweak if present (used for HTLC key derivation). - privKey := basePriv - if signDesc.SingleTweak != nil { - privKey = input.TweakPrivKey(basePriv, signDesc.SingleTweak) - } - - // Compute the tapscript sighash. - sigHashes := txscript.NewTxSigHashes( - tx, signDesc.PrevOutputFetcher, - ) - leaf := txscript.TapLeaf{ - LeafVersion: txscript.BaseLeafVersion, - Script: signDesc.WitnessScript, - } - sigHash, err := txscript.CalcTapscriptSignaturehash( - sigHashes, signDesc.HashType, tx, signDesc.InputIndex, - signDesc.PrevOutputFetcher, leaf, - ) - if err != nil { - return nil, err - } - - // Sign using BIP-340 nonce derivation with zero auxrand. - sig, err := schnorr.Sign( - privKey, sigHash, schnorr.CustomNonce([32]byte{}), - ) - if err != nil { - return nil, err - } - - return sig, nil -} - -// generateTaprootVectors controls whether to generate test vectors and write -// them to disk, or to verify the stored vectors match regenerated values. -var generateTaprootVectors = flag.Bool( - "generate-taproot-vectors", false, - "generate taproot test vectors and write to "+taprootVectorFile, -) - -const ( - // taprootVectorSeedHex is the single deterministic seed from which all - // test vector keys are derived. - taprootVectorSeedHex = "000102030405060708090a0b0c0d0e0f" + - "101112131415161718191a1b1c1d1e1f" - - // taprootVectorFile is the JSON file where test vectors are stored. - taprootVectorFile = "test_vectors_taproot.json" -) - -// deriveKeyFromSeed derives a deterministic private key from a seed and a -// label string. The key is computed as SHA256(seed || label). -func deriveKeyFromSeed(seed []byte, label string) *btcec.PrivateKey { - h := sha256.New() - h.Write(seed) - h.Write([]byte(label)) - keyBytes := h.Sum(nil) - - privKey, _ := btcec.PrivKeyFromBytes(keyBytes) - - return privKey -} - -// pubHex returns the compressed hex encoding of a public key. -func pubHex(pub *btcec.PublicKey) string { - return hex.EncodeToString(pub.SerializeCompressed()) -} - -// privHex returns the hex encoding of a private key scalar. -func privHex(priv *btcec.PrivateKey) string { - return hex.EncodeToString(priv.Serialize()) -} - -// scriptHex returns the hex encoding of a byte slice (script, hash, etc.). -func scriptHex(b []byte) string { - return hex.EncodeToString(b) -} - -// leafHash computes the TapHash of a tap leaf script. -func leafHash(script []byte) string { - leaf := txscript.NewBaseTapLeaf(script) - h := leaf.TapHash() - return hex.EncodeToString(h[:]) -} - -// taprootTestContext holds all deterministic keys and parameters for taproot -// test vector generation. -type taprootTestContext struct { - seed []byte - - localFundingPrivkey *btcec.PrivateKey - remoteFundingPrivkey *btcec.PrivateKey - localPaymentBasepointSecret *btcec.PrivateKey - remotePaymentBasepointSecret *btcec.PrivateKey - localDelayedPaymentBasepointSecret *btcec.PrivateKey - remoteRevocationBasepointSecret *btcec.PrivateKey - localHtlcBasepointSecret *btcec.PrivateKey - remoteHtlcBasepointSecret *btcec.PrivateKey - - localPerCommitSecret lntypes.Hash - - fundingAmount btcutil.Amount - dustLimit btcutil.Amount - localCsvDelay uint16 - commitHeight uint64 - - t *testing.T -} - -// newTaprootTestContext creates a new test context with all keys derived -// deterministically from the single seed. -func newTaprootTestContext(t *testing.T) *taprootTestContext { - seed, err := hex.DecodeString(taprootVectorSeedHex) - require.NoError(t, err) - - tc := &taprootTestContext{ - seed: seed, - fundingAmount: 10_000_000, - dustLimit: 354, - localCsvDelay: 144, - commitHeight: 42, - t: t, - } - - tc.localFundingPrivkey = deriveKeyFromSeed(seed, "local-funding") - tc.remoteFundingPrivkey = deriveKeyFromSeed(seed, "remote-funding") - tc.localPaymentBasepointSecret = deriveKeyFromSeed( - seed, "local-payment-basepoint", - ) - tc.remotePaymentBasepointSecret = deriveKeyFromSeed( - seed, "remote-payment-basepoint", - ) - tc.localDelayedPaymentBasepointSecret = deriveKeyFromSeed( - seed, "local-delayed-payment-basepoint", - ) - tc.remoteRevocationBasepointSecret = deriveKeyFromSeed( - seed, "remote-revocation-basepoint", - ) - tc.localHtlcBasepointSecret = deriveKeyFromSeed( - seed, "local-htlc-basepoint", - ) - tc.remoteHtlcBasepointSecret = deriveKeyFromSeed( - seed, "remote-htlc-basepoint", - ) - - // Derive per-commitment secret from the seed as well. - h := sha256.New() - h.Write(seed) - h.Write([]byte("local-per-commit-secret")) - copy(tc.localPerCommitSecret[:], h.Sum(nil)) - - return tc -} - -// commitPoint returns the per-commitment point derived from the secret. -func (tc *taprootTestContext) commitPoint() *btcec.PublicKey { - return input.ComputeCommitmentPoint(tc.localPerCommitSecret[:]) -} - -// TaprootTestVectors is the top-level JSON structure for taproot test vectors. -type TaprootTestVectors struct { - Params TestVectorParams `json:"params"` - Scripts ScriptVectors `json:"scripts"` - Transactions []TransactionTestCase `json:"transactions"` -} - -// TestVectorParams holds the seed, channel parameters, and all keys. -type TestVectorParams struct { - Seed string `json:"seed"` - FundingAmountSatoshis int64 `json:"funding_amount_satoshis"` - DustLimitSatoshis int64 `json:"dust_limit_satoshis"` - CsvDelay uint16 `json:"csv_delay"` - CommitHeight uint64 `json:"commit_height"` - NumsPoint string `json:"nums_point"` - Keys KeySet `json:"keys"` -} - -// KeySet contains all base point keys and derived per-commitment keys. -type KeySet struct { - LocalFundingPrivkey string `json:"local_funding_privkey"` - LocalFundingPubkey string `json:"local_funding_pubkey"` - RemoteFundingPrivkey string `json:"remote_funding_privkey"` - RemoteFundingPubkey string `json:"remote_funding_pubkey"` - - LocalPaymentBasepointSecret string `json:"local_payment_basepoint_secret"` //nolint:ll - LocalPaymentBasepoint string `json:"local_payment_basepoint"` - RemotePaymentBasepointSecret string `json:"remote_payment_basepoint_secret"` //nolint:ll - RemotePaymentBasepoint string `json:"remote_payment_basepoint"` - - LocalDelayedPaymentBasepointSecret string `json:"local_delayed_payment_basepoint_secret"` //nolint:ll - LocalDelayedPaymentBasepoint string `json:"local_delayed_payment_basepoint"` //nolint:ll - RemoteRevocationBasepointSecret string `json:"remote_revocation_basepoint_secret"` //nolint:ll - RemoteRevocationBasepoint string `json:"remote_revocation_basepoint"` //nolint:ll - - LocalHtlcBasepointSecret string `json:"local_htlc_basepoint_secret"` - LocalHtlcBasepoint string `json:"local_htlc_basepoint"` - RemoteHtlcBasepointSecret string `json:"remote_htlc_basepoint_secret"` - RemoteHtlcBasepoint string `json:"remote_htlc_basepoint"` - - LocalPerCommitSecret string `json:"local_per_commit_secret"` - LocalPerCommitPoint string `json:"local_per_commit_point"` - - // Derived per-commitment keys. - DerivedLocalDelayedPubkey string `json:"derived_local_delayed_pubkey"` - DerivedRevocationPubkey string `json:"derived_revocation_pubkey"` - DerivedLocalHtlcPubkey string `json:"derived_local_htlc_pubkey"` - DerivedRemoteHtlcPubkey string `json:"derived_remote_htlc_pubkey"` - DerivedRemotePaymentPubkey string `json:"derived_remote_payment_pubkey"` -} - -// ScriptVectorEntry represents a single tapscript tree decomposition. -type ScriptVectorEntry struct { - // For scripts with named leaves. - Scripts map[string]string `json:"scripts,omitempty"` - LeafHashes map[string]string `json:"leaf_hashes,omitempty"` - - TapscriptRoot string `json:"tapscript_root"` - InternalKey string `json:"internal_key"` - OutputKey string `json:"output_key"` - PkScript string `json:"pkscript"` -} - -// FundingScriptVector holds the funding output vector. -type FundingScriptVector struct { - FundingTxHex string `json:"funding_tx_hex"` - CombinedKey string `json:"combined_key"` - PkScript string `json:"pkscript"` -} - -// ScriptVectors holds all script test vectors. -type ScriptVectors struct { - Funding FundingScriptVector `json:"funding"` - ToLocal ScriptVectorEntry `json:"to_local"` - ToRemote ScriptVectorEntry `json:"to_remote"` - LocalAnchor ScriptVectorEntry `json:"local_anchor"` - RemoteAnchor ScriptVectorEntry `json:"remote_anchor"` - OfferedHtlcLocalCommit ScriptVectorEntry `json:"offered_htlc_local_commit"` //nolint:ll - OfferedHtlcRemoteCommit ScriptVectorEntry `json:"offered_htlc_remote_commit"` //nolint:ll - AcceptedHtlcLocalCommit ScriptVectorEntry `json:"accepted_htlc_local_commit"` //nolint:ll - AcceptedHtlcRemoteCommit ScriptVectorEntry `json:"accepted_htlc_remote_commit"` //nolint:ll - SecondLevelHtlcSuccess ScriptVectorEntry `json:"second_level_htlc_success"` //nolint:ll - SecondLevelHtlcTimeout ScriptVectorEntry `json:"second_level_htlc_timeout"` //nolint:ll -} - -// HtlcDesc describes an HTLC resolution in the transaction vectors. -type HtlcDesc struct { - RemotePartialSigHex string `json:"remote_partial_sig_hex"` - ResolutionTxHex string `json:"resolution_tx_hex"` -} - -// HtlcInput describes an HTLC added to the channel for a test case. -type HtlcInput struct { - Incoming bool `json:"incoming"` - AmountMsat uint64 `json:"amount_msat"` - Expiry uint32 `json:"expiry"` - Preimage string `json:"preimage"` -} - -// TransactionTestCase is one transaction test vector. -type TransactionTestCase struct { - Name string `json:"name"` - LocalBalanceMsat uint64 `json:"local_balance_msat"` - RemoteBalanceMsat uint64 `json:"remote_balance_msat"` - FeePerKw int64 `json:"fee_per_kw"` - DustLimitSatoshis int64 `json:"dust_limit_satoshis,omitempty"` //nolint:ll - Htlcs []HtlcInput `json:"htlcs"` - LocalSecNonce string `json:"local_sec_nonce"` - RemoteSecNonce string `json:"remote_sec_nonce"` - LocalNonce string `json:"local_nonce"` - RemoteNonce string `json:"remote_nonce"` - RemotePartialSig string `json:"remote_partial_sig"` - ExpectedCommitmentTxHex string `json:"expected_commitment_tx_hex"` - HtlcDescs []HtlcDesc `json:"htlc_descs"` -} - -// generateParams populates the params section of the test vectors. -func (tc *taprootTestContext) generateParams() TestVectorParams { - commitPt := tc.commitPoint() - - // Derive per-commitment tweaked keys. - localDelayedPubkey := input.TweakPubKey( - tc.localDelayedPaymentBasepointSecret.PubKey(), commitPt, - ) - revocationPubkey := input.DeriveRevocationPubkey( - tc.remoteRevocationBasepointSecret.PubKey(), commitPt, - ) - localHtlcPubkey := input.TweakPubKey( - tc.localHtlcBasepointSecret.PubKey(), commitPt, - ) - remoteHtlcPubkey := input.TweakPubKey( - tc.remoteHtlcBasepointSecret.PubKey(), commitPt, - ) - - // For tweakless channels, the remote payment key is untweaked. - remotePaymentPubkey := tc.remotePaymentBasepointSecret.PubKey() - - return TestVectorParams{ - Seed: taprootVectorSeedHex, - FundingAmountSatoshis: int64(tc.fundingAmount), - DustLimitSatoshis: int64(tc.dustLimit), - CsvDelay: tc.localCsvDelay, - CommitHeight: tc.commitHeight, - NumsPoint: input.TaprootNUMSHex, - Keys: KeySet{ - LocalFundingPrivkey: privHex(tc.localFundingPrivkey), - LocalFundingPubkey: pubHex( - tc.localFundingPrivkey.PubKey(), - ), - RemoteFundingPrivkey: privHex(tc.remoteFundingPrivkey), - RemoteFundingPubkey: pubHex( - tc.remoteFundingPrivkey.PubKey(), - ), - - LocalPaymentBasepointSecret: privHex( - tc.localPaymentBasepointSecret, - ), - LocalPaymentBasepoint: pubHex( - tc.localPaymentBasepointSecret.PubKey(), - ), - RemotePaymentBasepointSecret: privHex( - tc.remotePaymentBasepointSecret, - ), - RemotePaymentBasepoint: pubHex( - tc.remotePaymentBasepointSecret.PubKey(), - ), - - LocalDelayedPaymentBasepointSecret: privHex( - tc.localDelayedPaymentBasepointSecret, - ), - LocalDelayedPaymentBasepoint: pubHex( - tc.localDelayedPaymentBasepointSecret.PubKey(), - ), - RemoteRevocationBasepointSecret: privHex( - tc.remoteRevocationBasepointSecret, - ), - RemoteRevocationBasepoint: pubHex( - tc.remoteRevocationBasepointSecret.PubKey(), - ), - - LocalHtlcBasepointSecret: privHex( - tc.localHtlcBasepointSecret, - ), - LocalHtlcBasepoint: pubHex( - tc.localHtlcBasepointSecret.PubKey(), - ), - RemoteHtlcBasepointSecret: privHex( - tc.remoteHtlcBasepointSecret, - ), - RemoteHtlcBasepoint: pubHex( - tc.remoteHtlcBasepointSecret.PubKey(), - ), - - LocalPerCommitSecret: hex.EncodeToString( - tc.localPerCommitSecret[:], - ), - LocalPerCommitPoint: pubHex(commitPt), - - DerivedLocalDelayedPubkey: pubHex(localDelayedPubkey), - DerivedRevocationPubkey: pubHex(revocationPubkey), - DerivedLocalHtlcPubkey: pubHex(localHtlcPubkey), - DerivedRemoteHtlcPubkey: pubHex(remoteHtlcPubkey), - DerivedRemotePaymentPubkey: pubHex(remotePaymentPubkey), - }, - } -} - -// generateFundingVector generates the funding output script vector. -func (tc *taprootTestContext) generateFundingVector() FundingScriptVector { - t := tc.t - - pkScript, _, err := input.GenTaprootFundingScript( - tc.localFundingPrivkey.PubKey(), - tc.remoteFundingPrivkey.PubKey(), - int64(tc.fundingAmount), - fn.None[chainhash.Hash](), - ) - require.NoError(t, err) - - // Build a minimal funding transaction with the P2TR output. - fundingTx := wire.NewMsgTx(2) - fundingTx.AddTxIn(&wire.TxIn{ - PreviousOutPoint: wire.OutPoint{ - Hash: chainhash.Hash{}, - Index: 0, - }, - }) - fundingTx.AddTxOut(&wire.TxOut{ - Value: int64(tc.fundingAmount), - PkScript: pkScript, - }) - - var txBuf bytes.Buffer - require.NoError(t, fundingTx.Serialize(&txBuf)) - - // Extract the combined key from the pkScript. For P2TR, the pkScript - // is OP_1 <32-byte-key>, so the key starts at byte 2. - combinedKeyBytes := pkScript[2:] - - return FundingScriptVector{ - FundingTxHex: hex.EncodeToString(txBuf.Bytes()), - CombinedKey: hex.EncodeToString(combinedKeyBytes), - PkScript: scriptHex(pkScript), - } -} - -// commitScriptTreeToEntry converts a CommitScriptTree into a ScriptVectorEntry. -func commitScriptTreeToEntry( - tree *input.CommitScriptTree) ScriptVectorEntry { - - scripts := make(map[string]string) - leafHashes := make(map[string]string) - - settleScript := tree.SettleLeaf.Script - scripts["settle"] = scriptHex(settleScript) - leafHashes["settle"] = leafHash(settleScript) - - if tree.RevocationLeaf.Script != nil { - revokeScript := tree.RevocationLeaf.Script - scripts["revocation"] = scriptHex(revokeScript) - leafHashes["revocation"] = leafHash(revokeScript) - } - - return ScriptVectorEntry{ - Scripts: scripts, - LeafHashes: leafHashes, - TapscriptRoot: scriptHex(tree.TapscriptRoot), - InternalKey: pubHex(tree.InternalKey), - OutputKey: pubHex(tree.TaprootKey), - PkScript: scriptHex(tree.PkScript()), - } -} - -// htlcScriptTreeToEntry converts an HtlcScriptTree into a ScriptVectorEntry. -func htlcScriptTreeToEntry(tree *input.HtlcScriptTree) ScriptVectorEntry { - scripts := make(map[string]string) - leafHashes := make(map[string]string) - - successScript := tree.SuccessTapLeaf.Script - scripts["success"] = scriptHex(successScript) - leafHashes["success"] = leafHash(successScript) - - timeoutScript := tree.TimeoutTapLeaf.Script - scripts["timeout"] = scriptHex(timeoutScript) - leafHashes["timeout"] = leafHash(timeoutScript) - - return ScriptVectorEntry{ - Scripts: scripts, - LeafHashes: leafHashes, - TapscriptRoot: scriptHex(tree.TapscriptRoot), - InternalKey: pubHex(tree.InternalKey), - OutputKey: pubHex(tree.TaprootKey), - PkScript: scriptHex(tree.PkScript()), - } -} - -// secondLevelScriptTreeToEntry converts a SecondLevelScriptTree into a -// ScriptVectorEntry. -func secondLevelScriptTreeToEntry( - tree *input.SecondLevelScriptTree) ScriptVectorEntry { - - scripts := make(map[string]string) - leafHashes := make(map[string]string) - - successScript := tree.SuccessTapLeaf.Script - scripts["success"] = scriptHex(successScript) - leafHashes["success"] = leafHash(successScript) - - return ScriptVectorEntry{ - Scripts: scripts, - LeafHashes: leafHashes, - TapscriptRoot: scriptHex(tree.TapscriptRoot), - InternalKey: pubHex(tree.InternalKey), - OutputKey: pubHex(tree.TaprootKey), - PkScript: scriptHex(tree.PkScript()), - } -} - -// anchorScriptTreeToEntry converts an AnchorScriptTree into a -// ScriptVectorEntry. -func anchorScriptTreeToEntry( - tree *input.AnchorScriptTree) ScriptVectorEntry { - - scripts := make(map[string]string) - leafHashes := make(map[string]string) - - sweepScript := tree.SweepLeaf.Script - scripts["sweep"] = scriptHex(sweepScript) - leafHashes["sweep"] = leafHash(sweepScript) - - return ScriptVectorEntry{ - Scripts: scripts, - LeafHashes: leafHashes, - TapscriptRoot: scriptHex(tree.TapscriptRoot), - InternalKey: pubHex(tree.InternalKey), - OutputKey: pubHex(tree.TaprootKey), - PkScript: scriptHex(tree.PkScript()), - } -} - -// generateScriptVectors generates all script-only test vectors. -func (tc *taprootTestContext) generateScriptVectors() ScriptVectors { - t := tc.t - commitPt := tc.commitPoint() - - // Derive per-commitment tweaked keys. - localDelayedPubkey := input.TweakPubKey( - tc.localDelayedPaymentBasepointSecret.PubKey(), commitPt, - ) - revocationPubkey := input.DeriveRevocationPubkey( - tc.remoteRevocationBasepointSecret.PubKey(), commitPt, - ) - localHtlcPubkey := input.TweakPubKey( - tc.localHtlcBasepointSecret.PubKey(), commitPt, - ) - remoteHtlcPubkey := input.TweakPubKey( - tc.remoteHtlcBasepointSecret.PubKey(), commitPt, - ) - remotePaymentPubkey := tc.remotePaymentBasepointSecret.PubKey() - - noAux := fn.None[txscript.TapLeaf]() - - // 1. to_local script tree. - toLocalTree, err := input.NewLocalCommitScriptTree( - uint32(tc.localCsvDelay), localDelayedPubkey, - revocationPubkey, noAux, input.WithProdScripts(), - ) - require.NoError(t, err) - - // 2. to_remote script tree. - toRemoteTree, err := input.NewRemoteCommitScriptTree( - remotePaymentPubkey, noAux, input.WithProdScripts(), - ) - require.NoError(t, err) - - // 3. Anchor script trees. - localAnchorTree, err := input.NewAnchorScriptTree( - localDelayedPubkey, - ) - require.NoError(t, err) - - remoteAnchorTree, err := input.NewAnchorScriptTree( - remotePaymentPubkey, - ) - require.NoError(t, err) - - // Use HTLC 0 for offered/accepted HTLC vectors. - preimage0, err := lntypes.MakePreimageFromStr( - "00000000000000000000000000000000000000000000" + - "00000000000000000000", - ) - require.NoError(t, err) - payHash0 := preimage0.Hash() - - // 4. Offered HTLC (local commit). - offeredLocalTree, err := input.SenderHTLCScriptTaproot( - localHtlcPubkey, remoteHtlcPubkey, revocationPubkey, - payHash0[:], lntypes.Local, noAux, - input.WithProdScripts(), - ) - require.NoError(t, err) - - // 5. Offered HTLC (remote commit). - offeredRemoteTree, err := input.SenderHTLCScriptTaproot( - localHtlcPubkey, remoteHtlcPubkey, revocationPubkey, - payHash0[:], lntypes.Remote, noAux, - input.WithProdScripts(), - ) - require.NoError(t, err) - - // 6. Accepted HTLC (local commit). - acceptedLocalTree, err := input.ReceiverHTLCScriptTaproot( - 500, localHtlcPubkey, remoteHtlcPubkey, revocationPubkey, - payHash0[:], lntypes.Local, noAux, - input.WithProdScripts(), - ) - require.NoError(t, err) - - // 7. Accepted HTLC (remote commit). - acceptedRemoteTree, err := input.ReceiverHTLCScriptTaproot( - 500, localHtlcPubkey, remoteHtlcPubkey, revocationPubkey, - payHash0[:], lntypes.Remote, noAux, - input.WithProdScripts(), - ) - require.NoError(t, err) - - // 8. Second-level HTLC success. - secondLevelSuccess, err := input.TaprootSecondLevelScriptTree( - revocationPubkey, localDelayedPubkey, - uint32(tc.localCsvDelay), noAux, - input.WithProdScripts(), - ) - require.NoError(t, err) - - // 9. Second-level HTLC timeout (same function, different keys in a real - // scenario, but for vectors we show the construction with the same - // delay key since second-level success and timeout share the same - // script tree structure). - secondLevelTimeout, err := input.TaprootSecondLevelScriptTree( - revocationPubkey, localDelayedPubkey, - uint32(tc.localCsvDelay), noAux, - input.WithProdScripts(), - ) - require.NoError(t, err) - - return ScriptVectors{ - Funding: tc.generateFundingVector(), - ToLocal: commitScriptTreeToEntry(toLocalTree), - ToRemote: commitScriptTreeToEntry(toRemoteTree), - LocalAnchor: anchorScriptTreeToEntry(localAnchorTree), - RemoteAnchor: anchorScriptTreeToEntry( - remoteAnchorTree, - ), - OfferedHtlcLocalCommit: htlcScriptTreeToEntry(offeredLocalTree), - OfferedHtlcRemoteCommit: htlcScriptTreeToEntry( - offeredRemoteTree, - ), - AcceptedHtlcLocalCommit: htlcScriptTreeToEntry( - acceptedLocalTree, - ), - AcceptedHtlcRemoteCommit: htlcScriptTreeToEntry( - acceptedRemoteTree, - ), - SecondLevelHtlcSuccess: secondLevelScriptTreeToEntry( - secondLevelSuccess, - ), - SecondLevelHtlcTimeout: secondLevelScriptTreeToEntry( - secondLevelTimeout, - ), - } -} - -// --------------------------------------------------------------------------- -// Transaction vector generation (Section B) -// --------------------------------------------------------------------------- - -// taprootChanType is the channel type used for taproot test vectors. -var taprootChanType = channeldb.SingleFunderTweaklessBit | - channeldb.AnchorOutputsBit | - channeldb.ZeroHtlcTxFeeBit | - channeldb.SimpleTaprootFeatureBit | - channeldb.TaprootFinalBit - -// createTaprootTestChannelsForVectors creates a pair of LightningChannel -// instances configured for taproot test vector generation. All keys are -// deterministic. -func createTaprootTestChannelsForVectors(tc *taprootTestContext, - feeRate btcutil.Amount, remoteBalance, - localBalance btcutil.Amount) (*LightningChannel, *LightningChannel) { - - t := tc.t - - // Build the funding transaction with a P2TR output. - pkScript, _, err := input.GenTaprootFundingScript( - tc.localFundingPrivkey.PubKey(), - tc.remoteFundingPrivkey.PubKey(), - int64(tc.fundingAmount), - fn.None[chainhash.Hash](), - ) - require.NoError(t, err) - - fundingTx := wire.NewMsgTx(2) - fundingTx.AddTxIn(&wire.TxIn{ - PreviousOutPoint: wire.OutPoint{ - Hash: chainhash.Hash{}, - Index: 0, - }, - }) - fundingTx.AddTxOut(&wire.TxOut{ - Value: int64(tc.fundingAmount), - PkScript: pkScript, - }) - btcFundingTx := btcutil.NewTx(fundingTx) - - prevOut := &wire.OutPoint{ - Hash: *btcFundingTx.Hash(), - Index: 0, - } - fundingTxIn := wire.NewTxIn(prevOut, nil, nil) - - chanType := taprootChanType - - // Channel configurations using all deterministic keys. - remoteCfg := channeldb.ChannelConfig{ - ChannelStateBounds: channeldb.ChannelStateBounds{ - MaxPendingAmount: lnwire.NewMSatFromSatoshis( - tc.fundingAmount, - ), - ChanReserve: 0, - MinHTLC: 0, - MaxAcceptedHtlcs: input.MaxHTLCNumber / 2, - }, - CommitmentParams: channeldb.CommitmentParams{ - DustLimit: tc.dustLimit, - CsvDelay: tc.localCsvDelay, - }, - MultiSigKey: keychain.KeyDescriptor{ - PubKey: tc.remoteFundingPrivkey.PubKey(), - }, - PaymentBasePoint: keychain.KeyDescriptor{ - PubKey: tc.remotePaymentBasepointSecret.PubKey(), - }, - HtlcBasePoint: keychain.KeyDescriptor{ - PubKey: tc.remoteHtlcBasepointSecret.PubKey(), - }, - DelayBasePoint: keychain.KeyDescriptor{ - PubKey: tc.remotePaymentBasepointSecret.PubKey(), - }, - RevocationBasePoint: keychain.KeyDescriptor{ - PubKey: tc.remoteRevocationBasepointSecret.PubKey(), - }, - } - localCfg := channeldb.ChannelConfig{ - ChannelStateBounds: channeldb.ChannelStateBounds{ - MaxPendingAmount: lnwire.NewMSatFromSatoshis( - tc.fundingAmount, - ), - ChanReserve: 0, - MinHTLC: 0, - MaxAcceptedHtlcs: input.MaxHTLCNumber / 2, - }, - CommitmentParams: channeldb.CommitmentParams{ - DustLimit: tc.dustLimit, - CsvDelay: tc.localCsvDelay, - }, - MultiSigKey: keychain.KeyDescriptor{ - PubKey: tc.localFundingPrivkey.PubKey(), - }, - PaymentBasePoint: keychain.KeyDescriptor{ - PubKey: tc.localPaymentBasepointSecret.PubKey(), - }, - HtlcBasePoint: keychain.KeyDescriptor{ - PubKey: tc.localHtlcBasepointSecret.PubKey(), - }, - DelayBasePoint: keychain.KeyDescriptor{ - PubKey: tc.localDelayedPaymentBasepointSecret.PubKey(), - }, - RevocationBasePoint: keychain.KeyDescriptor{ - PubKey: tc.localPaymentBasepointSecret.PubKey(), - }, - } - - // Create mock producers for deterministic revocation secrets. - remotePreimageProducer := &mockProducer{ - secret: chainhash.Hash(tc.localPerCommitSecret), - } - remoteCommitPoint := input.ComputeCommitmentPoint( - tc.localPerCommitSecret[:], - ) - - localPreimageProducer := &mockProducer{ - secret: chainhash.Hash(tc.localPerCommitSecret), - } - localCommitPoint := input.ComputeCommitmentPoint( - tc.localPerCommitSecret[:], - ) - - // Create temporary databases. - dbRemote := channeldb.OpenForTesting(t, t.TempDir()) - dbLocal := channeldb.OpenForTesting(t, t.TempDir()) - - // Create initial commitment transactions. - feePerKw := chainfee.SatPerKWeight(feeRate) - commitWeight := lntypes.WeightUnit(input.AnchorCommitWeight) - commitFee := feePerKw.FeeForWeight(commitWeight) - anchorAmt := btcutil.Amount(2 * AnchorSize) //nolint:unconvert - - remoteCommitTx, localCommitTx, err := CreateCommitmentTxns( - remoteBalance, localBalance-commitFee, - &remoteCfg, &localCfg, remoteCommitPoint, - localCommitPoint, *fundingTxIn, chanType, true, 0, - ) - require.NoError(t, err) - - var commitHeight = tc.commitHeight - 1 - - remoteCommit := channeldb.ChannelCommitment{ - CommitHeight: commitHeight, - LocalBalance: lnwire.NewMSatFromSatoshis(remoteBalance), - RemoteBalance: lnwire.NewMSatFromSatoshis( - localBalance - commitFee - anchorAmt, - ), - CommitFee: commitFee, - FeePerKw: btcutil.Amount(feePerKw), - CommitTx: remoteCommitTx, - CommitSig: testSigBytes, - } - localCommit := channeldb.ChannelCommitment{ - CommitHeight: commitHeight, - LocalBalance: lnwire.NewMSatFromSatoshis( - localBalance - commitFee - anchorAmt, - ), - RemoteBalance: lnwire.NewMSatFromSatoshis(remoteBalance), - CommitFee: commitFee, - FeePerKw: btcutil.Amount(feePerKw), - CommitTx: localCommitTx, - CommitSig: testSigBytes, - } - - shortChanID := lnwire.NewShortChanIDFromInt(0xdeadbeef) - - remoteChannelState := &chanstate.OpenChannel{ - LocalChanCfg: remoteCfg, - RemoteChanCfg: localCfg, - IdentityPub: tc.remoteFundingPrivkey.PubKey(), - FundingOutpoint: *prevOut, - ShortChannelID: shortChanID, - ChanType: chanType, - IsInitiator: false, - Capacity: tc.fundingAmount, - RemoteCurrentRevocation: localCommitPoint, - RevocationProducer: remotePreimageProducer, - RevocationStore: shachain.NewRevocationStore(), - LocalCommitment: remoteCommit, - RemoteCommitment: remoteCommit, - Db: dbRemote.ChannelStateDB(), - FundingTxn: fundingTx, - } - localChannelState := &chanstate.OpenChannel{ - LocalChanCfg: localCfg, - RemoteChanCfg: remoteCfg, - IdentityPub: tc.localFundingPrivkey.PubKey(), - FundingOutpoint: *prevOut, - ShortChannelID: shortChanID, - ChanType: chanType, - IsInitiator: true, - Capacity: tc.fundingAmount, - RemoteCurrentRevocation: remoteCommitPoint, - RevocationProducer: localPreimageProducer, - RevocationStore: shachain.NewRevocationStore(), - LocalCommitment: localCommit, - RemoteCommitment: localCommit, - Db: dbLocal.ChannelStateDB(), - FundingTxn: fundingTx, - } - - // Create mock signers with all deterministic keys. The funding key must - // be at index 0 because the MusigSessionManager's key fetcher always - // returns Privkeys[0] as the MuSig2 signing key. - localKeys := []*btcec.PrivateKey{ - tc.localFundingPrivkey, - tc.localPaymentBasepointSecret, - tc.localDelayedPaymentBasepointSecret, - tc.localHtlcBasepointSecret, - } - remoteKeys := []*btcec.PrivateKey{ - tc.remoteFundingPrivkey, - tc.remoteRevocationBasepointSecret, - tc.remotePaymentBasepointSecret, - tc.remoteHtlcBasepointSecret, - } - - // Wrap the mock signers with bip340Signer so that taproot script - // spend signatures (used for HTLC second-level transactions) use - // BIP-340 standard nonce derivation instead of RFC6979. This makes - // the HTLC signatures deterministic and reproducible across different - // Schnorr implementations (e.g. libsecp256k1 vs btcd). - localSigner := newBIP340Signer( - input.NewMockSigner(localKeys, nil), localKeys, - ) - remoteSigner := newBIP340Signer( - input.NewMockSigner(remoteKeys, nil), remoteKeys, - ) - - // Derive deterministic signing rand for JIT nonces so MuSig2 - // signatures are reproducible across runs. - localRandHash := sha256.Sum256( - append(tc.seed, []byte("local-signing-rand")...), - ) - remoteRandHash := sha256.Sum256( - append(tc.seed, []byte("remote-signing-rand")...), - ) - - auxSigner := NewDefaultAuxSignerMock(t) - remotePool := NewSigPool(1, remoteSigner) - channelRemote, err := NewLightningChannel( - remoteSigner, remoteChannelState, remotePool, - WithLeafStore(&MockAuxLeafStore{}), - WithAuxSigner(auxSigner), - WithCustomSigningRand(bytes.NewReader(remoteRandHash[:])), - ) - require.NoError(t, err) - require.NoError(t, remotePool.Start()) - - localPool := NewSigPool(1, localSigner) - channelLocal, err := NewLightningChannel( - localSigner, localChannelState, localPool, - WithLeafStore(&MockAuxLeafStore{}), - WithAuxSigner(auxSigner), - WithCustomSigningRand(bytes.NewReader(localRandHash[:])), - ) - require.NoError(t, err) - require.NoError(t, localPool.Start()) - - // Create state hint obfuscator. - obfuscator := createStateHintObfuscator(remoteChannelState) - err = SetStateNumHint(remoteCommitTx, commitHeight, obfuscator) - require.NoError(t, err) - err = SetStateNumHint(localCommitTx, commitHeight, obfuscator) - require.NoError(t, err) - - // Initialize the databases. - addr := &net.TCPAddr{ - IP: net.ParseIP("127.0.0.1"), - Port: 18556, - } - require.NoError(t, channelRemote.channelState.SyncPending(addr, 101)) - - addr = &net.TCPAddr{ - IP: net.ParseIP("127.0.0.1"), - Port: 18555, - } - require.NoError(t, channelLocal.channelState.SyncPending(addr, 101)) - - // Initialize revocation windows and musig nonces. - err = initRevocationWindows(channelRemote, channelLocal) - require.NoError(t, err) - - t.Cleanup(func() { - dbLocal.Close() - dbRemote.Close() - - require.NoError(t, remotePool.Stop()) - require.NoError(t, localPool.Stop()) - }) - - return channelRemote, channelLocal -} - -// taprootTransactionTestCases defines the set of transaction test cases. -var taprootTransactionTestCases = []struct { - name string - localBalance lnwire.MilliSatoshi - remoteBalance lnwire.MilliSatoshi - feePerKw btcutil.Amount - dustLimit btcutil.Amount - useTestHtlcs bool -}{ - { - name: "simple commitment tx with no HTLCs", - localBalance: 7_000_000_000, - remoteBalance: 3_000_000_000, - feePerKw: 15_000, - useTestHtlcs: false, - }, - { - name: "commitment tx with five HTLCs untrimmed", - localBalance: 6_988_000_000, - remoteBalance: 3_000_000_000, - feePerKw: 644, - useTestHtlcs: true, - }, - { - name: "commitment tx with some HTLCs trimmed", - localBalance: 6_988_000_000, - remoteBalance: 3_000_000_000, - feePerKw: 644, - dustLimit: 2500, - useTestHtlcs: true, - }, -} - -// generateTransactionVectors generates all transaction test vectors. -func (tc *taprootTestContext) generateTransactionVectors() []TransactionTestCase { //nolint:ll - t := tc.t - var results []TransactionTestCase - - for _, testCase := range taprootTransactionTestCases { - // Override dust limit if specified in the test case. - origDust := tc.dustLimit - if testCase.dustLimit != 0 { - tc.dustLimit = testCase.dustLimit - } - - // Compute spendable balances by adding back in-flight HTLCs. - remoteBalance := testCase.remoteBalance - localBalance := testCase.localBalance - if testCase.useTestHtlcs { - for _, htlc := range testHtlcsSet1 { - if htlc.incoming { - remoteBalance += htlc.amount - } else { - localBalance += htlc.amount - } - } - } - - // Verify balances add up to channel capacity. - require.EqualValues(t, - lnwire.NewMSatFromSatoshis(tc.fundingAmount), - remoteBalance+localBalance, - ) - - remoteChannel, localChannel := createTaprootTestChannelsForVectors( //nolint:ll - tc, testCase.feePerKw, - remoteBalance.ToSatoshis(), - localBalance.ToSatoshis(), - ) - - // Add HTLCs if needed. - var hash160map map[[20]byte]lntypes.Preimage - if testCase.useTestHtlcs { - hash160map = addTestHtlcs( - t, remoteChannel, localChannel, - testHtlcsSet1, - ) - } - - // Execute commit dance. - localNewCommit, err := localChannel.SignNextCommitment(ctxb) - require.NoError(t, err) - - // Discard the local JIT signing nonce (used for remote's - // commitment, not local's). - localChannel.musigSessions.RemoteSession.lastSigningSecNonce() - - // Capture local's verification nonce for local's own - // commitment. This is the nonce local contributes to the MuSig2 - // session for the commitment tx stored in the test vector - // (which is local's commitment, obtained via ForceClose). We - // must capture it BEFORE ReceiveNewCommitment finalizes the - // local session. - localVerifNonce := localChannel.musigSessions.LocalSession.VerificationNonce() //nolint:ll - localNonceHex := hex.EncodeToString( - localVerifNonce.PubNonce[:], - ) - localSecNonceHex := hex.EncodeToString( - localVerifNonce.SecNonce[:], - ) - - err = remoteChannel.ReceiveNewCommitment( - localNewCommit.CommitSigs, - ) - require.NoError(t, err) - - revMsg, _, _, err := remoteChannel.RevokeCurrentCommitment() - require.NoError(t, err) - - _, _, err = localChannel.ReceiveRevocation(revMsg) - require.NoError(t, err) - - remoteNewCommit, err := remoteChannel.SignNextCommitment(ctxb) - require.NoError(t, err) - - // Capture the remote secret nonce from the musig session. - remoteSecNonceBytes := remoteChannel.musigSessions.RemoteSession.lastSigningSecNonce().UnwrapOrFail(t) //nolint:ll - remoteSecNonceHex := hex.EncodeToString( - remoteSecNonceBytes[:], - ) - - // Capture the remote partial signature and nonce from the - // musig2 partial sig (not CommitSig which is zero for - // taproot channels). - remotePartialSig := remoteNewCommit.PartialSig.UnwrapOrFailV(t) - sigBytes := remotePartialSig.Sig.Bytes() - remoteSigHex := hex.EncodeToString(sigBytes[:]) - remoteNonceHex := hex.EncodeToString( - remotePartialSig.Nonce[:], - ) - - err = localChannel.ReceiveNewCommitment( - remoteNewCommit.CommitSigs, - ) - require.NoError(t, err) - - _, _, _, err = localChannel.RevokeCurrentCommitment() - require.NoError(t, err) - - // Force close to get the commitment transaction. - forceCloseSum, err := localChannel.ForceClose() - require.NoError(t, err) - - var txBytes bytes.Buffer - require.NoError(t, forceCloseSum.CloseTx.Serialize(&txBytes)) - - // Collect HTLC resolution transactions. - var htlcDescs []HtlcDesc - if testCase.useTestHtlcs { - resolutions := forceCloseSum.ContractResolutions.UnwrapOrFail(t) //nolint:ll - htlcResolutions := resolutions.HtlcResolutions - - // Build a map from commitment tx output index to - // the second-level transaction and remote sig. - // HtlcSigs are sorted by output index (BIP 69), - // so we collect all HTLC output indices, sort - // them, and map each to the correct sig. - type htlcEntry struct { - outputIdx uint32 - tx *wire.MsgTx - } - var allHtlcs []htlcEntry - - for _, r := range htlcResolutions.IncomingHTLCs { - successTx := r.SignedSuccessTx - - // Complete the witness with the preimage. - // Witness layout for HTLC-success: - // [0]=remoteSig [1]=localSig - // [2]=preimage [3]=script - // [4]=controlBlock - // - // Parse the success script to extract the - // RIPEMD160 hash using the script tokenizer - // rather than hardcoded offsets. - script := successTx.TxIn[0].Witness[3] - payHash := extractHash160FromScript(t, script) - preimage := hash160map[payHash] - successTx.TxIn[0].Witness[2] = preimage[:] - - allHtlcs = append(allHtlcs, htlcEntry{ - outputIdx: r.HtlcPoint().Index, - tx: successTx, - }) - } - for _, r := range htlcResolutions.OutgoingHTLCs { - allHtlcs = append(allHtlcs, htlcEntry{ - outputIdx: r.HtlcPoint().Index, - tx: r.SignedTimeoutTx, - }) - } - - // Sort by output index to match HtlcSigs ordering. - sort.Slice(allHtlcs, func(a, b int) bool { - return allHtlcs[a].outputIdx < allHtlcs[b].outputIdx //nolint:ll - }) - - require.Equal(t, - len(allHtlcs), - len(remoteNewCommit.HtlcSigs), - "htlc sig count mismatch", - ) - - for i, entry := range allHtlcs { - sigHex := hex.EncodeToString( - remoteNewCommit.HtlcSigs[i].ToSignatureBytes(), //nolint:ll - ) - - var b bytes.Buffer - err := entry.tx.Serialize(&b) - require.NoError(t, err) - - htlcDescs = append(htlcDescs, HtlcDesc{ - RemotePartialSigHex: sigHex, - ResolutionTxHex: hex.EncodeToString( - b.Bytes(), - ), - }) - } - } - - // Build the HTLC input list. - var htlcInputs []HtlcInput - if testCase.useTestHtlcs { - for _, h := range testHtlcsSet1 { - htlcInputs = append(htlcInputs, HtlcInput{ - Incoming: h.incoming, - AmountMsat: uint64(h.amount), - Expiry: h.expiry, - Preimage: h.preimage, - }) - } - } - - result := TransactionTestCase{ - Name: testCase.name, - LocalBalanceMsat: uint64(testCase.localBalance), - RemoteBalanceMsat: uint64(testCase.remoteBalance), - FeePerKw: int64(testCase.feePerKw), - Htlcs: htlcInputs, - LocalSecNonce: localSecNonceHex, - RemoteSecNonce: remoteSecNonceHex, - LocalNonce: localNonceHex, - RemoteNonce: remoteNonceHex, - RemotePartialSig: remoteSigHex, - ExpectedCommitmentTxHex: hex.EncodeToString( - txBytes.Bytes(), - ), - HtlcDescs: htlcDescs, - } - if testCase.dustLimit != 0 { - result.DustLimitSatoshis = int64(testCase.dustLimit) - } - - results = append(results, result) - - // Restore dust limit. - tc.dustLimit = origDust - } - - return results -} - -// TestTaprootVectors either generates or verifies taproot test vectors -// depending on the -generate-taproot-vectors flag. -func TestTaprootVectors(t *testing.T) { - if *generateTaprootVectors { - t.Log("Generating taproot test vectors...") - generateAndWriteTaprootVectors(t) - return - } - - t.Log("Verifying taproot test vectors...") - verifyTaprootVectors(t) -} - -// generateAndWriteTaprootVectors generates all taproot test vectors and writes -// them to the JSON file. -func generateAndWriteTaprootVectors(t *testing.T) { - tc := newTaprootTestContext(t) - - vectors := TaprootTestVectors{ - Params: tc.generateParams(), - Scripts: tc.generateScriptVectors(), - Transactions: tc.generateTransactionVectors(), - } - - jsonData, err := json.MarshalIndent(vectors, "", " ") - require.NoError(t, err) - - err = os.WriteFile(taprootVectorFile, jsonData, 0644) - require.NoError(t, err) - - t.Logf("Wrote taproot test vectors to %s (%d bytes)", - taprootVectorFile, len(jsonData)) -} - -// verifyTaprootVectors reads the stored test vectors and verifies them by -// regenerating all values from the seed. -func verifyTaprootVectors(t *testing.T) { - jsonData, err := os.ReadFile(taprootVectorFile) - require.NoError(t, err, "test vectors file not found, run with "+ - "-generate-taproot-vectors first") - - var stored TaprootTestVectors - err = json.Unmarshal(jsonData, &stored) - require.NoError(t, err) - - tc := newTaprootTestContext(t) - - // Verify params. - t.Run("params", func(t *testing.T) { - params := tc.generateParams() - require.Equal(t, stored.Params, params) - }) - - // Verify script vectors. - t.Run("scripts", func(t *testing.T) { - scripts := tc.generateScriptVectors() - - t.Run("funding", func(t *testing.T) { - require.Equal(t, - stored.Scripts.Funding.CombinedKey, - scripts.Funding.CombinedKey, - ) - require.Equal(t, - stored.Scripts.Funding.PkScript, - scripts.Funding.PkScript, - ) - }) - - t.Run("to_local", func(t *testing.T) { - require.Equal(t, - stored.Scripts.ToLocal, scripts.ToLocal, - ) - }) - - t.Run("to_remote", func(t *testing.T) { - require.Equal(t, - stored.Scripts.ToRemote, scripts.ToRemote, - ) - }) - - t.Run("local_anchor", func(t *testing.T) { - require.Equal(t, - stored.Scripts.LocalAnchor, - scripts.LocalAnchor, - ) - }) - - t.Run("remote_anchor", func(t *testing.T) { - require.Equal(t, - stored.Scripts.RemoteAnchor, - scripts.RemoteAnchor, - ) - }) - - t.Run("offered_htlc_local_commit", func(t *testing.T) { - require.Equal(t, - stored.Scripts.OfferedHtlcLocalCommit, - scripts.OfferedHtlcLocalCommit, - ) - }) - - t.Run("offered_htlc_remote_commit", func(t *testing.T) { - require.Equal(t, - stored.Scripts.OfferedHtlcRemoteCommit, - scripts.OfferedHtlcRemoteCommit, - ) - }) - - t.Run("accepted_htlc_local_commit", func(t *testing.T) { - require.Equal(t, - stored.Scripts.AcceptedHtlcLocalCommit, - scripts.AcceptedHtlcLocalCommit, - ) - }) - - t.Run("accepted_htlc_remote_commit", func(t *testing.T) { - require.Equal(t, - stored.Scripts.AcceptedHtlcRemoteCommit, - scripts.AcceptedHtlcRemoteCommit, - ) - }) - - t.Run("second_level_htlc_success", func(t *testing.T) { - require.Equal(t, - stored.Scripts.SecondLevelHtlcSuccess, - scripts.SecondLevelHtlcSuccess, - ) - }) - - t.Run("second_level_htlc_timeout", func(t *testing.T) { - require.Equal(t, - stored.Scripts.SecondLevelHtlcTimeout, - scripts.SecondLevelHtlcTimeout, - ) - }) - }) - - // Verify transaction vectors. - t.Run("transactions", func(t *testing.T) { - txVectors := tc.generateTransactionVectors() - require.Equal(t, len(stored.Transactions), len(txVectors)) - - for i, storedTx := range stored.Transactions { - genTx := txVectors[i] - t.Run(storedTx.Name, func(t *testing.T) { - require.Equal(t, - storedTx.ExpectedCommitmentTxHex, - genTx.ExpectedCommitmentTxHex, - "commitment tx mismatch", - ) - require.Equal(t, - storedTx.RemotePartialSig, - genTx.RemotePartialSig, - "remote partial sig mismatch", - ) - require.Equal(t, - len(storedTx.HtlcDescs), - len(genTx.HtlcDescs), - "htlc desc count mismatch", - ) - for j, storedHtlc := range storedTx.HtlcDescs { - require.Equal(t, - storedHtlc.ResolutionTxHex, - genTx.HtlcDescs[j].ResolutionTxHex, //nolint:ll - fmt.Sprintf( - "htlc %d resolution "+ - "tx mismatch", j, //nolint:ll - ), - ) - } - }) - } - }) - - // Verify signatures cryptographically as a third party would. - t.Run("signature_verification", func(t *testing.T) { - fundingPkScript, err := hex.DecodeString( - stored.Scripts.Funding.PkScript, - ) - require.NoError(t, err) - - for _, storedTx := range stored.Transactions { - t.Run(storedTx.Name, func(t *testing.T) { - verifyCommitmentTxSig( - t, storedTx, fundingPkScript, - tc.fundingAmount, - ) - }) - } - }) - - // Replay MuSig2 signing from the secret nonces and private keys - // to verify that partial signatures are independently reproducible. - t.Run("musig2_partial_sig_replay", func(t *testing.T) { - fundingPkScript, err := hex.DecodeString( - stored.Scripts.Funding.PkScript, - ) - require.NoError(t, err) - - for _, storedTx := range stored.Transactions { - t.Run(storedTx.Name, func(t *testing.T) { - verifyMusig2PartialSigs( - t, storedTx, fundingPkScript, - tc.fundingAmount, - tc.localFundingPrivkey, - tc.remoteFundingPrivkey, - ) - }) - } - }) -} - -// verifyMusig2PartialSigs replays MuSig2 signing from the secret nonces and -// private keys in the test vectors, verifying that the partial signatures can -// be independently reproduced and that the combined signature matches the -// witness in the commitment transaction. -func verifyMusig2PartialSigs(t *testing.T, txCase TransactionTestCase, - fundingPkScript []byte, fundingAmt btcutil.Amount, - localPrivKey, remotePrivKey *btcec.PrivateKey) { - - t.Helper() - - // Decode secret nonces (97 bytes each). - localSecNonceBytes, err := hex.DecodeString(txCase.LocalSecNonce) - require.NoError(t, err) - require.Len(t, localSecNonceBytes, musig2.SecNonceSize) - - remoteSecNonceBytes, err := hex.DecodeString(txCase.RemoteSecNonce) - require.NoError(t, err) - require.Len(t, remoteSecNonceBytes, musig2.SecNonceSize) - - var localSecNonce, remoteSecNonce [musig2.SecNonceSize]byte - copy(localSecNonce[:], localSecNonceBytes) - copy(remoteSecNonce[:], remoteSecNonceBytes) - - // Decode public nonces (66 bytes each). - localPubNonceBytes, err := hex.DecodeString(txCase.LocalNonce) - require.NoError(t, err) - require.Len(t, localPubNonceBytes, musig2.PubNonceSize) - - remotePubNonceBytes, err := hex.DecodeString(txCase.RemoteNonce) - require.NoError(t, err) - require.Len(t, remotePubNonceBytes, musig2.PubNonceSize) - - var localPubNonce, remotePubNonce [musig2.PubNonceSize]byte - copy(localPubNonce[:], localPubNonceBytes) - copy(remotePubNonce[:], remotePubNonceBytes) - - // Aggregate the public nonces. - combinedNonce, err := musig2.AggregateNonces( - [][musig2.PubNonceSize]byte{localPubNonce, remotePubNonce}, - ) - require.NoError(t, err) - - // Deserialize the commitment transaction and compute the sighash. - commitTxBytes, err := hex.DecodeString( - txCase.ExpectedCommitmentTxHex, - ) - require.NoError(t, err) - - commitTx := wire.NewMsgTx(2) - err = commitTx.Deserialize(bytes.NewReader(commitTxBytes)) - require.NoError(t, err) - - prevOutFetcher := txscript.NewCannedPrevOutputFetcher( - fundingPkScript, int64(fundingAmt), - ) - sigHash, err := txscript.CalcTaprootSignatureHash( - txscript.NewTxSigHashes(commitTx, prevOutFetcher), - txscript.SigHashDefault, commitTx, 0, prevOutFetcher, - ) - require.NoError(t, err) - - var sigHashMsg [32]byte - copy(sigHashMsg[:], sigHash) - - pubKeys := []*btcec.PublicKey{ - localPrivKey.PubKey(), remotePrivKey.PubKey(), - } - signOpts := []musig2.SignOption{ - musig2.WithSortedKeys(), - musig2.WithBip86SignTweak(), - } - - // Replay the remote partial signature using the secret nonce. - remotePartialSig, err := musig2.Sign( - remoteSecNonce, remotePrivKey, combinedNonce, - pubKeys, sigHashMsg, signOpts..., - ) - require.NoError(t, err) - - // Verify the replayed remote partial sig matches the stored value. - storedRemoteSigBytes, err := hex.DecodeString( - txCase.RemotePartialSig, - ) - require.NoError(t, err) - - replayedSigBytes := remotePartialSig.S.Bytes() - require.Equal(t, - storedRemoteSigBytes, - replayedSigBytes[:], - "replayed remote partial sig does not match stored value", - ) - - t.Logf("remote partial sig replayed and matched successfully") - - // Also replay the local partial signature. - localPartialSig, err := musig2.Sign( - localSecNonce, localPrivKey, combinedNonce, - pubKeys, sigHashMsg, signOpts..., - ) - require.NoError(t, err) - - // Verify the local partial sig against its public nonce. - valid := localPartialSig.Verify( - localPubNonce, combinedNonce, pubKeys, - localPrivKey.PubKey(), sigHashMsg, signOpts..., - ) - require.True(t, valid, "local partial sig verification failed") - - t.Logf("local partial sig replayed and verified successfully") - - // Combine both partial signatures into the final Schnorr sig using - // the Session API, which handles nonce aggregation internally. - localCtx, err := musig2.NewContext( - localPrivKey, true, - musig2.WithKnownSigners(pubKeys), - musig2.WithBip86TweakCtx(), - ) - require.NoError(t, err) - - localNonces := &musig2.Nonces{ - PubNonce: localPubNonce, - SecNonce: localSecNonce, - } - localSession, err := localCtx.NewSession( - musig2.WithPreGeneratedNonce(localNonces), - ) - require.NoError(t, err) - - allNoncesKnown, err := localSession.RegisterPubNonce(remotePubNonce) - require.NoError(t, err) - require.True(t, allNoncesKnown) - - // Sign with local to advance the session state, then feed in - // remote's partial sig to combine. - _, err = localSession.Sign(sigHashMsg) - require.NoError(t, err) - - haveAll, err := localSession.CombineSig(remotePartialSig) - require.NoError(t, err) - require.True(t, haveAll) - - finalSig := localSession.FinalSig() - require.NotNil(t, finalSig) - - // The commitment tx witness should contain the 64-byte Schnorr sig. - require.GreaterOrEqual(t, len(commitTx.TxIn[0].Witness), 1, - "commitment tx has no witness") - - witnessSig := commitTx.TxIn[0].Witness[0] - require.Len(t, witnessSig, 64, - "witness sig should be 64 bytes") - - require.Equal(t, - finalSig.Serialize(), - witnessSig, - "combined MuSig2 sig does not match commitment tx witness", - ) - - t.Logf("combined MuSig2 signature matches commitment tx witness") -} - -// extractHash160FromScript uses the script tokenizer to find and extract the -// 20-byte RIPEMD160 payment hash from an HTLC success script. The script -// contains: OP_SIZE 32 OP_EQUALVERIFY OP_HASH160 <20-byte-hash> OP_EQUALVERIFY -// followed by checksig operations. -func extractHash160FromScript(t *testing.T, script []byte) [20]byte { - t.Helper() - - tokenizer := txscript.MakeScriptTokenizer(0, script) - for tokenizer.Next() { - if tokenizer.Opcode() == txscript.OP_HASH160 { - // The next token should be the 20-byte push data. - require.True(t, tokenizer.Next(), - "expected data push after OP_HASH160") - - data := tokenizer.Data() - require.Len(t, data, 20, - "expected 20-byte hash after OP_HASH160") - - var hash160 [20]byte - copy(hash160[:], data) - - return hash160 - } - } - - require.NoError(t, tokenizer.Err(), "script tokenizer error") - t.Fatal("OP_HASH160 not found in script") - - var zero [20]byte - - return zero -} - -// verifyCommitmentTxSig performs third-party verification of the commitment -// transaction and all HTLC resolution transactions by executing the taproot -// script verification engine against the provided witnesses. -func verifyCommitmentTxSig(t *testing.T, txCase TransactionTestCase, - fundingPkScript []byte, fundingAmt btcutil.Amount) { - - // Deserialize the commitment transaction. - commitTxBytes, err := hex.DecodeString( - txCase.ExpectedCommitmentTxHex, - ) - require.NoError(t, err) - - commitTx := wire.NewMsgTx(2) - err = commitTx.Deserialize(bytes.NewReader(commitTxBytes)) - require.NoError(t, err) - - // Verify the commitment tx witness against the funding output. - prevOutFetcher := txscript.NewCannedPrevOutputFetcher( - fundingPkScript, int64(fundingAmt), - ) - sigHashes := txscript.NewTxSigHashes(commitTx, prevOutFetcher) - - vm, err := txscript.NewEngine( - fundingPkScript, commitTx, 0, - txscript.StandardVerifyFlags, nil, - sigHashes, int64(fundingAmt), prevOutFetcher, - ) - require.NoError(t, err, "failed to create script engine for "+ - "commitment tx") - - err = vm.Execute() - require.NoError(t, err, "commitment tx signature verification "+ - "failed") - - t.Logf("commitment tx signature verified successfully") - - // Verify each HTLC resolution transaction against its commitment - // output. - for i, htlcDesc := range txCase.HtlcDescs { - htlcTxBytes, err := hex.DecodeString( - htlcDesc.ResolutionTxHex, - ) - require.NoError(t, err) - - htlcTx := wire.NewMsgTx(2) - err = htlcTx.Deserialize(bytes.NewReader(htlcTxBytes)) - require.NoError(t, err) - - // The HTLC tx spends from the commitment tx. Find the - // output it references. - prevOutIdx := htlcTx.TxIn[0].PreviousOutPoint.Index - require.Less(t, int(prevOutIdx), len(commitTx.TxOut), - "HTLC tx references invalid output index") - - prevOut := commitTx.TxOut[prevOutIdx] - htlcPrevFetcher := txscript.NewCannedPrevOutputFetcher( - prevOut.PkScript, prevOut.Value, - ) - htlcSigHashes := txscript.NewTxSigHashes( - htlcTx, htlcPrevFetcher, - ) - - htlcVM, err := txscript.NewEngine( - prevOut.PkScript, htlcTx, 0, - txscript.StandardVerifyFlags, nil, - htlcSigHashes, prevOut.Value, - htlcPrevFetcher, - ) - require.NoError(t, err, - fmt.Sprintf("failed to create script engine for "+ - "HTLC resolution tx %d", i)) - - err = htlcVM.Execute() - require.NoError(t, err, - fmt.Sprintf("HTLC resolution tx %d signature "+ - "verification failed", i)) - - t.Logf("HTLC resolution tx %d signature verified "+ - "successfully", i) - } -} diff --git a/lnwallet/test/test_interface.go b/lnwallet/test/test_interface.go index c9210bc1f..853d68a41 100644 --- a/lnwallet/test/test_interface.go +++ b/lnwallet/test/test_interface.go @@ -14,18 +14,17 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcd/mempool" "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/walletdb" @@ -246,7 +245,7 @@ func loadTestCredits(miner *rpctest.Harness, w *lnwallet.LightningWallet, return err } expectedBalance += btcutil.Amount(int64(satoshiPerOutput) * int64(numOutputs)) - addrs := make([]address.Address, 0, numOutputs) + addrs := make([]btcutil.Address, 0, numOutputs) for i := 0; i < numOutputs; i++ { // Grab a fresh address from the wallet to house this output. walletAddr, err := w.NewAddress( @@ -1589,8 +1588,8 @@ func testTransactionSubscriptions(miner *rpctest.Harness, // scriptFromKey creates a P2WKH script from the given pubkey. func scriptFromKey(pubkey *btcec.PublicKey) ([]byte, error) { - pubkeyHash := address.Hash160(pubkey.SerializeCompressed()) - keyAddr, err := address.NewAddressWitnessPubKeyHash( + pubkeyHash := btcutil.Hash160(pubkey.SerializeCompressed()) + keyAddr, err := btcutil.NewAddressWitnessPubKeyHash( pubkeyHash, &chaincfg.RegressionNetParams, ) if err != nil { @@ -2038,8 +2037,8 @@ func testSignOutputUsingTweaks(r *rpctest.Harness, // Using the given key for the current iteration, we'll // generate a regular p2wkh from that. - pubkeyHash := address.Hash160(tweakedKey.SerializeCompressed()) - keyAddr, err := address.NewAddressWitnessPubKeyHash(pubkeyHash, + pubkeyHash := btcutil.Hash160(tweakedKey.SerializeCompressed()) + keyAddr, err := btcutil.NewAddressWitnessPubKeyHash(pubkeyHash, &chaincfg.RegressionNetParams) if err != nil { t.Fatalf("unable to create addr: %v", err) @@ -3003,93 +3002,24 @@ func waitForMempoolTx(r *rpctest.Harness, txid *chainhash.Hash) error { return nil } -// waitForWalletSync blocks until the LightningWallet's chain backend has fully -// caught up to the best block known by the mining harness, or the timeout -// elapses. It performs two distinct checks that correspond to different layers -// of the sync pipeline: -// -// Layer 1 — header sync (ChainIO.GetBestBlock): -// BtcWallet.GetBestBlock delegates straight to the underlying chain client -// (e.g. the neutrino ChainService). This reflects whether the P2P layer has -// downloaded block *headers* up to the miner's tip. An 80-byte header is all -// that is needed here; no transaction data is involved yet. -// -// Layer 2 — full wallet sync (IsSynced): -// IsSynced checks three internal btcwallet conditions in sequence: -// -// a. ChainSynced() — a boolean flag set only when the chain client fires -// its dedicated "synced" notification to btcwallet. The header may -// already be present at layer 1 while this flag is still false. -// b. waddrmgr SyncedTo height — after neutrino matches a compact filter -// and fetches the full block, btcwallet walks every transaction, -// updates balances and UTXO state, and writes results to the DB. Only -// once that write completes does the address manager advance its sync -// height. On slower backends (e.g. postgres) this step is the common -// bottleneck. -// c. A timestamp sanity check (irrelevant in regtest). -// -// After a reorg, btcwallet must first *undo* the invalidated blocks (reverse -// UTXO/balance changes) and then *redo* the new chain, doubling the DB write -// pressure compared with a normal forward sync. -// -// When this function times out, the error message identifies which layer was -// stuck so the reader knows where to look: -// - "height lag" → stuck at layer 1; neutrino has not fetched the header. -// - "IsSynced() never returned true" → layer 1 is fine; the bottleneck is -// the chain-sync notification or the address-manager transaction walk. func waitForWalletSync(r *rpctest.Harness, w *lnwallet.LightningWallet) error { var ( synced bool err error bestHash, knownHash *chainhash.Hash bestHeight, knownHeight int32 - - // heightsMatched tracks whether the last poll found matching - // heights so the timeout message can distinguish between a - // layer-1 header lag and a layer-2 internal-state lag. - heightsMatched bool ) - - // Use a single ticker rather than time.Tick inside the loop; the - // latter leaks a goroutine on every iteration because the returned - // channel is never stopped. - ticker := time.NewTicker(100 * time.Millisecond) - defer ticker.Stop() - - // Neutrino syncs via P2P (header announcements + compact filter - // fetches) which is slower than a direct RPC client, especially - // under load (e.g. postgres backend). Use a generous timeout to - // avoid spurious failures. - timeout := time.After(2 * time.Minute) + timeout := time.After(30 * time.Second) for !synced { + // Do a short wait select { case <-timeout: - // Report which sync layer was stuck so a future - // investigator knows where to look. - if !heightsMatched { - return fmt.Errorf("timeout waiting for wallet "+ - "sync: chain tip at height=%d "+ - "hash=%v, wallet ChainIO at "+ - "height=%d hash=%v — stuck at "+ - "layer 1 (header/P2P sync)", - bestHeight, bestHash, - knownHeight, knownHash) - } - - return fmt.Errorf("timeout waiting for wallet "+ - "sync: heights matched at %d but "+ - "IsSynced() never returned true — "+ - "stuck at layer 2 (chain-sync "+ - "notification or address-manager "+ - "transaction walk, check "+ - "ChainSynced() and waddrmgr "+ - "SyncedTo height)", - bestHeight) - case <-ticker.C: + return errors.New("timeout after 30s") + case <-time.Tick(100 * time.Millisecond): } - // Layer 1 check: verify the chain backend has downloaded the - // header for the miner's current tip. + // Check whether the chain source of the wallet is caught up to + // the harness it's supposed to be catching up to. bestHash, bestHeight, err = r.Client.GetBestBlock() if err != nil { return err @@ -3098,8 +3028,7 @@ func waitForWalletSync(r *rpctest.Harness, w *lnwallet.LightningWallet) error { if err != nil { return err } - heightsMatched = knownHeight == bestHeight - if !heightsMatched { + if knownHeight != bestHeight { continue } if *knownHash != *bestHash { @@ -3108,8 +3037,7 @@ func waitForWalletSync(r *rpctest.Harness, w *lnwallet.LightningWallet) error { knownHash) } - // Layer 2 check: verify btcwallet has processed all - // transactions in the new blocks and considers itself synced. + // Check for synchronization. synced, _, err = w.IsSynced() if err != nil { return err @@ -3164,7 +3092,7 @@ func testSingleFunderExternalFundingTx(miner *rpctest.Harness, LocalAmt: btcutil.Amount(chanAmt), MinConfs: 1, FeeRate: 253, - ChangeAddr: func() (address.Address, error) { + ChangeAddr: func() (btcutil.Address, error) { return alice.NewAddress( lnwallet.WitnessPubKey, true, lnwallet.DefaultAccountName, @@ -3211,7 +3139,7 @@ func testSingleFunderExternalFundingTx(miner *rpctest.Harness, LocalAmt: btcutil.Amount(chanAmt), MinConfs: 1, FeeRate: 253, - ChangeAddr: func() (address.Address, error) { + ChangeAddr: func() (btcutil.Address, error) { return bob.NewAddress( lnwallet.WitnessPubKey, true, lnwallet.DefaultAccountName, @@ -3388,9 +3316,7 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver, if err != nil { t.Fatalf("unable to make neutrino: %v", err) } - if err := aliceChain.Start(t.Context()); err != nil { - t.Fatalf("unable to start neutrino: %v", err) - } + aliceChain.Start() defer aliceChain.Stop() aliceClient = chain.NewNeutrinoClient( netParams, aliceChain, @@ -3420,9 +3346,7 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver, if err != nil { t.Fatalf("unable to make neutrino: %v", err) } - if err := bobChain.Start(t.Context()); err != nil { - t.Fatalf("unable to start neutrino: %v", err) - } + bobChain.Start() defer bobChain.Stop() bobClient = chain.NewNeutrinoClient( netParams, bobChain, @@ -3541,6 +3465,8 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver, // wallet state after each step. for _, walletTest := range walletTests { + walletTest := walletTest + testName := fmt.Sprintf("%v/%v:%v", walletType, backEnd, walletTest.name) success := t.Run(testName, func(t *testing.T) { diff --git a/lnwallet/test_utils.go b/lnwallet/test_utils.go index 22f8861aa..738558e22 100644 --- a/lnwallet/test_utils.go +++ b/lnwallet/test_utils.go @@ -12,11 +12,10 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -309,7 +308,7 @@ func CreateTestChannels(t *testing.T, chanType channeldb.ChannelType, binary.BigEndian.Uint64(chanIDBytes[:]), ) - aliceChannelState := &chanstate.OpenChannel{ + aliceChannelState := &channeldb.OpenChannel{ LocalChanCfg: aliceCfg, RemoteChanCfg: bobCfg, IdentityPub: aliceKeys[0].PubKey(), @@ -324,9 +323,10 @@ func CreateTestChannels(t *testing.T, chanType channeldb.ChannelType, LocalCommitment: aliceLocalCommit, RemoteCommitment: aliceRemoteCommit, Db: dbAlice.ChannelStateDB(), + Packager: channeldb.NewChannelPackager(shortChanID), FundingTxn: testTx, } - bobChannelState := &chanstate.OpenChannel{ + bobChannelState := &channeldb.OpenChannel{ LocalChanCfg: bobCfg, RemoteChanCfg: aliceCfg, IdentityPub: bobKeys[0].PubKey(), @@ -341,6 +341,7 @@ func CreateTestChannels(t *testing.T, chanType channeldb.ChannelType, LocalCommitment: bobLocalCommit, RemoteCommitment: bobRemoteCommit, Db: dbBob.ChannelStateDB(), + Packager: channeldb.NewChannelPackager(shortChanID), } // If the channel type has a tapscript root, then we'll also specify diff --git a/lnwallet/test_vectors_taproot.json b/lnwallet/test_vectors_taproot.json deleted file mode 100644 index 26686d3f3..000000000 --- a/lnwallet/test_vectors_taproot.json +++ /dev/null @@ -1,309 +0,0 @@ -{ - "params": { - "seed": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", - "funding_amount_satoshis": 10000000, - "dust_limit_satoshis": 354, - "csv_delay": 144, - "commit_height": 42, - "nums_point": "02dca094751109d0bd055d03565874e8276dd53e926b44e3bd1bb6bf4bc130a279", - "keys": { - "local_funding_privkey": "20ae2d254ab29afd3dcbf8744a5b88d06070f55a4bd5532483a093ac4db91277", - "local_funding_pubkey": "03b7203dec7c13896b6ff1f58b24f84458c441720a12b5a57426397e22f0a8c78b", - "remote_funding_privkey": "f0c5500a9dbd7cdcd46ced7bdeb937d4dcbf90f9b9357626e7ee54ab024c3df0", - "remote_funding_pubkey": "02956e6845a6f346f97c5e028c0f8ab38a76b0124fd7184deab60f682b3e657fdb", - "local_payment_basepoint_secret": "277975b5b081a9cbc4834e066d7bb494e4fde4f7637257dd3d312a0ae7cb7754", - "local_payment_basepoint": "03955b6085296cbd2447a1dde0f7e273e19b83e83de1814993b1517aaf193b7f33", - "remote_payment_basepoint_secret": "f1cd3a5ca44b52baf4eacb849fbf06e75aace97477b8bfe31d2b814dbbb562b1", - "remote_payment_basepoint": "03595f2ef2a51d2250a21077dbea4a7fc3ce550f10676996bf63719e2a71d1f4c9", - "local_delayed_payment_basepoint_secret": "83ccf0b638c514db5ebefdc6cbf901505e2bb20edb2bb7248ce1a51523325f9b", - "local_delayed_payment_basepoint": "02ae68d8ff4c59864c03a42bbff6c07f9ae18047e0daa9bc40d07c410f9a0f7899", - "remote_revocation_basepoint_secret": "36c4175b91cff9731a63d1472b5b1c4cf3e7b688e87d5fb806b2e8350484e68d", - "remote_revocation_basepoint": "02c354121ef71922b5cb32fa685c08ac0014b558f96e28f383c45eb28b7da264c3", - "local_htlc_basepoint_secret": "786eb5024e4851bea3ddc6e40036c81b1efcf50eeed440eedefe5245bde6fc14", - "local_htlc_basepoint": "033ce88bf3c8333e242996964ac91ee7cd945bfe4c49668ea10f3211f3d418fbc8", - "remote_htlc_basepoint_secret": "51c9b6cf8279def85e3925bc8f16fc0ff100ee7b03ce7c954149ca29c834b684", - "remote_htlc_basepoint": "02932dfbf6737001e3c516696ae3dcd323fd91a01ce7898f7f91ab98eebacc323e", - "local_per_commit_secret": "037b507180b3985cea6396d6a70987cea11ccd05fde49e943a3ea0fe56ee33ed", - "local_per_commit_point": "02a0f5a09017c1dec2d30dd54a25dc4037fc5a2aa3832ee3c7b58f3a88a0836287", - "derived_local_delayed_pubkey": "0315ec0138eb42f1ab4603042123988d53c854e89d1d87aa4dbb97a57482029c05", - "derived_revocation_pubkey": "03d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0", - "derived_local_htlc_pubkey": "0271e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739e", - "derived_remote_htlc_pubkey": "032deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47d", - "derived_remote_payment_pubkey": "03595f2ef2a51d2250a21077dbea4a7fc3ce550f10676996bf63719e2a71d1f4c9" - } - }, - "scripts": { - "funding": { - "funding_tx_hex": "02000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000018096980000000000225120d0ebb4909d563a7ae1213fddede4ae54132fba0ef0b97ee3f8469191fecd348e00000000", - "combined_key": "d0ebb4909d563a7ae1213fddede4ae54132fba0ef0b97ee3f8469191fecd348e", - "pkscript": "5120d0ebb4909d563a7ae1213fddede4ae54132fba0ef0b97ee3f8469191fecd348e" - }, - "to_local": { - "scripts": { - "revocation": "2015ec0138eb42f1ab4603042123988d53c854e89d1d87aa4dbb97a57482029c057520d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0ac", - "settle": "2015ec0138eb42f1ab4603042123988d53c854e89d1d87aa4dbb97a57482029c05ad029000b2" - }, - "leaf_hashes": { - "revocation": "8fcd64d212bbbf1bcec2360bbf229963240d05992fc2efb482fe6dca85b9469a", - "settle": "dbf0400e9c7c57f30b6ad0b0677e396b5a002cbf050d873c8925b966048e6a62" - }, - "tapscript_root": "b8b76c2e893ca785072f0d7393e35d5bd72adf8b7ff2a53538aa664378a38a36", - "internal_key": "02dca094751109d0bd055d03565874e8276dd53e926b44e3bd1bb6bf4bc130a279", - "output_key": "023e1fcbbd06c8a7414704612c72be9834a75d86ed85b29f0ef0c52e1950afaff3", - "pkscript": "51203e1fcbbd06c8a7414704612c72be9834a75d86ed85b29f0ef0c52e1950afaff3" - }, - "to_remote": { - "scripts": { - "settle": "20595f2ef2a51d2250a21077dbea4a7fc3ce550f10676996bf63719e2a71d1f4c9ad51b2" - }, - "leaf_hashes": { - "settle": "63ce35b16eb8f8687293d5a88c1d8ada3236843b79ca315fe9dd7c47f30f2bc9" - }, - "tapscript_root": "63ce35b16eb8f8687293d5a88c1d8ada3236843b79ca315fe9dd7c47f30f2bc9", - "internal_key": "02dca094751109d0bd055d03565874e8276dd53e926b44e3bd1bb6bf4bc130a279", - "output_key": "023609bb705034e5629aa6ec05c5ca906ac89ac08b34c4583c259521ec30174408", - "pkscript": "51203609bb705034e5629aa6ec05c5ca906ac89ac08b34c4583c259521ec30174408" - }, - "local_anchor": { - "scripts": { - "sweep": "60b2" - }, - "leaf_hashes": { - "sweep": "2b88a8f3f52386d61d5b3f2d822df659c35214d7360ed05352ad7ddc1ab03912" - }, - "tapscript_root": "2b88a8f3f52386d61d5b3f2d822df659c35214d7360ed05352ad7ddc1ab03912", - "internal_key": "0315ec0138eb42f1ab4603042123988d53c854e89d1d87aa4dbb97a57482029c05", - "output_key": "02f67ab012701705f3203d132f909a6810ef18c5da4c11d986cb50818803b8344e", - "pkscript": "5120f67ab012701705f3203d132f909a6810ef18c5da4c11d986cb50818803b8344e" - }, - "remote_anchor": { - "scripts": { - "sweep": "60b2" - }, - "leaf_hashes": { - "sweep": "2b88a8f3f52386d61d5b3f2d822df659c35214d7360ed05352ad7ddc1ab03912" - }, - "tapscript_root": "2b88a8f3f52386d61d5b3f2d822df659c35214d7360ed05352ad7ddc1ab03912", - "internal_key": "03595f2ef2a51d2250a21077dbea4a7fc3ce550f10676996bf63719e2a71d1f4c9", - "output_key": "021249c50576fdf914caa14f9221370b986df520bdbc73f57d5056a86ee03e5ac4", - "pkscript": "51201249c50576fdf914caa14f9221370b986df520bdbc73f57d5056a86ee03e5ac4" - }, - "offered_htlc_local_commit": { - "scripts": { - "success": "82012088a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dad51b2", - "timeout": "2071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac" - }, - "leaf_hashes": { - "success": "cd4b7ba74d132998f2bcea85f76082f5018e614c86f27f2631b6569c4914320f", - "timeout": "dd0bd08b3df902c399f5493a682f6c50c476c89e233ba454e89a234d2d16ffe3" - }, - "tapscript_root": "f36c8bd45002c5264cfce9944211e7bc6ea974a6b90cf99a87812d18acf28a2a", - "internal_key": "03d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0", - "output_key": "033e5c3be9f4ce7ae07c28ad5e0eb0ab617c06eeb82b8d6ef10a5bf561848df5f0", - "pkscript": "51203e5c3be9f4ce7ae07c28ad5e0eb0ab617c06eeb82b8d6ef10a5bf561848df5f0" - }, - "offered_htlc_remote_commit": { - "scripts": { - "success": "82012088a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dad51b2", - "timeout": "2071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac" - }, - "leaf_hashes": { - "success": "cd4b7ba74d132998f2bcea85f76082f5018e614c86f27f2631b6569c4914320f", - "timeout": "dd0bd08b3df902c399f5493a682f6c50c476c89e233ba454e89a234d2d16ffe3" - }, - "tapscript_root": "f36c8bd45002c5264cfce9944211e7bc6ea974a6b90cf99a87812d18acf28a2a", - "internal_key": "03d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0", - "output_key": "033e5c3be9f4ce7ae07c28ad5e0eb0ab617c06eeb82b8d6ef10a5bf561848df5f0", - "pkscript": "51203e5c3be9f4ce7ae07c28ad5e0eb0ab617c06eeb82b8d6ef10a5bf561848df5f0" - }, - "accepted_htlc_local_commit": { - "scripts": { - "success": "82012088a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dad2071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739eac", - "timeout": "2071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead51b26902f401b1" - }, - "leaf_hashes": { - "success": "69192ca730d4480044ade8741b8bd0845a32880aebaf58bc6f9186f8d2be8cbf", - "timeout": "4da43c795365bf757ed1e9656d12ea744b4cf52b01719a3ea94e6569115623f0" - }, - "tapscript_root": "1a990caa4bb0ed41ceb19e7466fcea5d9b31e3da968f348f6223201c5831d0a3", - "internal_key": "03d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0", - "output_key": "029aadbdd9aff986e5ea086cf53ae062972d33d0a5c7f5fb986dafec7fa6d7e6ea", - "pkscript": "51209aadbdd9aff986e5ea086cf53ae062972d33d0a5c7f5fb986dafec7fa6d7e6ea" - }, - "accepted_htlc_remote_commit": { - "scripts": { - "success": "82012088a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dad2071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739eac", - "timeout": "2071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead51b26902f401b1" - }, - "leaf_hashes": { - "success": "69192ca730d4480044ade8741b8bd0845a32880aebaf58bc6f9186f8d2be8cbf", - "timeout": "4da43c795365bf757ed1e9656d12ea744b4cf52b01719a3ea94e6569115623f0" - }, - "tapscript_root": "1a990caa4bb0ed41ceb19e7466fcea5d9b31e3da968f348f6223201c5831d0a3", - "internal_key": "03d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0", - "output_key": "029aadbdd9aff986e5ea086cf53ae062972d33d0a5c7f5fb986dafec7fa6d7e6ea", - "pkscript": "51209aadbdd9aff986e5ea086cf53ae062972d33d0a5c7f5fb986dafec7fa6d7e6ea" - }, - "second_level_htlc_success": { - "scripts": { - "success": "2015ec0138eb42f1ab4603042123988d53c854e89d1d87aa4dbb97a57482029c05ad029000b2" - }, - "leaf_hashes": { - "success": "dbf0400e9c7c57f30b6ad0b0677e396b5a002cbf050d873c8925b966048e6a62" - }, - "tapscript_root": "dbf0400e9c7c57f30b6ad0b0677e396b5a002cbf050d873c8925b966048e6a62", - "internal_key": "03d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0", - "output_key": "02df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba0", - "pkscript": "5120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba0" - }, - "second_level_htlc_timeout": { - "scripts": { - "success": "2015ec0138eb42f1ab4603042123988d53c854e89d1d87aa4dbb97a57482029c05ad029000b2" - }, - "leaf_hashes": { - "success": "dbf0400e9c7c57f30b6ad0b0677e396b5a002cbf050d873c8925b966048e6a62" - }, - "tapscript_root": "dbf0400e9c7c57f30b6ad0b0677e396b5a002cbf050d873c8925b966048e6a62", - "internal_key": "03d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0", - "output_key": "02df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba0", - "pkscript": "5120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba0" - } - }, - "transactions": [ - { - "name": "simple commitment tx with no HTLCs", - "local_balance_msat": 7000000000, - "remote_balance_msat": 3000000000, - "fee_per_kw": 15000, - "htlcs": null, - "local_sec_nonce": "22a453171ba4a634da1addcf660d63d8e23fb63169a2a7206f4e23290e4cc59bb3c3011fe3c31cb4f1192a2df56c2e52350ce0a82060fadf2404af9f81652c5f03b7203dec7c13896b6ff1f58b24f84458c441720a12b5a57426397e22f0a8c78b", - "remote_sec_nonce": "ccdaea6955c7bd9fcb082dc67e32809a5bdd3a3edf770cbb990116c45f4b006e4b56aa81f8e555d6bd1906b159af16d0ac352690ccff6f6a99c43842ac2606ca02956e6845a6f346f97c5e028c0f8ab38a76b0124fd7184deab60f682b3e657fdb", - "local_nonce": "025f2272ea289c5fe9d52d411f5a50a6d4882341bf0ecb201d5675850a2ba0b09d025bc489cf67752134ba81f8d7f1146d7455baf3190de75a6a661e2405212991a9", - "remote_nonce": "02d324627074522af8cf4287caf1e073a3493550b99aed2697e58f476ec402e272039c25fc616207e15917b7145cefcb4c9c702580baf255597d2fa115564a74a130", - "remote_partial_sig": "3fa93659d4c2d590eadbd422595a37597ed58607e026a91f4e6e19329134a931", - "expected_commitment_tx_hex": "020000000001015474cba49124ab0c4327c244bb2907059585c4af3fa5f3469701534120fec0170000000000c5fe1780044a010000000000002251201249c50576fdf914caa14f9221370b986df520bdbc73f57d5056a86ee03e5ac44a01000000000000225120f67ab012701705f3203d132f909a6810ef18c5da4c11d986cb50818803b8344ec0c62d00000000002251203609bb705034e5629aa6ec05c5ca906ac89ac08b34c4583c259521ec3017440874946a00000000002251203e1fcbbd06c8a7414704612c72be9834a75d86ed85b29f0ef0c52e1950afaff30140a4a9eb512a2f4094efdd2c566f1f20cc8a6e2c307a4a44cc3f9fea7fa147dd7038f1b048aa43fa0b4009175c1c37c37b96c01058541f9e1b61110fce4e831d9f55dc1920", - "htlc_descs": null - }, - { - "name": "commitment tx with five HTLCs untrimmed", - "local_balance_msat": 6988000000, - "remote_balance_msat": 3000000000, - "fee_per_kw": 644, - "htlcs": [ - { - "incoming": true, - "amount_msat": 1000000, - "expiry": 500, - "preimage": "0000000000000000000000000000000000000000000000000000000000000000" - }, - { - "incoming": true, - "amount_msat": 2000000, - "expiry": 501, - "preimage": "0101010101010101010101010101010101010101010101010101010101010101" - }, - { - "incoming": false, - "amount_msat": 2000000, - "expiry": 502, - "preimage": "0202020202020202020202020202020202020202020202020202020202020202" - }, - { - "incoming": false, - "amount_msat": 3000000, - "expiry": 503, - "preimage": "0303030303030303030303030303030303030303030303030303030303030303" - }, - { - "incoming": true, - "amount_msat": 4000000, - "expiry": 504, - "preimage": "0404040404040404040404040404040404040404040404040404040404040404" - } - ], - "local_sec_nonce": "22a453171ba4a634da1addcf660d63d8e23fb63169a2a7206f4e23290e4cc59bb3c3011fe3c31cb4f1192a2df56c2e52350ce0a82060fadf2404af9f81652c5f03b7203dec7c13896b6ff1f58b24f84458c441720a12b5a57426397e22f0a8c78b", - "remote_sec_nonce": "8c40a8ab26f1bd7d58010e991c72b0f102df6d0f050df28f56cefdafb9d06479958ea0a72180184e338b1b77fdf20690d5d980aac5c34cf123b189e373dba33e02956e6845a6f346f97c5e028c0f8ab38a76b0124fd7184deab60f682b3e657fdb", - "local_nonce": "025f2272ea289c5fe9d52d411f5a50a6d4882341bf0ecb201d5675850a2ba0b09d025bc489cf67752134ba81f8d7f1146d7455baf3190de75a6a661e2405212991a9", - "remote_nonce": "038a4018a074b5ddfc1424551e871bd739259c4e209f47177eb67c70cfbdb1e57c03c75684ad3a42d8c86a3eddb83b8160d67ef272078b44f21a8f889ee25e2459d3", - "remote_partial_sig": "46efde50f08c128aa6472bbd50ea156fb9bde7b013f5e042f700729c94053613", - "expected_commitment_tx_hex": "020000000001015474cba49124ab0c4327c244bb2907059585c4af3fa5f3469701534120fec0170000000000c5fe1780094a010000000000002251201249c50576fdf914caa14f9221370b986df520bdbc73f57d5056a86ee03e5ac44a01000000000000225120f67ab012701705f3203d132f909a6810ef18c5da4c11d986cb50818803b8344ee8030000000000002251209ce82cd1b1f6f975049d58019a7145a3ec9680079969cf929d7d2c4bc9b30637d0070000000000002251208937f8afbc80cf4ba773f1adc3d63ea26259f80f5a3ba622211906d2e7e6e23dd007000000000000225120bf9ae94dda9b5b88485cc67a966ec946b237d19626916dee034b789ebd7fd5fcb80b0000000000002251208fe2e1306e414e896dfd879475b5c1a6a01d4e79b32c0544aa185ccb73c392aaa00f000000000000225120d93389ba5cdde8570d3ba73487ff7fc9f8c3816645009e42110fe5239f5a3e62c0c62d00000000002251203609bb705034e5629aa6ec05c5ca906ac89ac08b34c4583c259521ec30174408b3996a00000000002251203e1fcbbd06c8a7414704612c72be9834a75d86ed85b29f0ef0c52e1950afaff301409dfe3b178022d975e4b86bd1f04bccfc7576363dbaf58f2ac682136ad89cbeb1a1d07eca1e0bc547b5c5c1133214565e5dfdc230bc7d4736aa7e1be3fb8269d355dc1920", - "htlc_descs": [ - { - "remote_partial_sig_hex": "fc97f7cfb97e1e48792b0ed174704cd98c886d368ede5adeb6288f0b350c8f88d076488973d0656da72d072e03eb9eb8b31737850894bca0924d8fdd63ddea69", - "resolution_tx_hex": "02000000000101ec4c0a34c981864f9badcb8383bbe42ec6b32e68c2aa1a7c7c2e8422adde673f02000000000100000001e803000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba00541fc97f7cfb97e1e48792b0ed174704cd98c886d368ede5adeb6288f0b350c8f88d076488973d0656da72d072e03eb9eb8b31737850894bca0924d8fdd63ddea6983405bd66541625ba684bb6ff0def3c542bb88d5195a760cb616a5d09dec6823238ccdde18c99d4f3634394d9c23d0e198babc609f464d9b4552664ca5d3b985758f2000000000000000000000000000000000000000000000000000000000000000005f82012088a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc6882071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac41c0d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0e5e8fd071b9ade6367122afbd8acacc1a6727ddb6d478612af30827590027e0300000000" - }, - { - "remote_partial_sig_hex": "c99d8d1ca721d1d4b9796cde49698fd46bb3faddd2bc7215de291ae9bd85cd9b2ab9bf40e36c752b698098b0986abe92b3cb21de8d6a388d9a6988d468e981e5", - "resolution_tx_hex": "02000000000101ec4c0a34c981864f9badcb8383bbe42ec6b32e68c2aa1a7c7c2e8422adde673f03000000000100000001d007000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba00541c99d8d1ca721d1d4b9796cde49698fd46bb3faddd2bc7215de291ae9bd85cd9b2ab9bf40e36c752b698098b0986abe92b3cb21de8d6a388d9a6988d468e981e583406a52ec691b47371892192e7222a76d978c18038b3f12479977366a09d2db91e66091332158d8f0b4e125394e1bf2d7ab40ab564c49e0686ffad56eadd6a1297e2001010101010101010101010101010101010101010101010101010101010101015f82012088a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce239882071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac41c0d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0127d1790461eff920f14ba7cff2093c44b8a83e6f0a959fa60e04cf8c435cf4b00000000" - }, - { - "remote_partial_sig_hex": "b361ed8b70a09f4128fe610db649abcaafbc9b8600136e6f1b9735b4781cd65681faa32c29fef2a1485cc569c655b31b8eb75ab593749dd7f7d788fdec489133", - "resolution_tx_hex": "02000000000101ec4c0a34c981864f9badcb8383bbe42ec6b32e68c2aa1a7c7c2e8422adde673f04000000000100000001d007000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba00441b361ed8b70a09f4128fe610db649abcaafbc9b8600136e6f1b9735b4781cd65681faa32c29fef2a1485cc569c655b31b8eb75ab593749dd7f7d788fdec48913383403e94dbe70a26fc19b0d0053dfbf3da4c3e75dacf4abe337866645ffe21834a152f2edf7e520f352f104eef17fefa5a1535d692012cc766d36bd54f658c5c797f442071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac41c1d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a040b30263c4d7cd1fa6544e8bc8cd9efe857d7b5fd691c958936c3a2e0df2232ef6010000" - }, - { - "remote_partial_sig_hex": "9d1193d1bae8793ec502aa705a43a140e9f4c46607b4b8b414f76923c746a81cc320ffef6575f4c1ba55795770b41885dc90ed19025522805b1d0f5f5d440ebd", - "resolution_tx_hex": "02000000000101ec4c0a34c981864f9badcb8383bbe42ec6b32e68c2aa1a7c7c2e8422adde673f05000000000100000001b80b000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba004419d1193d1bae8793ec502aa705a43a140e9f4c46607b4b8b414f76923c746a81cc320ffef6575f4c1ba55795770b41885dc90ed19025522805b1d0f5f5d440ebd8340b149f17aab590815fbe4b19796dfc98aa857e81cdc1d15c82d64a2b626f40af8ae2a884197d74071e9b5648c7b5380db1084ac8ed2a8ed54ca9d589e6b5f7d8a442071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac41c0d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a064c44563d1bd58fa25c5c3ca7303c75849b6b3d91bf2e28f27068db4319b4c2ff7010000" - }, - { - "remote_partial_sig_hex": "58338a2d50a03ea615f0e0e295b12413308e6381412c14d276c4b39a2f1d17193a689cbe2ad0d33a63344cff36daa2edc94aa566d743ad6526ddb5cdb5819ea0", - "resolution_tx_hex": "02000000000101ec4c0a34c981864f9badcb8383bbe42ec6b32e68c2aa1a7c7c2e8422adde673f06000000000100000001a00f000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba0054158338a2d50a03ea615f0e0e295b12413308e6381412c14d276c4b39a2f1d17193a689cbe2ad0d33a63344cff36daa2edc94aa566d743ad6526ddb5cdb5819ea08340efcfcda15da18a2791a80700b2716d2386ea67649a5e43a505dbe22bd110cc2352e558d62e7abd7afe7640d2ee414affd88555378bfe05aa28d0fa289e8d1b542004040404040404040404040404040404040404040404040404040404040404045f82012088a91418bc1a114ccf9c052d3d23e28d3b0a9d12274342882071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac41c1d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a06c3390c812b2596986592f02c7f22e4f857fb553805ff9ac1c2bda361c47c3fb00000000" - } - ] - }, - { - "name": "commitment tx with some HTLCs trimmed", - "local_balance_msat": 6988000000, - "remote_balance_msat": 3000000000, - "fee_per_kw": 644, - "dust_limit_satoshis": 2500, - "htlcs": [ - { - "incoming": true, - "amount_msat": 1000000, - "expiry": 500, - "preimage": "0000000000000000000000000000000000000000000000000000000000000000" - }, - { - "incoming": true, - "amount_msat": 2000000, - "expiry": 501, - "preimage": "0101010101010101010101010101010101010101010101010101010101010101" - }, - { - "incoming": false, - "amount_msat": 2000000, - "expiry": 502, - "preimage": "0202020202020202020202020202020202020202020202020202020202020202" - }, - { - "incoming": false, - "amount_msat": 3000000, - "expiry": 503, - "preimage": "0303030303030303030303030303030303030303030303030303030303030303" - }, - { - "incoming": true, - "amount_msat": 4000000, - "expiry": 504, - "preimage": "0404040404040404040404040404040404040404040404040404040404040404" - } - ], - "local_sec_nonce": "22a453171ba4a634da1addcf660d63d8e23fb63169a2a7206f4e23290e4cc59bb3c3011fe3c31cb4f1192a2df56c2e52350ce0a82060fadf2404af9f81652c5f03b7203dec7c13896b6ff1f58b24f84458c441720a12b5a57426397e22f0a8c78b", - "remote_sec_nonce": "8c5bd39820b3e20d65bf72e530078bf5e8ba057c1654d0a15778a0d3225e0233eb8d7b1d3574e40e57a2cd12dc3b33193c0ac17545564df3ff88021f3c30033702956e6845a6f346f97c5e028c0f8ab38a76b0124fd7184deab60f682b3e657fdb", - "local_nonce": "025f2272ea289c5fe9d52d411f5a50a6d4882341bf0ecb201d5675850a2ba0b09d025bc489cf67752134ba81f8d7f1146d7455baf3190de75a6a661e2405212991a9", - "remote_nonce": "03fd9fa808377737b105f7df362ed513e3946f2bb49dfbca5c2ce2be138ff0607502e4c73701eae82afa7a01993f62321648a6235ef0c958e35766a9e53e4eaf9d34", - "remote_partial_sig": "3e454598e0661188da0e4cf1b806b13c627adb7ab38bab27418cc976769771c3", - "expected_commitment_tx_hex": "020000000001015474cba49124ab0c4327c244bb2907059585c4af3fa5f3469701534120fec0170000000000c5fe1780064a010000000000002251201249c50576fdf914caa14f9221370b986df520bdbc73f57d5056a86ee03e5ac44a01000000000000225120f67ab012701705f3203d132f909a6810ef18c5da4c11d986cb50818803b8344eb80b0000000000002251208fe2e1306e414e896dfd879475b5c1a6a01d4e79b32c0544aa185ccb73c392aaa00f000000000000225120d93389ba5cdde8570d3ba73487ff7fc9f8c3816645009e42110fe5239f5a3e62c0c62d00000000002251203609bb705034e5629aa6ec05c5ca906ac89ac08b34c4583c259521ec30174408009b6a00000000002251203e1fcbbd06c8a7414704612c72be9834a75d86ed85b29f0ef0c52e1950afaff30140dfd9604ad0b4fed040382f4829e20a0ef4b5558d0189178a5e9c26a8b4bd8547fd563ee60884daf91c8a1ae71e08b452a93ad3bdf5c572f642d42606736dc5f755dc1920", - "htlc_descs": [ - { - "remote_partial_sig_hex": "7058caa9a075344e095d95736bb6a00c5b7259f439e60e7e1c6cd641a20d6a25c13d2930ebe51a69ffea78daa12b07237179c8b0584c75499f7bfa4f5afa9776", - "resolution_tx_hex": "020000000001018c47e10e0d210da9e50d73b1adda7a598f79ced25806dd0fa6807bb78780dbbb02000000000100000001b80b000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba004417058caa9a075344e095d95736bb6a00c5b7259f439e60e7e1c6cd641a20d6a25c13d2930ebe51a69ffea78daa12b07237179c8b0584c75499f7bfa4f5afa97768340d3a4bcb6c20446364506f75e1d1c4da8ad5ae2f6e5a7ad544965e48f28c02a4e250b0a958a4b2d9d78632771b8f5ea739b63bbf98a6c7511a2456c0d78844642442071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac41c0d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a064c44563d1bd58fa25c5c3ca7303c75849b6b3d91bf2e28f27068db4319b4c2ff7010000" - }, - { - "remote_partial_sig_hex": "818132325cc01e441876615f30c3df5c27df1722db49005dff3856226a41f34c776f98a35dd74fe2f863ea23ac611e06d0aac890227346b1fc53dc62c789d372", - "resolution_tx_hex": "020000000001018c47e10e0d210da9e50d73b1adda7a598f79ced25806dd0fa6807bb78780dbbb03000000000100000001a00f000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba00541818132325cc01e441876615f30c3df5c27df1722db49005dff3856226a41f34c776f98a35dd74fe2f863ea23ac611e06d0aac890227346b1fc53dc62c789d37283408e0222c158069b3f19ec0798b3516b79cc2e6293abb1120d54c41c7bae3aa2579c5c3586ad68275a9f99ac14e9a1e48b9c3c7100fd834585264a943c523bea642004040404040404040404040404040404040404040404040404040404040404045f82012088a91418bc1a114ccf9c052d3d23e28d3b0a9d12274342882071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac41c1d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a06c3390c812b2596986592f02c7f22e4f857fb553805ff9ac1c2bda361c47c3fb00000000" - } - ] - } - ] -} \ No newline at end of file diff --git a/lnwallet/transactions.go b/lnwallet/transactions.go index 397f2413a..da86650bc 100644 --- a/lnwallet/transactions.go +++ b/lnwallet/transactions.go @@ -5,8 +5,8 @@ import ( "fmt" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" ) diff --git a/lnwallet/transactions_test.go b/lnwallet/transactions_test.go index 1ae21d887..38131eaa7 100644 --- a/lnwallet/transactions_test.go +++ b/lnwallet/transactions_test.go @@ -16,12 +16,11 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -227,6 +226,7 @@ func TestCommitmentAndHTLCTransactions(t *testing.T) { } for _, set := range vectorSets { + set := set var testCases []testCase @@ -237,6 +237,7 @@ func TestCommitmentAndHTLCTransactions(t *testing.T) { require.NoError(t, err) for _, test := range testCases { + test := test name := fmt.Sprintf("%s-%s", set.name, test.Name) t.Run(name, func(t *testing.T) { @@ -786,6 +787,7 @@ func TestCommitmentSpendValidation(t *testing.T) { // but we also need to support older nodes that want to open channels // with the legacy format, so we'll test spending in both scenarios. for _, tweakless := range []bool{true, false} { + tweakless := tweakless t.Run(fmt.Sprintf("tweak=%v", tweakless), func(t *testing.T) { testSpendValidation(t, tweakless) }) @@ -968,7 +970,7 @@ func createTestChannelsForVectors(tc *testContext, chanType channeldb.ChannelTyp binary.BigEndian.Uint64(chanIDBytes[:]), ) - remoteChannelState := &chanstate.OpenChannel{ + remoteChannelState := &channeldb.OpenChannel{ LocalChanCfg: remoteCfg, RemoteChanCfg: localCfg, IdentityPub: remoteDummy2.PubKey(), @@ -983,9 +985,10 @@ func createTestChannelsForVectors(tc *testContext, chanType channeldb.ChannelTyp LocalCommitment: remoteCommit, RemoteCommitment: remoteCommit, Db: dbRemote.ChannelStateDB(), + Packager: channeldb.NewChannelPackager(shortChanID), FundingTxn: tc.fundingTx.MsgTx(), } - localChannelState := &chanstate.OpenChannel{ + localChannelState := &channeldb.OpenChannel{ LocalChanCfg: localCfg, RemoteChanCfg: remoteCfg, IdentityPub: localDummy2.PubKey(), @@ -1000,6 +1003,7 @@ func createTestChannelsForVectors(tc *testContext, chanType channeldb.ChannelTyp LocalCommitment: localCommit, RemoteCommitment: localCommit, Db: dbLocal.ChannelStateDB(), + Packager: channeldb.NewChannelPackager(shortChanID), FundingTxn: tc.fundingTx.MsgTx(), } diff --git a/lnwallet/types/close_types.go b/lnwallet/types/close_types.go deleted file mode 100644 index 59d0002cd..000000000 --- a/lnwallet/types/close_types.go +++ /dev/null @@ -1,73 +0,0 @@ -package types - -import ( - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/tlv" -) - -// CloseOutput represents an output that should be included in the close -// transaction. -type CloseOutput struct { - // Amt is the amount of the output. - Amt btcutil.Amount - - // DustLimit is the dust limit for the local node. - DustLimit btcutil.Amount - - // PkScript is the script that should be used to pay to the output. - PkScript []byte - - // ShutdownRecords is the set of custom records that may result in - // extra close outputs being added. - ShutdownRecords lnwire.CustomRecords -} - -// AuxShutdownReq is used to request a set of extra custom records to include -// in the shutdown message. -type AuxShutdownReq struct { - // ChanPoint is the channel point of the channel that is being shut - // down. - ChanPoint wire.OutPoint - - // ShortChanID is the short channel ID of the channel that is being - // closed. - ShortChanID lnwire.ShortChannelID - - // Initiator is true if the local node is the initiator of the channel. - Initiator bool - - // InternalKey is the internal key for the shutdown addr. This will - // only be set for taproot shutdown addrs. - InternalKey fn.Option[btcec.PublicKey] - - // CommitBlob is the blob that was included in the last commitment. - CommitBlob fn.Option[tlv.Blob] - - // FundingBlob is the blob that was included in the funding state. - FundingBlob fn.Option[tlv.Blob] -} - -// AuxCloseDesc is used to describe the channel close that is being performed. -type AuxCloseDesc struct { - AuxShutdownReq - - // CloseFee is the closing fee to be paid for this state. - CloseFee btcutil.Amount - - // CommitFee is the fee that was paid for the last commitment. - CommitFee btcutil.Amount - - // LocalCloseOutput is the output that the local node should be paid - // to. This is None if the local party will not have an output on the - // co-op close transaction. - LocalCloseOutput fn.Option[CloseOutput] - - // RemoteCloseOutput is the output that the remote node should be paid - // to. This will be None if the remote party will not have an output on - // the co-op close transaction. - RemoteCloseOutput fn.Option[CloseOutput] -} diff --git a/lnwallet/update_log.go b/lnwallet/update_log.go index 4e4f736a7..b2b8af58d 100644 --- a/lnwallet/update_log.go +++ b/lnwallet/update_log.go @@ -95,30 +95,6 @@ func (u *updateLog) appendHtlc(pd *paymentDescriptor) { u.logIndex++ } -// appendFeeUpdate appends a fee update unless the newest fee update hasn't yet -// been committed to either commitment chain. In that case, only its fee is -// replaced. Keeping the original descriptor and log index preserves a -// contiguous update stream for persistence while avoiding redundant entries. -func (u *updateLog) appendFeeUpdate(pd *paymentDescriptor) { - for entry := u.Back(); entry != nil; entry = entry.Prev() { - update := entry.Value - if update.EntryType != FeeUpdate { - continue - } - - if update.addCommitHeights.Local == 0 && - update.addCommitHeights.Remote == 0 { - - update.Amount = pd.Amount - return - } - - break - } - - u.appendUpdate(pd) -} - // lookupHtlc attempts to look up an offered HTLC according to its offer // index. If the entry isn't found, then a nil pointer is returned. func (u *updateLog) lookupHtlc(i uint64) *paymentDescriptor { diff --git a/lnwallet/update_log_test.go b/lnwallet/update_log_test.go deleted file mode 100644 index 996590b4b..000000000 --- a/lnwallet/update_log_test.go +++ /dev/null @@ -1,214 +0,0 @@ -package lnwallet - -import ( - "testing" - - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/lnwallet/chainfee" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/stretchr/testify/require" - "pgregory.net/rapid" -) - -// TestAppendFeeUpdateReplacementSequences checks replacement behavior across -// generated sequences of fee and commitment state transitions. -func TestAppendFeeUpdateReplacementSequences(t *testing.T) { - t.Parallel() - - type feeAction struct { - fee uint32 - commitLocal bool - commitRemote bool - interleave bool - } - - actions := rapid.SliceOfN( - rapid.Custom(func(t *rapid.T) feeAction { - return feeAction{ - fee: rapid.Uint32Range(1, 10_000_000).Draw( - t, "fee", - ), - commitLocal: rapid.Bool().Draw( - t, "commit_local", - ), - commitRemote: rapid.Bool().Draw( - t, "commit_remote", - ), - interleave: rapid.Bool().Draw( - t, "interleave", - ), - } - }), 1, 200, - ) - - rapid.Check(t, func(t *rapid.T) { - log := newUpdateLog(0, 0) - committed := make(map[*paymentDescriptor]struct{}) - - for i, action := range actions.Draw(t, "actions") { - if action.interleave { - log.appendUpdate(&paymentDescriptor{ - LogIndex: log.logIndex, - EntryType: Settle, - }) - } - - feeUpdate := &paymentDescriptor{ - LogIndex: log.logIndex, - Amount: lnwire.NewMSatFromSatoshis( - btcutil.Amount(action.fee), - ), - EntryType: FeeUpdate, - } - log.appendFeeUpdate(feeUpdate) - - var currentFee *paymentDescriptor - entry := log.Back() - for entry != nil { - if entry.Value.EntryType == FeeUpdate { - currentFee = entry.Value - break - } - - entry = entry.Prev() - } - if currentFee == nil { - t.Fatal("fee update not retained") - } - if currentFee.Amount != feeUpdate.Amount { - t.Fatalf("latest fee is %v, expected %v", - currentFee.Amount, feeUpdate.Amount) - } - - if action.commitLocal { - currentFee.setCommitHeight( - lntypes.Local, uint64(i+1), - ) - } - if action.commitRemote { - currentFee.setCommitHeight( - lntypes.Remote, uint64(i+1), - ) - } - if action.commitLocal || action.commitRemote { - committed[currentFee] = struct{}{} - } - - var uncommitted int - resident := make(map[*paymentDescriptor]struct{}) - var nextLogIndex uint64 - entry = log.Front() - for entry != nil { - update := entry.Value - resident[update] = struct{}{} - if update.LogIndex != nextLogIndex { - t.Fatalf( - "non-contiguous log index: "+ - "got %d, want %d", - update.LogIndex, nextLogIndex, - ) - } - nextLogIndex++ - - if update.EntryType == FeeUpdate && - update.addCommitHeights.Local == 0 && - update.addCommitHeights.Remote == 0 { - - uncommitted++ - } - - entry = entry.Next() - } - if log.logIndex != nextLogIndex { - t.Fatalf("log index is %d, expected %d", - log.logIndex, nextLogIndex) - } - - if uncommitted > 1 { - t.Fatalf("retained %d uncommitted fee updates", - uncommitted) - } - for update := range committed { - if _, ok := resident[update]; !ok { - t.Fatal("committed fee update removed") - } - } - } - }) -} - -// TestReceiveUpdateFeeReplacement checks that consecutive fee updates retain -// the latest value until a commitment chain observes the update. -func TestReceiveUpdateFeeReplacement(t *testing.T) { - t.Parallel() - - _, bobChannel, err := CreateTestChannels( - t, channeldb.SingleFunderTweaklessBit, - ) - require.NoError(t, err) - - const numUpdates = 10_000 - for i := 1; i <= numUpdates; i++ { - require.NoError( - t, bobChannel.ReceiveUpdateFee( - chainfee.SatPerKWeight(i), - ), - ) - } - - require.Equal(t, uint64(1), bobChannel.updateLogs.Remote.logIndex) - - feeUpdates := make([]*paymentDescriptor, 0, 1) - entry := bobChannel.updateLogs.Remote.Front() - for entry != nil { - if entry.Value.EntryType == FeeUpdate { - feeUpdates = append(feeUpdates, entry.Value) - } - - entry = entry.Next() - } - - require.Len(t, feeUpdates, 1) - require.Equal( - t, int64(numUpdates), int64(feeUpdates[0].Amount.ToSatoshis()), - ) - require.Zero(t, feeUpdates[0].LogIndex) -} - -// TestAppendFeeUpdatePreservesCommitted checks that a fee update observed by -// either commitment chain isn't replaced by a later update. -func TestAppendFeeUpdatePreservesCommitted(t *testing.T) { - t.Parallel() - - log := newUpdateLog(0, 0) - first := &paymentDescriptor{ - LogIndex: log.logIndex, - EntryType: FeeUpdate, - } - log.appendFeeUpdate(first) - first.setCommitHeight(lntypes.Remote, 1) - - second := &paymentDescriptor{ - LogIndex: log.logIndex, - Amount: 2, - EntryType: FeeUpdate, - } - log.appendFeeUpdate(second) - - third := &paymentDescriptor{ - LogIndex: log.logIndex, - Amount: 3, - EntryType: FeeUpdate, - } - log.appendFeeUpdate(third) - - require.Same(t, first, log.Front().Value) - require.Same(t, second, log.Back().Value) - require.Equal(t, third.Amount, second.Amount) - require.Equal(t, uint64(2), log.logIndex) - require.Contains(t, log.updateIndex, first.LogIndex) - require.Contains(t, log.updateIndex, second.LogIndex) - require.NotContains(t, log.updateIndex, third.LogIndex) -} diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 54efab6a1..daba09925 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -10,20 +10,18 @@ import ( "sync" "sync/atomic" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/btcutil/v2/txsort" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/psbt/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/btcutil/txsort" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/wallet" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -336,7 +334,7 @@ type addCounterPartySigsMsg struct { // This channel is used to return the completed channel after the wallet // has completed all of its stages in the funding process. - completeChan chan *chanstate.OpenChannel + completeChan chan *channeldb.OpenChannel // NOTE: In order to avoid deadlocks, this channel MUST be buffered. err chan error @@ -365,7 +363,7 @@ type addSingleFunderSigsMsg struct { // This channel is used to return the completed channel after the wallet // has completed all of its stages in the funding process. - completeChan chan *chanstate.OpenChannel + completeChan chan *channeldb.OpenChannel // NOTE: In order to avoid deadlocks, this channel MUST be buffered. err chan error @@ -621,6 +619,7 @@ func (l *LightningWallet) ListUnspentWitnessFromDefaultAccount( func (l *LightningWallet) LockedOutpoints() []*wire.OutPoint { outPoints := make([]*wire.OutPoint, 0, len(l.lockedOutPoints)) for outPoint := range l.lockedOutPoints { + outPoint := outPoint outPoints = append(outPoints, &outPoint) } @@ -979,7 +978,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg MinConfs: req.MinConfs, SubtractFees: req.SubtractFees, FeeRate: req.FundingFeePerKw, - ChangeAddr: func() (address.Address, error) { + ChangeAddr: func() (btcutil.Address, error) { return l.NewAddress( TaprootPubkey, true, DefaultAccountName, ) @@ -1153,7 +1152,7 @@ func (l *LightningWallet) CurrentNumAnchorChans() (int, error) { } var numAnchors int - cntChannel := func(c *chanstate.OpenChannel) { + cntChannel := func(c *channeldb.OpenChannel) { // We skip private channels, as we assume they won't be used // for routing. if c.ChannelFlags&lnwire.FFAnnounceChannel == 0 { @@ -2602,7 +2601,7 @@ func initStateHints(commit1, commit2 *wire.MsgTx, // ValidateChannel will attempt to fully validate a newly mined channel, given // its funding transaction and existing channel state. If this method returns // an error, then the mined channel is invalid, and shouldn't be used. -func (l *LightningWallet) ValidateChannel(channelState *chanstate.OpenChannel, +func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel, fundingTx *wire.MsgTx) error { var chanOpts []ChannelOpt diff --git a/lnwire/accept_channel.go b/lnwire/accept_channel.go index c8be14793..afb2f1412 100644 --- a/lnwire/accept_channel.go +++ b/lnwire/accept_channel.go @@ -5,7 +5,7 @@ import ( "io" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/tlv" ) diff --git a/lnwire/accept_channel_test.go b/lnwire/accept_channel_test.go index b0b199ef7..87d9dc029 100644 --- a/lnwire/accept_channel_test.go +++ b/lnwire/accept_channel_test.go @@ -29,6 +29,7 @@ func TestDecodeAcceptChannel(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { priv, err := btcec.NewPrivateKey() diff --git a/lnwire/announcement_signatures.go b/lnwire/announcement_signatures.go index 29ada7008..cf8f68be5 100644 --- a/lnwire/announcement_signatures.go +++ b/lnwire/announcement_signatures.go @@ -121,10 +121,3 @@ func (a *AnnounceSignatures1) SCID() ShortChannelID { func (a *AnnounceSignatures1) ChanID() ChannelID { return a.ChannelID } - -// GossipVersion returns the gossip version that this message is part of. -// -// NOTE: this is part of the GossipMessage interface. -func (a *AnnounceSignatures1) GossipVersion() GossipVersion { - return GossipVersion1 -} diff --git a/lnwire/announcement_signatures_2.go b/lnwire/announcement_signatures_2.go index a2806f1ee..04e4c0a9f 100644 --- a/lnwire/announcement_signatures_2.go +++ b/lnwire/announcement_signatures_2.go @@ -102,13 +102,6 @@ func (a *AnnounceSignatures2) MsgType() MessageType { return MsgAnnounceSignatures2 } -// GossipVersion returns the gossip version that this message is part of. -// -// NOTE: this is part of the GossipMessage interface. -func (a *AnnounceSignatures2) GossipVersion() GossipVersion { - return GossipVersion2 -} - // SerializedSize returns the serialized size of the message in bytes. // // This is part of the lnwire.SizeableMessage interface. diff --git a/lnwire/blinded_path.go b/lnwire/blinded_path.go deleted file mode 100644 index 14e247af4..000000000 --- a/lnwire/blinded_path.go +++ /dev/null @@ -1,333 +0,0 @@ -package lnwire - -import ( - "encoding/binary" - "errors" - "fmt" - "io" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/tlv" -) - -var ( - // ErrInvalidIntroNode is returned when a blinded path's introduction - // node discriminator is not one of the spec-defined values. - ErrInvalidIntroNode = errors.New("invalid blinded-path introduction " + - "node discriminator") - - // ErrEmptyBlindedPath is returned when a blinded path has zero hops. - ErrEmptyBlindedPath = errors.New("blinded path with zero hops") -) - -// BlindedPath holds the introduction node, blinding point, and encrypted hops -// of a single blinded path. -type BlindedPath struct { - // IntroductionNode is the variant-defined introduction node for this - // blinded path. - IntroductionNode IntroductionNode - - // BlindingPoint is the blinding point for this path, used to derive the - // blinded node IDs and encrypt the hop payloads. - BlindingPoint *btcec.PublicKey - - // Hops is the ordered list of blinded hops in this path. - Hops []BlindedHop -} - -// BlindedPaths holds one or more blinded paths. -type BlindedPaths struct { - Paths []BlindedPath -} - -// BlindedHop represents a single hop in a blinded path. -type BlindedHop struct { - // BlindedNodeID is the blinded public key for this hop. - BlindedNodeID *btcec.PublicKey - - // EncryptedData is the encrypted payload for this hop. - EncryptedData []byte -} - -var ( - _ tlv.RecordProducer = (*BlindedPath)(nil) - _ tlv.RecordProducer = (*BlindedPaths)(nil) -) - -// Record returns a TLV record for a single BlindedPath at the BOLT 4 reply_path -// TLV type. Used directly by OnionMessagePayload's reply_path encoding. -func (p *BlindedPath) Record() tlv.Record { - return tlv.MakeDynamicRecord( - replyPathType, p, - func() uint64 { - return blindedPathSize(p) - }, - encodeBlindedPath, - decodeBlindedPath, - ) -} - -// blindedPathSize returns the on-wire size of a single BlindedPath. -func blindedPathSize(p *BlindedPath) uint64 { - var introLen uint64 - if p.IntroductionNode != nil { - introLen = p.IntroductionNode.encodedLen() - } - - // introduction_node (variant-defined) + blinding_point (33) + - // num_hops (1). - size := introLen + pubKeyLen + 1 - for _, h := range p.Hops { - // blinded_node_id (33) + enclen (2) + enc_data. - size += pubKeyLen + 2 + uint64(len(h.EncryptedData)) - } - - return size -} - -// encodeBlindedPath writes a single blinded path. No bytes are written if the -// path fails validation. -func encodeBlindedPath(w io.Writer, val any, buf *[8]byte) error { - p, ok := val.(*BlindedPath) - if !ok { - return fmt.Errorf("expected *BlindedPath, got %T", val) - } - - return writeBlindedPath(w, p, buf) -} - -// writeBlindedPath validates the path and writes a single blinded path to w. -func writeBlindedPath(w io.Writer, p *BlindedPath, buf *[8]byte) error { - if p.IntroductionNode == nil { - return fmt.Errorf("nil intro node") - } - - if err := p.IntroductionNode.validate(); err != nil { - return err - } - - if p.BlindingPoint == nil { - return fmt.Errorf("nil blinding point") - } - - if !p.BlindingPoint.IsOnCurve() { - return fmt.Errorf("blinding point not on curve") - } - - if len(p.Hops) == 0 { - return ErrEmptyBlindedPath - } - if len(p.Hops) > maxBlindedPathHops { - return fmt.Errorf("%d hops exceeds limit %d", len(p.Hops), - maxBlindedPathHops) - } - - if err := p.IntroductionNode.encode(w); err != nil { - return err - } - blindingBytes := p.BlindingPoint.SerializeCompressed() - if _, err := w.Write(blindingBytes); err != nil { - return err - } - - buf[0] = uint8(len(p.Hops)) - if _, err := w.Write(buf[:1]); err != nil { - return err - } - - for hIdx := range p.Hops { - if err := writeBlindedHop(w, &p.Hops[hIdx], buf); err != nil { - return fmt.Errorf("hop %d: %w", hIdx, err) - } - } - - return nil -} - -// decodeBlindedPath reads a single blinded path framed at the TLV-value level. -func decodeBlindedPath(r io.Reader, val any, buf *[8]byte, l uint64) error { - p, ok := val.(*BlindedPath) - if !ok { - return fmt.Errorf("expected *BlindedPath, got %T", val) - } - - lr := &io.LimitedReader{R: r, N: int64(l)} - - if err := readBlindedPath(lr, p, buf); err != nil { - return err - } - - if lr.N != 0 { - return fmt.Errorf("trailing %d bytes after blinded path", lr.N) - } - - return nil -} - -// readBlindedPath decodes a single blinded path from lr. -func readBlindedPath(lr *io.LimitedReader, p *BlindedPath, - buf *[8]byte) error { - - intro, err := decodeIntroductionNode(lr, buf) - if err != nil { - return err - } - p.IntroductionNode = intro - - var blindingBytes [pubKeyLen]byte - if _, err := io.ReadFull(lr, blindingBytes[:]); err != nil { - return fmt.Errorf("read blinding point: %w", err) - } - blinding, err := btcec.ParsePubKey(blindingBytes[:]) - if err != nil { - return fmt.Errorf("blinding point: %w", err) - } - p.BlindingPoint = blinding - - if _, err := io.ReadFull(lr, buf[:1]); err != nil { - return fmt.Errorf("read num_hops: %w", err) - } - numHops := int(buf[0]) - if numHops == 0 { - return ErrEmptyBlindedPath - } - - if int64(numHops)*minBlindedHopBytes > lr.N { - return fmt.Errorf("num_hops %d exceeds remaining %d bytes", - numHops, lr.N) - } - - p.Hops = make([]BlindedHop, numHops) - for i := range p.Hops { - if err := readBlindedHop(lr, &p.Hops[i], buf); err != nil { - return err - } - } - - return nil -} - -// Record returns a TLV record for BlindedPaths. -func (bp *BlindedPaths) Record() tlv.Record { - return tlv.MakeDynamicRecord( - 0, bp, - func() uint64 { - return blindedPathsSize(bp) - }, - encodeBlindedPaths, - decodeBlindedPaths, - ) -} - -// blindedPathsSize returns the on-wire size of multiple BlindedPaths. -func blindedPathsSize(bp *BlindedPaths) uint64 { - var size uint64 - for i := range bp.Paths { - size += blindedPathSize(&bp.Paths[i]) - } - - return size -} - -// encodeBlindedPaths writes the multi-path TLV value as concatenated paths. -// Fails closed under the same conditions as encodeBlindedPath. -func encodeBlindedPaths(w io.Writer, val any, buf *[8]byte) error { - bp, ok := val.(*BlindedPaths) - if !ok { - return fmt.Errorf("expected *BlindedPaths, got %T", val) - } - - for pIdx := range bp.Paths { - err := writeBlindedPath(w, &bp.Paths[pIdx], buf) - if err != nil { - return fmt.Errorf("blinded path %d: %w", pIdx, err) - } - } - - return nil -} - -// decodeBlindedPaths reads concatenated blinded paths. The LimitedReader gates -// each variable-length subfield against the bytes still on the wire, so an -// oversize hop count cannot force a large allocation before io.ReadFull -// notices the bytes are absent. -func decodeBlindedPaths(r io.Reader, val any, buf *[8]byte, l uint64) error { - bp, ok := val.(*BlindedPaths) - if !ok { - return fmt.Errorf("expected *BlindedPaths, got %T", val) - } - - lr := &io.LimitedReader{R: r, N: int64(l)} - - for lr.N > 0 { - var p BlindedPath - if err := readBlindedPath(lr, &p, buf); err != nil { - return err - } - bp.Paths = append(bp.Paths, p) - } - - return nil -} - -// writeBlindedHop emits BlindedNodeID + enclen + encrypted data. The size cap -// is checked first so no bytes hit the writer on rejection. -func writeBlindedHop(w io.Writer, h *BlindedHop, buf *[8]byte) error { - if h.BlindedNodeID == nil { - return fmt.Errorf("nil blinded node id") - } - - if !h.BlindedNodeID.IsOnCurve() { - return fmt.Errorf("blinded node id not on curve") - } - - if len(h.EncryptedData) > maxEncryptedDataLen { - return fmt.Errorf("encrypted data %d exceeds limit %d", - len(h.EncryptedData), maxEncryptedDataLen) - } - - nodeIDBytes := h.BlindedNodeID.SerializeCompressed() - if _, err := w.Write(nodeIDBytes); err != nil { - return err - } - - binary.BigEndian.PutUint16(buf[:2], uint16(len(h.EncryptedData))) - if _, err := w.Write(buf[:2]); err != nil { - return err - } - if _, err := w.Write(h.EncryptedData); err != nil { - return err - } - - return nil -} - -// readBlindedHop decodes a single blinded hop. The enclen guard against lr.N -// bounds the EncryptedData allocation. -func readBlindedHop(lr *io.LimitedReader, h *BlindedHop, buf *[8]byte) error { - var nodeBytes [pubKeyLen]byte - if _, err := io.ReadFull(lr, nodeBytes[:]); err != nil { - return fmt.Errorf("read blinded node: %w", err) - } - node, err := btcec.ParsePubKey(nodeBytes[:]) - if err != nil { - return fmt.Errorf("blinded node id: %w", err) - } - h.BlindedNodeID = node - - if _, err := io.ReadFull(lr, buf[:2]); err != nil { - return fmt.Errorf("read enclen: %w", err) - } - encLen := binary.BigEndian.Uint16(buf[:2]) - if int64(encLen) > lr.N { - return fmt.Errorf("enclen %d exceeds remaining %d", encLen, - lr.N) - } - - h.EncryptedData = make([]byte, encLen) - if _, err := io.ReadFull(lr, h.EncryptedData); err != nil { - return fmt.Errorf("read encrypted data: %w", err) - } - - return nil -} diff --git a/lnwire/blinded_path_test.go b/lnwire/blinded_path_test.go deleted file mode 100644 index 207e08a45..000000000 --- a/lnwire/blinded_path_test.go +++ /dev/null @@ -1,414 +0,0 @@ -package lnwire - -import ( - "bytes" - "testing" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/stretchr/testify/require" -) - -// validPubkeyIntro returns an on-curve PubkeyIntro plus the matching -// *btcec.PublicKey for assertions. -func validPubkeyIntro(t *testing.T) (PubkeyIntro, *btcec.PublicKey) { - t.Helper() - - priv, err := btcec.NewPrivateKey() - require.NoError(t, err) - pub := priv.PubKey() - - return PubkeyIntro{Pubkey: pub}, pub -} - -// validBlindingPoint returns an on-curve pubkey suitable for use as a -// BlindingPoint or BlindedNodeID in tests. -func validBlindingPoint(t *testing.T) *btcec.PublicKey { - t.Helper() - - priv, err := btcec.NewPrivateKey() - require.NoError(t, err) - - return priv.PubKey() -} - -// oversizeEncDataPaths returns a BlindedPaths with a single hop whose -// EncryptedData is one byte over the wire-format limit, used by the -// encode-rejects test. -func oversizeEncDataPaths(t *testing.T, intro IntroductionNode) *BlindedPaths { - t.Helper() - - return &BlindedPaths{ - Paths: []BlindedPath{{ - IntroductionNode: intro, - BlindingPoint: validBlindingPoint(t), - Hops: []BlindedHop{{ - BlindedNodeID: validBlindingPoint(t), - EncryptedData: make( - []byte, maxEncryptedDataLen+1, - ), - }}, - }}, - } -} - -// TestBlindedPathRoundTrip pins encode→decode parity across both -// IntroductionNode variants and across single- and multi-path framings, so -// concrete variant types survive the round-trip with byte-identical output. -func TestBlindedPathRoundTrip(t *testing.T) { - t.Parallel() - - pubkeyIntro, _ := validPubkeyIntro(t) - sciddirIntro := SciddirIntro{ - Direction: 0x01, - SCID: [8]byte{ - 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, - }, - } - - hop := func(payload byte) BlindedHop { - return BlindedHop{ - BlindedNodeID: validBlindingPoint(t), - EncryptedData: []byte{payload, payload ^ 0xff}, - } - } - - pubkeyPath := BlindedPath{ - IntroductionNode: pubkeyIntro, - BlindingPoint: validBlindingPoint(t), - Hops: []BlindedHop{ - hop(0xde), - hop(0xad), - }, - } - sciddirPath := BlindedPath{ - IntroductionNode: sciddirIntro, - BlindingPoint: validBlindingPoint(t), - Hops: []BlindedHop{hop(0xbe)}, - } - - tests := []struct { - name string - paths []BlindedPath - }{ - { - name: "single pubkey path", - paths: []BlindedPath{pubkeyPath}, - }, - { - name: "single sciddir path", - paths: []BlindedPath{sciddirPath}, - }, - { - name: "mixed multi-path", - paths: []BlindedPath{pubkeyPath, sciddirPath}, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - bp := &BlindedPaths{Paths: tc.paths} - - var buf bytes.Buffer - require.NoError(t, encodeBlindedPaths( - &buf, bp, new([8]byte), - )) - - var decoded BlindedPaths - err := decodeBlindedPaths( - bytes.NewReader(buf.Bytes()), &decoded, - new([8]byte), uint64(buf.Len()), - ) - require.NoError(t, err) - require.Equal(t, bp.Paths, decoded.Paths) - - // Single-path framing must round-trip too: the - // reply_path TLV carries one BlindedPath, not a list. - if len(tc.paths) == 1 { - var single bytes.Buffer - require.NoError(t, encodeBlindedPath( - &single, &tc.paths[0], new([8]byte), - )) - - var decodedSingle BlindedPath - err := decodeBlindedPath( - bytes.NewReader(single.Bytes()), - &decodedSingle, new([8]byte), - uint64(single.Len()), - ) - require.NoError(t, err) - require.Equal( - t, tc.paths[0], decodedSingle, - ) - } - }) - } -} - -// TestDecodeBlindedPathsRejects covers every malformed-input branch the -// decoder must refuse: bad discriminators, allocation bombs, and short reads. -// The catch-all is that the decoder never allocates more memory than the -// remaining wire bytes can justify. -func TestDecodeBlindedPathsRejects(t *testing.T) { - t.Parallel() - - // validKey is a 33-byte compressed SEC1 pubkey that the on-curve - // decoder accepts; reused as both intro pubkey and blinding point so - // the tests can exercise post-pubkey decode branches. - validKey := validBlindingPoint(t).SerializeCompressed() - - // hopAllocOverflow declares num_hops=255 with no hop payload. Without - // the remaining-bytes guard the decoder would make([]BlindedHop, 255) - // before io.ReadFull notices the bytes are absent. - hopAllocOverflow := func() []byte { - out := make([]byte, 0, 67) - out = append(out, validKey...) - out = append(out, validKey...) - out = append(out, 0xff) - - return out - } - - // enclenOverflow declares enclen=65535 on a hop with no payload. The - // guard against lr.N must reject before make([]byte, 65535). - enclenOverflow := func() []byte { - out := make([]byte, 0, 70) - out = append(out, validKey...) - out = append(out, validKey...) - out = append(out, 0x01) - out = append(out, validKey...) - out = append(out, 0xff, 0xff) - - return out - } - - // shortIntroPubkey truncates after the discriminator + 5 of 33 bytes - // of intro pubkey, exercising io.ReadFull's short-read error. - shortIntroPubkey := func() []byte { - return append([]byte{0x02}, bytes.Repeat([]byte{0x00}, 5)...) - } - - // shortBlindingPoint truncates after a full intro pubkey plus 5 of the - // 33 blinding-point bytes, exercising io.ReadFull's short-read path - // past the discriminator. - shortBlindingPoint := func() []byte { - out := make([]byte, 0, pubKeyLen+5) - out = append(out, validKey...) - out = append(out, bytes.Repeat([]byte{0x00}, 5)...) - - return out - } - - tests := []struct { - name string - data []byte - wantErr error - wantMsg []string - }{ - { - name: "invalid discriminator 0x04", - data: []byte{0x04}, - wantErr: ErrInvalidIntroNode, - }, - { - name: "invalid discriminator 0x05", - data: []byte{0x05}, - wantErr: ErrInvalidIntroNode, - }, - { - name: "invalid discriminator 0xff", - data: []byte{0xff}, - wantErr: ErrInvalidIntroNode, - }, - { - name: "hop alloc overflow", - data: hopAllocOverflow(), - wantMsg: []string{"num_hops", "exceeds remaining"}, - }, - { - name: "enclen alloc overflow", - data: enclenOverflow(), - wantMsg: []string{"enclen", "exceeds remaining"}, - }, - { - name: "short intro pubkey", - data: shortIntroPubkey(), - wantMsg: []string{"read intro pubkey"}, - }, - { - name: "short blinding point", - data: shortBlindingPoint(), - wantMsg: []string{"read blinding point"}, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - var bp BlindedPaths - err := decodeBlindedPaths( - bytes.NewReader(tc.data), &bp, new([8]byte), - uint64(len(tc.data)), - ) - require.Error(t, err) - - if tc.wantErr != nil { - require.ErrorIs(t, err, tc.wantErr) - } - for _, msg := range tc.wantMsg { - require.Contains(t, err.Error(), msg) - } - }) - } -} - -// TestEncodeBlindedPathsRejects pins the encoder's fail-closed guards. Any -// case here must not emit bytes — invalid input cannot be retracted from the -// wire once flushed. -func TestEncodeBlindedPathsRejects(t *testing.T) { - t.Parallel() - - validIntro, _ := validPubkeyIntro(t) - validHop := BlindedHop{BlindedNodeID: validBlindingPoint(t)} - - tests := []struct { - name string - paths *BlindedPaths - wantErr error - wantMsg []string - wantNoWrite bool - }{ - { - name: "nil intro", - paths: &BlindedPaths{ - Paths: []BlindedPath{{ - BlindingPoint: validBlindingPoint(t), - Hops: []BlindedHop{validHop}, - }}, - }, - wantMsg: []string{"nil intro node"}, - wantNoWrite: true, - }, - { - name: "nil pubkey in PubkeyIntro", - paths: &BlindedPaths{ - Paths: []BlindedPath{{ - IntroductionNode: PubkeyIntro{}, - BlindingPoint: validBlindingPoint(t), - Hops: []BlindedHop{ - validHop, - }, - }}, - }, - wantErr: ErrInvalidIntroNode, - wantNoWrite: true, - }, - { - name: "invalid sciddir direction 0x02", - paths: &BlindedPaths{ - Paths: []BlindedPath{{ - IntroductionNode: SciddirIntro{ - Direction: 0x02, - }, - BlindingPoint: validBlindingPoint(t), - Hops: []BlindedHop{validHop}, - }}, - }, - wantErr: ErrInvalidIntroNode, - wantNoWrite: true, - }, - { - name: "invalid sciddir direction 0xff", - paths: &BlindedPaths{ - Paths: []BlindedPath{{ - IntroductionNode: SciddirIntro{ - Direction: 0xff, - }, - BlindingPoint: validBlindingPoint(t), - Hops: []BlindedHop{validHop}, - }}, - }, - wantErr: ErrInvalidIntroNode, - wantNoWrite: true, - }, - { - name: "nil blinding point", - paths: &BlindedPaths{ - Paths: []BlindedPath{{ - IntroductionNode: validIntro, - Hops: []BlindedHop{ - validHop, - }, - }}, - }, - wantMsg: []string{"nil blinding point"}, - wantNoWrite: true, - }, - { - name: "zero hops", - paths: &BlindedPaths{ - Paths: []BlindedPath{{ - IntroductionNode: validIntro, - BlindingPoint: validBlindingPoint(t), - Hops: nil, - }}, - }, - wantErr: ErrEmptyBlindedPath, - wantNoWrite: true, - }, - { - name: "hop overflow", - paths: &BlindedPaths{ - Paths: []BlindedPath{{ - IntroductionNode: validIntro, - BlindingPoint: validBlindingPoint(t), - Hops: func() []BlindedHop { - hops := make([]BlindedHop, - maxBlindedPathHops+1) - pub := validBlindingPoint(t) - for i := range hops { - // Write to hop. - h := &hops[i] - h.BlindedNodeID = pub - } - - return hops - }(), - }}, - }, - wantMsg: []string{"exceeds limit"}, - wantNoWrite: true, - }, - { - name: "oversize encrypted data", - paths: oversizeEncDataPaths(t, validIntro), - wantMsg: []string{"exceeds limit"}, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - var buf bytes.Buffer - err := encodeBlindedPaths( - &buf, tc.paths, new([8]byte), - ) - require.Error(t, err) - - if tc.wantErr != nil { - require.ErrorIs(t, err, tc.wantErr) - } - for _, msg := range tc.wantMsg { - require.Contains(t, err.Error(), msg) - } - if tc.wantNoWrite { - require.Equal(t, 0, buf.Len(), - "encoder wrote bytes on fail-closed "+ - "path") - } - }) - } -} diff --git a/lnwire/bounds.go b/lnwire/bounds.go deleted file mode 100644 index 8cb79bb4f..000000000 --- a/lnwire/bounds.go +++ /dev/null @@ -1,35 +0,0 @@ -package lnwire - -import ( - "math" - - "github.com/btcsuite/btcd/btcec/v2" -) - -// BOLT 4 blinded-path field bounds. Each constant matches the format ceiling -// imposed by the spec encoding (uint8 num_hops, uint16 enclen). -const ( - // pubKeyLen aliases the upstream compressed-pubkey length for shorter - // usage in this package. - pubKeyLen = btcec.PubKeyBytesLenCompressed - - // sciddirLen is the on-wire length of a sciddir introduction node - // (1-byte direction + 8-byte SCID). - sciddirLen = 9 - - // scidLen is the byte length of a short channel ID. - scidLen = 8 - - // maxBlindedPathHops bounds the number of hops a single blinded path - // may declare. The spec encodes num_hops as a uint8, so 255 is the - // format's absolute ceiling. - maxBlindedPathHops = math.MaxUint8 - - // maxEncryptedDataLen bounds the encrypted-data field in a single - // blinded hop. The spec encodes the length as a uint16. - maxEncryptedDataLen = math.MaxUint16 - - // minBlindedHopBytes is the on-wire footprint of the smallest possible - // blinded hop: BlindedNodeID(33) + enclen(2) + 0 enc_data. - minBlindedHopBytes = pubKeyLen + 2 -) diff --git a/lnwire/channel_announcement.go b/lnwire/channel_announcement.go index bc053bc8e..05161cca8 100644 --- a/lnwire/channel_announcement.go +++ b/lnwire/channel_announcement.go @@ -4,7 +4,7 @@ import ( "bytes" "io" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" ) // ChannelAnnouncement1 message is used to announce the existence of a channel @@ -232,13 +232,6 @@ func (a *ChannelAnnouncement1) SCID() ShortChannelID { return a.ShortChannelID } -// GossipVersion returns the gossip version that this message is part of. -// -// NOTE: this is part of the GossipMessage interface. -func (a *ChannelAnnouncement1) GossipVersion() GossipVersion { - return GossipVersion1 -} - // A compile-time check to ensure that ChannelAnnouncement1 implements the // ChannelAnnouncement interface. var _ ChannelAnnouncement = (*ChannelAnnouncement1)(nil) diff --git a/lnwire/channel_announcement_2.go b/lnwire/channel_announcement_2.go index c300dbc7f..a82624a51 100644 --- a/lnwire/channel_announcement_2.go +++ b/lnwire/channel_announcement_2.go @@ -4,8 +4,8 @@ import ( "bytes" "io" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/tlv" ) @@ -307,13 +307,6 @@ func (c *ChannelAnnouncement2) SCID() ShortChannelID { return c.ShortChannelID.Val } -// GossipVersion returns the gossip version that this message is part of. -// -// NOTE: this is part of the GossipMessage interface. -func (c *ChannelAnnouncement2) GossipVersion() GossipVersion { - return GossipVersion2 -} - // A compile-time check to ensure that ChannelAnnouncement2 implements the // ChannelAnnouncement interface. var _ ChannelAnnouncement = (*ChannelAnnouncement2)(nil) diff --git a/lnwire/channel_id.go b/lnwire/channel_id.go index 22f3de413..5c9eca34f 100644 --- a/lnwire/channel_id.go +++ b/lnwire/channel_id.go @@ -6,8 +6,8 @@ import ( "io" "math" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/tlv" ) diff --git a/lnwire/channel_reestablish.go b/lnwire/channel_reestablish.go index 348b0ede8..f26a2fc5d 100644 --- a/lnwire/channel_reestablish.go +++ b/lnwire/channel_reestablish.go @@ -2,18 +2,15 @@ package lnwire import ( "bytes" - "fmt" "io" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/tlv" ) const ( - CRDynHeight tlv.Type = 20 - CRLocalNonces tlv.Type = 22 + CRDynHeight tlv.Type = 20 ) // DynHeight is a newtype wrapper to get the proper RecordProducer instance @@ -92,54 +89,12 @@ type ChannelReestablish struct { // a dynamic commitment negotiation DynHeight fn.Option[DynHeight] - // LocalNonces is an optional field that stores a map of local musig2 - // nonces, keyed by TXID. This extends the single-nonce LocalNonce - // field to support multiple in-flight splices, each of which needs - // its own nonce keyed by the relevant funding TXID. - LocalNonces OptLocalNonces - // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. ExtraData ExtraOpaqueData } -// LocalVerNonce extracts the local verification nonce from the message, -// checking the map-based LocalNonces field first (keyed by fundingTxid), then -// falling back to the legacy single LocalNonce field. This abstracts over the -// two nonce formats used by staging vs final taproot channels. -func (a *ChannelReestablish) LocalVerNonce( - fundingTxid chainhash.Hash) (Musig2Nonce, error) { - - // Prefer the map-based field (final taproot channels). - if a.LocalNonces.IsSome() { - noncesData, err := a.LocalNonces.UnwrapOrErr( - fmt.Errorf("local nonces not present"), - ) - if err != nil { - return Musig2Nonce{}, err - } - - nonce, ok := noncesData.NoncesMap[fundingTxid] - if !ok { - return Musig2Nonce{}, fmt.Errorf("missing nonce "+ - "for funding txid %v", fundingTxid) - } - - return nonce, nil - } - - // Fall back to legacy single nonce field (staging taproot channels). - nonce, err := a.LocalNonce.UnwrapOrErrV( - fmt.Errorf("remote verification nonce not sent"), - ) - if err != nil { - return Musig2Nonce{}, err - } - - return nonce, nil -} - // A compile time check to ensure ChannelReestablish implements the // lnwire.Message interface. var _ Message = (*ChannelReestablish)(nil) @@ -185,21 +140,19 @@ func (a *ChannelReestablish) Encode(w *bytes.Buffer, pver uint32) error { return err } - recordProducers := make([]tlv.RecordProducer, 0, 3) + recordProducers := make([]tlv.RecordProducer, 0, 1) a.LocalNonce.WhenSome(func(localNonce Musig2NonceTLV) { recordProducers = append(recordProducers, &localNonce) }) a.DynHeight.WhenSome(func(h DynHeight) { recordProducers = append(recordProducers, &h) }) - a.LocalNonces.WhenSome(func(ln LocalNoncesData) { - recordProducers = append(recordProducers, &ln) - }) err := EncodeMessageExtraData(&a.ExtraData, recordProducers...) if err != nil { return err } + return WriteBytes(w, a.ExtraData) } @@ -254,13 +207,11 @@ func (a *ChannelReestablish) Decode(r io.Reader, pver uint32) error { } var ( - dynHeight DynHeight - localNonce = a.LocalNonce.Zero() - localNoncesData LocalNoncesData + dynHeight DynHeight + localNonce = a.LocalNonce.Zero() ) - typeMap, err := tlvRecords.ExtractRecords( - &localNonce, &dynHeight, &localNoncesData, + &localNonce, &dynHeight, ) if err != nil { return err @@ -272,13 +223,11 @@ func (a *ChannelReestablish) Decode(r io.Reader, pver uint32) error { if val, ok := typeMap[CRDynHeight]; ok && val == nil { a.DynHeight = fn.Some(dynHeight) } - if val, ok := typeMap[CRLocalNonces]; ok && val == nil { - a.LocalNonces = SomeLocalNonces(localNoncesData) - } if len(tlvRecords) != 0 { a.ExtraData = tlvRecords } + return nil } diff --git a/lnwire/channel_update.go b/lnwire/channel_update.go index 73e8e3ad7..2dc4a11ca 100644 --- a/lnwire/channel_update.go +++ b/lnwire/channel_update.go @@ -5,7 +5,7 @@ import ( "fmt" "io" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/tlv" ) @@ -363,13 +363,6 @@ func (a *ChannelUpdate1) ForwardingPolicy() *ForwardingPolicy { } } -// GossipVersion returns the gossip version that this message is part of. -// -// NOTE: this is part of the GossipMessage interface. -func (a *ChannelUpdate1) GossipVersion() GossipVersion { - return GossipVersion1 -} - // CmpAge can be used to determine if the update is older or newer than the // passed update. It returns 1 if this update is newer, -1 if it is older, and // 0 if they are the same age. diff --git a/lnwire/channel_update_2.go b/lnwire/channel_update_2.go index 434cdb85b..7fe7670ed 100644 --- a/lnwire/channel_update_2.go +++ b/lnwire/channel_update_2.go @@ -5,8 +5,8 @@ import ( "fmt" "io" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/tlv" ) @@ -85,13 +85,6 @@ type ChannelUpdate2 struct { ExtraSignedFields } -// GossipVersion returns the gossip version that this message is part of. -// -// NOTE: this is part of the GossipMessage interface. -func (c *ChannelUpdate2) GossipVersion() GossipVersion { - return GossipVersion2 -} - // Encode serializes the target ChannelUpdate2 into the passed io.Writer // observing the protocol version specified. // @@ -380,23 +373,9 @@ func (c ChanUpdateDisableFlags) IsEnabled() bool { return c == 0 } -// String returns a human-readable representation of the disable flags. +// String returns the bitfield flags as a string. func (c ChanUpdateDisableFlags) String() string { - if c.IsEnabled() { - return "Enabled" - } - - incoming := c.IncomingDisabled() - outgoing := c.OutgoingDisabled() - - switch { - case incoming && outgoing: - return "Disabled(incoming&outgoing)" - case incoming: - return "Disabled(incoming)" - default: - return "Disabled(outgoing)" - } + return fmt.Sprintf("%08b", c) } // Record returns the tlv record for the disable flags. diff --git a/lnwire/closing_complete.go b/lnwire/closing_complete.go index ad9420fa7..7980ef1ee 100644 --- a/lnwire/closing_complete.go +++ b/lnwire/closing_complete.go @@ -2,10 +2,9 @@ package lnwire import ( "bytes" - "fmt" "io" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/tlv" ) @@ -14,7 +13,7 @@ import ( // either include both outputs, or only one of the outputs from either side. type ClosingSigs struct { // CloserNoClosee is a signature that excludes the output of the - // closee. + // clsoee. CloserNoClosee tlv.OptionalRecordT[tlv.TlvType1, Sig] // NoCloserClosee is a signature that excludes the output of the @@ -25,23 +24,6 @@ type ClosingSigs struct { CloserAndClosee tlv.OptionalRecordT[tlv.TlvType3, Sig] } -// TaprootClosingSigs houses the 3 possible taproot signatures (with nonces) -// that can be sent when attempting to complete a cooperative channel closure. -// These use PartialSigWithNonce to implement the JIT nonce pattern. -type TaprootClosingSigs struct { - // CloserNoClosee is a partial signature with nonce that excludes the - // output of the closee. Uses TLV type 5. - CloserNoClosee tlv.OptionalRecordT[tlv.TlvType5, PartialSigWithNonce] - - // NoCloserClosee is a partial signature with nonce that excludes the - // output of the closer. Uses TLV type 6. - NoCloserClosee tlv.OptionalRecordT[tlv.TlvType6, PartialSigWithNonce] - - // CloserAndClosee is a partial signature with nonce that includes - // both outputs. Uses TLV type 7. - CloserAndClosee tlv.OptionalRecordT[tlv.TlvType7, PartialSigWithNonce] -} - // ClosingComplete is sent by either side to kick off the process of obtaining // a valid signature on a c o-operative channel closure of their choice. type ClosingComplete struct { @@ -65,17 +47,8 @@ type ClosingComplete struct { LockTime uint32 // ClosingSigs houses the 3 possible signatures that can be sent. - // For non-taproot channels, these are regular signatures. ClosingSigs - // TaprootClosingSigs houses the 3 possible taproot signatures that - // can be sent. Each signature includes the nonce for the next RBF - // round (implementing the JIT nonce pattern). - // - // NOTE: This field is only populated for taproot channels. When - // present, the above ClosingSigs MUST be empty. - TaprootClosingSigs - // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. @@ -84,26 +57,19 @@ type ClosingComplete struct { // decodeClosingSigs decodes the closing sig TLV records in the passed // ExtraOpaqueData. -func decodeClosingSigs(c *ClosingSigs, - tc *TaprootClosingSigs, - tlvRecords ExtraOpaqueData) error { - // Regular signatures +func decodeClosingSigs(c *ClosingSigs, tlvRecords ExtraOpaqueData) error { sig1 := c.CloserNoClosee.Zero() sig2 := c.NoCloserClosee.Zero() sig3 := c.CloserAndClosee.Zero() - // Taproot signatures (with nonces) - tSig1 := tc.CloserNoClosee.Zero() - tSig2 := tc.NoCloserClosee.Zero() - tSig3 := tc.CloserAndClosee.Zero() - - typeMap, err := tlvRecords.ExtractRecords( - &sig1, &sig2, &sig3, &tSig1, &tSig2, &tSig3, - ) + typeMap, err := tlvRecords.ExtractRecords(&sig1, &sig2, &sig3) if err != nil { return err } + // TODO(roasbeef): helper func to made decode of the optional vals + // easier? + if val, ok := typeMap[c.CloserNoClosee.TlvType()]; ok && val == nil { c.CloserNoClosee = tlv.SomeRecordT(sig1) } @@ -114,27 +80,6 @@ func decodeClosingSigs(c *ClosingSigs, c.CloserAndClosee = tlv.SomeRecordT(sig3) } - if val, ok := typeMap[tc.CloserNoClosee.TlvType()]; ok && val == nil { - tc.CloserNoClosee = tlv.SomeRecordT(tSig1) - } - if val, ok := typeMap[tc.NoCloserClosee.TlvType()]; ok && val == nil { - tc.NoCloserClosee = tlv.SomeRecordT(tSig2) - } - if val, ok := typeMap[tc.CloserAndClosee.TlvType()]; ok && val == nil { - tc.CloserAndClosee = tlv.SomeRecordT(tSig3) - } - - // Reject messages that contain both regular and taproot signatures. - hasRegular := c.CloserNoClosee.IsSome() || - c.NoCloserClosee.IsSome() || c.CloserAndClosee.IsSome() - hasTaproot := tc.CloserNoClosee.IsSome() || - tc.NoCloserClosee.IsSome() || tc.CloserAndClosee.IsSome() - - if hasRegular && hasTaproot { - return fmt.Errorf("closing_complete contains both " + - "regular and taproot signatures") - } - return nil } @@ -157,11 +102,7 @@ func (c *ClosingComplete) Decode(r io.Reader, _ uint32) error { return err } - err = decodeClosingSigs( - &c.ClosingSigs, &c.TaprootClosingSigs, - tlvRecords, - ) - if err != nil { + if err := decodeClosingSigs(&c.ClosingSigs, tlvRecords); err != nil { return err } @@ -173,14 +114,9 @@ func (c *ClosingComplete) Decode(r io.Reader, _ uint32) error { } // closingSigRecords returns the set of records that encode the closing sigs, -// including both regular and taproot signatures. -func closingSigRecords(c *ClosingSigs, - tc *TaprootClosingSigs, -) []tlv.RecordProducer { - - recordProducers := make([]tlv.RecordProducer, 0, 6) - - // Regular signatures +// if present. +func closingSigRecords(c *ClosingSigs) []tlv.RecordProducer { + recordProducers := make([]tlv.RecordProducer, 0, 3) c.CloserNoClosee.WhenSome(func(sig tlv.RecordT[tlv.TlvType1, Sig]) { recordProducers = append(recordProducers, &sig) }) @@ -191,23 +127,6 @@ func closingSigRecords(c *ClosingSigs, recordProducers = append(recordProducers, &sig) }) - // Taproot signatures (with nonces). - tc.CloserNoClosee.WhenSome( - func(sig tlv.RecordT[tlv.TlvType5, PartialSigWithNonce]) { - recordProducers = append(recordProducers, &sig) - }, - ) - tc.NoCloserClosee.WhenSome( - func(sig tlv.RecordT[tlv.TlvType6, PartialSigWithNonce]) { - recordProducers = append(recordProducers, &sig) - }, - ) - tc.CloserAndClosee.WhenSome( - func(sig tlv.RecordT[tlv.TlvType7, PartialSigWithNonce]) { - recordProducers = append(recordProducers, &sig) - }, - ) - return recordProducers } @@ -232,9 +151,7 @@ func (c *ClosingComplete) Encode(w *bytes.Buffer, _ uint32) error { return err } - recordProducers := closingSigRecords( - &c.ClosingSigs, &c.TaprootClosingSigs, - ) + recordProducers := closingSigRecords(&c.ClosingSigs) err := EncodeMessageExtraData(&c.ExtraData, recordProducers...) if err != nil { diff --git a/lnwire/closing_sig.go b/lnwire/closing_sig.go index 3409fbe26..94a356066 100644 --- a/lnwire/closing_sig.go +++ b/lnwire/closing_sig.go @@ -2,31 +2,11 @@ package lnwire import ( "bytes" - "fmt" "io" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/lightningnetwork/lnd/tlv" + "github.com/btcsuite/btcd/btcutil" ) -// TaprootPartialSigs houses the 3 possible taproot partial -// signatures (without nonces) -// that can be sent in a ClosingSig message. These use just PartialSig since the -// receiver already knows our nonce from the previous ClosingComplete. -type TaprootPartialSigs struct { - // CloserNoClosee is a partial signature that excludes the - // output of the closee. Uses TLV type 5. - CloserNoClosee tlv.OptionalRecordT[tlv.TlvType5, PartialSig] - - // NoCloserClosee is a partial signature that excludes the - // output of the closer. Uses TLV type 6. - NoCloserClosee tlv.OptionalRecordT[tlv.TlvType6, PartialSig] - - // CloserAndClosee is a partial signature that includes - // both outputs. Uses TLV type 7. - CloserAndClosee tlv.OptionalRecordT[tlv.TlvType7, PartialSig] -} - // ClosingSig is sent in response to a ClosingComplete message. It carries the // signatures of the closee to the closer. type ClosingSig struct { @@ -50,97 +30,14 @@ type ClosingSig struct { LockTime uint32 // ClosingSigs houses the 3 possible signatures that can be sent. - // For non-taproot channels, these are regular signatures. ClosingSigs - // TaprootPartialSigs houses the 3 possible taproot partial signatures - // that can be sent. For ClosingSig, we only send the partial signature - // without the nonce since the remote already knows our nonce from the - // previous ClosingComplete message. - // - // NOTE: This field is only populated for taproot channels. When - // present, the above ClosingSigs MUST be empty. - TaprootPartialSigs - - // NextCloseeNonce is an optional nonce for RBF iterations. This is the - // nonce that the closer should use for this party's closee signature - // in the next RBF round. - // - // NOTE: This field is only populated for taproot channels during RBF. - NextCloseeNonce tlv.OptionalRecordT[tlv.TlvType22, Musig2Nonce] - // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. ExtraData ExtraOpaqueData } -// decodeClosingSigSigs decodes the closing sig TLV records from the passed -// ExtraOpaqueData. -func decodeClosingSigSigs(c *ClosingSigs, tp *TaprootPartialSigs, - nextNonce *tlv.OptionalRecordT[tlv.TlvType22, Musig2Nonce], - tlvRecords ExtraOpaqueData) error { - // Regular signatures - sig1 := c.CloserNoClosee.Zero() - sig2 := c.NoCloserClosee.Zero() - sig3 := c.CloserAndClosee.Zero() - - // Taproot partial signatures (without nonces) - tSig1 := tp.CloserNoClosee.Zero() - tSig2 := tp.NoCloserClosee.Zero() - tSig3 := tp.CloserAndClosee.Zero() - - // Next closee nonce for RBF - nonce := nextNonce.Zero() - - typeMap, err := tlvRecords.ExtractRecords( - &sig1, &sig2, &sig3, &tSig1, &tSig2, &tSig3, &nonce, - ) - if err != nil { - return err - } - - // Regular signatures - if val, ok := typeMap[c.CloserNoClosee.TlvType()]; ok && val == nil { - c.CloserNoClosee = tlv.SomeRecordT(sig1) - } - if val, ok := typeMap[c.NoCloserClosee.TlvType()]; ok && val == nil { - c.NoCloserClosee = tlv.SomeRecordT(sig2) - } - if val, ok := typeMap[c.CloserAndClosee.TlvType()]; ok && val == nil { - c.CloserAndClosee = tlv.SomeRecordT(sig3) - } - - // Taproot partial signatures - if val, ok := typeMap[tp.CloserNoClosee.TlvType()]; ok && val == nil { - tp.CloserNoClosee = tlv.SomeRecordT(tSig1) - } - if val, ok := typeMap[tp.NoCloserClosee.TlvType()]; ok && val == nil { - tp.NoCloserClosee = tlv.SomeRecordT(tSig2) - } - if val, ok := typeMap[tp.CloserAndClosee.TlvType()]; ok && val == nil { - tp.CloserAndClosee = tlv.SomeRecordT(tSig3) - } - - // Next closee nonce - if val, ok := typeMap[nextNonce.TlvType()]; ok && val == nil { - *nextNonce = tlv.SomeRecordT(nonce) - } - - // Reject messages that contain both regular and taproot signatures. - hasRegular := c.CloserNoClosee.IsSome() || - c.NoCloserClosee.IsSome() || c.CloserAndClosee.IsSome() - hasTaproot := tp.CloserNoClosee.IsSome() || - tp.NoCloserClosee.IsSome() || tp.CloserAndClosee.IsSome() - - if hasRegular && hasTaproot { - return fmt.Errorf("closing_sig contains both " + - "regular and taproot signatures") - } - - return nil -} - // Decode deserializes a serialized ClosingSig message stored in the passed // io.Reader. func (c *ClosingSig) Decode(r io.Reader, _ uint32) error { @@ -160,11 +57,7 @@ func (c *ClosingSig) Decode(r io.Reader, _ uint32) error { return err } - err = decodeClosingSigSigs( - &c.ClosingSigs, &c.TaprootPartialSigs, - &c.NextCloseeNonce, tlvRecords, - ) - if err != nil { + if err := decodeClosingSigs(&c.ClosingSigs, tlvRecords); err != nil { return err } @@ -175,57 +68,6 @@ func (c *ClosingSig) Decode(r io.Reader, _ uint32) error { return nil } -// closingSigSigRecords returns the set of records that encode the closing sigs, -// including both regular and taproot signatures. -func closingSigSigRecords(c *ClosingSigs, - tp *TaprootPartialSigs, - nextNonce tlv.OptionalRecordT[tlv.TlvType22, Musig2Nonce], -) []tlv.RecordProducer { - - recordProducers := make([]tlv.RecordProducer, 0, 7) - - // Regular signatures - c.CloserNoClosee.WhenSome(func(sig tlv.RecordT[tlv.TlvType1, Sig]) { - recordProducers = append(recordProducers, &sig) - }) - c.NoCloserClosee.WhenSome(func(sig tlv.RecordT[tlv.TlvType2, Sig]) { - recordProducers = append(recordProducers, &sig) - }) - c.CloserAndClosee.WhenSome(func(sig tlv.RecordT[tlv.TlvType3, Sig]) { - recordProducers = append(recordProducers, &sig) - }) - - // Taproot partial signatures (without nonces). - tp.CloserNoClosee.WhenSome( - func(sig tlv.RecordT[tlv.TlvType5, PartialSig]) { - recordProducers = append( - recordProducers, &sig, - ) - }, - ) - tp.NoCloserClosee.WhenSome( - func(sig tlv.RecordT[tlv.TlvType6, PartialSig]) { - recordProducers = append( - recordProducers, &sig, - ) - }, - ) - tp.CloserAndClosee.WhenSome( - func(sig tlv.RecordT[tlv.TlvType7, PartialSig]) { - recordProducers = append( - recordProducers, &sig, - ) - }, - ) - - // Next closee nonce for RBF - nextNonce.WhenSome(func(nonce tlv.RecordT[tlv.TlvType22, Musig2Nonce]) { - recordProducers = append(recordProducers, &nonce) - }) - - return recordProducers -} - // Encode serializes the target ClosingSig into the passed io.Writer. func (c *ClosingSig) Encode(w *bytes.Buffer, _ uint32) error { if err := WriteChannelID(w, c.ChannelID); err != nil { @@ -247,10 +89,7 @@ func (c *ClosingSig) Encode(w *bytes.Buffer, _ uint32) error { return err } - recordProducers := closingSigSigRecords( - &c.ClosingSigs, &c.TaprootPartialSigs, - c.NextCloseeNonce, - ) + recordProducers := closingSigRecords(&c.ClosingSigs) err := EncodeMessageExtraData(&c.ExtraData, recordProducers...) if err != nil { diff --git a/lnwire/closing_signed.go b/lnwire/closing_signed.go index a4d0b6a0c..c247cfe0a 100644 --- a/lnwire/closing_signed.go +++ b/lnwire/closing_signed.go @@ -4,7 +4,7 @@ import ( "bytes" "io" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/tlv" ) diff --git a/lnwire/commit_sig_test.go b/lnwire/commit_sig_test.go index 2524727d8..0772a2fb8 100644 --- a/lnwire/commit_sig_test.go +++ b/lnwire/commit_sig_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/lightningnetwork/lnd/tlv" "github.com/stretchr/testify/require" ) @@ -41,13 +42,8 @@ func generateCommitSigTestCases(t *testing.T) []commitSigTestCase { sigScalar := new(btcec.ModNScalar) sigScalar.SetByteSlice(sig.RawBytes()) - // Generate a valid MuSig2 nonce (two compressed public keys). - _, pub1 := btcec.PrivKeyFromBytes(chanIDBytes) - _, pub2 := btcec.PrivKeyFromBytes(commitSigBytes[:32]) - - var nonce Musig2Nonce - copy(nonce[:33], pub1.SerializeCompressed()) - copy(nonce[33:], pub2.SerializeCompressed()) + var nonce [musig2.PubNonceSize]byte + copy(nonce[:], commitSigBytes) sigWithNonce := NewPartialSigWithNonce(nonce, *sigScalar) partialSig := MaybePartialSigWithNonce(sigWithNonce) diff --git a/lnwire/custom_records.go b/lnwire/custom_records.go index 90f99d264..de5ff4a23 100644 --- a/lnwire/custom_records.go +++ b/lnwire/custom_records.go @@ -263,32 +263,6 @@ func DecodeRecordsP2P(r *bytes.Reader, return tlvStream.DecodeWithParsedTypesP2P(r) } -// AddOpt appends a record producer for the given optional record to producers -// when the optional is set, leaving producers unchanged otherwise. -func AddOpt[T tlv.TlvType, V any](producers *[]tlv.RecordProducer, - opt tlv.OptionalRecordT[T, V]) { - - opt.WhenSome( - func(r tlv.RecordT[T, V]) { - *producers = append(*producers, &r) - }, - ) -} - -// SetOptFromMap marks target as Some(record) when record's TLV type appeared -// on the wire (i.e., is a key in the decoded TypeMap). -// -// The caller must have passed record to the underlying Stream before decoding; -// otherwise record.Val will not have been populated, and wrapping it as Some -// would yield a zero-valued field. -func SetOptFromMap[T tlv.TlvType, V any](typeMap tlv.TypeMap, - target *tlv.OptionalRecordT[T, V], record tlv.RecordT[T, V]) { - - if _, ok := typeMap[record.TlvType()]; ok { - *target = tlv.SomeRecordT(record) - } -} - // AssertUniqueTypes asserts that the given records have unique types. func AssertUniqueTypes(r []tlv.Record) error { seen := make(fn.Set[tlv.Type], len(r)) diff --git a/lnwire/custom_records_test.go b/lnwire/custom_records_test.go index d14586b8e..d4aad2e54 100644 --- a/lnwire/custom_records_test.go +++ b/lnwire/custom_records_test.go @@ -249,46 +249,3 @@ func TestCustomRecordsMergedCopy(t *testing.T) { }) } } - -// TestAddOptAppendsOnlyWhenSet checks that AddOpt is a no-op for an empty -// optional and appends a producer when the optional is populated. -func TestAddOptAppendsOnlyWhenSet(t *testing.T) { - t.Parallel() - - var producers []tlv.RecordProducer - - emptyOpt := tlv.OptionalRecordT[tlv.TlvType1, uint16]{} - AddOpt(&producers, emptyOpt) - require.Empty(t, producers) - - setOpt := tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType1, uint16](42), - ) - AddOpt(&producers, setOpt) - require.Len(t, producers, 1) - - rec := producers[0].Record() - require.Equal(t, tlv.Type(1), rec.Type()) -} - -// TestSetOptFromMapUsesTypeMapPresence verifies that SetOptFromMap populates -// only when the TLV type is present in the TypeMap. -func TestSetOptFromMapUsesTypeMapPresence(t *testing.T) { - t.Parallel() - - present := tlv.TypeMap{tlv.Type(1): nil} - missing := tlv.TypeMap{} - - var target tlv.OptionalRecordT[tlv.TlvType1, uint16] - SetOptFromMap( - missing, &target, - tlv.NewPrimitiveRecord[tlv.TlvType1, uint16](7), - ) - require.True(t, target.IsNone()) - - SetOptFromMap( - present, &target, - tlv.NewPrimitiveRecord[tlv.TlvType1, uint16](7), - ) - require.True(t, target.IsSome()) -} diff --git a/lnwire/dyn_commit.go b/lnwire/dyn_commit.go index 0aa877656..2cb1047b0 100644 --- a/lnwire/dyn_commit.go +++ b/lnwire/dyn_commit.go @@ -4,7 +4,7 @@ import ( "bytes" "io" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/tlv" ) diff --git a/lnwire/dyn_propose.go b/lnwire/dyn_propose.go index 0c260fbda..2771e790f 100644 --- a/lnwire/dyn_propose.go +++ b/lnwire/dyn_propose.go @@ -4,7 +4,7 @@ import ( "bytes" "io" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/tlv" ) diff --git a/lnwire/features.go b/lnwire/features.go index c01fab315..107828ee2 100644 --- a/lnwire/features.go +++ b/lnwire/features.go @@ -289,15 +289,13 @@ const ( // being finalized. SimpleTaprootChannelsOptionalStaging = 181 - // ExperimentalAccountabilityRequired is a required feature bit that - // indicates that the node will relay experimental accountability - // signals. - ExperimentalAccountabilityRequired FeatureBit = 260 + // ExperimentalEndorsementRequired is a required feature bit that + // indicates that the node will relay experimental endorsement signals. + ExperimentalEndorsementRequired FeatureBit = 260 - // ExperimentalAccountabilityOptional is an optional feature bit that - // indicates that the node will relay experimental accountability - // signals. - ExperimentalAccountabilityOptional FeatureBit = 261 + // ExperimentalEndorsementOptional is an optional feature bit that + // indicates that the node will relay experimental endorsement signals. + ExperimentalEndorsementOptional FeatureBit = 261 // Bolt11BlindedPathsRequired is a required feature bit that indicates // that the node is able to understand the blinded path tagged field in @@ -317,14 +315,6 @@ const ( // support for the special custom taproot overlay channel. SimpleTaprootOverlayChansRequired = 2026 - // OnionMessagesRequired is a required feature bit that indicates that - // the node can forward onion messages. - OnionMessagesRequired = 38 - - // OnionMessagesOptional is an optional feature bit that indicates - // that the node can forward onion messages. - OnionMessagesOptional = 39 - // MaxBolt11Feature is the maximum feature bit value allowed in bolt 11 // invoices. // @@ -395,16 +385,14 @@ var Features = map[FeatureBit]string{ SimpleTaprootChannelsOptionalStaging: "simple-taproot-chans-x", SimpleTaprootOverlayChansOptional: "taproot-overlay-chans", SimpleTaprootOverlayChansRequired: "taproot-overlay-chans", - ExperimentalAccountabilityRequired: "accountable-x", - ExperimentalAccountabilityOptional: "accountable-x", + ExperimentalEndorsementRequired: "endorsement-x", + ExperimentalEndorsementOptional: "endorsement-x", Bolt11BlindedPathsOptional: "bolt-11-blinded-paths", Bolt11BlindedPathsRequired: "bolt-11-blinded-paths", RbfCoopCloseOptional: "rbf-coop-close", RbfCoopCloseRequired: "rbf-coop-close", RbfCoopCloseOptionalStaging: "rbf-coop-close-x", RbfCoopCloseRequiredStaging: "rbf-coop-close-x", - OnionMessagesOptional: "onion-messages", - OnionMessagesRequired: "onion-messages", } // RawFeatureVector represents a set of feature bits as defined in BOLT-09. A diff --git a/lnwire/features_test.go b/lnwire/features_test.go index f18e7b379..c77ddd4d1 100644 --- a/lnwire/features_test.go +++ b/lnwire/features_test.go @@ -340,6 +340,7 @@ func TestFeatures(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { fv := NewFeatureVector( toRawFV(test.exp), Features, @@ -507,6 +508,7 @@ func TestValidateUpdate(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.name, func(t *testing.T) { t.Parallel() diff --git a/lnwire/funding_created.go b/lnwire/funding_created.go index 8668ac091..82d0ff87c 100644 --- a/lnwire/funding_created.go +++ b/lnwire/funding_created.go @@ -4,7 +4,7 @@ import ( "bytes" "io" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/tlv" ) diff --git a/lnwire/fuzz_test.go b/lnwire/fuzz_test.go index 6bbc86710..a143ce6d9 100644 --- a/lnwire/fuzz_test.go +++ b/lnwire/fuzz_test.go @@ -498,12 +498,6 @@ func FuzzCustomMessage(f *testing.F) { }) } -func FuzzOnionMessage(f *testing.F) { - f.Fuzz(func(t *testing.T, data []byte) { - wireMsgHarness(t, data, MsgOnionMessage) - }) -} - // FuzzParseRawSignature tests that our DER-encoded signature parsing does not // panic for arbitrary inputs and that serializing and reparsing the signatures // does not mutate them. diff --git a/lnwire/gossip_timestamp_range.go b/lnwire/gossip_timestamp_range.go index ef76555d0..45ff1f939 100644 --- a/lnwire/gossip_timestamp_range.go +++ b/lnwire/gossip_timestamp_range.go @@ -4,7 +4,7 @@ import ( "bytes" "io" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/tlv" ) diff --git a/lnwire/interfaces.go b/lnwire/interfaces.go index 2b8d64e1f..3a8b7cbdf 100644 --- a/lnwire/interfaces.go +++ b/lnwire/interfaces.go @@ -1,39 +1,6 @@ package lnwire -import ( - "fmt" - - "github.com/btcsuite/btcd/chainhash/v2" -) - -// GossipVersion is a version number that describes the version of the -// gossip protocol that a gossip message was gossiped on. -type GossipVersion uint8 - -const ( - // GossipVersion1 is the initial version of the gossip protocol as - // defined in BOLT 7. This version of the protocol can only gossip P2WSH - // channels and makes use of ECDSA signatures. - GossipVersion1 GossipVersion = 1 - - // GossipVersion2 is the newest version of the gossip protocol. This - // version adds support for P2TR channels and makes use of Schnorr - // signatures. The BOLT number is TBD. - GossipVersion2 GossipVersion = 2 -) - -// String returns a string representation of the protocol version. -func (v GossipVersion) String() string { - return fmt.Sprintf("V%d", v) -} - -// GossipMessage is an interface that must be satisfied by all messages that are -// part of the gossip protocol. -type GossipMessage interface { - // GossipVersion returns the version of the gossip protocol that a - // message is part of. - GossipVersion() GossipVersion -} +import "github.com/btcsuite/btcd/chaincfg/chainhash" // AnnounceSignatures is an interface that represents a message used to // exchange signatures of a ChannelAnnouncment message during the funding flow. @@ -45,7 +12,6 @@ type AnnounceSignatures interface { ChanID() ChannelID Message - GossipMessage } // ChannelAnnouncement is an interface that must be satisfied by any message @@ -67,7 +33,6 @@ type ChannelAnnouncement interface { Node2KeyBytes() [33]byte Message - GossipMessage } // CompareResult represents the result after comparing two things. @@ -124,24 +89,6 @@ type ChannelUpdate interface { SetSCID(scid ShortChannelID) Message - GossipMessage -} - -// NodeAnnouncement is an interface that must be satisfied by any message used -// to announce the existence of a node. -type NodeAnnouncement interface { - // NodePub returns the identity public key of the node. - NodePub() [33]byte - - // NodeFeatures returns the set of features supported by the node. - NodeFeatures() *FeatureVector - - // TimestampDesc returns a human-readable description of the - // timestamp of the announcement. - TimestampDesc() string - - Message - GossipMessage } // ForwardingPolicy defines the set of forwarding constraints advertised in a diff --git a/lnwire/intro_node.go b/lnwire/intro_node.go deleted file mode 100644 index 769da1131..000000000 --- a/lnwire/intro_node.go +++ /dev/null @@ -1,181 +0,0 @@ -package lnwire - -import ( - "bytes" - "fmt" - "io" - - "github.com/btcsuite/btcd/btcec/v2" -) - -// IntroductionNode is the sealed sum-type for a blinded path's introduction -// node. {0x02, 0x03} → PubkeyIntro; {0x00, 0x01} → SciddirIntro. The unexported -// method seals the variant set so foreign packages cannot satisfy the interface -// with an unrecognised wire form. -type IntroductionNode interface { - isIntroductionNode() - - encodedLen() uint64 - - encode(w io.Writer) error - - // validate checks that the discriminator byte is valid for the variant. - validate() error - - // Bytes returns the wire-format encoding of the introduction node for - // callers that need it outside an io.Writer (RPC surfaces). - Bytes() []byte -} - -// PubkeyIntro is the 33-byte compressed-pubkey variant. The SEC1 parity byte -// (0x02 or 0x03) doubles as the wire discriminator. Use the constructor to -// ensure the non-nil, on-curve invariant is upheld. -type PubkeyIntro struct { - Pubkey *btcec.PublicKey -} - -// NewPubkeyIntro constructs the pubkey introduction-node variant, establishing -// the non-nil, on-curve invariant at construction time so callers receive an -// error up front rather than relying on every method to re-guard a nil key. -func NewPubkeyIntro(pubkey *btcec.PublicKey) (PubkeyIntro, error) { - p := PubkeyIntro{Pubkey: pubkey} - if err := p.validate(); err != nil { - return PubkeyIntro{}, err - } - - return p, nil -} - -// SciddirIntro is the 9-byte sciddir variant. Direction is the wire -// discriminator; SCID is the 8-byte short channel ID. Use the constructor to -// ensure the direction is valid at construction time. -type SciddirIntro struct { - Direction byte - SCID [scidLen]byte -} - -// NewSciddirIntro constructs the sciddir introduction-node variant, rejecting -// an invalid direction discriminator at construction time. -func NewSciddirIntro(direction byte, scid [scidLen]byte) (SciddirIntro, error) { - s := SciddirIntro{Direction: direction, SCID: scid} - if err := s.validate(); err != nil { - return SciddirIntro{}, err - } - - return s, nil -} - -var ( - _ IntroductionNode = PubkeyIntro{} - _ IntroductionNode = SciddirIntro{} -) - -// decodeIntroductionNode reads the discriminator byte and dispatches to the -// matching variant. -func decodeIntroductionNode(r io.Reader, - buf *[8]byte) (IntroductionNode, error) { - - if _, err := io.ReadFull(r, buf[:1]); err != nil { - return nil, fmt.Errorf("read intro node type: %w", err) - } - - disc := buf[0] - switch disc { - case 0x00, 0x01: - var scid [scidLen]byte - if _, err := io.ReadFull(r, scid[:]); err != nil { - return nil, fmt.Errorf("read sciddir: %w", err) - } - - return NewSciddirIntro(disc, scid) - - case 0x02, 0x03: - var b [pubKeyLen]byte - b[0] = disc - if _, err := io.ReadFull(r, b[1:]); err != nil { - return nil, fmt.Errorf("read intro pubkey: %w", err) - } - pub, err := btcec.ParsePubKey(b[:]) - if err != nil { - return nil, fmt.Errorf("%w: %w", - ErrInvalidIntroNode, err) - } - - return NewPubkeyIntro(pub) - - default: - return nil, fmt.Errorf("%w: 0x%02x", ErrInvalidIntroNode, disc) - } -} - -func (PubkeyIntro) isIntroductionNode() {} - -func (p PubkeyIntro) encodedLen() uint64 { return pubKeyLen } - -func (p PubkeyIntro) encode(w io.Writer) error { - if p.Pubkey == nil { - return fmt.Errorf("nil intro pubkey") - } - _, err := w.Write(p.Pubkey.SerializeCompressed()) - - return err -} - -func (p PubkeyIntro) validate() error { - if p.Pubkey == nil { - return fmt.Errorf("%w: nil pubkey", ErrInvalidIntroNode) - } - - if !p.Pubkey.IsOnCurve() { - return fmt.Errorf("%w: pubkey not on curve", - ErrInvalidIntroNode) - } - - return nil -} - -// Bytes returns the wire-format encoding of the pubkey variant. -func (p PubkeyIntro) Bytes() []byte { - var buf bytes.Buffer - buf.Grow(pubKeyLen) - - // We ignore errors because we have validated that the pubkey is non nil - // at construction time. - _ = p.encode(&buf) - - return buf.Bytes() -} - -func (SciddirIntro) isIntroductionNode() {} - -func (s SciddirIntro) encodedLen() uint64 { return sciddirLen } - -func (s SciddirIntro) encode(w io.Writer) error { - if _, err := w.Write([]byte{s.Direction}); err != nil { - return err - } - _, err := w.Write(s.SCID[:]) - - return err -} - -func (s SciddirIntro) validate() error { - switch s.Direction { - case 0x00, 0x01: - return nil - } - - return fmt.Errorf("%w: 0x%02x", ErrInvalidIntroNode, s.Direction) -} - -// Bytes returns the wire-format encoding of the sciddir variant. -func (s SciddirIntro) Bytes() []byte { - var buf bytes.Buffer - buf.Grow(sciddirLen) - - // We ignore the error because encode only writes the fixed-size - // direction and SCID to an in-memory buffer, which cannot fail. - _ = s.encode(&buf) - - return buf.Bytes() -} diff --git a/lnwire/lnwire.go b/lnwire/lnwire.go index aa636493a..938240b30 100644 --- a/lnwire/lnwire.go +++ b/lnwire/lnwire.go @@ -11,9 +11,9 @@ import ( "net" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/tor" ) diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index 02c282e09..dbd483f10 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -10,8 +10,8 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/tor" "github.com/stretchr/testify/require" "pgregory.net/rapid" diff --git a/lnwire/local_nonces.go b/lnwire/local_nonces.go deleted file mode 100644 index 3846d225d..000000000 --- a/lnwire/local_nonces.go +++ /dev/null @@ -1,192 +0,0 @@ -package lnwire - -import ( - "bytes" - "io" - "sort" - - "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/tlv" -) - -// LocalNoncesRecordTypeDef is the concrete TLV record type for LocalNoncesData. -// This is type 22 as defined in the BOLT specification for channel -// reestablish. -type LocalNoncesRecordTypeDef = tlv.TlvType22 - -// localNonceEntry holds a single TXID -> Musig2Nonce mapping. -type localNonceEntry struct { - txid chainhash.Hash - - nonce Musig2Nonce -} - -// LocalNoncesData is the core data structure holding the map of nonces. -type LocalNoncesData struct { - NoncesMap map[chainhash.Hash]Musig2Nonce -} - -// NewLocalNoncesData creates a new LocalNoncesData with an initialized map. -func NewLocalNoncesData() *LocalNoncesData { - return &LocalNoncesData{ - NoncesMap: make(map[chainhash.Hash]Musig2Nonce), - } -} - -// Record implements the tlv.RecordProducer interface. -func (l *LocalNoncesData) Record() tlv.Record { - return tlv.MakeDynamicRecord( - (LocalNoncesRecordTypeDef)(nil).TypeVal(), - l, - func() uint64 { - if len(l.NoncesMap) == 0 { - return 0 - } - - numEntries := len(l.NoncesMap) - entrySize := chainhash.HashSize + musig2.PubNonceSize - - return uint64(numEntries * entrySize) - }, - encodeLocalNoncesData, - decodeLocalNoncesData, - ) -} - -// encodeLocalNoncesData implements the tlv.Encoder for LocalNoncesData. -func encodeLocalNoncesData(w io.Writer, val any, _ *[8]byte) error { - data, ok := val.(*LocalNoncesData) - if !ok { - return tlv.NewTypeForEncodingErr(val, "*lnwire.LocalNoncesData") - } - - var sortedEntries []localNonceEntry - - if len(data.NoncesMap) > 0 { - sortedEntries = make([]localNonceEntry, 0, len(data.NoncesMap)) - for txid, nonce := range data.NoncesMap { - sortedEntries = append( - sortedEntries, localNonceEntry{ - txid: txid, nonce: nonce, - }, - ) - } - - sort.Slice(sortedEntries, func(i, j int) bool { - return bytes.Compare( - sortedEntries[i].txid[:], - sortedEntries[j].txid[:], - ) < 0 - }) - } - - for _, entry := range sortedEntries { - if _, err := w.Write(entry.txid[:]); err != nil { - return err - } - if _, err := w.Write(entry.nonce[:]); err != nil { - return err - } - } - - return nil -} - -// decodeLocalNoncesData implements the tlv.Decoder for LocalNoncesData. -func decodeLocalNoncesData(r io.Reader, val any, _ *[8]byte, - recordLen uint64) error { - - l, ok := val.(*LocalNoncesData) - if !ok { - return tlv.NewTypeForDecodingErr( - val, "*lnwire.LocalNoncesData", recordLen, 0, - ) - } - - if l.NoncesMap == nil { - l.NoncesMap = make(map[chainhash.Hash]Musig2Nonce) - } - - // If recordLen is 0, it means an empty TLV value, which is valid for - // 0 entries. Ensure the map is empty in this case. - if recordLen == 0 { - // Clear if it had previous entries. - if len(l.NoncesMap) > 0 { - l.NoncesMap = make(map[chainhash.Hash]Musig2Nonce) - } - - return nil - } - - // Each entry is a fixed size: TXID (32 bytes) + Nonce (66 bytes). We - // can use this to compute the number of expected entries and perform a - // sanity check while we're at it. - const entrySize = chainhash.HashSize + musig2.PubNonceSize - if recordLen%entrySize != 0 { - return tlv.NewTypeForDecodingErr( - l, "lnwire.LocalNoncesData (record length not "+ - "evenly divisible by entry size)", - recordLen, 0, - ) - } - - numEntries := recordLen / entrySize - - // Cap the number of entries to a reasonable limit. In practice, - // this is the number of active splices which is directly limited - // by implementations. - const maxEntries = 16 - if numEntries > maxEntries { - return tlv.NewTypeForDecodingErr( - l, "lnwire.LocalNoncesData (too many entries)", - recordLen, 0, - ) - } - - // Prepare the map for new entries. Using 'make' here also clears any - // existing entries if the LocalNoncesData instance is being reused. - l.NoncesMap = make(map[chainhash.Hash]Musig2Nonce, numEntries) - - for i := uint64(0); i < numEntries; i++ { - var ( - txid chainhash.Hash - nonce Musig2Nonce - ) - - if _, err := io.ReadFull(r, txid[:]); err != nil { - return err - } - if _, err := io.ReadFull(r, nonce[:]); err != nil { - return err - } - - err := ValidateMusig2Nonce(nonce) - if err != nil { - return err - } - - if _, exists := l.NoncesMap[txid]; exists { - return tlv.NewTypeForDecodingErr( - l, "lnwire.LocalNoncesData (duplicate txid)", - recordLen, 0, - ) - } - - l.NoncesMap[txid] = nonce - } - - return nil -} - -var _ tlv.RecordProducer = (*LocalNoncesData)(nil) - -// OptLocalNonces is a type alias for the optional TLV structure. -type OptLocalNonces = fn.Option[LocalNoncesData] - -// SomeLocalNonces is a helper function to create an fn.Option[LocalNoncesData] -// with the given data. -func SomeLocalNonces(data LocalNoncesData) OptLocalNonces { - return fn.Some(data) -} diff --git a/lnwire/local_nonces_test.go b/lnwire/local_nonces_test.go deleted file mode 100644 index b5dcec1b0..000000000 --- a/lnwire/local_nonces_test.go +++ /dev/null @@ -1,204 +0,0 @@ -package lnwire - -import ( - "bytes" - "testing" - - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/stretchr/testify/require" -) - -// makeTestTxId creates a chainhash.Hash for testing. -func makeTestTxId(val byte) chainhash.Hash { - var txid chainhash.Hash - for i := range txid { - txid[i] = val - } - - return txid -} - -// makeEncodedEntry encodes a single txid/nonce pair for testing. -func makeEncodedEntry(txidVal byte) []byte { - entry := make([]byte, chainhash.HashSize+len(Musig2Nonce{})) - txid := makeTestTxId(txidVal) - nonce := makeNonce() - - copy(entry[:chainhash.HashSize], txid[:]) - copy(entry[chainhash.HashSize:], nonce[:]) - - return entry -} - -// TestLocalNoncesDataEncodeDecodeValue tests that LocalNoncesData can be -// properly encoded and decoded for various map configurations. -func TestLocalNoncesDataEncodeDecodeValue(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - inputData *LocalNoncesData - }{ - { - name: "nil map", - inputData: &LocalNoncesData{NoncesMap: nil}, - }, - { - name: "empty map", - inputData: NewLocalNoncesData(), - }, - { - name: "one entry", - inputData: &LocalNoncesData{ - NoncesMap: map[chainhash.Hash]Musig2Nonce{ - makeTestTxId(1): makeNonce(), - }, - }, - }, - { - name: "multiple entries unsorted", - inputData: &LocalNoncesData{ - NoncesMap: map[chainhash.Hash]Musig2Nonce{ - makeTestTxId(3): makeNonce(), - makeTestTxId(1): makeNonce(), - makeTestTxId(2): makeNonce(), - }, - }, - }, - { - name: "multiple entries already sorted by key", - inputData: &LocalNoncesData{ - NoncesMap: map[chainhash.Hash]Musig2Nonce{ - makeTestTxId(1): makeNonce(), - makeTestTxId(2): makeNonce(), - makeTestTxId(3): makeNonce(), - }, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - var ( - b bytes.Buffer - buf [8]byte - ) - - err := encodeLocalNoncesData(&b, test.inputData, &buf) - require.NoError(t, err) - - decodedData := NewLocalNoncesData() - err = decodeLocalNoncesData( - bytes.NewReader(b.Bytes()), decodedData, &buf, - uint64(b.Len()), - ) - require.NoError(t, err) - - if len(test.inputData.NoncesMap) == 0 && - len(decodedData.NoncesMap) == 0 { - - return - } - - require.Equal( - t, test.inputData.NoncesMap, - decodedData.NoncesMap, - ) - }) - } -} - -// TestLocalNoncesDataDecodeFailuresValue tests that decoding fails -// appropriately for various invalid input scenarios. -func TestLocalNoncesDataDecodeFailuresValue(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - valueBytes []byte - length uint64 - expectError bool - errorContains string - }{ - { - name: "partial entry (1 byte value)", - valueBytes: []byte{0x01}, - length: 1, - expectError: true, - errorContains: "not evenly divisible", - }, - { - name: "partial entry (99 bytes)", - valueBytes: make([]byte, 99), - length: 99, - expectError: true, - errorContains: "not evenly divisible", - }, - { - name: "one complete entry", - valueBytes: makeEncodedEntry(2), - length: 98, - expectError: false, - }, - { - name: "malformed nonce", - valueBytes: func() []byte { - entry := make([]byte, - chainhash.HashSize+len(Musig2Nonce{})) - txid := makeTestTxId(1) - // An invalid nonce (e.g., all zeros). - var nonce Musig2Nonce - copy(entry[:chainhash.HashSize], txid[:]) - copy(entry[chainhash.HashSize:], nonce[:]) - - return entry - }(), - length: 98, - expectError: true, - errorContains: "invalid first nonce point", - }, - { - name: "empty value", - valueBytes: []byte{}, - length: 0, - expectError: false, - }, - { - name: "duplicate txid", - valueBytes: append( - makeEncodedEntry(1), - makeEncodedEntry(1)..., - ), - length: uint64( - 2 * (chainhash.HashSize + len(Musig2Nonce{})), - ), - expectError: true, - errorContains: "duplicate txid", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - var buf [8]byte - - decodedData := NewLocalNoncesData() - err := decodeLocalNoncesData( - bytes.NewReader(test.valueBytes), decodedData, - &buf, test.length, - ) - - if test.expectError { - require.Error(t, err) - require.Contains( - t, err.Error(), test.errorContains, - ) - } else { - require.NoError(t, err) - } - }) - } -} diff --git a/lnwire/message.go b/lnwire/message.go index 428f06b2f..c64b09b12 100644 --- a/lnwire/message.go +++ b/lnwire/message.go @@ -66,7 +66,6 @@ const ( MsgChannelAnnouncement2 = 267 MsgNodeAnnouncement2 = 269 MsgChannelUpdate2 = 271 - MsgOnionMessage = 513 MsgKickoffSig = 777 // MsgEnd defines the end of the official message range of the protocol. @@ -199,8 +198,6 @@ func (t MessageType) String() string { return "NodeAnnouncement2" case MsgChannelUpdate2: return "ChannelUpdate2" - case MsgOnionMessage: - return "OnionMessage" default: return "" } @@ -365,8 +362,6 @@ func makeEmptyMessage(msgType MessageType) (Message, error) { msg = &NodeAnnouncement2{} case MsgChannelUpdate2: msg = &ChannelUpdate2{} - case MsgOnionMessage: - msg = &OnionMessage{} default: // If the message is not within our custom range and has not // specifically been overridden, return an unknown message. diff --git a/lnwire/message_test.go b/lnwire/message_test.go index 11b3b1fc4..f1232e85f 100644 --- a/lnwire/message_test.go +++ b/lnwire/message_test.go @@ -14,9 +14,8 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/tlv" "github.com/lightningnetwork/lnd/tor" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -291,7 +290,6 @@ func makeAllMessages(t testing.TB, r *rand.Rand) []lnwire.Message { msgAll = append(msgAll, newMsgGossipTimestampRange(t, r)) msgAll = append(msgAll, newMsgQueryShortChanIDsZlib(t, r)) msgAll = append(msgAll, newMsgReplyChannelRangeZlib(t, r)) - msgAll = append(msgAll, newMsgOnionMessage(t, r)) return msgAll } @@ -309,13 +307,13 @@ func newMsgWarning(tb testing.TB, r io.Reader) *lnwire.Warning { return msg } -func newMsgInit(t testing.TB, r *rand.Rand) *lnwire.Init { +func newMsgInit(t testing.TB, r io.Reader) *lnwire.Init { t.Helper() return &lnwire.Init{ GlobalFeatures: rawFeatureVector(), Features: rawFeatureVector(), - ExtraData: createValidTLVExtraData(t, r), + ExtraData: createExtraData(t, r), } } @@ -477,7 +475,7 @@ func newMsgShutdown(t testing.TB, r *rand.Rand) *lnwire.Shutdown { msg := &lnwire.Shutdown{ Address: randDeliveryAddress(t, r), - ExtraData: createValidTLVExtraData(t, r), + ExtraData: createExtraData(t, r), } _, err := r.Read(msg.ChannelID[:]) @@ -508,7 +506,7 @@ func newMsgUpdateAddHTLC(t testing.TB, r *rand.Rand) *lnwire.UpdateAddHTLC { ID: r.Uint64(), Amount: lnwire.MilliSatoshi(r.Int63()), Expiry: r.Uint32(), - ExtraData: createValidTLVExtraData(t, r), + ExtraData: createExtraData(t, r), } _, err := r.Read(msg.ChanID[:]) @@ -530,7 +528,7 @@ func newMsgUpdateFulfillHTLC(t testing.TB, msg := &lnwire.UpdateFulfillHTLC{ ID: r.Uint64(), - ExtraData: createValidTLVExtraData(t, r), + ExtraData: createExtraData(t, r), } _, err := r.Read(msg.ChanID[:]) @@ -556,7 +554,7 @@ func newMsgUpdateFailHTLC(t testing.TB, r *rand.Rand) *lnwire.UpdateFailHTLC { return msg } -func newMsgCommitSig(t testing.TB, r *rand.Rand) *lnwire.CommitSig { +func newMsgCommitSig(t testing.TB, r io.Reader) *lnwire.CommitSig { t.Helper() msg := lnwire.NewCommitSig() @@ -565,7 +563,7 @@ func newMsgCommitSig(t testing.TB, r *rand.Rand) *lnwire.CommitSig { require.NoError(t, err, "unable to generate chan id") msg.CommitSig = testNodeSig - msg.ExtraData = createValidTLVExtraData(t, r) + msg.ExtraData = createExtraData(t, r) msg.HtlcSigs = make([]lnwire.Sig, testNumSigs) for i := 0; i < testNumSigs; i++ { @@ -658,7 +656,7 @@ func newMsgChannelAnnouncement(t testing.TB, NodeID2: randRawKey(t), BitcoinKey1: randRawKey(t), BitcoinKey2: randRawKey(t), - ExtraOpaqueData: createValidTLVExtraData(t, r), + ExtraOpaqueData: createExtraData(t, r), NodeSig1: testNodeSig, NodeSig2: testNodeSig, BitcoinSig1: testNodeSig, @@ -687,7 +685,7 @@ func newMsgNodeAnnouncement(t testing.TB, }, NodeID: randRawKey(t), Addresses: randAddrs(t, r), - ExtraOpaqueData: createValidTLVExtraData(t, r), + ExtraOpaqueData: createExtraData(t, r), Signature: testNodeSig, } @@ -885,19 +883,6 @@ func newMsgGossipTimestampRange(t testing.TB, return msg } -// newMsgOnionMessage creates a testing OnionMessage message. -func newMsgOnionMessage(t testing.TB, r *rand.Rand) *lnwire.OnionMessage { - t.Helper() - - // Generate a random onion blob (typical size ~1366 bytes). - onionBlobSize := r.Intn(1366) + 1 - onionBlob := make([]byte, onionBlobSize) - _, err := r.Read(onionBlob) - require.NoError(t, err, "unable to read onion blob") - - return lnwire.NewOnionMessage(randPubKey(t), onionBlob) -} - func randRawKey(t testing.TB) [33]byte { t.Helper() @@ -1065,37 +1050,3 @@ func createExtraData(t testing.TB, r io.Reader) []byte { return extraData } - -// createValidTLVExtraData creates a valid, canonically-ordered TLV stream -// suitable for use as ExtraData in messages whose Encode methods re-parse -// ExtraData as TLV. Unlike createExtraData which generates raw random bytes, -// this produces properly encoded TLV records with monotonically increasing -// types. -func createValidTLVExtraData(t testing.TB, r *rand.Rand) []byte { - t.Helper() - - // Generate between 1 and 4 TLV records with strictly increasing - // types and random values. - numRecords := r.Intn(4) + 1 - records := make([]tlv.Record, 0, numRecords) - tlvType := tlv.Type(r.Intn(100) + 1) - - for range numRecords { - val := make([]byte, r.Intn(20)+1) - _, err := r.Read(val) - require.NoError(t, err, "unable to generate tlv value") - - records = append( - records, tlv.MakePrimitiveRecord(tlvType, &val), - ) - - // Ensure strictly increasing types. - tlvType += tlv.Type(r.Intn(100) + 1) - } - - // Encode the records. - encoded, err := lnwire.EncodeRecords(records) - require.NoError(t, err, "unable to encode tlv records") - - return encoded -} diff --git a/lnwire/msat.go b/lnwire/msat.go index 609df7e38..7d6d581e1 100644 --- a/lnwire/msat.go +++ b/lnwire/msat.go @@ -4,7 +4,7 @@ import ( "fmt" "io" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/tlv" ) diff --git a/lnwire/msat_test.go b/lnwire/msat_test.go index 3c328b175..50e51f554 100644 --- a/lnwire/msat_test.go +++ b/lnwire/msat_test.go @@ -3,7 +3,7 @@ package lnwire import ( "testing" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" ) func TestMilliSatoshiConversion(t *testing.T) { diff --git a/lnwire/musig2.go b/lnwire/musig2.go index 4b69b7063..cfc753f82 100644 --- a/lnwire/musig2.go +++ b/lnwire/musig2.go @@ -1,10 +1,8 @@ package lnwire import ( - "fmt" "io" - "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/lightningnetwork/lnd/tlv" ) @@ -49,36 +47,13 @@ func nonceTypeEncoder(w io.Writer, val interface{}, _ *[8]byte) error { return tlv.NewTypeForEncodingErr(val, "lnwire.Musig2Nonce") } -// ValidateMusig2Nonce checks that a 66-byte MuSig2 public nonce contains two -// valid compressed secp256k1 points. -func ValidateMusig2Nonce(nonce Musig2Nonce) error { - const compressedKeyLen = 33 - - // A MuSig2 public nonce is two 33-byte compressed public keys (R1, R2). - _, err := btcec.ParsePubKey(nonce[:compressedKeyLen]) - if err != nil { - return fmt.Errorf("invalid first nonce point: %w", err) - } - - _, err = btcec.ParsePubKey(nonce[compressedKeyLen:]) - if err != nil { - return fmt.Errorf("invalid second nonce point: %w", err) - } - - return nil -} - // nonceTypeDecoder is a custom TLV decoder for the Musig2Nonce record. func nonceTypeDecoder(r io.Reader, val interface{}, _ *[8]byte, l uint64) error { - if v, ok := val.(*Musig2Nonce); ok && l == musig2.PubNonceSize { + if v, ok := val.(*Musig2Nonce); ok { _, err := io.ReadFull(r, v[:]) - if err != nil { - return err - } - - return ValidateMusig2Nonce(*v) + return err } return tlv.NewTypeForDecodingErr( diff --git a/lnwire/musig2_test.go b/lnwire/musig2_test.go deleted file mode 100644 index 8f55b5a80..000000000 --- a/lnwire/musig2_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package lnwire - -import ( - "testing" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/stretchr/testify/require" -) - -// makeNonce creates a test Musig2Nonce containing two valid compressed public -// keys for testing TLV encoding/decoding. -func makeNonce() Musig2Nonce { - _, pub1 := btcec.PrivKeyFromBytes([]byte{ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, - 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, - }) - _, pub2 := btcec.PrivKeyFromBytes([]byte{ - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, - 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, - 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, - }) - - var n Musig2Nonce - copy(n[:33], pub1.SerializeCompressed()) - copy(n[33:], pub2.SerializeCompressed()) - - return n -} - -// TestMusig2NonceEncodeDecode tests that we're able to properly encode and -// decode Musig2Nonce within TLV streams. -func TestMusig2NonceEncodeDecode(t *testing.T) { - t.Parallel() - - nonce := makeNonce() - - var extraData ExtraOpaqueData - require.NoError(t, extraData.PackRecords(&nonce)) - - var extractedNonce Musig2Nonce - _, err := extraData.ExtractRecords(&extractedNonce) - require.NoError(t, err) - - require.Equal(t, nonce, extractedNonce) -} - -// TestMusig2NonceTypeDecodeInvalidLength ensures that decoding a Musig2Nonce -// TLV with an invalid length (anything other than 66 bytes) fails with an -// error. -func TestMusig2NonceTypeDecodeInvalidLength(t *testing.T) { - t.Parallel() - - nonce := makeNonce() - - var extraData ExtraOpaqueData - require.NoError(t, extraData.PackRecords(&nonce)) - - // Corrupt the TLV length field to simulate malformed input. - // Byte 1 contains the varint size encoding. Since 66 bytes fits into - // a single varint byte, we can directly modify extraData[1]. - extraData[1] = musig2.PubNonceSize + 1 - - var out Musig2Nonce - _, err := extraData.ExtractRecords(&out) - require.Error(t, err) - - extraData[1] = musig2.PubNonceSize - 1 - - _, err = extraData.ExtractRecords(&out) - require.Error(t, err) -} diff --git a/lnwire/netaddress.go b/lnwire/netaddress.go index 0b9311b3c..dd5a7c57b 100644 --- a/lnwire/netaddress.go +++ b/lnwire/netaddress.go @@ -5,7 +5,7 @@ import ( "net" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" ) // NetAddress represents information pertaining to the identity and network diff --git a/lnwire/node_announcement.go b/lnwire/node_announcement.go index 468ac794e..0a7c60542 100644 --- a/lnwire/node_announcement.go +++ b/lnwire/node_announcement.go @@ -104,10 +104,6 @@ type NodeAnnouncement1 struct { // lnwire.Message interface. var _ Message = (*NodeAnnouncement1)(nil) -// A compile time check to ensure NodeAnnouncement1 implements the -// lnwire.NodeAnnouncement interface. -var _ NodeAnnouncement = (*NodeAnnouncement1)(nil) - // A compile time check to ensure NodeAnnouncement1 implements the // lnwire.SizeableMessage interface. var _ SizeableMessage = (*NodeAnnouncement1)(nil) @@ -221,32 +217,3 @@ func (a *NodeAnnouncement1) DataToSign() ([]byte, error) { func (a *NodeAnnouncement1) SerializedSize() (uint32, error) { return MessageSerializedSize(a) } - -// NodePub returns the identity public key of the node. -// -// NOTE: part of the NodeAnnouncement interface. -func (a *NodeAnnouncement1) NodePub() [33]byte { - return a.NodeID -} - -// NodeFeatures returns the set of features supported by the node. -// -// NOTE: part of the NodeAnnouncement interface. -func (a *NodeAnnouncement1) NodeFeatures() *FeatureVector { - return NewFeatureVector(a.Features, Features) -} - -// TimestampDesc returns a human-readable description of the timestamp of the -// announcement. -// -// NOTE: part of the NodeAnnouncement interface. -func (a *NodeAnnouncement1) TimestampDesc() string { - return fmt.Sprintf("timestamp=%d", a.Timestamp) -} - -// GossipVersion returns the gossip version that this message is part of. -// -// NOTE: this is part of the GossipMessage interface. -func (a *NodeAnnouncement1) GossipVersion() GossipVersion { - return GossipVersion1 -} diff --git a/lnwire/node_announcement_2.go b/lnwire/node_announcement_2.go index 93a3792a2..23fcc509f 100644 --- a/lnwire/node_announcement_2.go +++ b/lnwire/node_announcement_2.go @@ -199,43 +199,10 @@ func (n *NodeAnnouncement2) MsgType() MessageType { return MsgNodeAnnouncement2 } -// NodePub returns the identity public key of the node. -// -// NOTE: part of the NodeAnnouncement interface. -func (n *NodeAnnouncement2) NodePub() [33]byte { - return n.NodeID.Val -} - -// NodeFeatures returns the set of features supported by the node. -// -// NOTE: part of the NodeAnnouncement interface. -func (n *NodeAnnouncement2) NodeFeatures() *FeatureVector { - return NewFeatureVector(&n.Features.Val, Features) -} - -// TimestampDesc returns a human-readable description of the timestamp of the -// announcement. -// -// NOTE: part of the NodeAnnouncement interface. -func (n *NodeAnnouncement2) TimestampDesc() string { - return fmt.Sprintf("block_height=%d", n.BlockHeight.Val) -} - -// GossipVersion returns the gossip version that this message is part of. -// -// NOTE: this is part of the GossipMessage interface. -func (n *NodeAnnouncement2) GossipVersion() GossipVersion { - return GossipVersion2 -} - // A compile-time check to ensure NodeAnnouncement2 implements the Message // interface. var _ Message = (*NodeAnnouncement2)(nil) -// A compile time check to ensure NodeAnnouncement2 implements the -// lnwire.NodeAnnouncement interface. -var _ NodeAnnouncement = (*NodeAnnouncement2)(nil) - // A compile-time check to ensure NodeAnnouncement2 implements the // PureTLVMessage interface. var _ PureTLVMessage = (*NodeAnnouncement2)(nil) diff --git a/lnwire/onion_error_test.go b/lnwire/onion_error_test.go index 1eae25096..a3bbea5b7 100644 --- a/lnwire/onion_error_test.go +++ b/lnwire/onion_error_test.go @@ -93,6 +93,7 @@ func TestEncodeDecodeTlv(t *testing.T) { t.Parallel() for _, testFailure := range onionFailures { + testFailure := testFailure code := testFailure.Code().String() t.Run(code, func(t *testing.T) { diff --git a/lnwire/onion_message.go b/lnwire/onion_message.go deleted file mode 100644 index a9f13d8c7..000000000 --- a/lnwire/onion_message.go +++ /dev/null @@ -1,107 +0,0 @@ -package lnwire - -import ( - "bytes" - "io" - - "github.com/btcsuite/btcd/btcec/v2" -) - -// OnionMessage is a message that carries an onion-encrypted payload. -// This is used for BOLT12 messages. -type OnionMessage struct { - // PathKey is the route blinding ephemeral pubkey to be used for - // the onion message. - PathKey *btcec.PublicKey - - // OnionBlob contains the onion_message_packet, the raw serialized - // Sphinx onion packet (BOLT 4) containing the layered, per-hop - // encrypted payloads and routing instructions used to forward this - // message along its designated path. This blob should be handled in the - // same manner as onion_routing_packet used to route HTLCs, with the - // exception that it uses blinded routes by default. - OnionBlob []byte -} - -// NewOnionMessage creates a new OnionMessage. -func NewOnionMessage(pathKey *btcec.PublicKey, - onion []byte) *OnionMessage { - - return &OnionMessage{ - PathKey: pathKey, - OnionBlob: onion, - } -} - -// A compile-time check to ensure OnionMessage implements the Message interface. -var _ Message = (*OnionMessage)(nil) - -var _ SizeableMessage = (*OnionMessage)(nil) - -// Decode reads the bytes stream and converts it to the object. -func (o *OnionMessage) Decode(r io.Reader, _ uint32) error { - if err := ReadElement(r, &o.PathKey); err != nil { - return err - } - - var onionLen uint16 - if err := ReadElement(r, &onionLen); err != nil { - return err - } - - o.OnionBlob = make([]byte, onionLen) - if err := ReadElement(r, o.OnionBlob); err != nil { - return err - } - - return nil -} - -// Encode converts object to the bytes stream and write it into the -// write buffer. -func (o *OnionMessage) Encode(w *bytes.Buffer, _ uint32) error { - if err := WritePublicKey(w, o.PathKey); err != nil { - return err - } - - onionLen := len(o.OnionBlob) - if err := WriteUint16(w, uint16(onionLen)); err != nil { - return err - } - - if err := WriteBytes(w, o.OnionBlob); err != nil { - return err - } - - return nil -} - -// MsgType returns the integer uniquely identifying this message type on the -// wire. -func (o *OnionMessage) MsgType() MessageType { - return MsgOnionMessage -} - -// WireSize returns the on-the-wire size of the message in bytes, including -// the 2-byte message type prefix, the 33-byte compressed path key, the -// 2-byte onion blob length prefix, and the onion blob itself. It computes -// the size directly from the in-memory fields rather than round-tripping -// through Encode, so callers in the hot ingress path — notably the onion -// message rate limiter — can charge the right number of byte tokens -// without paying for a full serialization. -func (o *OnionMessage) WireSize() int { - const ( - msgTypeBytes = 2 - pathKeyBytes = 33 - onionLenBytes = 2 - ) - - return msgTypeBytes + pathKeyBytes + onionLenBytes + len(o.OnionBlob) -} - -// SerializedSize returns the serialized size of the message in bytes. -// -// This is part of the lnwire.SizeableMessage interface. -func (o *OnionMessage) SerializedSize() (uint32, error) { - return uint32(o.WireSize()), nil -} diff --git a/lnwire/onion_message_test.go b/lnwire/onion_message_test.go deleted file mode 100644 index bd01e3377..000000000 --- a/lnwire/onion_message_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package lnwire - -import ( - "bytes" - "testing" - - "github.com/stretchr/testify/require" - "pgregory.net/rapid" -) - -// TestOnionMessageWireSizeMatchesEncode verifies that the value produced by -// OnionMessage.WireSize — the value fed to the onion message rate limiter on -// every incoming packet — matches the number of bytes WriteMessage actually -// emits for that same message. WireSize computes its result directly from the -// in-memory fields without round-tripping through Encode, which is fast but -// creates a risk of silent divergence if the OnionMessage wire format ever -// gains an optional TLV extension or extra field. This test is the -// compile-time-cheap regression guard that divergence does not go undetected. -func TestOnionMessageWireSizeMatchesEncode(t *testing.T) { - t.Parallel() - - rapid.Check(t, func(rt *rapid.T) { - msg, ok := (*OnionMessage)(nil).RandTestMessage( - rt, - ).(*OnionMessage) - require.True( - rt, ok, "RandTestMessage did "+ - "not return an OnionMessage", - ) - - var buf bytes.Buffer - written, err := WriteMessage(&buf, msg, 0) - require.NoError(rt, err, "WriteMessage error") - require.Equal(rt, written, msg.WireSize(), - "WireSize=%d, WriteMessage wrote=%d bytes", - msg.WireSize(), written, - ) - }) -} diff --git a/lnwire/onion_msg_payload.go b/lnwire/onion_msg_payload.go deleted file mode 100644 index 157ebdc8b..000000000 --- a/lnwire/onion_msg_payload.go +++ /dev/null @@ -1,290 +0,0 @@ -package lnwire - -import ( - "bytes" - "errors" - "fmt" - "io" - "sort" - - "github.com/lightningnetwork/lnd/tlv" -) - -const ( - // finalHopPayloadStart is the inclusive beginning of the tlv type - // range that is reserved for payloads for the final hop. - finalHopPayloadStart tlv.Type = 64 - - // replyPathType is a record for onion messaging reply paths. - replyPathType tlv.Type = 2 - - // encryptedDataTLVType is a record containing encrypted data for - // message recipient. - encryptedDataTLVType tlv.Type = 4 - - // InvoiceRequestNamespaceType is a record containing the sub-namespace - // of tlvs that request invoices for offers. - InvoiceRequestNamespaceType tlv.Type = 64 - - // InvoiceNamespaceType is a record containing the sub-namespace of - // tlvs that describe an invoice. - InvoiceNamespaceType tlv.Type = 66 - - // InvoiceErrorNamespaceType is a record containing the sub-namespace of - // tlvs that describe an invoice error. - InvoiceErrorNamespaceType tlv.Type = 68 -) - -// ErrNotFinalPayload is returned when a final hop payload is not within the -// correct range. -var ErrNotFinalPayload = errors.New("final hop payloads type should be >= 64") - -// ErrUnknownEvenType is returned when an onion message payload contains an -// unknown even TLV type. BOLT 4 requires the whole message to be ignored in -// this case, because even types are "must understand". -var ErrUnknownEvenType = errors.New("onion message payload contains unknown " + - "even tlv type") - -// ErrMultipleFinalHopPayloads is returned when an onion message payload for the -// final hop contains more than one payload field (tlv type >= 64). BOLT 4 -// requires the message to be ignored in this case. -var ErrMultipleFinalHopPayloads = errors.New("onion message payload contains " + - "more than one final hop payload field") - -// OnionMessagePayload contains the contents of an onion message payload. -type OnionMessagePayload struct { - // ReplyPath contains a blinded path that can be used to respond to an - // onion message. - ReplyPath *BlindedPath - - // EncryptedData contains encrypted data for the recipient. - EncryptedData []byte - - // FinalHopTLVs contains any TLVs with type >= 64 that are reserved for - // the final hop's payload. - FinalHopTLVs []*FinalHopTLV -} - -// NewOnionMessagePayload creates a new OnionMessagePayload. -func NewOnionMessagePayload() *OnionMessagePayload { - return &OnionMessagePayload{} -} - -// Encode encodes an onion message's payload. -// -// This is part of the lnwire.Message interface. -func (o *OnionMessagePayload) Encode() ([]byte, error) { - var records []tlv.Record - - if o.ReplyPath != nil { - records = append(records, o.ReplyPath.Record()) - } - - if len(o.EncryptedData) != 0 { - record := tlv.MakePrimitiveRecord( - encryptedDataTLVType, &o.EncryptedData, - ) - records = append(records, record) - } - - for _, finalHopTLV := range o.FinalHopTLVs { - if err := finalHopTLV.Validate(); err != nil { - return nil, err - } - - // Create a primitive record that just writes the final hop - // tlv's bytes as-is. The creating function should have - // encoded the value correctly. - record := tlv.MakePrimitiveRecord( - finalHopTLV.TLVType, &finalHopTLV.Value, - ) - records = append(records, record) - } - - // Sort our records just in case the final hop payload records were - // provided in the incorrect order. - tlv.SortRecords(records) - - stream, err := tlv.NewStream(records...) - if err != nil { - return nil, fmt.Errorf("new stream: %w", err) - } - - b := new(bytes.Buffer) - if err := stream.Encode(b); err != nil { - return nil, fmt.Errorf("encode stream: %w", err) - } - - return b.Bytes(), nil -} - -// Decode decodes an onion message's payload. -// -// This is part of the lnwire.Message interface. -func (o *OnionMessagePayload) Decode(r io.Reader) (map[tlv.Type][]byte, error) { - var ( - invoicePayload = &FinalHopTLV{ - TLVType: InvoiceNamespaceType, - } - - invoiceErrorPayload = &FinalHopTLV{ - TLVType: InvoiceErrorNamespaceType, - } - - invoiceRequestPayload = &FinalHopTLV{ - TLVType: InvoiceRequestNamespaceType, - } - ) - - // replyPath is used for decoding, we will later check if it was - // actually present and assign it to the message struct. - var replyPath BlindedPath - - records := []tlv.Record{ - replyPath.Record(), - tlv.MakePrimitiveRecord( - encryptedDataTLVType, &o.EncryptedData, - ), - // Add a record for invoice request sub-namespace so that we - // won't fail on the even tlv - reasoning below. - tlv.MakePrimitiveRecord( - InvoiceRequestNamespaceType, - &invoiceRequestPayload.Value, - ), - // Add records to read invoice and invoice errors sub-namespaces - // out. Although this is technically one of our "final hop - // payload" tlvs, it is an even value, so we need to include it - // as a known tlv here, or decoding will fail. We decode - // directly into a final hop payload, so that we can just add it - // if present later. - tlv.MakePrimitiveRecord( - InvoiceNamespaceType, - &invoicePayload.Value, - ), - tlv.MakePrimitiveRecord( - InvoiceErrorNamespaceType, - &invoiceErrorPayload.Value, - ), - } - - stream, err := tlv.NewStream(records...) - if err != nil { - return nil, fmt.Errorf("new stream: %w", err) - } - - tlvMap, err := stream.DecodeWithParsedTypesP2P(r) - if err != nil { - return tlvMap, fmt.Errorf("decode stream: %w", err) - } - - if _, ok := tlvMap[replyPathType]; ok { - o.ReplyPath = &replyPath - } - - // Once we're decoded our message, we want to also include any tlvs - // that are intended for the final hop's payload which we may not have - // recognized. We'll just directly read these out and allow higher - // application layers to deal with them. - for tlvType, tlvBytes := range tlvMap { - // Skip any tlvs that have been recognized in our decoding. - // DecodeWithParsedTypesP2P stores a nil entry for known types - // that it decoded into a dedicated field above, and the raw - // bytes for unknown types. A nil check (rather than a length - // check) is required so that a valid unknown odd tlv with a - // zero-length value is not mistaken for a recognized type. - if tlvBytes == nil { - continue - } - - // BOLT 4: if the onionmsg_tlv contains unknown even types, the - // whole message must be ignored, since even types are - // "must understand". This applies regardless of the type range, - // so we check it before skipping types outside the final hop - // range. - if tlvType%2 == 0 { - return tlvMap, fmt.Errorf("%w: %v", ErrUnknownEvenType, - tlvType) - } - - // Skip any unknown odd tlvs outside the final hop payload - // range: they are not addressed to the final hop's application - // layer, and odd types are safe to ignore. - if tlvType < finalHopPayloadStart { - continue - } - - // Add the unknown odd final hop payload to our message so that - // higher application layers can deal with it. - payload := &FinalHopTLV{ - TLVType: tlvType, - Value: tlvBytes, - } - - o.FinalHopTLVs = append( - o.FinalHopTLVs, payload, - ) - } - - // If we read out an invoice, invoice error or invoice request tlv - // sub-namespace, add it to our set of final payloads. This value won't - // have been added in the loop above, because we recognized the TLV so - // tlvMap[invoiceType].tlvBytes will be nil (thus, skipped above). - if _, ok := tlvMap[InvoiceNamespaceType]; ok { - o.FinalHopTLVs = append( - o.FinalHopTLVs, invoicePayload, - ) - } - - if _, ok := tlvMap[InvoiceErrorNamespaceType]; ok { - o.FinalHopTLVs = append( - o.FinalHopTLVs, invoiceErrorPayload, - ) - } - - if _, ok := tlvMap[InvoiceRequestNamespaceType]; ok { - o.FinalHopTLVs = append( - o.FinalHopTLVs, invoiceRequestPayload, - ) - } - - // BOLT 4: the final node must ignore an onion message whose - // onionmsg_tlv contains more than one payload field (tlv type >= 64). - // Every entry in FinalHopTLVs is in the final hop range by - // construction, so its length is the number of payload fields present. - if len(o.FinalHopTLVs) > 1 { - return tlvMap, ErrMultipleFinalHopPayloads - } - - // Iteration through maps occurs in random order - sort final hop - // TLVs in ascending order to make this decoding function - // deterministic. - sort.SliceStable(o.FinalHopTLVs, func(i, j int) bool { - return o.FinalHopTLVs[i].TLVType < - o.FinalHopTLVs[j].TLVType - }) - - return tlvMap, nil -} - -// FinalHopTLV contains values reserved for the final hop, which are just -// directly read from the tlv stream. -type FinalHopTLV struct { - // TLVType is the type for the payload. - TLVType tlv.Type - - // Value is the raw byte value read for this tlv type. This field is - // expected to contain "sub-tlv" namespaces, and will require further - // decoding to be used. - Value []byte -} - -// Validate performs validation of items added to the final hop's payload in an -// onion. This function returns an error if a tlv is not within the range -// reserved for final payload. -func (f *FinalHopTLV) Validate() error { - if f.TLVType < finalHopPayloadStart { - return fmt.Errorf("%w: %v", ErrNotFinalPayload, f.TLVType) - } - - return nil -} diff --git a/lnwire/onion_msg_payload_test.go b/lnwire/onion_msg_payload_test.go deleted file mode 100644 index f5345ea91..000000000 --- a/lnwire/onion_msg_payload_test.go +++ /dev/null @@ -1,539 +0,0 @@ -package lnwire - -import ( - "bytes" - "fmt" - "testing" - - "github.com/lightningnetwork/lnd/tlv" - "github.com/stretchr/testify/require" - "pgregory.net/rapid" -) - -// makeBlindedPath creates a BlindedPath with the given number of hops for -// testing. Each hop has a random blinded node ID and some cipher text. -func makeBlindedPath(t *testing.T, numHops int) *BlindedPath { - t.Helper() - - introKey, err := randPubKey() - require.NoError(t, err) - - blindingKey, err := randPubKey() - require.NoError(t, err) - - hops := make([]BlindedHop, numHops) - for i := range hops { - nodePub, err := randPubKey() - require.NoError(t, err) - - hops[i].BlindedNodeID = nodePub - hops[i].EncryptedData = bytes.Repeat([]byte{byte(i + 1)}, 32) - } - - return &BlindedPath{ - IntroductionNode: PubkeyIntro{Pubkey: introKey}, - BlindingPoint: blindingKey, - Hops: hops, - } -} - -// assertBlindedPathEqual compares two BlindedPaths field-by-field. Direct -// require.Equal would also work, but the per-field assertions surface -// localised mismatches for easier triage. -func assertBlindedPathEqual(t *testing.T, expected, actual *BlindedPath) { - t.Helper() - - require.Equal( - t, expected.IntroductionNode, actual.IntroductionNode, - "IntroductionNode mismatch", - ) - require.Equal( - t, expected.BlindingPoint, actual.BlindingPoint, - "BlindingPoint mismatch", - ) - require.Len(t, actual.Hops, len(expected.Hops)) - - for i := range expected.Hops { - require.Equal( - t, expected.Hops[i].BlindedNodeID, - actual.Hops[i].BlindedNodeID, - "hop %d: BlindedNodeID mismatch", i, - ) - require.Equal( - t, expected.Hops[i].EncryptedData, - actual.Hops[i].EncryptedData, - "hop %d: EncryptedData mismatch", i, - ) - } -} - -// encodeAndDecode is a helper that encodes a payload and decodes it into a -// fresh OnionMessagePayload. -func encodeAndDecode(t *testing.T, - original *OnionMessagePayload) *OnionMessagePayload { - - t.Helper() - - encoded, err := original.Encode() - require.NoError(t, err) - - decoded := NewOnionMessagePayload() - _, err = decoded.Decode(bytes.NewReader(encoded)) - require.NoError(t, err) - - return decoded -} - -// TestOnionMessagePayloadRoundTrip tests encode/decode roundtrips for various -// payload configurations. -func TestOnionMessagePayloadRoundTrip(t *testing.T) { - t.Parallel() - - t.Run("only reply path", func(t *testing.T) { - t.Parallel() - - original := &OnionMessagePayload{ - ReplyPath: makeBlindedPath(t, 3), - } - - decoded := encodeAndDecode(t, original) - - require.NotNil(t, decoded.ReplyPath) - assertBlindedPathEqual(t, original.ReplyPath, decoded.ReplyPath) - require.Empty(t, decoded.EncryptedData) - require.Empty(t, decoded.FinalHopTLVs) - }) - - t.Run("sciddir intro reply path", func(t *testing.T) { - t.Parallel() - - path := makeBlindedPath(t, 2) - path.IntroductionNode = SciddirIntro{ - Direction: 0x01, - SCID: [scidLen]byte{ - 0x00, 0x11, 0x22, 0x33, - 0x44, 0x55, 0x66, 0x77, - }, - } - - original := &OnionMessagePayload{ReplyPath: path} - - decoded := encodeAndDecode(t, original) - - require.NotNil(t, decoded.ReplyPath) - require.IsType( - t, SciddirIntro{}, decoded.ReplyPath.IntroductionNode, - ) - assertBlindedPathEqual(t, original.ReplyPath, decoded.ReplyPath) - }) - - t.Run("only encrypted data", func(t *testing.T) { - t.Parallel() - - original := &OnionMessagePayload{ - EncryptedData: []byte("encrypted-recipient-data"), - } - - decoded := encodeAndDecode(t, original) - - require.Nil(t, decoded.ReplyPath) - require.Equal( - t, original.EncryptedData, decoded.EncryptedData, - ) - require.Empty(t, decoded.FinalHopTLVs) - }) - - t.Run("reply path and encrypted data", func(t *testing.T) { - t.Parallel() - - original := &OnionMessagePayload{ - ReplyPath: makeBlindedPath(t, 2), - EncryptedData: []byte("test-ciphertext"), - } - - decoded := encodeAndDecode(t, original) - - require.NotNil(t, decoded.ReplyPath) - assertBlindedPathEqual(t, original.ReplyPath, decoded.ReplyPath) - require.Equal( - t, original.EncryptedData, decoded.EncryptedData, - ) - require.Empty(t, decoded.FinalHopTLVs) - }) - - t.Run("single hop reply path", func(t *testing.T) { - t.Parallel() - - original := &OnionMessagePayload{ - ReplyPath: makeBlindedPath(t, 1), - } - - decoded := encodeAndDecode(t, original) - - require.NotNil(t, decoded.ReplyPath) - assertBlindedPathEqual(t, original.ReplyPath, decoded.ReplyPath) - }) - - t.Run("final hop TLVs", func(t *testing.T) { - t.Parallel() - - original := &OnionMessagePayload{ - EncryptedData: []byte("ciphertext"), - FinalHopTLVs: []*FinalHopTLV{ - { - TLVType: InvoiceRequestNamespaceType, - Value: []byte("invoice-request"), - }, - }, - } - - decoded := encodeAndDecode(t, original) - - require.Equal( - t, original.EncryptedData, decoded.EncryptedData, - ) - require.Len(t, decoded.FinalHopTLVs, 1) - require.Equal( - t, InvoiceRequestNamespaceType, - decoded.FinalHopTLVs[0].TLVType, - ) - require.Equal( - t, original.FinalHopTLVs[0].Value, - decoded.FinalHopTLVs[0].Value, - ) - }) - - t.Run("multiple final hop payloads rejected", func(t *testing.T) { - t.Parallel() - - // BOLT 4 requires the final node to ignore an onion message - // that carries more than one final hop payload field, so decode - // must reject a payload bundling invoice_request, invoice, and - // invoice_error together. - original := &OnionMessagePayload{ - FinalHopTLVs: []*FinalHopTLV{ - { - TLVType: InvoiceRequestNamespaceType, - Value: []byte("request"), - }, - { - TLVType: InvoiceNamespaceType, - Value: []byte("invoice"), - }, - { - TLVType: InvoiceErrorNamespaceType, - Value: []byte("error"), - }, - }, - } - - encoded, err := original.Encode() - require.NoError(t, err) - - decoded := NewOnionMessagePayload() - _, err = decoded.Decode(bytes.NewReader(encoded)) - require.ErrorIs(t, err, ErrMultipleFinalHopPayloads) - }) - - t.Run("all fields populated", func(t *testing.T) { - t.Parallel() - - original := &OnionMessagePayload{ - ReplyPath: makeBlindedPath(t, 2), - EncryptedData: []byte("encrypted-data"), - FinalHopTLVs: []*FinalHopTLV{ - { - TLVType: InvoiceNamespaceType, - Value: []byte("invoice-data"), - }, - }, - } - - decoded := encodeAndDecode(t, original) - - require.NotNil(t, decoded.ReplyPath) - assertBlindedPathEqual(t, original.ReplyPath, decoded.ReplyPath) - require.Equal( - t, original.EncryptedData, decoded.EncryptedData, - ) - require.Len(t, decoded.FinalHopTLVs, 1) - require.Equal( - t, original.FinalHopTLVs[0].Value, - decoded.FinalHopTLVs[0].Value, - ) - }) - - t.Run("odd unknown final hop TLV", func(t *testing.T) { - t.Parallel() - - // Odd TLV types >= 64 that we don't explicitly recognize - // should be preserved as FinalHopTLVs. - original := &OnionMessagePayload{ - FinalHopTLVs: []*FinalHopTLV{ - { - TLVType: 65, - Value: []byte("custom-data"), - }, - }, - } - - decoded := encodeAndDecode(t, original) - - require.Len(t, decoded.FinalHopTLVs, 1) - require.Equal(t, tlv.Type(65), decoded.FinalHopTLVs[0].TLVType) - require.Equal( - t, []byte("custom-data"), - decoded.FinalHopTLVs[0].Value, - ) - }) - - t.Run("odd unknown zero-length final hop TLV", func(t *testing.T) { - t.Parallel() - - // A valid unknown odd tlv with a zero-length value must be - // preserved rather than mistaken for a recognized type, which - // is why decode keys off a nil entry instead of an empty one. - original := &OnionMessagePayload{ - FinalHopTLVs: []*FinalHopTLV{ - { - TLVType: 65, - Value: []byte{}, - }, - }, - } - - decoded := encodeAndDecode(t, original) - - require.Len(t, decoded.FinalHopTLVs, 1) - require.Equal(t, tlv.Type(65), decoded.FinalHopTLVs[0].TLVType) - require.Empty(t, decoded.FinalHopTLVs[0].Value) - }) - - t.Run("unknown even final hop type rejected", func(t *testing.T) { - t.Parallel() - - // Type 70 is in the final hop range but is an unknown even - // type, so BOLT 4 requires the message to be ignored. - original := &OnionMessagePayload{ - FinalHopTLVs: []*FinalHopTLV{ - { - TLVType: 70, - Value: []byte("must-understand"), - }, - }, - } - - encoded, err := original.Encode() - require.NoError(t, err) - - decoded := NewOnionMessagePayload() - _, err = decoded.Decode(bytes.NewReader(encoded)) - require.ErrorIs(t, err, ErrUnknownEvenType) - }) - - t.Run("unknown even type below range rejected", func(t *testing.T) { - t.Parallel() - - // An unknown even type outside the final hop range must also be - // rejected: the must-understand rule applies regardless of the - // tlv range. We build the stream directly because the encoder's - // FinalHopTLV.Validate would reject a sub-64 type. - val := []byte("data") - record := tlv.MakePrimitiveRecord(tlv.Type(6), &val) - - stream, err := tlv.NewStream(record) - require.NoError(t, err) - - var b bytes.Buffer - require.NoError(t, stream.Encode(&b)) - - decoded := NewOnionMessagePayload() - _, err = decoded.Decode(bytes.NewReader(b.Bytes())) - require.ErrorIs(t, err, ErrUnknownEvenType) - }) -} - -// TestFinalHopTLVValidate tests that FinalHopTLV.Validate correctly rejects -// types below the final hop range and accepts types within it. -func TestFinalHopTLVValidate(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - recordType tlv.Type - wantErr error - }{ - { - name: "type 0 rejected", - recordType: 0, - wantErr: ErrNotFinalPayload, - }, - { - name: "type 2 rejected", - recordType: 2, - wantErr: ErrNotFinalPayload, - }, - { - name: "type 63 rejected", - recordType: 63, - wantErr: ErrNotFinalPayload, - }, - { - name: "type 64 accepted", - recordType: 64, - }, - { - name: "type 65 accepted", - recordType: 65, - }, - { - name: "type 255 accepted", - recordType: 255, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - f := &FinalHopTLV{ - TLVType: tc.recordType, - Value: []byte("value"), - } - - err := f.Validate() - if tc.wantErr != nil { - require.ErrorIs(t, err, tc.wantErr) - } else { - require.NoError(t, err) - } - }) - } -} - -// TestOnionMessagePayloadEncodeReplyPathNoHops tests that encoding a reply path -// with zero hops returns an error. -func TestOnionMessagePayloadEncodeReplyPathNoHops(t *testing.T) { - t.Parallel() - - introKey, err := randPubKey() - require.NoError(t, err) - - blindingKey, err := randPubKey() - require.NoError(t, err) - - payload := &OnionMessagePayload{ - ReplyPath: &BlindedPath{ - IntroductionNode: PubkeyIntro{Pubkey: introKey}, - BlindingPoint: blindingKey, - Hops: nil, - }, - } - - _, err = payload.Encode() - require.ErrorIs(t, err, ErrEmptyBlindedPath) -} - -// TestOnionMessagePayloadEmpty tests that an empty payload roundtrips -// correctly. -func TestOnionMessagePayloadEmpty(t *testing.T) { - t.Parallel() - - original := NewOnionMessagePayload() - decoded := encodeAndDecode(t, original) - - require.Nil(t, decoded.ReplyPath) - require.Empty(t, decoded.EncryptedData) - require.Empty(t, decoded.FinalHopTLVs) -} - -// TestOnionMessagePayloadRoundTripQuickCheck uses property-based testing to -// verify that randomly generated OnionMessagePayload values survive -// encode/decode roundtrips. -func TestOnionMessagePayloadRoundTripQuickCheck(t *testing.T) { - t.Parallel() - - rapid.Check(t, func(t *rapid.T) { - original := &OnionMessagePayload{} - - // Optionally include a reply path. - if rapid.Bool().Draw(t, "hasReplyPath") { - original.ReplyPath = RandBlindedPath(t) - } - - // Optionally include encrypted data. - if rapid.Bool().Draw(t, "hasEncryptedData") { - dataLen := rapid.IntRange(1, 256).Draw( - t, "encryptedDataLen", - ) - original.EncryptedData = rapid.SliceOfN( - rapid.Byte(), dataLen, dataLen, - ).Draw(t, "encryptedData") - } - - // Optionally include a final hop payload. We use the three - // known even types (64, 66, 68) since unknown even types would - // cause decode to fail. At most one payload field is drawn - // because BOLT 4 requires decode to reject more than one. - knownTypes := []tlv.Type{ - InvoiceRequestNamespaceType, - InvoiceNamespaceType, - InvoiceErrorNamespaceType, - } - numFinalTLVs := rapid.IntRange(0, 1).Draw( - t, "numFinalTLVs", - ) - for i := range numFinalTLVs { - valLen := rapid.IntRange(1, 64).Draw( - t, fmt.Sprintf("finalTLVLen-%d", i), - ) - original.FinalHopTLVs = append( - original.FinalHopTLVs, - &FinalHopTLV{ - TLVType: knownTypes[i], - Value: rapid.SliceOfN( - rapid.Byte(), valLen, valLen, - ).Draw( - t, - fmt.Sprintf("finalTLV-%d", i), - ), - }, - ) - } - - // Encode. - encoded, err := original.Encode() - require.NoError(t, err) - - // Decode. - decoded := NewOnionMessagePayload() - _, err = decoded.Decode(bytes.NewReader(encoded)) - require.NoError(t, err) - - // Verify reply path. - if original.ReplyPath == nil { - require.Nil(t, decoded.ReplyPath) - } else { - require.NotNil(t, decoded.ReplyPath) - require.Equal( - t, original.ReplyPath, decoded.ReplyPath, - ) - } - - // Verify encrypted data. - require.Equal( - t, original.EncryptedData, decoded.EncryptedData, - ) - - // Verify final hop TLVs. - require.Len( - t, decoded.FinalHopTLVs, - len(original.FinalHopTLVs), - ) - for i, orig := range original.FinalHopTLVs { - dec := decoded.FinalHopTLVs[i] - require.Equal(t, orig.TLVType, dec.TLVType) - require.Equal(t, orig.Value, dec.Value) - } - }) -} diff --git a/lnwire/open_channel.go b/lnwire/open_channel.go index 6973aede3..1751f748b 100644 --- a/lnwire/open_channel.go +++ b/lnwire/open_channel.go @@ -5,8 +5,8 @@ import ( "io" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/tlv" ) diff --git a/lnwire/outpoint.go b/lnwire/outpoint.go index c1a978696..ec893ee65 100644 --- a/lnwire/outpoint.go +++ b/lnwire/outpoint.go @@ -4,7 +4,7 @@ import ( "bytes" "io" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/tlv" ) diff --git a/lnwire/outpoint_test.go b/lnwire/outpoint_test.go index 656714978..b8ac1a70d 100644 --- a/lnwire/outpoint_test.go +++ b/lnwire/outpoint_test.go @@ -4,8 +4,8 @@ import ( "bytes" "testing" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/tlv" "github.com/stretchr/testify/require" "pgregory.net/rapid" diff --git a/lnwire/partial_sig.go b/lnwire/partial_sig.go index d5af8ec90..1751ae5ce 100644 --- a/lnwire/partial_sig.go +++ b/lnwire/partial_sig.go @@ -210,10 +210,6 @@ func partialSigWithNonceTypeDecoder(r io.Reader, val interface{}, buf *[8]byte, return err } - if err := ValidateMusig2Nonce(nonce); err != nil { - return err - } - *v = PartialSigWithNonce{ PartialSig: NewPartialSig(s), Nonce: nonce, diff --git a/lnwire/ping.go b/lnwire/ping.go index b864c4021..230187b84 100644 --- a/lnwire/ping.go +++ b/lnwire/ping.go @@ -47,8 +47,10 @@ func (p *Ping) Decode(r io.Reader, pver uint32) error { return err } - // Values above MaxPongBytes are still valid on the wire. Per BOLT 1, - // receivers must ignore those pings rather than fail deserialization. + if p.NumPongBytes > MaxPongBytes { + return ErrMaxPongBytesExceeded + } + return nil } diff --git a/lnwire/ping_test.go b/lnwire/ping_test.go deleted file mode 100644 index adbef29dc..000000000 --- a/lnwire/ping_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package lnwire - -import ( - "bytes" - "strconv" - "testing" - - "github.com/stretchr/testify/require" -) - -// TestPingDecodeAllowsNoReplyPongSizes asserts that ping messages using the -// BOLT 1 no-reply sentinel range still deserialize successfully. -func TestPingDecodeAllowsNoReplyPongSizes(t *testing.T) { - t.Parallel() - - // Arrange: Pick values from the BOLT 1 no-reply range. These - // pings are valid on the wire and should decode successfully - // even though they do not require a pong response. - testCases := []uint16{65532, 65535} - - for _, numPongBytes := range testCases { - testName := strconv.FormatUint(uint64(numPongBytes), 10) - - t.Run(testName, func(t *testing.T) { - // Arrange: Encode a ping carrying a no-reply pong - // size together with a small payload so we exercise - // the normal wire format. - var buf bytes.Buffer - - want := &Ping{ - NumPongBytes: numPongBytes, - PaddingBytes: PingPayload{1, 2, 3}, - } - - _, err := WriteMessage(&buf, want, 0) - require.NoError(t, err) - - // Act: Decode the serialized message through the - // standard parser. - msg, err := ReadMessage(bytes.NewReader(buf.Bytes()), 0) - require.NoError(t, err) - - // Assert: The decoded ping matches the original - // input exactly. - got, ok := msg.(*Ping) - require.True(t, ok) - require.Equal(t, want, got) - }) - } -} diff --git a/lnwire/pong.go b/lnwire/pong.go index f4e100267..b5fca24c9 100644 --- a/lnwire/pong.go +++ b/lnwire/pong.go @@ -2,6 +2,7 @@ package lnwire import ( "bytes" + "fmt" "io" ) @@ -10,6 +11,10 @@ import ( // 2 bytes, leaving 65531 bytes. const MaxPongBytes = 65531 +// ErrMaxPongBytesExceeded indicates that the NumPongBytes field from the ping +// message has exceeded MaxPongBytes. +var ErrMaxPongBytesExceeded = fmt.Errorf("pong bytes exceeded") + // PongPayload is a set of opaque bytes sent in response to a ping message. type PongPayload []byte diff --git a/lnwire/pure_tlv.go b/lnwire/pure_tlv.go index 6692ac2f5..8e6f7bd9f 100644 --- a/lnwire/pure_tlv.go +++ b/lnwire/pure_tlv.go @@ -23,12 +23,12 @@ const ( ) // PureTLVMessage describes an LN message that is a pure TLV stream. If the -// message includes a signature, the signature covers a subset of the records, -// which subset is determined by the protocol's signed/unsigned range (see -// SerialiseFieldsToSignFn). +// message includes a signature, it will sign all the TLV records in the +// inclusive ranges: 0 to 159 and 1000000000 to 2999999999. type PureTLVMessage interface { - // AllRecords returns all the TLV records for the message, including - // both records we know about and unknown records that we preserve. + // AllRecords returns all the TLV records for the message. This will + // include all the records we know about along with any that we don't + // know about but that fall in the signed TLV range. AllRecords() []tlv.Record } @@ -37,27 +37,13 @@ func EncodePureTLVMessage(msg PureTLVMessage, buf *bytes.Buffer) error { return EncodeRecordsTo(buf, msg.AllRecords()) } -// UnsignedRangeFunc returns true when a TLV type is in the unsigned range of a -// pure-TLV message (i.e., excluded from the signature). Each protocol supplies -// its own predicate to encode the boundary between signed and unsigned types. -type UnsignedRangeFunc func(tlv.Type) bool - // SerialiseFieldsToSign serialises all the records from the given -// PureTLVMessage that fall within the BOLT 7 v2 signed TLV range. Use -// SerialiseFieldsToSignFn for a protocol with a different boundary. +// PureTLVMessage that fall within the signed TLV range. func SerialiseFieldsToSign(msg PureTLVMessage) ([]byte, error) { - return SerialiseFieldsToSignFn(msg, InUnsignedRange) -} - -// SerialiseFieldsToSignFn serialises all the records from the given -// PureTLVMessage that the supplied predicate keeps in the signed range. A type -// for which isUnsigned returns true is excluded from the digest. -func SerialiseFieldsToSignFn(msg PureTLVMessage, - isUnsigned UnsignedRangeFunc) ([]byte, error) { - + // Filter out all the fields not in the signed ranges. var signedRecords []tlv.Record for _, record := range msg.AllRecords() { - if isUnsigned(record.Type()) { + if InUnsignedRange(record.Type()) { continue } @@ -72,9 +58,8 @@ func SerialiseFieldsToSignFn(msg PureTLVMessage, return buf.Bytes(), nil } -// InUnsignedRange is the BOLT 7 v2 UnsignedRangeFunc: it returns true for types -// in 160-999_999_999 or 3_000_000_000+, which sit outside the BOLT 7 v2 signed -// ranges (0-159 and 1_000_000_000-2_999_999_999). +// InUnsignedRange returns true if the given TLV type falls outside the TLV +// ranges that the signature of a pure TLV message will cover. func InUnsignedRange(t tlv.Type) bool { return (t >= pureTLVUnsignedRangeOneStart && t < pureTLVSignedSecondRangeStart) || @@ -87,41 +72,32 @@ func InUnsignedRange(t tlv.Type) bool { // for re-composing the wire message since the signature covers these fields. type ExtraSignedFields map[uint64][]byte -// ExtraSignedFieldsFromTypeMap returns the unhandled signed-range entries from -// a tlv.TypeMap (as returned by DecodeWithParsedTypes(P2P)) so the caller can -// re-emit them and keep the message signature valid. It uses the BOLT 7 v2 -// signed range; use ExtraSignedFieldsFromTypeMapFn for a different boundary. +// ExtraSignedFieldsFromTypeMap is a helper that can be used alongside calls to +// the tlv.Stream DecodeWithParsedTypesP2P or DecodeWithParsedTypes methods to +// extract the tlv type and value pairs in the defined PureTLVMessage signed +// range which we have not handled with any of our defined Records. These +// methods will return a tlv.TypeMap containing the records that were extracted +// from an io.Reader. If the record was know and handled by a defined record, +// then the value accompanying the record's type in the map will be nil. +// Otherwise, if the record was unhandled, it will be non-nil. func ExtraSignedFieldsFromTypeMap(m tlv.TypeMap) ExtraSignedFields { - return ExtraSignedFieldsFromTypeMapFn(m, InUnsignedRange) -} - -// ExtraSignedFieldsFromTypeMapFn returns the unhandled entries from a -// tlv.TypeMap that the supplied predicate keeps in the signed range, so the -// caller can re-emit them and keep the message signature valid. Entries for -// which isUnsigned returns true are dropped. -func ExtraSignedFieldsFromTypeMapFn(m tlv.TypeMap, - isUnsigned UnsignedRangeFunc) ExtraSignedFields { - extraFields := make(ExtraSignedFields) for t, v := range m { - // A nil value signals that this type was consumed by one of the - // typed records passed to the TLV stream decoder, so its bytes - // are already represented elsewhere and do not need to be - // tracked here. + // If the value in the type map is nil, then it indicates that + // we know this type, and it was handled by one of the records + // we passed to the decode function vai the TLV stream. if v == nil { continue } - // Types the predicate places outside the signed range fall - // outside the signature's coverage, so they do not need to - // survive into re-encoding. - if isUnsigned(t) { + // No need to keep this field if it is unknown to us and is not + // in the sign range. + if InUnsignedRange(t) { continue } - // The remaining types are unhandled but within the signed - // range; preserve their raw bytes so the message can re-emit - // them verbatim and the signature stays valid. + // Otherwise, this is an un-handled type, so we keep track of + // it for signature validation and re-encoding later on. extraFields[uint64(t)] = v } diff --git a/lnwire/pure_tlv_test.go b/lnwire/pure_tlv_test.go index 9148678d2..a81a89ecb 100644 --- a/lnwire/pure_tlv_test.go +++ b/lnwire/pure_tlv_test.go @@ -387,89 +387,3 @@ func (g *MsgV2) AllRecords() []tlv.Record { return ProduceRecordsSorted(recordProducers...) } - -// mockPureTLVMessage is a minimal PureTLVMessage backed by a fixed record -// slice, used to exercise the predicate-driven helpers. -type mockPureTLVMessage struct { - records []tlv.Record -} - -func (m *mockPureTLVMessage) AllRecords() []tlv.Record { - return m.records -} - -// TestSerialiseFieldsToSignFn verifies that the serialiser correctly filters -// records based on the provided predicate before encoding. -func TestSerialiseFieldsToSignFn(t *testing.T) { - t.Parallel() - - var ( - signedVal uint16 = 11 - unsignedVal uint16 = 22 - ) - - msg := &mockPureTLVMessage{ - records: []tlv.Record{ - tlv.MakePrimitiveRecord(5, &signedVal), - tlv.MakePrimitiveRecord(10, &unsignedVal), - }, - } - - // Predicate that defines type 10 as unsigned (excluded). - isUnsigned := func(typ tlv.Type) bool { - return typ == 10 - } - - encoded, err := SerialiseFieldsToSignFn(msg, isUnsigned) - require.NoError(t, err) - - // Only type 5 should be encoded (type 5, length 2, value 11). - require.Equal(t, []byte{0x05, 0x02, 0x00, 0x0b}, encoded) -} - -// TestExtraSignedFieldsFromTypeMapFn confirms the predicate-driven variant -// keeps and drops the right type ranges for callers whose signed range is not -// the BOLT 7 v2 default. It also locks in the round-trip identity with the -// convenience wrapper. -func TestExtraSignedFieldsFromTypeMapFn(t *testing.T) { - t.Parallel() - - // Bolt12 signature TLVs sit at 240-1000 and are excluded from the - // signed Merkle tree. Everything else is signed. - bolt12Unsigned := func(typ tlv.Type) bool { - return typ >= 240 && typ <= 1000 - } - - typeMap := tlv.TypeMap{ - // Handled by a typed record on the receiver. - tlv.Type(2): nil, - - // Unknown type in the bolt12 signed range — must survive. - tlv.Type(99): { - 0x01, - }, - - // Bolt12 signature TLV — must be dropped. - tlv.Type(240): { - 0x02, - }, - - // Bolt12 second-range type — signed for bolt12, signed for the - // BOLT 7 v2 default too. - tlv.Type(1_500_000_000): { - 0x03, - }, - } - - gotBolt12 := ExtraSignedFieldsFromTypeMapFn(typeMap, bolt12Unsigned) - require.Len(t, gotBolt12, 2) - require.Equal(t, []byte{0x01}, gotBolt12[99]) - require.Equal(t, []byte{0x03}, gotBolt12[1_500_000_000]) - - gotDefault := ExtraSignedFieldsFromTypeMap(typeMap) - // In the BOLT 7 v2 range, type 99 is signed but type 240 is unsigned. - require.Len(t, gotDefault, 2) - require.Equal(t, []byte{0x01}, gotDefault[99]) - require.Equal(t, []byte{0x03}, gotDefault[1_500_000_000]) - require.NotContains(t, gotDefault, uint64(240)) -} diff --git a/lnwire/query_channel_range.go b/lnwire/query_channel_range.go index cdacdf16e..c816a0050 100644 --- a/lnwire/query_channel_range.go +++ b/lnwire/query_channel_range.go @@ -5,7 +5,7 @@ import ( "io" "math" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/tlv" ) diff --git a/lnwire/query_channel_range_test.go b/lnwire/query_channel_range_test.go index 53c6bda94..5d690f38d 100644 --- a/lnwire/query_channel_range_test.go +++ b/lnwire/query_channel_range_test.go @@ -39,6 +39,7 @@ func TestQueryChannelRange(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/lnwire/query_short_chan_ids.go b/lnwire/query_short_chan_ids.go index 3e98a2b15..37a73ab7c 100644 --- a/lnwire/query_short_chan_ids.go +++ b/lnwire/query_short_chan_ids.go @@ -3,20 +3,19 @@ package lnwire import ( "bytes" "compress/zlib" - "errors" "fmt" "io" "sort" "sync" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" ) const ( - // maxDecodedShortChanIDs is the maximum number of short channel IDs - // accepted from a single message. The plain encoding is also bounded - // by the wire size, so its check is defense in depth. - maxDecodedShortChanIDs = 100_000 + // maxZlibBufSize is the max number of bytes that we'll accept from a + // zlib decoding instance. We do this in order to limit the total + // amount of memory allocated during a decoding instance. + maxZlibBufSize = 67413630 ) // ErrUnsortedSIDs is returned when decoding a QueryShortChannelID request whose @@ -165,12 +164,6 @@ func decodeShortChanIDs(r io.Reader) (QueryEncoding, []ShortChannelID, error) { // compute the number of bytes encoded based on the size of the // query body. numShortChanIDs := len(queryBody) / 8 - if numShortChanIDs > maxDecodedShortChanIDs { - return 0, nil, fmt.Errorf( - "too many short channel IDs: max=%v, got=%v", - maxDecodedShortChanIDs, numShortChanIDs, - ) - } if numShortChanIDs == 0 { return encodingType, nil, nil } @@ -217,28 +210,61 @@ func decodeShortChanIDs(r io.Reader) (QueryEncoding, []ShortChannelID, error) { return encodingType, nil, nil } - decompressor, err := zlib.NewReader(bytes.NewReader(queryBody)) + // Before we start to decode, we'll create a limit reader over + // the current reader. This will ensure that we can control how + // much memory we're allocating during the decoding process. + limitedDecompressor, err := zlib.NewReader(&io.LimitedReader{ + R: bytes.NewReader(queryBody), + N: maxZlibBufSize, + }) if err != nil { return 0, nil, fmt.Errorf("unable to create zlib "+ "reader: %w", err) } - shortChanIDs, decodeErr := decodeCompressedShortChanIDs( - decompressor, + var ( + shortChanIDs []ShortChannelID + lastChanID ShortChannelID + i int ) - closeErr := decompressor.Close() + for { + // We'll now attempt to read the next short channel ID + // encoded in the payload. + var cid ShortChannelID + err := ReadElements(limitedDecompressor, &cid) - switch { - case decodeErr != nil: - return 0, nil, decodeErr + switch { + // If we get an EOF error, then that either means we've + // read all that's contained in the buffer, or have hit + // our limit on the number of bytes we'll read. In + // either case, we'll return what we have so far. + case err == io.ErrUnexpectedEOF || err == io.EOF: + return encodingType, shortChanIDs, nil - case closeErr != nil: - return 0, nil, fmt.Errorf( - "unable to close zlib reader: %w", closeErr, - ) + // Otherwise, we hit some other sort of error, possibly + // an invalid payload, so we'll exit early with the + // error. + case err != nil: + return 0, nil, fmt.Errorf("unable to "+ + "deflate next short chan "+ + "ID: %v", err) + } - default: - return encodingType, shortChanIDs, nil + // We successfully read the next ID, so we'll collect + // that in the set of final ID's to return. + shortChanIDs = append(shortChanIDs, cid) + + // Finally, we'll ensure that this short chan ID is + // greater than the last one. This is a requirement + // within the encoding, and if violated can aide us in + // detecting malicious payloads. This can only be true + // starting at the second chanID. + if i > 0 && cid.ToUint64() <= lastChanID.ToUint64() { + return 0, nil, ErrUnsortedSIDs{lastChanID, cid} + } + + lastChanID = cid + i++ } default: @@ -249,45 +275,6 @@ func decodeShortChanIDs(r io.Reader) (QueryEncoding, []ShortChannelID, error) { } } -// decodeCompressedShortChanIDs decodes and validates the decompressed short -// channel ID stream. -func decodeCompressedShortChanIDs(r io.Reader) ([]ShortChannelID, error) { - var ( - shortChanIDs []ShortChannelID - lastChanID ShortChannelID - ) - - for { - var cid ShortChannelID - err := ReadElements(r, &cid) - - switch { - // Only a clean EOF terminates the stream. A partial final ID - // returns io.ErrUnexpectedEOF and remains an error. - case errors.Is(err, io.EOF): - return shortChanIDs, nil - - case err != nil: - return nil, fmt.Errorf("unable to deflate next short "+ - "chan ID: %w", err) - } - - if len(shortChanIDs) == maxDecodedShortChanIDs { - return nil, fmt.Errorf("too many short channel IDs: "+ - "max=%v", maxDecodedShortChanIDs) - } - - if len(shortChanIDs) > 0 && - cid.ToUint64() <= lastChanID.ToUint64() { - - return nil, ErrUnsortedSIDs{lastChanID, cid} - } - - shortChanIDs = append(shortChanIDs, cid) - lastChanID = cid - } -} - // Encode serializes the target QueryShortChanIDs into the passed io.Writer // observing the protocol version specified. // diff --git a/lnwire/query_short_chan_ids_test.go b/lnwire/query_short_chan_ids_test.go index 30ecbbe06..996c9f744 100644 --- a/lnwire/query_short_chan_ids_test.go +++ b/lnwire/query_short_chan_ids_test.go @@ -3,9 +3,6 @@ package lnwire import ( "bytes" "testing" - - "github.com/stretchr/testify/require" - "pgregory.net/rapid" ) type unsortedSidTest struct { @@ -53,6 +50,7 @@ var ( // that contains duplicate or unsorted ids returns an ErrUnsortedSIDs failure. func TestQueryShortChanIDsUnsorted(t *testing.T) { for _, test := range unsortedSidTests { + test := test t.Run(test.name, func(t *testing.T) { req := &QueryShortChanIDs{ EncodingType: test.encType, @@ -98,6 +96,7 @@ func TestQueryShortChanIDsZero(t *testing.T) { } for _, test := range testCases { + test := test t.Run(test.name, func(t *testing.T) { req := &QueryShortChanIDs{ EncodingType: test.encoding, @@ -119,208 +118,3 @@ func TestQueryShortChanIDsZero(t *testing.T) { }) } } - -// TestQueryShortChanIDsRoundTrip uses property-based testing to ensure both -// supported encodings preserve sorted short channel ID sets. -func TestQueryShortChanIDsRoundTrip(t *testing.T) { - t.Parallel() - - rapid.Check(t, func(t *rapid.T) { - encoding := rapid.SampledFrom([]QueryEncoding{ - EncodingSortedPlain, - EncodingSortedZlib, - }).Draw(t, "encoding") - - numSCIDs := rapid.IntRange(0, 512).Draw(t, "num-scids") - var scids []ShortChannelID - if numSCIDs > 0 { - scids = make([]ShortChannelID, numSCIDs) - } - - offset := rapid.IntRange(0, 1_000_000).Draw(t, "offset") - step := rapid.IntRange(1, 1_000_000).Draw(t, "step") - for i := range scids { - scid := uint64(offset + i*step) - scids[i] = NewShortChanIDFromInt(scid) - } - - var b bytes.Buffer - require.NoError(t, encodeShortChanIDs( - &b, encoding, scids, - )) - - decodedEncoding, decoded, err := decodeShortChanIDs( - bytes.NewReader(b.Bytes()), - ) - require.NoError(t, err) - require.Equal(t, encoding, decodedEncoding) - require.Equal(t, scids, decoded) - }) -} - -// TestQueryShortChanIDsDecodeLimit ensures that a decompressed short channel -// ID stream cannot exceed its resource limit. -func TestQueryShortChanIDsDecodeLimit(t *testing.T) { - t.Parallel() - - var stream bytes.Buffer - for i := 0; i <= maxDecodedShortChanIDs; i++ { - require.NoError(t, WriteElements( - &stream, NewShortChanIDFromInt(uint64(i)), - )) - } - - decoded, err := decodeCompressedShortChanIDs(bytes.NewReader( - stream.Bytes()[:maxDecodedShortChanIDs*8], - )) - require.NoError(t, err) - require.Len(t, decoded, maxDecodedShortChanIDs) - - _, err = decodeCompressedShortChanIDs( - bytes.NewReader(stream.Bytes()), - ) - require.ErrorContains(t, err, "too many short channel IDs") -} - -// TestQueryShortChanIDsZlibCompatibility ensures that a protocol-valid -// compressed reply can contain far more short channel IDs than a plain reply. -// The plain encoding is bounded by the wire size at maxPlainReplySCIDs, so it -// is the compressed encoding that determines how much headroom a single reply -// actually has. -func TestQueryShortChanIDsZlibCompatibility(t *testing.T) { - t.Parallel() - - const ( - // maxWireMsgSize is the largest a message may be on the wire, - // including its type prefix. - maxWireMsgSize = MaxMsgBody + MessageTypeSize - - // maxPlainReplySCIDs is the number of SCIDs that saturate a - // ReplyChannelRange under the plain encoding. The message - // carries 41 bytes of fixed fields, and the SCID blob adds a - // 2-byte length prefix plus a 1-byte encoding type, leaving - // (65533 - 44) / 8 SCIDs. - maxPlainReplySCIDs = 8186 - - // maxZlibReplySCIDs is the number of consecutive SCIDs that - // saturate the same message under the zlib encoding. Runs of - // consecutive SCIDs are the best case for the compressor, so - // this is an upper bound rather than a figure real peers hit. - maxZlibReplySCIDs = 30_794 - ) - - // A reply full of consecutive SCIDs is what we'll size both encodings - // against. - newReply := func(enc QueryEncoding, n int) *ReplyChannelRange { - scids := make([]ShortChannelID, n) - for i := range scids { - scids[i] = NewShortChanIDFromInt(uint64(i)) - } - - return &ReplyChannelRange{ - Complete: 1, - EncodingType: enc, - ShortChanIDs: scids, - ExtraData: make([]byte, 0), - } - } - - // The plain encoding tops out at maxPlainReplySCIDs: that many SCIDs - // fit, and one more overflows the message. - plain := newReply(EncodingSortedPlain, maxPlainReplySCIDs) - size, err := plain.SerializedSize() - require.NoError(t, err) - require.LessOrEqual(t, size, uint32(maxWireMsgSize)) - - plain = newReply(EncodingSortedPlain, maxPlainReplySCIDs+1) - size, err = plain.SerializedSize() - require.NoError(t, err) - require.Greater(t, size, uint32(maxWireMsgSize)) - - // The zlib encoding fits far more SCIDs into the very same message, - // which is the compatibility property we care about: a compressed - // reply can carry a much larger slice of the graph than a plain one. - zlib := newReply(EncodingSortedZlib, maxZlibReplySCIDs) - size, err = zlib.SerializedSize() - require.NoError(t, err) - require.LessOrEqual(t, size, uint32(maxWireMsgSize)) - require.Greater(t, maxZlibReplySCIDs, maxPlainReplySCIDs) - - // One more SCID pushes the compressed reply over the wire limit, so - // maxZlibReplySCIDs really is the ceiling. - over := newReply(EncodingSortedZlib, maxZlibReplySCIDs+1) - size, err = over.SerializedSize() - require.NoError(t, err) - require.Greater(t, size, uint32(maxWireMsgSize)) - - // Finally, the saturated compressed reply must still round trip - // cleanly through the decoder. - var b bytes.Buffer - require.NoError(t, encodeShortChanIDs( - &b, EncodingSortedZlib, zlib.ShortChanIDs, - )) - - encoding, decoded, err := decodeShortChanIDs( - bytes.NewReader(b.Bytes()), - ) - require.NoError(t, err) - require.Equal(t, EncodingSortedZlib, encoding) - require.Equal(t, zlib.ShortChanIDs, decoded) -} - -// TestQueryShortChanIDsRejectsCorruptZlib ensures that truncated or corrupt -// compressed streams are not accepted as valid partial replies. -func TestQueryShortChanIDsRejectsCorruptZlib(t *testing.T) { - t.Parallel() - - scids := []ShortChannelID{ - NewShortChanIDFromInt(1), - NewShortChanIDFromInt(2), - NewShortChanIDFromInt(3), - } - - var encoded bytes.Buffer - require.NoError(t, encodeShortChanIDs( - &encoded, EncodingSortedZlib, scids, - )) - - body := encoded.Bytes()[2:] - corruptChecksum := append([]byte(nil), body...) - corruptChecksum[len(corruptChecksum)-1] ^= 1 - - tests := []struct { - name string - body []byte - }{ - { - name: "truncated header", - body: body[:2], - }, - { - name: "truncated checksum", - body: body[:len(body)-1], - }, - { - name: "corrupt checksum", - body: corruptChecksum, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - var message bytes.Buffer - require.NoError(t, WriteElements( - &message, uint16(len(test.body)), - )) - _, err := message.Write(test.body) - require.NoError(t, err) - - _, _, err = decodeShortChanIDs( - bytes.NewReader(message.Bytes()), - ) - require.Error(t, err) - }) - } -} diff --git a/lnwire/reply_channel_range.go b/lnwire/reply_channel_range.go index 63ffd21f4..c3a744ebd 100644 --- a/lnwire/reply_channel_range.go +++ b/lnwire/reply_channel_range.go @@ -7,7 +7,7 @@ import ( "math" "sort" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/tlv" ) diff --git a/lnwire/reply_channel_range_test.go b/lnwire/reply_channel_range_test.go index ac95066a5..12955cfd9 100644 --- a/lnwire/reply_channel_range_test.go +++ b/lnwire/reply_channel_range_test.go @@ -12,6 +12,7 @@ import ( // that contains duplicate or unsorted ids returns an ErrUnsortedSIDs failure. func TestReplyChannelRangeUnsorted(t *testing.T) { for _, test := range unsortedSidTests { + test := test t.Run(test.name, func(t *testing.T) { req := &ReplyChannelRange{ EncodingType: test.encType, @@ -62,6 +63,7 @@ func TestReplyChannelRangeEmpty(t *testing.T) { } for _, test := range emptyChannelsTests { + test := test t.Run(test.name, func(t *testing.T) { req := ReplyChannelRange{ FirstBlockHeight: 1, @@ -208,6 +210,7 @@ func TestReplyChannelRangeEncode(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -324,6 +327,7 @@ func TestReplyChannelRangeDecode(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/lnwire/reply_short_chan_ids_end.go b/lnwire/reply_short_chan_ids_end.go index ef43e8f77..2e50d840f 100644 --- a/lnwire/reply_short_chan_ids_end.go +++ b/lnwire/reply_short_chan_ids_end.go @@ -4,7 +4,7 @@ import ( "bytes" "io" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" ) // ReplyShortChanIDsEnd is a message that marks the end of a streaming message diff --git a/lnwire/revoke_and_ack.go b/lnwire/revoke_and_ack.go index e2a4c9307..3c9775c99 100644 --- a/lnwire/revoke_and_ack.go +++ b/lnwire/revoke_and_ack.go @@ -38,10 +38,6 @@ type RevokeAndAck struct { // remote nonce and the sender's local nonce. LocalNonce OptMusig2NonceTLV - // LocalNonces is an optional field that stores a map of local musig2 - // nonces, keyed by TXID. This is used for splice nonce coordination. - LocalNonces OptLocalNonces - // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. @@ -82,14 +78,8 @@ func (c *RevokeAndAck) Decode(r io.Reader, pver uint32) error { return err } - var ( - localNonce = c.LocalNonce.Zero() - localNoncesData LocalNoncesData - ) - - typeMap, err := tlvRecords.ExtractRecords( - &localNonce, &localNoncesData, - ) + localNonce := c.LocalNonce.Zero() + typeMap, err := tlvRecords.ExtractRecords(&localNonce) if err != nil { return err } @@ -98,9 +88,6 @@ func (c *RevokeAndAck) Decode(r io.Reader, pver uint32) error { if val, ok := typeMap[c.LocalNonce.TlvType()]; ok && val == nil { c.LocalNonce = tlv.SomeRecordT(localNonce) } - if val, ok := typeMap[(LocalNoncesRecordTypeDef)(nil).TypeVal()]; ok && val == nil { //nolint:ll - c.LocalNonces = SomeLocalNonces(localNoncesData) - } if len(tlvRecords) != 0 { c.ExtraData = tlvRecords @@ -114,13 +101,10 @@ func (c *RevokeAndAck) Decode(r io.Reader, pver uint32) error { // // This is part of the lnwire.Message interface. func (c *RevokeAndAck) Encode(w *bytes.Buffer, pver uint32) error { - recordProducers := make([]tlv.RecordProducer, 0, 2) + recordProducers := make([]tlv.RecordProducer, 0, 1) c.LocalNonce.WhenSome(func(localNonce Musig2NonceTLV) { recordProducers = append(recordProducers, &localNonce) }) - c.LocalNonces.WhenSome(func(ln LocalNoncesData) { - recordProducers = append(recordProducers, &ln) - }) err := EncodeMessageExtraData(&c.ExtraData, recordProducers...) if err != nil { return err diff --git a/lnwire/short_channel_id.go b/lnwire/short_channel_id.go index e26575001..73e37ab96 100644 --- a/lnwire/short_channel_id.go +++ b/lnwire/short_channel_id.go @@ -92,8 +92,7 @@ func DShortChannelID(r io.Reader, val interface{}, buf *[8]byte, if v, ok := val.(*ShortChannelID); ok { var scid uint64 - // tlv.DUint64 forces the length to be 8 bytes. - err := tlv.DUint64(r, &scid, buf, l) + err := tlv.DUint64(r, &scid, buf, 8) if err != nil { return err } diff --git a/lnwire/short_channel_id_test.go b/lnwire/short_channel_id_test.go index efc0cba40..2916f20d1 100644 --- a/lnwire/short_channel_id_test.go +++ b/lnwire/short_channel_id_test.go @@ -62,28 +62,3 @@ func TestScidTypeEncodeDecode(t *testing.T) { require.Contains(t, tlvs, AliasScidRecordType) require.Equal(t, aliasScid, aliasScid2) } - -// TestScidTypeDecodeInvalidLength ensures that decoding a ShortChannelID TLV -// with an invalid length (anything other than 8 bytes) fails with an error. -func TestScidTypeDecodeInvalidLength(t *testing.T) { - t.Parallel() - - aliasScid := ShortChannelID{ - BlockHeight: 1, TxIndex: 1, TxPosition: 1, - } - - var extraData ExtraOpaqueData - require.NoError(t, extraData.PackRecords(&aliasScid)) - - // Corrupt the TLV length field to simulate malformed input. - extraData[1] = 8 + 1 - - var out ShortChannelID - _, err := extraData.ExtractRecords(&out) - require.Error(t, err) - - extraData[1] = 8 - 1 - - _, err = extraData.ExtractRecords(&out) - require.Error(t, err) -} diff --git a/lnwire/shutdown.go b/lnwire/shutdown.go index a7330ca47..28df9a4ca 100644 --- a/lnwire/shutdown.go +++ b/lnwire/shutdown.go @@ -9,8 +9,6 @@ import ( type ( // ShutdownNonceType is the type of the shutdown nonce TLV record. - // This nonce represents the sender's "closee nonce" - the nonce they'll - // use when signing the other party's closing transaction. ShutdownNonceType = tlv.TlvType8 // ShutdownNonceTLV is the TLV record that contains the shutdown nonce. @@ -36,10 +34,8 @@ type Shutdown struct { // Address is the script to which the channel funds will be paid. Address DeliveryAddress - // ShutdownNonce is the musig2 nonce the sender will use when acting as - // the closee (signing the other party's closing transaction). For - // taproot channels with RBF support, subsequent nonces are sent using - // the JIT (just-in-time) pattern alongside signatures. + // ShutdownNonce is the nonce the sender will use to sign the first + // co-op sign offer. ShutdownNonce ShutdownNonceTLV // CustomRecords maps TLV types to byte slices, storing arbitrary data diff --git a/lnwire/signature_test.go b/lnwire/signature_test.go index c47bf97af..73263f1a6 100644 --- a/lnwire/signature_test.go +++ b/lnwire/signature_test.go @@ -273,6 +273,7 @@ func TestNewSigFromRawSignature(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { result, err := NewSigFromECDSARawSignature(tc.rawSig) require.Equal(t, tc.expectedErr, err) diff --git a/lnwire/test_message.go b/lnwire/test_message.go index 469b6208a..4e62bee98 100644 --- a/lnwire/test_message.go +++ b/lnwire/test_message.go @@ -8,8 +8,8 @@ import ( "net" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/tlv" "github.com/lightningnetwork/lnd/tor" @@ -376,7 +376,6 @@ func (a *ChannelReestablish) RandTestMessage(t *rapid.T) Message { // Randomly decide whether to include optional fields includeLocalNonce := rapid.Bool().Draw(t, "includeLocalNonce") includeDynHeight := rapid.Bool().Draw(t, "includeDynHeight") - includeLocalNonces := rapid.Bool().Draw(t, "includeLocalNonces") if includeLocalNonce { nonce := RandMusig2Nonce(t) @@ -388,29 +387,6 @@ func (a *ChannelReestablish) RandTestMessage(t *rapid.T) Message { msg.DynHeight = fn.Some(height) } - if includeLocalNonces { - numNonces := rapid.IntRange(0, 3).Draw(t, "numLocalNonces") - nonces := make(map[chainhash.Hash]Musig2Nonce) - for i := 0; i < numNonces; i++ { - txid := RandChainHash(t) - - // Ensure unique txids for the map. - for { - _, ok := nonces[txid] - if !ok { - break - } - txid = RandChainHash(t) - } - - nonces[txid] = RandMusig2Nonce(t) - } - - msg.LocalNonces = SomeLocalNonces( - LocalNoncesData{NoncesMap: nonces}, - ) - } - return msg } @@ -661,71 +637,25 @@ func (c *ClosingComplete) RandTestMessage(t *rapid.T) Message { } } - // Randomly decide between regular sigs and taproot sigs - useTaprootSigs := rapid.Bool().Draw(t, "useTaprootSigs") + if includeCloserNoClosee { + sig := RandSignature(t) + msg.CloserNoClosee = tlv.SomeRecordT( + tlv.NewRecordT[tlv.TlvType1, Sig](sig), + ) + } - if useTaprootSigs { - // For taproot channels, use PartialSigWithNonce - if includeCloserNoClosee { - partialSig := *RandPartialSig(t) - nonce := RandMusig2Nonce(t) - msg.TaprootClosingSigs.CloserNoClosee = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType5, PartialSigWithNonce]( //nolint:ll - PartialSigWithNonce{ - PartialSig: partialSig, - Nonce: nonce, - }, - ), - ) - } + if includeNoCloserClosee { + sig := RandSignature(t) + msg.NoCloserClosee = tlv.SomeRecordT( + tlv.NewRecordT[tlv.TlvType2, Sig](sig), + ) + } - if includeNoCloserClosee { - partialSig := *RandPartialSig(t) - nonce := RandMusig2Nonce(t) - msg.TaprootClosingSigs.NoCloserClosee = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType6, PartialSigWithNonce]( //nolint:ll - PartialSigWithNonce{ - PartialSig: partialSig, - Nonce: nonce, - }, - ), - ) - } - - if includeCloserAndClosee { - partialSig := *RandPartialSig(t) - nonce := RandMusig2Nonce(t) - msg.TaprootClosingSigs.CloserAndClosee = tlv.SomeRecordT( //nolint:ll - tlv.NewRecordT[tlv.TlvType7, PartialSigWithNonce]( //nolint:ll - PartialSigWithNonce{ - PartialSig: partialSig, - Nonce: nonce, - }, - ), - ) - } - } else { - // For non-taproot channels, use regular signatures - if includeCloserNoClosee { - sig := RandSignature(t) - msg.ClosingSigs.CloserNoClosee = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType1, Sig](sig), - ) - } - - if includeNoCloserClosee { - sig := RandSignature(t) - msg.ClosingSigs.NoCloserClosee = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType2, Sig](sig), - ) - } - - if includeCloserAndClosee { - sig := RandSignature(t) - msg.ClosingSigs.CloserAndClosee = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType3, Sig](sig), - ) - } + if includeCloserAndClosee { + sig := RandSignature(t) + msg.CloserAndClosee = tlv.SomeRecordT( + tlv.NewRecordT[tlv.TlvType3, Sig](sig), + ) } return msg @@ -744,13 +674,7 @@ func (c *ClosingSig) RandTestMessage(t *rapid.T) Message { ChannelID: RandChannelID(t), CloseeScript: RandDeliveryAddress(t), CloserScript: RandDeliveryAddress(t), - FeeSatoshis: btcutil.Amount(rapid.Int64Range(0, 1000000).Draw( - t, "feeSatoshis"), - ), - LockTime: rapid.Uint32Range(0, 0xffffffff).Draw( - t, "lockTime", - ), - ExtraData: RandExtraOpaqueData(t, nil), + ExtraData: RandExtraOpaqueData(t, nil), } includeCloserNoClosee := rapid.Bool().Draw(t, "includeCloserNoClosee") @@ -773,54 +697,25 @@ func (c *ClosingSig) RandTestMessage(t *rapid.T) Message { } } - // Randomly decide between regular sigs and taproot sigs - useTaprootSigs := rapid.Bool().Draw(t, "useTaprootSigs") + if includeCloserNoClosee { + sig := RandSignature(t) + msg.CloserNoClosee = tlv.SomeRecordT( + tlv.NewRecordT[tlv.TlvType1, Sig](sig), + ) + } - if useTaprootSigs { - // For taproot channels in ClosingSig, use just PartialSig (no - // nonce). - if includeCloserNoClosee { - partialSig := *RandPartialSig(t) - msg.TaprootPartialSigs.CloserNoClosee = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType5, PartialSig](partialSig), //nolint:ll - ) - } + if includeNoCloserClosee { + sig := RandSignature(t) + msg.NoCloserClosee = tlv.SomeRecordT( + tlv.NewRecordT[tlv.TlvType2, Sig](sig), + ) + } - if includeNoCloserClosee { - partialSig := *RandPartialSig(t) - msg.TaprootPartialSigs.NoCloserClosee = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType6, PartialSig](partialSig), //nolint:ll - ) - } - - if includeCloserAndClosee { - partialSig := *RandPartialSig(t) - msg.TaprootPartialSigs.CloserAndClosee = tlv.SomeRecordT( //nolint:ll - tlv.NewRecordT[tlv.TlvType7, PartialSig](partialSig), //nolint:ll - ) - } - } else { - // For non-taproot channels, use regular signatures - if includeCloserNoClosee { - sig := RandSignature(t) - msg.ClosingSigs.CloserNoClosee = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType1, Sig](sig), - ) - } - - if includeNoCloserClosee { - sig := RandSignature(t) - msg.ClosingSigs.NoCloserClosee = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType2, Sig](sig), - ) - } - - if includeCloserAndClosee { - sig := RandSignature(t) - msg.ClosingSigs.CloserAndClosee = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType3, Sig](sig), - ) - } + if includeCloserAndClosee { + sig := RandSignature(t) + msg.CloserAndClosee = tlv.SomeRecordT( + tlv.NewRecordT[tlv.TlvType3, Sig](sig), + ) } return msg @@ -927,28 +822,6 @@ func (c *Custom) RandTestMessage(t *rapid.T) Message { return msg } -// A compile time check to ensure OnionMessage implements the lnwire.TestMessage -// interface. -var _ TestMessage = (*OnionMessage)(nil) - -// RandTestMessage populates the message with random data suitable for testing. -// It uses the rapid testing framework to generate random values. -// -// This is part of the TestMessage interface. -func (o *OnionMessage) RandTestMessage(t *rapid.T) Message { - // Generate random compressed public key for node ID - pathKey := RandPubKey(t) - - dataLen := rapid.IntRange(0, 1000).Draw(t, "onionMessageDataLength") - data := rapid.SliceOfN(rapid.Byte(), dataLen, dataLen).Draw( - t, "onionMessageData", - ) - - msg := NewOnionMessage(pathKey, data) - - return msg -} - // A compile time check to ensure DynAck implements the lnwire.TestMessage // interface. var _ TestMessage = (*DynAck)(nil) @@ -1622,7 +1495,7 @@ var _ TestMessage = (*Ping)(nil) // // This is part of the TestMessage interface. func (p *Ping) RandTestMessage(t *rapid.T) Message { - numPongBytes := uint16(rapid.IntRange(0, math.MaxUint16).Draw( + numPongBytes := uint16(rapid.IntRange(0, int(MaxPongBytes)).Draw( t, "numPongBytes"), ) @@ -1847,36 +1720,17 @@ func (c *RevokeAndAck) RandTestMessage(t *rapid.T) Message { msg.NextRevocationKey = RandPubKey(t) if rapid.Bool().Draw(t, "includeLocalNonce") { - nonce := RandMusig2Nonce(t) + var nonce Musig2Nonce + nonceBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw( + t, "nonce", + ) + copy(nonce[:], nonceBytes) msg.LocalNonce = tlv.SomeRecordT( tlv.NewRecordT[NonceRecordTypeT, Musig2Nonce](nonce), ) } - if rapid.Bool().Draw(t, "includeLocalNonces") { - numNonces := rapid.IntRange(0, 3).Draw(t, "numLocalNonces") - nonces := make(map[chainhash.Hash]Musig2Nonce) - for i := 0; i < numNonces; i++ { - txid := RandChainHash(t) - - // Ensure unique txids for the map. - for { - _, ok := nonces[txid] - if !ok { - break - } - txid = RandChainHash(t) - } - - nonces[txid] = RandMusig2Nonce(t) - } - - msg.LocalNonces = SomeLocalNonces(LocalNoncesData{ - NoncesMap: nonces, - }) - } - return msg } diff --git a/lnwire/test_utils.go b/lnwire/test_utils.go index 6b0c872b4..ed8623034 100644 --- a/lnwire/test_utils.go +++ b/lnwire/test_utils.go @@ -7,8 +7,8 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/stretchr/testify/require" "pgregory.net/rapid" @@ -58,51 +58,6 @@ func RandPubKey(t *rapid.T) *btcec.PublicKey { return pub } -// RandBlindedPath generates a random blinded path with 1-5 hops, alternating -// between the pubkey and sciddir introduction-node variants per draw. -func RandBlindedPath(t *rapid.T) *BlindedPath { - useSciddir := rapid.Bool().Draw(t, "introIsSciddir") - - var intro IntroductionNode - if useSciddir { - var scid [scidLen]byte - copy(scid[:], rapid.SliceOfN( - rapid.Byte(), scidLen, scidLen, - ).Draw(t, "introScid")) - - dir := byte(rapid.IntRange(0, 1).Draw(t, "introDir")) - sciddir, err := NewSciddirIntro(dir, scid) - require.NoError(t, err) - intro = sciddir - } else { - pubkey, err := NewPubkeyIntro(RandPubKey(t)) - require.NoError(t, err) - intro = pubkey - } - - blindingPoint := RandPubKey(t) - - numHops := rapid.IntRange(1, 5).Draw(t, "numBlindedHops") - hops := make([]BlindedHop, numHops) - for i := range hops { - cipherLen := rapid.IntRange(1, 128).Draw( - t, fmt.Sprintf("cipherLen-%d", i), - ) - - hops[i].BlindedNodeID = RandPubKey(t) - - hops[i].EncryptedData = rapid.SliceOfN( - rapid.Byte(), cipherLen, cipherLen, - ).Draw(t, fmt.Sprintf("cipherText-%d", i)) - } - - return &BlindedPath{ - IntroductionNode: intro, - BlindingPoint: blindingPoint, - Hops: hops, - } -} - // RandChannelID generates a random channel ID. func RandChannelID(t *rapid.T) ChannelID { var c ChannelID @@ -316,16 +271,11 @@ func RandTLVRecords(t *rapid.T, ignoreRecords fn.Set[uint64], return customRecords, ignoreSet } -// RandMusig2Nonce generates a random musig2 nonce containing two valid -// compressed secp256k1 public keys. +// RandMusig2Nonce generates a random musig2 nonce. func RandMusig2Nonce(t *rapid.T) Musig2Nonce { - // A MuSig2 public nonce is two 33-byte compressed public keys. - pub1 := RandPubKey(t) - pub2 := RandPubKey(t) - var nonce Musig2Nonce - copy(nonce[:33], pub1.SerializeCompressed()) - copy(nonce[33:], pub2.SerializeCompressed()) + bytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "nonce") + copy(nonce[:], bytes) return nonce } diff --git a/lnwire/timestamp.go b/lnwire/timestamp.go deleted file mode 100644 index 0fe4db78e..000000000 --- a/lnwire/timestamp.go +++ /dev/null @@ -1,67 +0,0 @@ -package lnwire - -import "fmt" - -// Timestamp is an interface for channel/node update ordering values. A -// timestamp can represent either unix time (v1) or block height (v2). -type Timestamp interface { - // IsZero returns true if the timestamp has no value. - IsZero() bool - - // Cmp compares this timestamp to the passed timestamp. Implementations - // only support comparisons against the same concrete timestamp type. - Cmp(other Timestamp) (CompareResult, error) -} - -// UnixTimestamp is a unix-time based update timestamp, used by v1 gossip -// channels and nodes. -type UnixTimestamp uint64 - -// IsZero returns true if the timestamp has no value. -func (u UnixTimestamp) IsZero() bool { - return u == 0 -} - -// Cmp compares this timestamp to another unix timestamp. -func (u UnixTimestamp) Cmp(other Timestamp) (CompareResult, error) { - o, ok := other.(UnixTimestamp) - if !ok { - return 0, fmt.Errorf("expected UnixTimestamp, got: %T", other) - } - - switch { - case u < o: - return LessThan, nil - case u > o: - return GreaterThan, nil - default: - return EqualTo, nil - } -} - -// BlockHeightTimestamp is a block-height based update timestamp, used by v2 -// gossip channels and nodes. -type BlockHeightTimestamp uint32 - -// IsZero returns true if the timestamp has no value. -func (b BlockHeightTimestamp) IsZero() bool { - return b == 0 -} - -// Cmp compares this timestamp to another block-height timestamp. -func (b BlockHeightTimestamp) Cmp(other Timestamp) (CompareResult, error) { - o, ok := other.(BlockHeightTimestamp) - if !ok { - return 0, fmt.Errorf("expected BlockHeightTimestamp, got: %T", - other) - } - - switch { - case b < o: - return LessThan, nil - case b > o: - return GreaterThan, nil - default: - return EqualTo, nil - } -} diff --git a/lnwire/timestamp_test.go b/lnwire/timestamp_test.go deleted file mode 100644 index 01df3e658..000000000 --- a/lnwire/timestamp_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package lnwire - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -// TestUnixTimestamp tests the IsZero and Cmp methods of UnixTimestamp. -func TestUnixTimestamp(t *testing.T) { - t.Parallel() - - t.Run("IsZero", func(t *testing.T) { - t.Parallel() - - require.True(t, UnixTimestamp(0).IsZero()) - require.False(t, UnixTimestamp(1).IsZero()) - require.False(t, UnixTimestamp(1_000_000).IsZero()) - }) - - t.Run("Cmp", func(t *testing.T) { - t.Parallel() - - a := UnixTimestamp(100) - b := UnixTimestamp(200) - - result, err := a.Cmp(b) - require.NoError(t, err) - require.Equal(t, LessThan, result) - - result, err = b.Cmp(a) - require.NoError(t, err) - require.Equal(t, GreaterThan, result) - - c := UnixTimestamp(100) - result, err = a.Cmp(c) - require.NoError(t, err) - require.Equal(t, EqualTo, result) - }) - - t.Run("Cmp wrong type", func(t *testing.T) { - t.Parallel() - - _, err := UnixTimestamp(1).Cmp(BlockHeightTimestamp(1)) - require.ErrorContains(t, err, "expected UnixTimestamp") - }) -} - -// TestBlockHeightTimestamp tests the IsZero and Cmp methods of -// BlockHeightTimestamp. -func TestBlockHeightTimestamp(t *testing.T) { - t.Parallel() - - t.Run("IsZero", func(t *testing.T) { - t.Parallel() - - require.True(t, BlockHeightTimestamp(0).IsZero()) - require.False(t, BlockHeightTimestamp(1).IsZero()) - require.False(t, BlockHeightTimestamp(800_000).IsZero()) - }) - - t.Run("Cmp", func(t *testing.T) { - t.Parallel() - - a := BlockHeightTimestamp(500) - b := BlockHeightTimestamp(800) - - result, err := a.Cmp(b) - require.NoError(t, err) - require.Equal(t, LessThan, result) - - result, err = b.Cmp(a) - require.NoError(t, err) - require.Equal(t, GreaterThan, result) - - c := BlockHeightTimestamp(500) - result, err = a.Cmp(c) - require.NoError(t, err) - require.Equal(t, EqualTo, result) - }) - - t.Run("Cmp wrong type", func(t *testing.T) { - t.Parallel() - - _, err := BlockHeightTimestamp(1).Cmp(UnixTimestamp(1)) - require.ErrorContains(t, err, "expected BlockHeightTimestamp") - }) -} diff --git a/lnwire/typed_fee.go b/lnwire/typed_fee.go index f9b6c8d01..6b139f196 100644 --- a/lnwire/typed_fee.go +++ b/lnwire/typed_fee.go @@ -20,7 +20,7 @@ type Fee struct { // type from a given TLV stream. func (l *Fee) Record() tlv.Record { return tlv.MakeStaticRecord( - FeeRecordType, l, 8, feeEncoder, feeDecoder, + FeeRecordType, l, 8, feeEncoder, feeDecoder, //nolint:gomnd ) } @@ -41,7 +41,7 @@ func feeEncoder(w io.Writer, val interface{}, buf *[8]byte) error { // feeDecoder is a custom TLV decoder for the fee record. func feeDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error { v, ok := val.(*Fee) - if !ok || l != 8 { + if !ok { return tlv.NewTypeForDecodingErr(val, "lnwire.Fee", l, 8) } diff --git a/lnwire/typed_fee_test.go b/lnwire/typed_fee_test.go index f9d41e58f..a54b765ea 100644 --- a/lnwire/typed_fee_test.go +++ b/lnwire/typed_fee_test.go @@ -38,28 +38,3 @@ func testTypedFee(t *testing.T, fee Fee) { //nolint: thelper require.Equal(t, fee, extractedFee) } - -// TestTypedFeeTypeDecodeInvalidLength ensures that decoding a Fee TLV -// with an invalid length (anything other than 8 bytes) fails with an error. -func TestTypedFeeTypeDecodeInvalidLength(t *testing.T) { - t.Parallel() - - fee := Fee{ - BaseFee: 1, FeeRate: 1, - } - - var extraData ExtraOpaqueData - require.NoError(t, extraData.PackRecords(&fee)) - - // Corrupt the TLV length field to simulate malformed input. - extraData[3] = 8 + 1 - - var out Fee - _, err := extraData.ExtractRecords(&out) - require.Error(t, err) - - extraData[3] = 8 - 1 - - _, err = extraData.ExtractRecords(&out) - require.Error(t, err) -} diff --git a/lnwire/update_add_htlc.go b/lnwire/update_add_htlc.go index 38ceeec21..e627dbf4e 100644 --- a/lnwire/update_add_htlc.go +++ b/lnwire/update_add_htlc.go @@ -16,21 +16,20 @@ const ( // entire packet. OnionPacketSize = 1366 - // ExperimentalAccountableType is the TLV type used for a custom - // record that sets an experimental accountable value. - ExperimentalAccountableType tlv.Type = 106823 + // ExperimentalEndorsementType is the TLV type used for a custom + // record that sets an experimental endorsement value. + ExperimentalEndorsementType tlv.Type = 106823 - // ExperimentalUnaccountable is the value that the experimental - // accountable field contains when a htlc is not accountable. - ExperimentalUnaccountable = 0 + // ExperimentalUnendorsed is the value that the experimental endorsement + // field contains when a htlc is not endorsed. + ExperimentalUnendorsed = 0 - // ExperimentalAccountable is the value that the experimental - // accountable field contains when a htlc is accountable. We're using a - // single byte to represent our accountable value, but limit the value - // to using the first three bits (max value = 00000111). Interpreted as - // a uint8 (an alias for byte in go), we can just define this constant - // as 7. - ExperimentalAccountable = 7 + // ExperimentalEndorsed is the value that the experimental endorsement + // field contains when a htlc is endorsed. We're using a single byte + // to represent our endorsement value, but limit the value to using + // the first three bits (max value = 00000111). Interpreted as a uint8 + // (an alias for byte in go), we can just define this constant as 7. + ExperimentalEndorsed = 7 ) type ( diff --git a/lnwire/writer.go b/lnwire/writer.go index 63d43ca93..ddf67e8a2 100644 --- a/lnwire/writer.go +++ b/lnwire/writer.go @@ -10,8 +10,8 @@ import ( "net" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/tor" ) @@ -334,8 +334,7 @@ func WriteOnionAddr(buf *bytes.Buffer, addr *tor.OnionAddr) error { descriptor []byte ) - // Decide the suffixIndex and descriptor. v2 round-trips for wire - // fidelity even though lnd no longer produces it. + // Decide the suffixIndex and descriptor. switch len(addr.OnionService) { case tor.V2Len: descriptor = []byte{byte(v2OnionAddr)} diff --git a/lnwire/writer_test.go b/lnwire/writer_test.go index b67745e67..bb2bada06 100644 --- a/lnwire/writer_test.go +++ b/lnwire/writer_test.go @@ -8,9 +8,9 @@ import ( "net" "testing" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/tor" "github.com/stretchr/testify/require" ) @@ -457,6 +457,7 @@ func TestWriteTCPAddr(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { oldLen := buf.Len() @@ -544,6 +545,7 @@ func TestWriteOnionAddr(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { oldLen := buf.Len() @@ -616,6 +618,7 @@ func TestWriteNetAddrs(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { buf := new(bytes.Buffer) diff --git a/log.go b/log.go index 563ee3eb1..2484a35dd 100644 --- a/log.go +++ b/log.go @@ -17,7 +17,6 @@ import ( "github.com/lightningnetwork/lnd/chanfitness" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channelnotifier" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/cluster" "github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/discovery" @@ -47,7 +46,6 @@ import ( "github.com/lightningnetwork/lnd/monitoring" "github.com/lightningnetwork/lnd/msgmux" "github.com/lightningnetwork/lnd/netann" - "github.com/lightningnetwork/lnd/onionmessage" paymentsdb "github.com/lightningnetwork/lnd/payments/db" "github.com/lightningnetwork/lnd/peer" "github.com/lightningnetwork/lnd/peernotifier" @@ -178,7 +176,6 @@ func SetupLoggers(root *build.SubLoggerManager, interceptor signal.Interceptor) AddSubLogger(root, "IRPC", interceptor, invoicesrpc.UseLogger) AddSubLogger(root, "CHNF", interceptor, channelnotifier.UseLogger) AddSubLogger(root, "CHBU", interceptor, chanbackup.UseLogger) - AddSubLogger(root, "CHST", interceptor, chanstate.UseLogger) AddSubLogger(root, "PROM", interceptor, monitoring.UseLogger) AddSubLogger(root, "WTCL", interceptor, wtclient.UseLogger) AddSubLogger(root, "PRNF", interceptor, peernotifier.UseLogger) @@ -215,7 +212,6 @@ func SetupLoggers(root *build.SubLoggerManager, interceptor signal.Interceptor) root, paymentsdb.Subsystem, interceptor, paymentsdb.UseLogger, ) - AddSubLogger(root, onionmessage.Subsystem, interceptor, onionmessage.UseLogger) } // AddSubLogger is a helper method to conveniently create and register the diff --git a/make/builder.Dockerfile b/make/builder.Dockerfile index 9a45d2136..99d4aec55 100644 --- a/make/builder.Dockerfile +++ b/make/builder.Dockerfile @@ -1,6 +1,6 @@ # If you change this please also update GO_VERSION in Makefile (then run # `make lint` to see where else it needs to be updated as well). -FROM golang:1.26.4-bookworm +FROM golang:1.25.5-bookworm MAINTAINER Olaoluwa Osuntokun diff --git a/make/release_flags.mk b/make/release_flags.mk index b585467a8..1e74b299f 100644 --- a/make/release_flags.mk +++ b/make/release_flags.mk @@ -1,49 +1,13 @@ VERSION_TAG = $(shell date +%Y%m%d)-01 VERSION_CHECK = @$(call print, "Building master with date version tag") -# Create these directories before Docker bind mounts them. Docker creates a -# missing bind-mount source as root, which makes the cache unwritable because -# the release helper deliberately runs as the invoking user. -DOCKER_RELEASE_GOCACHE = $(shell bash -c 'cache="$$($(GOCC) env GOCACHE 2>/dev/null)" || cache=/tmp/go-cache; printf "%s" "$$cache"') -DOCKER_RELEASE_GOMODCACHE = $(shell bash -c 'cache="$$($(GOCC) env GOMODCACHE 2>/dev/null)" || cache=/tmp/go-modcache; printf "%s" "$$cache"') - -# A linked worktree has a .git file that points outside the worktree. Mount -# its common Git directory at the same absolute path so tag checks and git -# archive work inside the release helper too. -DOCKER_RELEASE_GIT_COMMON_DIR = $(shell if [ -f .git ]; then git rev-parse --path-format=absolute --git-common-dir; fi) -DOCKER_RELEASE_GIT_MOUNT = $(if $(DOCKER_RELEASE_GIT_COMMON_DIR),-v $(DOCKER_RELEASE_GIT_COMMON_DIR):$(DOCKER_RELEASE_GIT_COMMON_DIR):ro) - -define check_docker_release_cache - @cache="$(1)"; \ - if ! mkdir -p "$$cache"; then \ - echo "error: cannot create Docker release cache: $$cache"; \ - exit 1; \ - fi; \ - cache_ok=1; \ - for shard in $$(printf '%02x\n' $$(seq 0 255)); do \ - shard_dir="$$cache/$$shard"; created=; \ - if [ ! -e "$$shard_dir" ]; then \ - mkdir "$$shard_dir" || { cache_ok=; break; }; created=1; \ - fi; \ - test_dir=$$(mktemp -d "$$shard_dir/.lnd-release-cache.XXXXXX" 2>/dev/null) || { cache_ok=; break; }; \ - rmdir "$$test_dir"; \ - if [ -n "$$created" ] && ! rmdir "$$shard_dir"; then cache_ok=; break; fi; \ - done; \ - if [ -z "$$cache_ok" ]; then \ - echo "error: Docker release cache cannot create directories: $$cache"; \ - echo "hint: remove or chown root-owned files in this cache"; \ - exit 1; \ - fi -endef - DOCKER_RELEASE_HELPER = docker run \ -it \ --rm \ --user $(shell id -u):$(shell id -g) \ -v $(shell pwd):/tmp/build/lnd \ - $(DOCKER_RELEASE_GIT_MOUNT) \ - -v $(DOCKER_RELEASE_GOCACHE):/tmp/build/.cache \ - -v $(DOCKER_RELEASE_GOMODCACHE):/tmp/build/.modcache \ + -v $(shell bash -c "$(GOCC) env GOCACHE || (mkdir -p /tmp/go-cache; echo /tmp/go-cache)"):/tmp/build/.cache \ + -v $(shell bash -c "$(GOCC) env GOMODCACHE || (mkdir -p /tmp/go-modcache; echo /tmp/go-modcache)"):/tmp/build/.modcache \ -e SKIP_VERSION_CHECK \ lnd-release-helper @@ -62,7 +26,7 @@ netbsd-amd64 \ openbsd-amd64 \ windows-386 \ windows-amd64 \ -windows-arm64 +windows-arm RELEASE_TAGS = autopilotrpc signrpc walletrpc chainrpc invoicesrpc watchtowerrpc neutrinorpc monitoring peersrpc kvdb_postgres kvdb_etcd kvdb_sqlite diff --git a/make/testing_flags.mk b/make/testing_flags.mk index 4f7bd42d5..95a493930 100644 --- a/make/testing_flags.mk +++ b/make/testing_flags.mk @@ -69,11 +69,6 @@ ifneq ($(dbbackend),) ITEST_FLAGS += -dbbackend=$(dbbackend) endif -# Select miner backend independently from chain backend. Defaults to btcd. -ifneq ($(minerbackend),) -ITEST_FLAGS += -minerbackend=$(minerbackend) -endif - ifeq ($(dbbackend),etcd) DEV_TAGS += kvdb_etcd endif diff --git a/mobile/README.md b/mobile/README.md index 38beb580b..d8627f753 100644 --- a/mobile/README.md +++ b/mobile/README.md @@ -103,7 +103,7 @@ following commands: ``` RUN apt-get install -y wget \ - && wget -c https://dl.google.com/go/go1.26.4.linux-amd64.tar.gz -O - \ + && wget -c https://golang.org/dl/go1.17.6.linux-amd64.tar.gz -O - \ | tar -xz -C /usr/local ENV GOPATH=/go ENV PATH=$PATH:/usr/local/go/bin:/go/bin diff --git a/msgmux/msg_router.go b/msgmux/msg_router.go index 42a046f96..823fed668 100644 --- a/msgmux/msg_router.go +++ b/msgmux/msg_router.go @@ -50,7 +50,7 @@ type Endpoint interface { SendMessage(ctx context.Context, msg PeerMsg) bool } -// Router is an interface that represents a message router, which is generic +// MsgRouter is an interface that represents a message router, which is generic // sub-system capable of routing any incoming wire message to a set of // registered endpoints. type Router interface { diff --git a/netann/chan_status_manager.go b/netann/chan_status_manager.go index 3b642c43f..feb3a5dd1 100644 --- a/netann/chan_status_manager.go +++ b/netann/chan_status_manager.go @@ -1,14 +1,13 @@ package netann import ( - "context" "errors" "sync" "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/channeldb" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet" @@ -600,14 +599,14 @@ func (m *ChanStatusManager) disableInactiveChannels() { // fetchChannels returns the working set of channels managed by the // ChanStatusManager. The returned channels are filtered to only contain public // channels. -func (m *ChanStatusManager) fetchChannels() ([]*chanstate.OpenChannel, error) { +func (m *ChanStatusManager) fetchChannels() ([]*channeldb.OpenChannel, error) { allChannels, err := m.cfg.DB.FetchAllOpenChannels() if err != nil { return nil, err } // Filter out private channels. - var channels []*chanstate.OpenChannel + var channels []*channeldb.OpenChannel for _, c := range allChannels { // We'll skip any private channels, as they aren't used for // routing within the network by other nodes. @@ -655,9 +654,7 @@ func (m *ChanStatusManager) fetchLastChanUpdateByOutPoint(op wire.OutPoint) ( *lnwire.ChannelUpdate1, bool, error) { // Get the edge info and policies for this channel from the graph. - info, edge1, edge2, err := m.cfg.Graph.FetchChannelEdgesByOutpoint( - context.TODO(), &op, - ) + info, edge1, edge2, err := m.cfg.Graph.FetchChannelEdgesByOutpoint(&op) if err != nil { return nil, false, err } diff --git a/netann/chan_status_manager_test.go b/netann/chan_status_manager_test.go index 16d43fb3a..024b77932 100644 --- a/netann/chan_status_manager_test.go +++ b/netann/chan_status_manager_test.go @@ -2,7 +2,6 @@ package netann_test import ( "bytes" - "context" "crypto/rand" "encoding/binary" "fmt" @@ -13,15 +12,13 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/channeldb" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/netann" - "github.com/lightningnetwork/lnd/routing/route" "github.com/stretchr/testify/require" ) @@ -51,14 +48,14 @@ func randOutpoint(t *testing.T) wire.OutPoint { var shortChanIDs uint64 -// createChannel generates a chanstate.OpenChannel with a random chanpoint and +// createChannel generates a channeldb.OpenChannel with a random chanpoint and // short channel id. -func createChannel(t *testing.T) *chanstate.OpenChannel { +func createChannel(t *testing.T) *channeldb.OpenChannel { t.Helper() sid := atomic.AddUint64(&shortChanIDs, 1) - return &chanstate.OpenChannel{ + return &channeldb.OpenChannel{ ShortChannelID: lnwire.NewShortChanIDFromInt(sid), ChannelFlags: lnwire.FFAnnounceChannel, FundingOutpoint: randOutpoint(t), @@ -69,7 +66,7 @@ func createChannel(t *testing.T) *chanstate.OpenChannel { // The remote party's public key is generated randomly, and then sorted against // our `pubkey` with the direction bit set appropriately in the policies. Our // update will be created with the disabled bit set if startEnabled is false. -func createEdgePolicies(t *testing.T, channel *chanstate.OpenChannel, +func createEdgePolicies(t *testing.T, channel *channeldb.OpenChannel, pubkey *btcec.PublicKey, startEnabled bool) (*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) { @@ -103,28 +100,18 @@ func createEdgePolicies(t *testing.T, channel *chanstate.OpenChannel, // bit. dir2 |= lnwire.ChanUpdateDirection - pubkey1Vertex, err := route.NewVertexFromBytes(pubkey1[:]) - require.NoError(t, err) - pubkey2Vertex, err := route.NewVertexFromBytes(pubkey2[:]) - require.NoError(t, err) - - edgeInfo, err := models.NewV1Channel( - channel.ShortChanID().ToUint64(), chainhash.Hash{}, - pubkey1Vertex, pubkey2Vertex, &models.ChannelV1Fields{}, - models.WithChannelPoint(channel.FundingOutpoint), - ) - require.NoError(t, err) - - return edgeInfo, + return &models.ChannelEdgeInfo{ + ChannelPoint: channel.FundingOutpoint, + NodeKey1Bytes: pubkey1, + NodeKey2Bytes: pubkey2, + }, &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, ChannelID: channel.ShortChanID().ToUint64(), ChannelFlags: dir1, LastUpdate: time.Now(), SigBytes: testSigBytes, }, &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, ChannelID: channel.ShortChanID().ToUint64(), ChannelFlags: dir2, LastUpdate: time.Now(), @@ -134,7 +121,7 @@ func createEdgePolicies(t *testing.T, channel *chanstate.OpenChannel, type mockGraph struct { mu sync.Mutex - channels []*chanstate.OpenChannel + channels []*channeldb.OpenChannel chanInfos map[wire.OutPoint]*models.ChannelEdgeInfo chanPols1 map[wire.OutPoint]*models.ChannelEdgePolicy chanPols2 map[wire.OutPoint]*models.ChannelEdgePolicy @@ -147,7 +134,7 @@ func newMockGraph(t *testing.T, numChannels int, startEnabled bool, pubKey *btcec.PublicKey) *mockGraph { g := &mockGraph{ - channels: make([]*chanstate.OpenChannel, 0, numChannels), + channels: make([]*channeldb.OpenChannel, 0, numChannels), chanInfos: make(map[wire.OutPoint]*models.ChannelEdgeInfo), chanPols1: make(map[wire.OutPoint]*models.ChannelEdgePolicy), chanPols2: make(map[wire.OutPoint]*models.ChannelEdgePolicy), @@ -169,13 +156,12 @@ func newMockGraph(t *testing.T, numChannels int, startEnabled bool, return g } -func (g *mockGraph) FetchAllOpenChannels() ([]*chanstate.OpenChannel, error) { +func (g *mockGraph) FetchAllOpenChannels() ([]*channeldb.OpenChannel, error) { return g.chans(), nil } func (g *mockGraph) FetchChannelEdgesByOutpoint( - _ context.Context, op *wire.OutPoint) ( - *models.ChannelEdgeInfo, + op *wire.OutPoint) (*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy, error) { g.mu.Lock() @@ -226,7 +212,6 @@ func (g *mockGraph) ApplyChannelUpdate(update *lnwire.ChannelUpdate1, timestamp := time.Unix(int64(update.Timestamp), 0) policy := &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, ChannelID: update.ShortChannelID.ToUint64(), ChannelFlags: update.ChannelFlags, LastUpdate: timestamp, @@ -246,24 +231,24 @@ func (g *mockGraph) ApplyChannelUpdate(update *lnwire.ChannelUpdate1, return nil } -func (g *mockGraph) chans() []*chanstate.OpenChannel { +func (g *mockGraph) chans() []*channeldb.OpenChannel { g.mu.Lock() defer g.mu.Unlock() - channels := make([]*chanstate.OpenChannel, 0, len(g.channels)) + channels := make([]*channeldb.OpenChannel, 0, len(g.channels)) channels = append(channels, g.channels...) return channels } -func (g *mockGraph) addChannel(channel *chanstate.OpenChannel) { +func (g *mockGraph) addChannel(channel *channeldb.OpenChannel) { g.mu.Lock() defer g.mu.Unlock() g.channels = append(g.channels, channel) } -func (g *mockGraph) addEdgePolicy(c *chanstate.OpenChannel, +func (g *mockGraph) addEdgePolicy(c *channeldb.OpenChannel, info *models.ChannelEdgeInfo, pol1, pol2 *models.ChannelEdgePolicy) { @@ -276,7 +261,7 @@ func (g *mockGraph) addEdgePolicy(c *chanstate.OpenChannel, g.sidToCid[c.ShortChanID()] = c.FundingOutpoint } -func (g *mockGraph) removeChannel(channel *chanstate.OpenChannel) { +func (g *mockGraph) removeChannel(channel *channeldb.OpenChannel) { g.mu.Lock() defer g.mu.Unlock() @@ -401,7 +386,7 @@ func newHarness(t *testing.T, numChannels int, // markActive updates the active status of the passed channels within the mock // switch to active. -func (h *testHarness) markActive(channels []*chanstate.OpenChannel) { +func (h *testHarness) markActive(channels []*channeldb.OpenChannel) { h.t.Helper() for _, channel := range channels { @@ -412,7 +397,7 @@ func (h *testHarness) markActive(channels []*chanstate.OpenChannel) { // markInactive updates the active status of the passed channels within the mock // switch to inactive. -func (h *testHarness) markInactive(channels []*chanstate.OpenChannel) { +func (h *testHarness) markInactive(channels []*channeldb.OpenChannel) { h.t.Helper() for _, channel := range channels { @@ -423,8 +408,8 @@ func (h *testHarness) markInactive(channels []*chanstate.OpenChannel) { // assertEnables requests enables for all of the passed channels, and asserts // that the errors returned from RequestEnable matches expErr. -func (h *testHarness) assertEnables(channels []*chanstate.OpenChannel, - expErr error, manual bool) { +func (h *testHarness) assertEnables(channels []*channeldb.OpenChannel, expErr error, + manual bool) { h.t.Helper() @@ -435,8 +420,8 @@ func (h *testHarness) assertEnables(channels []*chanstate.OpenChannel, // assertDisables requests disables for all of the passed channels, and asserts // that the errors returned from RequestDisable matches expErr. -func (h *testHarness) assertDisables(channels []*chanstate.OpenChannel, - expErr error, manual bool) { +func (h *testHarness) assertDisables(channels []*channeldb.OpenChannel, expErr error, + manual bool) { h.t.Helper() @@ -447,7 +432,7 @@ func (h *testHarness) assertDisables(channels []*chanstate.OpenChannel, // assertAutos requests auto state management for all of the passed channels, and // asserts that the errors returned from RequestAuto matches expErr. -func (h *testHarness) assertAutos(channels []*chanstate.OpenChannel, +func (h *testHarness) assertAutos(channels []*channeldb.OpenChannel, expErr error) { h.t.Helper() @@ -506,7 +491,7 @@ func (h *testHarness) assertNoUpdates(duration time.Duration) { // are receive on the network for each of the passed OpenChannels, and that all // of their disable bits are set to match expEnabled. The expEnabled parameter // is ignored if channels is nil. -func (h *testHarness) assertUpdates(channels []*chanstate.OpenChannel, +func (h *testHarness) assertUpdates(channels []*channeldb.OpenChannel, expEnabled bool, duration time.Duration) { h.t.Helper() @@ -554,7 +539,7 @@ func (h *testHarness) assertUpdates(channels []*chanstate.OpenChannel, // sidsFromChans returns an index contain the short channel ids of each channel // provided in the list of OpenChannels. func sidsFromChans( - channels []*chanstate.OpenChannel) map[lnwire.ShortChannelID]struct{} { + channels []*channeldb.OpenChannel) map[lnwire.ShortChannelID]struct{} { sids := make(map[lnwire.ShortChannelID]struct{}) for _, channel := range channels { @@ -703,7 +688,7 @@ var stateMachineTests = []stateMachineTest{ startEnabled: false, fn: func(h testHarness) { // Create channels unknown to the graph. - unknownChans := []*chanstate.OpenChannel{ + unknownChans := []*channeldb.OpenChannel{ createChannel(h.t), createChannel(h.t), createChannel(h.t), @@ -723,7 +708,7 @@ var stateMachineTests = []stateMachineTest{ startEnabled: false, fn: func(h testHarness) { // Create channels unknown to the graph. - unknownChans := []*chanstate.OpenChannel{ + unknownChans := []*channeldb.OpenChannel{ createChannel(h.t), createChannel(h.t), createChannel(h.t), @@ -749,7 +734,7 @@ var stateMachineTests = []stateMachineTest{ // Add a new channels to the graph, but don't yet add // the edge policies. We should see no updates sent // since the manager can't access the policies. - newChans := []*chanstate.OpenChannel{ + newChans := []*channeldb.OpenChannel{ createChannel(h.t), createChannel(h.t), createChannel(h.t), diff --git a/netann/channel_announcement.go b/netann/channel_announcement.go index 47eb90081..9bb21c401 100644 --- a/netann/channel_announcement.go +++ b/netann/channel_announcement.go @@ -4,12 +4,12 @@ import ( "errors" "fmt" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/tlv" @@ -34,14 +34,48 @@ const ( // function is used to transform out database structs into the corresponding wire // structs for announcing new channels to other peers, or simply syncing up a // peer's initial routing table upon connect. -func CreateChanAnnouncement(chanInfo *models.ChannelEdgeInfo, +func CreateChanAnnouncement(chanProof *models.ChannelAuthProof, + chanInfo *models.ChannelEdgeInfo, e1, e2 *models.ChannelEdgePolicy) (*lnwire.ChannelAnnouncement1, *lnwire.ChannelUpdate1, *lnwire.ChannelUpdate1, error) { // First, using the parameters of the channel, along with the channel - // authentication proof, we'll create re-create the original + // authentication chanProof, we'll create re-create the original // authenticated channel announcement. - chanAnn, err := chanInfo.ToChannelAnnouncement() + chanID := lnwire.NewShortChanIDFromInt(chanInfo.ChannelID) + chanAnn := &lnwire.ChannelAnnouncement1{ + ShortChannelID: chanID, + NodeID1: chanInfo.NodeKey1Bytes, + NodeID2: chanInfo.NodeKey2Bytes, + ChainHash: chanInfo.ChainHash, + BitcoinKey1: chanInfo.BitcoinKey1Bytes, + BitcoinKey2: chanInfo.BitcoinKey2Bytes, + Features: chanInfo.Features.RawFeatureVector, + ExtraOpaqueData: chanInfo.ExtraOpaqueData, + } + + var err error + chanAnn.BitcoinSig1, err = lnwire.NewSigFromECDSARawSignature( + chanProof.BitcoinSig1Bytes, + ) + if err != nil { + return nil, nil, nil, err + } + chanAnn.BitcoinSig2, err = lnwire.NewSigFromECDSARawSignature( + chanProof.BitcoinSig2Bytes, + ) + if err != nil { + return nil, nil, nil, err + } + chanAnn.NodeSig1, err = lnwire.NewSigFromECDSARawSignature( + chanProof.NodeSig1Bytes, + ) + if err != nil { + return nil, nil, nil, err + } + chanAnn.NodeSig2, err = lnwire.NewSigFromECDSARawSignature( + chanProof.NodeSig2Bytes, + ) if err != nil { return nil, nil, nil, err } @@ -73,7 +107,7 @@ func CreateChanAnnouncement(chanInfo *models.ChannelEdgeInfo, // FetchPkScript defines a function that can be used to fetch the output script // for the transaction with the given SCID. type FetchPkScript func(lnwire.ShortChannelID) (txscript.ScriptClass, - address.Address, error) + btcutil.Address, error) // ValidateChannelAnn validates the channel announcement. func ValidateChannelAnn(a lnwire.ChannelAnnouncement, @@ -275,7 +309,7 @@ func chanAnn2P2WSHMuSig2Keys(a *lnwire.ChannelAnnouncement2) ( // lnwire.ChannelAnnouncement2 message should be verified against in the case // where the channel being announced is a P2TR channel. func chanAnn2P2TRMuSig2Keys(a *lnwire.ChannelAnnouncement2, - scriptAddr address.Address) ([]*btcec.PublicKey, error) { + scriptAddr btcutil.Address) ([]*btcec.PublicKey, error) { nodeKey1, err := btcec.ParsePubKey(a.NodeID1.Val[:]) if err != nil { diff --git a/netann/channel_announcement_test.go b/netann/channel_announcement_test.go index 3be75d5ef..38949e046 100644 --- a/netann/channel_announcement_test.go +++ b/netann/channel_announcement_test.go @@ -4,14 +4,13 @@ import ( "bytes" "testing" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwire" @@ -41,26 +40,29 @@ func TestCreateChanAnnouncement(t *testing.T) { ExtraOpaqueData: []byte{0x1}, } - chanProof := models.NewV1ChannelAuthProof( - expChanAnn.NodeSig1.ToSignatureBytes(), - expChanAnn.NodeSig2.ToSignatureBytes(), - expChanAnn.BitcoinSig1.ToSignatureBytes(), - expChanAnn.BitcoinSig2.ToSignatureBytes(), + chanProof := &models.ChannelAuthProof{ + NodeSig1Bytes: expChanAnn.NodeSig1.ToSignatureBytes(), + NodeSig2Bytes: expChanAnn.NodeSig2.ToSignatureBytes(), + BitcoinSig1Bytes: expChanAnn.BitcoinSig1.ToSignatureBytes(), + BitcoinSig2Bytes: expChanAnn.BitcoinSig2.ToSignatureBytes(), + } + chanInfo := &models.ChannelEdgeInfo{ + ChainHash: expChanAnn.ChainHash, + ChannelID: expChanAnn.ShortChannelID.ToUint64(), + ChannelPoint: wire.OutPoint{Index: 1}, + Capacity: btcutil.SatoshiPerBitcoin, + NodeKey1Bytes: key, + NodeKey2Bytes: key, + BitcoinKey1Bytes: key, + BitcoinKey2Bytes: key, + Features: lnwire.NewFeatureVector( + features, lnwire.Features, + ), + ExtraOpaqueData: expChanAnn.ExtraOpaqueData, + } + chanAnn, _, _, err := CreateChanAnnouncement( + chanProof, chanInfo, nil, nil, ) - chanInfo, err := models.NewV1Channel( - expChanAnn.ShortChannelID.ToUint64(), expChanAnn.ChainHash, - key, key, &models.ChannelV1Fields{ - BitcoinKey1Bytes: key, - BitcoinKey2Bytes: key, - ExtraOpaqueData: expChanAnn.ExtraOpaqueData, - }, - models.WithChanProof(chanProof), - models.WithChannelPoint(wire.OutPoint{Index: 1}), - models.WithCapacity(btcutil.SatoshiPerBitcoin), - models.WithFeatures(features), - ) - require.NoError(t, err) - chanAnn, _, _, err := CreateChanAnnouncement(chanInfo, nil, nil) require.NoError(t, err, "unable to create channel announcement") assert.Equal(t, chanAnn, expChanAnn) @@ -176,7 +178,7 @@ func test4of4MuSig2P2WSHChanAnnouncement(t *testing.T) { scriptHash, err := input.WitnessScriptHash(multiSigScript) require.NoError(t, err) - pkAddr, err := address.NewAddressScriptHash( + pkAddr, err := btcutil.NewAddressScriptHash( scriptHash, &chaincfg.MainNetParams, ) require.NoError(t, err) @@ -184,7 +186,7 @@ func test4of4MuSig2P2WSHChanAnnouncement(t *testing.T) { // Create a mock tx fetcher that returns the expected script class and // pk address. fetchTx := func(lnwire.ShortChannelID) (txscript.ScriptClass, - address.Address, error) { + btcutil.Address, error) { return txscript.WitnessV0ScriptHashTy, pkAddr, nil } @@ -279,7 +281,7 @@ func test4of4MuSig2P2TRChanAnnouncement(t *testing.T) { ) require.NoError(t, err) - pkAddr, err := address.NewAddressTaproot( + pkAddr, err := btcutil.NewAddressTaproot( combinedKey.FinalKey.SerializeCompressed()[1:], &chaincfg.MainNetParams, ) @@ -288,7 +290,7 @@ func test4of4MuSig2P2TRChanAnnouncement(t *testing.T) { // Create a mock tx fetcher that returns the expected script class and // pk address. fetchTx := func(lnwire.ShortChannelID) (txscript.ScriptClass, - address.Address, error) { + btcutil.Address, error) { return txscript.WitnessV1TaprootTy, pkAddr, nil } @@ -349,7 +351,7 @@ func test3of3MuSig2ChanAnnouncement(t *testing.T) { }) require.NoError(t, err) - pkAddr, err := address.NewAddressTaproot( + pkAddr, err := btcutil.NewAddressTaproot( outputKey.SerializeCompressed()[1:], &chaincfg.MainNetParams, ) require.NoError(t, err) @@ -357,7 +359,7 @@ func test3of3MuSig2ChanAnnouncement(t *testing.T) { // Create a mock tx fetcher that returns the expected script class // and pk address. fetchTx := func(lnwire.ShortChannelID) (txscript.ScriptClass, - address.Address, error) { + btcutil.Address, error) { return txscript.WitnessV1TaprootTy, pkAddr, nil } diff --git a/netann/channel_state.go b/netann/channel_state.go index a17c80869..904196331 100644 --- a/netann/channel_state.go +++ b/netann/channel_state.go @@ -3,7 +3,7 @@ package netann import ( "time" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" ) // ChanStatus is a type that enumerates the possible states a ChanStatusManager diff --git a/netann/channel_update.go b/netann/channel_update.go index cf9978d99..7e87fd77b 100644 --- a/netann/channel_update.go +++ b/netann/channel_update.go @@ -6,8 +6,8 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnutils" diff --git a/netann/channel_update_test.go b/netann/channel_update_test.go index fe0bc33bf..2a619e062 100644 --- a/netann/channel_update_test.go +++ b/netann/channel_update_test.go @@ -105,6 +105,7 @@ func TestUpdateDisableFlag(t *testing.T) { t.Parallel() for _, tc := range updateDisableTests { + tc := tc t.Run(tc.name, func(t *testing.T) { // Create the initial update, the only fields we are // concerned with in this test are the timestamp and the diff --git a/netann/interface.go b/netann/interface.go index 366805bfc..aa559435d 100644 --- a/netann/interface.go +++ b/netann/interface.go @@ -1,10 +1,8 @@ package netann import ( - "context" - - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/graph/db/models" ) @@ -13,7 +11,7 @@ import ( type DB interface { // FetchAllOpenChannels returns a slice of all open channels known to // the daemon. This may include private or pending channels. - FetchAllOpenChannels() ([]*chanstate.OpenChannel, error) + FetchAllOpenChannels() ([]*channeldb.OpenChannel, error) } // ChannelGraph abstracts the required channel graph queries used by the @@ -21,7 +19,6 @@ type DB interface { type ChannelGraph interface { // FetchChannelEdgesByOutpoint returns the channel edge info and most // recent channel edge policies for a given outpoint. - FetchChannelEdgesByOutpoint(context.Context, *wire.OutPoint) ( - *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, - *models.ChannelEdgePolicy, error) + FetchChannelEdgesByOutpoint(*wire.OutPoint) (*models.ChannelEdgeInfo, + *models.ChannelEdgePolicy, *models.ChannelEdgePolicy, error) } diff --git a/netann/msg_hash.go b/netann/msg_hash.go index 562b1cdf3..3c4806f9b 100644 --- a/netann/msg_hash.go +++ b/netann/msg_hash.go @@ -1,7 +1,7 @@ package netann import ( - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" ) // MsgHashTag will prefix the message name and the field name in order to diff --git a/netann/node_announcement.go b/netann/node_announcement.go index 79553cd6f..b2d288bf7 100644 --- a/netann/node_announcement.go +++ b/netann/node_announcement.go @@ -9,7 +9,7 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" diff --git a/netann/node_announcement_test.go b/netann/node_announcement_test.go deleted file mode 100644 index 6b3659e65..000000000 --- a/netann/node_announcement_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package netann_test - -import ( - "bytes" - "net" - "testing" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/lightningnetwork/lnd/graph/db/models" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/netann" - "github.com/lightningnetwork/lnd/tor" - "github.com/stretchr/testify/require" -) - -// TestNodeAnnSignatureWithLegacyV2OnionAddr signs a [v3, v2, ipv4] -// announcement, round-trips it through the wire codec, and verifies that -// the signature still validates. If Decode or NodeFromWireAnnouncement ever -// starts filtering v2, DataToSign no longer reproduces the signed bytes and -// this test catches the regression. -func TestNodeAnnSignatureWithLegacyV2OnionAddr(t *testing.T) { - t.Parallel() - - privKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - var nodeID [33]byte - copy(nodeID[:], privKey.PubKey().SerializeCompressed()) - - v3 := &tor.OnionAddr{ - OnionService: "abcdefghijabcdefghijabcdefghij" + - "abcdefghijabcdefghij234567.onion", - Port: 9735, - } - v2 := &tor.OnionAddr{ - OnionService: "abcdefghijklmnop.onion", - Port: 9735, - } - tcp := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 9735} - addrs := []net.Addr{v3, v2, tcp} - - ann := &lnwire.NodeAnnouncement1{ - Features: lnwire.NewRawFeatureVector(), - Timestamp: 1700000000, - NodeID: nodeID, - Addresses: addrs, - } - copy(ann.Alias[:], "regression-test") - - dataToSign, err := ann.DataToSign() - require.NoError(t, err) - - rawSig := ecdsa.Sign(privKey, chainhash.DoubleHashB(dataToSign)) - ann.Signature, err = lnwire.NewSigFromSignature(rawSig) - require.NoError(t, err) - - var buf bytes.Buffer - require.NoError(t, ann.Encode(&buf, 0)) - - var decoded lnwire.NodeAnnouncement1 - require.NoError(t, decoded.Decode(&buf, 0)) - - // All three addresses must survive Decode for DataToSign to - // reproduce the signed bytes. - require.Len(t, decoded.Addresses, 3) - require.NoError(t, netann.ValidateNodeAnnSignature(&decoded)) - - node := models.NodeFromWireAnnouncement(&decoded) - require.Len(t, node.Addresses, 3) - - var sawV2, sawV3, sawTCP bool - for _, addr := range node.Addresses { - switch a := addr.(type) { - case *tor.OnionAddr: - switch len(a.OnionService) { - case tor.V2Len: - sawV2 = true - case tor.V3Len: - sawV3 = true - } - case *net.TCPAddr: - sawTCP = true - } - } - require.True(t, sawV2, "v2 onion address must be preserved on the Node") - require.True(t, sawV3, "v3 onion address must be preserved on the Node") - require.True(t, sawTCP, "ipv4 address must be preserved on the Node") -} diff --git a/onionmessage/actor.go b/onionmessage/actor.go deleted file mode 100644 index 6a80bb7dc..000000000 --- a/onionmessage/actor.go +++ /dev/null @@ -1,389 +0,0 @@ -package onionmessage - -import ( - "context" - "encoding/hex" - "log/slog" - - "github.com/btcsuite/btclog/v2" - "github.com/lightningnetwork/lnd/actor" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/lnutils" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/queue" - "github.com/lightningnetwork/lnd/record" -) - -const ( - // DefaultOnionMailboxSize is the buffer capacity for per-peer onion - // message actor mailboxes. - DefaultOnionMailboxSize = 50 - - // DefaultMinREDThreshold is the queue depth at which Random Early - // Detection begins probabilistically dropping onion messages. Below - // this threshold no drops occur; above DefaultOnionMailboxSize all - // messages are dropped. Must be strictly less than - // DefaultOnionMailboxSize. - DefaultMinREDThreshold = 40 - - // DefaultPeerOnionMsgKbps is the default sustained per-peer onion - // message ingress rate, in decimal kilobits per second (1 Kbps = - // 1000 bits/s). Sizing is expressed against a 32 KiB onion_message - // packet (the BOLT 4 spec cap on the sphinx-level payload inside - // onion_message), not the 65 KiB lnwire envelope cap — at ~32 KiB - // per packet this is roughly two such messages per second per peer. - // A value of zero disables the per-peer limiter entirely. - DefaultPeerOnionMsgKbps = 512 - - // DefaultPeerOnionMsgBurstBytes is the default per-peer token bucket - // depth, in bytes. Sized to hold approximately eight 32 KiB onion - // message packets (see DefaultPeerOnionMsgKbps for why we measure - // against 32 KiB rather than the 65 KiB lnwire envelope cap) so a - // peer can briefly burst above the sustained rate without drops. - DefaultPeerOnionMsgBurstBytes = 8 * 32 * 1024 - - // DefaultGlobalOnionMsgKbps is the default sustained aggregate onion - // message ingress rate across all peers, in decimal kilobits per - // second. Targets ~5 Mbps worst-case ingress so that onion message - // bandwidth cannot dwarf a typical routing node's payment traffic. - // A value of zero disables the global limiter entirely. - DefaultGlobalOnionMsgKbps = 5120 - - // DefaultGlobalOnionMsgBurstBytes is the default global token bucket - // depth, in bytes. Sized to hold approximately fifty 32 KiB onion - // message packets, measured against the BOLT 4 onion_message_packet - // cap rather than the 65 KiB lnwire envelope cap (see - // DefaultPeerOnionMsgKbps). - DefaultGlobalOnionMsgBurstBytes = 50 * 32 * 1024 -) - -// Compile-time assertion: DefaultMinREDThreshold must be strictly less than -// DefaultOnionMailboxSize. If this overflows, the constants are misconfigured. -const _ = uint(DefaultOnionMailboxSize - DefaultMinREDThreshold - 1) - -// Compile-time assertions: the default burst sizes must be able to hold at -// least one maximum-sized wire message, otherwise every AllowN call on a -// freshly constructed limiter would fail and the limiter would silently -// drop all onion traffic. -const _ = uint(DefaultPeerOnionMsgBurstBytes - lnwire.MaxMsgBody) -const _ = uint(DefaultGlobalOnionMsgBurstBytes - lnwire.MaxMsgBody) - -// Request is a message sent to an OnionPeerActor when an onion message is -// received from the peer. The actor processes the message through the full -// onion message pipeline: decode, decrypt, route, and forward/deliver. -type Request struct { - // Embed BaseMessage to satisfy the actor package Message interface. - actor.BaseMessage - - // msg is the onion message to process. This field is unexported as - // it's an implementation detail of the actor system and should not be - // accessed directly by external code. - msg lnwire.OnionMessage -} - -// NewRequest creates a new Request from an onion message. -func NewRequest(msg lnwire.OnionMessage) *Request { - return &Request{msg: msg} -} - -// MessageType returns a string identifier for the Request message type. -func (m *Request) MessageType() string { - return "OnionMessageRequest" -} - -// Response is the response message sent back from an OnionPeerActor after -// processing an incoming onion message. -type Response struct { - actor.BaseMessage - Success bool -} - -// MessageType returns a string identifier for the Response message type. -func (m *Response) MessageType() string { - return "OnionMessageResponse" -} - -// OnionPeerActorRef is a reference to an OnionPeerActor. -type OnionPeerActorRef actor.ActorRef[*Request, *Response] - -// NewOnionMessageServiceKey creates a service key for registering and looking -// up onion peer actors. The service key uses the peer's compressed public key -// (hex-encoded) as the identifier. It returns both the service key and the -// hex-encoded public key string for use in actor naming and logging. -func NewOnionMessageServiceKey( - pubKey [33]byte) (actor.ServiceKey[*Request, *Response], string) { - - pubKeyHex := hex.EncodeToString(pubKey[:]) - - return actor.NewServiceKey[*Request, *Response](pubKeyHex), pubKeyHex -} - -// OnionActorFactory is a function that spawns a new OnionPeerActor for a -// given peer within the actor system. The factory captures shared dependencies -// (router, resolver, sender, dispatcher) and only requires per-peer parameters -// at spawn time. Callers may pass ActorOptions to customise the mailbox (size, -// drop predicate, etc.) on a per-peer basis. -type OnionActorFactory func(system *actor.ActorSystem, - peerPubKey [33]byte, - opts ...actor.ActorOption[*Request, *Response]) (OnionPeerActorRef, - error) - -// OnionPeerActor handles the full onion message processing pipeline for a -// specific peer connection. It decodes incoming onion messages, determines -// the routing action (forward or deliver), executes the action, and dispatches -// updates to subscribers. -type OnionPeerActor struct { - // peerPubKey is the compressed public key of the peer this actor - // handles messages for. - peerPubKey [33]byte - - // peerSender is used to forward onion messages to other peers. - peerSender PeerMessageSender - - // router is the onion router used to process onion message packets. - router OnionRouter - - // resolver resolves node public keys from short channel IDs. - resolver NodeIDResolver - - // updateDispatcher dispatches onion message updates to subscribers. - updateDispatcher OnionMessageUpdateDispatcher -} - -// Receive processes an incoming onion message from the peer. It decodes the -// onion packet, determines whether to forward or deliver the message, executes -// the routing action, and dispatches an update to subscribers. -// -// This method implements the actor.ActorBehavior interface. -func (a *OnionPeerActor) Receive(ctx context.Context, - req *Request) fn.Result[*Response] { - - select { - case <-ctx.Done(): - log.DebugS(ctx, "OnionPeerActor context canceled, "+ - "not processing") - - return fn.Err[*Response](ErrActorShuttingDown) - default: - } - - logCtx := btclog.WithCtx(ctx, - slog.String("peer", - hex.EncodeToString(a.peerPubKey[:])), - lnutils.LogPubKey("path_key", req.msg.PathKey), - ) - - log.DebugS(logCtx, "OnionPeerActor received OnionMessage", - btclog.HexN("onion_blob", req.msg.OnionBlob, 10), - slog.Int("blob_length", len(req.msg.OnionBlob))) - - routingActionResult := processOnionMessage( - ctx, a.router, a.resolver, &req.msg, - ) - - routingAction, err := routingActionResult.Unpack() - if err != nil { - log.ErrorS(logCtx, "Failed to handle onion message", err) - - return fn.Err[*Response](err) - } - - // Block same-peer cycles: do not forward a message back to - // the peer that sent it. - routingAction.WhenLeft(func(fwdAction forwardAction) { - var nextNodeIDBytes [33]byte - copy( - nextNodeIDBytes[:], - fwdAction.nextNodeID.SerializeCompressed(), - ) - - if nextNodeIDBytes == a.peerPubKey { - log.WarnS(logCtx, - "Dropping cyclic onion message", - ErrSamePeerCycle, - lnutils.LogPubKey( - "next_node_id", - fwdAction.nextNodeID, - ), - ) - - err = ErrSamePeerCycle - } - }) - if err != nil { - return fn.Err[*Response](err) - } - - // Handle the routing action. - payload := fn.ElimEither(routingAction, - func(fwdAction forwardAction) *lnwire.OnionMessagePayload { - log.DebugS(logCtx, "Forwarding onion message", - lnutils.LogPubKey("next_node_id", - fwdAction.nextNodeID), - ) - - nextMsg := lnwire.NewOnionMessage( - fwdAction.nextPathKey, - fwdAction.nextPacket, - ) - - var nextNodeIDBytes [33]byte - copy( - nextNodeIDBytes[:], - fwdAction.nextNodeID.SerializeCompressed(), - ) - - sendErr := a.peerSender.SendToPeer( - nextNodeIDBytes, nextMsg, - ) - if sendErr != nil { - log.ErrorS(logCtx, "Failed to forward "+ - "onion message", sendErr) - } - - return fwdAction.payload - }, - func(dlvrAction deliverAction) *lnwire.OnionMessagePayload { - log.DebugS(logCtx, "Delivering onion message "+ - "to self") - - return dlvrAction.payload - }) - - // Convert path key to [33]byte. - var pathKeyArr [33]byte - copy(pathKeyArr[:], req.msg.PathKey.SerializeCompressed()) - - // Create the onion message update to send to subscribers. - update := &OnionMessageUpdate{ - Peer: a.peerPubKey, - PathKey: pathKeyArr, - OnionBlob: req.msg.OnionBlob, - } - - // If we have a payload, add its contents to our update. - if payload != nil { - customRecords := make(record.CustomSet) - for _, v := range payload.FinalHopTLVs { - customRecords[uint64(v.TLVType)] = v.Value - } - update.CustomRecords = customRecords - update.ReplyPath = payload.ReplyPath - update.EncryptedRecipientData = payload.EncryptedData - } - - // Send the update to any subscribers. - if sendErr := a.updateDispatcher.SendUpdate(update); sendErr != nil { - log.ErrorS(logCtx, "Failed to send onion message update", - sendErr) - - return fn.Err[*Response](sendErr) - } - - return fn.Ok(&Response{Success: true}) -} - -// NewOnionActorFactory creates a factory function that spawns OnionPeerActors -// with shared dependencies. The returned factory captures the router, -// resolver, peer sender, and update dispatcher, requiring only the actor -// system, peer public key, and optional per-peer ActorOptions at spawn time. -// -// Callers supply ActorOptions (mailbox factory, size overrides, etc.) via the -// opts variadic so that backpressure policy can be customised per peer. -func NewOnionActorFactory(router OnionRouter, resolver NodeIDResolver, - peerSender PeerMessageSender, - dispatcher OnionMessageUpdateDispatcher) OnionActorFactory { - - return func(system *actor.ActorSystem, peerPubKey [33]byte, - opts ...actor.ActorOption[*Request, *Response], - ) (OnionPeerActorRef, error) { - - peerActor := &OnionPeerActor{ - peerPubKey: peerPubKey, - peerSender: peerSender, - router: router, - resolver: resolver, - updateDispatcher: dispatcher, - } - - serviceKey, pubKeyHex := NewOnionMessageServiceKey( - peerPubKey, - ) - actorRef, err := serviceKey.Spawn( - system, "onion-peer-actor-"+pubKeyHex, peerActor, - opts..., - ) - if err != nil { - return nil, err - } - - log.Debugf("Spawned onion peer actor for peer %s", - pubKeyHex) - - return actorRef, nil - } -} - -// DefaultOnionActorOpts returns ActorOptions that configure a -// BackpressureMailbox with a RED drop predicate and the default onion mailbox -// size. The RED thresholds are derived from the mailbox capacity so that all -// parameters are centralised and self-consistent. -func DefaultOnionActorOpts() []actor.ActorOption[*Request, *Response] { - factory := func(ctx context.Context, - capacity int) actor.Mailbox[*Request, *Response] { - - // Dynamically calculate the min threshold to be - // the same proportion (40/50 = 80%) of the actual - // capacity. - minThreshold := (capacity * DefaultMinREDThreshold) / - DefaultOnionMailboxSize - - // Ensure minThreshold is strictly less than - // capacity for RED to work. - if minThreshold >= capacity { - minThreshold = capacity - 1 - } - if minThreshold < 0 { - minThreshold = 0 - } - - shouldDrop, err := queue.RandomEarlyDrop( - minThreshold, capacity, - ) - if err != nil { - // This should never happen given the - // threshold clamping above, but fall back to - // dropping all messages rather than risking - // a blocked readHandler. - shouldDrop = func(int) bool { - return true - } - } - - return actor.NewBackpressureMailbox[*Request, *Response]( - ctx, capacity, shouldDrop, - ) - } - - return []actor.ActorOption[*Request, *Response]{ - actor.WithMailboxFactory(factory), - actor.WithMailboxSize[*Request, *Response]( - DefaultOnionMailboxSize, - ), - } -} - -// StopOnionActor stops the onion peer actor for the given public key using the -// provided actor reference. This should be called when a peer disconnects to -// clean up the actor. -func StopOnionActor(system *actor.ActorSystem, pubKey [33]byte, - ref OnionPeerActorRef) { - - serviceKey, pubKeyHex := NewOnionMessageServiceKey(pubKey) - - log.Debugf("Stopping onion peer actor for peer %s", pubKeyHex) - - serviceKey.Unregister( - system, actor.ActorRef[*Request, *Response](ref), - ) -} diff --git a/onionmessage/actor_test.go b/onionmessage/actor_test.go deleted file mode 100644 index 3fb2e79c1..000000000 --- a/onionmessage/actor_test.go +++ /dev/null @@ -1,641 +0,0 @@ -package onionmessage - -import ( - "context" - "fmt" - "testing" - - "github.com/btcsuite/btcd/btcec/v2" - sphinx "github.com/lightningnetwork/lightning-onion" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/record" - "github.com/stretchr/testify/require" -) - -// mockPeerMessageSender implements PeerMessageSender for testing. -type mockPeerMessageSender struct { - sent chan peerMessage - err error -} - -type peerMessage struct { - pubKey [33]byte - msg *lnwire.OnionMessage -} - -func newMockPeerMessageSender() *mockPeerMessageSender { - return &mockPeerMessageSender{ - sent: make(chan peerMessage, 1), - } -} - -func (m *mockPeerMessageSender) SendToPeer(pubKey [33]byte, - msg *lnwire.OnionMessage) error { - - if m.err != nil { - return m.err - } - - m.sent <- peerMessage{pubKey: pubKey, msg: msg} - - return nil -} - -// mockUpdateDispatcher implements OnionMessageUpdateDispatcher for testing. -type mockUpdateDispatcher struct { - updates chan *OnionMessageUpdate - err error -} - -func newMockUpdateDispatcher() *mockUpdateDispatcher { - return &mockUpdateDispatcher{ - updates: make(chan *OnionMessageUpdate, 1), - } -} - -func (m *mockUpdateDispatcher) SendUpdate(update any) error { - if m.err != nil { - return m.err - } - - u, ok := update.(*OnionMessageUpdate) - if !ok { - return fmt.Errorf("unexpected update type: %T", update) - } - - m.updates <- u - - return nil -} - -// actorHarness wires up the minimal components required to exercise -// OnionPeerActor.Receive end-to-end. -type actorHarness struct { - actor *OnionPeerActor - sender *mockPeerMessageSender - dispatcher *mockUpdateDispatcher - resolver *mockNodeIDResolver - router *sphinx.Router - nodeKey *btcec.PrivateKey -} - -func newActorHarness(t *testing.T) *actorHarness { - t.Helper() - - nodeKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - router := sphinx.NewRouter( - &sphinx.PrivKeyECDH{PrivKey: nodeKey}, - sphinx.NewNoOpReplayLog(), - ) - require.NoError(t, router.Start()) - t.Cleanup(func() { router.Stop() }) - - sender := newMockPeerMessageSender() - dispatcher := newMockUpdateDispatcher() - resolver := newMockNodeIDResolver() - - var peerPubKey [33]byte - copy(peerPubKey[:], nodeKey.PubKey().SerializeCompressed()) - - peerActor := &OnionPeerActor{ - peerPubKey: peerPubKey, - peerSender: sender, - router: router, - resolver: resolver, - updateDispatcher: dispatcher, - } - - return &actorHarness{ - actor: peerActor, - sender: sender, - dispatcher: dispatcher, - resolver: resolver, - router: router, - nodeKey: nodeKey, - } -} - -func pubKeyToArray(pk *btcec.PublicKey) [33]byte { - var out [33]byte - copy(out[:], pk.SerializeCompressed()) - return out -} - -// hopBuildResult encapsulates the outputs of a hop building function. -type hopBuildResult struct { - blindedPath *sphinx.BlindedPathInfo - privKeys []*btcec.PrivateKey - after func() -} - -// buildHopsFunc is the signature for functions that construct test hop data. -type buildHopsFunc func(t *testing.T, h *actorHarness) hopBuildResult - -// buildForwardNextNodeHops constructs hops for testing forward via next node. -func buildForwardNextNodeHops( - t *testing.T, h *actorHarness) hopBuildResult { - - nextNodeKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - nextNodePub := nextNodeKey.PubKey() - - nextNode := fn.NewLeft[*btcec.PublicKey, lnwire.ShortChannelID]( - nextNodePub, - ) - rdA := record.NewNonFinalBlindedRouteDataOnionMessage( - nextNode, nil, nil, - ) - rdB := &record.BlindedRouteData{} - - plainA := EncodeBlindedRouteData(t, rdA) - plainB := EncodeBlindedRouteData(t, rdB) - hops := []*sphinx.HopInfo{ - {NodePub: h.nodeKey.PubKey(), PlainText: plainA}, - {NodePub: nextNodePub, PlainText: plainB}, - } - - privKeys := []*btcec.PrivateKey{h.nodeKey, nextNodeKey} - - after := func() { - select { - case msg := <-h.sender.sent: - require.NotNil(t, msg.msg) - require.Equal( - t, pubKeyToArray(nextNodePub), msg.pubKey, - ) - default: - require.FailNow(t, "forwarded message not sent") - } - } - - return hopBuildResult{ - blindedPath: BuildBlindedPath(t, hops), - privKeys: privKeys, - after: after, - } -} - -// buildForwardSCIDHops constructs hops for testing forward via SCID. -func buildForwardSCIDHops(t *testing.T, h *actorHarness) hopBuildResult { - nextNodeKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - nextNodePub := nextNodeKey.PubKey() - - scid := lnwire.NewShortChanIDFromInt(555) - h.resolver.addPeer(scid, nextNodePub) - - nextNode := fn.NewRight[*btcec.PublicKey](scid) - rdA := record.NewNonFinalBlindedRouteDataOnionMessage( - nextNode, nil, nil, - ) - rdB := &record.BlindedRouteData{} - - plainA := EncodeBlindedRouteData(t, rdA) - plainB := EncodeBlindedRouteData(t, rdB) - hops := []*sphinx.HopInfo{ - {NodePub: h.nodeKey.PubKey(), PlainText: plainA}, - {NodePub: nextNodePub, PlainText: plainB}, - } - - privKeys := []*btcec.PrivateKey{h.nodeKey, nextNodeKey} - - after := func() { - select { - case msg := <-h.sender.sent: - require.NotNil(t, msg.msg) - default: - require.FailNow(t, "forwarded message not sent") - } - } - - return hopBuildResult{ - blindedPath: BuildBlindedPath(t, hops), - privKeys: privKeys, - after: after, - } -} - -// buildDeliverHops constructs hops for testing the deliver action. -func buildDeliverHops(t *testing.T, h *actorHarness) hopBuildResult { - rd := &record.BlindedRouteData{} - plain := EncodeBlindedRouteData(t, rd) - hops := []*sphinx.HopInfo{ - {NodePub: h.nodeKey.PubKey(), PlainText: plain}, - } - privKeys := []*btcec.PrivateKey{h.nodeKey} - - return hopBuildResult{ - blindedPath: BuildBlindedPath(t, hops), - privKeys: privKeys, - after: func() {}, - } -} - -// buildForwardUnknownPeerHops constructs hops for testing forward to an -// unknown peer. The sender returns an error, so forwarding will fail but the -// message is still processed. -func buildForwardUnknownPeerHops( - t *testing.T, h *actorHarness) hopBuildResult { - - nextNodeKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - nextNodePub := nextNodeKey.PubKey() - - // Set up the sender to return an error for the unknown peer. - h.sender.err = fmt.Errorf("peer not connected") - - nextNode := fn.NewLeft[*btcec.PublicKey, lnwire.ShortChannelID]( - nextNodePub, - ) - rdA := record.NewNonFinalBlindedRouteDataOnionMessage( - nextNode, nil, nil, - ) - rdB := &record.BlindedRouteData{} - - hops := []*sphinx.HopInfo{ - { - NodePub: h.nodeKey.PubKey(), - PlainText: EncodeBlindedRouteData(t, rdA), - }, - { - NodePub: nextNodePub, - PlainText: EncodeBlindedRouteData(t, rdB), - }, - } - - privKeys := []*btcec.PrivateKey{h.nodeKey, nextNodeKey} - - after := func() { - // Verify no message was successfully sent. - select { - case <-h.sender.sent: - require.FailNow(t, "message should not have been "+ - "forwarded to unknown peer") - default: - // Expected: no forwarding happened. - } - } - - return hopBuildResult{ - blindedPath: BuildBlindedPath(t, hops), - privKeys: privKeys, - after: after, - } -} - -// buildConcatenatedPathHops constructs a concatenated blinded path scenario. -func buildConcatenatedPathHops( - t *testing.T, h *actorHarness) hopBuildResult { - - introNodeKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - introNodePub := introNodeKey.PubKey() - - finalNodeKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - finalNodePub := finalNodeKey.PubKey() - - // Build the receiver's blinded path: introNode -> finalNode. - nextNodeReceiver := fn.NewLeft[*btcec.PublicKey, lnwire.ShortChannelID]( - finalNodePub, - ) - rdReceiverIntro := record.NewNonFinalBlindedRouteDataOnionMessage( - nextNodeReceiver, nil, nil, - ) - rdReceiverFinal := &record.BlindedRouteData{} - - receiverHops := []*sphinx.HopInfo{ - { - NodePub: introNodePub, - PlainText: EncodeBlindedRouteData( - t, rdReceiverIntro, - ), - }, - { - NodePub: finalNodePub, - PlainText: EncodeBlindedRouteData( - t, rdReceiverFinal, - ), - }, - } - receiverPath := BuildBlindedPath(t, receiverHops) - - // Build the sender's path: firstHopNode -> introNode. - nextNodeSender := fn.NewLeft[*btcec.PublicKey, lnwire.ShortChannelID]( - introNodePub, - ) - blindingOverride := receiverPath.Path.BlindingPoint - rdFirstHop := record.NewNonFinalBlindedRouteDataOnionMessage( - nextNodeSender, blindingOverride, nil, - ) - - senderHops := []*sphinx.HopInfo{ - { - NodePub: h.nodeKey.PubKey(), - PlainText: EncodeBlindedRouteData( - t, rdFirstHop, - ), - }, - } - senderPath := BuildBlindedPath(t, senderHops) - - concatenatedPath := ConcatBlindedPaths( - t, senderPath, receiverPath, - ) - - privKeys := []*btcec.PrivateKey{h.nodeKey, introNodeKey, finalNodeKey} - - expectedPathKey := blindingOverride - - after := func() { - select { - case msg := <-h.sender.sent: - require.NotNil(t, msg.msg) - - // Verify the forwarded message uses the receiver's - // blinding point as the new path key. - require.Equal( - t, expectedPathKey, msg.msg.PathKey, - "forwarded message should use override "+ - "path key", - ) - default: - require.FailNow(t, "forwarded message not sent") - } - } - - return hopBuildResult{ - blindedPath: concatenatedPath, - privKeys: privKeys, - after: after, - } -} - -// TestOnionPeerActorRouting tests the OnionPeerActor's message routing -// functionality across various scenarios including forwarding via next node ID, -// forwarding via SCID, delivery, concatenated paths, and unknown peer handling. -func TestOnionPeerActorRouting(t *testing.T) { - t.Parallel() - - customTLVType := lnwire.InvoiceRequestNamespaceType + 1 - - tests := []struct { - name string - buildHops buildHopsFunc - finalHopTLVs []*lnwire.FinalHopTLV - }{ - { - name: "forward next node", - buildHops: buildForwardNextNodeHops, - }, - { - name: "forward scid", - buildHops: buildForwardSCIDHops, - }, - { - name: "deliver", - buildHops: buildDeliverHops, - finalHopTLVs: []*lnwire.FinalHopTLV{ - { - TLVType: customTLVType, - Value: []byte{1, 2, 3}, - }, - }, - }, - { - name: "forward concatenated path", - buildHops: buildConcatenatedPathHops, - }, - { - name: "forward unknown peer", - buildHops: buildForwardUnknownPeerHops, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - h := newActorHarness(t) - - result := tc.buildHops(t, h) - onionMsg, cipherTexts := BuildOnionMessage( - t, result.blindedPath, tc.finalHopTLVs, - ) - - req := &Request{msg: *onionMsg} - actorResult := h.actor.Receive(t.Context(), req) - require.True(t, actorResult.IsOk()) - - // Verify the update was dispatched. - select { - case update := <-h.dispatcher.updates: - require.Equal( - t, h.actor.peerPubKey, - update.Peer, - ) - require.Equal( - t, onionMsg.OnionBlob, - update.OnionBlob, - ) - expectedData := cipherTexts[0] - require.Equal( - t, expectedData, - update.EncryptedRecipientData, - ) - - for _, fht := range tc.finalHopTLVs { - tlvType := fht.TLVType - require.Equal( - t, fht.Value, - update.CustomRecords[uint64( - tlvType, - )], - ) - } - default: - require.FailNow(t, "no update dispatched") - } - - peeled := PeelOnionLayers( - t, result.privKeys, onionMsg, - ) - require.Len(t, peeled, len(cipherTexts)) - for i := range peeled { - require.Equal( - t, cipherTexts[i], - peeled[i].EncryptedData, - ) - } - - result.after() - }) - } -} - -// TestOnionPeerActorSamePeerCycle verifies that the actor rejects onion -// messages whose next hop is the same peer that sent them. Both the direct -// next-node-ID and the SCID-resolved paths are covered. -func TestOnionPeerActorSamePeerCycle(t *testing.T) { - t.Parallel() - - type nextNodeFn func(h *actorHarness, - pub *btcec.PublicKey) fn.Either[*btcec.PublicKey, - lnwire.ShortChannelID] - - tests := []struct { - name string - nextNode nextNodeFn - }{ - { - name: "via next node ID", - nextNode: func(_ *actorHarness, - pub *btcec.PublicKey) fn.Either[ - *btcec.PublicKey, lnwire.ShortChannelID] { - - return fn.NewLeft[*btcec.PublicKey, - lnwire.ShortChannelID](pub) - }, - }, - { - name: "via SCID", - nextNode: func(h *actorHarness, - pub *btcec.PublicKey) fn.Either[ - *btcec.PublicKey, lnwire.ShortChannelID] { - - scid := lnwire.NewShortChanIDFromInt(999) - h.resolver.addPeer(scid, pub) - - return fn.NewRight[*btcec.PublicKey](scid) - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - h := newActorHarness(t) - - // Generate a key for the next hop, then set the - // actor's peerPubKey to the same key to simulate - // the message arriving from that peer. - nextNodeKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - nextNodePub := nextNodeKey.PubKey() - - h.actor.peerPubKey = pubKeyToArray(nextNodePub) - - nextNode := tc.nextNode(h, nextNodePub) - rdA := record.NewNonFinalBlindedRouteDataOnionMessage( - nextNode, nil, nil, - ) - rdB := &record.BlindedRouteData{} - - plainA := EncodeBlindedRouteData(t, rdA) - plainB := EncodeBlindedRouteData(t, rdB) - hops := []*sphinx.HopInfo{ - { - NodePub: h.nodeKey.PubKey(), - PlainText: plainA, - }, - {NodePub: nextNodePub, PlainText: plainB}, - } - - blindedPath := BuildBlindedPath(t, hops) - onionMsg, _ := BuildOnionMessage(t, blindedPath, nil) - - req := &Request{msg: *onionMsg} - result := h.actor.Receive(t.Context(), req) - - // The actor must return an error. - require.True(t, result.IsErr()) - result.WhenErr(func(err error) { - require.ErrorIs(t, err, ErrSamePeerCycle) - }) - - // No message should have been forwarded. - select { - case <-h.sender.sent: - require.FailNow(t, "message should not have "+ - "been forwarded back to the sending "+ - "peer") - default: - } - - // No update should have been dispatched. - select { - case <-h.dispatcher.updates: - require.FailNow(t, "update should not be "+ - "dispatched for a cyclic message") - default: - } - }) - } -} - -// TestOnionPeerActorReceiveContextCanceled tests that OnionPeerActor.Receive -// returns an error when the context is canceled. -func TestOnionPeerActorReceiveContextCanceled(t *testing.T) { - t.Parallel() - - h := newActorHarness(t) - - ctx, cancel := context.WithCancel(t.Context()) - cancel() - - req := &Request{} - - result := h.actor.Receive(ctx, req) - - require.True(t, result.IsErr()) - result.WhenErr(func(err error) { - require.ErrorIs(t, err, ErrActorShuttingDown) - }) -} - -// TestOnionPeerActorReceiveInvalidOnionBlob verifies that processing fails -// gracefully when provided with an invalid onion blob that cannot be decoded. -func TestOnionPeerActorReceiveInvalidOnionBlob(t *testing.T) { - t.Parallel() - - h := newActorHarness(t) - - onionMsg := lnwire.OnionMessage{ - PathKey: h.nodeKey.PubKey(), - OnionBlob: []byte{1, 2, 3}, - } - - req := &Request{msg: onionMsg} - - result := h.actor.Receive(t.Context(), req) - require.True(t, result.IsErr()) - - // Verify no update was dispatched. - select { - case <-h.dispatcher.updates: - require.FailNow(t, "unexpected update dispatched") - default: - } -} - -// TestOnionPeerActorReceiveDispatcherError verifies that the actor returns an -// error when the update dispatcher fails. -func TestOnionPeerActorReceiveDispatcherError(t *testing.T) { - t.Parallel() - - h := newActorHarness(t) - h.dispatcher.err = fmt.Errorf("dispatcher error") - - rd := &record.BlindedRouteData{} - plain := EncodeBlindedRouteData(t, rd) - hops := []*sphinx.HopInfo{ - {NodePub: h.nodeKey.PubKey(), PlainText: plain}, - } - - blindedPath := BuildBlindedPath(t, hops) - onionMsg, _ := BuildOnionMessage(t, blindedPath, nil) - - req := &Request{msg: *onionMsg} - result := h.actor.Receive(t.Context(), req) - require.True(t, result.IsErr()) -} diff --git a/onionmessage/errors.go b/onionmessage/errors.go deleted file mode 100644 index 32898b41b..000000000 --- a/onionmessage/errors.go +++ /dev/null @@ -1,33 +0,0 @@ -package onionmessage - -import "errors" - -var ( - // ErrActorShuttingDown is returned by the actor logic when its context - // is cancelled. - ErrActorShuttingDown = errors.New("actor shutting down") - - // ErrNextNodeIdEmpty is returned when the next node ID is missing from - // the route data. - ErrNextNodeIdEmpty = errors.New("next node ID empty") - - // ErrSCIDEmpty is returned when the short channel ID is missing from - // the route data. - ErrSCIDEmpty = errors.New("short channel ID empty") - - // ErrSamePeerCycle is returned when a forwarding onion message - // would be sent back to the same peer it was received from. - ErrSamePeerCycle = errors.New("onion message cycle: next " + - "hop is the sending peer") - // ErrNoPathFound is returned when no path exists between the source - // and destination nodes that supports onion messaging. - ErrNoPathFound = errors.New("no path found to destination") - - // ErrDestinationNoOnionSupport is returned when the destination node - // does not advertise support for onion messages. - ErrDestinationNoOnionSupport = errors.New("destination does not " + - "support onion messages") - - // ErrNodeNotFound is returned when the node is not found in the graph. - ErrNodeNotFound = errors.New("node not found in graph") -) diff --git a/onionmessage/hop.go b/onionmessage/hop.go deleted file mode 100644 index 6cdfd97db..000000000 --- a/onionmessage/hop.go +++ /dev/null @@ -1,196 +0,0 @@ -package onionmessage - -import ( - "bytes" - "context" - - "github.com/btcsuite/btcd/btcec/v2" - sphinx "github.com/lightningnetwork/lightning-onion" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/record" - "github.com/lightningnetwork/lnd/tlv" -) - -// forwardAction contains the information needed to forward an onion message to -// the next node as well as update any subscribers with the payload we received. -type forwardAction struct { - // nextNodeID is the public key of the peer to forward the message to - nextNodeID *btcec.PublicKey - - // nextPathKey is the path key for the next hop, used for route - // blinding. - nextPathKey *btcec.PublicKey - - // nextPacket is the serialized onion packet to send to the next hop. - nextPacket []byte - - // payload contains the decoded payload for this hop, which may include - // custom records and routing information. - payload *lnwire.OnionMessagePayload -} - -// deliverAction contains the information needed to deliver the payload to any -// subscribers. Since we only support forwarding onion messages, this is only -// needed in itest to verify correct handling and behavior. -type deliverAction struct { - // payload contains the decoded payload for this hop, which may include - // custom records and routing information. - payload *lnwire.OnionMessagePayload -} - -type routingAction = fn.Either[forwardAction, deliverAction] - -// NodeIDResolver defines an interface to resolve a node public key from a short -// channel ID. -type NodeIDResolver interface { - RemotePubFromSCID(ctx context.Context, - scid lnwire.ShortChannelID) (*btcec.PublicKey, error) -} - -// processOnionMessage decodes and processes an onion message packet and its -// contents. It assumes route blinding is used, so it also decrypts encrypted -// recipient data, and derives the next path key. It returns a fn.Result type -// containing a routingAction, which contains all the information required to -// execute the next step in the routing process. -func processOnionMessage(ctx context.Context, router OnionRouter, - resolver NodeIDResolver, - msg *lnwire.OnionMessage) fn.Result[routingAction] { - - var onionPkt sphinx.OnionPacket - err := onionPkt.Decode(bytes.NewReader(msg.OnionBlob)) - if err != nil { - return fn.Err[routingAction](err) - } - - // TODO(gijs): We should not use the magic value 10 here. It's the - // incomingCltv value and only has use for the replay protection that we - // don't need anyway. - processedPkt, err := router.ProcessOnionPacket( - &onionPkt, nil, 10, sphinx.WithBlindingPoint(msg.PathKey), - ) - if err != nil { - return fn.Err[routingAction](err) - } - - payload := lnwire.NewOnionMessagePayload() - _, err = payload.Decode( - bytes.NewReader(processedPkt.Payload.Payload), - ) - if err != nil { - return fn.Err[routingAction](err) - } - - // Create a shallow copy of the payload but deep copy the EncryptedData - // field, as the decryption below will overwrite the EncryptedData field - // in-place. - originalPayload := *payload - originalPayload.EncryptedData = bytes.Clone(payload.EncryptedData) - - decrypted, err := router.DecryptBlindedHopData( - msg.PathKey, payload.EncryptedData, - ) - if err != nil { - return fn.Err[routingAction](err) - } - - routeData, err := record.DecodeBlindedRouteData( - bytes.NewReader(decrypted), - ) - if err != nil { - return fn.Err[routingAction](err) - } - - nextPathKey := deriveNextPathKey(router, msg.PathKey, - routeData.NextBlindingOverride) - - action, err := createRoutingAction( - ctx, resolver, processedPkt, &originalPayload, routeData, - nextPathKey, - ) - if err != nil { - return fn.Err[routingAction](err) - } - - return fn.Ok(action) -} - -// createRoutingAction creates the routing action based on whether we are -// forwarding or the receiver of the onion message. -func createRoutingAction(ctx context.Context, resolver NodeIDResolver, - packet *sphinx.ProcessedPacket, payload *lnwire.OnionMessagePayload, - routeData *record.BlindedRouteData, - nextPathKey *btcec.PublicKey) (routingAction, error) { - - if isForwarding(packet) { - var nextNodeID *btcec.PublicKey - if routeData.NextNodeID.IsSome() { - n, err := routeData.NextNodeID.UnwrapOrErr( - ErrNextNodeIdEmpty, - ) - if err != nil { - return routingAction{}, err - } - nextNodeID = n.Val - } else { - scid, err := routeData.ShortChannelID.UnwrapOrErr( - ErrSCIDEmpty, - ) - if err != nil { - return routingAction{}, err - } - nextNodeID, err = resolver.RemotePubFromSCID( - ctx, scid.Val, - ) - if err != nil { - return routingAction{}, err - } - } - - buf := new(bytes.Buffer) - err := packet.NextPacket.Encode(buf) - if err != nil { - return routingAction{}, err - } - nextPacket := buf.Bytes() - - return fn.NewLeft[forwardAction, deliverAction](forwardAction{ - nextNodeID: nextNodeID, - nextPathKey: nextPathKey, - nextPacket: nextPacket, - payload: payload, - }), nil - } - - return fn.NewRight[forwardAction](deliverAction{ - payload: payload, - }), nil -} - -// deriveNextPathKey derives the next path key using the router and current -// path key. If an override is provided, it is used instead. -func deriveNextPathKey(router OnionRouter, currentPathKey *btcec.PublicKey, - override tlv.OptionalRecordT[tlv.TlvType8, - *btcec.PublicKey]) *btcec.PublicKey { - - // If an override is provided, use it. - return override.UnwrapOrFunc(func() tlv.RecordT[tlv.TlvType8, - *btcec.PublicKey] { - - // Otherwise, derive the next path key using the router. - nextKey, err := router.NextEphemeral(currentPathKey) - if err != nil { - // If the derivation fails, log and return a zero key. - log.Warnf("Failed to derive next path key: %v", err) - - return override.Zero() - } - - return tlv.NewPrimitiveRecord[tlv.TlvType8](nextKey) - }).Val -} - -// isForwarding checks if the packet is to be forwarded or delivered. -func isForwarding(packet *sphinx.ProcessedPacket) bool { - return packet.Action != sphinx.ExitNode -} diff --git a/onionmessage/hop_test.go b/onionmessage/hop_test.go deleted file mode 100644 index 94910db84..000000000 --- a/onionmessage/hop_test.go +++ /dev/null @@ -1,288 +0,0 @@ -package onionmessage - -import ( - "testing" - - "github.com/btcsuite/btcd/btcec/v2" - sphinx "github.com/lightningnetwork/lightning-onion" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/record" - "github.com/lightningnetwork/lnd/tlv" - "github.com/stretchr/testify/require" -) - -// processOnionMessageTest defines the test parameters for testing -// processOnionMessage with different routing scenarios. -type processOnionMessageTest struct { - name string - hopsToBlind []*sphinx.HopInfo - isDeliver bool - expectedNextNode *btcec.PublicKey - expectedOverride *btcec.PublicKey -} - -// TestProcessOnionMessage tests the processOnionMessage function with various -// forwarding and delivery scenarios. -func TestProcessOnionMessage(t *testing.T) { - // Helper to generate keys. - genKey := func() *btcec.PrivateKey { - k, err := btcec.NewPrivateKey() - require.NoError(t, err) - return k - } - - // Setup the local node (router). - nodeKeyA := genKey() - pubKeyA := nodeKeyA.PubKey() - - router := sphinx.NewRouter( - &sphinx.PrivKeyECDH{PrivKey: nodeKeyA}, - sphinx.NewNoOpReplayLog(), - ) - require.NoError(t, router.Start()) - defer router.Stop() - - resolver := newMockNodeIDResolver() - - // Pre-generate keys for test cases. - nodeKeyB := genKey() - pubKeyB := nodeKeyB.PubKey() - - overrideKey := genKey() - pubKeyOverride := overrideKey.PubKey() - - // Helper to encode route data. - encodeData := func(data *record.BlindedRouteData) []byte { - b, err := record.EncodeBlindedRouteData(data) - require.NoError(t, err) - return b - } - - // Case 1 Data: Forward Action Success. - nextNodeByPubKey := fn.NewLeft[*btcec.PublicKey, lnwire.ShortChannelID]( - pubKeyB, - ) - rd1A := record.NewNonFinalBlindedRouteDataOnionMessage( - nextNodeByPubKey, nil, nil, - ) - rd1B := &record.BlindedRouteData{} - hops1 := []*sphinx.HopInfo{ - {NodePub: pubKeyA, PlainText: encodeData(rd1A)}, - {NodePub: pubKeyB, PlainText: encodeData(rd1B)}, - } - - // Case 2 Data: Forward Action Path Key Override Success. - nextNodeWithOverride := fn.NewLeft[ - *btcec.PublicKey, lnwire.ShortChannelID, - ](pubKeyB) - rd2A := record.NewNonFinalBlindedRouteDataOnionMessage( - nextNodeWithOverride, pubKeyOverride, nil, - ) - rd2B := &record.BlindedRouteData{} - hops2 := []*sphinx.HopInfo{ - {NodePub: pubKeyA, PlainText: encodeData(rd2A)}, - {NodePub: pubKeyB, PlainText: encodeData(rd2B)}, - } - - // Case 3 Data: Forward Action Success with SCID resolution. - scid := lnwire.NewShortChanIDFromInt(12345) - resolver.addPeer(scid, pubKeyB) - - nextNodeBySCID := fn.NewRight[*btcec.PublicKey]( - scid, - ) - rd3A := record.NewNonFinalBlindedRouteDataOnionMessage( - nextNodeBySCID, nil, nil, - ) - rd3B := &record.BlindedRouteData{} - hops3 := []*sphinx.HopInfo{ - {NodePub: pubKeyA, PlainText: encodeData(rd3A)}, - {NodePub: pubKeyB, PlainText: encodeData(rd3B)}, - } - - // Case 4 Data: Deliver Action Success. - rd4 := &record.BlindedRouteData{} - hops4 := []*sphinx.HopInfo{ - {NodePub: pubKeyA, PlainText: encodeData(rd4)}, - } - - tests := []processOnionMessageTest{ - { - name: "Forward Action Success", - hopsToBlind: hops1, - isDeliver: false, - expectedNextNode: pubKeyB, - expectedOverride: nil, // No path key override. - }, - { - name: "Forward Action Path Key Override " + - "Success", - hopsToBlind: hops2, - isDeliver: false, - expectedNextNode: pubKeyB, - expectedOverride: pubKeyOverride, - }, - { - name: "Forward Action Success with SCID " + - "Resolution", - hopsToBlind: hops3, - isDeliver: false, - expectedNextNode: pubKeyB, - expectedOverride: nil, - }, - { - name: "Deliver Action Success", - hopsToBlind: hops4, - isDeliver: true, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - testProcessOnionMessageCase(t, router, resolver, tc) - }) - } -} - -// testProcessOnionMessageCase is a helper that executes a single test case for -// processOnionMessage, building the blinded path and verifying the result. -func testProcessOnionMessageCase(t *testing.T, router OnionRouter, - resolver NodeIDResolver, tc processOnionMessageTest) { - - blindedPath := BuildBlindedPath(t, tc.hopsToBlind) - msg, expectedCipherTexts := BuildOnionMessage( - t, blindedPath, nil, - ) - - // Process the message. - result := processOnionMessage(t.Context(), router, resolver, msg) - require.True(t, result.IsOk()) - - // Verify result. - if tc.isDeliver { - result.WhenOk(func(action routingAction) { - // Should be deliverAction. - require.True(t, action.IsRight()) - action.WhenRight(func(dlvrAction deliverAction) { - require.Equal( - t, - expectedCipherTexts[0], - dlvrAction.payload.EncryptedData, - ) - }) - }) - } else { - result.WhenOk(func(action routingAction) { - // Should be forwardAction. - require.True(t, action.IsLeft()) - action.WhenLeft(func(fwdAction forwardAction) { - require.Equal( - t, tc.expectedNextNode, - fwdAction.nextNodeID, - ) - - if tc.expectedOverride != nil { - require.Equal( - t, tc.expectedOverride, - fwdAction.nextPathKey, - ) - } else { - require.NotNil(t, fwdAction.nextPathKey) - } - - require.NotEmpty(t, fwdAction.nextPacket) - require.Equal( - t, - expectedCipherTexts[0], - fwdAction.payload.EncryptedData, - ) - }) - }) - } -} - -// TestIsForwarding tests the isForwarding function. -func TestIsForwarding(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - packet *sphinx.ProcessedPacket - expected bool - }{ - { - name: "forwarding", - packet: &sphinx.ProcessedPacket{ - Action: sphinx.MoreHops, - }, - expected: true, - }, - { - name: "delivery", - packet: &sphinx.ProcessedPacket{ - Action: sphinx.ExitNode, - }, - expected: false, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - result := isForwarding(test.packet) - require.Equal(t, test.expected, result) - }) - } -} - -// TestDeriveNextPathKey tests the deriveNextPathKey function. -func TestDeriveNextPathKey(t *testing.T) { - t.Parallel() - - // create a private key for the router. - privKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - // create a path key. - sessionKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - pathKey := sessionKey.PubKey() - - // Create a router. We don't need a replay log for this test as - // NextEphemeral doesn't use it. - router := sphinx.NewRouter(&sphinx.PrivKeyECDH{PrivKey: privKey}, nil) - - t.Run("override present", func(t *testing.T) { - overrideKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - override := tlv.NewPrimitiveRecord[tlv.TlvType8]( - overrideKey.PubKey(), - ) - optOverride := tlv.SomeRecordT(override) - - // Router can be nil as it shouldn't be used. - result := deriveNextPathKey(nil, pathKey, optOverride) - require.Equal(t, overrideKey.PubKey(), result) - }) - - t.Run("derive success", func(t *testing.T) { - override := tlv.OptionalRecordT[tlv.TlvType8, - *btcec.PublicKey]{} - - result := deriveNextPathKey(router, pathKey, override) - require.NotNil(t, result) - - // Verify it matches manual derivation. - expected, err := router.NextEphemeral(pathKey) - require.NoError(t, err) - require.Equal(t, expected, result) - }) - - // It's currently impossible to test derivation failure as there is no - // way to make the key derivation fail with an error. You can only make - // it panick by passing in a nil path key. This is due to how - // PrivKeyECDH.ECDH is implemented in the keychain package. -} diff --git a/onionmessage/interfaces.go b/onionmessage/interfaces.go deleted file mode 100644 index 15da70425..000000000 --- a/onionmessage/interfaces.go +++ /dev/null @@ -1,41 +0,0 @@ -package onionmessage - -import ( - "github.com/btcsuite/btcd/btcec/v2" - sphinx "github.com/lightningnetwork/lightning-onion" - "github.com/lightningnetwork/lnd/lnwire" -) - -// OnionRouter wraps the sphinx router operations needed for onion message -// processing. -type OnionRouter interface { - // ProcessOnionPacket processes an onion packet and returns the - // processed result. - ProcessOnionPacket(pkt *sphinx.OnionPacket, assocData []byte, - incomingCltv uint32, - opts ...sphinx.ProcessOnionOpt) (*sphinx.ProcessedPacket, error) - - // DecryptBlindedHopData decrypts the encrypted hop data using the - // given path key. - DecryptBlindedHopData(pathKey *btcec.PublicKey, - encData []byte) ([]byte, error) - - // NextEphemeral derives the next ephemeral key from the current path - // key. - NextEphemeral( - currentPathKey *btcec.PublicKey) (*btcec.PublicKey, error) -} - -// OnionMessageUpdateDispatcher dispatches onion message updates to -// subscribers. -type OnionMessageUpdateDispatcher interface { - // SendUpdate sends an onion message update to all subscribers. - SendUpdate(update any) error -} - -// PeerMessageSender sends onion messages to peers identified by public key. -type PeerMessageSender interface { - // SendToPeer sends an onion message to the peer identified by the - // given compressed public key. - SendToPeer(pubKey [33]byte, msg *lnwire.OnionMessage) error -} diff --git a/onionmessage/log.go b/onionmessage/log.go deleted file mode 100644 index 7bf093681..000000000 --- a/onionmessage/log.go +++ /dev/null @@ -1,32 +0,0 @@ -package onionmessage - -import ( - "github.com/btcsuite/btclog/v2" - "github.com/lightningnetwork/lnd/build" -) - -// Subsystem defines the logging code for this subsystem. -const Subsystem = "OMSG" - -// log is a logger that is initialized with no output filters. This -// means the package will not perform any logging by default until the caller -// requests it. -var log btclog.Logger - -// The default amount of logging is none. -func init() { - UseLogger(build.NewSubLogger(Subsystem, nil)) -} - -// DisableLog disables all library log output. Logging output is disabled -// by default until UseLogger is called. -func DisableLog() { - UseLogger(btclog.Disabled) -} - -// UseLogger uses a specified Logger to output package logging info. -// This should be used in preference to SetLogWriter if the caller is also -// using btclog. -func UseLogger(logger btclog.Logger) { - log = logger -} diff --git a/onionmessage/onion_endpoint.go b/onionmessage/onion_endpoint.go deleted file mode 100644 index 0c9829e24..000000000 --- a/onionmessage/onion_endpoint.go +++ /dev/null @@ -1,36 +0,0 @@ -package onionmessage - -import ( - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/record" -) - -// OnionMessageUpdate is onion message update dispatched to any potential -// subscriber. -type OnionMessageUpdate struct { - // Peer is the peer pubkey - Peer [33]byte - - // PathKey is the route blinding ephemeral pubkey to be used for - // the onion message. - PathKey [33]byte - - // OnionBlob is the raw serialized mix header used to relay messages in - // a privacy-preserving manner. This blob should be handled in the same - // manner as onions used to route HTLCs, with the exception that it uses - // blinded routes by default. - OnionBlob []byte - - // CustomRecords contains any custom TLV records included in the - // payload. - CustomRecords record.CustomSet - - // ReplyPath contains the reply path information for the onion message. - ReplyPath *lnwire.BlindedPath - - // EncryptedRecipientData contains the encrypted recipient data for the - // onion message, created by the creator of the blinded route. This is - // the receiver for the last leg of the route, and the sender for the - // first leg up to the introduction point. - EncryptedRecipientData []byte -} diff --git a/onionmessage/pathfind.go b/onionmessage/pathfind.go deleted file mode 100644 index a9e4ff6ff..000000000 --- a/onionmessage/pathfind.go +++ /dev/null @@ -1,161 +0,0 @@ -package onionmessage - -import ( - "context" - "errors" - - graphdb "github.com/lightningnetwork/lnd/graph/db" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/routing/route" -) - -// OnionMessagePath represents a route found for an onion message. It is a slice -// of vertices ordered from the first-hop peer to the destination. -type OnionMessagePath []route.Vertex - -// FindPath finds the shortest path (by hop count) from source to destination -// through nodes that support onion messaging (feature bit 38/39). It uses a -// standard BFS on the channel graph filtered by the OnionMessagesOptional -// feature bit. -func FindPath(ctx context.Context, graph graphdb.NodeTraverser, source, - destination route.Vertex, maxHops int) (OnionMessagePath, error) { - - // Check that the destination supports onion messaging. - destFeatures, err := graph.FetchNodeFeatures(ctx, destination) - if err != nil { - return nil, err - } - - // An empty feature vector means the node is absent from our graph. - // In that case, we send back a NotFound error. - if len(destFeatures.Features()) == 0 { - return nil, ErrNodeNotFound - } - - if !destFeatures.HasFeature(lnwire.OnionMessagesOptional) { - return nil, ErrDestinationNoOnionSupport - } - - // If source == destination, return empty path. - if source == destination { - return OnionMessagePath{}, nil - } - - parent := make(map[route.Vertex]route.Vertex) - visited := make(map[route.Vertex]bool) - - visited[source] = true - - queue := []route.Vertex{source} - depth := 0 - - for len(queue) > 0 { - depth++ - if depth > maxHops { - break - } - - nextQueue := make([]route.Vertex, 0) - - for _, current := range queue { - err := graph.ForEachNodeDirectedChannel(ctx, current, - func(channel *graphdb.DirectedChannel) error { - neighbor := channel.OtherNode - - if visited[neighbor] { - return nil - } - - // Mark visited before the feature check - // so we never fetch features for the - // same node twice. - visited[neighbor] = true - - // Skip nodes that don't support onion - // messaging. - feats, err := graph.FetchNodeFeatures( - ctx, neighbor, - ) - if err != nil { - // If the context is canceled or - // deadline exceeded, propagate - // the error. - - if ctx.Err() != nil { - return err - } - - log.Tracef("Unable to fetch "+ - "features for node "+ - "%v: %v", - neighbor.String(), err) - - return nil - } - - if !feats.HasFeature( - lnwire.OnionMessagesOptional, - ) { - - return nil - } - - parent[neighbor] = current - - if neighbor == destination { - return errBFSDone - } - - nextQueue = append( - nextQueue, neighbor, - ) - - return nil - }, - func() {}, - ) - - // Check if we found the destination. - if errors.Is(err, errBFSDone) { - return reconstructPath( - parent, source, destination, - ), nil - } - - if err != nil { - return nil, err - } - } - - queue = nextQueue - } - - return nil, ErrNoPathFound -} - -// errBFSDone is a sentinel error used internally to break out of the -// ForEachNodeDirectedChannel callback when the destination is found. -var errBFSDone = errors.New("bfs done") - -// reconstructPath rebuilds the path from destination back to source using the -// parent map, returning the hops in forward order (excluding source). -func reconstructPath(parent map[route.Vertex]route.Vertex, - source, destination route.Vertex) OnionMessagePath { - - // Calculate path length to pre-allocate the slice. - pathLen := 0 - for curr := destination; curr != source; curr = parent[curr] { - pathLen++ - } - - // Populate the path in correct order, avoiding a separate reversal - // step. - path := make(OnionMessagePath, pathLen) - curr := destination - for i := pathLen - 1; i >= 0; i-- { - path[i] = curr - curr = parent[curr] - } - - return path -} diff --git a/onionmessage/pathfind_test.go b/onionmessage/pathfind_test.go deleted file mode 100644 index 1b7e6ff70..000000000 --- a/onionmessage/pathfind_test.go +++ /dev/null @@ -1,361 +0,0 @@ -package onionmessage - -import ( - "context" - "testing" - - graphdb "github.com/lightningnetwork/lnd/graph/db" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/routing/route" - "github.com/stretchr/testify/require" -) - -// mockNodeTraverser implements graphdb.NodeTraverser for testing the BFS -// pathfinding algorithm. -type mockNodeTraverser struct { - // edges maps each node to its list of channel neighbors. - edges map[route.Vertex][]route.Vertex - - // features maps each node to its advertised feature vector. - features map[route.Vertex]*lnwire.FeatureVector -} - -// newMockNodeTraverser creates a new mockNodeTraverser with initialized maps. -func newMockNodeTraverser() *mockNodeTraverser { - return &mockNodeTraverser{ - edges: make(map[route.Vertex][]route.Vertex), - features: make(map[route.Vertex]*lnwire.FeatureVector), - } -} - -// addNode adds a node with the given features to the mock graph. -func (m *mockNodeTraverser) addNode(v route.Vertex, - features *lnwire.FeatureVector) { - - m.features[v] = features -} - -// addEdge adds a bidirectional edge between two nodes. -func (m *mockNodeTraverser) addEdge(a, b route.Vertex) { - m.edges[a] = append(m.edges[a], b) - m.edges[b] = append(m.edges[b], a) -} - -// ForEachNodeDirectedChannel calls the callback for every channel neighbor of -// the given node. -func (m *mockNodeTraverser) ForEachNodeDirectedChannel( - _ context.Context, nodePub route.Vertex, - cb func(channel *graphdb.DirectedChannel) error, - reset func()) error { - - neighbors, ok := m.edges[nodePub] - if !ok { - return nil - } - - for _, neighbor := range neighbors { - err := cb(&graphdb.DirectedChannel{ - OtherNode: neighbor, - }) - if err != nil { - return err - } - } - - return nil -} - -// FetchNodeFeatures returns the features of the given node. Returns an -// EmptyFeatureVector (nil error) when the node is absent, matching the -// production DB behaviour of graphdb.nodeTraverserSession. -func (m *mockNodeTraverser) FetchNodeFeatures( - _ context.Context, - nodePub route.Vertex) (*lnwire.FeatureVector, error) { - - features, ok := m.features[nodePub] - if !ok { - return lnwire.EmptyFeatureVector(), nil - } - - return features, nil -} - -// vertexFromByte creates a test Vertex from a single byte for readability. -func vertexFromByte(b byte) route.Vertex { - var v route.Vertex - v[0] = b - - return v -} - -// onionFeatures returns a feature vector with the OnionMessagesOptional bit -// set. -func onionFeatures() *lnwire.FeatureVector { - return lnwire.NewFeatureVector( - lnwire.NewRawFeatureVector(lnwire.OnionMessagesOptional), - nil, - ) -} - -// noOnionFeatures returns a feature vector that has some bits set (e.g. -// data-loss-protect) but NOT onion message support. This simulates a node -// that is present in the graph but does not advertise onion messages, -// as distinct from a node that is absent from the graph entirely (which -// returns a zero-bit EmptyFeatureVector). -func noOnionFeatures() *lnwire.FeatureVector { - return lnwire.NewFeatureVector( - lnwire.NewRawFeatureVector(lnwire.DataLossProtectOptional), - nil, - ) -} - -// TestFindPathDirectNeighbor tests pathfinding when destination is a direct -// neighbor. -func TestFindPathDirectNeighbor(t *testing.T) { - t.Parallel() - - graph := newMockNodeTraverser() - - source := vertexFromByte(1) - dest := vertexFromByte(2) - - graph.addNode(source, onionFeatures()) - graph.addNode(dest, onionFeatures()) - graph.addEdge(source, dest) - - path, err := FindPath(t.Context(), graph, source, dest, 20) - require.NoError(t, err) - require.Len(t, path, 1) - require.Equal(t, dest, path[0]) -} - -// TestFindPathMultiHop tests pathfinding across multiple hops. -func TestFindPathMultiHop(t *testing.T) { - t.Parallel() - - graph := newMockNodeTraverser() - - source := vertexFromByte(1) - hop1 := vertexFromByte(2) - hop2 := vertexFromByte(3) - dest := vertexFromByte(4) - - graph.addNode(source, onionFeatures()) - graph.addNode(hop1, onionFeatures()) - graph.addNode(hop2, onionFeatures()) - graph.addNode(dest, onionFeatures()) - - graph.addEdge(source, hop1) - graph.addEdge(hop1, hop2) - graph.addEdge(hop2, dest) - - path, err := FindPath(t.Context(), graph, source, dest, 20) - require.NoError(t, err) - require.Len(t, path, 3) - require.Equal(t, hop1, path[0]) - require.Equal(t, hop2, path[1]) - require.Equal(t, dest, path[2]) -} - -// TestFindPathFeatureFiltering tests that nodes without onion message support -// are skipped, finding a longer path through supporting nodes. -func TestFindPathFeatureFiltering(t *testing.T) { - t.Parallel() - - graph := newMockNodeTraverser() - - source := vertexFromByte(1) - noOnion := vertexFromByte(2) - withOnion := vertexFromByte(3) - withOnion2 := vertexFromByte(4) - dest := vertexFromByte(5) - - graph.addNode(source, onionFeatures()) - graph.addNode(noOnion, noOnionFeatures()) - graph.addNode(withOnion, onionFeatures()) - graph.addNode(withOnion2, onionFeatures()) - graph.addNode(dest, onionFeatures()) - - // Direct path through noOnion (shorter). - graph.addEdge(source, noOnion) - graph.addEdge(noOnion, dest) - - // Alternate path through withOnion (longer). - graph.addEdge(source, withOnion) - graph.addEdge(withOnion, withOnion2) - graph.addEdge(withOnion2, dest) - - path, err := FindPath(t.Context(), graph, source, dest, 20) - require.NoError(t, err) - require.Len(t, path, 3) - require.Equal(t, withOnion, path[0]) - require.Equal(t, withOnion2, path[1]) - require.Equal(t, dest, path[2]) -} - -// TestFindPathNoPathExists tests that ErrNoPathFound is returned when the -// graph is disconnected. -func TestFindPathNoPathExists(t *testing.T) { - t.Parallel() - - graph := newMockNodeTraverser() - - source := vertexFromByte(1) - dest := vertexFromByte(2) - - graph.addNode(source, onionFeatures()) - graph.addNode(dest, onionFeatures()) - - // No edges between source and dest. - _, err := FindPath(t.Context(), graph, source, dest, 20) - require.ErrorIs(t, err, ErrNoPathFound) -} - -// TestFindPathDestinationNotInGraph tests that ErrNodeNotFound is returned when -// the destination has no entry in the graph (empty feature vector). -func TestFindPathDestinationNotInGraph(t *testing.T) { - t.Parallel() - - graph := newMockNodeTraverser() - - source := vertexFromByte(1) - dest := vertexFromByte(2) - - // dest not added to graph; FetchNodeFeatures returns - // EmptyFeatureVector. - graph.addNode(source, onionFeatures()) - - _, err := FindPath(t.Context(), graph, source, dest, 20) - require.ErrorIs(t, err, ErrNodeNotFound) -} - -// TestFindPathDestinationNoOnionSupport tests that -// ErrDestinationNoOnionSupport is returned when the destination doesn't -// support onion messages. -func TestFindPathDestinationNoOnionSupport(t *testing.T) { - t.Parallel() - - graph := newMockNodeTraverser() - - source := vertexFromByte(1) - dest := vertexFromByte(2) - - graph.addNode(source, onionFeatures()) - graph.addNode(dest, noOnionFeatures()) - graph.addEdge(source, dest) - - _, err := FindPath(t.Context(), graph, source, dest, 20) - require.ErrorIs(t, err, ErrDestinationNoOnionSupport) -} - -// TestFindPathMaxHopsExceeded tests that ErrNoPathFound is returned when the -// path exceeds the maximum number of hops. -func TestFindPathMaxHopsExceeded(t *testing.T) { - t.Parallel() - - graph := newMockNodeTraverser() - - source := vertexFromByte(1) - hop1 := vertexFromByte(2) - hop2 := vertexFromByte(3) - dest := vertexFromByte(4) - - graph.addNode(source, onionFeatures()) - graph.addNode(hop1, onionFeatures()) - graph.addNode(hop2, onionFeatures()) - graph.addNode(dest, onionFeatures()) - - graph.addEdge(source, hop1) - graph.addEdge(hop1, hop2) - graph.addEdge(hop2, dest) - - // Path requires 3 hops, but maxHops is 2. - _, err := FindPath(t.Context(), graph, source, dest, 2) - require.ErrorIs(t, err, ErrNoPathFound) -} - -// TestFindPathWithCycles tests that BFS correctly handles cycles in the graph. -func TestFindPathWithCycles(t *testing.T) { - t.Parallel() - - graph := newMockNodeTraverser() - - source := vertexFromByte(1) - a := vertexFromByte(2) - b := vertexFromByte(3) - c := vertexFromByte(4) - dest := vertexFromByte(5) - - graph.addNode(source, onionFeatures()) - graph.addNode(a, onionFeatures()) - graph.addNode(b, onionFeatures()) - graph.addNode(c, onionFeatures()) - graph.addNode(dest, onionFeatures()) - - // Create a cycle: source -> a -> b -> c -> a - graph.addEdge(source, a) - graph.addEdge(a, b) - graph.addEdge(b, c) - graph.addEdge(c, a) - - // Path to dest through b. - graph.addEdge(b, dest) - - path, err := FindPath(t.Context(), graph, source, dest, 20) - require.NoError(t, err) - require.Len(t, path, 3) - require.Equal(t, a, path[0]) - require.Equal(t, b, path[1]) - require.Equal(t, dest, path[2]) -} - -// TestFindPathShortestPath tests that BFS finds the shortest path when -// multiple paths of different lengths exist. -func TestFindPathShortestPath(t *testing.T) { - t.Parallel() - - graph := newMockNodeTraverser() - - source := vertexFromByte(1) - a := vertexFromByte(2) - b := vertexFromByte(3) - c := vertexFromByte(4) - dest := vertexFromByte(5) - - graph.addNode(source, onionFeatures()) - graph.addNode(a, onionFeatures()) - graph.addNode(b, onionFeatures()) - graph.addNode(c, onionFeatures()) - graph.addNode(dest, onionFeatures()) - - // Long path: source -> a -> b -> c -> dest (4 hops). - graph.addEdge(source, a) - graph.addEdge(a, b) - graph.addEdge(b, c) - graph.addEdge(c, dest) - - // Short path: source -> b -> dest (2 hops). - graph.addEdge(source, b) - graph.addEdge(b, dest) - - path, err := FindPath(t.Context(), graph, source, dest, 20) - require.NoError(t, err) - require.Len(t, path, 2) - require.Equal(t, b, path[0]) - require.Equal(t, dest, path[1]) -} - -// TestFindPathSameSourceAndDest tests that finding a path from a node to -// itself returns an empty path. -func TestFindPathSameSourceAndDest(t *testing.T) { - t.Parallel() - - graph := newMockNodeTraverser() - - node := vertexFromByte(1) - graph.addNode(node, onionFeatures()) - - path, err := FindPath(t.Context(), graph, node, node, 20) - require.NoError(t, err) - require.Len(t, path, 0) -} diff --git a/onionmessage/ratelimit.go b/onionmessage/ratelimit.go deleted file mode 100644 index f012951d5..000000000 --- a/onionmessage/ratelimit.go +++ /dev/null @@ -1,320 +0,0 @@ -package onionmessage - -import ( - "errors" - "sync/atomic" - "time" - - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/lnutils" - "golang.org/x/time/rate" -) - -var ( - // ErrPeerRateLimit is the sentinel error returned by - // IngressLimiter.AllowN when the per-peer token bucket rejects an - // incoming onion message. Callers match on it with errors.Is to - // distinguish per-peer drops from global drops. - ErrPeerRateLimit = errors.New("per-peer rate limit exceeded") - - // ErrGlobalRateLimit is the sentinel error returned by - // IngressLimiter.AllowN when the global token bucket rejects an - // incoming onion message. Callers match on it with errors.Is to - // distinguish global drops from per-peer drops. - ErrGlobalRateLimit = errors.New("global rate limit exceeded") -) - -// kbpsToBytesPerSecond converts a configured kilobits-per-second value into -// bytes-per-second, suitable for passing to rate.NewLimiter. A Kbps value is -// decimal (1 Kbps = 1000 bits/second) so the conversion factor is 125. -func kbpsToBytesPerSecond(kbps uint64) float64 { - return float64(kbps) * 125.0 -} - -// RateLimiter is the minimal token-bucket interface used at the onion message -// ingress path. Tokens are bytes: each call reports whether a message of size -// n bytes is permitted to proceed, and on success consumes n bytes from the -// underlying bucket. The interface is satisfied by *rate.Limiter (via a small -// counting wrapper) and a noop implementation used when a limit is configured -// as zero (disabled). It exists so that callers and tests can substitute -// alternate implementations without taking a hard dependency on the -// x/time/rate package. -// -// Implementations of AllowN must be safe for concurrent use by multiple -// goroutines; the ingress call site invokes it from per-peer readHandler -// goroutines without additional synchronization. -type RateLimiter interface { - // AllowN reports whether an onion message of n bytes is permitted - // to proceed at the current instant. It must be non-blocking and - // safe for concurrent use. - AllowN(n int) bool -} - -// noopLimiter is a RateLimiter that always allows traffic. It is returned by -// NewGlobalLimiter when the configured rate or burst is zero, meaning rate -// limiting is disabled and all messages are permitted without restriction. -// Using a noopLimiter avoids branching at the call site. PeerRateLimiter -// does not use this type directly; it short-circuits via its own disabled() -// helper. -type noopLimiter struct{} - -// AllowN always returns true regardless of the requested byte count. -func (noopLimiter) AllowN(int) bool { return true } - -// countingLimiter wraps a *rate.Limiter and tracks how many calls to AllowN -// have been rejected. The counter is exposed via Dropped for observability, -// and a one-shot flag records whether a log line has been emitted for the -// first rejection so operators can see that the limiter actually fired. -type countingLimiter struct { - limiter *rate.Limiter - dropped atomic.Uint64 - firstLog atomic.Bool -} - -// AllowN consults the underlying token bucket for n bytes and increments -// the dropped counter on rejection. -func (c *countingLimiter) AllowN(n int) bool { - if c.limiter.AllowN(time.Now(), n) { - return true - } - c.dropped.Add(1) - - return false -} - -// FirstDropClaim atomically returns true exactly once, on the first call. -// The caller is responsible for only invoking it after a rejection has -// actually occurred; the method itself does not inspect the dropped -// counter. It exists so that the ingress call site can emit a single -// info-level log line when a limiter first trips, without spamming the -// log on every subsequent drop. -func (c *countingLimiter) FirstDropClaim() bool { - return c.firstLog.CompareAndSwap(false, true) -} - -// Dropped returns the total number of onion messages this limiter has -// rejected since process start. -func (c *countingLimiter) Dropped() uint64 { - return c.dropped.Load() -} - -// NewGlobalLimiter constructs a process-wide onion message rate limiter. -// kbps is the sustained rate in kilobits per second (1 Kbps = 1000 bits/s); -// burstBytes is the token bucket depth in bytes. A zero rate or a zero -// burst disables limiting and returns a noopLimiter. Otherwise the returned -// RateLimiter is a token bucket whose tokens are bytes. -func NewGlobalLimiter(kbps uint64, burstBytes uint64) RateLimiter { - if kbps == 0 || burstBytes == 0 { - return noopLimiter{} - } - - bps := kbpsToBytesPerSecond(kbps) - - return &countingLimiter{ - limiter: rate.NewLimiter(rate.Limit(bps), int(burstBytes)), - } -} - -// PeerRateLimiter is a registry of per-peer onion message token buckets, -// keyed by the peer's compressed public key. Tokens are bytes: callers pass -// the on-the-wire size of each message to AllowN and the per-peer bucket is -// debited accordingly. Buckets are created lazily on the first call to -// AllowN for a given peer and retained for the lifetime of the process. -// When the configured rate or burst is zero the registry operates in -// disabled mode and AllowN is a no-op. -// -// Retention across disconnect is load-bearing. Without it, a peer could -// drain its burst, disconnect, reconnect, and get a fresh full-burst -// bucket on every cycle, effectively promoting the global limiter into -// its per-peer rate and using the shared budget as a personal allowance -// until the global bucket trips. By keeping the bucket, a drained peer -// stays drained until its bucket naturally refills regardless of how -// often it cycles the connection, and the per-peer rate becomes a real -// ceiling rather than a per-connection ceiling. -// -// The memory cost of retention is bounded by the number of channel -// peers that have ever sent an onion message: the ingress call site -// gates AllowN on the peer having at least one open channel before -// touching this registry, so random connecting strangers never allocate -// a bucket. At a realistic few hundred to few thousand channel partners -// and ~200 bytes per entry (rate.Limiter plus SyncMap overhead), the -// registry stays comfortably sub-megabyte for the lifetime of the -// process. -// -// The underlying bucket registry is an lnutils.SyncMap rather than a plain -// map guarded by a mutex. Per-peer keys are stable for the lifetime of the -// connection and the common path is a Load hit, which sync.Map serves -// without any write contention across peers. A plain map would serialize -// every hot-path AllowN call behind a single mutex even though rate.Limiter -// is already safe for concurrent use. -type PeerRateLimiter struct { - rate rate.Limit - burst int - peers lnutils.SyncMap[[33]byte, *rate.Limiter] - dropped atomic.Uint64 - firstLog atomic.Bool -} - -// FirstDropClaim atomically returns true exactly once, on the first call. -// The caller is responsible for only invoking it after a rejection has -// actually occurred. The ingress site uses this to emit a single -// info-level log line when per-peer rate limiting first trips, rather -// than spamming the log on every drop. -func (p *PeerRateLimiter) FirstDropClaim() bool { - return p.firstLog.CompareAndSwap(false, true) -} - -// NewPeerRateLimiter constructs a per-peer onion message rate limiter. -// kbps is the per-peer sustained rate in kilobits per second and burstBytes -// is the per-peer token bucket depth in bytes. A zero rate or a zero burst -// disables limiting; in that case AllowN always returns true and no -// per-peer state is retained. -func NewPeerRateLimiter(kbps uint64, burstBytes uint64) *PeerRateLimiter { - p := &PeerRateLimiter{} - if kbps > 0 && burstBytes > 0 { - p.rate = rate.Limit(kbpsToBytesPerSecond(kbps)) - p.burst = int(burstBytes) - } - - return p -} - -// disabled reports whether the limiter has been configured to permit all -// traffic. -func (p *PeerRateLimiter) disabled() bool { - return p.rate == 0 || p.burst <= 0 -} - -// AllowN reports whether an onion message of n bytes from the given peer -// is permitted at the current instant. The peer's bucket is created on -// first use. Rejected calls are counted and visible via Dropped. -func (p *PeerRateLimiter) AllowN(peer [33]byte, n int) bool { - if p.disabled() { - return true - } - - lim, ok := p.peers.Load(peer) - if !ok { - // Allocate a fresh limiter and race for ownership via - // LoadOrStore: if a concurrent caller inserted one first, - // we discard ours and use theirs so that every peer ends - // up with a single authoritative bucket. - newLim := rate.NewLimiter(p.rate, p.burst) - lim, _ = p.peers.LoadOrStore(peer, newLim) - } - - if lim.AllowN(time.Now(), n) { - return true - } - p.dropped.Add(1) - - return false -} - -// Dropped returns the total number of onion messages this registry has -// rejected since process start, summed across all peers. -func (p *PeerRateLimiter) Dropped() uint64 { - return p.dropped.Load() -} - -// IngressLimiter is the combined per-peer + global rate limiter surface -// consumed by the onion message ingress path. It hides the split between -// the two underlying buckets so callers in peer/brontide.go only need to -// thread a single object through Config and call a single method on every -// incoming onion message. The per-peer bucket is always checked first so -// that a hostile peer whose own budget is already empty cannot burn -// global tokens on every rejected attempt and starve legitimate peers. -// -// Implementations must be safe for concurrent use from per-peer -// readHandler goroutines. A nil IngressLimiter is a valid "disabled" -// sentinel at call sites and means "accept everything". -type IngressLimiter interface { - // AllowN reports whether an onion message of n bytes from the - // given peer is permitted. A successful result wraps fn.Unit; a - // rejection wraps either ErrPeerRateLimit or ErrGlobalRateLimit - // depending on which bucket fired. Callers use errors.Is against - // those sentinels to pick their log / metric / drop path. - // - // Per-peer state is retained for the lifetime of the process so - // that a peer cannot reset its bucket by cycling the connection; - // see the PeerRateLimiter doc for the memory-bound argument. - AllowN(peer [33]byte, n int) fn.Result[fn.Unit] - - // FirstPeerDropClaim atomically returns true exactly once, on - // the first call, and is intended to gate a one-shot info log - // when the per-peer limiter first trips. - FirstPeerDropClaim() bool - - // FirstGlobalDropClaim atomically returns true exactly once, on - // the first call, and is intended to gate a one-shot info log - // when the global limiter first trips. - FirstGlobalDropClaim() bool -} - -// ingressLimiter is the stock IngressLimiter implementation that -// composes a PeerRateLimiter with a global RateLimiter. Either side may -// be nil / disabled independently. -type ingressLimiter struct { - peer *PeerRateLimiter - global RateLimiter -} - -// NewIngressLimiter constructs an IngressLimiter that first consults the -// given per-peer limiter and then the given global limiter for each -// incoming onion message. Either argument may be nil (or the zero-value -// disabled limiter returned by the constructors in this package) in -// which case that side of the check is skipped. -func NewIngressLimiter(peer *PeerRateLimiter, - global RateLimiter) IngressLimiter { - - return &ingressLimiter{ - peer: peer, - global: global, - } -} - -// AllowN checks per-peer then global, returning the drop reason as a -// sentinel error wrapped in a fn.Result on rejection. The ordering is -// load-bearing: consulting the per-peer bucket first means over-limit -// traffic from one peer is rejected before it can touch the global -// bucket, so the global bucket only accounts for traffic that was -// within its source peer's allowance and a single hostile peer cannot -// drain the shared budget via rejected attempts. -func (l *ingressLimiter) AllowN(peer [33]byte, - n int) fn.Result[fn.Unit] { - - if l.peer != nil && !l.peer.AllowN(peer, n) { - return fn.Err[fn.Unit](ErrPeerRateLimit) - } - if l.global != nil && !l.global.AllowN(n) { - return fn.Err[fn.Unit](ErrGlobalRateLimit) - } - - return fn.Ok(fn.Unit{}) -} - -// FirstPeerDropClaim delegates to the per-peer limiter's one-shot -// claim. Returns false if the per-peer limiter is nil (disabled). -func (l *ingressLimiter) FirstPeerDropClaim() bool { - if l.peer == nil { - return false - } - - return l.peer.FirstDropClaim() -} - -// FirstGlobalDropClaim atomically returns true exactly once, on the -// first call, when the global limiter is an enabled countingLimiter -// that has just recorded its first rejection. A noop (disabled) global -// limiter, a nil global limiter, and a countingLimiter whose flag has -// already been claimed all return false. The type assertion is inlined -// here because the global limiter is consulted through the RateLimiter -// interface and only the countingLimiter implementation tracks drops. -func (l *ingressLimiter) FirstGlobalDropClaim() bool { - cl, ok := l.global.(*countingLimiter) - if !ok { - return false - } - - return cl.FirstDropClaim() -} diff --git a/onionmessage/ratelimit_test.go b/onionmessage/ratelimit_test.go deleted file mode 100644 index 4f7885c2c..000000000 --- a/onionmessage/ratelimit_test.go +++ /dev/null @@ -1,244 +0,0 @@ -package onionmessage - -import ( - "sync" - "sync/atomic" - "testing" - - "github.com/stretchr/testify/require" -) - -// msgBytes is the byte count used as the per-Allow token charge across the -// tests. It is intentionally close to the spec-max onion message size so -// that burst budgets in tests closely match real-world worst-case behavior. -const msgBytes = 32 * 1024 - -// TestGlobalLimiterDisabled verifies that constructing a global limiter with -// a zero rate or zero burst yields a noop limiter that always allows -// traffic regardless of the requested byte count. -func TestGlobalLimiterDisabled(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - kbps uint64 - burstBytes uint64 - }{ - {"zero kbps", 0, 1024}, - {"zero burst", 1024, 0}, - {"both zero", 0, 0}, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - lim := NewGlobalLimiter(tc.kbps, tc.burstBytes) - for i := 0; i < 1000; i++ { - require.True(t, lim.AllowN(msgBytes)) - } - // Disabled limiters must be noopLimiters, not - // countingLimiters, so the disabled sentinel is - // observable at the type level. - _, isNoop := lim.(noopLimiter) - require.True(t, isNoop) - }) - } -} - -// TestGlobalLimiterBurstExhaustion verifies that the global limiter permits -// exactly the configured burst worth of immediate bytes and rejects -// subsequent calls until the bucket refills. -func TestGlobalLimiterBurstExhaustion(t *testing.T) { - t.Parallel() - - // Burst just large enough for five max-size messages; a very low rate - // ensures the bucket does not refill within the test window so the - // burst boundary is observable. - const burstMessages = 5 - lim := NewGlobalLimiter(1, burstMessages*msgBytes) - - for i := 0; i < burstMessages; i++ { - require.True(t, lim.AllowN(msgBytes), - "burst slot %d should pass", i) - } - require.False(t, lim.AllowN(msgBytes), "post-burst call should drop") - - cl, ok := lim.(*countingLimiter) - require.True(t, ok) - require.Equal(t, uint64(1), cl.Dropped()) -} - -// TestPeerRateLimiterDisabled verifies that a per-peer limiter constructed -// with a zero rate or zero burst permits all traffic and never allocates -// per-peer state. -func TestPeerRateLimiterDisabled(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - kbps uint64 - burstBytes uint64 - }{ - {"zero kbps", 0, 1024}, - {"zero burst", 1024, 0}, - {"both zero", 0, 0}, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - p := NewPeerRateLimiter(tc.kbps, tc.burstBytes) - var peer [33]byte - peer[0] = 0x02 - - for i := 0; i < 1000; i++ { - require.True(t, p.AllowN(peer, msgBytes)) - } - require.Equal(t, uint64(0), p.Dropped()) - // No state should have been recorded for the peer. - require.Equal(t, 0, peerMapLen(p)) - }) - } -} - -// TestPeerRateLimiterIsolation verifies that exhausting one peer's bucket -// does not affect a different peer's allowance. -func TestPeerRateLimiterIsolation(t *testing.T) { - t.Parallel() - - const burstMessages = 3 - p := NewPeerRateLimiter(1, burstMessages*msgBytes) - - var peerA, peerB [33]byte - peerA[0] = 0x02 - peerB[0] = 0x03 - - // Drain peer A's bucket. - for i := 0; i < burstMessages; i++ { - require.True(t, p.AllowN(peerA, msgBytes)) - } - require.False(t, p.AllowN(peerA, msgBytes), - "peer A should be exhausted") - - // Peer B should still have its full burst. - for i := 0; i < burstMessages; i++ { - require.True(t, p.AllowN(peerB, msgBytes), - "peer B slot %d", i) - } - require.False(t, p.AllowN(peerB, msgBytes)) - - require.Equal(t, uint64(2), p.Dropped()) -} - -// TestCountingLimiterFirstDropClaimOnce verifies that FirstDropClaim on a -// countingLimiter returns true exactly once and false on every subsequent -// call, across concurrent goroutines, so that the first-drop info log is -// emitted at most once. -func TestCountingLimiterFirstDropClaimOnce(t *testing.T) { - t.Parallel() - - // A tiny bucket so that repeated AllowN calls quickly produce drops. - lim, ok := NewGlobalLimiter(1, msgBytes).(*countingLimiter) - require.True(t, ok) - - // Drain the bucket. - require.True(t, lim.AllowN(msgBytes)) - require.False(t, lim.AllowN(msgBytes)) - - const workers = 32 - var wins atomic.Uint64 - var wg sync.WaitGroup - for i := 0; i < workers; i++ { - wg.Add(1) - go func() { - defer wg.Done() - if lim.FirstDropClaim() { - wins.Add(1) - } - }() - } - wg.Wait() - require.Equal(t, uint64(1), wins.Load(), - "FirstDropClaim must be winnable exactly once") - - // A subsequent serial call must also return false. - require.False(t, lim.FirstDropClaim()) -} - -// TestPeerRateLimiterFirstDropClaimOnce verifies the same single-win -// guarantee for the per-peer limiter's FirstDropClaim. -func TestPeerRateLimiterFirstDropClaimOnce(t *testing.T) { - t.Parallel() - - p := NewPeerRateLimiter(1, msgBytes) - - const workers = 32 - var wins atomic.Uint64 - var wg sync.WaitGroup - for i := 0; i < workers; i++ { - wg.Add(1) - go func() { - defer wg.Done() - if p.FirstDropClaim() { - wins.Add(1) - } - }() - } - wg.Wait() - require.Equal(t, uint64(1), wins.Load()) - require.False(t, p.FirstDropClaim()) -} - -// TestFirstGlobalDropClaimNoopLimiter verifies that -// IngressLimiter.FirstGlobalDropClaim returns false when the composed -// global limiter is a noop (disabled) limiter: a disabled limiter never -// produces drops and must not claim the first-drop flag. -func TestFirstGlobalDropClaimNoopLimiter(t *testing.T) { - t.Parallel() - - ingress := NewIngressLimiter(nil, NewGlobalLimiter(0, 0)) - require.False(t, ingress.FirstGlobalDropClaim()) -} - -// TestPeerRateLimiterConcurrentAllowN exercises concurrent AllowN calls -// across many distinct peers to give the race detector an opportunity to -// observe any missing synchronization around the per-peer registry's -// Load / LoadOrStore path. With the registry now retained for the -// process lifetime, the final entry count should equal the number of -// distinct peers exactly. -func TestPeerRateLimiterConcurrentAllowN(t *testing.T) { - t.Parallel() - - p := NewPeerRateLimiter(100_000, 8*msgBytes) - - const workers = 8 - const iters = 200 - - var wg sync.WaitGroup - var ops atomic.Uint64 - for w := 0; w < workers; w++ { - wg.Add(1) - go func() { - defer wg.Done() - var key [33]byte - key[0] = byte(w) - for i := 0; i < iters; i++ { - p.AllowN(key, msgBytes) - ops.Add(1) - } - }() - } - wg.Wait() - - require.Equal(t, uint64(workers*iters), ops.Load()) - // Each worker uses a distinct peer key and entries are never - // removed, so the registry must contain exactly one entry per - // worker. - require.Equal(t, workers, peerMapLen(p)) -} - -// peerMapLen returns the number of entries in the per-peer registry. It -// exists solely for tests; production code has no need for the registry -// size since each per-peer bucket's own AllowN call already tracks the -// accounting it cares about. -func peerMapLen(p *PeerRateLimiter) int { - return p.peers.Len() -} diff --git a/onionmessage/resolver.go b/onionmessage/resolver.go deleted file mode 100644 index 278f2a699..000000000 --- a/onionmessage/resolver.go +++ /dev/null @@ -1,121 +0,0 @@ -package onionmessage - -import ( - "context" - "encoding/hex" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightninglabs/neutrino/cache/lru" - graphdb "github.com/lightningnetwork/lnd/graph/db" - "github.com/lightningnetwork/lnd/lnwire" -) - -const ( - // defaultSCIDCacheSize is the default number of SCID to pubkey mappings - // to cache. This is relatively small since onion message forwarding via - // SCID is expected to be infrequent compared to forwarding via explicit - // node ID. - defaultSCIDCacheSize = 1000 -) - -// cachedPubKey is a wrapper around a compressed public key that implements the -// cache.Value interface required by the LRU cache. -type cachedPubKey struct { - pubKeyBytes [33]byte -} - -// Size returns the "size" of an entry. We return 1 as we just want to limit -// the total number of entries rather than do accurate size accounting. -func (c *cachedPubKey) Size() (uint64, error) { - return 1, nil -} - -// GraphNodeResolver resolves node public keys from short channel IDs using the -// channel graph. It maintains an LRU cache to avoid repeated database lookups -// for frequently used SCIDs. -type GraphNodeResolver struct { - graph *graphdb.ChannelGraph - ourPub *btcec.PublicKey - - // scidCache is an LRU cache mapping SCID (as uint64) to the remote - // node's compressed public key bytes. - scidCache *lru.Cache[uint64, *cachedPubKey] -} - -// NewGraphNodeResolver creates a new GraphNodeResolver with the given channel -// graph and our node's public key. It initializes an LRU cache for SCID -// lookups. -func NewGraphNodeResolver(graph *graphdb.ChannelGraph, - ourPub *btcec.PublicKey) *GraphNodeResolver { - - return &GraphNodeResolver{ - graph: graph, - ourPub: ourPub, - scidCache: lru.NewCache[uint64, *cachedPubKey]( - defaultSCIDCacheSize, - ), - } -} - -// RemotePubFromSCID resolves a node public key from a short channel ID. -func (r *GraphNodeResolver) RemotePubFromSCID(ctx context.Context, - scid lnwire.ShortChannelID) (*btcec.PublicKey, error) { - - scidInt := scid.ToUint64() - - // Check the cache first. - if cached, err := r.scidCache.Get(scidInt); err == nil { - pubKey, parseErr := btcec.ParsePubKey(cached.pubKeyBytes[:]) - if parseErr == nil { - log.Tracef("Resolved SCID %v from cache to node %s", - scid, - hex.EncodeToString(cached.pubKeyBytes[:])) - - return pubKey, nil - } - - // Cache contained invalid data, fall through to DB lookup. - log.Debugf("Invalid cached pubkey for SCID %v: %v", - scid, parseErr) - } - - log.Tracef("Resolving node public key for SCID %v from graph", scid) - - edge, _, _, err := r.graph.FetchChannelEdgesByID(ctx, scid.ToUint64()) - if err != nil { - log.Debugf("Failed to fetch channel edges for SCID %v: %v", - scid, err) - - return nil, err - } - - otherNodeKeyBytes, err := edge.OtherNodeKeyBytes( - r.ourPub.SerializeCompressed(), - ) - if err != nil { - log.Debugf("Failed to get other node key for SCID %v: %v", - scid, err) - - return nil, err - } - - pubKey, err := btcec.ParsePubKey(otherNodeKeyBytes[:]) - if err != nil { - log.Debugf("Failed to parse public key for SCID %v: %v", - scid, err) - - return nil, err - } - - // Cache the result for future lookups. We ignore the return values as - // caching is best-effort and a failure just means the next lookup will - // hit the database again. - _, _ = r.scidCache.Put(scidInt, &cachedPubKey{ - pubKeyBytes: otherNodeKeyBytes, - }) - - log.Tracef("Resolved SCID %v to node %s", scid, - hex.EncodeToString(pubKey.SerializeCompressed())) - - return pubKey, nil -} diff --git a/onionmessage/resolver_test.go b/onionmessage/resolver_test.go deleted file mode 100644 index e8af8d87a..000000000 --- a/onionmessage/resolver_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package onionmessage - -import ( - "testing" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/stretchr/testify/require" -) - -func TestMockNodeIDResolverRemotePubFromSCID(t *testing.T) { - t.Parallel() - - t.Run("success", func(t *testing.T) { - t.Parallel() - - resolver := newMockNodeIDResolver() - priv, err := btcec.NewPrivateKey() - require.NoError(t, err) - pubKey := priv.PubKey() - - scid := lnwire.NewShortChanIDFromInt(1) - resolver.addPeer(scid, pubKey) - - got, err := resolver.RemotePubFromSCID(t.Context(), scid) - require.NoError(t, err) - require.Equal(t, pubKey, got) - }) - - t.Run("unknown scid", func(t *testing.T) { - t.Parallel() - - resolver := newMockNodeIDResolver() - scid := lnwire.NewShortChanIDFromInt(2) - - got, err := resolver.RemotePubFromSCID(t.Context(), scid) - require.Error(t, err) - require.Nil(t, got) - }) -} diff --git a/onionmessage/test_utils.go b/onionmessage/test_utils.go deleted file mode 100644 index 16c0aa417..000000000 --- a/onionmessage/test_utils.go +++ /dev/null @@ -1,249 +0,0 @@ -package onionmessage - -import ( - "bytes" - "context" - "fmt" - "testing" - - "github.com/btcsuite/btcd/btcec/v2" - sphinx "github.com/lightningnetwork/lightning-onion" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/record" - "github.com/lightningnetwork/lnd/routing/route" - "github.com/lightningnetwork/lnd/tlv" - "github.com/stretchr/testify/require" -) - -// mockNodeIDResolver implements NodeIDResolver for tests. -type mockNodeIDResolver struct { - peers map[lnwire.ShortChannelID]*btcec.PublicKey -} - -// addPeer registers a single SCID to pubkey mapping for tests. -func (m *mockNodeIDResolver) addPeer(scid lnwire.ShortChannelID, - pubKey *btcec.PublicKey) { - - m.peers[scid] = pubKey -} - -// newMockNodeIDResolver creates a new instance of mockNodeIDResolver. -func newMockNodeIDResolver() *mockNodeIDResolver { - return &mockNodeIDResolver{ - peers: make(map[lnwire.ShortChannelID]*btcec.PublicKey), - } -} - -// RemotePubFromSCID resolves a node public key from a short channel ID. -func (m *mockNodeIDResolver) RemotePubFromSCID(_ context.Context, - scid lnwire.ShortChannelID) (*btcec.PublicKey, error) { - - if pk, ok := m.peers[scid]; ok { - return pk, nil - } - - return nil, fmt.Errorf("unknown scid: %v", scid) -} - -// EncodeBlindedRouteData encodes BlindedRouteData to bytes for use in test -// hop payloads. -func EncodeBlindedRouteData(t *testing.T, - data *record.BlindedRouteData) []byte { - - t.Helper() - - buf, err := record.EncodeBlindedRouteData(data) - require.NoError(t, err) - - return buf -} - -// BuildBlindedPath creates a BlindedPathInfo from a list of HopInfo. This is a -// test helper that wraps sphinx.BuildBlindedPath with a fresh session key. -func BuildBlindedPath(t *testing.T, - hops []*sphinx.HopInfo) *sphinx.BlindedPathInfo { - - t.Helper() - - sessionKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - blindedPath, err := sphinx.BuildBlindedPath(sessionKey, hops) - require.NoError(t, err) - - return blindedPath -} - -// ConcatBlindedPaths concatenates two blinded paths. The sender's path points -// TO the introduction node (with NextBlindingOverride), and the receiver's -// path starts AT the introduction node. The concatenated path includes all -// hops from both paths - the sender's last hop instructs forwarding to the -// intro node, and all receiver hops follow. -func ConcatBlindedPaths(t *testing.T, senderPath, - receiverPath *sphinx.BlindedPathInfo) *sphinx.BlindedPathInfo { - - t.Helper() - - // The resulting path uses the sender's session key and introduction - // point but concatenates all blinded hops. - concatenated := &sphinx.BlindedPath{ - IntroductionPoint: senderPath.Path.IntroductionPoint, - BlindingPoint: senderPath.Path.BlindingPoint, - BlindedHops: append( - senderPath.Path.BlindedHops, - receiverPath.Path.BlindedHops..., - ), - } - - return &sphinx.BlindedPathInfo{ - Path: concatenated, - SessionKey: senderPath.SessionKey, - LastEphemeralKey: receiverPath.LastEphemeralKey, - } -} - -// BuildOnionMessage builds an onion message from a BlindedPathInfo and returns -// the message along with the ciphertexts for each blinded hop (in hop order). -// If finalPayloads is nil or empty, no final hop payload data is included. -func BuildOnionMessage(t *testing.T, blindedPath *sphinx.BlindedPathInfo, - finalHopTLVs []*lnwire.FinalHopTLV) (*lnwire.OnionMessage, - [][]byte) { - - t.Helper() - - // Convert the blinded path to a sphinx path and add final payloads. - sphinxPath, err := route.OnionMessageBlindedPathToSphinxPath( - blindedPath.Path, nil, finalHopTLVs, - ) - require.NoError(t, err) - - onionSessionKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - // Create an onion packet with no associated data. - onionPkt, err := sphinx.NewOnionPacket( - sphinxPath, onionSessionKey, nil, - sphinx.DeterministicPacketFiller, - sphinx.WithMaxPayloadSize(sphinx.MaxRoutingPayloadSize), - ) - require.NoError(t, err) - - // Encode the onion message packet. - var buf bytes.Buffer - require.NoError(t, onionPkt.Encode(&buf)) - - onionMsg := &lnwire.OnionMessage{ - PathKey: blindedPath.SessionKey.PubKey(), - OnionBlob: buf.Bytes(), - } - - var ctexts [][]byte - for _, bh := range blindedPath.Path.BlindedHops { - ctexts = append(ctexts, bh.CipherText) - } - - return onionMsg, ctexts -} - -// PeeledHop captures decrypted state for a single hop when peeling an onion. -type PeeledHop struct { - EncryptedData []byte - Payload *lnwire.OnionMessagePayload - IsFinal bool -} - -// PeelOnionLayers sequentially processes an onion message, creating a fresh -// router for each hop using the provided private keys (one per hop), returning -// the encrypted data and decoded payload for each hop until the final hop. -func PeelOnionLayers(t *testing.T, privKeys []*btcec.PrivateKey, - msg *lnwire.OnionMessage) []PeeledHop { - - t.Helper() - - var onionPkt sphinx.OnionPacket - require.NoError(t, onionPkt.Decode(bytes.NewReader(msg.OnionBlob))) - - currentPathKey := msg.PathKey - var hops []PeeledHop - - for i := 0; ; i++ { - require.Less(t, i, len(privKeys), "more hops than privKeys") - - router := sphinx.NewRouter( - &sphinx.PrivKeyECDH{PrivKey: privKeys[i]}, - sphinx.NewNoOpReplayLog(), - ) - require.NoError(t, router.Start()) - - processedPkt, err := router.ProcessOnionPacket( - &onionPkt, nil, 10, - sphinx.WithBlindingPoint(currentPathKey), - ) - require.NoError(t, err) - - payload := lnwire.NewOnionMessagePayload() - _, err = payload.Decode( - bytes.NewReader(processedPkt.Payload.Payload), - ) - require.NoError(t, err) - - origPayload := *payload - origPayload.EncryptedData = bytes.Clone(payload.EncryptedData) - - isFinal := processedPkt.Action == sphinx.ExitNode - hops = append(hops, PeeledHop{ - EncryptedData: origPayload.EncryptedData, - Payload: &origPayload, - IsFinal: isFinal, - }) - - if isFinal { - router.Stop() - break - } - - decrypted, err := router.DecryptBlindedHopData( - currentPathKey, payload.EncryptedData, - ) - require.NoError(t, err) - - routeData, err := record.DecodeBlindedRouteData( - bytes.NewReader(decrypted), - ) - require.NoError(t, err) - - nextPathKey := deriveNextPathKeyForTest( - router, currentPathKey, routeData.NextBlindingOverride, - ) - require.NotNil(t, nextPathKey) - - router.Stop() - - onionPkt = *processedPkt.NextPacket - currentPathKey = nextPathKey - } - - return hops -} - -// deriveNextPathKeyForTest derives the next path key using the router and -// current path key. If an override is provided, it is used instead. -func deriveNextPathKeyForTest(router *sphinx.Router, - currentPathKey *btcec.PublicKey, - override tlv.OptionalRecordT[tlv.TlvType8, - *btcec.PublicKey]) *btcec.PublicKey { - - // If an override is provided, use it. - return override.UnwrapOrFunc(func() tlv.RecordT[tlv.TlvType8, - *btcec.PublicKey] { - - // Otherwise, derive the next path key using the router. - nextKey, err := router.NextEphemeral(currentPathKey) - if err != nil { - // If the derivation fails, return a zero key. - return override.Zero() - } - - return tlv.NewPrimitiveRecord[tlv.TlvType8](nextKey) - }).Val -} diff --git a/payments/db/errors.go b/payments/db/errors.go index 3b4e25bd9..40e37d95c 100644 --- a/payments/db/errors.go +++ b/payments/db/errors.go @@ -78,18 +78,6 @@ var ( ErrBlindedPaymentTotalAmountMismatch = errors.New("blinded path " + "total amount mismatch") - // ErrMixedBlindedAndNonBlindedPayments is returned if we try to - // register a non-blinded attempt to a payment which uses a blinded - // paths or vice versa. - ErrMixedBlindedAndNonBlindedPayments = errors.New("mixed blinded and " + - "non-blinded payments") - - // ErrBlindedPaymentMissingTotalAmount is returned if we try to - // register a blinded payment attempt where the final hop doesn't set - // the total amount. - ErrBlindedPaymentMissingTotalAmount = errors.New("blinded payment " + - "final hop must set total amount") - // ErrMPPPaymentAddrMismatch is returned if we try to register an MPP // shard where the payment address doesn't match existing shards. ErrMPPPaymentAddrMismatch = errors.New("payment address mismatch") @@ -136,19 +124,10 @@ var ( ErrNoDuplicateNestedBucket = errors.New("nested duplicate bucket not " + "found") - // ErrNoDuplicateSequenceNumber is returned when a duplicate payment - // sub-bucket does not contain the sequence number key. - ErrNoDuplicateSequenceNumber = errors.New("duplicate payment " + - "sequence number not found") - // ErrNoSequenceNrIndex is returned when an attempt to lookup a payment // index is made for a sequence number that is not indexed. // // NOTE: Only used for the kv backend. ErrNoSequenceNrIndex = errors.New("payment sequence number index " + "does not exist") - - // errMaxPaymentsReached is used internally to signal that the maximum - // number of payments has been reached during a paginated query. - errMaxPaymentsReached = errors.New("max payments reached") ) diff --git a/payments/db/fetch_inflight_benchmark_postgres_test.go b/payments/db/fetch_inflight_benchmark_postgres_test.go deleted file mode 100644 index 9e6f9e967..000000000 --- a/payments/db/fetch_inflight_benchmark_postgres_test.go +++ /dev/null @@ -1,60 +0,0 @@ -//go:build test_db_postgres && !test_db_sqlite - -package paymentsdb - -import ( - "database/sql" - "testing" - - "github.com/lightningnetwork/lnd/sqldb" -) - -// postgresFetchInFlightBenchBackend creates Postgres-backed benchmark stores. -type postgresFetchInFlightBenchBackend struct { - fixture *sqldb.TestPgFixture -} - -// newFetchInFlightBenchBackend creates the benchmark backend selected by the -// active build tags. -func newFetchInFlightBenchBackend(b *testing.B) fetchInFlightBenchBackend { - b.Helper() - - fixture := sqldb.NewTestPgFixture( - b, sqldb.DefaultPostgresFixtureLifetime, - ) - b.Cleanup(func() { - fixture.TearDown(b) - }) - - return postgresFetchInFlightBenchBackend{fixture: fixture} -} - -// newStore creates a migrated Postgres SQLStore and seeds it with one benchmark -// scenario. -func (backend postgresFetchInFlightBenchBackend) newStore(b *testing.B, - scenario fetchInFlightBenchScenario, - totalPayments int) (*SQLStore, fetchInFlightBenchSeedStats) { - - b.Helper() - - pgDB := sqldb.NewTestPostgresDB(b, backend.fixture) - baseDB := pgDB.BaseDB - - withTx := func(tx *sql.Tx) SQLQueries { - return baseDB.WithTx(tx) - } - queries := sqldb.NewTransactionExecutor(baseDB, withTx) - - store, err := NewSQLStore(&SQLStoreConfig{ - QueryCfg: sqldb.DefaultPostgresConfig(), - }, queries) - if err != nil { - b.Fatalf("new SQL store: %v", err) - } - - stats := seedFetchInFlightBenchDB( - b, baseDB.DB, withTx, scenario, totalPayments, - ) - - return store, stats -} diff --git a/payments/db/fetch_inflight_benchmark_sqlite_test.go b/payments/db/fetch_inflight_benchmark_sqlite_test.go deleted file mode 100644 index b9238381c..000000000 --- a/payments/db/fetch_inflight_benchmark_sqlite_test.go +++ /dev/null @@ -1,49 +0,0 @@ -//go:build test_db_sqlite && !test_db_postgres - -package paymentsdb - -import ( - "database/sql" - "testing" - - "github.com/lightningnetwork/lnd/sqldb" -) - -// sqliteFetchInFlightBenchBackend creates SQLite-backed benchmark stores. -type sqliteFetchInFlightBenchBackend struct{} - -// newFetchInFlightBenchBackend creates the benchmark backend selected by the -// active build tags. -func newFetchInFlightBenchBackend(_ *testing.B) fetchInFlightBenchBackend { - return sqliteFetchInFlightBenchBackend{} -} - -// newStore creates a migrated SQLite SQLStore and seeds it with one benchmark -// scenario. -func (sqliteFetchInFlightBenchBackend) newStore(b *testing.B, - scenario fetchInFlightBenchScenario, - totalPayments int) (*SQLStore, fetchInFlightBenchSeedStats) { - - b.Helper() - - sqliteDB := sqldb.NewTestSqliteDB(b) - baseDB := sqliteDB.BaseDB - - withTx := func(tx *sql.Tx) SQLQueries { - return baseDB.WithTx(tx) - } - queries := sqldb.NewTransactionExecutor(baseDB, withTx) - - store, err := NewSQLStore(&SQLStoreConfig{ - QueryCfg: sqldb.DefaultSQLiteConfig(), - }, queries) - if err != nil { - b.Fatalf("new SQL store: %v", err) - } - - stats := seedFetchInFlightBenchDB( - b, baseDB.DB, withTx, scenario, totalPayments, - ) - - return store, stats -} diff --git a/payments/db/fetch_inflight_benchmark_test.go b/payments/db/fetch_inflight_benchmark_test.go deleted file mode 100644 index c408f8510..000000000 --- a/payments/db/fetch_inflight_benchmark_test.go +++ /dev/null @@ -1,501 +0,0 @@ -//go:build (test_db_sqlite && !test_db_postgres) || (test_db_postgres && !test_db_sqlite) - -package paymentsdb - -import ( - "database/sql" - "encoding/binary" - "fmt" - "os" - "strconv" - "strings" - "testing" - "time" - - "github.com/lightningnetwork/lnd/sqldb/sqlc" -) - -// fetchInFlightBenchScenario identifies one payment-history shape benchmarked -// by BenchmarkFetchInFlightPayments. -type fetchInFlightBenchScenario string - -// fetchInFlightBenchPaymentState is the seeded lifecycle state of a synthetic -// payment row. -type fetchInFlightBenchPaymentState uint8 - -// fetchInFlightBenchCase describes one benchmark scenario and its seeded data -// distribution. -type fetchInFlightBenchCase struct { - scenario fetchInFlightBenchScenario - description string -} - -// fetchInFlightBenchSeedStats describes the concrete row counts seeded for a -// benchmark scenario. -type fetchInFlightBenchSeedStats struct { - retryable int - unresolved int - terminalFailed int - terminalSettled int - attempts int - expected int -} - -// fetchInFlightBenchBackend creates a SQLStore for one benchmark backend. -type fetchInFlightBenchBackend interface { - newStore(*testing.B, fetchInFlightBenchScenario, - int) (*SQLStore, fetchInFlightBenchSeedStats) -} - -// Untyped scenario constants. Functions accept fetchInFlightBenchScenario; Go -// performs the implicit conversion at the call/case site, so we keep the type -// discipline on the function signatures while letting the const block fit -// within the 80-column line limit. -const ( - benchScenarioAllNonTerminal = "all-non-terminal" - benchScenarioTerminalFailed = "terminal-failed" - benchScenarioTerminalSettled = "terminal-settled" - benchScenarioNonTerminalEarly20Pct = "non-terminal-early-20pct" - benchScenarioNonTerminalLate20Pct = "non-terminal-late-20pct" - benchScenarioMixedShuffled = "mixed-shuffled-10f-10n-80s" -) - -const ( - benchPaymentStateRetryable fetchInFlightBenchPaymentState = iota - benchPaymentStateUnresolved - benchPaymentStateTerminalFailed - benchPaymentStateTerminalSettled -) - -// Per-scenario setup descriptions, kept at package level so the prose fits -// within the line-length limit without needing extra wrapping inside the -// nested struct literal returned by fetchInFlightBenchCases. -const ( - benchDescAllNonTerminal = "every payment is non-terminal. " + - "The rows alternate between retryable failed-only " + - "payments, which exercise the fail_reason=NULL/no " + - "settled-attempt predicate, and active payments with " + - "one settled attempt plus one unresolved attempt, " + - "which exercise the unresolved-attempt predicate." - - benchDescTerminalFailed = "every payment has " + - "fail_reason=FailureReasonNoRoute and one failed HTLC " + - "attempt resolution. This is a terminal failed history: " + - "no payments should be returned. It verifies the " + - "payment-driven query does not catastrophically regress " + - "when both non-terminal predicate branches are empty; " + - "small per-row overhead is expected." - - benchDescTerminalSettled = "every payment has fail_reason=NULL " + - "and one settled HTLC attempt resolution with a preimage. " + - "This is a terminal settled history: no payments should " + - "be returned." - - benchDescNonTerminalEarly20Pct = "the first 20 percent of " + - "payment ids are non-terminal payments split between " + - "retryable failed-only rows and active unresolved-attempt " + - "rows. The remaining 80 percent are settled terminal " + - "payments. This measures finding non-terminal payments " + - "early in the payments table." - - benchDescNonTerminalLate20Pct = "the first 80 percent of " + - "payment ids are settled terminal payments, and the final " + - "20 percent are non-terminal payments split between " + - "retryable failed-only rows and active unresolved-attempt " + - "rows. This measures the likely startup case where recent " + - "payments near the end of the table still need resumption." - - benchDescMixedShuffled = "payments are deterministically " + - "shuffled so 10 percent are terminal failed, 10 percent " + - "are non-terminal split between retryable failed-only " + - "rows and active unresolved-attempt rows, and 80 percent " + - "are terminal settled. This is the more realistic " + - "mixed-history case." -) - -// BenchmarkFetchInFlightPayments exercises SQLStore.FetchInFlightPayments -// end to end against the selected SQL backend. The setup inserts directly -// into the SQL tables so larger synthetic histories can be created without -// benchmarking payment lifecycle API setup cost. -// -// The default size is intentionally modest. For larger manual runs, set: -// -// PAYMENT_BENCH_SIZE=250000 -// -// Note that scenarios returning many payments include full MPPayment -// reconstruction and allocation costs, not just the SQL selector cost. -func BenchmarkFetchInFlightPayments(b *testing.B) { - b.ReportAllocs() - - backend := newFetchInFlightBenchBackend(b) - sizes := fetchInFlightBenchSizes(b) - benchCases := fetchInFlightBenchCases() - - for _, size := range sizes { - for _, benchCase := range benchCases { - name := fmt.Sprintf( - "%s/%d", benchCase.scenario, size, - ) - b.Run(name, func(b *testing.B) { - runFetchInFlightBench( - b, backend, benchCase, size, - ) - }) - } - } -} - -// runFetchInFlightBench seeds the configured scenario and exercises -// FetchInFlightPayments for the active sub-benchmark. -func runFetchInFlightBench(b *testing.B, - backend fetchInFlightBenchBackend, - benchCase fetchInFlightBenchCase, size int) { - - b.ReportAllocs() - - b.Logf("setup: %s", benchCase.description) - - store, stats := backend.newStore(b, benchCase.scenario, size) - b.Logf("seeded: %s", stats) - - ctx := b.Context() - - for b.Loop() { - payments, err := store.FetchInFlightPayments(ctx) - if err != nil { - b.Fatalf("fetch in-flight: %v", err) - } - - if len(payments) != stats.expected { - b.Fatalf("expected %d payments, got %d", - stats.expected, len(payments)) - } - } -} - -// String returns a compact human-readable summary of the seeded benchmark row -// counts. -func (s fetchInFlightBenchSeedStats) String() string { - return fmt.Sprintf( - "%d retryable, %d unresolved, %d terminal failed, "+ - "%d terminal settled, %d attempts total, "+ - "%d payments expected", - s.retryable, s.unresolved, s.terminalFailed, - s.terminalSettled, s.attempts, s.expected, - ) -} - -// fetchInFlightBenchCases returns the benchmark scenarios with explicit setup -// descriptions for each synthetic payment-history distribution. -func fetchInFlightBenchCases() []fetchInFlightBenchCase { - return []fetchInFlightBenchCase{ - { - scenario: benchScenarioAllNonTerminal, - description: benchDescAllNonTerminal, - }, - { - scenario: benchScenarioTerminalFailed, - description: benchDescTerminalFailed, - }, - { - scenario: benchScenarioTerminalSettled, - description: benchDescTerminalSettled, - }, - { - scenario: benchScenarioNonTerminalEarly20Pct, - description: benchDescNonTerminalEarly20Pct, - }, - { - scenario: benchScenarioNonTerminalLate20Pct, - description: benchDescNonTerminalLate20Pct, - }, - { - scenario: benchScenarioMixedShuffled, - description: benchDescMixedShuffled, - }, - } -} - -// fetchInFlightBenchSizes returns the configured payment counts for each -// benchmark scenario. -func fetchInFlightBenchSizes(b *testing.B) []int { - b.Helper() - - sizeEnv := strings.TrimSpace(os.Getenv("PAYMENT_BENCH_SIZE")) - if sizeEnv == "" { - return []int{1_000} - } - - parts := strings.Split(sizeEnv, ",") - sizes := make([]int, 0, len(parts)) - for _, part := range parts { - part = strings.TrimSpace(part) - if part == "" { - continue - } - - size, err := strconv.Atoi(part) - if err != nil || size <= 0 { - b.Fatalf("invalid PAYMENT_BENCH_SIZE %q", part) - } - - sizes = append(sizes, size) - } - - if len(sizes) == 0 { - b.Fatalf("PAYMENT_BENCH_SIZE did not contain any sizes") - } - - return sizes -} - -// seedFetchInFlightBenchDB inserts synthetic payments, HTLC attempts, -// resolutions, and route hops using the generated SQL queries. -func seedFetchInFlightBenchDB(b *testing.B, db *sql.DB, - withTx func(*sql.Tx) SQLQueries, scenario fetchInFlightBenchScenario, - totalPayments int) fetchInFlightBenchSeedStats { - - b.Helper() - - createdAt := time.Unix(1_700_000_000, 0).UTC() - pubKey := vertex[:] - ctx := b.Context() - - tx, err := db.BeginTx(ctx, nil) - if err != nil { - b.Fatalf("begin seed tx: %v", err) - } - defer func() { - _ = tx.Rollback() - }() - - queries := withTx(tx) - var stats fetchInFlightBenchSeedStats - - insertAttemptAndHop := func(paymentID, attemptIndex int, - paymentHash []byte) { - - attemptIndex64 := int64(attemptIndex) - _, err := queries.InsertHtlcAttempt( - ctx, sqlc.InsertHtlcAttemptParams{ - PaymentID: int64(paymentID), - AttemptIndex: attemptIndex64, - SessionKey: benchIDBytes(totalPayments + attemptIndex), - AttemptTime: createdAt, - PaymentHash: paymentHash, - FirstHopAmountMsat: int64(1000), - RouteTotalTimeLock: int32(40), - RouteTotalAmount: int64(1000), - RouteSourceKey: pubKey, - }, - ) - if err != nil { - b.Fatalf("insert attempt %d: %v", attemptIndex, err) - } - - _, err = queries.InsertRouteHop(ctx, sqlc.InsertRouteHopParams{ - HtlcAttemptIndex: attemptIndex64, - HopIndex: int32(0), - PubKey: pubKey, - Scid: strconv.Itoa(attemptIndex), - OutgoingTimeLock: int32(40), - AmtToForward: int64(1000), - }) - if err != nil { - b.Fatalf("insert hop %d: %v", attemptIndex, err) - } - - stats.attempts++ - } - - settleAttempt := func(attemptIndex int, settlePreimage []byte) { - attemptIndex64 := int64(attemptIndex) - err := queries.SettleAttempt(ctx, sqlc.SettleAttemptParams{ - AttemptIndex: attemptIndex64, - ResolutionTime: createdAt, - ResolutionType: int32(HTLCAttemptResolutionSettled), - SettlePreimage: settlePreimage, - }) - if err != nil { - b.Fatalf("settle attempt %d: %v", attemptIndex, err) - } - } - - failAttempt := func(attemptIndex int) { - attemptIndex64 := int64(attemptIndex) - err := queries.FailAttempt(ctx, sqlc.FailAttemptParams{ - AttemptIndex: attemptIndex64, - ResolutionTime: createdAt, - ResolutionType: int32(HTLCAttemptResolutionFailed), - }) - if err != nil { - b.Fatalf("fail attempt %d: %v", attemptIndex, err) - } - } - - for id := 1; id <= totalPayments; id++ { - paymentState := benchPaymentState(scenario, id, totalPayments) - identifier := benchIDBytes(id) - paymentID, err := queries.InsertPayment( - ctx, sqlc.InsertPaymentParams{ - AmountMsat: int64(2000), - CreatedAt: createdAt, - PaymentIdentifier: identifier, - }, - ) - if err != nil { - b.Fatalf("insert payment %d: %v", id, err) - } - - insertAttemptAndHop(int(paymentID), id, identifier) - - switch paymentState { - case benchPaymentStateRetryable: - stats.retryable++ - stats.expected++ - failAttempt(id) - - case benchPaymentStateTerminalFailed: - stats.terminalFailed++ - _, err := queries.FailPayment(ctx, sqlc.FailPaymentParams{ - FailReason: sql.NullInt32{ - Valid: true, - Int32: int32(FailureReasonNoRoute), - }, - PaymentIdentifier: identifier, - }) - if err != nil { - b.Fatalf("fail payment %d: %v", id, err) - } - failAttempt(id) - - case benchPaymentStateUnresolved: - stats.unresolved++ - stats.expected++ - settleAttempt(id, benchIDBytes(2*totalPayments+id)) - - insertAttemptAndHop( - int(paymentID), totalPayments+id, identifier, - ) - - case benchPaymentStateTerminalSettled: - stats.terminalSettled++ - settleAttempt(id, benchIDBytes(2*totalPayments+id)) - } - } - - if err := tx.Commit(); err != nil { - b.Fatalf("commit seed tx: %v", err) - } - - return stats -} - -// benchPaymentState returns the seeded lifecycle state for a payment in the -// given benchmark scenario. -func benchPaymentState(scenario fetchInFlightBenchScenario, - id, totalPayments int) fetchInFlightBenchPaymentState { - - switch scenario { - case benchScenarioAllNonTerminal: - return benchNonTerminalPaymentState(id) - - case benchScenarioTerminalFailed: - return benchPaymentStateTerminalFailed - - case benchScenarioTerminalSettled: - return benchPaymentStateTerminalSettled - - case benchScenarioNonTerminalEarly20Pct: - if id <= totalPayments/5 { - return benchNonTerminalPaymentState(id) - } - - return benchPaymentStateTerminalSettled - - case benchScenarioNonTerminalLate20Pct: - if id > totalPayments-totalPayments/5 { - return benchNonTerminalPaymentState(id) - } - - return benchPaymentStateTerminalSettled - - case benchScenarioMixedShuffled: - return benchMixedShuffledPaymentState(id, totalPayments) - - default: - return benchPaymentStateTerminalSettled - } -} - -// benchMixedShuffledPaymentState returns a deterministic mixed distribution of -// failed, non-terminal, and settled payments. -func benchMixedShuffledPaymentState(id, - totalPayments int) fetchInFlightBenchPaymentState { - - failedPayments := totalPayments / 10 - nonTerminalPayments := totalPayments / 10 - rank := benchPermutedRank(id, totalPayments) - - if rank < failedPayments { - return benchPaymentStateTerminalFailed - } - - if rank < failedPayments+nonTerminalPayments { - return benchNonTerminalPaymentState(rank - failedPayments) - } - - return benchPaymentStateTerminalSettled -} - -// benchNonTerminalPaymentState alternates non-terminal rows between the two SQL -// predicate branches that FetchNonTerminalPayments must include. -func benchNonTerminalPaymentState(index int) fetchInFlightBenchPaymentState { - if index%2 == 0 { - return benchPaymentStateRetryable - } - - return benchPaymentStateUnresolved -} - -// benchPermutedRank maps a payment id to a deterministic pseudo-random rank in -// the benchmark table. -func benchPermutedRank(id, totalPayments int) int { - return ((id - 1) * benchCoprimeStep(totalPayments)) % totalPayments -} - -// benchCoprimeStep returns a deterministic step that is coprime with the total -// payment count so benchPermutedRank visits each rank exactly once. -func benchCoprimeStep(totalPayments int) int { - for _, step := range []int{ - 7919, 5807, 4093, 2053, 1021, 521, 251, 127, 61, 31, 17, - 7, 5, 3, - } { - if benchGCD(step, totalPayments) == 1 { - return step - } - } - - return 1 -} - -// benchGCD returns the greatest common divisor of two integers. -func benchGCD(a, b int) int { - for b != 0 { - a, b = b, a%b - } - - if a < 0 { - return -a - } - - return a -} - -// benchIDBytes returns a stable 32-byte identifier for a seeded payment value. -func benchIDBytes(id int) []byte { - var value [32]byte - binary.BigEndian.PutUint64(value[24:], uint64(id)) - - return value[:] -} diff --git a/payments/db/interface.go b/payments/db/interface.go index 6edaa7f45..c41dc371f 100644 --- a/payments/db/interface.go +++ b/payments/db/interface.go @@ -21,24 +21,21 @@ type PaymentReader interface { // FetchPayment fetches the payment corresponding to the given payment // hash. - FetchPayment(ctx context.Context, - paymentHash lntypes.Hash) (*MPPayment, error) + FetchPayment(paymentHash lntypes.Hash) (*MPPayment, error) // FetchInFlightPayments returns all payments with status InFlight. - FetchInFlightPayments(ctx context.Context) ([]*MPPayment, error) + FetchInFlightPayments() ([]*MPPayment, error) } // PaymentWriter represents the interface to write operations to the payments // database. type PaymentWriter interface { // DeletePayment deletes a payment from the DB given its payment hash. - DeletePayment(ctx context.Context, paymentHash lntypes.Hash, - failedAttemptsOnly bool) error + DeletePayment(paymentHash lntypes.Hash, failedAttemptsOnly bool) error // DeletePayments deletes all payments from the DB given the specified // flags. - DeletePayments(ctx context.Context, failedOnly, - failedAttemptsOnly bool) (int, error) + DeletePayments(failedOnly, failedAttemptsOnly bool) (int, error) PaymentControl } @@ -61,22 +58,10 @@ type PaymentControl interface { // exists in the database before creating a new payment. However, it // should allow the user making a subsequent payment if the payment is // in a Failed state. - InitPayment(context.Context, lntypes.Hash, *PaymentCreationInfo) error + InitPayment(lntypes.Hash, *PaymentCreationInfo) error // RegisterAttempt atomically records the provided HTLCAttemptInfo. - // - // IMPORTANT: Callers MUST serialize calls to RegisterAttempt for the - // same payment hash. Concurrent calls will result in race conditions - // where both calls read the same initial payment state, validate - // against stale data, and could cause overpayment. For example: - // - Both goroutines fetch payment with 400 sats sent - // - Both validate sending 650 sats won't overpay (within limit) - // - Both commit successfully - // - Result: 1700 sats sent, exceeding the payment amount - // The payment router/controller layer is responsible for ensuring - // serialized access per payment hash. - RegisterAttempt(context.Context, lntypes.Hash, - *HTLCAttemptInfo) (*MPPayment, error) + RegisterAttempt(lntypes.Hash, *HTLCAttemptInfo) (*MPPayment, error) // SettleAttempt marks the given attempt settled with the preimage. If // this is a multi shard payment, this might implicitly mean the @@ -86,12 +71,10 @@ type PaymentControl interface { // error to prevent us from making duplicate payments to the same // payment hash. The provided preimage is atomically saved to the DB // for record keeping. - SettleAttempt(context.Context, lntypes.Hash, uint64, - *HTLCSettleInfo) (*MPPayment, error) + SettleAttempt(lntypes.Hash, uint64, *HTLCSettleInfo) (*MPPayment, error) // FailAttempt marks the given payment attempt failed. - FailAttempt(context.Context, lntypes.Hash, uint64, - *HTLCFailInfo) (*MPPayment, error) + FailAttempt(lntypes.Hash, uint64, *HTLCFailInfo) (*MPPayment, error) // Fail transitions a payment into the Failed state, and records // the ultimate reason the payment failed. Note that this should only @@ -99,12 +82,12 @@ type PaymentControl interface { // invoking this method, InitPayment should return nil on its next call // for this payment hash, allowing the user to make a subsequent // payment. - Fail(context.Context, lntypes.Hash, FailureReason) (*MPPayment, error) + Fail(lntypes.Hash, FailureReason) (*MPPayment, error) // DeleteFailedAttempts removes all failed HTLCs from the db. It should // be called for a given payment whenever all inflight htlcs are // completed, and the payment has reached a final terminal state. - DeleteFailedAttempts(context.Context, lntypes.Hash) error + DeleteFailedAttempts(lntypes.Hash) error } // DBMPPayment is an interface that represents the payment state during a diff --git a/payments/db/kv_duplicate_payments.go b/payments/db/kv_duplicate_payments.go index d5accf250..3ac1faddd 100644 --- a/payments/db/kv_duplicate_payments.go +++ b/payments/db/kv_duplicate_payments.go @@ -228,7 +228,7 @@ func fetchDuplicatePayments(paymentHashBucket kvdb.RBucket) ([]*MPPayment, subBucket := dup.NestedReadBucket(k) if subBucket == nil { // We one bucket for each duplicate to be found. - return fmt.Errorf("non bucket element " + + return fmt.Errorf("non bucket element" + "in duplicate bucket") } diff --git a/payments/db/kv_store.go b/payments/db/kv_store.go index 15d270ed1..62f0b8386 100644 --- a/payments/db/kv_store.go +++ b/payments/db/kv_store.go @@ -13,7 +13,7 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lntypes" @@ -127,6 +127,10 @@ type KVStore struct { // db is the underlying database implementation. db kvdb.Backend + + // keepFailedPaymentAttempts is a flag that indicates whether we should + // keep failed payment attempts in the database. + keepFailedPaymentAttempts bool } // A compile-time constraint to ensure KVStore implements DB. @@ -148,7 +152,8 @@ func NewKVStore(db kvdb.Backend, } return &KVStore{ - db: db, + db: db, + keepFailedPaymentAttempts: opts.KeepFailedPaymentAttempts, }, nil } @@ -181,7 +186,7 @@ func initKVStore(db kvdb.Backend) error { // making sure it does not already exist as an in-flight payment. When this // method returns successfully, the payment is guaranteed to be in the InFlight // state. -func (p *KVStore) InitPayment(_ context.Context, paymentHash lntypes.Hash, +func (p *KVStore) InitPayment(paymentHash lntypes.Hash, info *PaymentCreationInfo) error { // Obtain a new sequence number for this payment. This is used @@ -283,14 +288,15 @@ func (p *KVStore) InitPayment(_ context.Context, paymentHash lntypes.Hash, return updateErr } -// DeleteFailedAttempts deletes all failed htlcs for a payment. -func (p *KVStore) DeleteFailedAttempts(ctx context.Context, - hash lntypes.Hash) error { - - const failedHtlcsOnly = true - err := p.DeletePayment(ctx, hash, failedHtlcsOnly) - if err != nil { - return err +// DeleteFailedAttempts deletes all failed htlcs for a payment if configured +// by the KVStore db. +func (p *KVStore) DeleteFailedAttempts(hash lntypes.Hash) error { + if !p.keepFailedPaymentAttempts { + const failedHtlcsOnly = true + err := p.DeletePayment(hash, failedHtlcsOnly) + if err != nil { + return err + } } return nil @@ -351,7 +357,7 @@ func deserializePaymentIndex(r io.Reader) (lntypes.Hash, error) { // RegisterAttempt atomically records the provided HTLCAttemptInfo to the // DB. -func (p *KVStore) RegisterAttempt(_ context.Context, paymentHash lntypes.Hash, +func (p *KVStore) RegisterAttempt(paymentHash lntypes.Hash, attempt *HTLCAttemptInfo) (*MPPayment, error) { // Serialize the information before opening the db transaction. @@ -422,7 +428,7 @@ func (p *KVStore) RegisterAttempt(_ context.Context, paymentHash lntypes.Hash, // After invoking this method, InitPayment should always return an error to // prevent us from making duplicate payments to the same payment hash. The // provided preimage is atomically saved to the DB for record keeping. -func (p *KVStore) SettleAttempt(_ context.Context, hash lntypes.Hash, +func (p *KVStore) SettleAttempt(hash lntypes.Hash, attemptID uint64, settleInfo *HTLCSettleInfo) (*MPPayment, error) { var b bytes.Buffer @@ -435,7 +441,7 @@ func (p *KVStore) SettleAttempt(_ context.Context, hash lntypes.Hash, } // FailAttempt marks the given payment attempt failed. -func (p *KVStore) FailAttempt(_ context.Context, hash lntypes.Hash, +func (p *KVStore) FailAttempt(hash lntypes.Hash, attemptID uint64, failInfo *HTLCFailInfo) (*MPPayment, error) { var b bytes.Buffer @@ -520,7 +526,7 @@ func (p *KVStore) updateHtlcKey(paymentHash lntypes.Hash, // payment failed. After invoking this method, InitPayment should return nil on // its next call for this payment hash, allowing the switch to make a // subsequent payment. -func (p *KVStore) Fail(_ context.Context, paymentHash lntypes.Hash, +func (p *KVStore) Fail(paymentHash lntypes.Hash, reason FailureReason) (*MPPayment, error) { var ( @@ -577,8 +583,8 @@ func (p *KVStore) Fail(_ context.Context, paymentHash lntypes.Hash, } // FetchPayment returns information about a payment from the database. -func (p *KVStore) FetchPayment(_ context.Context, - paymentHash lntypes.Hash) (*MPPayment, error) { +func (p *KVStore) FetchPayment(paymentHash lntypes.Hash) ( + *MPPayment, error) { var payment *MPPayment err := kvdb.View(p.db, func(tx kvdb.RTx) error { @@ -733,9 +739,7 @@ func fetchPaymentStatus(bucket kvdb.RBucket) (PaymentStatus, error) { } // FetchInFlightPayments returns all payments with status InFlight. -func (p *KVStore) FetchInFlightPayments(_ context.Context) ([]*MPPayment, - error) { - +func (p *KVStore) FetchInFlightPayments() ([]*MPPayment, error) { var ( inFlights []*MPPayment start = time.Now() @@ -1236,7 +1240,7 @@ func fetchPaymentWithSequenceNumber(tx kvdb.RTx, paymentHash lntypes.Hash, seqBytes := subBucket.Get(duplicatePaymentSequenceKey) if seqBytes == nil { - return ErrNoDuplicateSequenceNumber + return err } // If this duplicate payment is not the sequence number we are @@ -1269,7 +1273,7 @@ func fetchPaymentWithSequenceNumber(tx kvdb.RTx, paymentHash lntypes.Hash, // DeletePayment deletes a payment from the DB given its payment hash. If // failedHtlcsOnly is set, only failed HTLC attempts of the payment will be // deleted. -func (p *KVStore) DeletePayment(_ context.Context, paymentHash lntypes.Hash, +func (p *KVStore) DeletePayment(paymentHash lntypes.Hash, failedHtlcsOnly bool) error { return kvdb.Update(p.db, func(tx kvdb.RwTx) error { @@ -1366,7 +1370,7 @@ func (p *KVStore) DeletePayment(_ context.Context, paymentHash lntypes.Hash, // failedHtlcsOnly is set, the payment itself won't be deleted, only failed HTLC // attempts. The method returns the number of deleted payments, which is always // 0 if failedHtlcsOnly is set. -func (p *KVStore) DeletePayments(_ context.Context, failedOnly, +func (p *KVStore) DeletePayments(failedOnly, failedHtlcsOnly bool) (int, error) { var numPayments int diff --git a/payments/db/kv_store_test.go b/payments/db/kv_store_test.go index 8e086b832..2c2895175 100644 --- a/payments/db/kv_store_test.go +++ b/payments/db/kv_store_test.go @@ -1,10 +1,7 @@ -//go:build !test_db_sqlite && !test_db_postgres - package paymentsdb import ( "bytes" - "crypto/sha256" "encoding/binary" "io" "math" @@ -13,74 +10,228 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/walletdb" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/tlv" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -// TestKVStoreDeleteDuplicatePayments tests that when a payment with duplicate -// payments is deleted, both the parent payment and its duplicates are properly -// removed from the payment index. This is specific to the KV store's legacy -// duplicate payment handling. -func TestKVStoreDeleteDuplicatePayments(t *testing.T) { +// TestKVStoreDeleteNonInFlight checks that calling DeletePayments only +// deletes payments from the database that are not in-flight. +// +// TODO(ziggie): Make this test db agnostic. +func TestKVStoreDeleteNonInFlight(t *testing.T) { t.Parallel() - ctx := t.Context() - paymentDB := NewKVTestDB(t) - // Create a successful payment. - preimg := genPreimage(t) + // Create a sequence number for duplicate payments that will not collide + // with the sequence numbers for the payments we create. These values + // start at 1, so 9999 is a safe bet for this test. + var duplicateSeqNr = 9999 - rhash := sha256.Sum256(preimg[:]) - info := genPaymentCreationInfo(t, rhash) - attempt := genAttemptWithHash(t, 0, genSessionKey(t), rhash) - - // Init and settle the payment. - err := paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) - require.NoError(t, err, "unable to init payment") - - _, err = paymentDB.RegisterAttempt( - ctx, info.PaymentIdentifier, attempt, - ) - require.NoError(t, err, "unable to register attempt") - - _, err = paymentDB.SettleAttempt( - ctx, info.PaymentIdentifier, attempt.AttemptID, - &HTLCSettleInfo{ - Preimage: preimg, + payments := []struct { + failed bool + success bool + hasDuplicate bool + }{ + { + failed: true, + success: false, + hasDuplicate: false, }, - ) - require.NoError(t, err, "unable to settle attempt") + { + failed: false, + success: true, + hasDuplicate: false, + }, + { + failed: false, + success: false, + hasDuplicate: false, + }, + { + failed: false, + success: true, + hasDuplicate: true, + }, + } - assertDBPaymentstatus( - t, paymentDB, info.PaymentIdentifier, StatusSucceeded, - ) + var numSuccess, numInflight int - // Fetch the payment to get its sequence number. - payment, err := paymentDB.FetchPayment(ctx, info.PaymentIdentifier) + for _, p := range payments { + info, attempt, preimg, err := genInfo(t) + if err != nil { + t.Fatalf("unable to generate htlc message: %v", err) + } + + // Sends base htlc message which initiate StatusInFlight. + err = paymentDB.InitPayment(info.PaymentIdentifier, info) + if err != nil { + t.Fatalf("unable to send htlc message: %v", err) + } + _, err = paymentDB.RegisterAttempt( + info.PaymentIdentifier, attempt, + ) + if err != nil { + t.Fatalf("unable to send htlc message: %v", err) + } + + htlc := &htlcStatus{ + HTLCAttemptInfo: attempt, + } + + switch { + case p.failed: + // Fail the payment attempt. + htlcFailure := HTLCFailUnreadable + _, err := paymentDB.FailAttempt( + info.PaymentIdentifier, attempt.AttemptID, + &HTLCFailInfo{ + Reason: htlcFailure, + }, + ) + if err != nil { + t.Fatalf("unable to fail htlc: %v", err) + } + + // Fail the payment, which should moved it to Failed. + failReason := FailureReasonNoRoute + _, err = paymentDB.Fail( + info.PaymentIdentifier, failReason, + ) + if err != nil { + t.Fatalf("unable to fail payment hash: %v", err) + } + + // Verify the status is indeed Failed. + assertDBPaymentstatus( + t, paymentDB, info.PaymentIdentifier, + StatusFailed, + ) + + htlc.failure = &htlcFailure + assertPaymentInfo( + t, paymentDB, info.PaymentIdentifier, info, + &failReason, htlc, + ) + + case p.success: + // Verifies that status was changed to StatusSucceeded. + _, err := paymentDB.SettleAttempt( + info.PaymentIdentifier, attempt.AttemptID, + &HTLCSettleInfo{ + Preimage: preimg, + }, + ) + if err != nil { + t.Fatalf("error shouldn't have been received,"+ + " got: %v", err) + } + + assertDBPaymentstatus( + t, paymentDB, info.PaymentIdentifier, + StatusSucceeded, + ) + + htlc.settle = &preimg + assertPaymentInfo( + t, paymentDB, info.PaymentIdentifier, info, nil, + htlc, + ) + + numSuccess++ + + default: + assertDBPaymentstatus( + t, paymentDB, info.PaymentIdentifier, + StatusInFlight, + ) + assertPaymentInfo( + t, paymentDB, info.PaymentIdentifier, info, nil, + htlc, + ) + + numInflight++ + } + + // If the payment is intended to have a duplicate payment, we + // add one. + if p.hasDuplicate { + appendDuplicatePayment( + t, paymentDB.db, info.PaymentIdentifier, + uint64(duplicateSeqNr), preimg, + ) + duplicateSeqNr++ + numSuccess++ + } + } + + // Delete all failed payments. + numPayments, err := paymentDB.DeletePayments(true, false) require.NoError(t, err) + require.EqualValues(t, 1, numPayments) - // Add two duplicate payments. Use high sequence numbers that won't - // collide with the original payment. - duplicateSeqNr1 := payment.SequenceNum + 1000 - duplicateSeqNr2 := payment.SequenceNum + 1001 + // This should leave the succeeded and in-flight payments. + dbPayments, err := paymentDB.FetchPayments() + if err != nil { + t.Fatal(err) + } - appendDuplicatePayment( - t, paymentDB.db, info.PaymentIdentifier, duplicateSeqNr1, - preimg, - ) - appendDuplicatePayment( - t, paymentDB.db, info.PaymentIdentifier, duplicateSeqNr2, - preimg, - ) + if len(dbPayments) != numSuccess+numInflight { + t.Fatalf("expected %d payments, got %d", + numSuccess+numInflight, len(dbPayments)) + } - // Verify we now have 3 index entries: original + 2 duplicates. + var s, i int + for _, p := range dbPayments { + t.Log("fetch payment has status", p.Status) + switch p.Status { + case StatusSucceeded: + s++ + case StatusInFlight: + i++ + } + } + + if s != numSuccess { + t.Fatalf("expected %d succeeded payments , got %d", + numSuccess, s) + } + if i != numInflight { + t.Fatalf("expected %d in-flight payments, got %d", + numInflight, i) + } + + // Now delete all payments except in-flight. + numPayments, err = paymentDB.DeletePayments(false, false) + require.NoError(t, err) + require.EqualValues(t, 2, numPayments) + + // This should leave the in-flight payment. + dbPayments, err = paymentDB.FetchPayments() + if err != nil { + t.Fatal(err) + } + + if len(dbPayments) != numInflight { + t.Fatalf("expected %d payments, got %d", numInflight, + len(dbPayments)) + } + + for _, p := range dbPayments { + if p.Status != StatusInFlight { + t.Fatalf("expected in-fligth status, got %v", p.Status) + } + } + + // Finally, check that we only have a single index left in the payment + // index bucket. var indexCount int err = kvdb.View(paymentDB.db, func(tx walletdb.ReadTx) error { index := tx.ReadBucket(paymentsIndexBucket) @@ -91,33 +242,85 @@ func TestKVStoreDeleteDuplicatePayments(t *testing.T) { }) }, func() { indexCount = 0 }) require.NoError(t, err) - require.Equal(t, 3, indexCount, "expected 3 index entries "+ - "(parent + 2 duplicates)") - // Delete all successful payments. - numPayments, err := paymentDB.DeletePayments(ctx, false, false) + require.Equal(t, 1, indexCount) +} + +type htlcStatus struct { + *HTLCAttemptInfo + settle *lntypes.Preimage + failure *HTLCFailReason +} + +// fetchPaymentIndexEntry gets the payment hash for the sequence number provided +// from our payment indexes bucket. +func fetchPaymentIndexEntry(t *testing.T, p *KVStore, + sequenceNumber uint64) (*lntypes.Hash, error) { + + t.Helper() + + var hash lntypes.Hash + + if err := kvdb.View(p.db, func(tx walletdb.ReadTx) error { + indexBucket := tx.ReadBucket(paymentsIndexBucket) + key := make([]byte, 8) + byteOrder.PutUint64(key, sequenceNumber) + + indexValue := indexBucket.Get(key) + if indexValue == nil { + return ErrNoSequenceNrIndex + } + + r := bytes.NewReader(indexValue) + + var err error + hash, err = deserializePaymentIndex(r) + + return err + }, func() { + hash = lntypes.Hash{} + }); err != nil { + return nil, err + } + + return &hash, nil +} + +// assertPaymentIndex looks up the index for a payment in the db and checks +// that its payment hash matches the expected hash passed in. +func assertPaymentIndex(t *testing.T, p DB, expectedHash lntypes.Hash) { + t.Helper() + + // Only the kv implementation uses the index so we exit early if the + // payment db is not a kv implementation. This helps us to reuse the + // same test for both implementations. + kvPaymentDB, ok := p.(*KVStore) + if !ok { + return + } + + // Lookup the payment so that we have its sequence number and check + // that is has correctly been indexed in the payment indexes bucket. + pmt, err := kvPaymentDB.FetchPayment(expectedHash) require.NoError(t, err) - require.EqualValues(t, 1, numPayments, "should delete 1 payment") - // Verify all payments are deleted. - dbPayments, err := paymentDB.FetchPayments() + hash, err := fetchPaymentIndexEntry(t, kvPaymentDB, pmt.SequenceNum) require.NoError(t, err) - require.Empty(t, dbPayments, "all payments should be deleted") + assert.Equal(t, expectedHash, *hash) +} - // Verify the payment index is now empty - all 3 entries (parent + - // duplicates) should be removed. - indexCount = 0 - err = kvdb.View(paymentDB.db, func(tx walletdb.ReadTx) error { - index := tx.ReadBucket(paymentsIndexBucket) +// assertNoIndex checks that an index for the sequence number provided does not +// exist. +func assertNoIndex(t *testing.T, p DB, seqNr uint64) { + t.Helper() - return index.ForEach(func(k, v []byte) error { - indexCount++ - return nil - }) - }, func() { indexCount = 0 }) - require.NoError(t, err) - require.Equal(t, 0, indexCount, "payment index should be empty "+ - "after deleting payment with duplicates") + kvPaymentDB, ok := p.(*KVStore) + if !ok { + return + } + + _, err := fetchPaymentIndexEntry(t, kvPaymentDB, seqNr) + require.Equal(t, ErrNoSequenceNrIndex, err) } func makeFakeInfo(t *testing.T) (*PaymentCreationInfo, @@ -274,35 +477,35 @@ func deletePayment(t *testing.T, db kvdb.Backend, paymentHash lntypes.Hash, func TestFetchPaymentWithSequenceNumber(t *testing.T) { paymentDB := NewKVTestDB(t) - ctx := t.Context() - // Generate a test payment which does not have duplicates. - noDuplicates, _ := genInfo(t) + noDuplicates, _, _, err := genInfo(t) + require.NoError(t, err) // Create a new payment entry in the database. - err := paymentDB.InitPayment( - ctx, noDuplicates.PaymentIdentifier, noDuplicates, + err = paymentDB.InitPayment( + noDuplicates.PaymentIdentifier, noDuplicates, ) require.NoError(t, err) // Fetch the payment so we can get its sequence nr. noDuplicatesPayment, err := paymentDB.FetchPayment( - ctx, noDuplicates.PaymentIdentifier, + noDuplicates.PaymentIdentifier, ) require.NoError(t, err) // Generate a test payment which we will add duplicates to. - hasDuplicates, preimg := genInfo(t) + hasDuplicates, _, preimg, err := genInfo(t) + require.NoError(t, err) // Create a new payment entry in the database. err = paymentDB.InitPayment( - ctx, hasDuplicates.PaymentIdentifier, hasDuplicates, + hasDuplicates.PaymentIdentifier, hasDuplicates, ) require.NoError(t, err) // Fetch the payment so we can get its sequence nr. hasDuplicatesPayment, err := paymentDB.FetchPayment( - ctx, hasDuplicates.PaymentIdentifier, + hasDuplicates.PaymentIdentifier, ) require.NoError(t, err) @@ -449,7 +652,8 @@ func putDuplicatePayment(t *testing.T, duplicateBucket kvdb.RwBucket, require.NoError(t, err) // Generate fake information for the duplicate payment. - info, _ := genInfo(t) + info, _, _, err := genInfo(t) + require.NoError(t, err) // Write the payment info to disk under the creation info key. This code // is copied rather than using serializePaymentCreationInfo to ensure @@ -480,19 +684,17 @@ func putDuplicatePayment(t *testing.T, duplicateBucket kvdb.RwBucket, require.NoError(t, err) } -// TestKVStoreQueryPaymentsDuplicates tests the KV store's legacy duplicate -// payment handling. This tests the specific case where duplicate payments -// are stored in a nested bucket within the parent payment bucket. -func TestKVStoreQueryPaymentsDuplicates(t *testing.T) { - t.Parallel() - +// TestQueryPayments tests retrieval of payments with forwards and reversed +// queries. +// +// TODO(ziggie): Make this test db agnostic. +func TestQueryPayments(t *testing.T) { + // Define table driven test for QueryPayments. // Test payments have sequence indices [1, 3, 4, 5, 6, 7]. // Note that the payment with index 7 has the same payment hash as 6, // and is stored in a nested bucket within payment 6 rather than being - // its own entry in the payments bucket. This tests retrieval of legacy - // duplicate payments which is KV-store specific. - // These test cases focus on validating that duplicate payments (seq 7, - // nested under payment 6) are correctly returned in queries. + // its own entry in the payments bucket. We do this to test retrieval + // of legacy payments. tests := []struct { name string query Query @@ -504,20 +706,31 @@ func TestKVStoreQueryPaymentsDuplicates(t *testing.T) { expectedSeqNrs []uint64 }{ { - name: "query includes duplicate payment in forward " + - "order", + name: "IndexOffset at the end of the payments range", query: Query{ - IndexOffset: 5, - MaxPayments: 3, + IndexOffset: 7, + MaxPayments: 7, Reversed: false, IncludeIncomplete: true, }, - firstIndex: 6, - lastIndex: 7, - expectedSeqNrs: []uint64{6, 7}, + firstIndex: 0, + lastIndex: 0, + expectedSeqNrs: nil, }, { - name: "query duplicate payment at end", + name: "query in forwards order, start at beginning", + query: Query{ + IndexOffset: 0, + MaxPayments: 2, + Reversed: false, + IncludeIncomplete: true, + }, + firstIndex: 1, + lastIndex: 3, + expectedSeqNrs: []uint64{1, 3}, + }, + { + name: "query in forwards order, start at end, overflow", query: Query{ IndexOffset: 6, MaxPayments: 2, @@ -529,7 +742,44 @@ func TestKVStoreQueryPaymentsDuplicates(t *testing.T) { expectedSeqNrs: []uint64{7}, }, { - name: "query includes duplicate in reverse order", + name: "start at offset index outside of payments", + query: Query{ + IndexOffset: 20, + MaxPayments: 2, + Reversed: false, + IncludeIncomplete: true, + }, + firstIndex: 0, + lastIndex: 0, + expectedSeqNrs: nil, + }, + { + name: "overflow in forwards order", + query: Query{ + IndexOffset: 4, + MaxPayments: math.MaxUint64, + Reversed: false, + IncludeIncomplete: true, + }, + firstIndex: 5, + lastIndex: 7, + expectedSeqNrs: []uint64{5, 6, 7}, + }, + { + name: "start at offset index outside of payments, " + + "reversed order", + query: Query{ + IndexOffset: 9, + MaxPayments: 2, + Reversed: true, + IncludeIncomplete: true, + }, + firstIndex: 6, + lastIndex: 7, + expectedSeqNrs: []uint64{6, 7}, + }, + { + name: "query in reverse order, start at end", query: Query{ IndexOffset: 0, MaxPayments: 2, @@ -541,11 +791,36 @@ func TestKVStoreQueryPaymentsDuplicates(t *testing.T) { expectedSeqNrs: []uint64{6, 7}, }, { - name: "query all payments includes duplicate", + name: "query in reverse order, starting in middle", + query: Query{ + IndexOffset: 4, + MaxPayments: 2, + Reversed: true, + IncludeIncomplete: true, + }, + firstIndex: 1, + lastIndex: 3, + expectedSeqNrs: []uint64{1, 3}, + }, + { + name: "query in reverse order, starting in middle, " + + "with underflow", + query: Query{ + IndexOffset: 4, + MaxPayments: 5, + Reversed: true, + IncludeIncomplete: true, + }, + firstIndex: 1, + lastIndex: 3, + expectedSeqNrs: []uint64{1, 3}, + }, + { + name: "all payments in reverse, order maintained", query: Query{ IndexOffset: 0, - MaxPayments: math.MaxUint64, - Reversed: false, + MaxPayments: 7, + Reversed: true, IncludeIncomplete: true, }, firstIndex: 1, @@ -553,7 +828,7 @@ func TestKVStoreQueryPaymentsDuplicates(t *testing.T) { expectedSeqNrs: []uint64{1, 3, 4, 5, 6, 7}, }, { - name: "exclude incomplete includes duplicate", + name: "exclude incomplete payments", query: Query{ IndexOffset: 0, MaxPayments: 7, @@ -564,6 +839,96 @@ func TestKVStoreQueryPaymentsDuplicates(t *testing.T) { lastIndex: 7, expectedSeqNrs: []uint64{7}, }, + { + name: "query payments at index gap", + query: Query{ + IndexOffset: 1, + MaxPayments: 7, + Reversed: false, + IncludeIncomplete: true, + }, + firstIndex: 3, + lastIndex: 7, + expectedSeqNrs: []uint64{3, 4, 5, 6, 7}, + }, + { + name: "query payments reverse before index gap", + query: Query{ + IndexOffset: 3, + MaxPayments: 7, + Reversed: true, + IncludeIncomplete: true, + }, + firstIndex: 1, + lastIndex: 1, + expectedSeqNrs: []uint64{1}, + }, + { + name: "query payments reverse on index gap", + query: Query{ + IndexOffset: 2, + MaxPayments: 7, + Reversed: true, + IncludeIncomplete: true, + }, + firstIndex: 1, + lastIndex: 1, + expectedSeqNrs: []uint64{1}, + }, + { + name: "query payments forward on index gap", + query: Query{ + IndexOffset: 2, + MaxPayments: 2, + Reversed: false, + IncludeIncomplete: true, + }, + firstIndex: 3, + lastIndex: 4, + expectedSeqNrs: []uint64{3, 4}, + }, + { + name: "query in forwards order, with start creation " + + "time", + query: Query{ + IndexOffset: 0, + MaxPayments: 2, + Reversed: false, + IncludeIncomplete: true, + CreationDateStart: 5, + }, + firstIndex: 5, + lastIndex: 6, + expectedSeqNrs: []uint64{5, 6}, + }, + { + name: "query in forwards order, with start creation " + + "time at end, overflow", + query: Query{ + IndexOffset: 0, + MaxPayments: 2, + Reversed: false, + IncludeIncomplete: true, + CreationDateStart: 7, + }, + firstIndex: 7, + lastIndex: 7, + expectedSeqNrs: []uint64{7}, + }, + { + name: "query with start and end creation time", + query: Query{ + IndexOffset: 9, + MaxPayments: math.MaxUint64, + Reversed: true, + IncludeIncomplete: true, + CreationDateStart: 3, + CreationDateEnd: 5, + }, + firstIndex: 3, + lastIndex: 5, + expectedSeqNrs: []uint64{3, 4, 5}, + }, } for _, tt := range tests { @@ -574,6 +939,10 @@ func TestKVStoreQueryPaymentsDuplicates(t *testing.T) { paymentDB := NewKVTestDB(t) + // Initialize the payment database. + paymentDB, err := NewKVStore(paymentDB.db) + require.NoError(t, err) + // Make a preliminary query to make sure it's ok to // query when we have no payments. resp, err := paymentDB.QueryPayments(ctx, tt.query) @@ -591,22 +960,25 @@ func TestKVStoreQueryPaymentsDuplicates(t *testing.T) { for i := 0; i < nonDuplicatePayments; i++ { // Generate a test payment. - info, preimg := genInfo(t) - + info, _, preimg, err := genInfo(t) + if err != nil { + t.Fatalf("unable to create test "+ + "payment: %v", err) + } // Override creation time to allow for testing // of CreationDateStart and CreationDateEnd. info.CreationTime = time.Unix(int64(i+1), 0) // Create a new payment entry in the database. err = paymentDB.InitPayment( - ctx, info.PaymentIdentifier, info, + info.PaymentIdentifier, info, ) require.NoError(t, err) // Immediately delete the payment with index 2. if i == 1 { pmt, err := paymentDB.FetchPayment( - ctx, info.PaymentIdentifier, + info.PaymentIdentifier, ) require.NoError(t, err) @@ -623,7 +995,7 @@ func TestKVStoreQueryPaymentsDuplicates(t *testing.T) { // duplicate payments will always be succeeded. if i == (nonDuplicatePayments - 1) { pmt, err := paymentDB.FetchPayment( - ctx, info.PaymentIdentifier, + info.PaymentIdentifier, ) require.NoError(t, err) diff --git a/payments/db/log.go b/payments/db/log.go index c8892341d..8a77dbcec 100644 --- a/payments/db/log.go +++ b/payments/db/log.go @@ -3,7 +3,6 @@ package paymentsdb import ( "github.com/btcsuite/btclog/v2" "github.com/lightningnetwork/lnd/build" - paymentsmig1 "github.com/lightningnetwork/lnd/payments/db/migration1" ) // log is a logger that is initialized with no output filters. This @@ -30,5 +29,4 @@ func DisableLog() { // using btclog. func UseLogger(logger btclog.Logger) { log = logger - paymentsmig1.UseLogger(logger) } diff --git a/payments/db/migration1/codec.go b/payments/db/migration1/codec.go deleted file mode 100644 index 8e9a0c81e..000000000 --- a/payments/db/migration1/codec.go +++ /dev/null @@ -1,198 +0,0 @@ -package migration1 - -import ( - "encoding/binary" - "fmt" - "io" - "time" - - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire" -) - -// Big endian is the preferred byte order, due to cursor scans over -// integer keys iterating in order. -var byteOrder = binary.BigEndian - -// UnknownElementType is an error returned when the codec is unable to encode -// or decode a particular type. -// -// NOTE: This is a frozen, self-contained copy of the serialization error type -// to ensure that this migration package has no dependency on live packages -// whose implementation may change after this migration was released. -type UnknownElementType struct { - method string - element interface{} -} - -// NewUnknownElementType creates a new UnknownElementType error from the passed -// method name and element. -func NewUnknownElementType(method string, el interface{}) UnknownElementType { - return UnknownElementType{method: method, element: el} -} - -// Error returns the name of the method that encountered the error, as well as -// the type that was unsupported. -func (e UnknownElementType) Error() string { - return fmt.Sprintf("Unknown type in %s: %T", e.method, e.element) -} - -// WriteElement serializes a single element into the provided io.Writer using -// big-endian byte order. Only the types required by the migration1 package are -// supported. This is a frozen, self-contained copy of the serialization logic -// to ensure that this migration package has no dependency on live packages -// whose implementation may change after this migration was released. -func WriteElement(w io.Writer, element interface{}) error { - switch e := element.(type) { - case paymentIndexType: - if err := binary.Write(w, byteOrder, e); err != nil { - return err - } - - case uint8: - if err := binary.Write(w, byteOrder, e); err != nil { - return err - } - - case uint32: - if err := binary.Write(w, byteOrder, e); err != nil { - return err - } - - case uint64: - if err := binary.Write(w, byteOrder, e); err != nil { - return err - } - - case lnwire.MilliSatoshi: - if err := binary.Write(w, byteOrder, uint64(e)); err != nil { - return err - } - - case [32]byte: - if _, err := w.Write(e[:]); err != nil { - return err - } - - case []byte: - if err := wire.WriteVarBytes(w, 0, e); err != nil { - return err - } - - default: - return UnknownElementType{"WriteElement", e} - } - - return nil -} - -// WriteElements serializes a variadic list of elements into the given -// io.Writer. -func WriteElements(w io.Writer, elements ...interface{}) error { - for _, element := range elements { - if err := WriteElement(w, element); err != nil { - return err - } - } - - return nil -} - -// ReadElement deserializes a single element from the provided io.Reader using -// big-endian byte order. Only the types required by the migration1 package are -// supported. This is a frozen, self-contained copy of the deserialization -// logic to ensure that this migration package has no dependency on live -// packages whose implementation may change after this migration was released. -func ReadElement(r io.Reader, element interface{}) error { - switch e := element.(type) { - case *paymentIndexType: - if err := binary.Read(r, byteOrder, e); err != nil { - return err - } - - case *uint8: - if err := binary.Read(r, byteOrder, e); err != nil { - return err - } - - case *uint32: - if err := binary.Read(r, byteOrder, e); err != nil { - return err - } - - case *uint64: - if err := binary.Read(r, byteOrder, e); err != nil { - return err - } - - case *lnwire.MilliSatoshi: - var a uint64 - if err := binary.Read(r, byteOrder, &a); err != nil { - return err - } - *e = lnwire.MilliSatoshi(a) - - case *[32]byte: - if _, err := io.ReadFull(r, e[:]); err != nil { - return err - } - - case *[]byte: - b, err := wire.ReadVarBytes(r, 0, 66000, "[]byte") - if err != nil { - return err - } - *e = b - - default: - return UnknownElementType{"ReadElement", e} - } - - return nil -} - -// ReadElements deserializes the provided io.Reader into a variadic list of -// target elements. -func ReadElements(r io.Reader, elements ...interface{}) error { - for _, element := range elements { - if err := ReadElement(r, element); err != nil { - return err - } - } - - return nil -} - -// deserializeTime deserializes time as unix nanoseconds. -func deserializeTime(r io.Reader) (time.Time, error) { - var scratch [8]byte - if _, err := io.ReadFull(r, scratch[:]); err != nil { - return time.Time{}, err - } - - // Convert to time.Time. Interpret unix nano time zero as a zero - // time.Time value. - unixNano := byteOrder.Uint64(scratch[:]) - if unixNano == 0 { - return time.Time{}, nil - } - - return time.Unix(0, int64(unixNano)), nil -} - -// serializeTime serializes time as unix nanoseconds. -func serializeTime(w io.Writer, t time.Time) error { - var scratch [8]byte - - // Convert to unix nano seconds, but only if time is non-zero. Calling - // UnixNano() on a zero time yields an undefined result. - var unixNano int64 - if !t.IsZero() { - unixNano = t.UnixNano() - } - - byteOrder.PutUint64(scratch[:], uint64(unixNano)) - _, err := w.Write(scratch[:]) - - return err -} diff --git a/payments/db/migration1/errors.go b/payments/db/migration1/errors.go deleted file mode 100644 index 7c2c98288..000000000 --- a/payments/db/migration1/errors.go +++ /dev/null @@ -1,154 +0,0 @@ -package migration1 - -import "errors" - -var ( - // ErrAlreadyPaid signals we have already paid this payment hash. - ErrAlreadyPaid = errors.New("invoice is already paid") - - // ErrPaymentInFlight signals that payment for this payment hash is - // already "in flight" on the network. - ErrPaymentInFlight = errors.New("payment is in transition") - - // ErrPaymentExists is returned when we try to initialize an already - // existing payment that is not failed. - ErrPaymentExists = errors.New("payment already exists") - - // ErrPaymentInternal is returned when performing the payment has a - // conflicting state, such as, - // - payment has StatusSucceeded but remaining amount is not zero. - // - payment has StatusInitiated but remaining amount is zero. - // - payment has StatusFailed but remaining amount is zero. - ErrPaymentInternal = errors.New("internal error") - - // ErrPaymentNotInitiated is returned if the payment wasn't initiated. - ErrPaymentNotInitiated = errors.New("payment isn't initiated") - - // ErrPaymentAlreadySucceeded is returned in the event we attempt to - // change the status of a payment already succeeded. - ErrPaymentAlreadySucceeded = errors.New("payment is already succeeded") - - // ErrPaymentAlreadyFailed is returned in the event we attempt to alter - // a failed payment. - ErrPaymentAlreadyFailed = errors.New("payment has already failed") - - // ErrUnknownPaymentStatus is returned when we do not recognize the - // existing state of a payment. - ErrUnknownPaymentStatus = errors.New("unknown payment status") - - // ErrPaymentTerminal is returned if we attempt to alter a payment that - // already has reached a terminal condition. - ErrPaymentTerminal = errors.New("payment has reached terminal " + - "condition") - - // ErrAttemptAlreadySettled is returned if we try to alter an already - // settled HTLC attempt. - ErrAttemptAlreadySettled = errors.New("attempt already settled") - - // ErrAttemptAlreadyFailed is returned if we try to alter an already - // failed HTLC attempt. - ErrAttemptAlreadyFailed = errors.New("attempt already failed") - - // ErrValueMismatch is returned if we try to register a non-MPP attempt - // with an amount that doesn't match the payment amount. - ErrValueMismatch = errors.New("attempted value doesn't match payment " + - "amount") - - // ErrValueExceedsAmt is returned if we try to register an attempt that - // would take the total sent amount above the payment amount. - ErrValueExceedsAmt = errors.New("attempted value exceeds payment " + - "amount") - - // ErrNonMPPayment is returned if we try to register an MPP attempt for - // a payment that already has a non-MPP attempt registered. - ErrNonMPPayment = errors.New("payment has non-MPP attempts") - - // ErrMPPayment is returned if we try to register a non-MPP attempt for - // a payment that already has an MPP attempt registered. - ErrMPPayment = errors.New("payment has MPP attempts") - - // ErrMPPRecordInBlindedPayment is returned if we try to register an - // attempt with an MPP record for a payment to a blinded path. - ErrMPPRecordInBlindedPayment = errors.New("blinded payment cannot " + - "contain MPP records") - - // ErrBlindedPaymentTotalAmountMismatch is returned if we try to - // register an HTLC shard to a blinded route where the total amount - // doesn't match existing shards. - ErrBlindedPaymentTotalAmountMismatch = errors.New("blinded path " + - "total amount mismatch") - - // ErrMixedBlindedAndNonBlindedPayments is returned if we try to - // register a non-blinded attempt to a payment which uses a blinded - // paths or vice versa. - ErrMixedBlindedAndNonBlindedPayments = errors.New("mixed blinded and " + - "non-blinded payments") - - // ErrBlindedPaymentMissingTotalAmount is returned if we try to - // register a blinded payment attempt where the final hop doesn't set - // the total amount. - ErrBlindedPaymentMissingTotalAmount = errors.New("blinded payment " + - "final hop must set total amount") - - // ErrMPPPaymentAddrMismatch is returned if we try to register an MPP - // shard where the payment address doesn't match existing shards. - ErrMPPPaymentAddrMismatch = errors.New("payment address mismatch") - - // ErrMPPTotalAmountMismatch is returned if we try to register an MPP - // shard where the total amount doesn't match existing shards. - ErrMPPTotalAmountMismatch = errors.New("mp payment total amount " + - "mismatch") - - // ErrPaymentPendingSettled is returned when we try to add a new - // attempt to a payment that has at least one of its HTLCs settled. - ErrPaymentPendingSettled = errors.New("payment has settled htlcs") - - // ErrPaymentPendingFailed is returned when we try to add a new attempt - // to a payment that already has a failure reason. - ErrPaymentPendingFailed = errors.New("payment has failure reason") - - // ErrSentExceedsTotal is returned if the payment's current total sent - // amount exceed the total amount. - ErrSentExceedsTotal = errors.New("total sent exceeds total amount") - - // ErrNoAttemptInfo is returned when no attempt info is stored yet. - ErrNoAttemptInfo = errors.New("unable to find attempt info for " + - "inflight payment") -) - -// KV backend specific errors. -var ( - // ErrNoSequenceNumber is returned if we look up a payment which does - // not have a sequence number. - ErrNoSequenceNumber = errors.New("sequence number not found") - - // ErrDuplicateNotFound is returned when we lookup a payment by its - // index and cannot find a payment with a matching sequence number. - ErrDuplicateNotFound = errors.New("duplicate payment not found") - - // ErrNoDuplicateBucket is returned when we expect to find duplicates - // when looking up a payment from its index, but the payment does not - // have any. - ErrNoDuplicateBucket = errors.New("expected duplicate bucket") - - // ErrNoDuplicateNestedBucket is returned if we do not find duplicate - // payments in their own sub-bucket. - ErrNoDuplicateNestedBucket = errors.New("nested duplicate bucket not " + - "found") - - // ErrNoDuplicateSequenceNumber is returned when a duplicate payment - // sub-bucket does not contain the sequence number key. - ErrNoDuplicateSequenceNumber = errors.New("duplicate payment " + - "sequence number not found") - - // ErrNoSequenceNrIndex is returned when an attempt to lookup a payment - // index is made for a sequence number that is not indexed. - // - // NOTE: Only used for the kv backend. - ErrNoSequenceNrIndex = errors.New("payment sequence number index " + - "does not exist") - - // errMaxPaymentsReached is used internally to signal that the maximum - // number of payments has been reached during a paginated query. - errMaxPaymentsReached = errors.New("max payments reached") -) diff --git a/payments/db/migration1/interface.go b/payments/db/migration1/interface.go deleted file mode 100644 index 7d47118e9..000000000 --- a/payments/db/migration1/interface.go +++ /dev/null @@ -1,140 +0,0 @@ -package migration1 - -import ( - "context" - - "github.com/lightningnetwork/lnd/lntypes" -) - -// DB represents the interface to the underlying payments database. -type DB interface { - PaymentReader - PaymentWriter -} - -// PaymentReader represents the interface to read operations from the payments -// database. -type PaymentReader interface { - // QueryPayments queries the payments database and should support - // pagination. - QueryPayments(ctx context.Context, query Query) (Response, error) - - // FetchPayment fetches the payment corresponding to the given payment - // hash. - FetchPayment(ctx context.Context, - paymentHash lntypes.Hash) (*MPPayment, error) - - // FetchInFlightPayments returns all payments with status InFlight. - FetchInFlightPayments(ctx context.Context) ([]*MPPayment, error) -} - -// PaymentWriter represents the interface to write operations to the payments -// database. -type PaymentWriter interface { - // DeletePayment deletes a payment from the DB given its payment hash. - DeletePayment(ctx context.Context, paymentHash lntypes.Hash, - failedAttemptsOnly bool) error - - // DeletePayments deletes all payments from the DB given the specified - // flags. - DeletePayments(ctx context.Context, failedOnly, - failedAttemptsOnly bool) (int, error) - - PaymentControl -} - -// PaymentControl represents the interface to control the payment lifecycle and -// its database operations. This interface represents the control flow of how -// a payment should be handled in the database. They are not just writing -// operations but they inherently represent the flow of a payment. The methods -// are called in the following order. -// -// 1. InitPayment. -// 2. RegisterAttempt (a payment can have multiple attempts). -// 3. SettleAttempt or FailAttempt (attempts can also fail as long as the -// sending amount will be eventually settled). -// 4. Payment succeeds or "Fail" is called. -// 5. DeleteFailedAttempts is called which will delete all failed attempts -// for a payment to clean up the database. -type PaymentControl interface { - // InitPayment checks that no other payment with the same payment hash - // exists in the database before creating a new payment. However, it - // should allow the user making a subsequent payment if the payment is - // in a Failed state. - InitPayment(context.Context, lntypes.Hash, *PaymentCreationInfo) error - - // RegisterAttempt atomically records the provided HTLCAttemptInfo. - // - // IMPORTANT: Callers MUST serialize calls to RegisterAttempt for the - // same payment hash. Concurrent calls will result in race conditions - // where both calls read the same initial payment state, validate - // against stale data, and could cause overpayment. For example: - // - Both goroutines fetch payment with 400 sats sent - // - Both validate sending 650 sats won't overpay (within limit) - // - Both commit successfully - // - Result: 1700 sats sent, exceeding the payment amount - // The payment router/controller layer is responsible for ensuring - // serialized access per payment hash. - RegisterAttempt(context.Context, lntypes.Hash, - *HTLCAttemptInfo) (*MPPayment, error) - - // SettleAttempt marks the given attempt settled with the preimage. If - // this is a multi shard payment, this might implicitly mean the - // full payment succeeded. - // - // After invoking this method, InitPayment should always return an - // error to prevent us from making duplicate payments to the same - // payment hash. The provided preimage is atomically saved to the DB - // for record keeping. - SettleAttempt(context.Context, lntypes.Hash, uint64, - *HTLCSettleInfo) (*MPPayment, error) - - // FailAttempt marks the given payment attempt failed. - FailAttempt(context.Context, lntypes.Hash, uint64, - *HTLCFailInfo) (*MPPayment, error) - - // Fail transitions a payment into the Failed state, and records - // the ultimate reason the payment failed. Note that this should only - // be called when all active attempts are already failed. After - // invoking this method, InitPayment should return nil on its next call - // for this payment hash, allowing the user to make a subsequent - // payment. - Fail(context.Context, lntypes.Hash, FailureReason) (*MPPayment, error) - - // DeleteFailedAttempts removes all failed HTLCs from the db. It should - // be called for a given payment whenever all inflight htlcs are - // completed, and the payment has reached a final terminal state. - DeleteFailedAttempts(context.Context, lntypes.Hash) error -} - -// DBMPPayment is an interface that represents the payment state during a -// payment lifecycle. -type DBMPPayment interface { - // GetState returns the current state of the payment. - GetState() *MPPaymentState - - // Terminated returns true if the payment is in a final state. - Terminated() bool - - // GetStatus returns the current status of the payment. - GetStatus() PaymentStatus - - // NeedWaitAttempts specifies whether the payment needs to wait for the - // outcome of an attempt. - NeedWaitAttempts() (bool, error) - - // GetHTLCs returns all HTLCs of this payment. - GetHTLCs() []HTLCAttempt - - // InFlightHTLCs returns all HTLCs that are in flight. - InFlightHTLCs() []HTLCAttempt - - // AllowMoreAttempts is used to decide whether we can safely attempt - // more HTLCs for a given payment state. Return an error if the payment - // is in an unexpected state. - AllowMoreAttempts() (bool, error) - - // TerminalInfo returns the settled HTLC attempt or the payment's - // failure reason. - TerminalInfo() (*HTLCAttempt, *FailureReason) -} diff --git a/payments/db/migration1/kv_duplicate_payments.go b/payments/db/migration1/kv_duplicate_payments.go deleted file mode 100644 index bde7b71c0..000000000 --- a/payments/db/migration1/kv_duplicate_payments.go +++ /dev/null @@ -1,249 +0,0 @@ -package migration1 - -import ( - "bytes" - "encoding/binary" - "fmt" - "io" - "time" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/kvdb" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire" -) - -var ( - // duplicatePaymentsBucket is the name of a optional sub-bucket within - // the payment hash bucket, that is used to hold duplicate payments to a - // payment hash. This is needed to support information from earlier - // versions of lnd, where it was possible to pay to a payment hash more - // than once. - duplicatePaymentsBucket = []byte("payment-duplicate-bucket") - - // duplicatePaymentSettleInfoKey is a key used in the payment's - // sub-bucket to store the settle info of the payment. - duplicatePaymentSettleInfoKey = []byte("payment-settle-info") - - // duplicatePaymentAttemptInfoKey is a key used in the payment's - // sub-bucket to store the info about the latest attempt that was done - // for the payment in question. - duplicatePaymentAttemptInfoKey = []byte("payment-attempt-info") - - // duplicatePaymentCreationInfoKey is a key used in the payment's - // sub-bucket to store the creation info of the payment. - duplicatePaymentCreationInfoKey = []byte("payment-creation-info") - - // duplicatePaymentFailInfoKey is a key used in the payment's sub-bucket - // to store information about the reason a payment failed. - duplicatePaymentFailInfoKey = []byte("payment-fail-info") - - // duplicatePaymentSequenceKey is a key used in the payment's sub-bucket - // to store the sequence number of the payment. - duplicatePaymentSequenceKey = []byte("payment-sequence-key") -) - -// duplicateHTLCAttemptInfo contains static information about a specific HTLC -// attempt for a payment. This information is used by the router to handle any -// errors coming back after an attempt is made, and to query the switch about -// the status of the attempt. -type duplicateHTLCAttemptInfo struct { - // attemptID is the unique ID used for this attempt. - attemptID uint64 - - // sessionKey is the ephemeral key used for this attempt. - sessionKey [btcec.PrivKeyBytesLen]byte - - // route is the route attempted to send the HTLC. - route Route -} - -// fetchDuplicatePaymentStatus fetches the payment status of the payment. If -// the payment isn't found, it will return error `ErrPaymentNotInitiated`. -func fetchDuplicatePaymentStatus(bucket kvdb.RBucket) (PaymentStatus, error) { - if bucket.Get(duplicatePaymentSettleInfoKey) != nil { - return StatusSucceeded, nil - } - - if bucket.Get(duplicatePaymentFailInfoKey) != nil { - return StatusFailed, nil - } - - if bucket.Get(duplicatePaymentCreationInfoKey) != nil { - return StatusInFlight, nil - } - - return 0, ErrPaymentNotInitiated -} - -func deserializeDuplicateHTLCAttemptInfo(r io.Reader) ( - *duplicateHTLCAttemptInfo, error) { - - a := &duplicateHTLCAttemptInfo{} - err := ReadElements(r, &a.attemptID, &a.sessionKey) - if err != nil { - return nil, err - } - a.route, err = DeserializeRoute(r) - if err != nil { - return nil, err - } - - return a, nil -} - -func deserializeDuplicatePaymentCreationInfo(r io.Reader) ( - *PaymentCreationInfo, error) { - - var scratch [8]byte - - c := &PaymentCreationInfo{} - - if _, err := io.ReadFull(r, c.PaymentIdentifier[:]); err != nil { - return nil, err - } - - if _, err := io.ReadFull(r, scratch[:]); err != nil { - return nil, err - } - c.Value = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:])) - - if _, err := io.ReadFull(r, scratch[:]); err != nil { - return nil, err - } - c.CreationTime = time.Unix(int64(byteOrder.Uint64(scratch[:])), 0) - - if _, err := io.ReadFull(r, scratch[:4]); err != nil { - return nil, err - } - - reqLen := byteOrder.Uint32(scratch[:4]) - payReq := make([]byte, reqLen) - if reqLen > 0 { - if _, err := io.ReadFull(r, payReq); err != nil { - return nil, err - } - } - c.PaymentRequest = payReq - - return c, nil -} - -func fetchDuplicatePayment(bucket kvdb.RBucket) (*MPPayment, error) { - seqBytes := bucket.Get(duplicatePaymentSequenceKey) - if seqBytes == nil { - return nil, fmt.Errorf("sequence number not found") - } - - sequenceNum := binary.BigEndian.Uint64(seqBytes) - - // Get the payment status. - paymentStatus, err := fetchDuplicatePaymentStatus(bucket) - if err != nil { - return nil, err - } - - // Get the PaymentCreationInfo. - b := bucket.Get(duplicatePaymentCreationInfoKey) - if b == nil { - return nil, fmt.Errorf("creation info not found") - } - - r := bytes.NewReader(b) - creationInfo, err := deserializeDuplicatePaymentCreationInfo(r) - if err != nil { - return nil, err - } - - // Get failure reason if available. - var failureReason *FailureReason - b = bucket.Get(duplicatePaymentFailInfoKey) - if b != nil { - reason := FailureReason(b[0]) - failureReason = &reason - } - - payment := &MPPayment{ - SequenceNum: sequenceNum, - Info: creationInfo, - FailureReason: failureReason, - Status: paymentStatus, - } - - // Get the HTLCAttemptInfo. It can be absent. - b = bucket.Get(duplicatePaymentAttemptInfoKey) - if b != nil { - r = bytes.NewReader(b) - attempt, err := deserializeDuplicateHTLCAttemptInfo(r) - if err != nil { - return nil, err - } - - htlc := HTLCAttempt{ - HTLCAttemptInfo: HTLCAttemptInfo{ - AttemptID: attempt.attemptID, - Route: attempt.route, - sessionKey: attempt.sessionKey, - }, - } - - // Get the payment preimage. This is only found for - // successful payments. - b = bucket.Get(duplicatePaymentSettleInfoKey) - if b != nil { - var preimg lntypes.Preimage - copy(preimg[:], b) - - htlc.Settle = &HTLCSettleInfo{ - Preimage: preimg, - SettleTime: time.Time{}, - } - } else { - // Otherwise the payment must have failed. - htlc.Failure = &HTLCFailInfo{ - FailTime: time.Time{}, - } - } - - payment.HTLCs = []HTLCAttempt{htlc} - } - - return payment, nil -} - -func fetchDuplicatePayments(paymentHashBucket kvdb.RBucket) ([]*MPPayment, - error) { - - var payments []*MPPayment - - // For older versions of lnd, duplicate payments to a payment has was - // possible. These will be found in a sub-bucket indexed by their - // sequence number if available. - dup := paymentHashBucket.NestedReadBucket(duplicatePaymentsBucket) - if dup == nil { - return nil, nil - } - - err := dup.ForEach(func(k, v []byte) error { - subBucket := dup.NestedReadBucket(k) - if subBucket == nil { - // We one bucket for each duplicate to be found. - return fmt.Errorf("non bucket element " + - "in duplicate bucket") - } - - p, err := fetchDuplicatePayment(subBucket) - if err != nil { - return err - } - - payments = append(payments, p) - - return nil - }) - if err != nil { - return nil, err - } - - return payments, nil -} diff --git a/payments/db/migration1/kv_store.go b/payments/db/migration1/kv_store.go deleted file mode 100644 index 95a8266a9..000000000 --- a/payments/db/migration1/kv_store.go +++ /dev/null @@ -1,2120 +0,0 @@ -package migration1 - -import ( - "bytes" - "context" - "encoding/binary" - "errors" - "fmt" - "io" - "math" - "sort" - "sync" - "time" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/kvdb" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire" - "github.com/lightningnetwork/lnd/payments/db/migration1/record" - "github.com/lightningnetwork/lnd/tlv" -) - -const ( - // paymentSeqBlockSize is the block size used when we batch allocate - // payment sequences for future payments. - paymentSeqBlockSize = 1000 - - // paymentProgressLogInterval is the interval we use limiting the - // logging output of payment processing. - paymentProgressLogInterval = 30 * time.Second -) - -//nolint:ll -var ( - // paymentsRootBucket is the name of the top-level bucket within the - // database that stores all data related to payments. Within this - // bucket, each payment hash its own sub-bucket keyed by its payment - // hash. - // - // Bucket hierarchy: - // - // root-bucket - // | - // |-- - // | |--sequence-key: - // | |--creation-info-key: - // | |--fail-info-key: <(optional) fail info> - // | | - // | |--payment-htlcs-bucket (shard-bucket) - // | | | - // | | |-- ai: - // | | |-- si: <(optional) settle info> - // | | |-- fi: <(optional) fail info> - // | | | - // | | ... - // | | - // | | - // | |--duplicate-bucket (only for old, completed payments) - // | | - // | |-- - // | | |--sequence-key: - // | | |--creation-info-key: - // | | |--ai: - // | | |--si: - // | | |--fi: - // | | - // | |-- - // | | | - // | ... ... - // | - // |-- - // | | - // | ... - // ... - // - paymentsRootBucket = []byte("payments-root-bucket") - - // paymentSequenceKey is a key used in the payment's sub-bucket to - // store the sequence number of the payment. - paymentSequenceKey = []byte("payment-sequence-key") - - // paymentCreationInfoKey is a key used in the payment's sub-bucket to - // store the creation info of the payment. - paymentCreationInfoKey = []byte("payment-creation-info") - - // paymentHtlcsBucket is a bucket where we'll store the information - // about the HTLCs that were attempted for a payment. - paymentHtlcsBucket = []byte("payment-htlcs-bucket") - - // htlcAttemptInfoKey is the key used as the prefix of an HTLC attempt - // to store the info about the attempt that was done for the HTLC in - // question. The HTLC attempt ID is concatenated at the end. - htlcAttemptInfoKey = []byte("ai") - - // htlcSettleInfoKey is the key used as the prefix of an HTLC attempt - // settle info, if any. The HTLC attempt ID is concatenated at the end. - htlcSettleInfoKey = []byte("si") - - // htlcFailInfoKey is the key used as the prefix of an HTLC attempt - // failure information, if any.The HTLC attempt ID is concatenated at - // the end. - htlcFailInfoKey = []byte("fi") - - // paymentFailInfoKey is a key used in the payment's sub-bucket to - // store information about the reason a payment failed. - paymentFailInfoKey = []byte("payment-fail-info") - - // paymentsIndexBucket is the name of the top-level bucket within the - // database that stores an index of payment sequence numbers to its - // payment hash. - // payments-sequence-index-bucket - // |--: - // |--... - // |--: - paymentsIndexBucket = []byte("payments-index-bucket") -) - -// KVStore implements persistence for payments and payment attempts. -type KVStore struct { - // Sequence management for the kv store. - seqMu sync.Mutex - currSeq uint64 - storedSeq uint64 - - // db is the underlying database implementation. - db kvdb.Backend -} - -// A compile-time constraint to ensure KVStore implements DB. -var _ DB = (*KVStore)(nil) - -// NewKVStore creates a new KVStore for payments. -func NewKVStore(db kvdb.Backend, - options ...OptionModifier) (*KVStore, error) { - - opts := DefaultOptions() - for _, applyOption := range options { - applyOption(opts) - } - - if !opts.NoMigration { - if err := initKVStore(db); err != nil { - return nil, err - } - } - - return &KVStore{ - db: db, - }, nil -} - -// paymentsTopLevelBuckets is a list of top-level buckets that are used for -// the payments database when using the kv store. -var paymentsTopLevelBuckets = [][]byte{ - paymentsRootBucket, - paymentsIndexBucket, -} - -// initKVStore creates and initializes the top-level buckets for the payment db. -func initKVStore(db kvdb.Backend) error { - err := kvdb.Update(db, func(tx kvdb.RwTx) error { - for _, tlb := range paymentsTopLevelBuckets { - if _, err := tx.CreateTopLevelBucket(tlb); err != nil { - return err - } - } - - return nil - }, func() {}) - if err != nil { - return fmt.Errorf("unable to create new payments db: %w", err) - } - - return nil -} - -// InitPayment checks or records the given PaymentCreationInfo with the DB, -// making sure it does not already exist as an in-flight payment. When this -// method returns successfully, the payment is guaranteed to be in the InFlight -// state. -func (p *KVStore) InitPayment(_ context.Context, paymentHash lntypes.Hash, - info *PaymentCreationInfo) error { - - // Obtain a new sequence number for this payment. This is used - // to sort the payments in order of creation, and also acts as - // a unique identifier for each payment. - sequenceNum, err := p.nextPaymentSequence() - if err != nil { - return err - } - - var b bytes.Buffer - if err := serializePaymentCreationInfo(&b, info); err != nil { - return err - } - infoBytes := b.Bytes() - - var updateErr error - err = kvdb.Batch(p.db, func(tx kvdb.RwTx) error { - // Reset the update error, to avoid carrying over an error - // from a previous execution of the batched db transaction. - updateErr = nil - - prefetchPayment(tx, paymentHash) - bucket, err := createPaymentBucket(tx, paymentHash) - if err != nil { - return err - } - - // Get the existing status of this payment, if any. - paymentStatus, err := fetchPaymentStatus(bucket) - - switch { - // If no error is returned, it means we already have this - // payment. We'll check the status to decide whether we allow - // retrying the payment or return a specific error. - case err == nil: - if err := paymentStatus.initializable(); err != nil { - updateErr = err - return nil - } - - // Otherwise, if the error is not `ErrPaymentNotInitiated`, - // we'll return the error. - case !errors.Is(err, ErrPaymentNotInitiated): - return err - } - - // Before we set our new sequence number, we check whether this - // payment has a previously set sequence number and remove its - // index entry if it exists. This happens in the case where we - // have a previously attempted payment which was left in a state - // where we can retry. - seqBytes := bucket.Get(paymentSequenceKey) - if seqBytes != nil { - indexBucket := tx.ReadWriteBucket(paymentsIndexBucket) - if err := indexBucket.Delete(seqBytes); err != nil { - return err - } - } - - // Once we have obtained a sequence number, we add an entry - // to our index bucket which will map the sequence number to - // our payment identifier. - err = createPaymentIndexEntry( - tx, sequenceNum, info.PaymentIdentifier, - ) - if err != nil { - return err - } - - err = bucket.Put(paymentSequenceKey, sequenceNum) - if err != nil { - return err - } - - // Add the payment info to the bucket, which contains the - // static information for this payment - err = bucket.Put(paymentCreationInfoKey, infoBytes) - if err != nil { - return err - } - - // We'll delete any lingering HTLCs to start with, in case we - // are initializing a payment that was attempted earlier, but - // left in a state where we could retry. - err = bucket.DeleteNestedBucket(paymentHtlcsBucket) - if err != nil && !errors.Is(err, kvdb.ErrBucketNotFound) { - return err - } - - // Also delete any lingering failure info now that we are - // re-attempting. - return bucket.Delete(paymentFailInfoKey) - }) - if err != nil { - return fmt.Errorf("unable to init payment: %w", err) - } - - return updateErr -} - -// DeleteFailedAttempts deletes all failed htlcs for a payment. -func (p *KVStore) DeleteFailedAttempts(ctx context.Context, - hash lntypes.Hash) error { - - const failedHtlcsOnly = true - err := p.DeletePayment(ctx, hash, failedHtlcsOnly) - if err != nil { - return err - } - - return nil -} - -// paymentIndexTypeHash is a payment index type which indicates that we have -// created an index of payment sequence number to payment hash. -type paymentIndexType uint8 - -// paymentIndexTypeHash is a payment index type which indicates that we have -// created an index of payment sequence number to payment hash. -const paymentIndexTypeHash paymentIndexType = 0 - -// createPaymentIndexEntry creates a payment hash typed index for a payment. The -// index produced contains a payment index type (which can be used in future to -// signal different payment index types) and the payment identifier. -func createPaymentIndexEntry(tx kvdb.RwTx, sequenceNumber []byte, - id lntypes.Hash) error { - - var b bytes.Buffer - if err := WriteElements(&b, paymentIndexTypeHash, id[:]); err != nil { - return err - } - - indexes := tx.ReadWriteBucket(paymentsIndexBucket) - - return indexes.Put(sequenceNumber, b.Bytes()) -} - -// deserializePaymentIndex deserializes a payment index entry. This function -// currently only supports deserialization of payment hash indexes, and will -// fail for other types. -func deserializePaymentIndex(r io.Reader) (lntypes.Hash, error) { - var ( - indexType paymentIndexType - paymentHash []byte - ) - - if err := ReadElements(r, &indexType, &paymentHash); err != nil { - return lntypes.Hash{}, err - } - - // While we only have on payment index type, we do not need to use our - // index type to deserialize the index. However, we sanity check that - // this type is as expected, since we had to read it out anyway. - if indexType != paymentIndexTypeHash { - return lntypes.Hash{}, fmt.Errorf("unknown payment index "+ - "type: %v", indexType) - } - - hash, err := lntypes.MakeHash(paymentHash) - if err != nil { - return lntypes.Hash{}, err - } - - return hash, nil -} - -// RegisterAttempt atomically records the provided HTLCAttemptInfo to the -// DB. -func (p *KVStore) RegisterAttempt(_ context.Context, paymentHash lntypes.Hash, - attempt *HTLCAttemptInfo) (*MPPayment, error) { - - // Serialize the information before opening the db transaction. - var a bytes.Buffer - err := serializeHTLCAttemptInfo(&a, attempt) - if err != nil { - return nil, err - } - htlcInfoBytes := a.Bytes() - - htlcIDBytes := make([]byte, 8) - binary.BigEndian.PutUint64(htlcIDBytes, attempt.AttemptID) - - var payment *MPPayment - err = kvdb.Batch(p.db, func(tx kvdb.RwTx) error { - prefetchPayment(tx, paymentHash) - bucket, err := fetchPaymentBucketUpdate(tx, paymentHash) - if err != nil { - return err - } - - payment, err = fetchPayment(bucket) - if err != nil { - return err - } - - // Check if registering a new attempt is allowed. - if err := payment.Registrable(); err != nil { - return err - } - - // Verify the attempt is compatible with the existing payment. - if err := verifyAttempt(payment, attempt); err != nil { - return err - } - - htlcsBucket, err := bucket.CreateBucketIfNotExists( - paymentHtlcsBucket, - ) - if err != nil { - return err - } - - err = htlcsBucket.Put( - htlcBucketKey(htlcAttemptInfoKey, htlcIDBytes), - htlcInfoBytes, - ) - if err != nil { - return err - } - - // Retrieve attempt info for the notification. - payment, err = fetchPayment(bucket) - - return err - }) - if err != nil { - return nil, err - } - - return payment, err -} - -// SettleAttempt marks the given attempt settled with the preimage. If this is -// a multi shard payment, this might implicitly mean that the full payment -// succeeded. -// -// After invoking this method, InitPayment should always return an error to -// prevent us from making duplicate payments to the same payment hash. The -// provided preimage is atomically saved to the DB for record keeping. -func (p *KVStore) SettleAttempt(_ context.Context, hash lntypes.Hash, - attemptID uint64, settleInfo *HTLCSettleInfo) (*MPPayment, error) { - - var b bytes.Buffer - if err := serializeHTLCSettleInfo(&b, settleInfo); err != nil { - return nil, err - } - settleBytes := b.Bytes() - - return p.updateHtlcKey(hash, attemptID, htlcSettleInfoKey, settleBytes) -} - -// FailAttempt marks the given payment attempt failed. -func (p *KVStore) FailAttempt(_ context.Context, hash lntypes.Hash, - attemptID uint64, failInfo *HTLCFailInfo) (*MPPayment, error) { - - var b bytes.Buffer - if err := serializeHTLCFailInfo(&b, failInfo); err != nil { - return nil, err - } - failBytes := b.Bytes() - - return p.updateHtlcKey(hash, attemptID, htlcFailInfoKey, failBytes) -} - -// updateHtlcKey updates a database key for the specified htlc. -func (p *KVStore) updateHtlcKey(paymentHash lntypes.Hash, - attemptID uint64, key, value []byte) (*MPPayment, error) { - - aid := make([]byte, 8) - binary.BigEndian.PutUint64(aid, attemptID) - - var payment *MPPayment - err := kvdb.Batch(p.db, func(tx kvdb.RwTx) error { - payment = nil - - prefetchPayment(tx, paymentHash) - bucket, err := fetchPaymentBucketUpdate(tx, paymentHash) - if err != nil { - return err - } - - p, err := fetchPayment(bucket) - if err != nil { - return err - } - - // We can only update keys of in-flight payments. We allow - // updating keys even if the payment has reached a terminal - // condition, since the HTLC outcomes must still be updated. - if err := p.Status.updatable(); err != nil { - return err - } - - htlcsBucket := bucket.NestedReadWriteBucket(paymentHtlcsBucket) - if htlcsBucket == nil { - return fmt.Errorf("htlcs bucket not found") - } - - attemptKey := htlcBucketKey(htlcAttemptInfoKey, aid) - if htlcsBucket.Get(attemptKey) == nil { - return fmt.Errorf("HTLC with ID %v not registered", - attemptID) - } - - // Make sure the shard is not already failed or settled. - failKey := htlcBucketKey(htlcFailInfoKey, aid) - if htlcsBucket.Get(failKey) != nil { - return ErrAttemptAlreadyFailed - } - - settleKey := htlcBucketKey(htlcSettleInfoKey, aid) - if htlcsBucket.Get(settleKey) != nil { - return ErrAttemptAlreadySettled - } - - // Add or update the key for this htlc. - err = htlcsBucket.Put(htlcBucketKey(key, aid), value) - if err != nil { - return err - } - - // Retrieve attempt info for the notification. - payment, err = fetchPayment(bucket) - - return err - }) - if err != nil { - return nil, err - } - - return payment, err -} - -// Fail transitions a payment into the Failed state, and records the reason the -// payment failed. After invoking this method, InitPayment should return nil on -// its next call for this payment hash, allowing the switch to make a -// subsequent payment. -func (p *KVStore) Fail(_ context.Context, paymentHash lntypes.Hash, - reason FailureReason) (*MPPayment, error) { - - var ( - updateErr error - payment *MPPayment - ) - err := kvdb.Batch(p.db, func(tx kvdb.RwTx) error { - // Reset the update error, to avoid carrying over an error - // from a previous execution of the batched db transaction. - updateErr = nil - payment = nil - - prefetchPayment(tx, paymentHash) - bucket, err := fetchPaymentBucketUpdate(tx, paymentHash) - if errors.Is(err, ErrPaymentNotInitiated) { - updateErr = ErrPaymentNotInitiated - return nil - } else if err != nil { - return err - } - - // We mark the payment as failed as long as it is known. This - // lets the last attempt to fail with a terminal write its - // failure to the KVStore without synchronizing with - // other attempts. - _, err = fetchPaymentStatus(bucket) - if errors.Is(err, ErrPaymentNotInitiated) { - updateErr = ErrPaymentNotInitiated - return nil - } else if err != nil { - return err - } - - // Put the failure reason in the bucket for record keeping. - v := []byte{byte(reason)} - err = bucket.Put(paymentFailInfoKey, v) - if err != nil { - return err - } - - // Retrieve attempt info for the notification, if available. - payment, err = fetchPayment(bucket) - if err != nil { - return err - } - - return nil - }) - if err != nil { - return nil, err - } - - return payment, updateErr -} - -// FetchPayment returns information about a payment from the database. -func (p *KVStore) FetchPayment(_ context.Context, - paymentHash lntypes.Hash) (*MPPayment, error) { - - var payment *MPPayment - err := kvdb.View(p.db, func(tx kvdb.RTx) error { - prefetchPayment(tx, paymentHash) - bucket, err := fetchPaymentBucket(tx, paymentHash) - if err != nil { - return err - } - - payment, err = fetchPayment(bucket) - - return err - }, func() { - payment = nil - }) - if err != nil { - return nil, err - } - - return payment, nil -} - -// prefetchPayment attempts to prefetch as much of the payment as possible to -// reduce DB roundtrips. -func prefetchPayment(tx kvdb.RTx, paymentHash lntypes.Hash) { - rb := kvdb.RootBucket(tx) - kvdb.Prefetch( - rb, - []string{ - // Prefetch all keys in the payment's bucket. - string(paymentsRootBucket), - string(paymentHash[:]), - }, - []string{ - // Prefetch all keys in the payment's htlc bucket. - string(paymentsRootBucket), - string(paymentHash[:]), - string(paymentHtlcsBucket), - }, - ) -} - -// createPaymentBucket creates or fetches the sub-bucket assigned to this -// payment hash. -func createPaymentBucket(tx kvdb.RwTx, paymentHash lntypes.Hash) ( - kvdb.RwBucket, error) { - - payments, err := tx.CreateTopLevelBucket(paymentsRootBucket) - if err != nil { - return nil, err - } - - return payments.CreateBucketIfNotExists(paymentHash[:]) -} - -// fetchPaymentBucket fetches the sub-bucket assigned to this payment hash. If -// the bucket does not exist, it returns ErrPaymentNotInitiated. -func fetchPaymentBucket(tx kvdb.RTx, paymentHash lntypes.Hash) ( - kvdb.RBucket, error) { - - payments := tx.ReadBucket(paymentsRootBucket) - if payments == nil { - return nil, ErrPaymentNotInitiated - } - - bucket := payments.NestedReadBucket(paymentHash[:]) - if bucket == nil { - return nil, ErrPaymentNotInitiated - } - - return bucket, nil -} - -// fetchPaymentBucketUpdate is identical to fetchPaymentBucket, but it returns a -// bucket that can be written to. -func fetchPaymentBucketUpdate(tx kvdb.RwTx, paymentHash lntypes.Hash) ( - kvdb.RwBucket, error) { - - payments := tx.ReadWriteBucket(paymentsRootBucket) - if payments == nil { - return nil, ErrPaymentNotInitiated - } - - bucket := payments.NestedReadWriteBucket(paymentHash[:]) - if bucket == nil { - return nil, ErrPaymentNotInitiated - } - - return bucket, nil -} - -// nextPaymentSequence returns the next sequence number to store for a new -// payment. -func (p *KVStore) nextPaymentSequence() ([]byte, error) { - p.seqMu.Lock() - defer p.seqMu.Unlock() - - // Set a new upper bound in the DB every 1000 payments to avoid - // conflicts on the sequence when using etcd. - if p.currSeq == p.storedSeq { - var currPaymentSeq, newUpperBound uint64 - if err := kvdb.Update(p.db, func(tx kvdb.RwTx) error { - paymentsBucket, err := tx.CreateTopLevelBucket( - paymentsRootBucket, - ) - if err != nil { - return err - } - - currPaymentSeq = paymentsBucket.Sequence() - newUpperBound = currPaymentSeq + paymentSeqBlockSize - - return paymentsBucket.SetSequence(newUpperBound) - }, func() {}); err != nil { - return nil, err - } - - // We lazy initialize the cached currPaymentSeq here using the - // first nextPaymentSequence() call. This if statement will auto - // initialize our stored currPaymentSeq, since by default both - // this variable and storedPaymentSeq are zero which in turn - // will have us fetch the current values from the DB. - if p.currSeq == 0 { - p.currSeq = currPaymentSeq - } - - p.storedSeq = newUpperBound - } - - p.currSeq++ - b := make([]byte, 8) - binary.BigEndian.PutUint64(b, p.currSeq) - - return b, nil -} - -// fetchPaymentStatus fetches the payment status of the payment. If the payment -// isn't found, it will return error `ErrPaymentNotInitiated`. -func fetchPaymentStatus(bucket kvdb.RBucket) (PaymentStatus, error) { - // Creation info should be set for all payments, regardless of state. - // If not, it is unknown. - if bucket.Get(paymentCreationInfoKey) == nil { - return 0, ErrPaymentNotInitiated - } - - payment, err := fetchPayment(bucket) - if err != nil { - return 0, err - } - - return payment.Status, nil -} - -// FetchInFlightPayments returns all payments with status InFlight. -func (p *KVStore) FetchInFlightPayments(_ context.Context) ([]*MPPayment, - error) { - - var ( - inFlights []*MPPayment - start = time.Now() - lastLogTime = time.Now() - processedCount int - ) - - err := kvdb.View(p.db, func(tx kvdb.RTx) error { - payments := tx.ReadBucket(paymentsRootBucket) - if payments == nil { - return nil - } - - return payments.ForEach(func(k, _ []byte) error { - bucket := payments.NestedReadBucket(k) - if bucket == nil { - return fmt.Errorf("non bucket element") - } - - p, err := fetchPayment(bucket) - if err != nil { - return err - } - - processedCount++ - if time.Since(lastLogTime) >= - paymentProgressLogInterval { - - log.Debugf("Scanning inflight payments "+ - "(in progress), processed %d, last "+ - "processed payment: %v", processedCount, - p.Info) - - lastLogTime = time.Now() - } - - // Skip the payment if it's terminated. - if p.Terminated() { - return nil - } - - inFlights = append(inFlights, p) - - return nil - }) - }, func() { - inFlights = nil - }) - if err != nil { - return nil, err - } - - elapsed := time.Since(start) - log.Debugf("Completed scanning for inflight payments: "+ - "total_processed=%d, found_inflight=%d, elapsed=%v", - processedCount, len(inFlights), - elapsed.Round(time.Millisecond)) - - return inFlights, nil -} - -// htlcBucketKey creates a composite key from prefix and id where the result is -// simply the two concatenated. -func htlcBucketKey(prefix, id []byte) []byte { - key := make([]byte, len(prefix)+len(id)) - copy(key, prefix) - copy(key[len(prefix):], id) - - return key -} - -// FetchPayments returns all sent payments found in the DB. -func (p *KVStore) FetchPayments() ([]*MPPayment, error) { - var payments []*MPPayment - - err := kvdb.View(p.db, func(tx kvdb.RTx) error { - paymentsBucket := tx.ReadBucket(paymentsRootBucket) - if paymentsBucket == nil { - return nil - } - - return paymentsBucket.ForEach(func(k, v []byte) error { - bucket := paymentsBucket.NestedReadBucket(k) - if bucket == nil { - // We only expect sub-buckets to be found in - // this top-level bucket. - return fmt.Errorf("non bucket element in " + - "payments bucket") - } - - p, err := fetchPayment(bucket) - if err != nil { - return err - } - - payments = append(payments, p) - - // For older versions of lnd, duplicate payments to a - // payment has was possible. These will be found in a - // sub-bucket indexed by their sequence number if - // available. - duplicatePayments, err := fetchDuplicatePayments(bucket) - if err != nil { - return err - } - - payments = append(payments, duplicatePayments...) - - return nil - }) - }, func() { - payments = nil - }) - if err != nil { - return nil, err - } - - // Before returning, sort the payments by their sequence number. - sort.Slice(payments, func(i, j int) bool { - return payments[i].SequenceNum < payments[j].SequenceNum - }) - - return payments, nil -} - -func fetchCreationInfo(bucket kvdb.RBucket) (*PaymentCreationInfo, error) { - b := bucket.Get(paymentCreationInfoKey) - if b == nil { - return nil, fmt.Errorf("creation info not found") - } - - r := bytes.NewReader(b) - - return deserializePaymentCreationInfo(r) -} - -func fetchPayment(bucket kvdb.RBucket) (*MPPayment, error) { - seqBytes := bucket.Get(paymentSequenceKey) - if seqBytes == nil { - return nil, fmt.Errorf("sequence number not found") - } - - sequenceNum := binary.BigEndian.Uint64(seqBytes) - - // Get the PaymentCreationInfo. - creationInfo, err := fetchCreationInfo(bucket) - if err != nil { - return nil, err - } - - var htlcs []HTLCAttempt - htlcsBucket := bucket.NestedReadBucket(paymentHtlcsBucket) - if htlcsBucket != nil { - // Get the payment attempts. This can be empty. - htlcs, err = fetchHtlcAttempts(htlcsBucket) - if err != nil { - return nil, err - } - } - - // Get failure reason if available. - var failureReason *FailureReason - b := bucket.Get(paymentFailInfoKey) - if b != nil { - reason := FailureReason(b[0]) - failureReason = &reason - } - - // Create a new payment. - payment := &MPPayment{ - SequenceNum: sequenceNum, - Info: creationInfo, - HTLCs: htlcs, - FailureReason: failureReason, - } - - // Set its state and status. - if err := payment.setState(); err != nil { - return nil, err - } - - return payment, nil -} - -// fetchHtlcAttempts retrieves all htlc attempts made for the payment found in -// the given bucket. -func fetchHtlcAttempts(bucket kvdb.RBucket) ([]HTLCAttempt, error) { - htlcsMap := make(map[uint64]*HTLCAttempt) - - attemptInfoCount := 0 - err := bucket.ForEach(func(k, v []byte) error { - aid := byteOrder.Uint64(k[len(k)-8:]) - - if _, ok := htlcsMap[aid]; !ok { - htlcsMap[aid] = &HTLCAttempt{} - } - - var err error - switch { - case bytes.HasPrefix(k, htlcAttemptInfoKey): - attemptInfo, err := readHtlcAttemptInfo(v) - if err != nil { - return err - } - - attemptInfo.AttemptID = aid - htlcsMap[aid].HTLCAttemptInfo = *attemptInfo - attemptInfoCount++ - - case bytes.HasPrefix(k, htlcSettleInfoKey): - htlcsMap[aid].Settle, err = readHtlcSettleInfo(v) - if err != nil { - return err - } - - case bytes.HasPrefix(k, htlcFailInfoKey): - htlcsMap[aid].Failure, err = readHtlcFailInfo(v) - if err != nil { - return err - } - - default: - return fmt.Errorf("unknown htlc attempt key") - } - - return nil - }) - if err != nil { - return nil, err - } - - // Sanity check that all htlcs have an attempt info. - if attemptInfoCount != len(htlcsMap) { - return nil, ErrNoAttemptInfo - } - - keys := make([]uint64, len(htlcsMap)) - i := 0 - for k := range htlcsMap { - keys[i] = k - i++ - } - - // Sort HTLC attempts by their attempt ID. This is needed because in the - // DB we store the attempts with keys prefixed by their status which - // changes order (groups them together by status). - sort.Slice(keys, func(i, j int) bool { - return keys[i] < keys[j] - }) - - htlcs := make([]HTLCAttempt, len(htlcsMap)) - for i, key := range keys { - htlcs[i] = *htlcsMap[key] - } - - return htlcs, nil -} - -// readHtlcAttemptInfo reads the payment attempt info for this htlc. -func readHtlcAttemptInfo(b []byte) (*HTLCAttemptInfo, error) { - r := bytes.NewReader(b) - return deserializeHTLCAttemptInfo(r) -} - -// readHtlcSettleInfo reads the settle info for the htlc. If the htlc isn't -// settled, nil is returned. -func readHtlcSettleInfo(b []byte) (*HTLCSettleInfo, error) { - r := bytes.NewReader(b) - return deserializeHTLCSettleInfo(r) -} - -// readHtlcFailInfo reads the failure info for the htlc. If the htlc hasn't -// failed, nil is returned. -func readHtlcFailInfo(b []byte) (*HTLCFailInfo, error) { - r := bytes.NewReader(b) - return deserializeHTLCFailInfo(r) -} - -// fetchFailedHtlcKeys retrieves the bucket keys of all failed HTLCs of a -// payment bucket. -func fetchFailedHtlcKeys(bucket kvdb.RBucket) ([][]byte, error) { - htlcsBucket := bucket.NestedReadBucket(paymentHtlcsBucket) - - var htlcs []HTLCAttempt - var err error - if htlcsBucket != nil { - htlcs, err = fetchHtlcAttempts(htlcsBucket) - if err != nil { - return nil, err - } - } - - // Now iterate though them and save the bucket keys for the failed - // HTLCs. - var htlcKeys [][]byte - for _, h := range htlcs { - if h.Failure == nil { - continue - } - - htlcKeyBytes := make([]byte, 8) - binary.BigEndian.PutUint64(htlcKeyBytes, h.AttemptID) - - htlcKeys = append(htlcKeys, htlcKeyBytes) - } - - return htlcKeys, nil -} - -// QueryPayments is a query to the payments database which is restricted -// to a subset of payments by the payments query, containing an offset -// index and a maximum number of returned payments. -func (p *KVStore) QueryPayments(_ context.Context, - query Query) (Response, error) { - - var resp Response - - if err := kvdb.View(p.db, func(tx kvdb.RTx) error { - // Get the root payments bucket. - paymentsBucket := tx.ReadBucket(paymentsRootBucket) - if paymentsBucket == nil { - return nil - } - - // Get the index bucket which maps sequence number -> payment - // hash and duplicate bool. If we have a payments bucket, we - // should have an indexes bucket as well. - indexes := tx.ReadBucket(paymentsIndexBucket) - if indexes == nil { - return fmt.Errorf("index bucket does not exist") - } - - // accumulatePayments gets payments with the sequence number - // and hash provided and adds them to our list of payments if - // they meet the criteria of our query. It returns the number - // of payments that were added. - accumulatePayments := func(sequenceKey, hash []byte) (bool, - error) { - - r := bytes.NewReader(hash) - paymentHash, err := deserializePaymentIndex(r) - if err != nil { - return false, err - } - - payment, err := fetchPaymentWithSequenceNumber( - tx, paymentHash, sequenceKey, - ) - if err != nil { - return false, err - } - - // To keep compatibility with the old API, we only - // return non-succeeded payments if requested. - if payment.Status != StatusSucceeded && - !query.IncludeIncomplete { - - return false, err - } - - // Get the creation time in Unix seconds, this always - // rounds down the nanoseconds to full seconds. - createTime := payment.Info.CreationTime.Unix() - - // Skip any payments that were created before the - // specified time. - if createTime < query.CreationDateStart { - return false, nil - } - - // Skip any payments that were created after the - // specified time. - if query.CreationDateEnd != 0 && - createTime > query.CreationDateEnd { - - return false, nil - } - - // At this point, we've exhausted the offset, so we'll - // begin collecting invoices found within the range. - resp.Payments = append(resp.Payments, payment) - - return true, nil - } - - // Create a paginator which reads from our sequence index bucket - // with the parameters provided by the payments query. - paginator := channeldb.NewPaginator( - indexes.ReadCursor(), query.Reversed, query.IndexOffset, - query.MaxPayments, - ) - - // Run a paginated query, adding payments to our response. - if err := paginator.Query(accumulatePayments); err != nil { - return err - } - - // Counting the total number of payments is expensive, since we - // literally have to traverse the cursor linearly, which can - // take quite a while. So it's an optional query parameter. - if query.CountTotal { - var ( - totalPayments uint64 - err error - ) - countFn := func(_, _ []byte) error { - totalPayments++ - - return nil - } - - // In non-boltdb database backends, there's a faster - // ForAll query that allows for batch fetching items. - fastBucket, ok := indexes.(kvdb.ExtendedRBucket) - if ok { - err = fastBucket.ForAll(countFn) - } else { - err = indexes.ForEach(countFn) - } - if err != nil { - return fmt.Errorf("error counting payments: %w", - err) - } - - resp.TotalCount = totalPayments - } - - return nil - }, func() { - resp = Response{} - }); err != nil { - return resp, err - } - - // Need to swap the payments slice order if reversed order. - if query.Reversed { - for l, r := 0, len(resp.Payments)-1; l < r; l, r = l+1, r-1 { - resp.Payments[l], resp.Payments[r] = - resp.Payments[r], resp.Payments[l] - } - } - - // Set the first and last index of the returned payments so that the - // caller can resume from this point later on. - if len(resp.Payments) > 0 { - resp.FirstIndexOffset = resp.Payments[0].SequenceNum - resp.LastIndexOffset = - resp.Payments[len(resp.Payments)-1].SequenceNum - } - - return resp, nil -} - -// fetchPaymentWithSequenceNumber get the payment which matches the payment hash -// *and* sequence number provided from the database. This is required because -// we previously had more than one payment per hash, so we have multiple indexes -// pointing to a single payment; we want to retrieve the correct one. -func fetchPaymentWithSequenceNumber(tx kvdb.RTx, paymentHash lntypes.Hash, - sequenceNumber []byte) (*MPPayment, error) { - - // We can now lookup the payment keyed by its hash in - // the payments root bucket. - bucket, err := fetchPaymentBucket(tx, paymentHash) - if err != nil { - return nil, err - } - - // A single payment hash can have multiple payments associated with it. - // We lookup our sequence number first, to determine whether this is - // the payment we are actually looking for. - seqBytes := bucket.Get(paymentSequenceKey) - if seqBytes == nil { - return nil, ErrNoSequenceNumber - } - - // If this top level payment has the sequence number we are looking for, - // return it. - if bytes.Equal(seqBytes, sequenceNumber) { - return fetchPayment(bucket) - } - - // If we were not looking for the top level payment, we are looking for - // one of our duplicate payments. We need to iterate through the seq - // numbers in this bucket to find the correct payments. If we do not - // find a duplicate payments bucket here, something is wrong. - dup := bucket.NestedReadBucket(duplicatePaymentsBucket) - if dup == nil { - return nil, ErrNoDuplicateBucket - } - - var duplicatePayment *MPPayment - err = dup.ForEach(func(k, v []byte) error { - subBucket := dup.NestedReadBucket(k) - if subBucket == nil { - // We one bucket for each duplicate to be found. - return ErrNoDuplicateNestedBucket - } - - seqBytes := subBucket.Get(duplicatePaymentSequenceKey) - if seqBytes == nil { - return ErrNoDuplicateSequenceNumber - } - - // If this duplicate payment is not the sequence number we are - // looking for, we can continue. - if !bytes.Equal(seqBytes, sequenceNumber) { - return nil - } - - duplicatePayment, err = fetchDuplicatePayment(subBucket) - if err != nil { - return err - } - - return nil - }) - if err != nil { - return nil, err - } - - // If none of the duplicate payments matched our sequence number, we - // failed to find the payment with this sequence number; something is - // wrong. - if duplicatePayment == nil { - return nil, ErrDuplicateNotFound - } - - return duplicatePayment, nil -} - -// DeletePayment deletes a payment from the DB given its payment hash. If -// failedHtlcsOnly is set, only failed HTLC attempts of the payment will be -// deleted. -func (p *KVStore) DeletePayment(_ context.Context, paymentHash lntypes.Hash, - failedHtlcsOnly bool) error { - - return kvdb.Update(p.db, func(tx kvdb.RwTx) error { - payments := tx.ReadWriteBucket(paymentsRootBucket) - if payments == nil { - return nil - } - - bucket := payments.NestedReadWriteBucket(paymentHash[:]) - if bucket == nil { - return fmt.Errorf("non bucket element in payments " + - "bucket") - } - - // If the status is InFlight, we cannot safely delete - // the payment information, so we return early. - paymentStatus, err := fetchPaymentStatus(bucket) - if err != nil { - return err - } - - // If the payment has inflight HTLCs, we cannot safely delete - // the payment information, so we return an error. - if err := paymentStatus.removable(); err != nil { - return fmt.Errorf("payment '%v' has inflight HTLCs"+ - "and therefore cannot be deleted: %w", - paymentHash.String(), err) - } - - // Delete the failed HTLC attempts we found. - if failedHtlcsOnly { - toDelete, err := fetchFailedHtlcKeys(bucket) - if err != nil { - return err - } - - htlcsBucket := bucket.NestedReadWriteBucket( - paymentHtlcsBucket, - ) - - for _, htlcID := range toDelete { - err = htlcsBucket.Delete( - htlcBucketKey( - htlcAttemptInfoKey, htlcID, - ), - ) - if err != nil { - return err - } - - err = htlcsBucket.Delete( - htlcBucketKey(htlcFailInfoKey, htlcID), - ) - if err != nil { - return err - } - - err = htlcsBucket.Delete( - htlcBucketKey( - htlcSettleInfoKey, htlcID, - ), - ) - if err != nil { - return err - } - } - - return nil - } - - seqNrs, err := fetchSequenceNumbers(bucket) - if err != nil { - return err - } - - err = payments.DeleteNestedBucket(paymentHash[:]) - if err != nil { - return err - } - - indexBucket := tx.ReadWriteBucket(paymentsIndexBucket) - for _, k := range seqNrs { - if err := indexBucket.Delete(k); err != nil { - return err - } - } - - return nil - }, func() {}) -} - -// DeletePayments deletes all completed and failed payments from the DB. If -// failedOnly is set, only failed payments will be considered for deletion. If -// failedHtlcsOnly is set, the payment itself won't be deleted, only failed HTLC -// attempts. The method returns the number of deleted payments, which is always -// 0 if failedHtlcsOnly is set. -func (p *KVStore) DeletePayments(_ context.Context, failedOnly, - failedHtlcsOnly bool) (int, error) { - - var numPayments int - err := kvdb.Update(p.db, func(tx kvdb.RwTx) error { - payments := tx.ReadWriteBucket(paymentsRootBucket) - if payments == nil { - return nil - } - - var ( - // deleteBuckets is the set of payment buckets we need - // to delete. - deleteBuckets [][]byte - - // deleteIndexes is the set of indexes pointing to these - // payments that need to be deleted. - deleteIndexes [][]byte - - // deleteHtlcs maps a payment hash to the HTLC IDs we - // want to delete for that payment. - deleteHtlcs = make(map[lntypes.Hash][][]byte) - ) - err := payments.ForEach(func(k, _ []byte) error { - bucket := payments.NestedReadBucket(k) - if bucket == nil { - // We only expect sub-buckets to be found in - // this top-level bucket. - return fmt.Errorf("non bucket element in " + - "payments bucket") - } - - // If the status is InFlight, we cannot safely delete - // the payment information, so we return early. - paymentStatus, err := fetchPaymentStatus(bucket) - if err != nil { - return err - } - - // If the payment has inflight HTLCs, we cannot safely - // delete the payment information, so we return an nil - // to skip it. - if err := paymentStatus.removable(); err != nil { - return nil - } - - // If we requested to only delete failed payments, we - // can return if this one is not. - if failedOnly && paymentStatus != StatusFailed { - return nil - } - - // If we are only deleting failed HTLCs, fetch them. - if failedHtlcsOnly { - toDelete, err := fetchFailedHtlcKeys(bucket) - if err != nil { - return err - } - - hash, err := lntypes.MakeHash(k) - if err != nil { - return err - } - - deleteHtlcs[hash] = toDelete - - // We return, we are only deleting attempts. - return nil - } - - // Add the bucket to the set of buckets we can delete. - deleteBuckets = append(deleteBuckets, k) - - // Get all the sequence number associated with the - // payment, including duplicates. - seqNrs, err := fetchSequenceNumbers(bucket) - if err != nil { - return err - } - - deleteIndexes = append(deleteIndexes, seqNrs...) - numPayments++ - - return nil - }) - if err != nil { - return err - } - - // Delete the failed HTLC attempts we found. - for hash, htlcIDs := range deleteHtlcs { - bucket := payments.NestedReadWriteBucket(hash[:]) - htlcsBucket := bucket.NestedReadWriteBucket( - paymentHtlcsBucket, - ) - - for _, aid := range htlcIDs { - if err := htlcsBucket.Delete( - htlcBucketKey(htlcAttemptInfoKey, aid), - ); err != nil { - return err - } - - if err := htlcsBucket.Delete( - htlcBucketKey(htlcFailInfoKey, aid), - ); err != nil { - return err - } - - if err := htlcsBucket.Delete( - htlcBucketKey(htlcSettleInfoKey, aid), - ); err != nil { - return err - } - } - } - - for _, k := range deleteBuckets { - if err := payments.DeleteNestedBucket(k); err != nil { - return err - } - } - - // Get our index bucket and delete all indexes pointing to the - // payments we are deleting. - indexBucket := tx.ReadWriteBucket(paymentsIndexBucket) - for _, k := range deleteIndexes { - if err := indexBucket.Delete(k); err != nil { - return err - } - } - - return nil - }, func() { - numPayments = 0 - }) - if err != nil { - return 0, err - } - - return numPayments, nil -} - -// fetchSequenceNumbers fetches all the sequence numbers associated with a -// payment, including those belonging to any duplicate payments. -func fetchSequenceNumbers(paymentBucket kvdb.RBucket) ([][]byte, error) { - seqNum := paymentBucket.Get(paymentSequenceKey) - if seqNum == nil { - return nil, errors.New("expected sequence number") - } - - sequenceNumbers := [][]byte{seqNum} - - // Get the duplicate payments bucket, if it has no duplicates, just - // return early with the payment sequence number. - duplicates := paymentBucket.NestedReadBucket(duplicatePaymentsBucket) - if duplicates == nil { - return sequenceNumbers, nil - } - - // If we do have duplicated, they are keyed by sequence number, so we - // iterate through the duplicates bucket and add them to our set of - // sequence numbers. - if err := duplicates.ForEach(func(k, v []byte) error { - sequenceNumbers = append(sequenceNumbers, k) - return nil - }); err != nil { - return nil, err - } - - return sequenceNumbers, nil -} - -func serializePaymentCreationInfo(w io.Writer, c *PaymentCreationInfo) error { - var scratch [8]byte - - if _, err := w.Write(c.PaymentIdentifier[:]); err != nil { - return err - } - - byteOrder.PutUint64(scratch[:], uint64(c.Value)) - if _, err := w.Write(scratch[:]); err != nil { - return err - } - - if err := serializeTime(w, c.CreationTime); err != nil { - return err - } - - byteOrder.PutUint32(scratch[:4], uint32(len(c.PaymentRequest))) - if _, err := w.Write(scratch[:4]); err != nil { - return err - } - - if _, err := w.Write(c.PaymentRequest); err != nil { - return err - } - - // Any remaining bytes are TLV encoded records. Currently, these are - // only the custom records provided by the user to be sent to the first - // hop. But this can easily be extended with further records by merging - // the records into a single TLV stream. - err := c.FirstHopCustomRecords.SerializeTo(w) - if err != nil { - return err - } - - return nil -} - -func deserializePaymentCreationInfo(r io.Reader) (*PaymentCreationInfo, - error) { - - var scratch [8]byte - - c := &PaymentCreationInfo{} - - if _, err := io.ReadFull(r, c.PaymentIdentifier[:]); err != nil { - return nil, err - } - - if _, err := io.ReadFull(r, scratch[:]); err != nil { - return nil, err - } - c.Value = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:])) - - creationTime, err := deserializeTime(r) - if err != nil { - return nil, err - } - c.CreationTime = creationTime - - if _, err := io.ReadFull(r, scratch[:4]); err != nil { - return nil, err - } - - reqLen := byteOrder.Uint32(scratch[:4]) - payReq := make([]byte, reqLen) - if reqLen > 0 { - if _, err := io.ReadFull(r, payReq); err != nil { - return nil, err - } - } - c.PaymentRequest = payReq - - // Any remaining bytes are TLV encoded records. Currently, these are - // only the custom records provided by the user to be sent to the first - // hop. But this can easily be extended with further records by merging - // the records into a single TLV stream. - c.FirstHopCustomRecords, err = lnwire.ParseCustomRecordsFrom(r) - if err != nil { - return nil, err - } - - return c, nil -} - -func serializeHTLCAttemptInfo(w io.Writer, a *HTLCAttemptInfo) error { - if err := WriteElements(w, a.sessionKey); err != nil { - return err - } - - if err := SerializeRoute(w, a.Route); err != nil { - return err - } - - if err := serializeTime(w, a.AttemptTime); err != nil { - return err - } - - // If the hash is nil we can just return. - if a.Hash == nil { - return nil - } - - if _, err := w.Write(a.Hash[:]); err != nil { - return err - } - - // Merge the fixed/known records together with the custom records to - // serialize them as a single blob. We can't do this in SerializeRoute - // because we're in the middle of the byte stream there. We can only do - // TLV serialization at the end of the stream, since EOF is allowed for - // a stream if no more data is expected. - producers := []tlv.RecordProducer{ - &a.Route.FirstHopAmount, - } - tlvData, err := lnwire.MergeAndEncode( - producers, nil, a.Route.FirstHopWireCustomRecords, - ) - if err != nil { - return err - } - - if _, err := w.Write(tlvData); err != nil { - return err - } - - return nil -} - -func deserializeHTLCAttemptInfo(r io.Reader) (*HTLCAttemptInfo, error) { - a := &HTLCAttemptInfo{} - err := ReadElements(r, &a.sessionKey) - if err != nil { - return nil, err - } - - a.Route, err = DeserializeRoute(r) - if err != nil { - return nil, err - } - - a.AttemptTime, err = deserializeTime(r) - if err != nil { - return nil, err - } - - hash := lntypes.Hash{} - _, err = io.ReadFull(r, hash[:]) - - switch { - // Older payment attempts wouldn't have the hash set, in which case we - // can just return. - case errors.Is(err, io.EOF), errors.Is(err, io.ErrUnexpectedEOF): - return a, nil - - case err != nil: - return nil, err - - default: - } - - a.Hash = &hash - - // Read any remaining data (if any) and parse it into the known records - // and custom records. - extraData, err := io.ReadAll(r) - if err != nil { - return nil, err - } - - customRecords, _, _, err := lnwire.ParseAndExtractCustomRecords( - extraData, &a.Route.FirstHopAmount, - ) - if err != nil { - return nil, err - } - - a.Route.FirstHopWireCustomRecords = customRecords - - return a, nil -} - -func serializeHop(w io.Writer, h *Hop) error { - if err := WriteElements(w, - h.PubKeyBytes[:], - h.ChannelID, - h.OutgoingTimeLock, - h.AmtToForward, - ); err != nil { - return err - } - - if err := binary.Write(w, byteOrder, h.LegacyPayload); err != nil { - return err - } - - // For legacy payloads, we don't need to write any TLV records, so - // we'll write a zero indicating the our serialized TLV map has no - // records. - if h.LegacyPayload { - return WriteElements(w, uint32(0)) - } - - // Gather all non-primitive TLV records so that they can be serialized - // as a single blob. - // - // TODO(conner): add migration to unify all fields in a single TLV - // blobs. The split approach will cause headaches down the road as more - // fields are added, which we can avoid by having a single TLV stream - // for all payload fields. - var records []tlv.Record - if h.MPP != nil { - records = append(records, h.MPP.Record()) - } - - // Add blinding point and encrypted data if present. - if h.EncryptedData != nil { - records = append(records, record.NewEncryptedDataRecord( - &h.EncryptedData, - )) - } - - if h.BlindingPoint != nil { - records = append(records, record.NewBlindingPointRecord( - &h.BlindingPoint, - )) - } - - if h.AMP != nil { - records = append(records, h.AMP.Record()) - } - - if h.Metadata != nil { - records = append(records, record.NewMetadataRecord(&h.Metadata)) - } - - if h.TotalAmtMsat != 0 { - totalMsatInt := uint64(h.TotalAmtMsat) - records = append( - records, record.NewTotalAmtMsatBlinded(&totalMsatInt), - ) - } - - // Final sanity check to absolutely rule out custom records that are not - // custom and write into the standard range. - if err := h.CustomRecords.Validate(); err != nil { - return err - } - - // Convert custom records to tlv and add to the record list. - // MapToRecords sorts the list, so adding it here will keep the list - // canonical. - tlvRecords := tlv.MapToRecords(h.CustomRecords) - records = append(records, tlvRecords...) - - // Otherwise, we'll transform our slice of records into a map of the - // raw bytes, then serialize them in-line with a length (number of - // elements) prefix. - mapRecords, err := tlv.RecordsToMap(records) - if err != nil { - return err - } - - numRecords := uint32(len(mapRecords)) - if err := WriteElements(w, numRecords); err != nil { - return err - } - - for recordType, rawBytes := range mapRecords { - if err := WriteElements(w, recordType); err != nil { - return err - } - - if err := wire.WriteVarBytes(w, 0, rawBytes); err != nil { - return err - } - } - - return nil -} - -// maxOnionPayloadSize is the largest Sphinx payload possible, so we don't need -// to read/write a TLV stream larger than this. -const maxOnionPayloadSize = 1300 - -func deserializeHop(r io.Reader) (*Hop, error) { - h := &Hop{} - - var pub []byte - if err := ReadElements(r, &pub); err != nil { - return nil, err - } - copy(h.PubKeyBytes[:], pub) - - if err := ReadElements(r, - &h.ChannelID, &h.OutgoingTimeLock, &h.AmtToForward, - ); err != nil { - return nil, err - } - - // TODO(roasbeef): change field to allow LegacyPayload false to be the - // legacy default? - err := binary.Read(r, byteOrder, &h.LegacyPayload) - if err != nil { - return nil, err - } - - var numElements uint32 - if err := ReadElements(r, &numElements); err != nil { - return nil, err - } - - // If there're no elements, then we can return early. - if numElements == 0 { - return h, nil - } - - tlvMap := make(map[uint64][]byte) - for i := uint32(0); i < numElements; i++ { - var tlvType uint64 - if err := ReadElements(r, &tlvType); err != nil { - return nil, err - } - - rawRecordBytes, err := wire.ReadVarBytes( - r, 0, maxOnionPayloadSize, "tlv", - ) - if err != nil { - return nil, err - } - - tlvMap[tlvType] = rawRecordBytes - } - - // If the MPP type is present, remove it from the generic TLV map and - // parse it back into a proper MPP struct. - // - // TODO(conner): add migration to unify all fields in a single TLV - // blobs. The split approach will cause headaches down the road as more - // fields are added, which we can avoid by having a single TLV stream - // for all payload fields. - mppType := uint64(record.MPPOnionType) - if mppBytes, ok := tlvMap[mppType]; ok { - delete(tlvMap, mppType) - - var ( - mpp = &record.MPP{} - mppRec = mpp.Record() - r = bytes.NewReader(mppBytes) - ) - err := mppRec.Decode(r, uint64(len(mppBytes))) - if err != nil { - return nil, err - } - h.MPP = mpp - } - - // If encrypted data or blinding key are present, remove them from - // the TLV map and parse into proper types. - encryptedDataType := uint64(record.EncryptedDataOnionType) - if data, ok := tlvMap[encryptedDataType]; ok { - delete(tlvMap, encryptedDataType) - h.EncryptedData = data - } - - blindingType := uint64(record.BlindingPointOnionType) - if blindingPoint, ok := tlvMap[blindingType]; ok { - delete(tlvMap, blindingType) - - h.BlindingPoint, err = btcec.ParsePubKey(blindingPoint) - if err != nil { - return nil, fmt.Errorf("invalid blinding point: %w", - err) - } - } - - ampType := uint64(record.AMPOnionType) - if ampBytes, ok := tlvMap[ampType]; ok { - delete(tlvMap, ampType) - - var ( - amp = &record.AMP{} - ampRec = amp.Record() - r = bytes.NewReader(ampBytes) - ) - err := ampRec.Decode(r, uint64(len(ampBytes))) - if err != nil { - return nil, err - } - h.AMP = amp - } - - // If the metadata type is present, remove it from the tlv map and - // populate directly on the hop. - metadataType := uint64(record.MetadataOnionType) - if metadata, ok := tlvMap[metadataType]; ok { - delete(tlvMap, metadataType) - - h.Metadata = metadata - } - - totalAmtMsatType := uint64(record.TotalAmtMsatBlindedType) - if totalAmtMsat, ok := tlvMap[totalAmtMsatType]; ok { - delete(tlvMap, totalAmtMsatType) - - var ( - totalAmtMsatInt uint64 - buf [8]byte - ) - if err := tlv.DTUint64( - bytes.NewReader(totalAmtMsat), - &totalAmtMsatInt, - &buf, - uint64(len(totalAmtMsat)), - ); err != nil { - return nil, err - } - - h.TotalAmtMsat = lnwire.MilliSatoshi(totalAmtMsatInt) - } - - h.CustomRecords = tlvMap - - return h, nil -} - -// SerializeRoute serializes a route. -func SerializeRoute(w io.Writer, r Route) error { - if err := WriteElements(w, - r.TotalTimeLock, r.TotalAmount, r.SourcePubKey[:], - ); err != nil { - return err - } - - if err := WriteElements(w, uint32(len(r.Hops))); err != nil { - return err - } - - for _, h := range r.Hops { - if err := serializeHop(w, h); err != nil { - return err - } - } - - // Any new/extra TLV data is encoded in serializeHTLCAttemptInfo! - - return nil -} - -// DeserializeRoute deserializes a route. -func DeserializeRoute(r io.Reader) (Route, error) { - rt := Route{} - if err := ReadElements(r, - &rt.TotalTimeLock, &rt.TotalAmount, - ); err != nil { - return rt, err - } - - var pub []byte - if err := ReadElements(r, &pub); err != nil { - return rt, err - } - copy(rt.SourcePubKey[:], pub) - - var numHops uint32 - if err := ReadElements(r, &numHops); err != nil { - return rt, err - } - - var hops []*Hop - for i := uint32(0); i < numHops; i++ { - hop, err := deserializeHop(r) - if err != nil { - return rt, err - } - hops = append(hops, hop) - } - rt.Hops = hops - - // Any new/extra TLV data is decoded in deserializeHTLCAttemptInfo! - - return rt, nil -} - -// serializeHTLCSettleInfo serializes the details of a settled htlc. -func serializeHTLCSettleInfo(w io.Writer, s *HTLCSettleInfo) error { - if _, err := w.Write(s.Preimage[:]); err != nil { - return err - } - - if err := serializeTime(w, s.SettleTime); err != nil { - return err - } - - return nil -} - -// deserializeHTLCSettleInfo deserializes the details of a settled htlc. -func deserializeHTLCSettleInfo(r io.Reader) (*HTLCSettleInfo, error) { - s := &HTLCSettleInfo{} - if _, err := io.ReadFull(r, s.Preimage[:]); err != nil { - return nil, err - } - - var err error - s.SettleTime, err = deserializeTime(r) - if err != nil { - return nil, err - } - - return s, nil -} - -// serializeHTLCFailInfo serializes the details of a failed htlc including the -// wire failure. -func serializeHTLCFailInfo(w io.Writer, f *HTLCFailInfo) error { - if err := serializeTime(w, f.FailTime); err != nil { - return err - } - - // Write failure. If there is no failure message, write an empty - // byte slice. - var messageBytes bytes.Buffer - if f.Message != nil { - err := lnwire.EncodeFailureMessage(&messageBytes, f.Message, 0) - if err != nil { - return err - } - } - if err := wire.WriteVarBytes(w, 0, messageBytes.Bytes()); err != nil { - return err - } - - return WriteElements(w, byte(f.Reason), f.FailureSourceIndex) -} - -// deserializeHTLCFailInfo deserializes the details of a failed htlc including -// the wire failure. -func deserializeHTLCFailInfo(r io.Reader) (*HTLCFailInfo, error) { - f := &HTLCFailInfo{} - var err error - f.FailTime, err = deserializeTime(r) - if err != nil { - return nil, err - } - - // Read failure. - failureBytes, err := wire.ReadVarBytes( - r, 0, math.MaxUint16, "failure", - ) - if err != nil { - return nil, err - } - if len(failureBytes) > 0 { - f.Message, err = lnwire.DecodeFailureMessage( - bytes.NewReader(failureBytes), 0, - ) - if err != nil && - !errors.Is(err, lnwire.ErrParsingExtraTLVBytes) { - - return nil, err - } - - // In case we have an invalid TLV stream regarding the extra - // tlv data we still continue with the decoding of the - // HTLCFailInfo. - if errors.Is(err, lnwire.ErrParsingExtraTLVBytes) { - log.Warnf("Failed to decode extra TLV bytes for "+ - "failure message: %v", err) - } - } - - var reason byte - err = ReadElements(r, &reason, &f.FailureSourceIndex) - if err != nil { - return nil, err - } - f.Reason = HTLCFailReason(reason) - - return f, nil -} diff --git a/payments/db/migration1/lnwire/channel_id.go b/payments/db/migration1/lnwire/channel_id.go deleted file mode 100644 index 22f3de413..000000000 --- a/payments/db/migration1/lnwire/channel_id.go +++ /dev/null @@ -1,127 +0,0 @@ -package lnwire - -import ( - "encoding/binary" - "encoding/hex" - "io" - "math" - - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/tlv" -) - -const ( - // MaxFundingTxOutputs is the maximum number of allowed outputs on a - // funding transaction within the protocol. This is due to the fact - // that we use 2-bytes to encode the index within the funding output - // during the funding workflow. Funding transaction with more outputs - // than this are considered invalid within the protocol. - MaxFundingTxOutputs = math.MaxUint16 -) - -// ChannelID is a series of 32-bytes that uniquely identifies all channels -// within the network. The ChannelID is computed using the outpoint of the -// funding transaction (the txid, and output index). Given a funding output the -// ChannelID can be calculated by XOR'ing the big-endian serialization of the -// txid and the big-endian serialization of the output index, truncated to -// 2 bytes. -type ChannelID [32]byte - -// ConnectionWideID is an all-zero ChannelID, which is used to represent a -// message intended for all channels to specific peer. -var ConnectionWideID = ChannelID{} - -// String returns the string representation of the ChannelID. This is just the -// hex string encoding of the ChannelID itself. -func (c ChannelID) String() string { - return hex.EncodeToString(c[:]) -} - -// Record returns a TLV record that can be used to encode/decode a ChannelID -// to/from a TLV stream. -func (c *ChannelID) Record() tlv.Record { - return tlv.MakeStaticRecord(0, c, 32, encodeChannelID, decodeChannelID) -} - -func encodeChannelID(w io.Writer, val interface{}, buf *[8]byte) error { - if v, ok := val.(*ChannelID); ok { - bigSize := [32]byte(*v) - - return tlv.EBytes32(w, &bigSize, buf) - } - - return tlv.NewTypeForEncodingErr(val, "lnwire.ChannelID") -} - -func decodeChannelID(r io.Reader, val interface{}, buf *[8]byte, - l uint64) error { - - if v, ok := val.(*ChannelID); ok { - var id [32]byte - err := tlv.DBytes32(r, &id, buf, l) - if err != nil { - return err - } - - *v = id - - return nil - } - - return tlv.NewTypeForDecodingErr(val, "lnwire.ChannelID", l, l) -} - -// NewChanIDFromOutPoint converts a target OutPoint into a ChannelID that is -// usable within the network. In order to convert the OutPoint into a ChannelID, -// we XOR the lower 2-bytes of the txid within the OutPoint with the big-endian -// serialization of the Index of the OutPoint, truncated to 2-bytes. -func NewChanIDFromOutPoint(op wire.OutPoint) ChannelID { - // First we'll copy the txid of the outpoint into our channel ID slice. - var cid ChannelID - copy(cid[:], op.Hash[:]) - - // With the txid copied over, we'll now XOR the lower 2-bytes of the - // partial channelID with big-endian serialization of output index. - xorTxid(&cid, uint16(op.Index)) - - return cid -} - -// xorTxid performs the transformation needed to transform an OutPoint into a -// ChannelID. To do this, we expect the cid parameter to contain the txid -// unaltered and the outputIndex to be the output index -func xorTxid(cid *ChannelID, outputIndex uint16) { - var buf [2]byte - binary.BigEndian.PutUint16(buf[:], outputIndex) - - cid[30] ^= buf[0] - cid[31] ^= buf[1] -} - -// GenPossibleOutPoints generates all the possible outputs given a channel ID. -// In order to generate these possible outpoints, we perform a brute-force -// search through the candidate output index space, performing a reverse -// mapping from channelID back to OutPoint. -func (c *ChannelID) GenPossibleOutPoints() [MaxFundingTxOutputs]wire.OutPoint { - var possiblePoints [MaxFundingTxOutputs]wire.OutPoint - for i := uint16(0); i < MaxFundingTxOutputs; i++ { - cidCopy := *c - xorTxid(&cidCopy, i) - - possiblePoints[i] = wire.OutPoint{ - Hash: chainhash.Hash(cidCopy), - Index: uint32(i), - } - } - - return possiblePoints -} - -// IsChanPoint returns true if the OutPoint passed corresponds to the target -// ChannelID. -func (c ChannelID) IsChanPoint(op *wire.OutPoint) bool { - candidateCid := NewChanIDFromOutPoint(*op) - - return candidateCid == c -} diff --git a/payments/db/migration1/lnwire/channel_update.go b/payments/db/migration1/lnwire/channel_update.go deleted file mode 100644 index 73e8e3ad7..000000000 --- a/payments/db/migration1/lnwire/channel_update.go +++ /dev/null @@ -1,422 +0,0 @@ -package lnwire - -import ( - "bytes" - "fmt" - "io" - - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/lightningnetwork/lnd/tlv" -) - -// ChanUpdateMsgFlags is a bitfield that signals whether optional fields are -// present in the ChannelUpdate. -type ChanUpdateMsgFlags uint8 - -const ( - // ChanUpdateRequiredMaxHtlc is a bit that indicates whether the - // required htlc_maximum_msat field is present in this ChannelUpdate. - ChanUpdateRequiredMaxHtlc ChanUpdateMsgFlags = 1 << iota -) - -// String returns the bitfield flags as a string. -func (c ChanUpdateMsgFlags) String() string { - return fmt.Sprintf("%08b", c) -} - -// HasMaxHtlc returns true if the htlc_maximum_msat option bit is set in the -// message flags. -func (c ChanUpdateMsgFlags) HasMaxHtlc() bool { - return c&ChanUpdateRequiredMaxHtlc != 0 -} - -// ChanUpdateChanFlags is a bitfield that signals various options concerning a -// particular channel edge. Each bit is to be examined in order to determine -// how the ChannelUpdate message is to be interpreted. -type ChanUpdateChanFlags uint8 - -const ( - // ChanUpdateDirection indicates the direction of a channel update. If - // this bit is set to 0 if Node1 (the node with the "smaller" Node ID) - // is updating the channel, and to 1 otherwise. - ChanUpdateDirection ChanUpdateChanFlags = 1 << iota - - // ChanUpdateDisabled is a bit that indicates if the channel edge - // selected by the ChanUpdateDirection bit is to be treated as being - // disabled. - ChanUpdateDisabled -) - -// IsDisabled determines whether the channel flags has the disabled bit set. -func (c ChanUpdateChanFlags) IsDisabled() bool { - return c&ChanUpdateDisabled == ChanUpdateDisabled -} - -// String returns the bitfield flags as a string. -func (c ChanUpdateChanFlags) String() string { - return fmt.Sprintf("%08b", c) -} - -// ChannelUpdate1 message is used after channel has been initially announced. -// Each side independently announces its fees and minimum expiry for HTLCs and -// other parameters. Also this message is used to redeclare initially set -// channel parameters. -type ChannelUpdate1 struct { - // Signature is used to validate the announced data and prove the - // ownership of node id. - Signature Sig - - // ChainHash denotes the target chain that this channel was opened - // within. This value should be the genesis hash of the target chain. - // Along with the short channel ID, this uniquely identifies the - // channel globally in a blockchain. - ChainHash chainhash.Hash - - // ShortChannelID is the unique description of the funding transaction. - ShortChannelID ShortChannelID - - // Timestamp allows ordering in the case of multiple announcements. We - // should ignore the message if timestamp is not greater than - // the last-received. - Timestamp uint32 - - // MessageFlags is a bitfield that describes whether optional fields - // are present in this update. Currently, the least-significant bit - // must be set to 1 if the optional field MaxHtlc is present. - MessageFlags ChanUpdateMsgFlags - - // ChannelFlags is a bitfield that describes additional meta-data - // concerning how the update is to be interpreted. Currently, the - // least-significant bit must be set to 0 if the creating node - // corresponds to the first node in the previously sent channel - // announcement and 1 otherwise. If the second bit is set, then the - // channel is set to be disabled. - ChannelFlags ChanUpdateChanFlags - - // TimeLockDelta is the minimum number of blocks this node requires to - // be added to the expiry of HTLCs. This is a security parameter - // determined by the node operator. This value represents the required - // gap between the time locks of the incoming and outgoing HTLC's set - // to this node. - TimeLockDelta uint16 - - // HtlcMinimumMsat is the minimum HTLC value which will be accepted. - HtlcMinimumMsat MilliSatoshi - - // BaseFee is the base fee that must be used for incoming HTLC's to - // this particular channel. This value will be tacked onto the required - // for a payment independent of the size of the payment. - BaseFee uint32 - - // FeeRate is the fee rate that will be charged per millionth of a - // satoshi. - FeeRate uint32 - - // HtlcMaximumMsat is the maximum HTLC value which will be accepted. - HtlcMaximumMsat MilliSatoshi - - // InboundFee is an optional TLV record that contains the fee - // information for incoming HTLCs. - InboundFee tlv.OptionalRecordT[tlv.TlvType55555, Fee] - - // ExtraData is the set of data that was appended to this message to - // fill out the full maximum transport message size. These fields can - // be used to specify optional data such as custom TLV fields. - ExtraOpaqueData ExtraOpaqueData -} - -// A compile time check to ensure ChannelUpdate implements the lnwire.Message -// interface. -var _ Message = (*ChannelUpdate1)(nil) - -// A compile time check to ensure ChannelUpdate1 implements the -// lnwire.SizeableMessage interface. -var _ SizeableMessage = (*ChannelUpdate1)(nil) - -// Decode deserializes a serialized ChannelUpdate stored in the passed -// io.Reader observing the specified protocol version. -// -// This is part of the lnwire.Message interface. -func (a *ChannelUpdate1) Decode(r io.Reader, _ uint32) error { - err := ReadElements(r, - &a.Signature, - a.ChainHash[:], - &a.ShortChannelID, - &a.Timestamp, - &a.MessageFlags, - &a.ChannelFlags, - &a.TimeLockDelta, - &a.HtlcMinimumMsat, - &a.BaseFee, - &a.FeeRate, - ) - if err != nil { - return err - } - - // Now check whether the max HTLC field is present and read it if so. - if a.MessageFlags.HasMaxHtlc() { - if err := ReadElements(r, &a.HtlcMaximumMsat); err != nil { - return err - } - } - - var tlvRecords ExtraOpaqueData - if err := ReadElements(r, &tlvRecords); err != nil { - return err - } - - var inboundFee = a.InboundFee.Zero() - typeMap, err := tlvRecords.ExtractRecords(&inboundFee) - if err != nil { - return fmt.Errorf("%w: %w", ErrParsingExtraTLVBytes, err) - } - - val, ok := typeMap[a.InboundFee.TlvType()] - if ok && val == nil { - a.InboundFee = tlv.SomeRecordT(inboundFee) - } - - if len(tlvRecords) != 0 { - a.ExtraOpaqueData = tlvRecords - } - - return nil -} - -// Encode serializes the target ChannelUpdate into the passed io.Writer -// observing the protocol version specified. -// -// This is part of the lnwire.Message interface. -func (a *ChannelUpdate1) Encode(w *bytes.Buffer, pver uint32) error { - if err := WriteSig(w, a.Signature); err != nil { - return err - } - - if err := WriteBytes(w, a.ChainHash[:]); err != nil { - return err - } - - if err := WriteShortChannelID(w, a.ShortChannelID); err != nil { - return err - } - - if err := WriteUint32(w, a.Timestamp); err != nil { - return err - } - - if err := WriteChanUpdateMsgFlags(w, a.MessageFlags); err != nil { - return err - } - - if err := WriteChanUpdateChanFlags(w, a.ChannelFlags); err != nil { - return err - } - - if err := WriteUint16(w, a.TimeLockDelta); err != nil { - return err - } - - if err := WriteMilliSatoshi(w, a.HtlcMinimumMsat); err != nil { - return err - } - - if err := WriteUint32(w, a.BaseFee); err != nil { - return err - } - - if err := WriteUint32(w, a.FeeRate); err != nil { - return err - } - - // Now append optional fields if they are set. Currently, the only - // optional field is max HTLC. - if a.MessageFlags.HasMaxHtlc() { - err := WriteMilliSatoshi(w, a.HtlcMaximumMsat) - if err != nil { - return err - } - } - - recordProducers := make([]tlv.RecordProducer, 0, 1) - a.InboundFee.WhenSome(func(fee tlv.RecordT[tlv.TlvType55555, Fee]) { - recordProducers = append(recordProducers, &fee) - }) - - err := EncodeMessageExtraData(&a.ExtraOpaqueData, recordProducers...) - if err != nil { - return err - } - - // Finally, append any extra opaque data. - return WriteBytes(w, a.ExtraOpaqueData) -} - -// MsgType returns the integer uniquely identifying this message type on the -// wire. -// -// This is part of the lnwire.Message interface. -func (a *ChannelUpdate1) MsgType() MessageType { - return MsgChannelUpdate -} - -// DataToSign is used to retrieve part of the announcement message which should -// be signed. -func (a *ChannelUpdate1) DataToSign() ([]byte, error) { - // We should not include the signatures itself. - b := make([]byte, 0, MaxMsgBody) - buf := bytes.NewBuffer(b) - if err := WriteBytes(buf, a.ChainHash[:]); err != nil { - return nil, err - } - - if err := WriteShortChannelID(buf, a.ShortChannelID); err != nil { - return nil, err - } - - if err := WriteUint32(buf, a.Timestamp); err != nil { - return nil, err - } - - if err := WriteChanUpdateMsgFlags(buf, a.MessageFlags); err != nil { - return nil, err - } - - if err := WriteChanUpdateChanFlags(buf, a.ChannelFlags); err != nil { - return nil, err - } - - if err := WriteUint16(buf, a.TimeLockDelta); err != nil { - return nil, err - } - - if err := WriteMilliSatoshi(buf, a.HtlcMinimumMsat); err != nil { - return nil, err - } - - if err := WriteUint32(buf, a.BaseFee); err != nil { - return nil, err - } - - if err := WriteUint32(buf, a.FeeRate); err != nil { - return nil, err - } - - // Now append optional fields if they are set. Currently, the only - // optional field is max HTLC. - if a.MessageFlags.HasMaxHtlc() { - err := WriteMilliSatoshi(buf, a.HtlcMaximumMsat) - if err != nil { - return nil, err - } - } - - // Finally, append any extra opaque data. - if err := WriteBytes(buf, a.ExtraOpaqueData); err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -// SCID returns the ShortChannelID of the channel that the update applies to. -// -// NOTE: this is part of the ChannelUpdate interface. -func (a *ChannelUpdate1) SCID() ShortChannelID { - return a.ShortChannelID -} - -// IsNode1 is true if the update was produced by node 1 of the channel peers. -// Node 1 is the node with the lexicographically smaller public key. -// -// NOTE: this is part of the ChannelUpdate interface. -func (a *ChannelUpdate1) IsNode1() bool { - return a.ChannelFlags&ChanUpdateDirection == 0 -} - -// IsDisabled is true if the update is announcing that the channel should be -// considered disabled. -// -// NOTE: this is part of the ChannelUpdate interface. -func (a *ChannelUpdate1) IsDisabled() bool { - return a.ChannelFlags&ChanUpdateDisabled == ChanUpdateDisabled -} - -// GetChainHash returns the hash of the chain that the message is referring to. -// -// NOTE: this is part of the ChannelUpdate interface. -func (a *ChannelUpdate1) GetChainHash() chainhash.Hash { - return a.ChainHash -} - -// ForwardingPolicy returns the set of forwarding constraints of the update. -// -// NOTE: this is part of the ChannelUpdate interface. -func (a *ChannelUpdate1) ForwardingPolicy() *ForwardingPolicy { - return &ForwardingPolicy{ - TimeLockDelta: a.TimeLockDelta, - BaseFee: MilliSatoshi(a.BaseFee), - FeeRate: MilliSatoshi(a.FeeRate), - MinHTLC: a.HtlcMinimumMsat, - HasMaxHTLC: a.MessageFlags.HasMaxHtlc(), - MaxHTLC: a.HtlcMaximumMsat, - } -} - -// GossipVersion returns the gossip version that this message is part of. -// -// NOTE: this is part of the GossipMessage interface. -func (a *ChannelUpdate1) GossipVersion() GossipVersion { - return GossipVersion1 -} - -// CmpAge can be used to determine if the update is older or newer than the -// passed update. It returns 1 if this update is newer, -1 if it is older, and -// 0 if they are the same age. -// -// NOTE: this is part of the ChannelUpdate interface. -func (a *ChannelUpdate1) CmpAge(update ChannelUpdate) (CompareResult, error) { - other, ok := update.(*ChannelUpdate1) - if !ok { - return 0, fmt.Errorf("expected *ChannelUpdate1, got: %T", - update) - } - - switch { - case a.Timestamp > other.Timestamp: - return GreaterThan, nil - case a.Timestamp < other.Timestamp: - return LessThan, nil - default: - return EqualTo, nil - } -} - -// SetDisabledFlag can be used to adjust the disabled flag of an update. -// -// NOTE: this is part of the ChannelUpdate interface. -func (a *ChannelUpdate1) SetDisabledFlag(disabled bool) { - if disabled { - a.ChannelFlags |= ChanUpdateDisabled - } else { - a.ChannelFlags &= ^ChanUpdateDisabled - } -} - -// SetSCID can be used to overwrite the SCID of the update. -// -// NOTE: this is part of the ChannelUpdate interface. -func (a *ChannelUpdate1) SetSCID(scid ShortChannelID) { - a.ShortChannelID = scid -} - -// A compile time assertion to ensure ChannelUpdate1 implements the -// ChannelUpdate interface. -var _ ChannelUpdate = (*ChannelUpdate1)(nil) - -// SerializedSize returns the serialized size of the message in bytes. -// -// This is part of the lnwire.SizeableMessage interface. -func (a *ChannelUpdate1) SerializedSize() (uint32, error) { - return MessageSerializedSize(a) -} diff --git a/payments/db/migration1/lnwire/custom_records.go b/payments/db/migration1/lnwire/custom_records.go deleted file mode 100644 index de5ff4a23..000000000 --- a/payments/db/migration1/lnwire/custom_records.go +++ /dev/null @@ -1,278 +0,0 @@ -package lnwire - -import ( - "bytes" - "fmt" - "io" - "maps" - "sort" - - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/tlv" -) - -const ( - // MinCustomRecordsTlvType is the minimum custom records TLV type as - // defined in BOLT 01. - MinCustomRecordsTlvType = 65536 -) - -// CustomRecords stores a set of custom key/value pairs. Map keys are TLV types -// which must be greater than or equal to MinCustomRecordsTlvType. -type CustomRecords map[uint64][]byte - -// NewCustomRecords creates a new CustomRecords instance from a -// tlv.TypeMap. -func NewCustomRecords(tlvMap tlv.TypeMap) (CustomRecords, error) { - // Make comparisons in unit tests easy by returning nil if the map is - // empty. - if len(tlvMap) == 0 { - return nil, nil - } - - customRecords := make(CustomRecords, len(tlvMap)) - for k, v := range tlvMap { - customRecords[uint64(k)] = v - } - - // Validate the custom records. - err := customRecords.Validate() - if err != nil { - return nil, fmt.Errorf("custom records from tlv map "+ - "validation error: %w", err) - } - - return customRecords, nil -} - -// ParseCustomRecords creates a new CustomRecords instance from a tlv.Blob. -func ParseCustomRecords(b tlv.Blob) (CustomRecords, error) { - return ParseCustomRecordsFrom(bytes.NewReader(b)) -} - -// ParseCustomRecordsFrom creates a new CustomRecords instance from a reader. -func ParseCustomRecordsFrom(r io.Reader) (CustomRecords, error) { - typeMap, err := DecodeRecords(r) - if err != nil { - return nil, fmt.Errorf("error decoding HTLC record: %w", err) - } - - return NewCustomRecords(typeMap) -} - -// Validate checks that all custom records are in the custom type range. -func (c CustomRecords) Validate() error { - if c == nil { - return nil - } - - for key := range c { - if key < MinCustomRecordsTlvType { - return fmt.Errorf("custom records entry with TLV "+ - "type below min: %d", MinCustomRecordsTlvType) - } - } - - return nil -} - -// Copy returns a copy of the custom records. -func (c CustomRecords) Copy() CustomRecords { - if c == nil { - return nil - } - - customRecords := make(CustomRecords, len(c)) - for k, v := range c { - customRecords[k] = v - } - - return customRecords -} - -// MergedCopy creates a copy of the records and merges them with the given -// records. If the same key is present in both sets, the value from the other -// records will be used. -func (c CustomRecords) MergedCopy(other CustomRecords) CustomRecords { - copiedRecords := make(CustomRecords, len(c)) - maps.Copy(copiedRecords, c) - maps.Copy(copiedRecords, other) - - return copiedRecords -} - -// ExtendRecordProducers extends the given records slice with the custom -// records. The resultant records slice will be sorted if the given records -// slice contains TLV types greater than or equal to MinCustomRecordsTlvType. -func (c CustomRecords) ExtendRecordProducers( - producers []tlv.RecordProducer) ([]tlv.RecordProducer, error) { - - // If the custom records are nil or empty, there is nothing to do. - if len(c) == 0 { - return producers, nil - } - - // Validate the custom records. - err := c.Validate() - if err != nil { - return nil, err - } - - // Ensure that the existing records slice TLV types are not also present - // in the custom records. If they are, the resultant extended records - // slice would erroneously contain duplicate TLV types. - for _, rp := range producers { - record := rp.Record() - recordTlvType := uint64(record.Type()) - - _, foundDuplicateTlvType := c[recordTlvType] - if foundDuplicateTlvType { - return nil, fmt.Errorf("custom records contains a TLV "+ - "type that is already present in the "+ - "existing records: %d", recordTlvType) - } - } - - // Convert the custom records map to a TLV record producer slice and - // append them to the exiting records slice. - customRecordProducers := RecordsAsProducers(tlv.MapToRecords(c)) - producers = append(producers, customRecordProducers...) - - // If the records slice which was given as an argument included TLV - // values greater than or equal to the minimum custom records TLV type - // we will sort the extended records slice to ensure that it is ordered - // correctly. - SortProducers(producers) - - return producers, nil -} - -// RecordProducers returns a slice of record producers for the custom records. -func (c CustomRecords) RecordProducers() []tlv.RecordProducer { - // If the custom records are nil or empty, return an empty slice. - if len(c) == 0 { - return nil - } - - // Convert the custom records map to a TLV record producer slice. - records := tlv.MapToRecords(c) - - return RecordsAsProducers(records) -} - -// Serialize serializes the custom records into a byte slice. -func (c CustomRecords) Serialize() ([]byte, error) { - records := tlv.MapToRecords(c) - return EncodeRecords(records) -} - -// SerializeTo serializes the custom records into the given writer. -func (c CustomRecords) SerializeTo(w io.Writer) error { - records := tlv.MapToRecords(c) - return EncodeRecordsTo(w, records) -} - -// ProduceRecordsSorted converts a slice of record producers into a slice of -// records and then sorts it by type. -func ProduceRecordsSorted(recordProducers ...tlv.RecordProducer) []tlv.Record { - records := fn.Map( - recordProducers, - func(producer tlv.RecordProducer) tlv.Record { - return producer.Record() - }, - ) - - // Ensure that the set of records are sorted before we attempt to - // decode from the stream, to ensure they're canonical. - tlv.SortRecords(records) - - return records -} - -// SortProducers sorts the given record producers by their type. -func SortProducers(producers []tlv.RecordProducer) { - sort.Slice(producers, func(i, j int) bool { - recordI := producers[i].Record() - recordJ := producers[j].Record() - return recordI.Type() < recordJ.Type() - }) -} - -// TlvMapToRecords converts a TLV map into a slice of records. -func TlvMapToRecords(tlvMap tlv.TypeMap) []tlv.Record { - tlvMapGeneric := make(map[uint64][]byte) - for k, v := range tlvMap { - tlvMapGeneric[uint64(k)] = v - } - - return tlv.MapToRecords(tlvMapGeneric) -} - -// RecordsAsProducers converts a slice of records into a slice of record -// producers. -func RecordsAsProducers(records []tlv.Record) []tlv.RecordProducer { - return fn.Map(records, func(record tlv.Record) tlv.RecordProducer { - return &record - }) -} - -// EncodeRecords encodes the given records into a byte slice. -func EncodeRecords(records []tlv.Record) ([]byte, error) { - var buf bytes.Buffer - if err := EncodeRecordsTo(&buf, records); err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -// EncodeRecordsTo encodes the given records into the given writer. -func EncodeRecordsTo(w io.Writer, records []tlv.Record) error { - tlvStream, err := tlv.NewStream(records...) - if err != nil { - return err - } - - return tlvStream.Encode(w) -} - -// DecodeRecords decodes the given byte slice into the given records and returns -// the rest as a TLV type map. -func DecodeRecords(r io.Reader, - records ...tlv.Record) (tlv.TypeMap, error) { - - tlvStream, err := tlv.NewStream(records...) - if err != nil { - return nil, err - } - - return tlvStream.DecodeWithParsedTypes(r) -} - -// DecodeRecordsP2P decodes the given byte slice into the given records and -// returns the rest as a TLV type map. This function is identical to -// DecodeRecords except that the record size is capped at 65535. -func DecodeRecordsP2P(r *bytes.Reader, - records ...tlv.Record) (tlv.TypeMap, error) { - - tlvStream, err := tlv.NewStream(records...) - if err != nil { - return nil, err - } - - return tlvStream.DecodeWithParsedTypesP2P(r) -} - -// AssertUniqueTypes asserts that the given records have unique types. -func AssertUniqueTypes(r []tlv.Record) error { - seen := make(fn.Set[tlv.Type], len(r)) - for _, record := range r { - t := record.Type() - if seen.Contains(t) { - return fmt.Errorf("duplicate record type: %d", t) - } - seen.Add(t) - } - - return nil -} diff --git a/payments/db/migration1/lnwire/error.go b/payments/db/migration1/lnwire/error.go deleted file mode 100644 index 382411999..000000000 --- a/payments/db/migration1/lnwire/error.go +++ /dev/null @@ -1,143 +0,0 @@ -package lnwire - -import ( - "bytes" - "fmt" - "io" -) - -var ( - // ErrParsingExtraTLVBytes is returned when we attempt to parse - // extra opaque bytes as a TLV stream, but the parsing fails due to - // and invalid TLV stream. - ErrParsingExtraTLVBytes = fmt.Errorf("error parsing extra TLV bytes") -) - -// FundingError represents a set of errors that can be encountered and sent -// during the funding workflow. -type FundingError uint8 - -const ( - // ErrMaxPendingChannels is returned by remote peer when the number of - // active pending channels exceeds their maximum policy limit. - ErrMaxPendingChannels FundingError = 1 - - // ErrChanTooLarge is returned by a remote peer that receives a - // FundingOpen request for a channel that is above their current - // soft-limit. - ErrChanTooLarge FundingError = 2 -) - -// String returns a human readable version of the target FundingError. -func (e FundingError) String() string { - switch e { - case ErrMaxPendingChannels: - return "Number of pending channels exceed maximum" - case ErrChanTooLarge: - return "channel too large" - default: - return "unknown error" - } -} - -// Error returns the human readable version of the target FundingError. -// -// NOTE: Satisfies the Error interface. -func (e FundingError) Error() string { - return e.String() -} - -// ErrorData is a set of bytes associated with a particular sent error. A -// receiving node SHOULD only print out data verbatim if the string is composed -// solely of printable ASCII characters. For reference, the printable character -// set includes byte values 32 through 127 inclusive. -type ErrorData []byte - -// Error represents a generic error bound to an exact channel. The message -// format is purposefully general in order to allow expression of a wide array -// of possible errors. Each Error message is directed at a particular open -// channel referenced by ChannelPoint. -type Error struct { - // ChanID references the active channel in which the error occurred - // within. If the ChanID is all zeros, then this error applies to the - // entire established connection. - ChanID ChannelID - - // Data is the attached error data that describes the exact failure - // which caused the error message to be sent. - Data ErrorData -} - -// NewError creates a new Error message. -func NewError() *Error { - return &Error{} -} - -// A compile time check to ensure Error implements the lnwire.Message -// interface. -var _ Message = (*Error)(nil) - -// A compile time check to ensure Error implements the lnwire.SizeableMessage -// interface. -var _ SizeableMessage = (*Error)(nil) - -// Error returns the string representation to Error. -// -// NOTE: Satisfies the error interface. -func (c *Error) Error() string { - errMsg := "non-ascii data" - if isASCII(c.Data) { - errMsg = string(c.Data) - } - - return fmt.Sprintf("chan_id=%v, err=%v", c.ChanID, errMsg) -} - -// Decode deserializes a serialized Error message stored in the passed -// io.Reader observing the specified protocol version. -// -// This is part of the lnwire.Message interface. -func (c *Error) Decode(r io.Reader, pver uint32) error { - return ReadElements(r, - &c.ChanID, - &c.Data, - ) -} - -// Encode serializes the target Error into the passed io.Writer observing the -// protocol version specified. -// -// This is part of the lnwire.Message interface. -func (c *Error) Encode(w *bytes.Buffer, pver uint32) error { - if err := WriteBytes(w, c.ChanID[:]); err != nil { - return err - } - - return WriteErrorData(w, c.Data) -} - -// MsgType returns the integer uniquely identifying an Error message on the -// wire. -// -// This is part of the lnwire.Message interface. -func (c *Error) MsgType() MessageType { - return MsgError -} - -// SerializedSize returns the serialized size of the message in bytes. -// -// This is part of the lnwire.SizeableMessage interface. -func (c *Error) SerializedSize() (uint32, error) { - return MessageSerializedSize(c) -} - -// isASCII is a helper method that checks whether all bytes in `data` would be -// printable ASCII characters if interpreted as a string. -func isASCII(data []byte) bool { - for _, c := range data { - if c < 32 || c > 126 { - return false - } - } - return true -} diff --git a/payments/db/migration1/lnwire/extra_bytes.go b/payments/db/migration1/lnwire/extra_bytes.go deleted file mode 100644 index 39228c101..000000000 --- a/payments/db/migration1/lnwire/extra_bytes.go +++ /dev/null @@ -1,309 +0,0 @@ -package lnwire - -import ( - "bytes" - "fmt" - "io" - - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/tlv" -) - -// ExtraOpaqueData is the set of data that was appended to this message, some -// of which we may not actually know how to iterate or parse. By holding onto -// this data, we ensure that we're able to properly validate the set of -// signatures that cover these new fields, and ensure we're able to make -// upgrades to the network in a forwards compatible manner. -type ExtraOpaqueData []byte - -// NewExtraOpaqueData creates a new ExtraOpaqueData instance from a tlv.TypeMap. -func NewExtraOpaqueData(tlvMap tlv.TypeMap) (ExtraOpaqueData, error) { - // If the tlv map is empty, we'll want to mirror the behavior of - // decoding an empty extra opaque data field (see Decode method). - if len(tlvMap) == 0 { - return make([]byte, 0), nil - } - - // Convert the TLV map into a slice of records. - records := TlvMapToRecords(tlvMap) - - // Encode the records into the extra data byte slice. - return EncodeRecords(records) -} - -// Encode attempts to encode the raw extra bytes into the passed io.Writer. -func (e *ExtraOpaqueData) Encode(w *bytes.Buffer) error { - eBytes := []byte((*e)[:]) - if err := WriteBytes(w, eBytes); err != nil { - return err - } - - return nil -} - -// Decode attempts to unpack the raw bytes encoded in the passed-in io.Reader as -// a set of extra opaque data. -func (e *ExtraOpaqueData) Decode(r io.Reader) error { - // First, we'll attempt to read a set of bytes contained within the - // passed io.Reader (if any exist). - rawBytes, err := io.ReadAll(r) - if err != nil { - return err - } - - // If we _do_ have some bytes, then we'll swap out our backing pointer. - // This ensures that any struct that embeds this type will properly - // store the bytes once this method exits. - if len(rawBytes) > 0 { - *e = rawBytes - } else { - *e = make([]byte, 0) - } - - return nil -} - -// ValidateTLV checks that the raw bytes that make up the ExtraOpaqueData -// instance are a valid TLV stream. -func (e *ExtraOpaqueData) ValidateTLV() error { - // There is nothing to validate if the ExtraOpaqueData is nil or empty. - if e == nil || len(*e) == 0 { - return nil - } - - tlvStream, err := tlv.NewStream() - if err != nil { - return err - } - - // Ensure that the TLV stream is valid by attempting to decode it. - _, err = tlvStream.DecodeWithParsedTypesP2P(bytes.NewReader(*e)) - if err != nil { - return fmt.Errorf("invalid TLV stream: %w: %v", err, *e) - } - - return nil -} - -// PackRecords attempts to encode the set of tlv records into the target -// ExtraOpaqueData instance. The records will be encoded as a raw TLV stream -// and stored within the backing slice pointer. -func (e *ExtraOpaqueData) PackRecords( - recordProducers ...tlv.RecordProducer) error { - - // Assemble all the records passed in series, then encode them. - records := ProduceRecordsSorted(recordProducers...) - encoded, err := EncodeRecords(records) - if err != nil { - return err - } - - *e = encoded - - return nil -} - -// ExtractRecords attempts to decode any types in the internal raw bytes as if -// it were a tlv stream. The set of raw parsed types is returned, and any -// passed records (if found in the stream) will be parsed into the proper -// tlv.Record. -func (e *ExtraOpaqueData) ExtractRecords( - recordProducers ...tlv.RecordProducer) (tlv.TypeMap, error) { - - // First, assemble all the records passed in series. - records := ProduceRecordsSorted(recordProducers...) - extraBytesReader := bytes.NewReader(*e) - - // Since ExtraOpaqueData is provided by a potentially malicious peer, - // pass it into the P2P decoding variant. - return DecodeRecordsP2P(extraBytesReader, records...) -} - -// RecordProducers parses ExtraOpaqueData into a slice of TLV record producers -// by interpreting it as a TLV map. -func (e *ExtraOpaqueData) RecordProducers() ([]tlv.RecordProducer, error) { - var recordProducers []tlv.RecordProducer - - // If the instance is nil or empty, return an empty slice. - if e == nil || len(*e) == 0 { - return recordProducers, nil - } - - // Parse the extra opaque data as a TLV map. - tlvMap, err := e.ExtractRecords() - if err != nil { - return nil, err - } - - // Convert the TLV map into a slice of record producers. - records := TlvMapToRecords(tlvMap) - - return RecordsAsProducers(records), nil -} - -// EncodeMessageExtraData encodes the given recordProducers into the given -// extraData. -func EncodeMessageExtraData(extraData *ExtraOpaqueData, - recordProducers ...tlv.RecordProducer) error { - - // Treat extraData as a mutable reference. - if extraData == nil { - return fmt.Errorf("extra data cannot be nil") - } - - // Pack in the series of TLV records into this message. The order we - // pass them in doesn't matter, as the method will ensure that things - // are all properly sorted. - return extraData.PackRecords(recordProducers...) -} - -// ParseAndExtractCustomRecords parses the given extra data into the passed-in -// records, then returns any remaining records split into custom records and -// extra data. -func ParseAndExtractCustomRecords(allExtraData ExtraOpaqueData, - knownRecords ...tlv.RecordProducer) (CustomRecords, - fn.Set[tlv.Type], ExtraOpaqueData, error) { - - extraDataTlvMap, err := allExtraData.ExtractRecords(knownRecords...) - if err != nil { - return nil, nil, nil, err - } - - // Remove the known and now extracted records from the leftover extra - // data map. - parsedKnownRecords := make(fn.Set[tlv.Type], len(knownRecords)) - for _, producer := range knownRecords { - r := producer.Record() - - // Only remove the records if it was parsed (remainder is nil). - // We'll just store the type so we can tell the caller which - // records were actually parsed fully. - val, ok := extraDataTlvMap[r.Type()] - if ok && val == nil { - parsedKnownRecords.Add(r.Type()) - delete(extraDataTlvMap, r.Type()) - } - } - - // Any records from the extra data TLV map which are in the custom - // records TLV type range will be included in the custom records field - // and removed from the extra data field. - customRecordsTlvMap := make(tlv.TypeMap, len(extraDataTlvMap)) - for k, v := range extraDataTlvMap { - // Skip records that are not in the custom records TLV type - // range. - if k < MinCustomRecordsTlvType { - continue - } - - // Include the record in the custom records map. - customRecordsTlvMap[k] = v - - // Now that the record is included in the custom records map, - // we can remove it from the extra data TLV map. - delete(extraDataTlvMap, k) - } - - // Set the custom records field to the custom records specific TLV - // record map. - customRecords, err := NewCustomRecords(customRecordsTlvMap) - if err != nil { - return nil, nil, nil, err - } - - // Encode the remaining records back into the extra data field. These - // records are not in the custom records TLV type range and do not - // have associated fields in the struct that produced the records. - extraData, err := NewExtraOpaqueData(extraDataTlvMap) - if err != nil { - return nil, nil, nil, err - } - - // Help with unit testing where we might have the empty value (nil) for - // the extra data instead of the default that's returned by the - // constructor (empty slice). - if len(extraData) == 0 { - extraData = nil - } - - return customRecords, parsedKnownRecords, extraData, nil -} - -// MergeAndEncode merges the known records with the extra data and custom -// records, then encodes the merged records into raw bytes. -func MergeAndEncode(knownRecords []tlv.RecordProducer, - extraData ExtraOpaqueData, customRecords CustomRecords) ([]byte, - error) { - - // Construct a slice of all the records that we should include in the - // message extra data field. We will start by including any records from - // the extra data field. - mergedRecords, err := extraData.RecordProducers() - if err != nil { - return nil, err - } - - // Merge the known and extra data records. - mergedRecords = append(mergedRecords, knownRecords...) - - // Include custom records in the extra data wire field if they are - // present. Ensure that the custom records are validated before encoding - // them. - if err := customRecords.Validate(); err != nil { - return nil, fmt.Errorf("custom records validation error: %w", - err) - } - - // Extend the message extra data records slice with TLV records from the - // custom records field. - mergedRecords = append( - mergedRecords, customRecords.RecordProducers()..., - ) - - // Now we can sort the records and make sure there are no records with - // the same type that would collide when encoding. - sortedRecords := ProduceRecordsSorted(mergedRecords...) - if err := AssertUniqueTypes(sortedRecords); err != nil { - return nil, err - } - - return EncodeRecords(sortedRecords) -} - -// ParseAndExtractExtraData parses the given extra data into the passed-in -// records, then returns any remaining records as extra data. -func ParseAndExtractExtraData(allTlvData ExtraOpaqueData, - knownRecords ...tlv.RecordProducer) (fn.Set[tlv.Type], - ExtraOpaqueData, error) { - - extraDataTlvMap, err := allTlvData.ExtractRecords(knownRecords...) - if err != nil { - return nil, nil, err - } - - // Remove the known and now extracted records from the leftover extra - // data map. - parsedKnownRecords := make(fn.Set[tlv.Type], len(knownRecords)) - for _, producer := range knownRecords { - r := producer.Record() - - // Only remove the records if it was parsed (remainder is nil). - // We'll just store the type so we can tell the caller which - // records were actually parsed fully. - val, ok := extraDataTlvMap[r.Type()] - if ok && val == nil { - parsedKnownRecords.Add(r.Type()) - delete(extraDataTlvMap, r.Type()) - } - } - - // Encode the remaining records back into the extra data field. These - // records are not in the custom records TLV type range and do not - // have associated fields in the struct that produced the records. - extraData, err := NewExtraOpaqueData(extraDataTlvMap) - if err != nil { - return nil, nil, err - } - - return parsedKnownRecords, extraData, nil -} diff --git a/payments/db/migration1/lnwire/features.go b/payments/db/migration1/lnwire/features.go deleted file mode 100644 index 4e927e170..000000000 --- a/payments/db/migration1/lnwire/features.go +++ /dev/null @@ -1,898 +0,0 @@ -package lnwire - -import ( - "encoding/binary" - "errors" - "fmt" - "io" - - "github.com/lightningnetwork/lnd/tlv" -) - -var ( - // ErrFeaturePairExists signals an error in feature vector construction - // where the opposing bit in a feature pair has already been set. - ErrFeaturePairExists = errors.New("feature pair exists") - - // ErrFeatureStandard is returned when attempts to modify LND's known - // set of features are made. - ErrFeatureStandard = errors.New("feature is used in standard " + - "protocol set") - - // ErrFeatureBitMaximum is returned when a feature bit exceeds the - // maximum allowable value. - ErrFeatureBitMaximum = errors.New("feature bit exceeds allowed maximum") -) - -// FeatureBit represents a feature that can be enabled in either a local or -// global feature vector at a specific bit position. Feature bits follow the -// "it's OK to be odd" rule, where features at even bit positions must be known -// to a node receiving them from a peer while odd bits do not. In accordance, -// feature bits are usually assigned in pairs, first being assigned an odd bit -// position which may later be changed to the preceding even position once -// knowledge of the feature becomes required on the network. -type FeatureBit uint16 - -const ( - // DataLossProtectRequired is a feature bit that indicates that a peer - // *requires* the other party know about the data-loss-protect optional - // feature. If the remote peer does not know of such a feature, then - // the sending peer SHOULD disconnect them. The data-loss-protect - // feature allows a peer that's lost partial data to recover their - // settled funds of the latest commitment state. - DataLossProtectRequired FeatureBit = 0 - - // DataLossProtectOptional is an optional feature bit that indicates - // that the sending peer knows of this new feature and can activate it - // it. The data-loss-protect feature allows a peer that's lost partial - // data to recover their settled funds of the latest commitment state. - DataLossProtectOptional FeatureBit = 1 - - // InitialRoutingSync is a local feature bit meaning that the receiving - // node should send a complete dump of routing information when a new - // connection is established. - InitialRoutingSync FeatureBit = 3 - - // UpfrontShutdownScriptRequired is a feature bit which indicates that a - // peer *requires* that the remote peer accept an upfront shutdown script to - // which payout is enforced on cooperative closes. - UpfrontShutdownScriptRequired FeatureBit = 4 - - // UpfrontShutdownScriptOptional is an optional feature bit which indicates - // that the peer will accept an upfront shutdown script to which payout is - // enforced on cooperative closes. - UpfrontShutdownScriptOptional FeatureBit = 5 - - // GossipQueriesRequired is a feature bit that indicates that the - // receiving peer MUST know of the set of features that allows nodes to - // more efficiently query the network view of peers on the network for - // reconciliation purposes. - GossipQueriesRequired FeatureBit = 6 - - // GossipQueriesOptional is an optional feature bit that signals that - // the setting peer knows of the set of features that allows more - // efficient network view reconciliation. - GossipQueriesOptional FeatureBit = 7 - - // TLVOnionPayloadRequired is a feature bit that indicates a node is - // able to decode the new TLV information included in the onion packet. - TLVOnionPayloadRequired FeatureBit = 8 - - // TLVOnionPayloadOptional is an optional feature bit that indicates a - // node is able to decode the new TLV information included in the onion - // packet. - TLVOnionPayloadOptional FeatureBit = 9 - - // StaticRemoteKeyRequired is a required feature bit that signals that - // within one's commitment transaction, the key used for the remote - // party's non-delay output should not be tweaked. - StaticRemoteKeyRequired FeatureBit = 12 - - // StaticRemoteKeyOptional is an optional feature bit that signals that - // within one's commitment transaction, the key used for the remote - // party's non-delay output should not be tweaked. - StaticRemoteKeyOptional FeatureBit = 13 - - // PaymentAddrRequired is a required feature bit that signals that a - // node requires payment addresses, which are used to mitigate probing - // attacks on the receiver of a payment. - PaymentAddrRequired FeatureBit = 14 - - // PaymentAddrOptional is an optional feature bit that signals that a - // node supports payment addresses, which are used to mitigate probing - // attacks on the receiver of a payment. - PaymentAddrOptional FeatureBit = 15 - - // MPPRequired is a required feature bit that signals that the receiver - // of a payment requires settlement of an invoice with more than one - // HTLC. - MPPRequired FeatureBit = 16 - - // MPPOptional is an optional feature bit that signals that the receiver - // of a payment supports settlement of an invoice with more than one - // HTLC. - MPPOptional FeatureBit = 17 - - // WumboChannelsRequired is a required feature bit that signals that a - // node is willing to accept channels larger than 2^24 satoshis. - WumboChannelsRequired FeatureBit = 18 - - // WumboChannelsOptional is an optional feature bit that signals that a - // node is willing to accept channels larger than 2^24 satoshis. - WumboChannelsOptional FeatureBit = 19 - - // AnchorsRequired is a required feature bit that signals that the node - // requires channels to be made using commitments having anchor - // outputs. - AnchorsRequired FeatureBit = 20 - - // AnchorsOptional is an optional feature bit that signals that the - // node supports channels to be made using commitments having anchor - // outputs. - AnchorsOptional FeatureBit = 21 - - // AnchorsZeroFeeHtlcTxRequired is a required feature bit that signals - // that the node requires channels having zero-fee second-level HTLC - // transactions, which also imply anchor commitments. - AnchorsZeroFeeHtlcTxRequired FeatureBit = 22 - - // AnchorsZeroFeeHtlcTxOptional is an optional feature bit that signals - // that the node supports channels having zero-fee second-level HTLC - // transactions, which also imply anchor commitments. - AnchorsZeroFeeHtlcTxOptional FeatureBit = 23 - - // RouteBlindingRequired is a required feature bit that signals that - // the node supports blinded payments. - RouteBlindingRequired FeatureBit = 24 - - // RouteBlindingOptional is an optional feature bit that signals that - // the node supports blinded payments. - RouteBlindingOptional FeatureBit = 25 - - // ShutdownAnySegwitRequired is an required feature bit that signals - // that the sender is able to properly handle/parse segwit witness - // programs up to version 16. This enables utilization of Taproot - // addresses for cooperative closure addresses. - ShutdownAnySegwitRequired FeatureBit = 26 - - // ShutdownAnySegwitOptional is an optional feature bit that signals - // that the sender is able to properly handle/parse segwit witness - // programs up to version 16. This enables utilization of Taproot - // addresses for cooperative closure addresses. - ShutdownAnySegwitOptional FeatureBit = 27 - - // AMPRequired is a required feature bit that signals that the receiver - // of a payment supports accepts spontaneous payments, i.e. - // sender-generated preimages according to BOLT XX. - AMPRequired FeatureBit = 30 - - // AMPOptional is an optional feature bit that signals that the receiver - // of a payment supports accepts spontaneous payments, i.e. - // sender-generated preimages according to BOLT XX. - AMPOptional FeatureBit = 31 - - // QuiescenceRequired is a required feature bit that denotes that a - // connection established with this node must support the quiescence - // protocol if it wants to have a channel relationship. - QuiescenceRequired FeatureBit = 34 - - // QuiescenceOptional is an optional feature bit that denotes that a - // connection established with this node is permitted to use the - // quiescence protocol. - QuiescenceOptional FeatureBit = 35 - - // ExplicitChannelTypeRequired is a required bit that denotes that a - // connection established with this node is to use explicit channel - // commitment types for negotiation instead of the existing implicit - // negotiation methods. With this bit, there is no longer a "default" - // implicit channel commitment type, allowing a connection to - // open/maintain types of several channels over its lifetime. - ExplicitChannelTypeRequired = 44 - - // ExplicitChannelTypeOptional is an optional bit that denotes that a - // connection established with this node is to use explicit channel - // commitment types for negotiation instead of the existing implicit - // negotiation methods. With this bit, there is no longer a "default" - // implicit channel commitment type, allowing a connection to - // TODO: Decide on actual feature bit value. - ExplicitChannelTypeOptional = 45 - - // ScidAliasRequired is a required feature bit that signals that the - // node requires understanding of ShortChannelID aliases in the TLV - // segment of the channel_ready message. - ScidAliasRequired FeatureBit = 46 - - // ScidAliasOptional is an optional feature bit that signals that the - // node understands ShortChannelID aliases in the TLV segment of the - // channel_ready message. - ScidAliasOptional FeatureBit = 47 - - // PaymentMetadataRequired is a required bit that denotes that if an - // invoice contains metadata, it must be passed along with the payment - // htlc(s). - PaymentMetadataRequired = 48 - - // PaymentMetadataOptional is an optional bit that denotes that if an - // invoice contains metadata, it may be passed along with the payment - // htlc(s). - PaymentMetadataOptional = 49 - - // ZeroConfRequired is a required feature bit that signals that the - // node requires understanding of the zero-conf channel_type. - ZeroConfRequired FeatureBit = 50 - - // ZeroConfOptional is an optional feature bit that signals that the - // node understands the zero-conf channel type. - ZeroConfOptional FeatureBit = 51 - - // KeysendRequired is a required bit that indicates that the node is - // able and willing to accept keysend payments. - KeysendRequired = 54 - - // KeysendOptional is an optional bit that indicates that the node is - // able and willing to accept keysend payments. - KeysendOptional = 55 - - // RbfCoopCloseRequired is a required feature bit that signals that - // the new RBF-based co-op close protocol is supported. - RbfCoopCloseRequired = 60 - - // RbfCoopCloseOptional is an optional feature bit that signals that the - // new RBF-based co-op close protocol is supported. - RbfCoopCloseOptional = 61 - - // RbfCoopCloseRequiredStaging is a required feature bit that signals - // that the new RBF-based co-op close protocol is supported. - RbfCoopCloseRequiredStaging = 160 - - // RbfCoopCloseOptionalStaging is an optional feature bit that signals - // that the new RBF-based co-op close protocol is supported. - RbfCoopCloseOptionalStaging = 161 - - // ScriptEnforcedLeaseRequired is a required feature bit that signals - // that the node requires channels having zero-fee second-level HTLC - // transactions, which also imply anchor commitments, along with an - // additional CLTV constraint of a channel lease's expiration height - // applied to all outputs that pay directly to the channel initiator. - // - // TODO: Decide on actual feature bit value. - ScriptEnforcedLeaseRequired FeatureBit = 2022 - - // ScriptEnforcedLeaseOptional is an optional feature bit that signals - // that the node requires channels having zero-fee second-level HTLC - // transactions, which also imply anchor commitments, along with an - // additional CLTV constraint of a channel lease's expiration height - // applied to all outputs that pay directly to the channel initiator. - // - // TODO: Decide on actual feature bit value. - ScriptEnforcedLeaseOptional FeatureBit = 2023 - - // SimpleTaprootChannelsRequiredFinal is a required bit that indicates - // the node is able to create taproot-native channels. This is the - // final feature bit to be used once the channel type is finalized. - SimpleTaprootChannelsRequiredFinal = 80 - - // SimpleTaprootChannelsOptionalFinal is an optional bit that indicates - // the node is able to create taproot-native channels. This is the - // final feature bit to be used once the channel type is finalized. - SimpleTaprootChannelsOptionalFinal = 81 - - // SimpleTaprootChannelsRequiredStaging is a required bit that indicates - // the node is able to create taproot-native channels. This is a - // feature bit used in the wild while the channel type is still being - // finalized. - SimpleTaprootChannelsRequiredStaging = 180 - - // SimpleTaprootChannelsOptionalStaging is an optional bit that - // indicates the node is able to create taproot-native channels. This - // is a feature bit used in the wild while the channel type is still - // being finalized. - SimpleTaprootChannelsOptionalStaging = 181 - - // ExperimentalAccountabilityRequired is a required feature bit that - // indicates that the node will relay experimental accountability - // signals. - ExperimentalAccountabilityRequired FeatureBit = 260 - - // ExperimentalAccountabilityOptional is an optional feature bit that - // indicates that the node will relay experimental accountability - // signals. - ExperimentalAccountabilityOptional FeatureBit = 261 - - // Bolt11BlindedPathsRequired is a required feature bit that indicates - // that the node is able to understand the blinded path tagged field in - // a BOLT 11 invoice. - Bolt11BlindedPathsRequired = 262 - - // Bolt11BlindedPathsOptional is an optional feature bit that indicates - // that the node is able to understand the blinded path tagged field in - // a BOLT 11 invoice. - Bolt11BlindedPathsOptional = 263 - - // SimpleTaprootOverlayChansRequired is a required bit that indicates - // support for the special custom taproot overlay channel. - SimpleTaprootOverlayChansOptional = 2025 - - // SimpleTaprootOverlayChansRequired is a required bit that indicates - // support for the special custom taproot overlay channel. - SimpleTaprootOverlayChansRequired = 2026 - - // MaxBolt11Feature is the maximum feature bit value allowed in bolt 11 - // invoices. - // - // The base 32 encoded tagged fields in invoices are limited to 10 bits - // to express the length of the field's data. - //nolint:ll - // See: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md#tagged-fields - // - // With a maximum length field of 1023 (2^10 -1) and 5 bit encoding, - // the highest feature bit that can be expressed is: - // 1023 * 5 - 1 = 5114. - MaxBolt11Feature = 5114 -) - -// IsRequired returns true if the feature bit is even, and false otherwise. -func (b FeatureBit) IsRequired() bool { - return b&0x01 == 0x00 -} - -// Features is a mapping of known feature bits to a descriptive name. All known -// feature bits must be assigned a name in this mapping, and feature bit pairs -// must be assigned together for correct behavior. -var Features = map[FeatureBit]string{ - DataLossProtectRequired: "data-loss-protect", - DataLossProtectOptional: "data-loss-protect", - InitialRoutingSync: "initial-routing-sync", - UpfrontShutdownScriptRequired: "upfront-shutdown-script", - UpfrontShutdownScriptOptional: "upfront-shutdown-script", - GossipQueriesRequired: "gossip-queries", - GossipQueriesOptional: "gossip-queries", - TLVOnionPayloadRequired: "tlv-onion", - TLVOnionPayloadOptional: "tlv-onion", - StaticRemoteKeyOptional: "static-remote-key", - StaticRemoteKeyRequired: "static-remote-key", - PaymentAddrOptional: "payment-addr", - PaymentAddrRequired: "payment-addr", - MPPOptional: "multi-path-payments", - MPPRequired: "multi-path-payments", - AnchorsRequired: "anchor-commitments", - AnchorsOptional: "anchor-commitments", - AnchorsZeroFeeHtlcTxRequired: "anchors-zero-fee-htlc-tx", - AnchorsZeroFeeHtlcTxOptional: "anchors-zero-fee-htlc-tx", - WumboChannelsRequired: "wumbo-channels", - WumboChannelsOptional: "wumbo-channels", - AMPRequired: "amp", - AMPOptional: "amp", - QuiescenceRequired: "quiescence", - QuiescenceOptional: "quiescence", - PaymentMetadataOptional: "payment-metadata", - PaymentMetadataRequired: "payment-metadata", - ExplicitChannelTypeOptional: "explicit-commitment-type", - ExplicitChannelTypeRequired: "explicit-commitment-type", - KeysendOptional: "keysend", - KeysendRequired: "keysend", - ScriptEnforcedLeaseRequired: "script-enforced-lease", - ScriptEnforcedLeaseOptional: "script-enforced-lease", - ScidAliasRequired: "scid-alias", - ScidAliasOptional: "scid-alias", - ZeroConfRequired: "zero-conf", - ZeroConfOptional: "zero-conf", - RouteBlindingRequired: "route-blinding", - RouteBlindingOptional: "route-blinding", - ShutdownAnySegwitRequired: "shutdown-any-segwit", - ShutdownAnySegwitOptional: "shutdown-any-segwit", - SimpleTaprootChannelsRequiredFinal: "simple-taproot-chans", - SimpleTaprootChannelsOptionalFinal: "simple-taproot-chans", - SimpleTaprootChannelsRequiredStaging: "simple-taproot-chans-x", - SimpleTaprootChannelsOptionalStaging: "simple-taproot-chans-x", - SimpleTaprootOverlayChansOptional: "taproot-overlay-chans", - SimpleTaprootOverlayChansRequired: "taproot-overlay-chans", - ExperimentalAccountabilityRequired: "accountable-x", - ExperimentalAccountabilityOptional: "accountable-x", - Bolt11BlindedPathsOptional: "bolt-11-blinded-paths", - Bolt11BlindedPathsRequired: "bolt-11-blinded-paths", - RbfCoopCloseOptional: "rbf-coop-close", - RbfCoopCloseRequired: "rbf-coop-close", - RbfCoopCloseOptionalStaging: "rbf-coop-close-x", - RbfCoopCloseRequiredStaging: "rbf-coop-close-x", -} - -// RawFeatureVector represents a set of feature bits as defined in BOLT-09. A -// RawFeatureVector itself just stores a set of bit flags but can be used to -// construct a FeatureVector which binds meaning to each bit. Feature vectors -// can be serialized and deserialized to/from a byte representation that is -// transmitted in Lightning network messages. -type RawFeatureVector struct { - features map[FeatureBit]struct{} -} - -// NewRawFeatureVector creates a feature vector with all of the feature bits -// given as arguments enabled. -func NewRawFeatureVector(bits ...FeatureBit) *RawFeatureVector { - fv := &RawFeatureVector{features: make(map[FeatureBit]struct{})} - for _, bit := range bits { - fv.Set(bit) - } - return fv -} - -// IsEmpty returns whether the feature vector contains any feature bits. -func (fv RawFeatureVector) IsEmpty() bool { - return len(fv.features) == 0 -} - -// OnlyContains determines whether only the specified feature bits are found. -func (fv RawFeatureVector) OnlyContains(bits ...FeatureBit) bool { - if len(bits) != len(fv.features) { - return false - } - for _, bit := range bits { - if !fv.IsSet(bit) { - return false - } - } - return true -} - -// Equals determines whether two features vectors contain exactly the same -// features. -func (fv RawFeatureVector) Equals(other *RawFeatureVector) bool { - if len(fv.features) != len(other.features) { - return false - } - for bit := range fv.features { - if _, ok := other.features[bit]; !ok { - return false - } - } - return true -} - -// Merge sets all feature bits in other on the receiver's feature vector. -func (fv *RawFeatureVector) Merge(other *RawFeatureVector) error { - for bit := range other.features { - err := fv.SafeSet(bit) - if err != nil { - return err - } - } - return nil -} - -// ValidateUpdate checks whether a feature vector can safely be updated to the -// new feature vector provided, checking that it does not alter any of the -// "standard" features that are defined by LND. The new feature vector should -// be inclusive of all features in the original vector that it still wants to -// advertise, setting and unsetting updates as desired. Features in the vector -// are also checked against a maximum inclusive value, as feature vectors in -// different contexts have different maximum values. -func (fv *RawFeatureVector) ValidateUpdate(other *RawFeatureVector, - maximumValue FeatureBit) error { - - // Run through the new set of features and check that we're not adding - // any feature bits that are defined but not set in LND. - for feature := range other.features { - if fv.IsSet(feature) { - continue - } - - if feature > maximumValue { - return fmt.Errorf("can't set feature bit %d: %w %v", - feature, ErrFeatureBitMaximum, - maximumValue) - } - - if name, known := Features[feature]; known { - return fmt.Errorf("can't set feature "+ - "bit %d (%v): %w", feature, name, - ErrFeatureStandard) - } - } - - // Check that the new feature vector for this set does not unset any - // features that are standard in LND by comparing the features in our - // current set to the omitted values in the new set. - for feature := range fv.features { - if other.IsSet(feature) { - continue - } - - if name, known := Features[feature]; known { - return fmt.Errorf("can't unset feature "+ - "bit %d (%v): %w", feature, name, - ErrFeatureStandard) - } - } - - return nil -} - -// ValidatePairs checks each feature bit in a raw vector to ensure that the -// opposing bit is not set, validating that the vector has either the optional -// or required bit set, not both. -func (fv *RawFeatureVector) ValidatePairs() error { - for feature := range fv.features { - if _, ok := fv.features[feature^1]; ok { - return ErrFeaturePairExists - } - } - - return nil -} - -// Clone makes a copy of a feature vector. -func (fv *RawFeatureVector) Clone() *RawFeatureVector { - newFeatures := NewRawFeatureVector() - for bit := range fv.features { - newFeatures.Set(bit) - } - return newFeatures -} - -// IsSet returns whether a particular feature bit is enabled in the vector. -func (fv *RawFeatureVector) IsSet(feature FeatureBit) bool { - _, ok := fv.features[feature] - return ok -} - -// Set marks a feature as enabled in the vector. -func (fv *RawFeatureVector) Set(feature FeatureBit) { - fv.features[feature] = struct{}{} -} - -// SafeSet sets the chosen feature bit in the feature vector, but returns an -// error if the opposing feature bit is already set. This ensures both that we -// are creating properly structured feature vectors, and in some cases, that -// peers are sending properly encoded ones, i.e. it can't be both optional and -// required. -func (fv *RawFeatureVector) SafeSet(feature FeatureBit) error { - if _, ok := fv.features[feature^1]; ok { - return ErrFeaturePairExists - } - - fv.Set(feature) - return nil -} - -// Unset marks a feature as disabled in the vector. -func (fv *RawFeatureVector) Unset(feature FeatureBit) { - delete(fv.features, feature) -} - -// SerializeSize returns the number of bytes needed to represent feature vector -// in byte format. -func (fv *RawFeatureVector) SerializeSize() int { - // We calculate byte-length via the largest bit index. - return fv.serializeSize(8) -} - -// SerializeSize32 returns the number of bytes needed to represent feature -// vector in base32 format. -func (fv *RawFeatureVector) SerializeSize32() int { - // We calculate base32-length via the largest bit index. - return fv.serializeSize(5) -} - -// serializeSize returns the number of bytes required to encode the feature -// vector using at most width bits per encoded byte. -func (fv *RawFeatureVector) serializeSize(width int) int { - // Find the largest feature bit index - max := -1 - for feature := range fv.features { - index := int(feature) - if index > max { - max = index - } - } - if max == -1 { - return 0 - } - - return max/width + 1 -} - -// Encode writes the feature vector in byte representation. Every feature -// encoded as a bit, and the bit vector is serialized using the least number of -// bytes. Since the bit vector length is variable, the first two bytes of the -// serialization represent the length. -func (fv *RawFeatureVector) Encode(w io.Writer) error { - // Write length of feature vector. - var l [2]byte - length := fv.SerializeSize() - binary.BigEndian.PutUint16(l[:], uint16(length)) - if _, err := w.Write(l[:]); err != nil { - return err - } - - return fv.encode(w, length, 8) -} - -// EncodeBase256 writes the feature vector in base256 representation. Every -// feature is encoded as a bit, and the bit vector is serialized using the least -// number of bytes. -func (fv *RawFeatureVector) EncodeBase256(w io.Writer) error { - length := fv.SerializeSize() - return fv.encode(w, length, 8) -} - -// EncodeBase32 writes the feature vector in base32 representation. Every feature -// is encoded as a bit, and the bit vector is serialized using the least number of -// bytes. -func (fv *RawFeatureVector) EncodeBase32(w io.Writer) error { - length := fv.SerializeSize32() - return fv.encode(w, length, 5) -} - -// encode writes the feature vector -func (fv *RawFeatureVector) encode(w io.Writer, length, width int) error { - // Generate the data and write it. - data := make([]byte, length) - for feature := range fv.features { - byteIndex := int(feature) / width - bitIndex := int(feature) % width - data[length-byteIndex-1] |= 1 << uint(bitIndex) - } - - _, err := w.Write(data) - return err -} - -// Decode reads the feature vector from its byte representation. Every feature -// is encoded as a bit, and the bit vector is serialized using the least number -// of bytes. Since the bit vector length is variable, the first two bytes of the -// serialization represent the length. -func (fv *RawFeatureVector) Decode(r io.Reader) error { - // Read the length of the feature vector. - var l [2]byte - if _, err := io.ReadFull(r, l[:]); err != nil { - return err - } - length := binary.BigEndian.Uint16(l[:]) - - return fv.decode(r, int(length), 8) -} - -// DecodeBase256 reads the feature vector from its base256 representation. Every -// feature encoded as a bit, and the bit vector is serialized using the least -// number of bytes. -func (fv *RawFeatureVector) DecodeBase256(r io.Reader, length int) error { - return fv.decode(r, length, 8) -} - -// DecodeBase32 reads the feature vector from its base32 representation. Every -// feature encoded as a bit, and the bit vector is serialized using the least -// number of bytes. -func (fv *RawFeatureVector) DecodeBase32(r io.Reader, length int) error { - return fv.decode(r, length, 5) -} - -// decode reads a feature vector from the next length bytes of the io.Reader, -// assuming each byte has width feature bits encoded per byte. -func (fv *RawFeatureVector) decode(r io.Reader, length, width int) error { - // Read the feature vector data. - data := make([]byte, length) - if _, err := io.ReadFull(r, data); err != nil { - return err - } - - // Set feature bits from parsed data. - bitsNumber := len(data) * width - for i := 0; i < bitsNumber; i++ { - byteIndex := int(i / width) - bitIndex := uint(i % width) - if (data[length-byteIndex-1]>>bitIndex)&1 == 1 { - fv.Set(FeatureBit(i)) - } - } - - return nil -} - -// sizeFunc returns the length required to encode the feature vector. -func (fv *RawFeatureVector) sizeFunc() uint64 { - return uint64(fv.SerializeSize()) -} - -// Record returns a TLV record that can be used to encode/decode raw feature -// vectors. Note that the length of the feature vector is not included, because -// it is covered by the TLV record's length field. -func (fv *RawFeatureVector) Record() tlv.Record { - return tlv.MakeDynamicRecord( - 0, fv, fv.sizeFunc, rawFeatureEncoder, rawFeatureDecoder, - ) -} - -// rawFeatureEncoder is a custom TLV encoder for raw feature vectors. -func rawFeatureEncoder(w io.Writer, val interface{}, _ *[8]byte) error { - if v, ok := val.(*RawFeatureVector); ok { - // Encode the feature bits as a byte slice without its length - // prepended, as that's already taken care of by the TLV record. - fv := *v - return fv.encode(w, fv.SerializeSize(), 8) - } - - return tlv.NewTypeForEncodingErr(val, "lnwire.RawFeatureVector") -} - -// rawFeatureDecoder is a custom TLV decoder for raw feature vectors. -func rawFeatureDecoder(r io.Reader, val interface{}, _ *[8]byte, - l uint64) error { - - if v, ok := val.(*RawFeatureVector); ok { - fv := NewRawFeatureVector() - if err := fv.decode(r, int(l), 8); err != nil { - return err - } - *v = *fv - - return nil - } - - return tlv.NewTypeForEncodingErr(val, "lnwire.RawFeatureVector") -} - -// FeatureVector represents a set of enabled features. The set stores -// information on enabled flags and metadata about the feature names. A feature -// vector is serializable to a compact byte representation that is included in -// Lightning network messages. -type FeatureVector struct { - *RawFeatureVector - featureNames map[FeatureBit]string -} - -// NewFeatureVector constructs a new FeatureVector from a raw feature vector -// and mapping of feature definitions. If the feature vector argument is nil, a -// new one will be constructed with no enabled features. -func NewFeatureVector(featureVector *RawFeatureVector, - featureNames map[FeatureBit]string) *FeatureVector { - - if featureVector == nil { - featureVector = NewRawFeatureVector() - } - return &FeatureVector{ - RawFeatureVector: featureVector, - featureNames: featureNames, - } -} - -// EmptyFeatureVector returns a feature vector with no bits set. -func EmptyFeatureVector() *FeatureVector { - return NewFeatureVector(nil, Features) -} - -// Record implements the RecordProducer interface for FeatureVector. Note that -// it uses a zero-value type is used to produce the record, as we expect this -// type value to be overwritten when used in generic TLV record production. -// This allows a single Record function to serve in the many different contexts -// in which feature vectors are encoded. This record wraps the encoding/ -// decoding for our raw feature vectors so that we can directly parse fully -// formed feature vector types. -func (fv *FeatureVector) Record() tlv.Record { - return tlv.MakeDynamicRecord(0, fv, fv.sizeFunc, - func(w io.Writer, val interface{}, buf *[8]byte) error { - if f, ok := val.(*FeatureVector); ok { - return rawFeatureEncoder( - w, f.RawFeatureVector, buf, - ) - } - - return tlv.NewTypeForEncodingErr( - val, "*lnwire.FeatureVector", - ) - }, - func(r io.Reader, val interface{}, buf *[8]byte, - l uint64) error { - - if f, ok := val.(*FeatureVector); ok { - features := NewFeatureVector(nil, Features) - err := rawFeatureDecoder( - r, features.RawFeatureVector, buf, l, - ) - if err != nil { - return err - } - - *f = *features - - return nil - } - - return tlv.NewTypeForDecodingErr( - val, "*lnwire.FeatureVector", l, l, - ) - }, - ) -} - -// HasFeature returns whether a particular feature is included in the set. The -// feature can be seen as set either if the bit is set directly OR the queried -// bit has the same meaning as its corresponding even/odd bit, which is set -// instead. The second case is because feature bits are generally assigned in -// pairs where both the even and odd position represent the same feature. -func (fv *FeatureVector) HasFeature(feature FeatureBit) bool { - return fv.IsSet(feature) || - (fv.isFeatureBitPair(feature) && fv.IsSet(feature^1)) -} - -// RequiresFeature returns true if the referenced feature vector *requires* -// that the given required bit be set. This method can be used with both -// optional and required feature bits as a parameter. -func (fv *FeatureVector) RequiresFeature(feature FeatureBit) bool { - // If we weren't passed a required feature bit, then we'll flip the - // lowest bit to query for the required version of the feature. This - // lets callers pass in both the optional and required bits. - if !feature.IsRequired() { - feature ^= 1 - } - - return fv.IsSet(feature) -} - -// UnknownRequiredFeatures returns a list of feature bits set in the vector -// that are unknown and in an even bit position. Feature bits with an even -// index must be known to a node receiving the feature vector in a message. -func (fv *FeatureVector) UnknownRequiredFeatures() []FeatureBit { - var unknown []FeatureBit - for feature := range fv.features { - if feature%2 == 0 && !fv.IsKnown(feature) { - unknown = append(unknown, feature) - } - } - return unknown -} - -// UnknownFeatures returns a boolean if a feature vector contains *any* -// unknown features (even if they are odd). -func (fv *FeatureVector) UnknownFeatures() bool { - for feature := range fv.features { - if !fv.IsKnown(feature) { - return true - } - } - - return false -} - -// Name returns a string identifier for the feature represented by this bit. If -// the bit does not represent a known feature, this returns a string indicating -// as such. -func (fv *FeatureVector) Name(bit FeatureBit) string { - name, known := fv.featureNames[bit] - if !known { - return "unknown" - } - return name -} - -// IsKnown returns whether this feature bit represents a known feature. -func (fv *FeatureVector) IsKnown(bit FeatureBit) bool { - _, known := fv.featureNames[bit] - return known -} - -// isFeatureBitPair returns whether this feature bit and its corresponding -// even/odd bit both represent the same feature. This may often be the case as -// bits are generally assigned in pairs, first being assigned an odd bit -// position then being promoted to an even bit position once the network is -// ready. -func (fv *FeatureVector) isFeatureBitPair(bit FeatureBit) bool { - name1, known1 := fv.featureNames[bit] - name2, known2 := fv.featureNames[bit^1] - return known1 && known2 && name1 == name2 -} - -// Features returns the set of raw features contained in the feature vector. -func (fv *FeatureVector) Features() map[FeatureBit]struct{} { - fs := make(map[FeatureBit]struct{}, len(fv.RawFeatureVector.features)) - for b := range fv.RawFeatureVector.features { - fs[b] = struct{}{} - } - return fs -} - -// Clone copies a feature vector, carrying over its feature bits. The feature -// names are not copied. -func (fv *FeatureVector) Clone() *FeatureVector { - features := fv.RawFeatureVector.Clone() - return NewFeatureVector(features, fv.featureNames) -} diff --git a/payments/db/migration1/lnwire/interfaces.go b/payments/db/migration1/lnwire/interfaces.go deleted file mode 100644 index 2b8d64e1f..000000000 --- a/payments/db/migration1/lnwire/interfaces.go +++ /dev/null @@ -1,174 +0,0 @@ -package lnwire - -import ( - "fmt" - - "github.com/btcsuite/btcd/chainhash/v2" -) - -// GossipVersion is a version number that describes the version of the -// gossip protocol that a gossip message was gossiped on. -type GossipVersion uint8 - -const ( - // GossipVersion1 is the initial version of the gossip protocol as - // defined in BOLT 7. This version of the protocol can only gossip P2WSH - // channels and makes use of ECDSA signatures. - GossipVersion1 GossipVersion = 1 - - // GossipVersion2 is the newest version of the gossip protocol. This - // version adds support for P2TR channels and makes use of Schnorr - // signatures. The BOLT number is TBD. - GossipVersion2 GossipVersion = 2 -) - -// String returns a string representation of the protocol version. -func (v GossipVersion) String() string { - return fmt.Sprintf("V%d", v) -} - -// GossipMessage is an interface that must be satisfied by all messages that are -// part of the gossip protocol. -type GossipMessage interface { - // GossipVersion returns the version of the gossip protocol that a - // message is part of. - GossipVersion() GossipVersion -} - -// AnnounceSignatures is an interface that represents a message used to -// exchange signatures of a ChannelAnnouncment message during the funding flow. -type AnnounceSignatures interface { - // SCID returns the ShortChannelID of the channel. - SCID() ShortChannelID - - // ChanID returns the ChannelID identifying the channel. - ChanID() ChannelID - - Message - GossipMessage -} - -// ChannelAnnouncement is an interface that must be satisfied by any message -// used to announce and prove the existence of a channel. -type ChannelAnnouncement interface { - // SCID returns the short channel ID of the channel. - SCID() ShortChannelID - - // GetChainHash returns the hash of the chain which this channel's - // funding transaction is confirmed in. - GetChainHash() chainhash.Hash - - // Node1KeyBytes returns the bytes representing the public key of node - // 1 in the channel. - Node1KeyBytes() [33]byte - - // Node2KeyBytes returns the bytes representing the public key of node - // 2 in the channel. - Node2KeyBytes() [33]byte - - Message - GossipMessage -} - -// CompareResult represents the result after comparing two things. -type CompareResult uint8 - -const ( - // LessThan indicates that base object is less than the object it was - // compared to. - LessThan CompareResult = iota - - // EqualTo indicates that the base object is equal to the object it was - // compared to. - EqualTo - - // GreaterThan indicates that base object is greater than the object it - // was compared to. - GreaterThan -) - -// ChannelUpdate is an interface that describes a message used to update the -// forwarding rules of a channel. -type ChannelUpdate interface { - // SCID returns the ShortChannelID of the channel that the update - // applies to. - SCID() ShortChannelID - - // IsNode1 is true if the update was produced by node 1 of the channel - // peers. Node 1 is the node with the lexicographically smaller public - // key. - IsNode1() bool - - // IsDisabled is true if the update is announcing that the channel - // should be considered disabled. - IsDisabled() bool - - // GetChainHash returns the hash of the chain that the message is - // referring to. - GetChainHash() chainhash.Hash - - // ForwardingPolicy returns the set of forwarding constraints of the - // update. - ForwardingPolicy() *ForwardingPolicy - - // CmpAge can be used to determine if the update is older or newer than - // the passed update. It returns LessThan if this update is older than - // the passed update, GreaterThan if it is newer and EqualTo if they are - // the same age. - CmpAge(update ChannelUpdate) (CompareResult, error) - - // SetDisabledFlag can be used to adjust the disabled flag of an update. - SetDisabledFlag(bool) - - // SetSCID can be used to overwrite the SCID of the update. - SetSCID(scid ShortChannelID) - - Message - GossipMessage -} - -// NodeAnnouncement is an interface that must be satisfied by any message used -// to announce the existence of a node. -type NodeAnnouncement interface { - // NodePub returns the identity public key of the node. - NodePub() [33]byte - - // NodeFeatures returns the set of features supported by the node. - NodeFeatures() *FeatureVector - - // TimestampDesc returns a human-readable description of the - // timestamp of the announcement. - TimestampDesc() string - - Message - GossipMessage -} - -// ForwardingPolicy defines the set of forwarding constraints advertised in a -// ChannelUpdate message. -type ForwardingPolicy struct { - // TimeLockDelta is the minimum number of blocks that the node requires - // to be added to the expiry of HTLCs. This is a security parameter - // determined by the node operator. This value represents the required - // gap between the time locks of the incoming and outgoing HTLC's set - // to this node. - TimeLockDelta uint16 - - // BaseFee is the base fee that must be used for incoming HTLC's to - // this particular channel. This value will be tacked onto the required - // for a payment independent of the size of the payment. - BaseFee MilliSatoshi - - // FeeRate is the fee rate that will be charged per millionth of a - // satoshi. - FeeRate MilliSatoshi - - // HtlcMinimumMsat is the minimum HTLC value which will be accepted. - MinHTLC MilliSatoshi - - // HasMaxHTLC is true if the MaxHTLC field is provided in the update. - HasMaxHTLC bool - - // HtlcMaximumMsat is the maximum HTLC value which will be accepted. - MaxHTLC MilliSatoshi -} diff --git a/payments/db/migration1/lnwire/lnwire.go b/payments/db/migration1/lnwire/lnwire.go deleted file mode 100644 index ae40e629a..000000000 --- a/payments/db/migration1/lnwire/lnwire.go +++ /dev/null @@ -1,426 +0,0 @@ -package lnwire - -import ( - "bytes" - "encoding/binary" - "errors" - "fmt" - "io" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" -) - -const ( - // MaxSliceLength is the maximum allowed length for any opaque byte - // slices in the wire protocol. - MaxSliceLength = 65535 - - // MaxMsgBody is the largest payload any message is allowed to provide. - // This is two less than the MaxSliceLength as each message has a 2 - // byte type that precedes the message body. - MaxMsgBody = 65533 -) - -// PkScript is simple type definition which represents a raw serialized public -// key script. -type PkScript []byte - -// WriteElement is a one-stop shop to write the big endian representation of -// any element which is to be serialized for the wire protocol. -// -// TODO(yy): rm this method once we finish dereferencing it from other -// packages. -func WriteElement(w *bytes.Buffer, element interface{}) error { - switch e := element.(type) { - case uint8: - var b [1]byte - b[0] = e - if _, err := w.Write(b[:]); err != nil { - return err - } - - case uint16: - var b [2]byte - binary.BigEndian.PutUint16(b[:], e) - if _, err := w.Write(b[:]); err != nil { - return err - } - - case ChanUpdateMsgFlags: - var b [1]byte - b[0] = uint8(e) - if _, err := w.Write(b[:]); err != nil { - return err - } - - case ChanUpdateChanFlags: - var b [1]byte - b[0] = uint8(e) - if _, err := w.Write(b[:]); err != nil { - return err - } - - case MilliSatoshi: - var b [8]byte - binary.BigEndian.PutUint64(b[:], uint64(e)) - if _, err := w.Write(b[:]); err != nil { - return err - } - - case btcutil.Amount: - var b [8]byte - binary.BigEndian.PutUint64(b[:], uint64(e)) - if _, err := w.Write(b[:]); err != nil { - return err - } - - case uint32: - var b [4]byte - binary.BigEndian.PutUint32(b[:], e) - if _, err := w.Write(b[:]); err != nil { - return err - } - - case uint64: - var b [8]byte - binary.BigEndian.PutUint64(b[:], e) - if _, err := w.Write(b[:]); err != nil { - return err - } - - case *btcec.PublicKey: - if e == nil { - return fmt.Errorf("cannot write nil pubkey") - } - - var b [33]byte - serializedPubkey := e.SerializeCompressed() - copy(b[:], serializedPubkey) - if _, err := w.Write(b[:]); err != nil { - return err - } - - case []Sig: - var b [2]byte - numSigs := uint16(len(e)) - binary.BigEndian.PutUint16(b[:], numSigs) - if _, err := w.Write(b[:]); err != nil { - return err - } - - for _, sig := range e { - if err := WriteElement(w, sig); err != nil { - return err - } - } - - case Sig: - // Write buffer - if _, err := w.Write(e.bytes[:]); err != nil { - return err - } - - case ErrorData: - var l [2]byte - binary.BigEndian.PutUint16(l[:], uint16(len(e))) - if _, err := w.Write(l[:]); err != nil { - return err - } - - if _, err := w.Write(e[:]); err != nil { - return err - } - - case [33]byte: - if _, err := w.Write(e[:]); err != nil { - return err - } - - case []byte: - if _, err := w.Write(e[:]); err != nil { - return err - } - - case *RawFeatureVector: - if e == nil { - return fmt.Errorf("cannot write nil feature vector") - } - - if err := e.Encode(w); err != nil { - return err - } - - case ChannelID: - if _, err := w.Write(e[:]); err != nil { - return err - } - - case FailCode: - if err := WriteElement(w, uint16(e)); err != nil { - return err - } - - case ShortChannelID: - // Check that field fit in 3 bytes and write the blockHeight - if e.BlockHeight > ((1 << 24) - 1) { - return errors.New("block height should fit in 3 bytes") - } - - var blockHeight [4]byte - binary.BigEndian.PutUint32(blockHeight[:], e.BlockHeight) - - if _, err := w.Write(blockHeight[1:]); err != nil { - return err - } - - // Check that field fit in 3 bytes and write the txIndex - if e.TxIndex > ((1 << 24) - 1) { - return errors.New("tx index should fit in 3 bytes") - } - - var txIndex [4]byte - binary.BigEndian.PutUint32(txIndex[:], e.TxIndex) - if _, err := w.Write(txIndex[1:]); err != nil { - return err - } - - // Write the txPosition - var txPosition [2]byte - binary.BigEndian.PutUint16(txPosition[:], e.TxPosition) - if _, err := w.Write(txPosition[:]); err != nil { - return err - } - - case bool: - var b [1]byte - if e { - b[0] = 1 - } - if _, err := w.Write(b[:]); err != nil { - return err - } - - case ExtraOpaqueData: - return e.Encode(w) - - default: - return fmt.Errorf("unknown type in WriteElement: %T", e) - } - - return nil -} - -// WriteElements is writes each element in the elements slice to the passed -// buffer using WriteElement. -// -// TODO(yy): rm this method once we finish dereferencing it from other -// packages. -func WriteElements(buf *bytes.Buffer, elements ...interface{}) error { - for _, element := range elements { - err := WriteElement(buf, element) - if err != nil { - return err - } - } - return nil -} - -// ReadElement is a one-stop utility function to deserialize any datastructure -// encoded using the serialization format of lnwire. -func ReadElement(r io.Reader, element interface{}) error { - var err error - switch e := element.(type) { - case *bool: - var b [1]byte - if _, err := io.ReadFull(r, b[:]); err != nil { - return err - } - - if b[0] == 1 { - *e = true - } - - case *uint8: - var b [1]uint8 - if _, err := r.Read(b[:]); err != nil { - return err - } - *e = b[0] - - case *uint16: - var b [2]byte - if _, err := io.ReadFull(r, b[:]); err != nil { - return err - } - *e = binary.BigEndian.Uint16(b[:]) - - case *ChanUpdateMsgFlags: - var b [1]uint8 - if _, err := r.Read(b[:]); err != nil { - return err - } - *e = ChanUpdateMsgFlags(b[0]) - - case *ChanUpdateChanFlags: - var b [1]uint8 - if _, err := r.Read(b[:]); err != nil { - return err - } - *e = ChanUpdateChanFlags(b[0]) - - case *uint32: - var b [4]byte - if _, err := io.ReadFull(r, b[:]); err != nil { - return err - } - *e = binary.BigEndian.Uint32(b[:]) - - case *uint64: - var b [8]byte - if _, err := io.ReadFull(r, b[:]); err != nil { - return err - } - *e = binary.BigEndian.Uint64(b[:]) - - case *MilliSatoshi: - var b [8]byte - if _, err := io.ReadFull(r, b[:]); err != nil { - return err - } - *e = MilliSatoshi(int64(binary.BigEndian.Uint64(b[:]))) - - case *btcutil.Amount: - var b [8]byte - if _, err := io.ReadFull(r, b[:]); err != nil { - return err - } - *e = btcutil.Amount(int64(binary.BigEndian.Uint64(b[:]))) - - case **btcec.PublicKey: - var b [btcec.PubKeyBytesLenCompressed]byte - if _, err = io.ReadFull(r, b[:]); err != nil { - return err - } - - pubKey, err := btcec.ParsePubKey(b[:]) - if err != nil { - return err - } - *e = pubKey - - case *RawFeatureVector: - f := NewRawFeatureVector() - err = f.Decode(r) - if err != nil { - return err - } - *e = *f - - case **RawFeatureVector: - f := NewRawFeatureVector() - err = f.Decode(r) - if err != nil { - return err - } - *e = f - - case *[]Sig: - var l [2]byte - if _, err := io.ReadFull(r, l[:]); err != nil { - return err - } - numSigs := binary.BigEndian.Uint16(l[:]) - - var sigs []Sig - if numSigs > 0 { - sigs = make([]Sig, numSigs) - for i := 0; i < int(numSigs); i++ { - if err := ReadElement(r, &sigs[i]); err != nil { - return err - } - } - } - *e = sigs - - case *Sig: - if _, err := io.ReadFull(r, e.bytes[:]); err != nil { - return err - } - - case *ErrorData: - var l [2]byte - if _, err := io.ReadFull(r, l[:]); err != nil { - return err - } - errorLen := binary.BigEndian.Uint16(l[:]) - - *e = ErrorData(make([]byte, errorLen)) - if _, err := io.ReadFull(r, *e); err != nil { - return err - } - - case *[33]byte: - if _, err := io.ReadFull(r, e[:]); err != nil { - return err - } - - case []byte: - if _, err := io.ReadFull(r, e); err != nil { - return err - } - - case *FailCode: - if err := ReadElement(r, (*uint16)(e)); err != nil { - return err - } - - case *ChannelID: - if _, err := io.ReadFull(r, e[:]); err != nil { - return err - } - - case *ShortChannelID: - var blockHeight [4]byte - if _, err = io.ReadFull(r, blockHeight[1:]); err != nil { - return err - } - - var txIndex [4]byte - if _, err = io.ReadFull(r, txIndex[1:]); err != nil { - return err - } - - var txPosition [2]byte - if _, err = io.ReadFull(r, txPosition[:]); err != nil { - return err - } - - *e = ShortChannelID{ - BlockHeight: binary.BigEndian.Uint32(blockHeight[:]), - TxIndex: binary.BigEndian.Uint32(txIndex[:]), - TxPosition: binary.BigEndian.Uint16(txPosition[:]), - } - - case *ExtraOpaqueData: - return e.Decode(r) - - default: - return fmt.Errorf("unknown type in ReadElement: %T", e) - } - - return nil -} - -// ReadElements deserializes a variable number of elements into the passed -// io.Reader, with each element being deserialized according to the ReadElement -// function. -func ReadElements(r io.Reader, elements ...interface{}) error { - for _, element := range elements { - err := ReadElement(r, element) - if err != nil { - return err - } - } - return nil -} diff --git a/payments/db/migration1/lnwire/message.go b/payments/db/migration1/lnwire/message.go deleted file mode 100644 index def762f18..000000000 --- a/payments/db/migration1/lnwire/message.go +++ /dev/null @@ -1,323 +0,0 @@ -// Copyright (c) 2013-2017 The btcsuite developers -// Copyright (c) 2015-2016 The Decred developers -// code derived from https://github .com/btcsuite/btcd/blob/master/wire/message.go -// Copyright (C) 2015-2022 The Lightning Network Developers - -package lnwire - -import ( - "bytes" - "encoding/binary" - "fmt" - "io" -) - -// MessageTypeSize is the size in bytes of the message type field in the header -// of all messages. -const MessageTypeSize = 2 - -// MessageType is the unique 2 byte big-endian integer that indicates the type -// of message on the wire. All messages have a very simple header which -// consists simply of 2-byte message type. We omit a length field, and checksum -// as the Lightning Protocol is intended to be encapsulated within a -// confidential+authenticated cryptographic messaging protocol. -type MessageType uint16 - -// The currently defined message types within this current version of the -// Lightning protocol. -const ( - MsgWarning MessageType = 1 - MsgStfu = 2 - MsgInit = 16 - MsgError = 17 - MsgPing = 18 - MsgPong = 19 - MsgOpenChannel = 32 - MsgAcceptChannel = 33 - MsgFundingCreated = 34 - MsgFundingSigned = 35 - MsgChannelReady = 36 - MsgShutdown = 38 - MsgClosingSigned = 39 - MsgClosingComplete = 40 - MsgClosingSig = 41 - MsgDynPropose = 111 - MsgDynAck = 113 - MsgDynReject = 115 - MsgDynCommit = 117 - MsgUpdateAddHTLC = 128 - MsgUpdateFulfillHTLC = 130 - MsgUpdateFailHTLC = 131 - MsgCommitSig = 132 - MsgRevokeAndAck = 133 - MsgUpdateFee = 134 - MsgUpdateFailMalformedHTLC = 135 - MsgChannelReestablish = 136 - MsgChannelAnnouncement = 256 - MsgNodeAnnouncement = 257 - MsgChannelUpdate = 258 - MsgAnnounceSignatures = 259 - MsgAnnounceSignatures2 = 260 - MsgQueryShortChanIDs = 261 - MsgReplyShortChanIDsEnd = 262 - MsgQueryChannelRange = 263 - MsgReplyChannelRange = 264 - MsgGossipTimestampRange = 265 - MsgChannelAnnouncement2 = 267 - MsgNodeAnnouncement2 = 269 - MsgChannelUpdate2 = 271 - MsgOnionMessage = 513 - MsgKickoffSig = 777 - - // MsgEnd defines the end of the official message range of the protocol. - // If a new message is added beyond this message, then this should be - // modified. - MsgEnd = 778 -) - -// IsChannelUpdate is a filter function that discerns channel update messages -// from the other messages in the Lightning Network Protocol. -func (t MessageType) IsChannelUpdate() bool { - switch t { - case MsgUpdateAddHTLC: - return true - case MsgUpdateFulfillHTLC: - return true - case MsgUpdateFailHTLC: - return true - case MsgUpdateFailMalformedHTLC: - return true - case MsgUpdateFee: - return true - default: - return false - } -} - -// ErrorEncodeMessage is used when failed to encode the message payload. -func ErrorEncodeMessage(err error) error { - return fmt.Errorf("failed to encode message to buffer, got %w", err) -} - -// ErrorWriteMessageType is used when failed to write the message type. -func ErrorWriteMessageType(err error) error { - return fmt.Errorf("failed to write message type, got %w", err) -} - -// ErrorPayloadTooLarge is used when the payload size exceeds the -// MaxMsgBody. -func ErrorPayloadTooLarge(size int) error { - return fmt.Errorf( - "message payload is too large - encoded %d bytes, "+ - "but maximum message payload is %d bytes", - size, MaxMsgBody, - ) -} - -// String return the string representation of message type. -func (t MessageType) String() string { - switch t { - case MsgWarning: - return "Warning" - case MsgStfu: - return "Stfu" - case MsgInit: - return "Init" - case MsgOpenChannel: - return "MsgOpenChannel" - case MsgAcceptChannel: - return "MsgAcceptChannel" - case MsgFundingCreated: - return "MsgFundingCreated" - case MsgFundingSigned: - return "MsgFundingSigned" - case MsgChannelReady: - return "ChannelReady" - case MsgShutdown: - return "Shutdown" - case MsgClosingSigned: - return "ClosingSigned" - case MsgDynPropose: - return "DynPropose" - case MsgDynAck: - return "DynAck" - case MsgDynReject: - return "DynReject" - case MsgDynCommit: - return "DynCommit" - case MsgKickoffSig: - return "KickoffSig" - case MsgUpdateAddHTLC: - return "UpdateAddHTLC" - case MsgUpdateFailHTLC: - return "UpdateFailHTLC" - case MsgUpdateFulfillHTLC: - return "UpdateFulfillHTLC" - case MsgCommitSig: - return "CommitSig" - case MsgRevokeAndAck: - return "RevokeAndAck" - case MsgUpdateFailMalformedHTLC: - return "UpdateFailMalformedHTLC" - case MsgChannelReestablish: - return "ChannelReestablish" - case MsgError: - return "Error" - case MsgChannelAnnouncement: - return "ChannelAnnouncement" - case MsgChannelUpdate: - return "ChannelUpdate" - case MsgNodeAnnouncement: - return "NodeAnnouncement1" - case MsgPing: - return "Ping" - case MsgAnnounceSignatures: - return "AnnounceSignatures" - case MsgPong: - return "Pong" - case MsgUpdateFee: - return "UpdateFee" - case MsgQueryShortChanIDs: - return "QueryShortChanIDs" - case MsgReplyShortChanIDsEnd: - return "ReplyShortChanIDsEnd" - case MsgQueryChannelRange: - return "QueryChannelRange" - case MsgReplyChannelRange: - return "ReplyChannelRange" - case MsgGossipTimestampRange: - return "GossipTimestampRange" - case MsgClosingComplete: - return "ClosingComplete" - case MsgClosingSig: - return "ClosingSig" - case MsgAnnounceSignatures2: - return "MsgAnnounceSignatures2" - case MsgChannelAnnouncement2: - return "ChannelAnnouncement2" - case MsgNodeAnnouncement2: - return "NodeAnnouncement2" - case MsgChannelUpdate2: - return "ChannelUpdate2" - case MsgOnionMessage: - return "OnionMessage" - default: - return "" - } -} - -// UnknownMessage is an implementation of the error interface that allows the -// creation of an error in response to an unknown message. -type UnknownMessage struct { - messageType MessageType -} - -// Error returns a human readable string describing the error. -// -// This is part of the error interface. -func (u *UnknownMessage) Error() string { - return fmt.Sprintf("unable to parse message of unknown type: %v", - u.messageType) -} - -// Serializable is an interface which defines a lightning wire serializable -// object. -type Serializable interface { - // Decode reads the bytes stream and converts it to the object. - Decode(io.Reader, uint32) error - - // Encode converts object to the bytes stream and write it into the - // write buffer. - Encode(*bytes.Buffer, uint32) error -} - -// Message is an interface that defines a lightning wire protocol message. The -// interface is general in order to allow implementing types full control over -// the representation of its data. -type Message interface { - Serializable - MsgType() MessageType -} - -// LinkUpdater is an interface implemented by most messages in BOLT 2 that are -// allowed to update the channel state. -type LinkUpdater interface { - // All LinkUpdater messages are messages and so we embed the interface - // so that we can treat it as a message if all we know about it is that - // it is a LinkUpdater message. - Message - - // TargetChanID returns the channel id of the link for which this - // message is intended. - TargetChanID() ChannelID -} - -// SizeableMessage is an interface that extends the base Message interface with -// a method to calculate the serialized size of a message. -type SizeableMessage interface { - Message - - // SerializedSize returns the serialized size of the message in bytes. - // The returned size includes the message type header bytes. - SerializedSize() (uint32, error) -} - -// MessageSerializedSize calculates the serialized size of a message in bytes. -// This is a helper function that can be used by all message types to implement -// the SerializedSize method. -func MessageSerializedSize(msg Message) (uint32, error) { - var buf bytes.Buffer - - // Encode the message to the buffer. - if err := msg.Encode(&buf, 0); err != nil { - return 0, err - } - - // Add the size of the message type. - return uint32(buf.Len()) + MessageTypeSize, nil -} - -// WriteMessage writes a lightning Message to a buffer including the necessary -// header information and returns the number of bytes written. If any error is -// encountered, the buffer passed will be reset to its original state since we -// don't want any broken bytes left. In other words, no bytes will be written -// if there's an error. Either all or none of the message bytes will be written -// to the buffer. -// -// NOTE: this method is not concurrent safe. -func WriteMessage(buf *bytes.Buffer, msg Message, pver uint32) (int, error) { - // Record the size of the bytes already written in buffer. - oldByteSize := buf.Len() - - // cleanBrokenBytes is a helper closure that helps reset the buffer to - // its original state. It truncates all the bytes written in current - // scope. - var cleanBrokenBytes = func(b *bytes.Buffer) int { - b.Truncate(oldByteSize) - return 0 - } - - // Write the message type. - var mType [2]byte - binary.BigEndian.PutUint16(mType[:], uint16(msg.MsgType())) - msgTypeBytes, err := buf.Write(mType[:]) - if err != nil { - return cleanBrokenBytes(buf), ErrorWriteMessageType(err) - } - - // Use the write buffer to encode our message. - if err := msg.Encode(buf, pver); err != nil { - return cleanBrokenBytes(buf), ErrorEncodeMessage(err) - } - - // Enforce maximum overall message payload. The write buffer now has - // the size of len(originalBytes) + len(payload) + len(type). We want - // to enforce the payload here, so we subtract it by the length of the - // type and old bytes. - lenp := buf.Len() - oldByteSize - msgTypeBytes - if lenp > MaxMsgBody { - return cleanBrokenBytes(buf), ErrorPayloadTooLarge(lenp) - } - - return buf.Len() - oldByteSize, nil -} diff --git a/payments/db/migration1/lnwire/msat.go b/payments/db/migration1/lnwire/msat.go deleted file mode 100644 index 609df7e38..000000000 --- a/payments/db/migration1/lnwire/msat.go +++ /dev/null @@ -1,92 +0,0 @@ -package lnwire - -import ( - "fmt" - "io" - - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/lightningnetwork/lnd/tlv" -) - -const ( - // mSatScale is a value that's used to scale satoshis to milli-satoshis, and - // the other way around. - mSatScale uint64 = 1000 - - // MaxMilliSatoshi is the maximum number of msats that can be expressed - // in this data type. - MaxMilliSatoshi = ^MilliSatoshi(0) -) - -// MilliSatoshi are the native unit of the Lightning Network. A milli-satoshi -// is simply 1/1000th of a satoshi. There are 1000 milli-satoshis in a single -// satoshi. Within the network, all HTLC payments are denominated in -// milli-satoshis. As milli-satoshis aren't deliverable on the native -// blockchain, before settling to broadcasting, the values are rounded down to -// the nearest satoshi. -type MilliSatoshi uint64 - -// NewMSatFromSatoshis creates a new MilliSatoshi instance from a target amount -// of satoshis. -func NewMSatFromSatoshis(sat btcutil.Amount) MilliSatoshi { - return MilliSatoshi(uint64(sat) * mSatScale) -} - -// ToBTC converts the target MilliSatoshi amount to its corresponding value -// when expressed in BTC. -func (m MilliSatoshi) ToBTC() float64 { - sat := m.ToSatoshis() - return sat.ToBTC() -} - -// ToSatoshis converts the target MilliSatoshi amount to satoshis. Simply, this -// sheds a factor of 1000 from the mSAT amount in order to convert it to SAT. -func (m MilliSatoshi) ToSatoshis() btcutil.Amount { - return btcutil.Amount(uint64(m) / mSatScale) -} - -// String returns the string representation of the mSAT amount. -func (m MilliSatoshi) String() string { - return fmt.Sprintf("%v mSAT", uint64(m)) -} - -// TODO(roasbeef): extend with arithmetic operations? - -// Record returns a TLV record that can be used to encode/decode a MilliSatoshi -// to/from a TLV stream. -func (m *MilliSatoshi) Record() tlv.Record { - msat := uint64(*m) - - return tlv.MakeDynamicRecord( - 0, m, tlv.SizeBigSize(&msat), encodeMilliSatoshis, - decodeMilliSatoshis, - ) -} - -func encodeMilliSatoshis(w io.Writer, val interface{}, buf *[8]byte) error { - if v, ok := val.(*MilliSatoshi); ok { - bigSize := uint64(*v) - - return tlv.EBigSize(w, &bigSize, buf) - } - - return tlv.NewTypeForEncodingErr(val, "lnwire.MilliSatoshi") -} - -func decodeMilliSatoshis(r io.Reader, val interface{}, buf *[8]byte, - l uint64) error { - - if v, ok := val.(*MilliSatoshi); ok { - var bigSize uint64 - err := tlv.DBigSize(r, &bigSize, buf, l) - if err != nil { - return err - } - - *v = MilliSatoshi(bigSize) - - return nil - } - - return tlv.NewTypeForDecodingErr(val, "lnwire.MilliSatoshi", l, l) -} diff --git a/payments/db/migration1/lnwire/onion_error.go b/payments/db/migration1/lnwire/onion_error.go deleted file mode 100644 index 9cc115ff0..000000000 --- a/payments/db/migration1/lnwire/onion_error.go +++ /dev/null @@ -1,1546 +0,0 @@ -package lnwire - -import ( - "bufio" - "bytes" - "crypto/sha256" - "encoding/binary" - "fmt" - "io" - - "github.com/davecgh/go-spew/spew" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/tlv" -) - -// FailureMessage represents the onion failure object identified by its unique -// failure code. -type FailureMessage interface { - // Code returns a failure code describing the exact nature of the - // error. - Code() FailCode - - // Error returns a human readable string describing the error. With - // this method, the FailureMessage interface meets the built-in error - // interface. - Error() string -} - -// FailureMessageLength is the size of the failure message plus the size of -// padding. The FailureMessage message should always be EXACTLY this size. -const FailureMessageLength = 256 - -const ( - // FlagBadOnion error flag describes an unparsable, encrypted by - // previous node. - FlagBadOnion FailCode = 0x8000 - - // FlagPerm error flag indicates a permanent failure. - FlagPerm FailCode = 0x4000 - - // FlagNode error flag indicates a node failure. - FlagNode FailCode = 0x2000 - - // FlagUpdate error flag indicates a new channel update is enclosed - // within the error. - FlagUpdate FailCode = 0x1000 -) - -// FailCode specifies the precise reason that an upstream HTLC was canceled. -// Each UpdateFailHTLC message carries a FailCode which is to be passed -// backwards, encrypted at each step back to the source of the HTLC within the -// route. -type FailCode uint16 - -// The currently defined onion failure types within this current version of the -// Lightning protocol. -const ( - CodeNone FailCode = 0 - CodeInvalidRealm = FlagBadOnion | 1 - CodeTemporaryNodeFailure = FlagNode | 2 - CodePermanentNodeFailure = FlagPerm | FlagNode | 2 - CodeRequiredNodeFeatureMissing = FlagPerm | FlagNode | 3 - CodeInvalidOnionVersion = FlagBadOnion | FlagPerm | 4 - CodeInvalidOnionHmac = FlagBadOnion | FlagPerm | 5 - CodeInvalidOnionKey = FlagBadOnion | FlagPerm | 6 - CodeTemporaryChannelFailure = FlagUpdate | 7 - CodePermanentChannelFailure = FlagPerm | 8 - CodeRequiredChannelFeatureMissing = FlagPerm | 9 - CodeUnknownNextPeer = FlagPerm | 10 - CodeAmountBelowMinimum = FlagUpdate | 11 - CodeFeeInsufficient = FlagUpdate | 12 - CodeIncorrectCltvExpiry = FlagUpdate | 13 - CodeExpiryTooSoon = FlagUpdate | 14 - CodeChannelDisabled = FlagUpdate | 20 - CodeIncorrectOrUnknownPaymentDetails = FlagPerm | 15 - CodeIncorrectPaymentAmount = FlagPerm | 16 - CodeFinalExpiryTooSoon FailCode = 17 - CodeFinalIncorrectCltvExpiry FailCode = 18 - CodeFinalIncorrectHtlcAmount FailCode = 19 - CodeExpiryTooFar FailCode = 21 - CodeInvalidOnionPayload = FlagPerm | 22 - CodeMPPTimeout FailCode = 23 - CodeInvalidBlinding = FlagBadOnion | FlagPerm | 24 //nolint:ll -) - -// String returns the string representation of the failure code. -func (c FailCode) String() string { - switch c { - case CodeInvalidRealm: - return "InvalidRealm" - - case CodeTemporaryNodeFailure: - return "TemporaryNodeFailure" - - case CodePermanentNodeFailure: - return "PermanentNodeFailure" - - case CodeRequiredNodeFeatureMissing: - return "RequiredNodeFeatureMissing" - - case CodeInvalidOnionVersion: - return "InvalidOnionVersion" - - case CodeInvalidOnionHmac: - return "InvalidOnionHmac" - - case CodeInvalidOnionKey: - return "InvalidOnionKey" - - case CodeTemporaryChannelFailure: - return "TemporaryChannelFailure" - - case CodePermanentChannelFailure: - return "PermanentChannelFailure" - - case CodeRequiredChannelFeatureMissing: - return "RequiredChannelFeatureMissing" - - case CodeUnknownNextPeer: - return "UnknownNextPeer" - - case CodeAmountBelowMinimum: - return "AmountBelowMinimum" - - case CodeFeeInsufficient: - return "FeeInsufficient" - - case CodeIncorrectCltvExpiry: - return "IncorrectCltvExpiry" - - case CodeIncorrectPaymentAmount: - return "IncorrectPaymentAmount" - - case CodeExpiryTooSoon: - return "ExpiryTooSoon" - - case CodeChannelDisabled: - return "ChannelDisabled" - - case CodeIncorrectOrUnknownPaymentDetails: - return "IncorrectOrUnknownPaymentDetails" - - case CodeFinalExpiryTooSoon: - return "FinalExpiryTooSoon" - - case CodeFinalIncorrectCltvExpiry: - return "FinalIncorrectCltvExpiry" - - case CodeFinalIncorrectHtlcAmount: - return "FinalIncorrectHtlcAmount" - - case CodeExpiryTooFar: - return "ExpiryTooFar" - - case CodeInvalidOnionPayload: - return "InvalidOnionPayload" - - case CodeMPPTimeout: - return "MPPTimeout" - - case CodeInvalidBlinding: - return "InvalidBlinding" - - default: - return "" - } -} - -// FailInvalidRealm is returned if the realm byte is unknown. -// -// NOTE: May be returned by any node in the payment route. -type FailInvalidRealm struct{} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailInvalidRealm) Error() string { - return f.Code().String() -} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailInvalidRealm) Code() FailCode { - return CodeInvalidRealm -} - -// FailTemporaryNodeFailure is returned if an otherwise unspecified transient -// error occurs for the entire node. -// -// NOTE: May be returned by any node in the payment route. -type FailTemporaryNodeFailure struct{} - -// Code returns the failure unique code. -// NOTE: Part of the FailureMessage interface. -func (f *FailTemporaryNodeFailure) Code() FailCode { - return CodeTemporaryNodeFailure -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailTemporaryNodeFailure) Error() string { - return f.Code().String() -} - -// FailPermanentNodeFailure is returned if an otherwise unspecified permanent -// error occurs for the entire node. -// -// NOTE: May be returned by any node in the payment route. -type FailPermanentNodeFailure struct{} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailPermanentNodeFailure) Code() FailCode { - return CodePermanentNodeFailure -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailPermanentNodeFailure) Error() string { - return f.Code().String() -} - -// FailRequiredNodeFeatureMissing is returned if a node has requirement -// advertised in its node_announcement features which were not present in the -// onion. -// -// NOTE: May be returned by any node in the payment route. -type FailRequiredNodeFeatureMissing struct{} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailRequiredNodeFeatureMissing) Code() FailCode { - return CodeRequiredNodeFeatureMissing -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailRequiredNodeFeatureMissing) Error() string { - return f.Code().String() -} - -// FailPermanentChannelFailure is return if an otherwise unspecified permanent -// error occurs for the outgoing channel (eg. channel (recently). -// -// NOTE: May be returned by any node in the payment route. -type FailPermanentChannelFailure struct{} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailPermanentChannelFailure) Code() FailCode { - return CodePermanentChannelFailure -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailPermanentChannelFailure) Error() string { - return f.Code().String() -} - -// FailRequiredChannelFeatureMissing is returned if the outgoing channel has a -// requirement advertised in its channel announcement features which were not -// present in the onion. -// -// NOTE: May only be returned by intermediate nodes. -type FailRequiredChannelFeatureMissing struct{} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailRequiredChannelFeatureMissing) Code() FailCode { - return CodeRequiredChannelFeatureMissing -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailRequiredChannelFeatureMissing) Error() string { - return f.Code().String() -} - -// FailUnknownNextPeer is returned if the next peer specified by the onion is -// not known. -// -// NOTE: May only be returned by intermediate nodes. -type FailUnknownNextPeer struct{} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailUnknownNextPeer) Code() FailCode { - return CodeUnknownNextPeer -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailUnknownNextPeer) Error() string { - return f.Code().String() -} - -// FailIncorrectPaymentAmount is returned if the amount paid is less than the -// amount expected, the final node MUST fail the HTLC. If the amount paid is -// more than twice the amount expected, the final node SHOULD fail the HTLC. -// This allows the sender to reduce information leakage by altering the amount, -// without allowing accidental gross overpayment. -// -// NOTE: May only be returned by the final node in the path. -type FailIncorrectPaymentAmount struct{} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailIncorrectPaymentAmount) Code() FailCode { - return CodeIncorrectPaymentAmount -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailIncorrectPaymentAmount) Error() string { - return f.Code().String() -} - -// FailIncorrectDetails is returned for two reasons: -// -// 1) if the payment hash has already been paid, the final node MAY treat the -// payment hash as unknown, or may succeed in accepting the HTLC. If the -// payment hash is unknown, the final node MUST fail the HTLC. -// -// 2) if the amount paid is less than the amount expected, the final node MUST -// fail the HTLC. If the amount paid is more than twice the amount expected, -// the final node SHOULD fail the HTLC. This allows the sender to reduce -// information leakage by altering the amount, without allowing accidental -// gross overpayment. -// -// NOTE: May only be returned by the final node in the path. -type FailIncorrectDetails struct { - // amount is the value of the extended HTLC. - amount MilliSatoshi - - // height is the block height when the htlc was received. - height uint32 - - // extraOpaqueData contains additional failure message tlv data. - extraOpaqueData ExtraOpaqueData -} - -// NewFailIncorrectDetails makes a new instance of the FailIncorrectDetails -// error bound to the specified HTLC amount and acceptance height. -func NewFailIncorrectDetails(amt MilliSatoshi, - height uint32) *FailIncorrectDetails { - - return &FailIncorrectDetails{ - amount: amt, - height: height, - extraOpaqueData: []byte{}, - } -} - -// Amount is the value of the extended HTLC. -func (f *FailIncorrectDetails) Amount() MilliSatoshi { - return f.amount -} - -// Height is the block height when the htlc was received. -func (f *FailIncorrectDetails) Height() uint32 { - return f.height -} - -// ExtraOpaqueData returns additional failure message tlv data. -func (f *FailIncorrectDetails) ExtraOpaqueData() ExtraOpaqueData { - return f.extraOpaqueData -} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailIncorrectDetails) Code() FailCode { - return CodeIncorrectOrUnknownPaymentDetails -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailIncorrectDetails) Error() string { - return fmt.Sprintf( - "%v(amt=%v, height=%v)", CodeIncorrectOrUnknownPaymentDetails, - f.amount, f.height, - ) -} - -// Decode decodes the failure from bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailIncorrectDetails) Decode(r io.Reader, pver uint32) error { - err := ReadElement(r, &f.amount) - switch { - // This is an optional tack on that was added later in the protocol. As - // a result, older nodes may not include this value. We'll account for - // this by checking for io.EOF here which means that no bytes were read - // at all. - case err == io.EOF: - return nil - - case err != nil: - return err - } - - // At a later stage, the height field was also tacked on. We need to - // check for io.EOF here as well. - err = ReadElement(r, &f.height) - switch { - case err == io.EOF: - return nil - - case err != nil: - return err - } - - return f.extraOpaqueData.Decode(r) -} - -// Encode writes the failure in bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailIncorrectDetails) Encode(w *bytes.Buffer, pver uint32) error { - if err := WriteMilliSatoshi(w, f.amount); err != nil { - return err - } - - if err := WriteUint32(w, f.height); err != nil { - return err - } - - return f.extraOpaqueData.Encode(w) -} - -// FailFinalExpiryTooSoon is returned if the cltv_expiry is too low, the final -// node MUST fail the HTLC. -// -// NOTE: May only be returned by the final node in the path. -type FailFinalExpiryTooSoon struct{} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailFinalExpiryTooSoon) Code() FailCode { - return CodeFinalExpiryTooSoon -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailFinalExpiryTooSoon) Error() string { - return f.Code().String() -} - -// NewFinalExpiryTooSoon creates new instance of the FailFinalExpiryTooSoon. -func NewFinalExpiryTooSoon() *FailFinalExpiryTooSoon { - return &FailFinalExpiryTooSoon{} -} - -// FailInvalidOnionVersion is returned if the onion version byte is unknown. -// -// NOTE: May be returned only by intermediate nodes. -type FailInvalidOnionVersion struct { - // OnionSHA256 hash of the onion blob which haven't been proceeded. - OnionSHA256 [sha256.Size]byte -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailInvalidOnionVersion) Error() string { - return fmt.Sprintf("InvalidOnionVersion(onion_sha=%x)", f.OnionSHA256[:]) -} - -// NewInvalidOnionVersion creates new instance of the FailInvalidOnionVersion. -func NewInvalidOnionVersion(onion []byte) *FailInvalidOnionVersion { - return &FailInvalidOnionVersion{OnionSHA256: sha256.Sum256(onion)} -} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailInvalidOnionVersion) Code() FailCode { - return CodeInvalidOnionVersion -} - -// Decode decodes the failure from bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailInvalidOnionVersion) Decode(r io.Reader, pver uint32) error { - return ReadElement(r, f.OnionSHA256[:]) -} - -// Encode writes the failure in bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailInvalidOnionVersion) Encode(w *bytes.Buffer, pver uint32) error { - return WriteBytes(w, f.OnionSHA256[:]) -} - -// FailInvalidOnionHmac is return if the onion HMAC is incorrect. -// -// NOTE: May only be returned by intermediate nodes. -type FailInvalidOnionHmac struct { - // OnionSHA256 hash of the onion blob which haven't been proceeded. - OnionSHA256 [sha256.Size]byte -} - -// NewInvalidOnionHmac creates new instance of the FailInvalidOnionHmac. -func NewInvalidOnionHmac(onion []byte) *FailInvalidOnionHmac { - return &FailInvalidOnionHmac{OnionSHA256: sha256.Sum256(onion)} -} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailInvalidOnionHmac) Code() FailCode { - return CodeInvalidOnionHmac -} - -// Decode decodes the failure from bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailInvalidOnionHmac) Decode(r io.Reader, pver uint32) error { - return ReadElement(r, f.OnionSHA256[:]) -} - -// Encode writes the failure in bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailInvalidOnionHmac) Encode(w *bytes.Buffer, pver uint32) error { - return WriteBytes(w, f.OnionSHA256[:]) -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailInvalidOnionHmac) Error() string { - return fmt.Sprintf("InvalidOnionHMAC(onion_sha=%x)", f.OnionSHA256[:]) -} - -// FailInvalidOnionKey is return if the ephemeral key in the onion is -// unparsable. -// -// NOTE: May only be returned by intermediate nodes. -type FailInvalidOnionKey struct { - // OnionSHA256 hash of the onion blob which haven't been proceeded. - OnionSHA256 [sha256.Size]byte -} - -// NewInvalidOnionKey creates new instance of the FailInvalidOnionKey. -func NewInvalidOnionKey(onion []byte) *FailInvalidOnionKey { - return &FailInvalidOnionKey{OnionSHA256: sha256.Sum256(onion)} -} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailInvalidOnionKey) Code() FailCode { - return CodeInvalidOnionKey -} - -// Decode decodes the failure from bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailInvalidOnionKey) Decode(r io.Reader, pver uint32) error { - return ReadElement(r, f.OnionSHA256[:]) -} - -// Encode writes the failure in bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailInvalidOnionKey) Encode(w *bytes.Buffer, pver uint32) error { - return WriteBytes(w, f.OnionSHA256[:]) -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailInvalidOnionKey) Error() string { - return fmt.Sprintf("InvalidOnionKey(onion_sha=%x)", f.OnionSHA256[:]) -} - -// parseChannelUpdateCompatibilityMode will attempt to parse a channel updated -// encoded into an onion error payload in two ways. First, we'll try the -// compatibility oriented version wherein we'll _skip_ the length prefixing on -// the channel update message. Older versions of c-lighting do this so we'll -// attempt to parse these messages in order to retain compatibility. If we're -// unable to pull out a fully valid version, then we'll fall back to the -// regular parsing mechanism which includes the length prefix an NO type byte. -func parseChannelUpdateCompatibilityMode(reader io.Reader, length uint16, - chanUpdate *ChannelUpdate1, pver uint32) error { - - // Instantiate a LimitReader because there may be additional data - // present after the channel update. Without limiting the stream, the - // additional data would be interpreted as channel update tlv data. - limitReader := io.LimitReader(reader, int64(length)) - - r := bufio.NewReader(limitReader) - - // We'll peek out two bytes from the buffer without advancing the - // buffer so we can decide how to parse the remainder of it. - maybeTypeBytes, err := r.Peek(2) - if err != nil { - return err - } - - // Some nodes well prefix an additional set of bytes in front of their - // channel updates. These bytes will _almost_ always be 258 or the type - // of the ChannelUpdate message. - typeInt := binary.BigEndian.Uint16(maybeTypeBytes) - if typeInt == MsgChannelUpdate { - // At this point it's likely the case that this is a channel - // update message with its type prefixed, so we'll snip off the - // first two bytes and parse it as normal. - var throwAwayTypeBytes [2]byte - _, err := r.Read(throwAwayTypeBytes[:]) - if err != nil { - return err - } - } - - // At this pint, we've either decided to keep the entire thing, or snip - // off the first two bytes. In either case, we can just read it as - // normal. - return chanUpdate.Decode(r, pver) -} - -// FailTemporaryChannelFailure is if an otherwise unspecified transient error -// occurs for the outgoing channel (eg. channel capacity reached, too many -// in-flight htlcs) -// -// NOTE: May only be returned by intermediate nodes. -type FailTemporaryChannelFailure struct { - // Update is used to update information about state of the channel - // which caused the failure. - // - // NOTE: This field is optional. - Update *ChannelUpdate1 -} - -// NewTemporaryChannelFailure creates new instance of the FailTemporaryChannelFailure. -func NewTemporaryChannelFailure( - update *ChannelUpdate1) *FailTemporaryChannelFailure { - - return &FailTemporaryChannelFailure{Update: update} -} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailTemporaryChannelFailure) Code() FailCode { - return CodeTemporaryChannelFailure -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailTemporaryChannelFailure) Error() string { - if f.Update == nil { - return f.Code().String() - } - - return fmt.Sprintf("TemporaryChannelFailure(update=%v)", - spew.Sdump(f.Update)) -} - -// Decode decodes the failure from bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailTemporaryChannelFailure) Decode(r io.Reader, pver uint32) error { - var length uint16 - err := ReadElement(r, &length) - if err != nil { - return err - } - - if length != 0 { - f.Update = &ChannelUpdate1{} - - return parseChannelUpdateCompatibilityMode( - r, length, f.Update, pver, - ) - } - - return nil -} - -// Encode writes the failure in bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailTemporaryChannelFailure) Encode(w *bytes.Buffer, - pver uint32) error { - - if f.Update != nil { - return writeOnionErrorChanUpdate(w, f.Update, pver) - } - - // Write zero length to indicate no channel_update is present. - return WriteUint16(w, 0) -} - -// FailAmountBelowMinimum is returned if the HTLC does not reach the current -// minimum amount, we tell them the amount of the incoming HTLC and the current -// channel setting for the outgoing channel. -// -// NOTE: May only be returned by the intermediate nodes in the path. -type FailAmountBelowMinimum struct { - // HtlcMsat is the wrong amount of the incoming HTLC. - HtlcMsat MilliSatoshi - - // Update is used to update information about state of the channel - // which caused the failure. - Update ChannelUpdate1 -} - -// NewAmountBelowMinimum creates new instance of the FailAmountBelowMinimum. -func NewAmountBelowMinimum(htlcMsat MilliSatoshi, - update ChannelUpdate1) *FailAmountBelowMinimum { - - return &FailAmountBelowMinimum{ - HtlcMsat: htlcMsat, - Update: update, - } -} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailAmountBelowMinimum) Code() FailCode { - return CodeAmountBelowMinimum -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailAmountBelowMinimum) Error() string { - return fmt.Sprintf("AmountBelowMinimum(amt=%v, update=%v", f.HtlcMsat, - spew.Sdump(f.Update)) -} - -// Decode decodes the failure from bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailAmountBelowMinimum) Decode(r io.Reader, pver uint32) error { - if err := ReadElement(r, &f.HtlcMsat); err != nil { - return err - } - - var length uint16 - if err := ReadElement(r, &length); err != nil { - return err - } - - f.Update = ChannelUpdate1{} - - return parseChannelUpdateCompatibilityMode( - r, length, &f.Update, pver, - ) -} - -// Encode writes the failure in bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailAmountBelowMinimum) Encode(w *bytes.Buffer, pver uint32) error { - if err := WriteMilliSatoshi(w, f.HtlcMsat); err != nil { - return err - } - - return writeOnionErrorChanUpdate(w, &f.Update, pver) -} - -// FailFeeInsufficient is returned if the HTLC does not pay sufficient fee, we -// tell them the amount of the incoming HTLC and the current channel setting -// for the outgoing channel. -// -// NOTE: May only be returned by intermediate nodes. -type FailFeeInsufficient struct { - // HtlcMsat is the wrong amount of the incoming HTLC. - HtlcMsat MilliSatoshi - - // Update is used to update information about state of the channel - // which caused the failure. - Update ChannelUpdate1 -} - -// NewFeeInsufficient creates new instance of the FailFeeInsufficient. -func NewFeeInsufficient(htlcMsat MilliSatoshi, - update ChannelUpdate1) *FailFeeInsufficient { - return &FailFeeInsufficient{ - HtlcMsat: htlcMsat, - Update: update, - } -} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailFeeInsufficient) Code() FailCode { - return CodeFeeInsufficient -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailFeeInsufficient) Error() string { - return fmt.Sprintf("FeeInsufficient(htlc_amt==%v, update=%v", f.HtlcMsat, - spew.Sdump(f.Update)) -} - -// Decode decodes the failure from bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailFeeInsufficient) Decode(r io.Reader, pver uint32) error { - if err := ReadElement(r, &f.HtlcMsat); err != nil { - return err - } - - var length uint16 - if err := ReadElement(r, &length); err != nil { - return err - } - - f.Update = ChannelUpdate1{} - - return parseChannelUpdateCompatibilityMode( - r, length, &f.Update, pver, - ) -} - -// Encode writes the failure in bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailFeeInsufficient) Encode(w *bytes.Buffer, pver uint32) error { - if err := WriteMilliSatoshi(w, f.HtlcMsat); err != nil { - return err - } - - return writeOnionErrorChanUpdate(w, &f.Update, pver) -} - -// FailIncorrectCltvExpiry is returned if outgoing cltv value does not match -// the update add htlc's cltv expiry minus cltv expiry delta for the outgoing -// channel, we tell them the cltv expiry and the current channel setting for -// the outgoing channel. -// -// NOTE: May only be returned by intermediate nodes. -type FailIncorrectCltvExpiry struct { - // CltvExpiry is the wrong absolute timeout in blocks, after which - // outgoing HTLC expires. - CltvExpiry uint32 - - // Update is used to update information about state of the channel - // which caused the failure. - Update ChannelUpdate1 -} - -// NewIncorrectCltvExpiry creates new instance of the FailIncorrectCltvExpiry. -func NewIncorrectCltvExpiry(cltvExpiry uint32, - update ChannelUpdate1) *FailIncorrectCltvExpiry { - - return &FailIncorrectCltvExpiry{ - CltvExpiry: cltvExpiry, - Update: update, - } -} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailIncorrectCltvExpiry) Code() FailCode { - return CodeIncorrectCltvExpiry -} - -func (f *FailIncorrectCltvExpiry) Error() string { - return fmt.Sprintf("IncorrectCltvExpiry(expiry=%v, update=%v", - f.CltvExpiry, spew.Sdump(f.Update)) -} - -// Decode decodes the failure from bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailIncorrectCltvExpiry) Decode(r io.Reader, pver uint32) error { - if err := ReadElement(r, &f.CltvExpiry); err != nil { - return err - } - - var length uint16 - if err := ReadElement(r, &length); err != nil { - return err - } - - f.Update = ChannelUpdate1{} - - return parseChannelUpdateCompatibilityMode( - r, length, &f.Update, pver, - ) -} - -// Encode writes the failure in bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailIncorrectCltvExpiry) Encode(w *bytes.Buffer, pver uint32) error { - if err := WriteUint32(w, f.CltvExpiry); err != nil { - return err - } - - return writeOnionErrorChanUpdate(w, &f.Update, pver) -} - -// FailExpiryTooSoon is returned if the ctlv-expiry is too near, we tell them -// the current channel setting for the outgoing channel. -// -// NOTE: May only be returned by intermediate nodes. -type FailExpiryTooSoon struct { - // Update is used to update information about state of the channel - // which caused the failure. - Update ChannelUpdate1 -} - -// NewExpiryTooSoon creates new instance of the FailExpiryTooSoon. -func NewExpiryTooSoon(update ChannelUpdate1) *FailExpiryTooSoon { - return &FailExpiryTooSoon{ - Update: update, - } -} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailExpiryTooSoon) Code() FailCode { - return CodeExpiryTooSoon -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailExpiryTooSoon) Error() string { - return fmt.Sprintf("ExpiryTooSoon(update=%v", spew.Sdump(f.Update)) -} - -// Decode decodes the failure from l stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailExpiryTooSoon) Decode(r io.Reader, pver uint32) error { - var length uint16 - if err := ReadElement(r, &length); err != nil { - return err - } - - f.Update = ChannelUpdate1{} - - return parseChannelUpdateCompatibilityMode( - r, length, &f.Update, pver, - ) -} - -// Encode writes the failure in bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailExpiryTooSoon) Encode(w *bytes.Buffer, pver uint32) error { - return writeOnionErrorChanUpdate(w, &f.Update, pver) -} - -// FailChannelDisabled is returned if the channel is disabled, we tell them the -// current channel setting for the outgoing channel. -// -// NOTE: May only be returned by intermediate nodes. -type FailChannelDisabled struct { - // Flags least-significant bit must be set to 0 if the creating node - // corresponds to the first node in the previously sent channel - // announcement and 1 otherwise. - Flags uint16 - - // Update is used to update information about state of the channel - // which caused the failure. - Update ChannelUpdate1 -} - -// NewChannelDisabled creates new instance of the FailChannelDisabled. -func NewChannelDisabled(flags uint16, - update ChannelUpdate1) *FailChannelDisabled { - - return &FailChannelDisabled{ - Flags: flags, - Update: update, - } -} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailChannelDisabled) Code() FailCode { - return CodeChannelDisabled -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailChannelDisabled) Error() string { - return fmt.Sprintf("ChannelDisabled(flags=%v, update=%v", f.Flags, - spew.Sdump(f.Update)) -} - -// Decode decodes the failure from bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailChannelDisabled) Decode(r io.Reader, pver uint32) error { - if err := ReadElement(r, &f.Flags); err != nil { - return err - } - - var length uint16 - if err := ReadElement(r, &length); err != nil { - return err - } - - f.Update = ChannelUpdate1{} - - return parseChannelUpdateCompatibilityMode( - r, length, &f.Update, pver, - ) -} - -// Encode writes the failure in bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailChannelDisabled) Encode(w *bytes.Buffer, pver uint32) error { - if err := WriteUint16(w, f.Flags); err != nil { - return err - } - - return writeOnionErrorChanUpdate(w, &f.Update, pver) -} - -// FailFinalIncorrectCltvExpiry is returned if the outgoing_cltv_value does not -// match the ctlv_expiry of the HTLC at the final hop. -// -// NOTE: might be returned by final node only. -type FailFinalIncorrectCltvExpiry struct { - // CltvExpiry is the wrong absolute timeout in blocks, after which - // outgoing HTLC expires. - CltvExpiry uint32 -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailFinalIncorrectCltvExpiry) Error() string { - return fmt.Sprintf("FinalIncorrectCltvExpiry(expiry=%v)", f.CltvExpiry) -} - -// NewFinalIncorrectCltvExpiry creates new instance of the -// FailFinalIncorrectCltvExpiry. -func NewFinalIncorrectCltvExpiry(cltvExpiry uint32) *FailFinalIncorrectCltvExpiry { - return &FailFinalIncorrectCltvExpiry{ - CltvExpiry: cltvExpiry, - } -} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailFinalIncorrectCltvExpiry) Code() FailCode { - return CodeFinalIncorrectCltvExpiry -} - -// Decode decodes the failure from bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailFinalIncorrectCltvExpiry) Decode(r io.Reader, pver uint32) error { - return ReadElement(r, &f.CltvExpiry) -} - -// Encode writes the failure in bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailFinalIncorrectCltvExpiry) Encode(w *bytes.Buffer, - pver uint32) error { - - return WriteUint32(w, f.CltvExpiry) -} - -// FailFinalIncorrectHtlcAmount is returned if the amt_to_forward is higher -// than incoming_htlc_amt of the HTLC at the final hop. -// -// NOTE: May only be returned by the final node. -type FailFinalIncorrectHtlcAmount struct { - // IncomingHTLCAmount is the wrong forwarded htlc amount. - IncomingHTLCAmount MilliSatoshi -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailFinalIncorrectHtlcAmount) Error() string { - return fmt.Sprintf("FinalIncorrectHtlcAmount(amt=%v)", - f.IncomingHTLCAmount) -} - -// NewFinalIncorrectHtlcAmount creates new instance of the -// FailFinalIncorrectHtlcAmount. -func NewFinalIncorrectHtlcAmount(amount MilliSatoshi) *FailFinalIncorrectHtlcAmount { - return &FailFinalIncorrectHtlcAmount{ - IncomingHTLCAmount: amount, - } -} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailFinalIncorrectHtlcAmount) Code() FailCode { - return CodeFinalIncorrectHtlcAmount -} - -// Decode decodes the failure from bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailFinalIncorrectHtlcAmount) Decode(r io.Reader, pver uint32) error { - return ReadElement(r, &f.IncomingHTLCAmount) -} - -// Encode writes the failure in bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailFinalIncorrectHtlcAmount) Encode(w *bytes.Buffer, - pver uint32) error { - - return WriteMilliSatoshi(w, f.IncomingHTLCAmount) -} - -// FailExpiryTooFar is returned if the CLTV expiry in the HTLC is too far in the -// future. -// -// NOTE: May be returned by any node in the payment route. -type FailExpiryTooFar struct{} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailExpiryTooFar) Code() FailCode { - return CodeExpiryTooFar -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailExpiryTooFar) Error() string { - return f.Code().String() -} - -// InvalidOnionPayload is returned if the hop could not process the TLV payload -// enclosed in the onion. -type InvalidOnionPayload struct { - // Type is the TLV type that caused the specific failure. - Type uint64 - - // Offset is the byte offset within the payload where the failure - // occurred. - Offset uint16 -} - -// NewInvalidOnionPayload initializes a new InvalidOnionPayload failure. -func NewInvalidOnionPayload(typ uint64, offset uint16) *InvalidOnionPayload { - return &InvalidOnionPayload{ - Type: typ, - Offset: offset, - } -} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *InvalidOnionPayload) Code() FailCode { - return CodeInvalidOnionPayload -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *InvalidOnionPayload) Error() string { - return fmt.Sprintf("%v(type=%v, offset=%d)", - f.Code(), f.Type, f.Offset) -} - -// Decode decodes the failure from bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *InvalidOnionPayload) Decode(r io.Reader, pver uint32) error { - var buf [8]byte - typ, err := tlv.ReadVarInt(r, &buf) - if err != nil { - return err - } - f.Type = typ - - return ReadElements(r, &f.Offset) -} - -// Encode writes the failure in bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *InvalidOnionPayload) Encode(w *bytes.Buffer, pver uint32) error { - var buf [8]byte - if err := tlv.WriteVarInt(w, f.Type, &buf); err != nil { - return err - } - - return WriteUint16(w, f.Offset) -} - -// FailMPPTimeout is returned if the complete amount for a multi part payment -// was not received within a reasonable time. -// -// NOTE: May only be returned by the final node in the path. -type FailMPPTimeout struct{} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailMPPTimeout) Code() FailCode { - return CodeMPPTimeout -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailMPPTimeout) Error() string { - return f.Code().String() -} - -// FailInvalidBlinding is returned if there has been a route blinding related -// error. -type FailInvalidBlinding struct { - OnionSHA256 [sha256.Size]byte -} - -// Code returns the failure unique code. -// -// NOTE: Part of the FailureMessage interface. -func (f *FailInvalidBlinding) Code() FailCode { - return CodeInvalidBlinding -} - -// Returns a human readable string describing the target FailureMessage. -// -// NOTE: Implements the error interface. -func (f *FailInvalidBlinding) Error() string { - return f.Code().String() -} - -// Decode decodes the failure from bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailInvalidBlinding) Decode(r io.Reader, _ uint32) error { - return ReadElement(r, f.OnionSHA256[:]) -} - -// Encode writes the failure in bytes stream. -// -// NOTE: Part of the Serializable interface. -func (f *FailInvalidBlinding) Encode(w *bytes.Buffer, _ uint32) error { - return WriteBytes(w, f.OnionSHA256[:]) -} - -// NewInvalidBlinding creates new instance of FailInvalidBlinding. -func NewInvalidBlinding( - onion fn.Option[[OnionPacketSize]byte]) *FailInvalidBlinding { - // The spec allows empty onion hashes for invalid blinding, so we only - // include our onion hash if it's provided. - if onion.IsNone() { - return &FailInvalidBlinding{} - } - - shaSum := fn.MapOptionZ(onion, func(o [OnionPacketSize]byte) [32]byte { - return sha256.Sum256(o[:]) - }) - - return &FailInvalidBlinding{OnionSHA256: shaSum} -} - -// DecodeFailure decodes, validates, and parses the lnwire onion failure, for -// the provided protocol version. -func DecodeFailure(r io.Reader, pver uint32) (FailureMessage, error) { - // First, we'll parse out the encapsulated failure message itself. This - // is a 2 byte length followed by the payload itself. - var failureLength uint16 - if err := ReadElement(r, &failureLength); err != nil { - return nil, fmt.Errorf("unable to read failure len: %w", err) - } - - failureData := make([]byte, failureLength) - if _, err := io.ReadFull(r, failureData); err != nil { - return nil, fmt.Errorf("unable to full read payload of "+ - "%v: %w", failureLength, err) - } - - // Read the padding. - var padLength uint16 - if err := ReadElement(r, &padLength); err != nil { - return nil, fmt.Errorf("unable to read pad len: %w", err) - } - - if _, err := io.CopyN(io.Discard, r, int64(padLength)); err != nil { - return nil, fmt.Errorf("unable to read padding %w", err) - } - - // Verify that we are at the end of the stream now. - scratch := make([]byte, 1) - _, err := r.Read(scratch) - if err != io.EOF { - return nil, fmt.Errorf("unexpected failure bytes") - } - - // Check the total length. Convert to 32 bits to prevent overflow. - totalLength := uint32(padLength) + uint32(failureLength) - if totalLength < FailureMessageLength { - return nil, fmt.Errorf("failure message too short: "+ - "msg=%v, pad=%v, total=%v", - failureLength, padLength, totalLength) - } - - // Decode the failure message. - dataReader := bytes.NewReader(failureData) - - return DecodeFailureMessage(dataReader, pver) -} - -// DecodeFailureMessage decodes just the failure message, ignoring any padding -// that may be present at the end. -func DecodeFailureMessage(r io.Reader, pver uint32) (FailureMessage, error) { - // Once we have the failure data, we can obtain the failure code from - // the first two bytes of the buffer. - var codeBytes [2]byte - if _, err := io.ReadFull(r, codeBytes[:]); err != nil { - return nil, fmt.Errorf("unable to read failure code: %w", err) - } - failCode := FailCode(binary.BigEndian.Uint16(codeBytes[:])) - - // Create the empty failure by given code and populate the failure with - // additional data if needed. - failure, err := makeEmptyOnionError(failCode) - if err != nil { - return nil, fmt.Errorf("unable to make empty error: %w", err) - } - - // Finally, if this failure has a payload, then we'll read that now as - // well. - switch f := failure.(type) { - case Serializable: - if err := f.Decode(r, pver); err != nil { - return nil, fmt.Errorf("unable to decode error "+ - "update (type=%T): %w", failure, err) - } - } - - return failure, nil -} - -// EncodeFailure encodes, including the necessary onion failure header -// information. -func EncodeFailure(w *bytes.Buffer, failure FailureMessage, pver uint32) error { - var failureMessageBuffer bytes.Buffer - - err := EncodeFailureMessage(&failureMessageBuffer, failure, pver) - if err != nil { - return err - } - - // The combined size of this message must be below the max allowed - // failure message length. - failureMessage := failureMessageBuffer.Bytes() - if len(failureMessage) > FailureMessageLength { - return fmt.Errorf("failure message exceed max "+ - "available size: %v", len(failureMessage)) - } - - // Finally, we'll add some padding in order to ensure that all failure - // messages are fixed size. - pad := make([]byte, FailureMessageLength-len(failureMessage)) - - if err := WriteUint16(w, uint16(len(failureMessage))); err != nil { - return err - } - - if err := WriteBytes(w, failureMessage); err != nil { - return err - } - if err := WriteUint16(w, uint16(len(pad))); err != nil { - return err - } - - return WriteBytes(w, pad) -} - -// EncodeFailureMessage encodes just the failure message without adding a length -// and padding the message for the onion protocol. -func EncodeFailureMessage(w *bytes.Buffer, - failure FailureMessage, pver uint32) error { - - // First, we'll write out the error code itself into the failure - // buffer. - var codeBytes [2]byte - code := uint16(failure.Code()) - binary.BigEndian.PutUint16(codeBytes[:], code) - _, err := w.Write(codeBytes[:]) - if err != nil { - return err - } - - // Next, some message have an additional message payload, if this is - // one of those types, then we'll also encode the error payload as - // well. - switch failure := failure.(type) { - case Serializable: - if err := failure.Encode(w, pver); err != nil { - return err - } - } - - return nil -} - -// makeEmptyOnionError creates a new empty onion error of the proper concrete -// type based on the passed failure code. -func makeEmptyOnionError(code FailCode) (FailureMessage, error) { - switch code { - case CodeInvalidRealm: - return &FailInvalidRealm{}, nil - - case CodeTemporaryNodeFailure: - return &FailTemporaryNodeFailure{}, nil - - case CodePermanentNodeFailure: - return &FailPermanentNodeFailure{}, nil - - case CodeRequiredNodeFeatureMissing: - return &FailRequiredNodeFeatureMissing{}, nil - - case CodePermanentChannelFailure: - return &FailPermanentChannelFailure{}, nil - - case CodeRequiredChannelFeatureMissing: - return &FailRequiredChannelFeatureMissing{}, nil - - case CodeUnknownNextPeer: - return &FailUnknownNextPeer{}, nil - - case CodeIncorrectOrUnknownPaymentDetails: - return &FailIncorrectDetails{}, nil - - case CodeIncorrectPaymentAmount: - return &FailIncorrectPaymentAmount{}, nil - - case CodeFinalExpiryTooSoon: - return &FailFinalExpiryTooSoon{}, nil - - case CodeInvalidOnionVersion: - return &FailInvalidOnionVersion{}, nil - - case CodeInvalidOnionHmac: - return &FailInvalidOnionHmac{}, nil - - case CodeInvalidOnionKey: - return &FailInvalidOnionKey{}, nil - - case CodeTemporaryChannelFailure: - return &FailTemporaryChannelFailure{}, nil - - case CodeAmountBelowMinimum: - return &FailAmountBelowMinimum{}, nil - - case CodeFeeInsufficient: - return &FailFeeInsufficient{}, nil - - case CodeIncorrectCltvExpiry: - return &FailIncorrectCltvExpiry{}, nil - - case CodeExpiryTooSoon: - return &FailExpiryTooSoon{}, nil - - case CodeChannelDisabled: - return &FailChannelDisabled{}, nil - - case CodeFinalIncorrectCltvExpiry: - return &FailFinalIncorrectCltvExpiry{}, nil - - case CodeFinalIncorrectHtlcAmount: - return &FailFinalIncorrectHtlcAmount{}, nil - - case CodeExpiryTooFar: - return &FailExpiryTooFar{}, nil - - case CodeInvalidOnionPayload: - return &InvalidOnionPayload{}, nil - - case CodeMPPTimeout: - return &FailMPPTimeout{}, nil - - case CodeInvalidBlinding: - return &FailInvalidBlinding{}, nil - - default: - return nil, fmt.Errorf("unknown error code: %v", code) - } -} - -// writeOnionErrorChanUpdate writes out a ChannelUpdate using the onion error -// format. The format is that we first write out the true serialized length of -// the channel update, followed by the serialized channel update itself. -func writeOnionErrorChanUpdate(w *bytes.Buffer, chanUpdate *ChannelUpdate1, - pver uint32) error { - - // First, we encode the channel update in a temporary buffer in order - // to get the exact serialized size. - var b bytes.Buffer - updateLen, err := WriteMessage(&b, chanUpdate, pver) - if err != nil { - return err - } - - // Now that we know the size, we can write the length out in the main - // writer. - if err := WriteUint16(w, uint16(updateLen)); err != nil { - return err - } - - // With the length written, we'll then write out the serialized channel - // update. - if _, err := w.Write(b.Bytes()); err != nil { - return err - } - - return nil -} diff --git a/payments/db/migration1/lnwire/short_channel_id.go b/payments/db/migration1/lnwire/short_channel_id.go deleted file mode 100644 index e26575001..000000000 --- a/payments/db/migration1/lnwire/short_channel_id.go +++ /dev/null @@ -1,105 +0,0 @@ -package lnwire - -import ( - "fmt" - "io" - - "github.com/lightningnetwork/lnd/tlv" -) - -const ( - // AliasScidRecordType is the type of the experimental record to denote - // the alias being used in an option_scid_alias channel. - AliasScidRecordType tlv.Type = 1 -) - -// ShortChannelID represents the set of data which is needed to retrieve all -// necessary data to validate the channel existence. -type ShortChannelID struct { - // BlockHeight is the height of the block where funding transaction - // located. - // - // NOTE: This field is limited to 3 bytes. - BlockHeight uint32 - - // TxIndex is a position of funding transaction within a block. - // - // NOTE: This field is limited to 3 bytes. - TxIndex uint32 - - // TxPosition indicating transaction output which pays to the channel. - TxPosition uint16 -} - -// NewShortChanIDFromInt returns a new ShortChannelID which is the decoded -// version of the compact channel ID encoded within the uint64. The format of -// the compact channel ID is as follows: 3 bytes for the block height, 3 bytes -// for the transaction index, and 2 bytes for the output index. -func NewShortChanIDFromInt(chanID uint64) ShortChannelID { - return ShortChannelID{ - BlockHeight: uint32(chanID >> 40), - TxIndex: uint32(chanID>>16) & 0xFFFFFF, - TxPosition: uint16(chanID), - } -} - -// ToUint64 converts the ShortChannelID into a compact format encoded within a -// uint64 (8 bytes). -func (c ShortChannelID) ToUint64() uint64 { - // TODO(roasbeef): explicit error on overflow? - return ((uint64(c.BlockHeight) << 40) | (uint64(c.TxIndex) << 16) | - (uint64(c.TxPosition))) -} - -// String generates a human-readable representation of the channel ID. -func (c ShortChannelID) String() string { - return fmt.Sprintf("%d:%d:%d", c.BlockHeight, c.TxIndex, c.TxPosition) -} - -// AltString generates a human-readable representation of the channel ID -// with 'x' as a separator. -func (c ShortChannelID) AltString() string { - return fmt.Sprintf("%dx%dx%d", c.BlockHeight, c.TxIndex, c.TxPosition) -} - -// Record returns a TLV record that can be used to encode/decode a -// ShortChannelID to/from a TLV stream. -func (c *ShortChannelID) Record() tlv.Record { - return tlv.MakeStaticRecord( - AliasScidRecordType, c, 8, EShortChannelID, DShortChannelID, - ) -} - -// IsDefault returns true if the ShortChannelID represents the zero value for -// its type. -func (c ShortChannelID) IsDefault() bool { - return c == ShortChannelID{} -} - -// EShortChannelID is an encoder for ShortChannelID. It is exported so other -// packages can use the encoding scheme. -func EShortChannelID(w io.Writer, val interface{}, buf *[8]byte) error { - if v, ok := val.(*ShortChannelID); ok { - return tlv.EUint64T(w, v.ToUint64(), buf) - } - return tlv.NewTypeForEncodingErr(val, "lnwire.ShortChannelID") -} - -// DShortChannelID is a decoder for ShortChannelID. It is exported so other -// packages can use the decoding scheme. -func DShortChannelID(r io.Reader, val interface{}, buf *[8]byte, - l uint64) error { - - if v, ok := val.(*ShortChannelID); ok { - var scid uint64 - // tlv.DUint64 forces the length to be 8 bytes. - err := tlv.DUint64(r, &scid, buf, l) - if err != nil { - return err - } - - *v = NewShortChanIDFromInt(scid) - return nil - } - return tlv.NewTypeForDecodingErr(val, "lnwire.ShortChannelID", l, 8) -} diff --git a/payments/db/migration1/lnwire/signature.go b/payments/db/migration1/lnwire/signature.go deleted file mode 100644 index 35b039ed0..000000000 --- a/payments/db/migration1/lnwire/signature.go +++ /dev/null @@ -1,292 +0,0 @@ -package lnwire - -import ( - "errors" - "fmt" - - "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/lightningnetwork/lnd/input" - "github.com/lightningnetwork/lnd/tlv" -) - -var ( - errSigTooShort = errors.New("malformed signature: too short") - errBadLength = errors.New("malformed signature: bad length") - errBadRLength = errors.New("malformed signature: bogus R length") - errBadSLength = errors.New("malformed signature: bogus S length") - errRTooLong = errors.New("R is over 32 bytes long without padding") - errSTooLong = errors.New("S is over 32 bytes long without padding") -) - -// sigType represents the type of signature that is carried within the Sig. -// Today this can either be an ECDSA sig or a schnorr sig. Both of these can -// fit cleanly into 64 bytes. -type sigType uint - -const ( - // sigTypeECDSA represents an ECDSA signature. - sigTypeECDSA sigType = iota - - // sigTypeSchnorr represents a schnorr signature. - sigTypeSchnorr -) - -// Sig is a fixed-sized ECDSA signature or 64-byte schnorr signature. For the -// ECDSA sig, unlike Bitcoin, we use fixed sized signatures on the wire, -// instead of DER encoded signatures. This type provides several methods to -// convert to/from a regular Bitcoin DER encoded signature (raw bytes and -// *ecdsa.Signature). -type Sig struct { - bytes [64]byte - - sigType sigType -} - -// ForceSchnorr forces the signature to be interpreted as a schnorr signature. -// This is useful when reading an HTLC sig off the wire for a taproot channel. -// In this case, in order to obtain an input.Signature, we need to know that -// the sig is a schnorr sig. -func (s *Sig) ForceSchnorr() { - s.sigType = sigTypeSchnorr -} - -// RawBytes returns the raw bytes of signature. -func (s *Sig) RawBytes() []byte { - return s.bytes[:] -} - -// Copy copies the signature into a new Sig instance. -func (s *Sig) Copy() Sig { - var sCopy Sig - copy(sCopy.bytes[:], s.bytes[:]) - sCopy.sigType = s.sigType - - return sCopy -} - -// Record returns a Record that can be used to encode or decode the backing -// object. -// -// This returns a record that serializes the sig as a 64-byte fixed size -// signature. -func (s *Sig) Record() tlv.Record { - // We set a type here as zero as it isn't needed when used as a - // RecordT. - return tlv.MakePrimitiveRecord(0, &s.bytes) -} - -// NewSigFromWireECDSA returns a Sig instance based on an ECDSA signature -// that's already in the 64-byte format we expect. -func NewSigFromWireECDSA(sig []byte) (Sig, error) { - if len(sig) != 64 { - return Sig{}, fmt.Errorf("%w: %v bytes", errSigTooShort, - len(sig)) - } - - var s Sig - copy(s.bytes[:], sig) - - return s, nil -} - -// NewSigFromECDSARawSignature returns a Sig from a Bitcoin raw signature -// encoded in the canonical DER encoding. -func NewSigFromECDSARawSignature(sig []byte) (Sig, error) { - var b [64]byte - - // Check the total length is above the minimal. - if len(sig) < ecdsa.MinSigLen { - return Sig{}, errSigTooShort - } - - // The DER representation is laid out as: - // 0x30 0x02 r 0x02 s - // which means the length of R is the 4th byte and the length of S is - // the second byte after R ends. 0x02 signifies a length-prefixed, - // zero-padded, big-endian bigint. 0x30 signifies a DER signature. - // See the Serialize() method for ecdsa.Signature for details. - - // Reading , remaining: [0x02 r 0x02 s] - sigLen := int(sig[1]) - - // siglen should be less than the entire message and greater than - // the minimal message size. - if sigLen+2 > len(sig) || sigLen+2 < ecdsa.MinSigLen { - return Sig{}, errBadLength - } - - // Reading , remaining: [r 0x02 s] - rLen := int(sig[3]) - - // rLen must be positive and must be able to fit in other elements. - // Assuming s is one byte, then we have 0x30, , 0x20, - // , 0x20, , s, a total of 7 bytes. - if rLen <= 0 || rLen+7 > len(sig) { - return Sig{}, errBadRLength - } - - // Reading , remaining: [s] - sLen := int(sig[5+rLen]) - - // S should be the rest of the string. - // sLen must be positive and must be able to fit in other elements. - // We know r is rLen bytes, and we have 0x30, , 0x20, - // , 0x20, , a total of rLen+6 bytes. - if sLen <= 0 || sLen+rLen+6 > len(sig) { - return Sig{}, errBadSLength - } - - // Check to make sure R and S can both fit into their intended buffers. - // We check S first because these code blocks decrement sLen and rLen - // in the case of a 33-byte 0-padded integer returned from Serialize() - // and rLen is used in calculating array indices for S. We can track - // this with additional variables, but it's more efficient to just - // check S first. - if sLen > 32 { - if (sLen > 33) || (sig[6+rLen] != 0x00) { - return Sig{}, errSTooLong - } - sLen-- - copy(b[64-sLen:], sig[7+rLen:]) - } else { - copy(b[64-sLen:], sig[6+rLen:]) - } - - // Do the same for R as we did for S - if rLen > 32 { - if (rLen > 33) || (sig[4] != 0x00) { - return Sig{}, errRTooLong - } - rLen-- - copy(b[32-rLen:], sig[5:5+rLen]) - } else { - copy(b[32-rLen:], sig[4:4+rLen]) - } - - return Sig{ - bytes: b, - sigType: sigTypeECDSA, - }, nil -} - -// NewSigFromSchnorrRawSignature converts a raw schnorr signature into an -// lnwire.Sig. -func NewSigFromSchnorrRawSignature(sig []byte) (Sig, error) { - var s Sig - copy(s.bytes[:], sig) - s.sigType = sigTypeSchnorr - - return s, nil -} - -// NewSigFromSignature creates a new signature as used on the wire, from an -// existing ecdsa.Signature or schnorr.Signature. -func NewSigFromSignature(e input.Signature) (Sig, error) { - if e == nil { - return Sig{}, fmt.Errorf("cannot decode empty signature") - } - - // Nil is still a valid interface, apparently. So we need a more - // explicit check here. - if ecsig, ok := e.(*ecdsa.Signature); ok && ecsig == nil { - return Sig{}, fmt.Errorf("cannot decode empty signature") - } - - switch ecSig := e.(type) { - // If this is a schnorr signature, then we can just pack it as normal, - // since the default encoding is already 64 bytes. - case *schnorr.Signature: - return NewSigFromSchnorrRawSignature(e.Serialize()) - - // For ECDSA signatures, we'll need to do a bit more work to map the - // signature into a compact 64 byte form. - case *ecdsa.Signature: - // Serialize the signature with all the checks that entails. - return NewSigFromECDSARawSignature(e.Serialize()) - - default: - return Sig{}, fmt.Errorf("unknown wire sig type: %T", ecSig) - } -} - -// ToSignature converts the fixed-sized signature to a input.Signature which -// can be used for signature validation checks. -func (s *Sig) ToSignature() (input.Signature, error) { - switch s.sigType { - case sigTypeSchnorr: - return schnorr.ParseSignature(s.bytes[:]) - - case sigTypeECDSA: - // Parse the signature with strict checks. - sigBytes := s.ToSignatureBytes() - sig, err := ecdsa.ParseDERSignature(sigBytes) - if err != nil { - return nil, err - } - - return sig, nil - - default: - return nil, fmt.Errorf("unknown sig type: %v", s.sigType) - } -} - -// ToSignatureBytes serializes the target fixed-sized signature into the -// encoding of the primary domain for the signature. For ECDSA signatures, this -// is the raw bytes of a DER encoding. -func (s *Sig) ToSignatureBytes() []byte { - switch s.sigType { - // For ECDSA signatures, we'll convert to DER encoding. - case sigTypeECDSA: - // Extract canonically-padded bigint representations from buffer - r := extractCanonicalPadding(s.bytes[0:32]) - s := extractCanonicalPadding(s.bytes[32:64]) - rLen := uint8(len(r)) - sLen := uint8(len(s)) - - // Create a canonical serialized signature. DER format is: - // 0x30 0x02 r 0x02 s - sigBytes := make([]byte, 6+rLen+sLen) - sigBytes[0] = 0x30 // DER signature magic value - sigBytes[1] = 4 + rLen + sLen // Length of rest of signature - sigBytes[2] = 0x02 // Big integer magic value - sigBytes[3] = rLen // Length of R - sigBytes[rLen+4] = 0x02 // Big integer magic value - sigBytes[rLen+5] = sLen // Length of S - copy(sigBytes[4:], r) // Copy R - copy(sigBytes[rLen+6:], s) // Copy S - - return sigBytes - - // For schnorr signatures, we can use the same internal 64 bytes. - case sigTypeSchnorr: - // We'll make a copy of the signature so we don't return a - // reference into the raw slice. - var sig [64]byte - copy(sig[:], s.bytes[:]) - return sig[:] - - default: - // TODO(roasbeef): can only be called via public methods so - // never reachable? - panic("sig type not set") - } -} - -// extractCanonicalPadding is a utility function to extract the canonical -// padding of a big-endian integer from the wire encoding (a 0-padded -// big-endian integer) such that it passes btcec.canonicalPadding test. -func extractCanonicalPadding(b []byte) []byte { - for i := 0; i < len(b); i++ { - // Found first non-zero byte. - if b[i] > 0 { - // If the MSB is set, we need zero padding. - if b[i]&0x80 == 0x80 { - return append([]byte{0x00}, b[i:]...) - } - return b[i:] - } - } - return []byte{0x00} -} diff --git a/payments/db/migration1/lnwire/typed_fee.go b/payments/db/migration1/lnwire/typed_fee.go deleted file mode 100644 index f9b6c8d01..000000000 --- a/payments/db/migration1/lnwire/typed_fee.go +++ /dev/null @@ -1,60 +0,0 @@ -package lnwire - -import ( - "io" - - "github.com/lightningnetwork/lnd/tlv" -) - -const ( - FeeRecordType tlv.Type = 55555 -) - -// Fee represents a fee schedule. -type Fee struct { - BaseFee int32 - FeeRate int32 -} - -// Record returns a TLV record that can be used to encode/decode the fee -// type from a given TLV stream. -func (l *Fee) Record() tlv.Record { - return tlv.MakeStaticRecord( - FeeRecordType, l, 8, feeEncoder, feeDecoder, - ) -} - -// feeEncoder is a custom TLV encoder for the fee record. -func feeEncoder(w io.Writer, val interface{}, buf *[8]byte) error { - v, ok := val.(*Fee) - if !ok { - return tlv.NewTypeForEncodingErr(val, "lnwire.Fee") - } - - if err := tlv.EUint32T(w, uint32(v.BaseFee), buf); err != nil { - return err - } - - return tlv.EUint32T(w, uint32(v.FeeRate), buf) -} - -// feeDecoder is a custom TLV decoder for the fee record. -func feeDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error { - v, ok := val.(*Fee) - if !ok || l != 8 { - return tlv.NewTypeForDecodingErr(val, "lnwire.Fee", l, 8) - } - - var baseFee, feeRate uint32 - if err := tlv.DUint32(r, &baseFee, buf, 4); err != nil { - return err - } - if err := tlv.DUint32(r, &feeRate, buf, 4); err != nil { - return err - } - - v.FeeRate = int32(feeRate) - v.BaseFee = int32(baseFee) - - return nil -} diff --git a/payments/db/migration1/lnwire/update_add_htlc.go b/payments/db/migration1/lnwire/update_add_htlc.go deleted file mode 100644 index 38ceeec21..000000000 --- a/payments/db/migration1/lnwire/update_add_htlc.go +++ /dev/null @@ -1,226 +0,0 @@ -package lnwire - -import ( - "bytes" - "io" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/tlv" -) - -const ( - // OnionPacketSize is the size of the serialized Sphinx onion packet - // included in each UpdateAddHTLC message. The breakdown of the onion - // packet is as follows: 1-byte version, 33-byte ephemeral public key - // (for ECDH), 1300-bytes of per-hop data, and a 32-byte HMAC over the - // entire packet. - OnionPacketSize = 1366 - - // ExperimentalAccountableType is the TLV type used for a custom - // record that sets an experimental accountable value. - ExperimentalAccountableType tlv.Type = 106823 - - // ExperimentalUnaccountable is the value that the experimental - // accountable field contains when a htlc is not accountable. - ExperimentalUnaccountable = 0 - - // ExperimentalAccountable is the value that the experimental - // accountable field contains when a htlc is accountable. We're using a - // single byte to represent our accountable value, but limit the value - // to using the first three bits (max value = 00000111). Interpreted as - // a uint8 (an alias for byte in go), we can just define this constant - // as 7. - ExperimentalAccountable = 7 -) - -type ( - // BlindingPointTlvType is the type for ephemeral pubkeys used in - // route blinding. - BlindingPointTlvType = tlv.TlvType0 - - // BlindingPointRecord holds an optional blinding point on update add - // htlc. - //nolint:ll - BlindingPointRecord = tlv.OptionalRecordT[BlindingPointTlvType, *btcec.PublicKey] -) - -// UpdateAddHTLC is the message sent by Alice to Bob when she wishes to add an -// HTLC to his remote commitment transaction. In addition to information -// detailing the value, the ID, expiry, and the onion blob is also included -// which allows Bob to derive the next hop in the route. The HTLC added by this -// message is to be added to the remote node's "pending" HTLCs. A subsequent -// CommitSig message will move the pending HTLC to the newly created commitment -// transaction, marking them as "staged". -type UpdateAddHTLC struct { - // ChanID is the particular active channel that this UpdateAddHTLC is - // bound to. - ChanID ChannelID - - // ID is the identification server for this HTLC. This value is - // explicitly included as it allows nodes to survive single-sided - // restarts. The ID value for this sides starts at zero, and increases - // with each offered HTLC. - ID uint64 - - // Amount is the amount of millisatoshis this HTLC is worth. - Amount MilliSatoshi - - // PaymentHash is the payment hash to be included in the HTLC this - // request creates. The pre-image to this HTLC must be revealed by the - // upstream peer in order to fully settle the HTLC. - PaymentHash [32]byte - - // Expiry is the number of blocks after which this HTLC should expire. - // It is the receiver's duty to ensure that the outgoing HTLC has a - // sufficient expiry value to allow her to redeem the incoming HTLC. - Expiry uint32 - - // OnionBlob is the raw serialized mix header used to route an HTLC in - // a privacy-preserving manner. The mix header is defined currently to - // be parsed as a 4-tuple: (groupElement, routingInfo, headerMAC, - // body). First the receiving node should use the groupElement, and - // its current onion key to derive a shared secret with the source. - // Once the shared secret has been derived, the headerMAC should be - // checked FIRST. Note that the MAC only covers the routingInfo field. - // If the MAC matches, and the shared secret is fresh, then the node - // should strip off a layer of encryption, exposing the next hop to be - // used in the subsequent UpdateAddHTLC message. - OnionBlob [OnionPacketSize]byte - - // BlindingPoint is the ephemeral pubkey used to optionally blind the - // next hop for this htlc. - BlindingPoint BlindingPointRecord - - // CustomRecords maps TLV types to byte slices, storing arbitrary data - // intended for inclusion in the ExtraData field of the UpdateAddHTLC - // message. - CustomRecords CustomRecords - - // ExtraData is the set of data that was appended to this message to - // fill out the full maximum transport message size. These fields can - // be used to specify optional data such as custom TLV fields. - ExtraData ExtraOpaqueData -} - -// NewUpdateAddHTLC returns a new empty UpdateAddHTLC message. -func NewUpdateAddHTLC() *UpdateAddHTLC { - return &UpdateAddHTLC{} -} - -// A compile time check to ensure UpdateAddHTLC implements the lnwire.Message -// interface. -var _ Message = (*UpdateAddHTLC)(nil) - -// Decode deserializes a serialized UpdateAddHTLC message stored in the passed -// io.Reader observing the specified protocol version. -// -// This is part of the lnwire.Message interface. -func (c *UpdateAddHTLC) Decode(r io.Reader, pver uint32) error { - // msgExtraData is a temporary variable used to read the message extra - // data field from the reader. - var msgExtraData ExtraOpaqueData - - if err := ReadElements(r, - &c.ChanID, - &c.ID, - &c.Amount, - c.PaymentHash[:], - &c.Expiry, - c.OnionBlob[:], - &msgExtraData, - ); err != nil { - return err - } - - // Extract TLV records from the extra data field. - blindingRecord := c.BlindingPoint.Zero() - - customRecords, parsed, extraData, err := ParseAndExtractCustomRecords( - msgExtraData, &blindingRecord, - ) - if err != nil { - return err - } - - // Assign the parsed records back to the message. - if parsed.Contains(blindingRecord.TlvType()) { - c.BlindingPoint = tlv.SomeRecordT(blindingRecord) - } - - c.CustomRecords = customRecords - c.ExtraData = extraData - - return nil -} - -// Encode serializes the target UpdateAddHTLC into the passed io.Writer -// observing the protocol version specified. -// -// This is part of the lnwire.Message interface. -func (c *UpdateAddHTLC) Encode(w *bytes.Buffer, pver uint32) error { - if err := WriteChannelID(w, c.ChanID); err != nil { - return err - } - - if err := WriteUint64(w, c.ID); err != nil { - return err - } - - if err := WriteMilliSatoshi(w, c.Amount); err != nil { - return err - } - - if err := WriteBytes(w, c.PaymentHash[:]); err != nil { - return err - } - - if err := WriteUint32(w, c.Expiry); err != nil { - return err - } - - if err := WriteBytes(w, c.OnionBlob[:]); err != nil { - return err - } - - // Only include blinding point in extra data if present. - var records []tlv.RecordProducer - c.BlindingPoint.WhenSome( - func(b tlv.RecordT[BlindingPointTlvType, *btcec.PublicKey]) { - records = append(records, &b) - }, - ) - - extraData, err := MergeAndEncode(records, c.ExtraData, c.CustomRecords) - if err != nil { - return err - } - - return WriteBytes(w, extraData) -} - -// MsgType returns the integer uniquely identifying this message type on the -// wire. -// -// This is part of the lnwire.Message interface. -func (c *UpdateAddHTLC) MsgType() MessageType { - return MsgUpdateAddHTLC -} - -// TargetChanID returns the channel id of the link for which this message is -// intended. -// -// NOTE: Part of peer.LinkUpdater interface. -func (c *UpdateAddHTLC) TargetChanID() ChannelID { - return c.ChanID -} - -// SerializedSize returns the serialized size of the message in bytes. -// -// This is part of the lnwire.SizeableMessage interface. -func (c *UpdateAddHTLC) SerializedSize() (uint32, error) { - return MessageSerializedSize(c) -} - -// A compile time check to ensure UpdateAddHTLC implements the -// lnwire.SizeableMessage interface. -var _ SizeableMessage = (*UpdateAddHTLC)(nil) diff --git a/payments/db/migration1/lnwire/writer.go b/payments/db/migration1/lnwire/writer.go deleted file mode 100644 index 301dd875e..000000000 --- a/payments/db/migration1/lnwire/writer.go +++ /dev/null @@ -1,185 +0,0 @@ -package lnwire - -import ( - "bytes" - "encoding/binary" - "errors" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" -) - -var ( - // ErrNilFeatureVector is returned when the supplied feature is nil. - ErrNilFeatureVector = errors.New("cannot write nil feature vector") - - // ErrNilPublicKey is returned when a nil pubkey is used. - ErrNilPublicKey = errors.New("cannot write nil pubkey") -) - -// WriteBytes appends the given bytes to the provided buffer. -func WriteBytes(buf *bytes.Buffer, b []byte) error { - _, err := buf.Write(b) - return err -} - -// WriteUint8 appends the uint8 to the provided buffer. -func WriteUint8(buf *bytes.Buffer, n uint8) error { - _, err := buf.Write([]byte{n}) - return err -} - -// WriteUint16 appends the uint16 to the provided buffer. It encodes the -// integer using big endian byte order. -func WriteUint16(buf *bytes.Buffer, n uint16) error { - var b [2]byte - binary.BigEndian.PutUint16(b[:], n) - _, err := buf.Write(b[:]) - return err -} - -// WriteUint32 appends the uint32 to the provided buffer. It encodes the -// integer using big endian byte order. -func WriteUint32(buf *bytes.Buffer, n uint32) error { - var b [4]byte - binary.BigEndian.PutUint32(b[:], n) - _, err := buf.Write(b[:]) - return err -} - -// WriteUint64 appends the uint64 to the provided buffer. It encodes the -// integer using big endian byte order. -func WriteUint64(buf *bytes.Buffer, n uint64) error { - var b [8]byte - binary.BigEndian.PutUint64(b[:], n) - _, err := buf.Write(b[:]) - return err -} - -// WriteSatoshi appends the Satoshi value to the provided buffer. -func WriteSatoshi(buf *bytes.Buffer, amount btcutil.Amount) error { - return WriteUint64(buf, uint64(amount)) -} - -// WriteMilliSatoshi appends the MilliSatoshi value to the provided buffer. -func WriteMilliSatoshi(buf *bytes.Buffer, amount MilliSatoshi) error { - return WriteUint64(buf, uint64(amount)) -} - -// WritePublicKey appends the compressed public key to the provided buffer. -func WritePublicKey(buf *bytes.Buffer, pub *btcec.PublicKey) error { - if pub == nil { - return ErrNilPublicKey - } - - serializedPubkey := pub.SerializeCompressed() - return WriteBytes(buf, serializedPubkey) -} - -// WriteChannelID appends the ChannelID to the provided buffer. -func WriteChannelID(buf *bytes.Buffer, channelID ChannelID) error { - return WriteBytes(buf, channelID[:]) -} - -// WriteShortChannelID appends the ShortChannelID to the provided buffer. It -// encodes the BlockHeight and TxIndex each using 3 bytes with big endian byte -// order, and encodes txPosition using 2 bytes with big endian byte order. -func WriteShortChannelID(buf *bytes.Buffer, shortChanID ShortChannelID) error { - // Check that field fit in 3 bytes and write the blockHeight - if shortChanID.BlockHeight > ((1 << 24) - 1) { - return errors.New("block height should fit in 3 bytes") - } - - var blockHeight [4]byte - binary.BigEndian.PutUint32(blockHeight[:], shortChanID.BlockHeight) - - if _, err := buf.Write(blockHeight[1:]); err != nil { - return err - } - - // Check that field fit in 3 bytes and write the txIndex - if shortChanID.TxIndex > ((1 << 24) - 1) { - return errors.New("tx index should fit in 3 bytes") - } - - var txIndex [4]byte - binary.BigEndian.PutUint32(txIndex[:], shortChanID.TxIndex) - if _, err := buf.Write(txIndex[1:]); err != nil { - return err - } - - // Write the TxPosition - return WriteUint16(buf, shortChanID.TxPosition) -} - -// WriteSig appends the signature to the provided buffer. -func WriteSig(buf *bytes.Buffer, sig Sig) error { - return WriteBytes(buf, sig.bytes[:]) -} - -// WriteSigs appends the slice of signatures to the provided buffer with its -// length. -func WriteSigs(buf *bytes.Buffer, sigs []Sig) error { - // Write the length of the sigs. - if err := WriteUint16(buf, uint16(len(sigs))); err != nil { - return err - } - - for _, sig := range sigs { - if err := WriteSig(buf, sig); err != nil { - return err - } - } - return nil -} - -// WriteFailCode appends the FailCode to the provided buffer. -func WriteFailCode(buf *bytes.Buffer, e FailCode) error { - return WriteUint16(buf, uint16(e)) -} - -// WriteRawFeatureVector encodes the feature using the feature's Encode method -// and appends the data to the provided buffer. An error will return if the -// passed feature is nil. -func WriteRawFeatureVector(buf *bytes.Buffer, feature *RawFeatureVector) error { - if feature == nil { - return ErrNilFeatureVector - } - - return feature.Encode(buf) -} - -// WriteChanUpdateMsgFlags appends the update flag to the provided buffer. -func WriteChanUpdateMsgFlags(buf *bytes.Buffer, f ChanUpdateMsgFlags) error { - return WriteUint8(buf, uint8(f)) -} - -// WriteChanUpdateChanFlags appends the update flag to the provided buffer. -func WriteChanUpdateChanFlags(buf *bytes.Buffer, f ChanUpdateChanFlags) error { - return WriteUint8(buf, uint8(f)) -} - -// WriteErrorData appends the data to the provided buffer. -func WriteErrorData(buf *bytes.Buffer, data ErrorData) error { - return writeDataWithLength(buf, data) -} - -// WriteBool appends the boolean to the provided buffer. -func WriteBool(buf *bytes.Buffer, b bool) error { - if b { - return WriteBytes(buf, []byte{1}) - } - return WriteBytes(buf, []byte{0}) -} - -// writeDataWithLength writes the data and its length to the buffer. -func writeDataWithLength(buf *bytes.Buffer, data []byte) error { - var l [2]byte - binary.BigEndian.PutUint16(l[:], uint16(len(data))) - if _, err := buf.Write(l[:]); err != nil { - return err - } - - _, err := buf.Write(data) - return err -} diff --git a/payments/db/migration1/log.go b/payments/db/migration1/log.go deleted file mode 100644 index 52f1f7505..000000000 --- a/payments/db/migration1/log.go +++ /dev/null @@ -1,32 +0,0 @@ -package migration1 - -import ( - "github.com/btcsuite/btclog/v2" - "github.com/lightningnetwork/lnd/build" -) - -// log is a logger that is initialized with no output filters. This -// means the package will not perform any logging by default until the caller -// requests it. -var log btclog.Logger - -// Subsystem defines the logging identifier for this subsystem. -const Subsystem = "PYDB" - -// The default amount of logging is none. -func init() { - UseLogger(build.NewSubLogger(Subsystem, nil)) -} - -// DisableLog disables all library log output. Logging output is disabled -// by default until UseLogger is called. -func DisableLog() { - UseLogger(btclog.Disabled) -} - -// UseLogger uses a specified Logger to output package logging info. -// This should be used in preference to SetLogWriter if the caller is also -// using btclog. -func UseLogger(logger btclog.Logger) { - log = logger -} diff --git a/payments/db/migration1/migration_external_test.go b/payments/db/migration1/migration_external_test.go deleted file mode 100644 index 85f828961..000000000 --- a/payments/db/migration1/migration_external_test.go +++ /dev/null @@ -1,373 +0,0 @@ -//go:build test_db_postgres || test_db_sqlite - -package migration1 - -import ( - "context" - "fmt" - "os" - "path" - "strings" - "testing" - "time" - - "github.com/btcsuite/btclog/v2" - "github.com/lightningnetwork/lnd/kvdb" - "github.com/lightningnetwork/lnd/kvdb/postgres" - "github.com/lightningnetwork/lnd/kvdb/sqlbase" - "github.com/lightningnetwork/lnd/kvdb/sqlite" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/payments/db/migration1/sqlc" - "github.com/lightningnetwork/lnd/sqldb" - "github.com/stretchr/testify/require" -) - -// TestMigrationWithExternalDB tests the migration of the payment store from a -// bolt backed channel.db or a kvdb channel.sqlite to a SQL database. Note that -// this test does not attempt to be a complete migration test for all payment -// store types but rather is added as a tool for developers and users to debug -// payment migration issues with an actual channel.db/channel.sqlite file. -// -// NOTE: To use this test, place either of those files in the -// payments/db/migration1/testdata directory, uncomment the "Skipf" line, and -// set the "fileName" variable to the name of the channel database file you -// want to use for the migration test. -func TestMigrationWithExternalDB(t *testing.T) { - ctx := context.Background() - - // NOTE: comment this line out to run the test. - t.Skipf("skipping test meant for local debugging only") - - // NOTE: set this to the name of the channel database file you want - // to use for the migration test. This may be either a bbolt ".db" file - // or a SQLite ".sqlite" file. If you want to migrate from a - // bbolt channel.db file, set this to "channel.db". - const fileName = "channel.db" - - // NOTE: if set, this test will prefer migrating from a Postgres-backed - // kvdb source instead of a local file. Leave empty to use fileName. - const postgresKVDSN = "" - const postgresKVPfx = "channeldb" - const logSequenceOrder = false - - // Determine if we are using a SQLite file or a Bolt DB file. - isSqlite := strings.HasSuffix(fileName, ".sqlite") - - // Set up logging for the test. - logger := btclog.NewSLogger(btclog.NewDefaultHandler(os.Stdout)) - UseLogger(logger) - - // migrate runs the migration from the kvdb store to the SQL store - // and then performs a batched deep comparison of every migrated - // payment to ensure all data (including HTLC details) was preserved - // correctly. - migrate := func(t *testing.T, kvBackend kvdb.Backend) { - sqlStore := setupTestSQLDB(t) - - // Run migration in a transaction. - err := sqlStore.db.ExecTx( - ctx, sqldb.WriteTxOpt(), func(tx SQLQueries) error { - migTx, ok := tx.(SQLMigrationQueries) - if !ok { - return fmt.Errorf("db does not " + - "implement SQLMigrationQueries") - } - - return MigratePaymentsKVToSQL( - ctx, kvBackend, migTx, &SQLStoreConfig{ - QueryCfg: sqlStore.cfg.QueryCfg, - }, - ) - }, sqldb.NoOpReset, - ) - require.NoError(t, err) - - t.Log("========================================") - t.Log(" Deep Validation") - t.Log("========================================") - - // Deep compare all migrated payments in batches. - deepValidateAllPayments( - t, ctx, kvBackend, sqlStore, - ) - - _ = logSequenceOrder - } - - connectPostgres := func(t *testing.T, dsn, prefix string) kvdb.Backend { - dsn = strings.TrimSpace(dsn) - if dsn == "" { - t.Fatalf("missing postgres kvdb dsn") - } - - prefix = strings.TrimSpace(prefix) - if prefix == "" { - prefix = "channeldb" - } - - const ( - timeout = 10 * time.Second - maxConns = 5 - ) - sqlbase.Init(maxConns) - - dbCfg := &postgres.Config{ - Dsn: dsn, - Timeout: timeout, - MaxConnections: maxConns, - } - - kvStore, err := kvdb.Open( - kvdb.PostgresBackendName, ctx, dbCfg, prefix, - ) - require.NoError(t, err) - - t.Cleanup(func() { _ = kvStore.Close() }) - - return kvStore - } - - connectPostgresKV := func(t *testing.T) kvdb.Backend { - return connectPostgres(t, postgresKVDSN, postgresKVPfx) - } - - connectBBolt := func(t *testing.T, dbPath string) kvdb.Backend { - cfg := &kvdb.BoltBackendConfig{ - DBPath: dbPath, - DBFileName: fileName, - NoFreelistSync: true, - AutoCompact: false, - AutoCompactMinAge: kvdb.DefaultBoltAutoCompactMinAge, - DBTimeout: kvdb.DefaultDBTimeout, - ReadOnly: true, - } - - kvStore, err := kvdb.GetBoltBackend(cfg) - require.NoError(t, err) - - t.Cleanup(func() { _ = kvStore.Close() }) - - return kvStore - } - - connectSQLite := func(t *testing.T, dbPath string) kvdb.Backend { - const ( - timeout = 10 * time.Second - maxConns = 5 - ) - sqlbase.Init(maxConns) - - cfg := &sqlite.Config{ - Timeout: timeout, - BusyTimeout: timeout, - MaxConnections: maxConns, - } - - kvStore, err := kvdb.Open( - kvdb.SqliteBackendName, ctx, cfg, - dbPath, fileName, - // NOTE: we use the raw string here else we get an - // import cycle if we try to import lncfg.NSChannelDB. - "channeldb", - ) - require.NoError(t, err) - - t.Cleanup(func() { _ = kvStore.Close() }) - - return kvStore - } - - tests := []struct { - name string - dbPath string - }{ - { - name: "testdata", - dbPath: "testdata", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - if postgresKVDSN != "" { - migrate(t, connectPostgresKV(t)) - return - } - - chanDBPath := path.Join(test.dbPath, fileName) - t.Logf("Connecting to channel DB at: %s", chanDBPath) - - connectDB := connectBBolt - if isSqlite { - connectDB = connectSQLite - } - - migrate(t, connectDB(t, test.dbPath)) - }) - } -} - -// kvPaymentRef holds a KV payment and its hash for batched deep validation. -type kvPaymentRef struct { - hash lntypes.Hash - payment *MPPayment -} - -// deepValidateAllPayments iterates all KV payments in batches and performs a -// deep comparison against their SQL counterparts. For each batch, it fetches -// the full SQL payment data (including HTLCs, routes, custom records) via -// batch queries and compares field-by-field with the KV data. -func deepValidateAllPayments(t *testing.T, ctx context.Context, - kvBackend kvdb.Backend, sqlStore *SQLStore) { - - t.Helper() - - batchSize := int(sqlStore.cfg.QueryCfg.MaxBatchSize) - - // Get total payment count from SQL so we can show progress. - var totalPayments int64 - err := sqlStore.db.ExecTx( - ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - var err error - totalPayments, err = db.CountPayments(ctx) - return err - }, sqldb.NoOpReset, - ) - require.NoError(t, err) - - var totalValidated int - - // Iterate all KV payments, collecting them into batches. For each - // full batch, perform a batched deep comparison against SQL. - var batch []kvPaymentRef - err = kvdb.View(kvBackend, func(kvTx kvdb.RTx) error { - paymentsBucket := kvTx.ReadBucket(paymentsRootBucket) - if paymentsBucket == nil { - return nil - } - - return paymentsBucket.ForEach(func(k, v []byte) error { - bucket := paymentsBucket.NestedReadBucket(k) - if bucket == nil { - return nil - } - - payment, err := fetchPayment(bucket) - if err != nil { - return err - } - - var hash lntypes.Hash - copy(hash[:], k) - - batch = append(batch, kvPaymentRef{ - hash: hash, - payment: payment, - }) - - if len(batch) >= batchSize { - deepCompareBatch( - t, ctx, sqlStore, batch, - ) - totalValidated += len(batch) - t.Logf("Deep validated %d/%d payments", - totalValidated, totalPayments) - - batch = batch[:0] - } - - return nil - }) - }, func() { - batch = nil - totalValidated = 0 - }) - require.NoError(t, err) - - // Validate any remaining payments in the last batch. - if len(batch) > 0 { - deepCompareBatch(t, ctx, sqlStore, batch) - totalValidated += len(batch) - } - - t.Logf("Deep validated %d/%d payments (complete)", - totalValidated, totalPayments) -} - -// deepCompareBatch performs a batched deep comparison of KV payments against -// their SQL counterparts. It fetches SQL payment data in a single read -// transaction using batch queries. -func deepCompareBatch(t *testing.T, ctx context.Context, - sqlStore *SQLStore, batch []kvPaymentRef) { - - t.Helper() - - err := sqlStore.db.ExecTx( - ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - // Look up each payment by hash to get the SQL row - // with payment ID. - ids := make([]int64, 0, len(batch)) - rowsByHash := make( - map[lntypes.Hash]sqlc.FetchPaymentsByIDsRow, - len(batch), - ) - for _, ref := range batch { - row, err := fetchPaymentByHash( - ctx, db, ref.hash, - ) - if err != nil { - return fmt.Errorf("fetch SQL payment "+ - "%x: %w", ref.hash[:8], err) - } - ids = append(ids, row.Payment.ID) - } - - // Batch-fetch full payment details. - rows, err := db.FetchPaymentsByIDs(ctx, ids) - if err != nil { - return fmt.Errorf("batch fetch payments: %w", - err) - } - for _, row := range rows { - var hash lntypes.Hash - copy(hash[:], row.PaymentIdentifier) - rowsByHash[hash] = row - } - - batchData, err := batchLoadPaymentDetailsData( - ctx, sqlStore.cfg.QueryCfg, db, ids, - ) - if err != nil { - return fmt.Errorf("batch load details: %w", - err) - } - - // Deep compare each payment. - for _, ref := range batch { - row, ok := rowsByHash[ref.hash] - require.True(t, ok, - "SQL payment %x not in batch "+ - "results", ref.hash[:8]) - - sqlPayment, err := buildPaymentFromBatchData( - row, batchData, - ) - require.NoError(t, err, - "build SQL payment %x", - ref.hash[:8]) - - normalizePaymentData(ref.payment) - normalizePaymentData(sqlPayment) - - require.Equal( - t, ref.payment, sqlPayment, - "payment mismatch %x", - ref.hash[:8], - ) - } - - return nil - }, sqldb.NoOpReset, - ) - require.NoError(t, err) -} diff --git a/payments/db/migration1/migration_validation.go b/payments/db/migration1/migration_validation.go deleted file mode 100644 index 7b239d2d1..000000000 --- a/payments/db/migration1/migration_validation.go +++ /dev/null @@ -1,668 +0,0 @@ -package migration1 - -import ( - "bytes" - "context" - "database/sql" - "fmt" - "reflect" - "sort" - "time" - - "github.com/davecgh/go-spew/spew" - "github.com/lightningnetwork/lnd/kvdb" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire" - "github.com/lightningnetwork/lnd/payments/db/migration1/record" - "github.com/lightningnetwork/lnd/payments/db/migration1/sqlc" - "github.com/pmezard/go-difflib/difflib" -) - -// migratedPaymentRef is a reference to a migrated payment. -type migratedPaymentRef struct { - Hash lntypes.Hash - PaymentID int64 -} - -// validateMigratedPaymentBatch performs a structural validation pass by -// comparing key fields (hash, amount, fail reason, HTLC count) of KV payments -// with their SQL counterparts. If a structural mismatch is detected, a full -// deep comparison is performed to produce a detailed diff for debugging. -func validateMigratedPaymentBatch(ctx context.Context, - kvBackend kvdb.Backend, sqlDB SQLMigrationQueries, - cfg *SQLStoreConfig, batch []migratedPaymentRef) error { - - if len(batch) == 0 { - return nil - } - - if cfg == nil || cfg.QueryCfg == nil { - return fmt.Errorf("missing SQL store config for validation") - } - - paymentIDs := make([]int64, 0, len(batch)) - for _, item := range batch { - paymentIDs = append(paymentIDs, item.PaymentID) - } - - rows, err := sqlDB.FetchPaymentsByIDsMig(ctx, paymentIDs) - if err != nil { - return fmt.Errorf("fetch SQL payments: %w", err) - } - if len(rows) != len(paymentIDs) { - return fmt.Errorf("SQL payment batch mismatch: got=%d want=%d", - len(rows), len(paymentIDs)) - } - - // Perform the structural check by comparing key fields from the KV - // store with the SQL store. - err = kvBackend.View(func(kvTx kvdb.RTx) error { - paymentsBucket := kvTx.ReadBucket(paymentsRootBucket) - if paymentsBucket == nil { - return fmt.Errorf("no payments bucket") - } - - for _, row := range rows { - hash := row.PaymentIdentifier - var paymentHash lntypes.Hash - copy(paymentHash[:], hash) - - paymentBucket := paymentsBucket.NestedReadBucket(hash) - if paymentBucket == nil { - return fmt.Errorf("missing payment bucket %x", - hash[:8]) - } - - kvPayment, err := fetchPayment(paymentBucket) - if err != nil { - return fmt.Errorf("fetch KV payment %x: %w", - hash[:8], err) - } - - if kvPayment.Status == StatusInFlight { - // Mirror the migration's legacy terminalization - // before comparing KV with SQL. - // - //nolint:ll - _, err = terminalizeUnresolvedLegacyZeroAttempts( - kvPayment, - ) - if err != nil { - return fmt.Errorf("normalize KV "+ - "payment %x: %w", hash[:8], err) - } - } - - err = structuralCompare(kvPayment, row) - if err != nil { - // On structural mismatch, perform a deep - // comparison to produce a detailed diff. - deepErr := deepComparePayment( - ctx, cfg, sqlDB, row.ID, - paymentHash, kvPayment, - ) - if deepErr != nil { - return deepErr - } - - // If deep comparison passes but structural - // failed, report the structural error as it - // indicates an unexpected inconsistency. - return err - } - - err = compareDuplicatePayments( - ctx, paymentBucket, sqlDB, row.ID, - paymentHash, - ) - if err != nil { - return err - } - } - - return nil - }, func() {}) - if err != nil { - return err - } - - return nil -} - -// structuralCompare performs a fast structural comparison between a KV payment -// and a SQL payment row, checking key fields: payment identifier, amount, -// failure reason, and HTLC attempt count. -func structuralCompare(kvPayment *MPPayment, - sqlRow sqlc.FetchPaymentsByIDsMigRow) error { - - // Compare payment identifier. - kvHash := kvPayment.Info.PaymentIdentifier[:] - if !bytes.Equal(kvHash, sqlRow.PaymentIdentifier) { - return fmt.Errorf("payment identifier mismatch: kv=%x sql=%x", - kvHash[:8], sqlRow.PaymentIdentifier[:8]) - } - - // Compare amount. - kvAmount := int64(kvPayment.Info.Value) - if kvAmount != sqlRow.AmountMsat { - return fmt.Errorf("amount mismatch for %x: kv=%d sql=%d", - sqlRow.PaymentIdentifier[:8], kvAmount, - sqlRow.AmountMsat) - } - - // Compare failure reason. - var kvFailReason sql.NullInt32 - if kvPayment.FailureReason != nil { - kvFailReason = sql.NullInt32{ - Int32: int32(*kvPayment.FailureReason), - Valid: true, - } - } - if kvFailReason != sqlRow.FailReason { - return fmt.Errorf("fail reason mismatch for %x: kv=%v sql=%v", - sqlRow.PaymentIdentifier[:8], kvFailReason, - sqlRow.FailReason) - } - - // Compare HTLC attempt count. - kvHTLCCount := int64(len(kvPayment.HTLCs)) - if kvHTLCCount != sqlRow.HtlcAttemptCount { - return fmt.Errorf("HTLC count mismatch for %x: kv=%d sql=%d", - sqlRow.PaymentIdentifier[:8], kvHTLCCount, - sqlRow.HtlcAttemptCount) - } - - return nil -} - -// deepComparePayment performs a full deep comparison between a KV payment and -// its SQL counterpart, producing a detailed diff on mismatch. -func deepComparePayment(ctx context.Context, cfg *SQLStoreConfig, - sqlDB SQLQueries, paymentID int64, paymentHash lntypes.Hash, - kvPayment *MPPayment) error { - - batchData, err := batchLoadPaymentDetailsData( - ctx, cfg.QueryCfg, sqlDB, []int64{paymentID}, - ) - if err != nil { - return fmt.Errorf("load payment data for deep compare %x: %w", - paymentHash[:8], err) - } - - byIDRows, err := sqlDB.FetchPaymentsByIDs(ctx, []int64{paymentID}) - if err != nil { - return fmt.Errorf("fetch payment by ID for deep compare "+ - "%x: %w", paymentHash[:8], err) - } - if len(byIDRows) != 1 { - return fmt.Errorf("expected 1 payment for deep compare, "+ - "got %d", len(byIDRows)) - } - - sqlPayment, err := buildPaymentFromBatchData(byIDRows[0], batchData) - if err != nil { - return fmt.Errorf("build SQL payment %x: %w", - paymentHash[:8], err) - } - - if kvPayment.Status == StatusInFlight { - // Mirror the migration's legacy terminalization before - // comparing KV with SQL. - _, err = terminalizeUnresolvedLegacyZeroAttempts(kvPayment) - if err != nil { - return fmt.Errorf("normalize KV payment %x: %w", - paymentHash[:8], err) - } - } - normalizeLegacyZeroAttemptIDsForCompare(kvPayment, sqlPayment) - normalizePaymentForCompare(kvPayment) - normalizePaymentForCompare(sqlPayment) - - if !reflect.DeepEqual(kvPayment, sqlPayment) { - dumpCfg := spew.ConfigState{ - DisablePointerAddresses: true, - DisableCapacities: true, - DisableMethods: true, - SortKeys: true, - } - diff := difflib.UnifiedDiff{ - A: difflib.SplitLines( - dumpCfg.Sdump(kvPayment), - ), - B: difflib.SplitLines( - dumpCfg.Sdump(sqlPayment), - ), - FromFile: "kv", - ToFile: "sql", - Context: 3, - } - diffText, _ := difflib.GetUnifiedDiffString(diff) - - return fmt.Errorf("payment mismatch %x\n%s", - paymentHash[:8], diffText) - } - - return nil -} - -// normalizeLegacyZeroAttemptIDsForCompare aligns expected legacy attempt ID -// remaps before deep comparison. -// -// Legacy KV payments can use attempt ID zero to represent an unknown ID. During -// migration those attempts are assigned synthetic SQL attempt indexes, while -// the source KV payment is left unchanged. Match by session key and copy the -// SQL attempt ID into the KV object so fallback deep comparison still reports -// real data mismatches instead of this expected migration repair. -func normalizeLegacyZeroAttemptIDsForCompare(kvPayment, - sqlPayment *MPPayment) { - - if kvPayment == nil || sqlPayment == nil { - return - } - - sqlAttemptsBySessionKey := make(map[string]uint64) - for i := range sqlPayment.HTLCs { - htlc := &sqlPayment.HTLCs[i] - sessionKey := htlc.SessionKey() - if sessionKey == nil { - // Leave normalization incomplete so the deep comparison - // reports the malformed attempt instead of panicking. - continue - } - - sessionKeyBytes := sessionKey.Serialize() - sessionKeyStr := string(sessionKeyBytes) - sqlAttemptsBySessionKey[sessionKeyStr] = htlc.AttemptID - } - - for i := range kvPayment.HTLCs { - htlc := &kvPayment.HTLCs[i] - if htlc.AttemptID != 0 { - continue - } - - sessionKey := htlc.SessionKey() - if sessionKey == nil { - // Leave normalization incomplete so the deep comparison - // reports the malformed attempt instead of panicking. - continue - } - - sessionKeyBytes := sessionKey.Serialize() - sessionKeyStr := string(sessionKeyBytes) - attemptID, ok := sqlAttemptsBySessionKey[sessionKeyStr] - if !ok { - continue - } - - htlc.AttemptID = attemptID - } -} - -// normalizePaymentForCompare normalizes fields that are expected to differ -// between KV and SQL representations before deep comparison. -func normalizePaymentForCompare(payment *MPPayment) { - if payment == nil { - return - } - - // SequenceNum will not be equal because the kv db can have already - // payments deleted during its lifetime. - payment.SequenceNum = 0 - - // We normalize timestamps before deep-comparing KV vs SQL objects. - // - // - **Microseconds**: SQL backends typically persist timestamps at - // microsecond precision (e.g. Postgres), while KV (Go `time.Time`) - // can contain nanoseconds. Truncating avoids false mismatches caused - // solely by differing storage precision. - // - // - **Local timezone**: when reading from SQL, timestamps are typically - // materialized in the local timezone by the SQL layer (and/or - // converters). Converting both sides to `time.Local` ensures the - // comparison is consistent across KV and SQL representations. - trunc := func(t time.Time) time.Time { - if t.IsZero() { - return t - } - - return time.Unix(0, t.UnixNano()). - In(time.Local). - Truncate(time.Microsecond) - } - - // Normalize PaymentCreationInfo fields. - if payment.Info != nil { - payment.Info.CreationTime = trunc( - payment.Info.CreationTime, - ) - if len(payment.Info.PaymentRequest) == 0 { - payment.Info.PaymentRequest = []byte{} - } - if len(payment.Info.FirstHopCustomRecords) == 0 { - payment.Info.FirstHopCustomRecords = lnwire. - CustomRecords{} - } - } - - // Normalize HTLCAttemptInfo so nil is converted to an empty slice. - if len(payment.HTLCs) == 0 { - payment.HTLCs = []HTLCAttempt{} - } - - // Normalize HTLC attempt ordering; SQL/KV may return attempts - // in different orders. - sort.SliceStable(payment.HTLCs, func(i, j int) bool { - return payment.HTLCs[i].AttemptID < payment.HTLCs[j].AttemptID - }) - - // Normalize HTLCAttemptInfo fields. - for i := range payment.HTLCs { - htlc := &payment.HTLCs[i] - - htlc.AttemptTime = trunc(htlc.AttemptTime) - if htlc.Settle != nil { - htlc.Settle.SettleTime = trunc( - htlc.Settle.SettleTime, - ) - } - if htlc.Failure != nil { - htlc.Failure.FailTime = trunc( - htlc.Failure.FailTime, - ) - } - - // Clear cached fields not persisted in storage. - htlc.onionBlob = [1366]byte{} - htlc.circuit = nil - htlc.cachedSessionKey = nil - - // For legacy payments, the HTLC Hash field may be nil in the - // bbolt backend. During migration, the SQL code uses the - // parent payment hash as fallback. To ensure the comparison - // between bbolt and SQL data succeeds, we apply the same - // fallback here. - // - // See also: patchLegacyPaymentHash in payment_lifecycle.go. - if htlc.Hash == nil && payment.Info != nil { - htlc.Hash = &payment.Info.PaymentIdentifier - } - - if len(htlc.Route.FirstHopWireCustomRecords) == 0 { - htlc.Route.FirstHopWireCustomRecords = - lnwire.CustomRecords{} - } - - for j := range htlc.Route.Hops { - hop := htlc.Route.Hops[j] - if len(hop.CustomRecords) == 0 { - hop.CustomRecords = - record.CustomSet{} - } - - // The migration treats nil and empty encrypted data as - // absent, so it omits the blinded child row. SQL reads - // the hop back with nil encrypted data and a zero - // total. Apply the same transformation to the KV - // copy before comparing the payments. A blinding point - // without data is rejected during migration, so it - // cannot reach this comparison. - if len(hop.EncryptedData) == 0 && - hop.BlindingPoint == nil { - - hop.EncryptedData = nil - hop.TotalAmtMsat = 0 - } - - // LegacyPayload was a hint used by the KV store to - // determine how to serialize and deserialize the hop - // payload (i.e. whether to use the legacy format or - // TLV). The SQL store does not serialize hop data at - // all — each field is stored natively in its own - // column — so this flag has no meaning there and is - // never persisted. - hop.LegacyPayload = false - } - } -} - -// duplicateRecord is a record that represents a duplicate payment. -type duplicateRecord struct { - AmountMsat int64 - CreatedAt time.Time - FailReason sql.NullInt32 - SettlePreimage []byte - SettleTime sql.NullTime -} - -// compareDuplicatePayments validates migrated duplicate rows against KV data. -func compareDuplicatePayments(ctx context.Context, paymentBucket kvdb.RBucket, - sqlDB SQLQueries, paymentID int64, hash lntypes.Hash) error { - - // Fetch the duplicate payments from the KV store. - kvDuplicates, err := fetchDuplicateRecords(paymentBucket) - if err != nil { - return fmt.Errorf("fetch KV duplicates %x: %w", - hash[:8], err) - } - - // Fetch the duplicate payments from the SQL store. - sqlDuplicates, err := sqlDB.FetchPaymentDuplicates(ctx, paymentID) - if err != nil { - return fmt.Errorf("fetch SQL duplicates %x: %w", - hash[:8], err) - } - - if len(kvDuplicates) != len(sqlDuplicates) { - return fmt.Errorf("duplicate count mismatch %x: kv=%d "+ - "sql=%d", hash[:8], len(kvDuplicates), - len(sqlDuplicates)) - } - - kvNormalized := normalizeDuplicateRecords(kvDuplicates) - sqlNormalized := normalizeDuplicateRecords( - dbDuplicatesToDuplicateRecords(sqlDuplicates), - ) - - sortDuplicates(kvNormalized) - sortDuplicates(sqlNormalized) - - if !reflect.DeepEqual(kvNormalized, sqlNormalized) { - dumpCfg := spew.ConfigState{ - DisablePointerAddresses: true, - DisableCapacities: true, - DisableMethods: true, - SortKeys: true, - } - diff := difflib.UnifiedDiff{ - A: difflib.SplitLines( - dumpCfg.Sdump(kvNormalized), - ), - B: difflib.SplitLines( - dumpCfg.Sdump(sqlNormalized), - ), - FromFile: "kv", - ToFile: "sql", - Context: 3, - } - diffText, _ := difflib.GetUnifiedDiffString(diff) - - return fmt.Errorf("duplicate mismatch %x\n%s", - hash[:8], diffText) - } - - return nil -} - -// fetchDuplicateRecords reads duplicate payment records from the KV bucket. -func fetchDuplicateRecords(paymentBucket kvdb.RBucket) ([]duplicateRecord, - error) { - - dupBucket := paymentBucket.NestedReadBucket(duplicatePaymentsBucket) - if dupBucket == nil { - return nil, nil - } - - var duplicates []duplicateRecord - err := dupBucket.ForEach(func(seqBytes, _ []byte) error { - if len(seqBytes) != 8 { - return nil - } - - subBucket := dupBucket.NestedReadBucket(seqBytes) - if subBucket == nil { - return nil - } - - creationData := subBucket.Get(duplicatePaymentCreationInfoKey) - if creationData == nil { - return fmt.Errorf("missing duplicate creation info") - } - - creationInfo, err := deserializeDuplicatePaymentCreationInfo( - bytes.NewReader(creationData), - ) - if err != nil { - return fmt.Errorf("deserialize duplicate creation "+ - "info: %w", err) - } - - settleData := subBucket.Get(duplicatePaymentSettleInfoKey) - failReasonData := subBucket.Get(duplicatePaymentFailInfoKey) - - if settleData != nil && len(failReasonData) > 0 { - return fmt.Errorf("duplicate has both settle and " + - "fail info") - } - - var ( - failReason sql.NullInt32 - settlePreimage []byte - settleTime sql.NullTime - ) - - switch { - case settleData != nil: - settlePreimage, settleTime, err = - parseDuplicateSettleData(settleData) - if err != nil { - return err - } - case len(failReasonData) > 0: - failReason = sql.NullInt32{ - Int32: int32(failReasonData[0]), - Valid: true, - } - default: - // If the duplicate has no settle or fail info, it is - // considered failed. Every duplicate payment must have - // either a settle or fail info in the sql database. So - // we set the fail reason to error to mimic the behavior - // for the kv store. - failReason = sql.NullInt32{ - Int32: int32(FailureReasonError), - Valid: true, - } - } - - duplicates = append(duplicates, duplicateRecord{ - AmountMsat: int64(creationInfo.Value), - CreatedAt: normalizeTimeForSQL( - creationInfo.CreationTime, - ), - FailReason: failReason, - SettlePreimage: settlePreimage, - SettleTime: settleTime, - }) - - return nil - }) - if err != nil { - return nil, err - } - - return duplicates, nil -} - -// dbDuplicatesToDuplicateRecords maps SQL duplicate rows into comparable -// duplicate records. -func dbDuplicatesToDuplicateRecords( - rows []sqlc.PaymentDuplicate) []duplicateRecord { - - duplicates := make([]duplicateRecord, 0, len(rows)) - for _, row := range rows { - duplicates = append(duplicates, duplicateRecord{ - AmountMsat: row.AmountMsat, - CreatedAt: row.CreatedAt, - FailReason: row.FailReason, - SettlePreimage: row.SettlePreimage, - SettleTime: row.SettleTime, - }) - } - - return duplicates -} - -// normalizeDuplicateRecords normalizes time precision and empty fields. -func normalizeDuplicateRecords(records []duplicateRecord) []duplicateRecord { - if len(records) == 0 { - return []duplicateRecord{} - } - - trunc := func(t time.Time) time.Time { - if t.IsZero() { - return t - } - - return t.In(time.Local).Truncate(time.Microsecond) - } - - for i := range records { - records[i].CreatedAt = trunc(records[i].CreatedAt) - if records[i].SettleTime.Valid { - records[i].SettleTime.Time = trunc( - records[i].SettleTime.Time, - ) - } - - if len(records[i].SettlePreimage) == 0 { - records[i].SettlePreimage = []byte{} - } - } - - return records -} - -// sortDuplicates orders records deterministically for deep comparison. -func sortDuplicates(records []duplicateRecord) { - sort.SliceStable(records, func(i, j int) bool { - ai := records[i] - aj := records[j] - - // Duplicates are "duplicates" because they share the same - // payment identifier. So ordering can be stable using - // timestamp + amount. - if !ai.CreatedAt.Equal(aj.CreatedAt) { - return ai.CreatedAt.Before(aj.CreatedAt) - } - - return ai.AmountMsat < aj.AmountMsat - }) -} - -// validatePaymentCounts compares the number of migrated payments with the SQL -// payment count to catch missing rows. -func validatePaymentCounts(ctx context.Context, sqlDB SQLQueries, - expectedCount int64) error { - - sqlCount, err := sqlDB.CountPayments(ctx) - if err != nil { - return fmt.Errorf("count SQL payments: %w", err) - } - if expectedCount != sqlCount { - return fmt.Errorf("payment count mismatch: kv=%d sql=%d", - expectedCount, sqlCount) - } - - return nil -} diff --git a/payments/db/migration1/options.go b/payments/db/migration1/options.go deleted file mode 100644 index 382afb26c..000000000 --- a/payments/db/migration1/options.go +++ /dev/null @@ -1,26 +0,0 @@ -package migration1 - -// StoreOptions holds parameters for the KVStore. -type StoreOptions struct { - // NoMigration allows to open the database in readonly mode - NoMigration bool -} - -// DefaultOptions returns a StoreOptions populated with default values. -func DefaultOptions() *StoreOptions { - return &StoreOptions{ - NoMigration: false, - } -} - -// OptionModifier is a function signature for modifying the default -// StoreOptions. -type OptionModifier func(*StoreOptions) - -// WithNoMigration allows the database to be opened in read only mode by -// disabling migrations. -func WithNoMigration(b bool) OptionModifier { - return func(o *StoreOptions) { - o.NoMigration = b - } -} diff --git a/payments/db/migration1/payment.go b/payments/db/migration1/payment.go deleted file mode 100644 index fbc1b21e5..000000000 --- a/payments/db/migration1/payment.go +++ /dev/null @@ -1,688 +0,0 @@ -package migration1 - -import ( - "errors" - "fmt" - "time" - - "github.com/btcsuite/btcd/btcec/v2" - sphinx "github.com/lightningnetwork/lightning-onion" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire" -) - -// FailureReason encodes the reason a payment ultimately failed. -type FailureReason byte - -const ( - // FailureReasonTimeout indicates that the payment did timeout before a - // successful payment attempt was made. - FailureReasonTimeout FailureReason = 0 - - // FailureReasonNoRoute indicates no successful route to the - // destination was found during path finding. - FailureReasonNoRoute FailureReason = 1 - - // FailureReasonError indicates that an unexpected error happened during - // payment. - FailureReasonError FailureReason = 2 - - // FailureReasonPaymentDetails indicates that either the hash is unknown - // or the final cltv delta or amount is incorrect. - FailureReasonPaymentDetails FailureReason = 3 - - // FailureReasonInsufficientBalance indicates that we didn't have enough - // balance to complete the payment. - FailureReasonInsufficientBalance FailureReason = 4 - - // FailureReasonCanceled indicates that the payment was canceled by the - // user. - FailureReasonCanceled FailureReason = 5 - - // TODO(joostjager): Add failure reasons for: - // LocalLiquidityInsufficient, RemoteCapacityInsufficient. -) - -// Error returns a human-readable error string for the FailureReason. -func (r FailureReason) Error() string { - return r.String() -} - -// String returns a human-readable FailureReason. -func (r FailureReason) String() string { - switch r { - case FailureReasonTimeout: - return "timeout" - case FailureReasonNoRoute: - return "no_route" - case FailureReasonError: - return "error" - case FailureReasonPaymentDetails: - return "incorrect_payment_details" - case FailureReasonInsufficientBalance: - return "insufficient_balance" - case FailureReasonCanceled: - return "canceled" - } - - return "unknown" -} - -// PaymentCreationInfo is the information necessary to have ready when -// initiating a payment, moving it into state InFlight. -type PaymentCreationInfo struct { - // PaymentIdentifier is the hash this payment is paying to in case of - // non-AMP payments, and the SetID for AMP payments. - PaymentIdentifier lntypes.Hash - - // Value is the amount we are paying. - Value lnwire.MilliSatoshi - - // CreationTime is the time when this payment was initiated. - CreationTime time.Time - - // PaymentRequest is the full payment request, if any. - PaymentRequest []byte - - // FirstHopCustomRecords are the TLV records that are to be sent to the - // first hop of this payment. These records will be transmitted via the - // wire message (UpdateAddHTLC) only and therefore do not affect the - // onion payload size. - FirstHopCustomRecords lnwire.CustomRecords -} - -// String returns a human-readable description of the payment creation info. -func (p *PaymentCreationInfo) String() string { - return fmt.Sprintf("payment_id=%v, amount=%v, created_at=%v", - p.PaymentIdentifier, p.Value, p.CreationTime) -} - -// HTLCAttemptInfo contains static information about a specific HTLC attempt -// for a payment. This information is used by the router to handle any errors -// coming back after an attempt is made, and to query the switch about the -// status of the attempt. -type HTLCAttemptInfo struct { - // AttemptID is the unique ID used for this attempt. - AttemptID uint64 - - // sessionKey is the raw bytes ephemeral key used for this attempt. - // These bytes are lazily read off disk to save ourselves the expensive - // EC operations used by btcec.PrivKeyFromBytes. - sessionKey [btcec.PrivKeyBytesLen]byte - - // cachedSessionKey is our fully deserialized sesionKey. This value - // may be nil if the attempt has just been read from disk and its - // session key has not been used yet. - cachedSessionKey *btcec.PrivateKey - - // Route is the route attempted to send the HTLC. - Route Route - - // AttemptTime is the time at which this HTLC was attempted. - AttemptTime time.Time - - // Hash is the hash used for this single HTLC attempt. For AMP payments - // this will differ across attempts, for non-AMP payments each attempt - // will use the same hash. This can be nil for older payment attempts, - // in which the payment's PaymentHash in the PaymentCreationInfo should - // be used. - Hash *lntypes.Hash - - // onionBlob is the cached value for onion blob created from the sphinx - // construction. - onionBlob [lnwire.OnionPacketSize]byte - - // circuit is the cached value for sphinx circuit. - circuit *sphinx.Circuit -} - -// SessionKey returns the ephemeral key used for a htlc attempt. This function -// performs expensive ec-ops to obtain the session key if it is not cached. -func (h *HTLCAttemptInfo) SessionKey() *btcec.PrivateKey { - if h.cachedSessionKey == nil { - h.cachedSessionKey, _ = btcec.PrivKeyFromBytes( - h.sessionKey[:], - ) - } - - return h.cachedSessionKey -} - -// HTLCAttempt contains information about a specific HTLC attempt for a given -// payment. It contains the HTLCAttemptInfo used to send the HTLC, as well -// as a timestamp and any known outcome of the attempt. -type HTLCAttempt struct { - HTLCAttemptInfo - - // Settle is the preimage of a successful payment. This serves as a - // proof of payment. It will only be non-nil for settled payments. - // - // NOTE: Can be nil if payment is not settled. - Settle *HTLCSettleInfo - - // Fail is a failure reason code indicating the reason the payment - // failed. It is only non-nil for failed payments. - // - // NOTE: Can be nil if payment is not failed. - Failure *HTLCFailInfo -} - -// HTLCSettleInfo encapsulates the information that augments an HTLCAttempt in -// the event that the HTLC is successful. -type HTLCSettleInfo struct { - // Preimage is the preimage of a successful HTLC. This serves as a proof - // of payment. - Preimage lntypes.Preimage - - // SettleTime is the time at which this HTLC was settled. - SettleTime time.Time -} - -// HTLCFailReason is the reason an htlc failed. -type HTLCFailReason byte - -const ( - // HTLCFailUnknown is recorded for htlcs that failed with an unknown - // reason. - HTLCFailUnknown HTLCFailReason = 0 - - // HTLCFailUnreadable is recorded for htlcs that had a failure message - // that couldn't be decrypted. - HTLCFailUnreadable HTLCFailReason = 1 - - // HTLCFailInternal is recorded for htlcs that failed because of an - // internal error. - HTLCFailInternal HTLCFailReason = 2 - - // HTLCFailMessage is recorded for htlcs that failed with a network - // failure message. - HTLCFailMessage HTLCFailReason = 3 -) - -// HTLCFailInfo encapsulates the information that augments an HTLCAttempt in the -// event that the HTLC fails. -type HTLCFailInfo struct { - // FailTime is the time at which this HTLC was failed. - FailTime time.Time - - // Message is the wire message that failed this HTLC. This field will be - // populated when the failure reason is HTLCFailMessage. - Message lnwire.FailureMessage - - // Reason is the failure reason for this HTLC. - Reason HTLCFailReason - - // The position in the path of the intermediate or final node that - // generated the failure message. Position zero is the sender node. This - // field will be populated when the failure reason is either - // HTLCFailMessage or HTLCFailUnknown. - FailureSourceIndex uint32 -} - -// MPPaymentState wraps a series of info needed for a given payment, which is -// used by both MPP and AMP. This is a memory representation of the payment's -// current state and is updated whenever the payment is read from disk. -type MPPaymentState struct { - // NumAttemptsInFlight specifies the number of HTLCs the payment is - // waiting results for. - NumAttemptsInFlight int - - // RemainingAmt specifies how much more money to be sent. - RemainingAmt lnwire.MilliSatoshi - - // FeesPaid specifies the total fees paid so far that can be used to - // calculate remaining fee budget. - FeesPaid lnwire.MilliSatoshi - - // HasSettledHTLC is true if at least one of the payment's HTLCs is - // settled. - HasSettledHTLC bool - - // PaymentFailed is true if the payment has been marked as failed with - // a reason. - PaymentFailed bool -} - -// MPPayment is a wrapper around a payment's PaymentCreationInfo and -// HTLCAttempts. All payments will have the PaymentCreationInfo set, any -// HTLCs made in attempts to be completed will populated in the HTLCs slice. -// Each populated HTLCAttempt represents an attempted HTLC, each of which may -// have the associated Settle or Fail struct populated if the HTLC is no longer -// in-flight. -type MPPayment struct { - // SequenceNum is a unique identifier used to sort the payments in - // order of creation. - SequenceNum uint64 - - // Info holds all static information about this payment, and is - // populated when the payment is initiated. - Info *PaymentCreationInfo - - // HTLCs holds the information about individual HTLCs that we send in - // order to make the payment. - HTLCs []HTLCAttempt - - // FailureReason is the failure reason code indicating the reason the - // payment failed. - // - // NOTE: Will only be set once the daemon has given up on the payment - // altogether. - FailureReason *FailureReason - - // Status is the current PaymentStatus of this payment. - Status PaymentStatus - - // State is the current state of the payment that holds a number of key - // insights and is used to determine what to do on each payment loop - // iteration. - State *MPPaymentState -} - -// Terminated returns a bool to specify whether the payment is in a terminal -// state. -func (m *MPPayment) Terminated() bool { - // If the payment is in terminal state, it cannot be updated. - return m.Status.updatable() != nil -} - -// TerminalInfo returns any HTLC settle info recorded. If no settle info is -// recorded, any payment level failure will be returned. If neither a settle -// nor a failure is recorded, both return values will be nil. -func (m *MPPayment) TerminalInfo() (*HTLCAttempt, *FailureReason) { - for _, h := range m.HTLCs { - if h.Settle != nil { - return &h, nil - } - } - - return nil, m.FailureReason -} - -// SentAmt returns the sum of sent amount and fees for HTLCs that are either -// settled or still in flight. -func (m *MPPayment) SentAmt() (lnwire.MilliSatoshi, lnwire.MilliSatoshi) { - var sent, fees lnwire.MilliSatoshi - for _, h := range m.HTLCs { - if h.Failure != nil { - continue - } - - // The attempt was not failed, meaning the amount was - // potentially sent to the receiver. - sent += h.Route.ReceiverAmt() - fees += h.Route.TotalFees() - } - - return sent, fees -} - -// InFlightHTLCs returns the HTLCs that are still in-flight, meaning they have -// not been settled or failed. -func (m *MPPayment) InFlightHTLCs() []HTLCAttempt { - var inflights []HTLCAttempt - for _, h := range m.HTLCs { - if h.Settle != nil || h.Failure != nil { - continue - } - - inflights = append(inflights, h) - } - - return inflights -} - -// GetAttempt returns the specified htlc attempt on the payment. -func (m *MPPayment) GetAttempt(id uint64) (*HTLCAttempt, error) { - // TODO(yy): iteration can be slow, make it into a tree or use BS. - for _, htlc := range m.HTLCs { - if htlc.AttemptID == id { - return &htlc, nil - } - } - - return nil, errors.New("htlc attempt not found on payment") -} - -// Registrable returns an error to specify whether adding more HTLCs to the -// payment with its current status is allowed. A payment can accept new HTLC -// registrations when it's newly created, or none of its HTLCs is in a terminal -// state. -func (m *MPPayment) Registrable() error { - // If updating the payment is not allowed, we can't register new HTLCs. - // Otherwise, the status must be either `StatusInitiated` or - // `StatusInFlight`. - if err := m.Status.updatable(); err != nil { - return err - } - - // Exit early if this is not inflight. - if m.Status != StatusInFlight { - return nil - } - - // There are still inflight HTLCs and we need to check whether there - // are settled HTLCs or the payment is failed. If we already have - // settled HTLCs, we won't allow adding more HTLCs. - if m.State.HasSettledHTLC { - return ErrPaymentPendingSettled - } - - // If the payment is already failed, we won't allow adding more HTLCs. - if m.State.PaymentFailed { - return ErrPaymentPendingFailed - } - - // Otherwise we can add more HTLCs. - return nil -} - -// setState creates and attaches a new MPPaymentState to the payment. It also -// updates the payment's status based on its current state. -func (m *MPPayment) setState() error { - // Fetch the total amount and fees that has already been sent in - // settled and still in-flight shards. - sentAmt, fees := m.SentAmt() - - // Sanity check we haven't sent a value larger than the payment amount. - totalAmt := m.Info.Value - if sentAmt > totalAmt { - return fmt.Errorf("%w: sent=%v, total=%v", - ErrSentExceedsTotal, sentAmt, totalAmt) - } - - // Get any terminal info for this payment. - settle, failure := m.TerminalInfo() - - // Now determine the payment's status. - status, err := decidePaymentStatus(m.HTLCs, m.FailureReason) - if err != nil { - return err - } - - // Update the payment state and status. - m.State = &MPPaymentState{ - NumAttemptsInFlight: len(m.InFlightHTLCs()), - RemainingAmt: totalAmt - sentAmt, - FeesPaid: fees, - HasSettledHTLC: settle != nil, - PaymentFailed: failure != nil, - } - m.Status = status - - return nil -} - -// SetState calls the internal method setState. This is a temporary method -// to be used by the tests in routing. Once the tests are updated to use mocks, -// this method can be removed. -// -// TODO(yy): delete. -func (m *MPPayment) SetState() error { - return m.setState() -} - -// NeedWaitAttempts decides whether we need to hold creating more HTLC attempts -// and wait for the results of the payment's inflight HTLCs. Return an error if -// the payment is in an unexpected state. -func (m *MPPayment) NeedWaitAttempts() (bool, error) { - // Check when the remainingAmt is not zero, which means we have more - // money to be sent. - if m.State.RemainingAmt != 0 { - switch m.Status { - // If the payment is newly created, no need to wait for HTLC - // results. - case StatusInitiated: - return false, nil - - // If we have inflight HTLCs, we'll check if we have terminal - // states to decide if we need to wait. - case StatusInFlight: - // We still have money to send, and one of the HTLCs is - // settled. We'd stop sending money and wait for all - // inflight HTLC attempts to finish. - if m.State.HasSettledHTLC { - log.Warnf("payment=%v has remaining amount "+ - "%v, yet at least one of its HTLCs is "+ - "settled", m.Info.PaymentIdentifier, - m.State.RemainingAmt) - - return true, nil - } - - // The payment has a failure reason though we still - // have money to send, we'd stop sending money and wait - // for all inflight HTLC attempts to finish. - if m.State.PaymentFailed { - return true, nil - } - - // Otherwise we don't need to wait for inflight HTLCs - // since we still have money to be sent. - return false, nil - - // We need to send more money, yet the payment is already - // succeeded. Return an error in this case as the receiver is - // violating the protocol. - case StatusSucceeded: - return false, fmt.Errorf("%w: parts of the payment "+ - "already succeeded but still have remaining "+ - "amount %v", ErrPaymentInternal, - m.State.RemainingAmt) - - // The payment is failed and we have no inflight HTLCs, no need - // to wait. - case StatusFailed: - return false, nil - - // Unknown payment status. - default: - return false, fmt.Errorf("%w: %s", - ErrUnknownPaymentStatus, m.Status) - } - } - - // Now we determine whether we need to wait when the remainingAmt is - // already zero. - switch m.Status { - // When the payment is newly created, yet the payment has no remaining - // amount, return an error. - case StatusInitiated: - return false, fmt.Errorf("%w: %v", - ErrPaymentInternal, m.Status) - - // If the payment is inflight, we must wait. - // - // NOTE: an edge case is when all HTLCs are failed while the payment is - // not failed we'd still be in this inflight state. However, since the - // remainingAmt is zero here, it means we cannot be in that state as - // otherwise the remainingAmt would not be zero. - case StatusInFlight: - return true, nil - - // If the payment is already succeeded, no need to wait. - case StatusSucceeded: - return false, nil - - // If the payment is already failed, yet the remaining amount is zero, - // return an error as this indicates an error state. We will only each - // this status when there are no inflight HTLCs and the payment is - // marked as failed with a reason, which means the remainingAmt must - // not be zero because our sentAmt is zero. - case StatusFailed: - return false, fmt.Errorf("%w: %v", - ErrPaymentInternal, m.Status) - - // Unknown payment status. - default: - return false, fmt.Errorf("%w: %s", - ErrUnknownPaymentStatus, m.Status) - } -} - -// GetState returns the internal state of the payment. -func (m *MPPayment) GetState() *MPPaymentState { - return m.State -} - -// GetStatus returns the current status of the payment. -func (m *MPPayment) GetStatus() PaymentStatus { - return m.Status -} - -// GetHTLCs returns all the HTLCs for this payment. -func (m *MPPayment) GetHTLCs() []HTLCAttempt { - return m.HTLCs -} - -// AllowMoreAttempts is used to decide whether we can safely attempt more HTLCs -// for a given payment state. Return an error if the payment is in an -// unexpected state. -func (m *MPPayment) AllowMoreAttempts() (bool, error) { - // Now check whether the remainingAmt is zero or not. If we don't have - // any remainingAmt, no more HTLCs should be made. - if m.State.RemainingAmt == 0 { - // If the payment is newly created, yet we don't have any - // remainingAmt, return an error. - if m.Status == StatusInitiated { - return false, fmt.Errorf("%w: initiated payment has "+ - "zero remainingAmt", - ErrPaymentInternal) - } - - // Otherwise, exit early since all other statuses with zero - // remainingAmt indicate no more HTLCs can be made. - return false, nil - } - - // Otherwise, the remaining amount is not zero, we now decide whether - // to make more attempts based on the payment's current status. - // - // If at least one of the payment's attempts is settled, yet we haven't - // sent all the amount, it indicates something is wrong with the peer - // as the preimage is received. In this case, return an error state. - if m.Status == StatusSucceeded { - return false, fmt.Errorf("%w: payment already succeeded but "+ - "still have remaining amount %v", - ErrPaymentInternal, m.State.RemainingAmt) - } - - // Now check if we can register a new HTLC. - err := m.Registrable() - if err != nil { - log.Warnf("Payment(%v): cannot register HTLC attempt: %v, "+ - "current status: %s", m.Info.PaymentIdentifier, - err, m.Status) - - return false, nil - } - - // Now we know we can register new HTLCs. - return true, nil -} - -// verifyAttempt validates that a new HTLC attempt is compatible with the -// existing payment and its in-flight HTLCs. This function checks: -// 1. MPP (Multi-Path Payment) compatibility between attempts -// 2. Blinded payment consistency -// 3. Amount validation -// 4. Total payment amount limits -func verifyAttempt(payment *MPPayment, attempt *HTLCAttemptInfo) error { - // If the final hop has encrypted data, then we know this is a - // blinded payment. In blinded payments, MPP records are not set - // for split payments and the recipient is responsible for using - // a consistent PathID across the various encrypted data - // payloads that we received from them for this payment. All we - // need to check is that the total amount field for each HTLC - // in the split payment is correct. - isBlinded := len(attempt.Route.FinalHop().EncryptedData) != 0 - - // For blinded payments, the last hop must set the total amount. - if isBlinded { - if attempt.Route.FinalHop().TotalAmtMsat == 0 { - return ErrBlindedPaymentMissingTotalAmount - } - } - - // Make sure any existing shards match the new one with regards - // to MPP options. - mpp := attempt.Route.FinalHop().MPP - - // MPP records should not be set for attempts to blinded paths. - if isBlinded && mpp != nil { - return ErrMPPRecordInBlindedPayment - } - - for _, h := range payment.InFlightHTLCs() { - hMpp := h.Route.FinalHop().MPP - hBlinded := len(h.Route.FinalHop().EncryptedData) != 0 - - // If this is a blinded payment, then no existing HTLCs - // should have MPP records. - if isBlinded && hMpp != nil { - return ErrMPPRecordInBlindedPayment - } - - // If the payment is blinded (previous attempts used blinded - // paths) and the attempt is not, or vice versa, return an - // error. - if isBlinded != hBlinded { - return ErrMixedBlindedAndNonBlindedPayments - } - - // If this is a blinded payment, then we just need to - // check that the TotalAmtMsat field for this shard - // is equal to that of any other shard in the same - // payment. - if isBlinded { - if attempt.Route.FinalHop().TotalAmtMsat != - h.Route.FinalHop().TotalAmtMsat { - - return ErrBlindedPaymentTotalAmountMismatch - } - - continue - } - - switch { - // We tried to register a non-MPP attempt for a MPP - // payment. - case mpp == nil && hMpp != nil: - return ErrMPPayment - - // We tried to register a MPP shard for a non-MPP - // payment. - case mpp != nil && hMpp == nil: - return ErrNonMPPayment - - // Non-MPP payment, nothing more to validate. - case mpp == nil: - continue - } - - // Check that MPP options match. - if mpp.PaymentAddr() != hMpp.PaymentAddr() { - return ErrMPPPaymentAddrMismatch - } - - if mpp.TotalMsat() != hMpp.TotalMsat() { - return ErrMPPTotalAmountMismatch - } - } - - // If this is a non-MPP attempt, it must match the total amount - // exactly. Note that a blinded payment is considered an MPP - // attempt. - amt := attempt.Route.ReceiverAmt() - if !isBlinded && mpp == nil && amt != payment.Info.Value { - return ErrValueMismatch - } - - // Ensure we aren't sending more than the total payment amount. - sentAmt, _ := payment.SentAmt() - if sentAmt+amt > payment.Info.Value { - return fmt.Errorf("%w: attempted=%v, payment amount=%v", - ErrValueExceedsAmt, sentAmt+amt, payment.Info.Value) - } - - return nil -} diff --git a/payments/db/migration1/payment_status.go b/payments/db/migration1/payment_status.go deleted file mode 100644 index 16c4b90fb..000000000 --- a/payments/db/migration1/payment_status.go +++ /dev/null @@ -1,257 +0,0 @@ -package migration1 - -import ( - "fmt" -) - -// PaymentStatus represent current status of payment. -type PaymentStatus byte - -const ( - // NOTE: PaymentStatus = 0 was previously used for status unknown and - // is now deprecated. - - // StatusInitiated is the status where a payment has just been - // initiated. - StatusInitiated PaymentStatus = 1 - - // StatusInFlight is the status where a payment has been initiated, but - // a response has not been received. - StatusInFlight PaymentStatus = 2 - - // StatusSucceeded is the status where a payment has been initiated and - // the payment was completed successfully. - StatusSucceeded PaymentStatus = 3 - - // StatusFailed is the status where a payment has been initiated and a - // failure result has come back. - StatusFailed PaymentStatus = 4 -) - -// errPaymentStatusUnknown is returned when a payment has an unknown status. -var errPaymentStatusUnknown = fmt.Errorf("unknown payment status") - -// String returns readable representation of payment status. -func (ps PaymentStatus) String() string { - switch ps { - case StatusInitiated: - return "Initiated" - - case StatusInFlight: - return "In Flight" - - case StatusSucceeded: - return "Succeeded" - - case StatusFailed: - return "Failed" - - default: - return "Unknown" - } -} - -// initializable returns an error to specify whether initiating the payment -// with its current status is allowed. A payment can only be initialized if it -// hasn't been created yet or already failed. -func (ps PaymentStatus) initializable() error { - switch ps { - // The payment has been created already. We will disallow creating it - // again in case other goroutines have already been creating HTLCs for - // it. - case StatusInitiated: - return ErrPaymentExists - - // We already have an InFlight payment on the network. We will disallow - // any new payments. - case StatusInFlight: - return ErrPaymentInFlight - - // The payment has been attempted and is succeeded so we won't allow - // creating it again. - case StatusSucceeded: - return ErrAlreadyPaid - - // We allow retrying failed payments. - case StatusFailed: - return nil - - default: - return fmt.Errorf("%w: %v", ErrUnknownPaymentStatus, - ps) - } -} - -// removable returns an error to specify whether deleting the payment with its -// current status is allowed. A payment cannot be safely deleted if it has -// inflight HTLCs. -func (ps PaymentStatus) removable() error { - switch ps { - // The payment has been created but has no HTLCs and can be removed. - case StatusInitiated: - return nil - - // There are still inflight HTLCs and the payment needs to wait for the - // final outcomes. - case StatusInFlight: - return ErrPaymentInFlight - - // The payment has been attempted and is succeeded and is allowed to be - // removed. - case StatusSucceeded: - return nil - - // Failed payments are allowed to be removed. - case StatusFailed: - return nil - - default: - return fmt.Errorf("%w: %v", ErrUnknownPaymentStatus, - ps) - } -} - -// updatable returns an error to specify whether the payment's HTLCs can be -// updated. A payment can update its HTLCs when it has inflight HTLCs. -func (ps PaymentStatus) updatable() error { - switch ps { - // Newly created payments can be updated. - case StatusInitiated: - return nil - - // Inflight payments can be updated. - case StatusInFlight: - return nil - - // If the payment has a terminal condition, we won't allow any updates. - case StatusSucceeded: - return ErrPaymentAlreadySucceeded - - case StatusFailed: - return ErrPaymentAlreadyFailed - - default: - return fmt.Errorf("%w: %v", ErrUnknownPaymentStatus, - ps) - } -} - -// decidePaymentStatus uses the payment's DB state to determine a memory status -// that's used by the payment router to decide following actions. -// Together, we use four variables to determine the payment's status, -// - inflight: whether there are any pending HTLCs. -// - settled: whether any of the HTLCs has been settled. -// - htlc failed: whether any of the HTLCs has been failed. -// - payment failed: whether the payment has been marked as failed. -// -// Based on the above variables, we derive the status using the following -// table, -// | inflight | settled | htlc failed | payment failed | status | -// |:--------:|:-------:|:-----------:|:--------------:|:--------------------:| -// | true | true | true | true | StatusInFlight | -// | true | true | true | false | StatusInFlight | -// | true | true | false | true | StatusInFlight | -// | true | true | false | false | StatusInFlight | -// | true | false | true | true | StatusInFlight | -// | true | false | true | false | StatusInFlight | -// | true | false | false | true | StatusInFlight | -// | true | false | false | false | StatusInFlight | -// | false | true | true | true | StatusSucceeded | -// | false | true | true | false | StatusSucceeded | -// | false | true | false | true | StatusSucceeded | -// | false | true | false | false | StatusSucceeded | -// | false | false | true | true | StatusFailed | -// | false | false | true | false | StatusInFlight | -// | false | false | false | true | StatusFailed | -// | false | false | false | false | StatusInitiated | -// -// When `inflight`, `settled`, `htlc failed`, and `payment failed` are false, -// this indicates the payment is newly created and hasn't made any HTLCs yet. -// When `inflight` and `settled` are false, `htlc failed` is true yet `payment -// failed` is false, this indicates all the payment's HTLCs have occurred a -// temporarily failure and the payment is still in-flight. -func decidePaymentStatus(htlcs []HTLCAttempt, - reason *FailureReason) (PaymentStatus, error) { - - var ( - inflight bool - htlcSettled bool - htlcFailed bool - paymentFailed bool - ) - - // If we have a failure reason, the payment is failed. - if reason != nil { - paymentFailed = true - } - - // Go through all HTLCs for this payment, check whether we have any - // settled HTLC, and any still in-flight. - for _, h := range htlcs { - if h.Failure != nil { - htlcFailed = true - continue - } - - if h.Settle != nil { - htlcSettled = true - continue - } - - // If any of the HTLCs are not failed nor settled, we - // still have inflight HTLCs. - inflight = true - } - - // Use the DB state to determine the status of the payment. - switch { - // If we have inflight HTLCs, no matter we have settled or failed - // HTLCs, or the payment failed, we still consider it inflight so we - // inform upper systems to wait for the results. - case inflight: - return StatusInFlight, nil - - // If we have no in-flight HTLCs, and at least one of the HTLCs is - // settled, the payment succeeded. - // - // NOTE: when reaching this case, paymentFailed could be true, which - // means we have a conflicting state for this payment. We choose to - // mark the payment as succeeded because it's the receiver's - // responsibility to only settle the payment iff all HTLCs are - // received. - case htlcSettled: - return StatusSucceeded, nil - - // If we have no in-flight HTLCs, and the payment failure is set, the - // payment is considered failed. - // - // NOTE: when reaching this case, settled must be false. - case paymentFailed: - return StatusFailed, nil - - // If we have no in-flight HTLCs, yet the payment is NOT failed, it - // means all the HTLCs are failed. In this case we can attempt more - // HTLCs. - // - // NOTE: when reaching this case, both settled and paymentFailed must - // be false. - case htlcFailed: - return StatusInFlight, nil - - // If none of the HTLCs is either settled or failed, and we have no - // inflight HTLCs, this means the payment has no HTLCs created yet. - // - // NOTE: when reaching this case, both settled and paymentFailed must - // be false. - case !htlcFailed: - return StatusInitiated, nil - - // Otherwise an impossible state is reached. - // - // NOTE: we should never end up here. - default: - log.Error("Impossible payment state reached") - return 0, fmt.Errorf("%w: payment is corrupted", - errPaymentStatusUnknown) - } -} diff --git a/payments/db/migration1/query.go b/payments/db/migration1/query.go deleted file mode 100644 index 1fab2fbd9..000000000 --- a/payments/db/migration1/query.go +++ /dev/null @@ -1,75 +0,0 @@ -package migration1 - -const ( - // DefaultMaxPayments is the default maximum number of payments returned - // in the payments query pagination. - DefaultMaxPayments = 100 -) - -// Query represents a query to the payments database starting or ending -// at a certain offset index. The number of retrieved records can be limited. -type Query struct { - // IndexOffset determines the starting point of the payments query and - // is always exclusive. In normal order, the query starts at the next - // higher (available) index compared to IndexOffset. In reversed order, - // the query ends at the next lower (available) index compared to the - // IndexOffset. In the case of a zero index_offset, the query will start - // with the oldest payment when paginating forwards, or will end with - // the most recent payment when paginating backwards. - IndexOffset uint64 - - // MaxPayments is the maximal number of payments returned in the - // payments query. - MaxPayments uint64 - - // Reversed gives a meaning to the IndexOffset. If reversed is set to - // true, the query will fetch payments with indices lower than the - // IndexOffset, otherwise, it will return payments with indices greater - // than the IndexOffset. - Reversed bool - - // If IncludeIncomplete is true, then return payments that have not yet - // fully completed. This means that pending payments, as well as failed - // payments will show up if this field is set to true. - IncludeIncomplete bool - - // CountTotal indicates that all payments currently present in the - // payment index (complete and incomplete) should be counted. - CountTotal bool - - // CreationDateStart, expressed in Unix seconds, if set, filters out - // all payments with a creation date greater than or equal to it. - CreationDateStart int64 - - // CreationDateEnd, expressed in Unix seconds, if set, filters out all - // payments with a creation date less than or equal to it. - CreationDateEnd int64 -} - -// Response contains the result of a query to the payments database. -// It includes the set of payments that match the query and integers which -// represent the index of the first and last item returned in the series of -// payments. These integers allow callers to resume their query in the event -// that the query's response exceeds the max number of returnable events. -type Response struct { - // Payments is the set of payments returned from the database for the - // Query. - Payments []*MPPayment - - // FirstIndexOffset is the index of the first element in the set of - // returned MPPayments. Callers can use this to resume their query - // in the event that the slice has too many events to fit into a single - // response. The offset can be used to continue reverse pagination. - FirstIndexOffset uint64 - - // LastIndexOffset is the index of the last element in the set of - // returned MPPayments. Callers can use this to resume their query - // in the event that the slice has too many events to fit into a single - // response. The offset can be used to continue forward pagination. - LastIndexOffset uint64 - - // TotalCount represents the total number of payments that are currently - // stored in the payment database. This will only be set if the - // CountTotal field in the query was set to true. - TotalCount uint64 -} diff --git a/payments/db/migration1/record/amp.go b/payments/db/migration1/record/amp.go deleted file mode 100644 index f63c7a141..000000000 --- a/payments/db/migration1/record/amp.go +++ /dev/null @@ -1,121 +0,0 @@ -package record - -import ( - "fmt" - "io" - - "github.com/lightningnetwork/lnd/tlv" -) - -// AMPOnionType is the type used in the onion to reference the AMP fields: -// root_share, set_id, and child_index. -const AMPOnionType tlv.Type = 14 - -// AMP is a record that encodes the fields necessary for atomic multi-path -// payments. -type AMP struct { - rootShare [32]byte - setID [32]byte - childIndex uint32 -} - -// MaxAmpPayLoadSize is an AMP Record which when serialized to a tlv record uses -// the maximum payload size. The `childIndex` is created randomly and is a -// 4 byte `varint` type so we make sure we use an index which will be encoded in -// 4 bytes. -var MaxAmpPayLoadSize = AMP{ - rootShare: [32]byte{}, - setID: [32]byte{}, - childIndex: 0x80000000, -} - -// NewAMP generate a new AMP record with the given root_share, set_id, and -// child_index. -func NewAMP(rootShare, setID [32]byte, childIndex uint32) *AMP { - return &{ - rootShare: rootShare, - setID: setID, - childIndex: childIndex, - } -} - -// RootShare returns the root share contained in the AMP record. -func (a *AMP) RootShare() [32]byte { - return a.rootShare -} - -// SetID returns the set id contained in the AMP record. -func (a *AMP) SetID() [32]byte { - return a.setID -} - -// ChildIndex returns the child index contained in the AMP record. -func (a *AMP) ChildIndex() uint32 { - return a.childIndex -} - -// AMPEncoder writes the AMP record to the provided io.Writer. -func AMPEncoder(w io.Writer, val interface{}, buf *[8]byte) error { - if v, ok := val.(*AMP); ok { - if err := tlv.EBytes32(w, &v.rootShare, buf); err != nil { - return err - } - - if err := tlv.EBytes32(w, &v.setID, buf); err != nil { - return err - } - - return tlv.ETUint32T(w, v.childIndex, buf) - } - return tlv.NewTypeForEncodingErr(val, "AMP") -} - -const ( - // minAMPLength is the minimum length of a serialized AMP TLV record, - // which occurs when the truncated encoding of child_index takes 0 - // bytes, leaving only the root_share and set_id. - minAMPLength = 64 - - // maxAMPLength is the maximum length of a serialized AMP TLV record, - // which occurs when the truncated encoding of a child_index takes 2 - // bytes. - maxAMPLength = 68 -) - -// AMPDecoder reads the AMP record from the provided io.Reader. -func AMPDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error { - if v, ok := val.(*AMP); ok && minAMPLength <= l && l <= maxAMPLength { - if err := tlv.DBytes32(r, &v.rootShare, buf, 32); err != nil { - return err - } - - if err := tlv.DBytes32(r, &v.setID, buf, 32); err != nil { - return err - } - - return tlv.DTUint32(r, &v.childIndex, buf, l-minAMPLength) - } - return tlv.NewTypeForDecodingErr(val, "AMP", l, maxAMPLength) -} - -// Record returns a tlv.Record that can be used to encode or decode this record. -func (a *AMP) Record() tlv.Record { - return tlv.MakeDynamicRecord( - AMPOnionType, a, a.PayloadSize, AMPEncoder, AMPDecoder, - ) -} - -// PayloadSize returns the size this record takes up in encoded form. -func (a *AMP) PayloadSize() uint64 { - return 32 + 32 + tlv.SizeTUint32(a.childIndex) -} - -// String returns a human-readable description of the amp payload fields. -func (a *AMP) String() string { - if a == nil { - return "" - } - - return fmt.Sprintf("root_share=%x set_id=%x child_index=%d", - a.rootShare, a.setID, a.childIndex) -} diff --git a/payments/db/migration1/record/blinded_data.go b/payments/db/migration1/record/blinded_data.go deleted file mode 100644 index 52f0e6556..000000000 --- a/payments/db/migration1/record/blinded_data.go +++ /dev/null @@ -1,442 +0,0 @@ -package record - -import ( - "bytes" - "encoding/binary" - "io" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire" - "github.com/lightningnetwork/lnd/tlv" -) - -// AverageDummyHopPayloadSize is the size of a standard blinded path dummy hop -// payload. In most cases, this is larger than the other payload types and so -// to make sure that a sender cannot use this fact to know if a dummy hop is -// present or not, we'll make sure to always pad all payloads to at least this -// size. -const AverageDummyHopPayloadSize = 51 - -// BlindedRouteData contains the information that is included in a blinded -// route encrypted data blob that is created by the recipient to provide -// forwarding information. -type BlindedRouteData struct { - // Padding is an optional set of bytes that a recipient can use to pad - // the data so that the encrypted recipient data blobs are all the same - // length. - Padding tlv.OptionalRecordT[tlv.TlvType1, []byte] - - // ShortChannelID is the channel ID of the next hop. - ShortChannelID tlv.OptionalRecordT[tlv.TlvType2, lnwire.ShortChannelID] - - // NextNodeID is the node ID of the next node on the path. In the - // context of blinded path payments, this is used to indicate the - // presence of dummy hops that need to be peeled from the onion, or to - // identify a real next-node forwarding target when the public key is - // not ours. - NextNodeID tlv.OptionalRecordT[tlv.TlvType4, *btcec.PublicKey] - - // PathID is a secret set of bytes that the blinded path creator will - // set so that they can check the value on decryption to ensure that the - // path they created was used for the intended purpose. - PathID tlv.OptionalRecordT[tlv.TlvType6, []byte] - - // NextBlindingOverride is a blinding point that should be switched - // in for the next hop. This is used to combine two blinded paths into - // one (which primarily is used in onion messaging, but in theory - // could be used for payments as well). - NextBlindingOverride tlv.OptionalRecordT[tlv.TlvType8, *btcec.PublicKey] - - // RelayInfo provides the relay parameters for the hop. - RelayInfo tlv.OptionalRecordT[tlv.TlvType10, PaymentRelayInfo] - - // Constraints provides the payment relay constraints for the hop. - Constraints tlv.OptionalRecordT[tlv.TlvType12, PaymentConstraints] - - // Features is the set of features the payment requires. - Features tlv.OptionalRecordT[tlv.TlvType14, lnwire.FeatureVector] -} - -// NewNonFinalBlindedRouteData creates the data that's provided for hops within -// a blinded route. -func NewNonFinalBlindedRouteData(chanID lnwire.ShortChannelID, - blindingOverride *btcec.PublicKey, relayInfo PaymentRelayInfo, - constraints *PaymentConstraints, - features *lnwire.FeatureVector) *BlindedRouteData { - - info := &BlindedRouteData{ - ShortChannelID: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType2](chanID), - ), - RelayInfo: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType10](relayInfo), - ), - } - - if blindingOverride != nil { - info.NextBlindingOverride = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType8](blindingOverride)) - } - - if constraints != nil { - info.Constraints = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType12](*constraints)) - } - - if features != nil { - info.Features = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType14](*features), - ) - } - - return info -} - -// NewFinalHopBlindedRouteData creates the data that's provided for the final -// hop in a blinded route. -func NewFinalHopBlindedRouteData(constraints *PaymentConstraints, - pathID []byte) *BlindedRouteData { - - var data BlindedRouteData - if pathID != nil { - data.PathID = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType6](pathID), - ) - } - - if constraints != nil { - data.Constraints = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType12](*constraints)) - } - - return &data -} - -// NewDummyHopRouteData creates the data that's provided for any hop preceding -// a dummy hop. The presence of such a payload indicates to the reader that -// they are the intended recipient and should peel the remainder of the onion. -func NewDummyHopRouteData(ourPubKey *btcec.PublicKey, - relayInfo PaymentRelayInfo, - constraints PaymentConstraints) *BlindedRouteData { - - return &BlindedRouteData{ - NextNodeID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType4](ourPubKey), - ), - RelayInfo: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType10](relayInfo), - ), - Constraints: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType12](constraints), - ), - } -} - -// DecodeBlindedRouteData decodes the data provided within a blinded route. -func DecodeBlindedRouteData(r io.Reader) (*BlindedRouteData, error) { - var ( - d BlindedRouteData - - padding = d.Padding.Zero() - scid = d.ShortChannelID.Zero() - nextNodeID = d.NextNodeID.Zero() - pathID = d.PathID.Zero() - blindingOverride = d.NextBlindingOverride.Zero() - relayInfo = d.RelayInfo.Zero() - constraints = d.Constraints.Zero() - features = d.Features.Zero() - ) - - var tlvRecords lnwire.ExtraOpaqueData - if err := lnwire.ReadElements(r, &tlvRecords); err != nil { - return nil, err - } - - typeMap, err := tlvRecords.ExtractRecords( - &padding, &scid, &nextNodeID, &pathID, &blindingOverride, - &relayInfo, &constraints, &features, - ) - if err != nil { - return nil, err - } - - val, ok := typeMap[d.Padding.TlvType()] - if ok && val == nil { - d.Padding = tlv.SomeRecordT(padding) - } - - if val, ok := typeMap[d.ShortChannelID.TlvType()]; ok && val == nil { - d.ShortChannelID = tlv.SomeRecordT(scid) - } - - if val, ok := typeMap[d.NextNodeID.TlvType()]; ok && val == nil { - d.NextNodeID = tlv.SomeRecordT(nextNodeID) - } - - if val, ok := typeMap[d.PathID.TlvType()]; ok && val == nil { - d.PathID = tlv.SomeRecordT(pathID) - } - - val, ok = typeMap[d.NextBlindingOverride.TlvType()] - if ok && val == nil { - d.NextBlindingOverride = tlv.SomeRecordT(blindingOverride) - } - - if val, ok := typeMap[d.RelayInfo.TlvType()]; ok && val == nil { - d.RelayInfo = tlv.SomeRecordT(relayInfo) - } - - if val, ok := typeMap[d.Constraints.TlvType()]; ok && val == nil { - d.Constraints = tlv.SomeRecordT(constraints) - } - - if val, ok := typeMap[d.Features.TlvType()]; ok && val == nil { - d.Features = tlv.SomeRecordT(features) - } - - return &d, nil -} - -// EncodeBlindedRouteData encodes the blinded route data provided. -func EncodeBlindedRouteData(data *BlindedRouteData) ([]byte, error) { - var ( - e lnwire.ExtraOpaqueData - recordProducers = make([]tlv.RecordProducer, 0, 5) - ) - - data.Padding.WhenSome(func(p tlv.RecordT[tlv.TlvType1, []byte]) { - recordProducers = append(recordProducers, &p) - }) - - data.ShortChannelID.WhenSome(func(scid tlv.RecordT[tlv.TlvType2, - lnwire.ShortChannelID]) { - - recordProducers = append(recordProducers, &scid) - }) - - data.NextNodeID.WhenSome(func(f tlv.RecordT[tlv.TlvType4, - *btcec.PublicKey]) { - - recordProducers = append(recordProducers, &f) - }) - - data.PathID.WhenSome(func(pathID tlv.RecordT[tlv.TlvType6, []byte]) { - recordProducers = append(recordProducers, &pathID) - }) - - data.NextBlindingOverride.WhenSome(func(pk tlv.RecordT[tlv.TlvType8, - *btcec.PublicKey]) { - - recordProducers = append(recordProducers, &pk) - }) - - data.RelayInfo.WhenSome(func(r tlv.RecordT[tlv.TlvType10, - PaymentRelayInfo]) { - - recordProducers = append(recordProducers, &r) - }) - - data.Constraints.WhenSome(func(cs tlv.RecordT[tlv.TlvType12, - PaymentConstraints]) { - - recordProducers = append(recordProducers, &cs) - }) - - data.Features.WhenSome(func(f tlv.RecordT[tlv.TlvType14, - lnwire.FeatureVector]) { - - recordProducers = append(recordProducers, &f) - }) - - if err := e.PackRecords(recordProducers...); err != nil { - return nil, err - } - - return e[:], nil -} - -// PadBy adds "n" padding bytes to the BlindedRouteData using the Padding field. -// Callers should be aware that the total payload size will change by more than -// "n" since the "n" bytes will be prefixed by BigSize type and length fields. -// Callers may need to call PadBy iteratively until each encrypted data packet -// is the same size and so each call will overwrite the Padding record. -// Note that calling PadBy with an n value of 0 will still result in a zero -// length TLV entry being added. -func (b *BlindedRouteData) PadBy(n int) { - b.Padding = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType1](make([]byte, n)), - ) -} - -// PaymentRelayInfo describes the relay policy for a blinded path. -type PaymentRelayInfo struct { - // CltvExpiryDelta is the expiry delta for the payment. - CltvExpiryDelta uint16 - - // FeeRate is the fee rate that will be charged per millionth of a - // satoshi. - FeeRate uint32 - - // BaseFee is the per-htlc fee charged in milli-satoshis. - BaseFee lnwire.MilliSatoshi -} - -// Record creates a tlv.Record that encodes the payment relay (type 10) type for -// an encrypted blob payload. -func (i *PaymentRelayInfo) Record() tlv.Record { - return tlv.MakeDynamicRecord( - 10, &i, func() uint64 { - // uint16 + uint32 + tuint32 - return 2 + 4 + tlv.SizeTUint32(uint32(i.BaseFee)) - }, encodePaymentRelay, decodePaymentRelay, - ) -} - -func encodePaymentRelay(w io.Writer, val interface{}, buf *[8]byte) error { - if t, ok := val.(**PaymentRelayInfo); ok { - relayInfo := *t - - // Just write our first 6 bytes directly. - binary.BigEndian.PutUint16(buf[:2], relayInfo.CltvExpiryDelta) - binary.BigEndian.PutUint32(buf[2:6], relayInfo.FeeRate) - if _, err := w.Write(buf[0:6]); err != nil { - return err - } - - baseFee := uint32(relayInfo.BaseFee) - - // We can safely reuse buf here because we overwrite its - // contents. - return tlv.ETUint32(w, &baseFee, buf) - } - - return tlv.NewTypeForEncodingErr(val, "**hop.PaymentRelayInfo") -} - -func decodePaymentRelay(r io.Reader, val interface{}, buf *[8]byte, - l uint64) error { - - if t, ok := val.(**PaymentRelayInfo); ok && l <= 10 { - scratch := make([]byte, l) - - n, err := io.ReadFull(r, scratch) - if err != nil { - return err - } - - // We expect at least 6 bytes, because we have 2 bytes for - // cltv delta and 4 bytes for fee rate. - if n < 6 { - return tlv.NewTypeForDecodingErr(val, - "*hop.paymentRelayInfo", uint64(n), 6) - } - - relayInfo := *t - - relayInfo.CltvExpiryDelta = binary.BigEndian.Uint16( - scratch[0:2], - ) - relayInfo.FeeRate = binary.BigEndian.Uint32(scratch[2:6]) - - // To be able to re-use the DTUint32 function we create a - // buffer with just the bytes holding the variable length u32. - // If the base fee is zero, this will be an empty buffer, which - // is okay. - b := bytes.NewBuffer(scratch[6:]) - - var baseFee uint32 - err = tlv.DTUint32(b, &baseFee, buf, l-6) - if err != nil { - return err - } - - relayInfo.BaseFee = lnwire.MilliSatoshi(baseFee) - - return nil - } - - return tlv.NewTypeForDecodingErr(val, "*hop.paymentRelayInfo", l, 10) -} - -// PaymentConstraints is a set of restrictions on a payment. -type PaymentConstraints struct { - // MaxCltvExpiry is the maximum expiry height for the payment. - MaxCltvExpiry uint32 - - // HtlcMinimumMsat is the minimum htlc size for the payment. - HtlcMinimumMsat lnwire.MilliSatoshi -} - -func (p *PaymentConstraints) Record() tlv.Record { - return tlv.MakeDynamicRecord( - 12, &p, func() uint64 { - // uint32 + tuint64. - return 4 + tlv.SizeTUint64(uint64( - p.HtlcMinimumMsat, - )) - }, - encodePaymentConstraints, decodePaymentConstraints, - ) -} - -func encodePaymentConstraints(w io.Writer, val interface{}, - buf *[8]byte) error { - - if c, ok := val.(**PaymentConstraints); ok { - constraints := *c - - binary.BigEndian.PutUint32(buf[:4], constraints.MaxCltvExpiry) - if _, err := w.Write(buf[:4]); err != nil { - return err - } - - // We can safely re-use buf here because we overwrite its - // contents. - htlcMsat := uint64(constraints.HtlcMinimumMsat) - - return tlv.ETUint64(w, &htlcMsat, buf) - } - - return tlv.NewTypeForEncodingErr(val, "**PaymentConstraints") -} - -func decodePaymentConstraints(r io.Reader, val interface{}, buf *[8]byte, - l uint64) error { - - if c, ok := val.(**PaymentConstraints); ok && l <= 12 { - scratch := make([]byte, l) - - n, err := io.ReadFull(r, scratch) - if err != nil { - return err - } - - // We expect at least 4 bytes for our uint32. - if n < 4 { - return tlv.NewTypeForDecodingErr(val, - "*paymentConstraints", uint64(n), 4) - } - - payConstraints := *c - - payConstraints.MaxCltvExpiry = binary.BigEndian.Uint32( - scratch[:4], - ) - - // This could be empty if our minimum is zero, that's okay. - var ( - b = bytes.NewBuffer(scratch[4:]) - minHtlc uint64 - ) - - err = tlv.DTUint64(b, &minHtlc, buf, l-4) - if err != nil { - return err - } - payConstraints.HtlcMinimumMsat = lnwire.MilliSatoshi(minHtlc) - - return nil - } - - return tlv.NewTypeForDecodingErr(val, "**PaymentConstraints", l, l) -} diff --git a/payments/db/migration1/record/custom_records.go b/payments/db/migration1/record/custom_records.go deleted file mode 100644 index 01952c24e..000000000 --- a/payments/db/migration1/record/custom_records.go +++ /dev/null @@ -1,31 +0,0 @@ -package record - -import ( - "fmt" -) - -const ( - // CustomTypeStart is the start of the custom tlv type range as defined - // in BOLT 01. - CustomTypeStart = 65536 -) - -// CustomSet stores a set of custom key/value pairs. -type CustomSet map[uint64][]byte - -// Validate checks that all custom records are in the custom type range. -func (c CustomSet) Validate() error { - for key := range c { - if key < CustomTypeStart { - return fmt.Errorf("no custom records with types "+ - "below %v allowed", CustomTypeStart) - } - } - - return nil -} - -// IsKeysend checks if the custom records contain the key send type. -func (c CustomSet) IsKeysend() bool { - return c[KeySendType] != nil -} diff --git a/payments/db/migration1/record/experimental.go b/payments/db/migration1/record/experimental.go deleted file mode 100644 index 3aff0ff26..000000000 --- a/payments/db/migration1/record/experimental.go +++ /dev/null @@ -1,6 +0,0 @@ -package record - -const ( - // KeySendType is the custom record identifier for keysend preimages. - KeySendType uint64 = 5482373484 -) diff --git a/payments/db/migration1/record/hop.go b/payments/db/migration1/record/hop.go deleted file mode 100644 index e5c0884f1..000000000 --- a/payments/db/migration1/record/hop.go +++ /dev/null @@ -1,99 +0,0 @@ -package record - -import ( - "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/tlv" -) - -const ( - // AmtOnionType is the type used in the onion to reference the amount to - // send to the next hop. - AmtOnionType tlv.Type = 2 - - // LockTimeTLV is the type used in the onion to reference the CLTV - // value that should be used for the next hop's HTLC. - LockTimeOnionType tlv.Type = 4 - - // NextHopOnionType is the type used in the onion to reference the ID - // of the next hop. - NextHopOnionType tlv.Type = 6 - - // EncryptedDataOnionType is the type used to include encrypted data - // provided by the receiver in the onion for use in blinded paths. - EncryptedDataOnionType tlv.Type = 10 - - // BlindingPointOnionType is the type used to include receiver provided - // ephemeral keys in the onion that are used in blinded paths. - BlindingPointOnionType tlv.Type = 12 - - // MetadataOnionType is the type used in the onion for the payment - // metadata. - MetadataOnionType tlv.Type = 16 - - // TotalAmtMsatBlindedType is the type used in the onion for the total - // amount field that is included in the final hop for blinded payments. - TotalAmtMsatBlindedType tlv.Type = 18 -) - -// NewAmtToFwdRecord creates a tlv.Record that encodes the amount_to_forward -// (type 2) for an onion payload. -func NewAmtToFwdRecord(amt *uint64) tlv.Record { - return tlv.MakeDynamicRecord( - AmtOnionType, amt, func() uint64 { - return tlv.SizeTUint64(*amt) - }, - tlv.ETUint64, tlv.DTUint64, - ) -} - -// NewLockTimeRecord creates a tlv.Record that encodes the outgoing_cltv_value -// (type 4) for an onion payload. -func NewLockTimeRecord(lockTime *uint32) tlv.Record { - return tlv.MakeDynamicRecord( - LockTimeOnionType, lockTime, func() uint64 { - return tlv.SizeTUint32(*lockTime) - }, - tlv.ETUint32, tlv.DTUint32, - ) -} - -// NewNextHopIDRecord creates a tlv.Record that encodes the short_channel_id -// (type 6) for an onion payload. -func NewNextHopIDRecord(cid *uint64) tlv.Record { - return tlv.MakePrimitiveRecord(NextHopOnionType, cid) -} - -// NewEncryptedDataRecord creates a tlv.Record that encodes the encrypted_data -// (type 10) record for an onion payload. -func NewEncryptedDataRecord(data *[]byte) tlv.Record { - return tlv.MakePrimitiveRecord(EncryptedDataOnionType, data) -} - -// NewBlindingPointRecord creates a tlv.Record that encodes the blinding_point -// (type 12) record for an onion payload. -func NewBlindingPointRecord(point **btcec.PublicKey) tlv.Record { - return tlv.MakePrimitiveRecord(BlindingPointOnionType, point) -} - -// NewMetadataRecord creates a tlv.Record that encodes the metadata (type 10) -// for an onion payload. -func NewMetadataRecord(metadata *[]byte) tlv.Record { - return tlv.MakeDynamicRecord( - MetadataOnionType, metadata, - func() uint64 { - return uint64(len(*metadata)) - }, - tlv.EVarBytes, tlv.DVarBytes, - ) -} - -// NewTotalAmtMsatBlinded creates a tlv.Record that encodes the -// total_amount_msat for the final an onion payload within a blinded route. -func NewTotalAmtMsatBlinded(amt *uint64) tlv.Record { - return tlv.MakeDynamicRecord( - TotalAmtMsatBlindedType, amt, func() uint64 { - return tlv.SizeTUint64(*amt) - }, - tlv.ETUint64, tlv.DTUint64, - ) -} diff --git a/payments/db/migration1/record/mpp.go b/payments/db/migration1/record/mpp.go deleted file mode 100644 index 576ada776..000000000 --- a/payments/db/migration1/record/mpp.go +++ /dev/null @@ -1,112 +0,0 @@ -package record - -import ( - "fmt" - "io" - - "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire" - "github.com/lightningnetwork/lnd/tlv" -) - -// MPPOnionType is the type used in the onion to reference the MPP fields: -// total_amt and payment_addr. -const MPPOnionType tlv.Type = 8 - -// MPP is a record that encodes the fields necessary for multi-path payments. -type MPP struct { - // paymentAddr is a random, receiver-generated value used to avoid - // collisions with concurrent payers. - paymentAddr [32]byte - - // totalMsat is the total value of the payment, potentially spread - // across more than one HTLC. - totalMsat lnwire.MilliSatoshi -} - -// NewMPP generates a new MPP record with the given total and payment address. -func NewMPP(total lnwire.MilliSatoshi, addr [32]byte) *MPP { - return &MPP{ - paymentAddr: addr, - totalMsat: total, - } -} - -// PaymentAddr returns the payment address contained in the MPP record. -func (r *MPP) PaymentAddr() [32]byte { - return r.paymentAddr -} - -// TotalMsat returns the total value of an MPP payment in msats. -func (r *MPP) TotalMsat() lnwire.MilliSatoshi { - return r.totalMsat -} - -// MPPEncoder writes the MPP record to the provided io.Writer. -func MPPEncoder(w io.Writer, val interface{}, buf *[8]byte) error { - if v, ok := val.(*MPP); ok { - err := tlv.EBytes32(w, &v.paymentAddr, buf) - if err != nil { - return err - } - - return tlv.ETUint64T(w, uint64(v.totalMsat), buf) - } - return tlv.NewTypeForEncodingErr(val, "MPP") -} - -const ( - // minMPPLength is the minimum length of a serialized MPP TLV record, - // which occurs when the truncated encoding of total_amt_msat takes 0 - // bytes, leaving only the payment_addr. - minMPPLength = 32 - - // maxMPPLength is the maximum length of a serialized MPP TLV record, - // which occurs when the truncated encoding of total_amt_msat takes 8 - // bytes. - maxMPPLength = 40 -) - -// MPPDecoder reads the MPP record to the provided io.Reader. -func MPPDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error { - if v, ok := val.(*MPP); ok && minMPPLength <= l && l <= maxMPPLength { - if err := tlv.DBytes32(r, &v.paymentAddr, buf, 32); err != nil { - return err - } - - var total uint64 - if err := tlv.DTUint64(r, &total, buf, l-32); err != nil { - return err - } - v.totalMsat = lnwire.MilliSatoshi(total) - - return nil - } - return tlv.NewTypeForDecodingErr(val, "MPP", l, maxMPPLength) -} - -// Record returns a tlv.Record that can be used to encode or decode this record. -func (r *MPP) Record() tlv.Record { - // Fixed-size, 32 byte payment address followed by truncated 64-bit - // total msat. - size := func() uint64 { - return 32 + tlv.SizeTUint64(uint64(r.totalMsat)) - } - - return tlv.MakeDynamicRecord( - MPPOnionType, r, size, MPPEncoder, MPPDecoder, - ) -} - -// PayloadSize returns the size this record takes up in encoded form. -func (r *MPP) PayloadSize() uint64 { - return 32 + tlv.SizeTUint64(uint64(r.totalMsat)) -} - -// String returns a human-readable representation of the mpp payload field. -func (r *MPP) String() string { - if r == nil { - return "" - } - - return fmt.Sprintf("total=%v, addr=%x", r.totalMsat, r.paymentAddr) -} diff --git a/payments/db/migration1/route.go b/payments/db/migration1/route.go deleted file mode 100644 index bdf8c6ff0..000000000 --- a/payments/db/migration1/route.go +++ /dev/null @@ -1,138 +0,0 @@ -package migration1 - -import ( - "fmt" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire" - "github.com/lightningnetwork/lnd/payments/db/migration1/record" - "github.com/lightningnetwork/lnd/tlv" -) - -// Vertex is a frozen local type representing a 33-byte compressed public key, -// equivalent to route.Vertex at the time this migration was written. -type Vertex [33]byte - -// NewVertex returns a new Vertex from a compressed public key. -func NewVertex(pub *btcec.PublicKey) Vertex { - var v Vertex - copy(v[:], pub.SerializeCompressed()) - - return v -} - -// NewVertexFromBytes returns a new Vertex from a serialized compressed public -// key byte slice. -func NewVertexFromBytes(b []byte) (Vertex, error) { - if len(b) != 33 { - return Vertex{}, fmt.Errorf("invalid vertex length %d, "+ - "expected 33", len(b)) - } - - var v Vertex - copy(v[:], b) - - return v, nil -} - -// Hop is a frozen local snapshot of route.Hop, containing exactly the fields -// that existed at the time this migration was written. This ensures the -// migration's serialization boundary is independent of future changes to -// route.Hop, in particular the planned removal of LegacyPayload once the KV -// backend is phased out. -type Hop struct { - // PubKeyBytes is the raw bytes of the public key of the target node. - PubKeyBytes Vertex - - // ChannelID is the unique channel ID for the channel. - ChannelID uint64 - - // OutgoingTimeLock is the timelock value that should be used when - // crafting the _outgoing_ HTLC from this hop. - OutgoingTimeLock uint32 - - // AmtToForward is the amount that this hop will forward to the next - // hop. - AmtToForward lnwire.MilliSatoshi - - // MPP encapsulates the data required for option_mpp. This field should - // only be set for the final hop. - MPP *record.MPP - - // AMP encapsulates the data required for option_amp. This field should - // only be set for the final hop. - AMP *record.AMP - - // CustomRecords if non-nil are a set of additional TLV records that - // should be included in the forwarding instructions for this node. - CustomRecords record.CustomSet - - // LegacyPayload signals that this node doesn't understand the new TLV - // payload, so the legacy payload format must be used. - // - // NOTE: This field is preserved here even though it is marked for - // removal in the live route.Hop, because old KV data may have been - // serialized with LegacyPayload=true and the migration must be able to - // deserialize it correctly. - LegacyPayload bool - - // Metadata is additional data sent along with the payment to the - // payee. - Metadata []byte - - // EncryptedData is an encrypted data blob included for hops that are - // part of a blinded route. - EncryptedData []byte - - // BlindingPoint is an ephemeral public key used by introduction nodes - // in blinded routes. - BlindingPoint *btcec.PublicKey - - // TotalAmtMsat is the total amount for a blinded payment. This field - // should only be set for the final hop in a blinded path. - TotalAmtMsat lnwire.MilliSatoshi -} - -// Route is a frozen local snapshot of route.Route, containing exactly the -// fields that existed at the time this migration was written. -type Route struct { - // TotalTimeLock is the cumulative (final) time lock across the entire - // route. - TotalTimeLock uint32 - - // TotalAmount is the total amount of funds required to complete a - // payment over this route, including fees. - TotalAmount lnwire.MilliSatoshi - - // SourcePubKey is the pubkey of the node where this route originates. - SourcePubKey Vertex - - // Hops contains details concerning the specific forwarding details at - // each hop. - Hops []*Hop - - // FirstHopAmount is the amount that should actually be sent to the - // first hop. Only differs from TotalAmount for custom channels. - FirstHopAmount tlv.RecordT[ - tlv.TlvType0, tlv.BigSizeT[lnwire.MilliSatoshi], - ] - - // FirstHopWireCustomRecords is a set of custom TLV records to include - // in the wire message sent to the first hop. - FirstHopWireCustomRecords lnwire.CustomRecords -} - -// FinalHop returns the last hop in the route. -func (r *Route) FinalHop() *Hop { - return r.Hops[len(r.Hops)-1] -} - -// ReceiverAmt returns the amount forwarded to the final hop. -func (r *Route) ReceiverAmt() lnwire.MilliSatoshi { - return r.FinalHop().AmtToForward -} - -// TotalFees returns the total fees paid along the route. -func (r *Route) TotalFees() lnwire.MilliSatoshi { - return r.TotalAmount - r.ReceiverAmt() -} diff --git a/payments/db/migration1/sql_converters.go b/payments/db/migration1/sql_converters.go deleted file mode 100644 index 50d129bf7..000000000 --- a/payments/db/migration1/sql_converters.go +++ /dev/null @@ -1,274 +0,0 @@ -package migration1 - -import ( - "bytes" - "fmt" - "strconv" - "time" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire" - "github.com/lightningnetwork/lnd/payments/db/migration1/record" - "github.com/lightningnetwork/lnd/payments/db/migration1/sqlc" - "github.com/lightningnetwork/lnd/tlv" -) - -// dbPaymentToCreationInfo converts database payment data to the -// PaymentCreationInfo struct. -func dbPaymentToCreationInfo(paymentIdentifier []byte, amountMsat int64, - createdAt time.Time, intentPayload []byte, - firstHopCustomRecords lnwire.CustomRecords) *PaymentCreationInfo { - - // This is the payment hash for non-AMP payments and the SetID for AMP - // payments. - var identifier lntypes.Hash - copy(identifier[:], paymentIdentifier) - - return &PaymentCreationInfo{ - PaymentIdentifier: identifier, - Value: lnwire.MilliSatoshi(amountMsat), - // The creation time is stored in the database as UTC but here - // we convert it to local time. - CreationTime: createdAt.Local(), - PaymentRequest: intentPayload, - FirstHopCustomRecords: firstHopCustomRecords, - } -} - -// dbAttemptToHTLCAttempt converts a database HTLC attempt to an HTLCAttempt. -func dbAttemptToHTLCAttempt(dbAttempt sqlc.FetchHtlcAttemptsForPaymentsRow, - hops []sqlc.FetchHopsForAttemptsRow, - hopCustomRecords map[int64][]sqlc.PaymentHopCustomRecord, - routeCustomRecords []sqlc.PaymentAttemptFirstHopCustomRecord) ( - *HTLCAttempt, error) { - - // Convert route-level first hop custom records to CustomRecords map. - var firstHopWireCustomRecords lnwire.CustomRecords - if len(routeCustomRecords) > 0 { - firstHopWireCustomRecords = make(lnwire.CustomRecords) - for _, record := range routeCustomRecords { - firstHopWireCustomRecords[uint64(record.Key)] = - record.Value - } - } - - // Build the route from the database data. - route, err := dbDataToRoute( - hops, hopCustomRecords, dbAttempt.FirstHopAmountMsat, - dbAttempt.RouteTotalTimeLock, dbAttempt.RouteTotalAmount, - dbAttempt.RouteSourceKey, firstHopWireCustomRecords, - ) - if err != nil { - return nil, fmt.Errorf("failed to convert to route: %w", - err) - } - - hash, err := lntypes.MakeHash(dbAttempt.PaymentHash) - if err != nil { - return nil, fmt.Errorf("failed to parse payment "+ - "hash: %w", err) - } - - // Create the attempt info. - var sessionKey [32]byte - copy(sessionKey[:], dbAttempt.SessionKey) - - info := HTLCAttemptInfo{ - AttemptID: uint64(dbAttempt.AttemptIndex), - sessionKey: sessionKey, - Route: *route, - AttemptTime: dbAttempt.AttemptTime, - Hash: &hash, - } - - attempt := &HTLCAttempt{ - HTLCAttemptInfo: info, - } - - // If there's no resolution type, the attempt is still in-flight. - // Return early without processing settlement or failure info. - if !dbAttempt.ResolutionType.Valid { - return attempt, nil - } - - // Add settlement info if present. - if HTLCAttemptResolutionType(dbAttempt.ResolutionType.Int32) == - HTLCAttemptResolutionSettled { - - var preimage lntypes.Preimage - copy(preimage[:], dbAttempt.SettlePreimage) - - attempt.Settle = &HTLCSettleInfo{ - Preimage: preimage, - SettleTime: dbAttempt.ResolutionTime.Time, - } - } - - // Add failure info if present. - if HTLCAttemptResolutionType(dbAttempt.ResolutionType.Int32) == - HTLCAttemptResolutionFailed { - - failure := &HTLCFailInfo{ - FailTime: dbAttempt.ResolutionTime.Time, - } - - if dbAttempt.HtlcFailReason.Valid { - failure.Reason = HTLCFailReason( - dbAttempt.HtlcFailReason.Int32, - ) - } - - if dbAttempt.FailureSourceIndex.Valid { - failure.FailureSourceIndex = uint32( - dbAttempt.FailureSourceIndex.Int32, - ) - } - - // Decode the failure message if present. - if len(dbAttempt.FailureMsg) > 0 { - msg, err := lnwire.DecodeFailureMessage( - bytes.NewReader(dbAttempt.FailureMsg), 0, - ) - if err != nil { - return nil, fmt.Errorf("failed to decode "+ - "failure message: %w", err) - } - failure.Message = msg - } - - attempt.Failure = failure - } - - return attempt, nil -} - -// dbDataToRoute converts database route data to a Route. -func dbDataToRoute(hops []sqlc.FetchHopsForAttemptsRow, - hopCustomRecords map[int64][]sqlc.PaymentHopCustomRecord, - firstHopAmountMsat int64, totalTimeLock int32, totalAmount int64, - sourceKey []byte, firstHopWireCustomRecords lnwire.CustomRecords) ( - *Route, error) { - - if len(hops) == 0 { - return nil, fmt.Errorf("no hops provided") - } - - // Hops are already sorted by hop_index from the SQL query. - routeHops := make([]*Hop, len(hops)) - - for i, hop := range hops { - pubKey, err := NewVertexFromBytes(hop.PubKey) - if err != nil { - return nil, fmt.Errorf("failed to parse pub key: %w", - err) - } - - var channelID uint64 - if hop.Scid != "" { - // The SCID is stored as a string representation - // of the uint64. - var err error - channelID, err = strconv.ParseUint(hop.Scid, 10, 64) - if err != nil { - return nil, fmt.Errorf("failed to parse "+ - "scid: %w", err) - } - } - - routeHop := &Hop{ - PubKeyBytes: pubKey, - ChannelID: channelID, - OutgoingTimeLock: uint32(hop.OutgoingTimeLock), - AmtToForward: lnwire.MilliSatoshi(hop.AmtToForward), - } - - // Add MPP record if present. - if len(hop.MppPaymentAddr) > 0 { - var paymentAddr [32]byte - copy(paymentAddr[:], hop.MppPaymentAddr) - routeHop.MPP = record.NewMPP( - lnwire.MilliSatoshi(hop.MppTotalMsat.Int64), - paymentAddr, - ) - } - - // Add AMP record if present. - if len(hop.AmpRootShare) > 0 { - var rootShare [32]byte - copy(rootShare[:], hop.AmpRootShare) - var setID [32]byte - copy(setID[:], hop.AmpSetID) - - routeHop.AMP = record.NewAMP( - rootShare, setID, - uint32(hop.AmpChildIndex.Int32), - ) - } - - // Add blinding point if present (only for introduction node - // in blinded route). - if len(hop.BlindingPoint) > 0 { - pubKey, err := btcec.ParsePubKey(hop.BlindingPoint) - if err != nil { - return nil, fmt.Errorf("failed to parse "+ - "blinding point: %w", err) - } - routeHop.BlindingPoint = pubKey - } - - // Add encrypted data if present (for all blinded hops). - if len(hop.EncryptedData) > 0 { - routeHop.EncryptedData = hop.EncryptedData - } - - // Add total amount if present (only for final hop in blinded - // route). - if hop.BlindedPathTotalAmt.Valid { - routeHop.TotalAmtMsat = lnwire.MilliSatoshi( - hop.BlindedPathTotalAmt.Int64, - ) - } - - // Add hop-level custom records. - if records, ok := hopCustomRecords[hop.ID]; ok { - routeHop.CustomRecords = make( - record.CustomSet, - ) - for _, rec := range records { - routeHop.CustomRecords[uint64(rec.Key)] = - rec.Value - } - } - - // Add metadata if present. - if len(hop.MetaData) > 0 { - routeHop.Metadata = hop.MetaData - } - - routeHops[i] = routeHop - } - - // Parse the source node public key. - var sourceNode Vertex - copy(sourceNode[:], sourceKey) - - route := &Route{ - TotalTimeLock: uint32(totalTimeLock), - TotalAmount: lnwire.MilliSatoshi(totalAmount), - SourcePubKey: sourceNode, - Hops: routeHops, - FirstHopWireCustomRecords: firstHopWireCustomRecords, - } - - // Set the first hop amount if it is set. - if firstHopAmountMsat != 0 { - route.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0]( - tlv.NewBigSizeT(lnwire.MilliSatoshi( - firstHopAmountMsat, - )), - ) - } - - return route, nil -} diff --git a/payments/db/migration1/sql_migration.go b/payments/db/migration1/sql_migration.go deleted file mode 100644 index 9e187b829..000000000 --- a/payments/db/migration1/sql_migration.go +++ /dev/null @@ -1,1156 +0,0 @@ -package migration1 - -import ( - "bytes" - "context" - "database/sql" - "fmt" - "math" - "strconv" - "time" - - "github.com/lightningnetwork/lnd/kvdb" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire" - "github.com/lightningnetwork/lnd/payments/db/migration1/sqlc" - "golang.org/x/time/rate" -) - -const ( - // defaultRateWindowDuration is the rolling window used for ETA - // calculation during migration progress reporting. - defaultRateWindowDuration = 300 * time.Second -) - -var ( - // switchNextPaymentIDKey is the switch sequencer bucket key. This is - // intentionally kept in sync with htlcswitch.nextPaymentIDKey without - // importing htlcswitch into the migration package. - switchNextPaymentIDKey = []byte("next-payment-id-key") -) - -// MigrationStats tracks migration progress. -type MigrationStats struct { - TotalPayments int64 - SuccessfulPayments int64 - FailedPayments int64 - InFlightPayments int64 - InitiatedPayments int64 - TotalAttempts int64 - SettledAttempts int64 - FailedAttempts int64 - InFlightAttempts int64 - TotalHops int64 - DuplicatePayments int64 - DuplicateEntries int64 - SkippedPayments int64 - MigrationDuration time.Duration -} - -// migrationProgressReporter tracks rolling-window ETA state and logs -// periodic progress lines during the payment migration. -type migrationProgressReporter struct { - startTime time.Time - stats *MigrationStats - indexedPayments int64 - rateWindowDuration time.Duration - windowStart time.Time - windowPayments int64 - prevWindowRate float64 -} - -// report logs a progress line showing the current migration rate and ETA. -func (p *migrationProgressReporter) report() { - elapsed := time.Since(p.startTime) - if elapsed <= 0 || p.stats.TotalPayments == 0 { - return - } - - paymentRate := float64(p.stats.TotalPayments) / elapsed.Seconds() - attemptRate := float64(p.stats.TotalAttempts) / elapsed.Seconds() - - var pctStr string - if p.indexedPayments > 0 { - pct := float64(p.stats.TotalPayments) / - float64(p.indexedPayments) * 100 - pctStr = fmt.Sprintf(" (~%.1f%%)", pct) - } - - // Compute ETA using the rolling window rate so it responds to - // recent throughput changes. When the window expires we save - // the previous rate as a fallback for the reset tick. - windowElapsed := time.Since(p.windowStart) - if windowElapsed >= p.rateWindowDuration { - n := p.stats.TotalPayments - p.windowPayments - p.prevWindowRate = float64(n) / windowElapsed.Seconds() - p.windowPayments = p.stats.TotalPayments - p.windowStart = time.Now() - windowElapsed = 0 - } - - var etaStr string - if p.indexedPayments > 0 { - windowRate := p.prevWindowRate - if windowElapsed > 0 { - n := p.stats.TotalPayments - p.windowPayments - windowRate = float64(n) / windowElapsed.Seconds() - } - - if windowRate > 0 { - remaining := p.indexedPayments - p.stats.TotalPayments - secs := float64(remaining) / windowRate - eta := time.Duration(secs) * time.Second - etaStr = fmt.Sprintf( - " | ETA: ~%v", eta.Round(time.Second), - ) - } - } - - log.Infof("Progress: %d payments%s, %d attempts, %d hops | Rate: %.1f "+ - "pmt/s, %.1f att/s | Elapsed: %v%s", p.stats.TotalPayments, - pctStr, p.stats.TotalAttempts, p.stats.TotalHops, - paymentRate, attemptRate, elapsed.Round(time.Second), etaStr) -} - -// MigratePaymentsKVToSQL migrates payments from KV to SQL and validates -// migrated data in batches. Callers are responsible for executing this within -// a single SQL transaction if atomicity is required. -func MigratePaymentsKVToSQL(ctx context.Context, kvBackend kvdb.Backend, - sqlDB SQLMigrationQueries, cfg *SQLStoreConfig) error { - - if cfg == nil { - return fmt.Errorf("missing SQL store config for migration") - } - - if cfg.QueryCfg == nil { - return fmt.Errorf("missing SQL store config for validation") - } - - if cfg.QueryCfg.MaxBatchSize == 0 { - return fmt.Errorf("invalid max batch size for validation") - } - - stats := &MigrationStats{} - startTime := time.Now() - - log.Infof("Starting payment migration from KV to SQL...") - - var ( - validationBatch []migratedPaymentRef - - reportInterval = rate.Sometimes{Interval: 5 * time.Second} - ) - - indexedPayments, nextSwitchPaymentID, err := collectMigrationState( - kvBackend, - ) - if err != nil { - return fmt.Errorf("collect payment migration state: %w", err) - } - - attemptIDAllocator := newAttemptIDAllocator(nextSwitchPaymentID) - - log.Infof("Found ~%d index entries to migrate (includes duplicates)", - indexedPayments) - - // Set up a progress reporter with rolling-window ETA. - reporter := &migrationProgressReporter{ - startTime: startTime, - stats: stats, - indexedPayments: indexedPayments, - rateWindowDuration: defaultRateWindowDuration, - windowStart: startTime, - } - - // Open the KV backend in read-only mode. - err = kvBackend.View(func(kvTx kvdb.RTx) error { - // In case we start with an empty database, there are no - // payments to migrate. - paymentsBucket := kvTx.ReadBucket(paymentsRootBucket) - if paymentsBucket == nil { - log.Infof("No payments bucket found - database is " + - "empty") - - return nil - } - - // The index bucket maps sequence number -> payment hash. - indexes := kvTx.ReadBucket(paymentsIndexBucket) - if indexes == nil { - return fmt.Errorf("index bucket does not exist") - } - - // We iterate over all sequence numbers in the index bucket to - // make sure we have the correct order of payments. Otherwise, - // if we just loop over the payments bucket, we might get the - // payments not in the chronological order but rather the - // lexicographical order of the payment hashes. - return indexes.ForEach(func(seqKey, indexVal []byte) error { - reportInterval.Do(reporter.report) - - return migrateIndexEntry( - ctx, seqKey, indexVal, paymentsBucket, - kvBackend, sqlDB, cfg, stats, &validationBatch, - attemptIDAllocator, - ) - }) - }, func() {}) - - if err != nil { - return fmt.Errorf("migrate payments: %w", err) - } - - // Validate any remaining payments in the batch. - if len(validationBatch) > 0 { - if err := validateMigratedPaymentBatch( - ctx, kvBackend, sqlDB, cfg, validationBatch, - ); err != nil { - return err - } - } - - // Validate the total number of payments as an additional sanity check. - if err := validatePaymentCounts( - ctx, sqlDB, stats.TotalPayments, - ); err != nil { - return err - } - - if err := advanceSwitchPaymentIDSequence( - kvBackend, attemptIDAllocator.nextID, - ); err != nil { - return fmt.Errorf("advance switch payment ID sequence: %w", err) - } - - stats.MigrationDuration = time.Since(startTime) - - printMigrationSummary(stats) - - return nil -} - -// normalizeTimeForSQL converts a timestamp into the representation we persist -// and compare against in SQL: -// - drops any monotonic clock reading (SQL can't store it), -// - forces UTC for deterministic comparisons across environments. -// -// A zero time is returned unchanged. -func normalizeTimeForSQL(t time.Time) time.Time { - if t.IsZero() { - return t - } - - return time.Unix(0, t.UnixNano()).UTC() -} - -// collectMigrationState scans the payment index once to gather progress -// information and reads the switch sequencer horizon used for legacy attempt -// ID allocation. -func collectMigrationState(kvBackend kvdb.Backend) (int64, uint64, error) { - var ( - indexedPayments int64 - nextSwitchPaymentID uint64 - ) - err := kvBackend.View(func(kvTx kvdb.RTx) error { - // Read the switch sequencer horizon that legacy zero-ID - // attempts will allocate from if needed. - seqBucket := kvTx.ReadBucket(switchNextPaymentIDKey) - if seqBucket != nil { - nextSwitchPaymentID = seqBucket.Sequence() - } - - // If there are no payments, there is nothing to count or - // migrate. - paymentsBucket := kvTx.ReadBucket(paymentsRootBucket) - if paymentsBucket == nil { - log.Infof("No payments bucket found - database is " + - "empty") - - return nil - } - - // Count index entries for approximate progress reporting. The - // main migration still streams over this index in order. - indexes := kvTx.ReadBucket(paymentsIndexBucket) - if indexes == nil { - return fmt.Errorf("index bucket does not exist") - } - - return indexes.ForEach(func(_, _ []byte) error { - indexedPayments++ - return nil - }) - }, func() { - indexedPayments = 0 - nextSwitchPaymentID = 0 - }) - if err != nil { - return 0, 0, err - } - - return indexedPayments, nextSwitchPaymentID, nil -} - -// attemptIDAllocator tracks the next switch payment ID that is safe to hand -// out after migration. -type attemptIDAllocator struct { - // nextID is the in-memory counter used for the next synthetic attempt - // ID and the final switch sequencer horizon to persist. - nextID uint64 -} - -// newAttemptIDAllocator creates a new attempt ID allocator. -// -// The SQL schema requires payment_htlc_attempts.attempt_index to be globally -// unique because attempt-related rows use it as their stable identifier. Very -// old KV payments can contain attempt ID zero, which represented an unknown -// legacy value and cannot be preserved in SQL without colliding with every -// other such legacy attempt. -// -// Non-zero attempt IDs were allocated from the switch sequencer. The sequencer -// persists a horizon: it reads Sequence() as the next ID to hand out, then -// writes a higher value before returning that ID. This means the value stored -// in switchNextPaymentIDKey is already beyond all IDs it handed out. We -// therefore allocate replacement IDs for legacy zero attempts from that horizon -// and persist the final next-unused value once migration succeeds. These old -// attempts may receive high attempt_index values, which means that within a -// payment that mixes remapped and non-remapped attempts the remapped ones will -// sort after the originals when SQL queries order by attempt_index. This is -// acceptable because it only affects very old payments whose attempt IDs were -// already unknown, and intra-payment attempt ordering is not a load-bearing -// user-visible invariant; uniqueness and future non-collision with the switch -// sequencer are the actual invariants we need to preserve. -func newAttemptIDAllocator(nextSwitchPaymentID uint64) *attemptIDAllocator { - return &attemptIDAllocator{ - nextID: nextSwitchPaymentID, - } -} - -// allocateLegacyAttemptID returns a new unique attempt ID for a legacy payment. -// It uses the in-memory counter initialized from the switch payment ID -// sequencer horizon. -func (a *attemptIDAllocator) allocateLegacyAttemptID() (uint64, error) { - // The runtime switch sequencer never hands out ID zero: when its - // persisted sequence is zero, it starts by issuing ID one. Mirror - // that behavior for legacy attempts on otherwise idle nodes whose - // switch sequencer bucket has not allocated a batch yet. - if a.nextID == 0 { - a.nextID = 1 - } - - if a.nextID == ^uint64(0) { - return 0, fmt.Errorf("cannot allocate legacy attempt ID: "+ - "switch payment ID sequence is %d", a.nextID) - } - - attemptID := a.nextID - a.nextID++ - - return attemptID, nil -} - -// advanceSwitchPaymentIDSequence makes sure the switch sequencer cannot later -// hand out an ID that was already present in a migrated payment attempt. -func advanceSwitchPaymentIDSequence(kvBackend kvdb.Backend, - nextID uint64) error { - - return kvdb.Update(kvBackend, func(tx kvdb.RwTx) error { - seqBucket := tx.ReadWriteBucket(switchNextPaymentIDKey) - if seqBucket == nil { - if nextID <= 1 { - return nil - } - - var err error - seqBucket, err = tx.CreateTopLevelBucket( - switchNextPaymentIDKey, - ) - if err != nil { - return err - } - } - - currentSeq := seqBucket.Sequence() - if currentSeq == nextID { - // No synthetic IDs were allocated, so the sequencer is - // already at the migration cursor. - return nil - } - if currentSeq > nextID { - // Migration runs exclusively, so the sequencer should - // not move beyond the cursor computed by migration. - return fmt.Errorf("switch payment ID sequence above "+ - "migration horizon: current=%d, expected=%d", - currentSeq, nextID) - } - - // Synthetic IDs were allocated, so advance the sequencer to - // the final next-unused ID. - return seqBucket.SetSequence(nextID) - }, func() {}) -} - -// migrateIndexEntry processes a single entry from the payments index bucket, -// migrating the corresponding payment to SQL and appending it to the -// validation batch. -func migrateIndexEntry(ctx context.Context, seqKey, indexVal []byte, - paymentsBucket kvdb.RBucket, kvBackend kvdb.Backend, - sqlDB SQLMigrationQueries, cfg *SQLStoreConfig, stats *MigrationStats, - validationBatch *[]migratedPaymentRef, - attemptIDAllocator *attemptIDAllocator) error { - - r := bytes.NewReader(indexVal) - paymentHash, err := deserializePaymentIndex(r) - if err != nil { - return err - } - - paymentBucket := paymentsBucket.NestedReadBucket(paymentHash[:]) - if paymentBucket == nil { - // We skip the entry in case this sequence number does not - // have a corresponding payment bucket. But aborting would - // not help either because it is just a db inconsistency. - log.Warnf("Missing bucket for payment %x", paymentHash[:8]) - stats.SkippedPayments++ - - return nil - } - - // Every payment bucket should have a sequence number which is - // also important to check for duplicates. - seqBytes := paymentBucket.Get(paymentSequenceKey) - if seqBytes == nil { - return ErrNoSequenceNumber - } - - // Skip duplicates. They are migrated into the payment_duplicates - // table when the primary payment is processed. - if !bytes.Equal(seqBytes, seqKey) { - return nil - } - - // Fetch the payment from the kv store. - payment, err := fetchPayment(paymentBucket) - if err != nil { - return fmt.Errorf("fetch payment %x: %w", paymentHash[:8], err) - } - - // Migrate the payment to the SQL database. - paymentID, err := migratePayment( - ctx, payment, paymentHash, sqlDB, stats, attemptIDAllocator, - ) - if err != nil { - return fmt.Errorf("migrate payment %x: %w", paymentHash[:8], - err) - } - - // Migrate any duplicate payments for this hash. - dupBucket := paymentBucket.NestedReadBucket(duplicatePaymentsBucket) - if dupBucket != nil { - err = migrateDuplicatePayments( - ctx, dupBucket, paymentHash, paymentID, sqlDB, stats, - ) - if err != nil { - return fmt.Errorf("migrate duplicates %x: %w", - paymentHash[:8], err) - } - } - - // Add the payment to the validation batch. - *validationBatch = append(*validationBatch, migratedPaymentRef{ - Hash: paymentHash, - PaymentID: paymentID, - }) - if uint32(len(*validationBatch)) >= cfg.QueryCfg.MaxBatchSize { - err := validateMigratedPaymentBatch( - ctx, kvBackend, sqlDB, cfg, *validationBatch, - ) - if err != nil { - return err - } - - *validationBatch = (*validationBatch)[:0] - } - - return nil -} - -// migratePayment migrates a single payment from KV to SQL. -func migratePayment(ctx context.Context, payment *MPPayment, hash lntypes.Hash, - sqlDB SQLMigrationQueries, stats *MigrationStats, - attemptIDAllocator *attemptIDAllocator) (int64, error) { - - if payment.Status == StatusInFlight { - terminalizedLegacyAttempts, err := - terminalizeUnresolvedLegacyZeroAttempts(payment) - if err != nil { - return 0, err - } - if terminalizedLegacyAttempts > 0 { - log.Warnf("Terminalized %d unresolved legacy HTLC "+ - "attempt(s) with unknown attempt ID zero for "+ - "payment %x; the parent payment was failed if "+ - "no other settled or in-flight HTLC kept "+ - "it active", terminalizedLegacyAttempts, - hash[:8]) - } - } - - // Update migration stats based on payment status. - switch payment.Status { - case StatusSucceeded: - stats.SuccessfulPayments++ - - case StatusFailed: - stats.FailedPayments++ - - case StatusInFlight: - stats.InFlightPayments++ - - case StatusInitiated: - stats.InitiatedPayments++ - } - - // Prepare fail reason for SQL insert. - var failReason sql.NullInt32 - if payment.FailureReason != nil { - failReason = sql.NullInt32{ - Int32: int32(*payment.FailureReason), - Valid: true, - } - } - - // Insert payment using migration query. - paymentID, err := sqlDB.InsertPaymentMig( - ctx, sqlc.InsertPaymentMigParams{ - AmountMsat: int64(payment.Info.Value), - CreatedAt: normalizeTimeForSQL( - payment.Info.CreationTime, - ), - PaymentIdentifier: hash[:], - FailReason: failReason, - }) - if err != nil { - return 0, fmt.Errorf("insert payment: %w", err) - } - - // Insert payment intent. - // - // Only insert a row if we have an actual intent payload. For legacy - // hash-only/keysend-style payments, the intent may be absent. - if len(payment.Info.PaymentRequest) > 0 { - _, err = sqlDB.InsertPaymentIntent( - ctx, sqlc.InsertPaymentIntentParams{ - PaymentID: paymentID, - IntentType: int16(PaymentIntentTypeBolt11), - IntentPayload: payment.Info.PaymentRequest, - }, - ) - if err != nil { - return 0, fmt.Errorf("insert intent: %w", err) - } - } - - // Insert first hop custom records (payment level). - for key, value := range payment.Info.FirstHopCustomRecords { - err = sqlDB.InsertPaymentFirstHopCustomRecord(ctx, - sqlc.InsertPaymentFirstHopCustomRecordParams{ - PaymentID: paymentID, - Key: int64(key), - Value: value, - }, - ) - if err != nil { - return 0, fmt.Errorf("insert custom record: %w", err) - } - } - - // Migrate HTLC attempts. - for _, htlc := range payment.HTLCs { - err = migrateHTLCAttempt( - ctx, paymentID, hash, &htlc, sqlDB, stats, - attemptIDAllocator, - ) - if err != nil { - return 0, fmt.Errorf("migrate attempt %d: %w", - htlc.AttemptID, err) - } - } - - stats.TotalPayments++ - - return paymentID, nil -} - -// terminalizeUnresolvedLegacyZeroAttempts marks unresolved legacy zero-ID HTLC -// attempts failed and fails the parent payment if no other resolved or -// recoverable in-flight HTLC keeps it active. -// -// Attempt ID zero was written by an old KV migration as an unknown legacy -// value. If such an attempt has no settle/fail resolution, it cannot be safely -// resumed after SQL migration because the live switch state would not know the -// synthetic attempt ID assigned below. -// -// Callers only need this for in-flight payments: any unresolved HTLC makes the -// parent payment in-flight, while terminal historical payments can use the -// regular zero-ID remap path. -func terminalizeUnresolvedLegacyZeroAttempts(payment *MPPayment) (int, error) { - var ( - terminalizedLegacyAttempts int - hasSettled bool - hasNonZeroInFlight bool - ) - - for i := range payment.HTLCs { - htlc := &payment.HTLCs[i] - switch { - case htlc.Settle != nil: - hasSettled = true - - case htlc.Failure != nil: - - case htlc.AttemptID == 0: - htlc.Failure = &HTLCFailInfo{ - Reason: HTLCFailUnknown, - } - terminalizedLegacyAttempts++ - - default: - hasNonZeroInFlight = true - } - } - - if terminalizedLegacyAttempts == 0 { - return 0, nil - } - - if !hasSettled && !hasNonZeroInFlight && payment.FailureReason == nil { - reason := FailureReasonError - payment.FailureReason = &reason - } - - if err := payment.setState(); err != nil { - return 0, err - } - - return terminalizedLegacyAttempts, nil -} - -// migrateHTLCAttempt migrates a single HTLC attempt. -func migrateHTLCAttempt(ctx context.Context, paymentID int64, - parentPaymentHash lntypes.Hash, htlc *HTLCAttempt, sqlDB SQLQueries, - stats *MigrationStats, attemptIDAllocator *attemptIDAllocator) error { - - // Determine the payment hash for this HTLC attempt. - // - // For AMP payments, each HTLC has its own unique hash. For non-AMP - // payments (MPP, Legacy), all HTLCs use the same hash as the parent - // payment. Older payment attempts may not have the hash stored - // explicitly, in which case we fall back to the parent payment hash - // which is ok since non-AMP payments have a single hash for all HTLCs. - var paymentHash []byte - switch { - case htlc.Hash != nil: - paymentHash = (*htlc.Hash)[:] - - default: - // For older payments where Hash is nil, use the parent payment - // hash. This is consistent with how the router handles these - // legacy payments. - paymentHash = parentPaymentHash[:] - } - - firstHopAmountMsat := int64(htlc.Route.FirstHopAmount.Val.Int()) - - sessionKey := htlc.SessionKey() - if sessionKey == nil { - return fmt.Errorf("HTLC attempt %d for payment %x is "+ - "missing session key", htlc.AttemptID, - parentPaymentHash[:8]) - } - - sessionKeyBytes := sessionKey.Serialize() - - attemptID := htlc.AttemptID - if attemptID == 0 { - var err error - attemptID, err = attemptIDAllocator.allocateLegacyAttemptID() - if err != nil { - return fmt.Errorf("allocate legacy attempt ID: %w", err) - } - - log.Warnf("Allocated HTLC attempt index %d from switch "+ - "sequencer for legacy payment %x with unknown "+ - "attempt ID", attemptID, - parentPaymentHash[:8]) - } - - if attemptID > math.MaxInt64 { - return fmt.Errorf("unable to convert HTLC attempt ID to "+ - "SQL attempt index: attempt_id=%d payment=%x max=%d", - attemptID, parentPaymentHash[:8], uint64(math.MaxInt64)) - } - - attemptIndex := int64(attemptID) - - // Insert HTLC attempt. - _, err := sqlDB.InsertHtlcAttempt(ctx, sqlc.InsertHtlcAttemptParams{ - PaymentID: paymentID, - AttemptIndex: attemptIndex, - SessionKey: sessionKeyBytes, - AttemptTime: normalizeTimeForSQL(htlc.AttemptTime), - PaymentHash: paymentHash, - FirstHopAmountMsat: firstHopAmountMsat, - RouteTotalTimeLock: int32(htlc.Route.TotalTimeLock), - RouteTotalAmount: int64(htlc.Route.TotalAmount), - RouteSourceKey: htlc.Route.SourcePubKey[:], - }) - if err != nil { - // SQL unique constraint errors do not include the conflicting - // value. Include the attempted index so failures point directly - // at the problematic legacy attempt. - return fmt.Errorf("unable to insert HTLC attempt: "+ - "index=%d payment=%x original_attempt_id=%d: %w", - attemptIndex, parentPaymentHash[:8], - htlc.AttemptID, err) - } - - // Insert the route-level first hop custom records. - for key, value := range htlc.Route.FirstHopWireCustomRecords { - err = sqlDB.InsertPaymentAttemptFirstHopCustomRecord( - ctx, - sqlc.InsertPaymentAttemptFirstHopCustomRecordParams{ - HtlcAttemptIndex: attemptIndex, - Key: int64(key), - Value: value, - }, - ) - if err != nil { - return fmt.Errorf("insert attempt first hop custom "+ - "record: %w", err) - } - } - - // Insert route hops. - for hopIndex := range htlc.Route.Hops { - hop := htlc.Route.Hops[hopIndex] - - // Use the parent hash for diagnostics. For AMP payments this - // is the set ID, which identifies the payment containing the - // shard. - err = migrateRouteHop( - ctx, parentPaymentHash, attemptIndex, hopIndex, hop, - sqlDB, stats, - ) - if err != nil { - return fmt.Errorf("migrate hop %d: %w", hopIndex, err) - } - } - - // Handle attempt resolution (settle or fail). - switch { - case htlc.Settle != nil: - // Settled - err = sqlDB.SettleAttempt(ctx, sqlc.SettleAttemptParams{ - AttemptIndex: attemptIndex, - ResolutionTime: normalizeTimeForSQL( - htlc.Settle.SettleTime, - ), - ResolutionType: int32(HTLCAttemptResolutionSettled), - SettlePreimage: htlc.Settle.Preimage[:], - }) - if err != nil { - return fmt.Errorf("settle attempt: %w", err) - } - - stats.SettledAttempts++ - - case htlc.Failure != nil: - var failureMsg bytes.Buffer - if htlc.Failure.Message != nil { - err := lnwire.EncodeFailureMessage( - &failureMsg, htlc.Failure.Message, 0, - ) - if err != nil { - return fmt.Errorf("failed to encode "+ - "failure message: %w", err) - } - } - - err = sqlDB.FailAttempt(ctx, sqlc.FailAttemptParams{ - AttemptIndex: attemptIndex, - ResolutionTime: normalizeTimeForSQL( - htlc.Failure.FailTime, - ), - ResolutionType: int32(HTLCAttemptResolutionFailed), - FailureSourceIndex: sql.NullInt32{ - Int32: int32(htlc.Failure.FailureSourceIndex), - Valid: true, - }, - HtlcFailReason: sql.NullInt32{ - Int32: int32(htlc.Failure.Reason), - Valid: true, - }, - FailureMsg: failureMsg.Bytes(), - }) - if err != nil { - return fmt.Errorf("fail attempt: %w", err) - } - - stats.FailedAttempts++ - - default: - // If the attempt is not settled or failed, it is in flight. - stats.InFlightAttempts++ - } - - stats.TotalAttempts++ - - return nil -} - -// migrateRouteHop migrates a single route hop. -func migrateRouteHop(ctx context.Context, - parentPaymentHash lntypes.Hash, attemptIndex int64, hopIndex int, - hop *Hop, sqlDB SQLQueries, stats *MigrationStats) error { - - // Convert channel ID to string representation of uint64. - // The SCID is stored as a decimal string to match the converter - // expectations (sql_converters.go:173). - scidStr := strconv.FormatUint(hop.ChannelID, 10) - - // Insert route hop. - hopID, err := sqlDB.InsertRouteHop(ctx, sqlc.InsertRouteHopParams{ - HtlcAttemptIndex: attemptIndex, - HopIndex: int32(hopIndex), - PubKey: hop.PubKeyBytes[:], - Scid: scidStr, - OutgoingTimeLock: int32(hop.OutgoingTimeLock), - AmtToForward: int64(hop.AmtToForward), - MetaData: hop.Metadata, - }) - if err != nil { - return fmt.Errorf("insert hop: %w", err) - } - - // Non-empty encrypted recipient data identifies a blinded hop. - // The RPC boundary has required encrypted data with a blinding point - // since these fields were introduced. Internally built blinded routes - // also always contain both. Unlike an orphaned total, a point without - // encrypted data is not a supported legacy encoding. Report it as - // malformed instead of silently discarding it. Keep this rejection in - // sync with normalizePaymentForCompare, which only normalizes hops - // without a blinding point. - hasEncryptedData := len(hop.EncryptedData) > 0 - if !hasEncryptedData && hop.BlindingPoint != nil { - return fmt.Errorf("invalid blinded hop: payment_hash=%x, "+ - "attempt_index=%d, hop=%d: blinding point requires "+ - "encrypted recipient data", parentPaymentHash[:8], - attemptIndex, hopIndex) - } - - // SendToRouteV2 historically allowed a blinded total amount without - // blinded hop data. Omit such an orphaned total rather than creating a - // blinded-hop row. - if !hasEncryptedData && hop.TotalAmtMsat != 0 { - log.Warnf("Ignoring orphaned blinded total amount: "+ - "payment_hash=%x, attempt_index=%d, hop=%d, "+ - "total_amt_msat=%d", parentPaymentHash[:8], - attemptIndex, hopIndex, hop.TotalAmtMsat) - } - - // The blinding point and total amount are only associated fields. Use - // the length so nil and empty encrypted data are handled consistently. - if hasEncryptedData { - var blindingPoint []byte - if hop.BlindingPoint != nil { - blindingPoint = hop.BlindingPoint.SerializeCompressed() - } - - var totalAmt sql.NullInt64 - if hop.TotalAmtMsat != 0 { - totalAmt = sql.NullInt64{ - Int64: int64(hop.TotalAmtMsat), - Valid: true, - } - } - - err := sqlDB.InsertRouteHopBlinded( - ctx, sqlc.InsertRouteHopBlindedParams{ - HopID: hopID, - EncryptedData: hop.EncryptedData, - BlindingPoint: blindingPoint, - BlindedPathTotalAmt: totalAmt, - }, - ) - if err != nil { - return fmt.Errorf("insert blinded hop: %w", err) - } - } - - // Check for MPP record. - if hop.MPP != nil { - paymentAddr := hop.MPP.PaymentAddr() - err = sqlDB.InsertRouteHopMpp(ctx, sqlc.InsertRouteHopMppParams{ - HopID: hopID, - PaymentAddr: paymentAddr[:], - TotalMsat: int64(hop.MPP.TotalMsat()), - }) - if err != nil { - return fmt.Errorf("insert MPP: %w", err) - } - } - - // Check for AMP record. - if hop.AMP != nil { - rootShare := hop.AMP.RootShare() - setID := hop.AMP.SetID() - err = sqlDB.InsertRouteHopAmp(ctx, sqlc.InsertRouteHopAmpParams{ - HopID: hopID, - RootShare: rootShare[:], - SetID: setID[:], - ChildIndex: int32(hop.AMP.ChildIndex()), - }) - if err != nil { - return fmt.Errorf("insert AMP: %w", err) - } - } - - // Check for custom records. - if hop.CustomRecords != nil { - for tlvType, value := range hop.CustomRecords { - err = sqlDB.InsertPaymentHopCustomRecord( - ctx, - sqlc.InsertPaymentHopCustomRecordParams{ - HopID: hopID, - Key: int64(tlvType), - Value: value, - }, - ) - if err != nil { - return fmt.Errorf("insert hop custom "+ - "record: %w", err) - } - } - } - - stats.TotalHops++ - - return nil -} - -// migrateDuplicatePayments migrates duplicate payments into the dedicated -// payment_duplicates table. -func migrateDuplicatePayments(ctx context.Context, dupBucket kvdb.RBucket, - hash [32]byte, primaryPaymentID int64, sqlDB SQLMigrationQueries, - stats *MigrationStats) error { - - duplicateCount := 0 - - err := dupBucket.ForEach(func(seqBytes, _ []byte) error { - // The duplicates bucket should only contain nested buckets - // keyed by 8-byte sequence numbers. Skip any unexpected keys - // (defensive check for corrupted or malformed data). - if len(seqBytes) != 8 { - log.Warnf("Skipping unexpected key in duplicates "+ - "bucket for payment %x: key length %d, "+ - "expected 8", hash[:8], len(seqBytes)) - - return nil - } - - seqNum := byteOrder.Uint64(seqBytes) - subBucket := dupBucket.NestedReadBucket(seqBytes) - if subBucket == nil { - return nil - } - - duplicateCount++ - log.Infof("Migrating duplicate payment seq=%d for "+ - "payment %x", seqNum, hash[:8]) - - err := migrateSingleDuplicatePayment( - ctx, subBucket, hash, primaryPaymentID, seqNum, - sqlDB, - ) - if err != nil { - return fmt.Errorf("migrate duplicate payment "+ - "seq=%d: %w", seqNum, err) - } - - return nil - }) - - if duplicateCount > 0 { - stats.DuplicatePayments++ - stats.DuplicateEntries += int64(duplicateCount) - - log.Infof("Payment %x had %d duplicate(s) migrated", hash[:8], - duplicateCount) - } - - return err -} - -// migrateSingleDuplicatePayment inserts a duplicate payment record for the -// given payment hash into payment_duplicates. -func migrateSingleDuplicatePayment(ctx context.Context, dupBucket kvdb.RBucket, - hash [32]byte, primaryPaymentID int64, duplicateSeq uint64, - sqlDB SQLMigrationQueries) error { - - creationData := dupBucket.Get(duplicatePaymentCreationInfoKey) - if creationData == nil { - return fmt.Errorf("duplicate payment seq=%d missing "+ - "creation info (payment=%x)", duplicateSeq, hash[:8]) - } - - creationInfo, err := deserializeDuplicatePaymentCreationInfo( - bytes.NewReader(creationData), - ) - if err != nil { - return fmt.Errorf("deserialize duplicate creation "+ - "info: %w", err) - } - - settleData := dupBucket.Get(duplicatePaymentSettleInfoKey) - failReasonData := dupBucket.Get(duplicatePaymentFailInfoKey) - attemptData := dupBucket.Get(duplicatePaymentAttemptInfoKey) - - if settleData != nil && len(failReasonData) > 0 { - return fmt.Errorf("duplicate payment seq=%d has both "+ - "settle and fail info (payment=%x)", duplicateSeq, - hash[:8]) - } - - var ( - failReason sql.NullInt32 - settlePreimage []byte - settleTime sql.NullTime - ) - - switch { - case settleData != nil: - settlePreimage, settleTime, err = parseDuplicateSettleData( - settleData, - ) - if err != nil { - return err - } - - case len(failReasonData) > 0: - failReason = sql.NullInt32{ - Int32: int32(failReasonData[0]), - Valid: true, - } - - default: - // If the duplicate payment has no settle or fail info, - // we mark it as failed during the migration. Duplicate - // payments were a bug in older versions of LND, so we can be - // sure if a duplicate payment has no failure reason or - // settlement data, the corresponding HTLC for this payment - // has been failed (resolved). - if attemptData == nil { - log.Warnf("Duplicate payment seq=%d has no "+ - "attempt info and no resolution (payment=%x); "+ - "marking failed", duplicateSeq, hash[:8]) - } else { - log.Warnf("Duplicate payment seq=%d has attempt "+ - "info but no resolution (payment=%x); "+ - "marking failed", duplicateSeq, hash[:8]) - } - - failReason = sql.NullInt32{ - Int32: int32(FailureReasonError), - Valid: true, - } - } - - _, err = sqlDB.InsertPaymentDuplicateMig( - ctx, sqlc.InsertPaymentDuplicateMigParams{ - PaymentID: primaryPaymentID, - AmountMsat: int64(creationInfo.Value), - CreatedAt: normalizeTimeForSQL( - creationInfo.CreationTime, - ), - FailReason: failReason, - SettlePreimage: settlePreimage, - SettleTime: settleTime, - }, - ) - if err != nil { - return fmt.Errorf("insert duplicate payment: %w", err) - } - - return nil -} - -// parseDuplicateSettleData extracts settle data from either legacy or modern -// duplicate formats. -func parseDuplicateSettleData(settleData []byte) ([]byte, sql.NullTime, error) { - if len(settleData) == lntypes.PreimageSize { - return append([]byte(nil), settleData...), sql.NullTime{}, nil - } - - settleInfo, err := deserializeHTLCSettleInfo( - bytes.NewReader(settleData), - ) - if err != nil { - return nil, sql.NullTime{}, - fmt.Errorf("deserialize duplicate settle: %w", err) - } - - settleTime := normalizeTimeForSQL(settleInfo.SettleTime) - - return settleInfo.Preimage[:], sql.NullTime{ - Time: settleTime, - Valid: !settleTime.IsZero(), - }, nil -} - -// printMigrationSummary prints a summary of the migration. -func printMigrationSummary(stats *MigrationStats) { - if stats.TotalPayments == 0 { - log.Infof("No payments migrated - database is empty") - - return - } - - log.Infof("========================================") - log.Infof(" Payment Migration Summary") - log.Infof("========================================") - log.Infof("Total Payments: %d", stats.TotalPayments) - log.Infof(" Successful: %d", stats.SuccessfulPayments) - log.Infof(" Failed: %d", stats.FailedPayments) - log.Infof(" In-Flight: %d", stats.InFlightPayments) - log.Infof(" Initiated: %d", stats.InitiatedPayments) - log.Infof("") - log.Infof("Total HTLC Attempts: %d", stats.TotalAttempts) - log.Infof(" Settled: %d", stats.SettledAttempts) - log.Infof(" Failed: %d", stats.FailedAttempts) - log.Infof(" In-Flight: %d", stats.InFlightAttempts) - log.Infof("") - log.Infof("Total Route Hops: %d", stats.TotalHops) - - if stats.SkippedPayments > 0 { - log.Infof("") - log.Warnf("SKIPPED PAYMENTS:") - log.Warnf(" Indexed payments with missing buckets: %d", - stats.SkippedPayments) - log.Warnf(" These indicate minor DB inconsistencies.") - } - - if stats.DuplicatePayments > 0 { - log.Infof("") - log.Warnf("DUPLICATE PAYMENTS DETECTED:") - log.Warnf(" Unique payment hashes with duplicates: %d", - stats.DuplicatePayments) - log.Warnf(" Total duplicate entries migrated: %d", - stats.DuplicateEntries) - log.Warnf(" These were caused by an old LND bug.") - } - - log.Infof("") - log.Infof("Migration Duration: %v", stats.MigrationDuration) - log.Infof("========================================") -} diff --git a/payments/db/migration1/sql_migration_test.go b/payments/db/migration1/sql_migration_test.go deleted file mode 100644 index 295f2302a..000000000 --- a/payments/db/migration1/sql_migration_test.go +++ /dev/null @@ -1,3152 +0,0 @@ -//go:build test_db_postgres || test_db_sqlite - -package migration1 - -import ( - "bytes" - "context" - "crypto/sha256" - "fmt" - "io" - "sort" - "testing" - "time" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/kvdb" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire" - "github.com/lightningnetwork/lnd/payments/db/migration1/record" - "github.com/lightningnetwork/lnd/payments/db/migration1/sqlc" - "github.com/lightningnetwork/lnd/sqldb" - "github.com/stretchr/testify/require" -) - -// TestMigrationKVToSQL tests the basic payment migration from KV to SQL. -func TestMigrationKVToSQL(t *testing.T) { - t.Parallel() - - ctx := context.Background() - - // Setup KV database and populate with test data. - kvDB := setupTestKVDB(t) - populateTestPayments(t, kvDB, 5) - - sqlStore := setupTestSQLDB(t) - - // Run migration in a single transaction. - err := runPaymentsMigration(ctx, kvDB, sqlStore) - require.NoError(t, err) -} - -// TestMigrationSequenceOrder ensures the migration follows sequence order -// rather than lexicographic hash order. -func TestMigrationSequenceOrder(t *testing.T) { - t.Parallel() - - ctx := context.Background() - - kvDB := setupTestKVDB(t) - err := kvdb.Update(kvDB, func(tx kvdb.RwTx) error { - paymentsBucket, err := tx.CreateTopLevelBucket( - paymentsRootBucket, - ) - if err != nil { - return err - } - - indexBucket, err := tx.CreateTopLevelBucket(paymentsIndexBucket) - if err != nil { - return err - } - - var globalAttemptID uint64 - hash0 := [32]byte{} - hash1 := [32]byte{} - hash2 := [32]byte{} - hash0[0] = 3 - hash1[0] = 2 - hash2[0] = 1 - - if err := createTestPaymentInKV( - t, paymentsBucket, indexBucket, 0, hash0, - &globalAttemptID, - ); err != nil { - return err - } - - // We make sure that the duplicate payment is skipped because - // it will be migrated separately into payment_duplicates. - if err := createTestDuplicatePaymentWithIndex( - t, paymentsBucket, indexBucket, hash0, 1, false, - &globalAttemptID, - ); err != nil { - return err - } - if err := createTestPaymentInKV( - t, paymentsBucket, indexBucket, 2, hash1, - &globalAttemptID, - ); err != nil { - return err - } - if err := createTestPaymentInKV( - t, paymentsBucket, indexBucket, 3, hash2, - &globalAttemptID, - ); err != nil { - return err - } - - return nil - }, func() {}) - require.NoError(t, err) - - sqlStore := setupTestSQLDB(t) - - err = runPaymentsMigration(ctx, kvDB, sqlStore) - require.NoError(t, err) - - resp, err := sqlStore.QueryPayments(ctx, Query{ - MaxPayments: 10, - IncludeIncomplete: true, - }) - require.NoError(t, err) - require.Len(t, resp.Payments, 3) - - var ( - exp0 lntypes.Hash - exp1 lntypes.Hash - exp2 lntypes.Hash - ) - exp0[0] = 3 - exp1[0] = 2 - exp2[0] = 1 - - require.Equal(t, exp0, resp.Payments[0].Info.PaymentIdentifier) - require.Equal(t, exp1, resp.Payments[1].Info.PaymentIdentifier) - require.Equal(t, exp2, resp.Payments[2].Info.PaymentIdentifier) -} - -// TestMigrationDataIntegrity verifies that migrated payment data exactly -// matches the original KV data when fetched through the SQLStore -// (SQLStore.FetchPayment). This covers the SQLStore query path separately -// from the migration's own batch validation. -func TestMigrationDataIntegrity(t *testing.T) { - t.Parallel() - - ctx := context.Background() - - // Setup KV database with test data. - kvDB := setupTestKVDB(t) - numPayments := populateTestPayments(t, kvDB, 5) - - // Fetch all payments from KV before migration. - kvPayments := fetchAllPaymentsFromKV(t, kvDB) - require.Len(t, kvPayments, numPayments) - - // Setup SQL database and run migration. - sqlStore := setupTestSQLDB(t) - - err := runPaymentsMigration(ctx, kvDB, sqlStore) - require.NoError(t, err) - - // Compare each KV payment with its SQL counterpart using deep equality. - // This ensures that ALL fields match, not just a few selected ones. - for _, kvPayment := range kvPayments { - comparePaymentData(t, ctx, sqlStore, kvPayment) - } -} - -// TestMigrationLegacyPayloadNormalized verifies that a payment whose HTLC -// hops carry LegacyPayload=true in the KV store is migrated and compared -// correctly. LegacyPayload was a hint used by the KV store to choose between -// the legacy and TLV hop-payload serialization formats. The SQL store does not -// serialize hop data at all — each field is stored natively in its own -// column — so the flag is never persisted there. normalizePaymentForCompare -// clears it on both sides before the equality check, so the comparison must -// succeed even when the KV source has LegacyPayload=true. -func TestMigrationLegacyPayloadNormalized(t *testing.T) { - t.Parallel() - - ctx := context.Background() - - kvDB := setupTestKVDB(t) - - hash := createTestPaymentHash(t, 0) - - err := kvdb.Update(kvDB, func(tx kvdb.RwTx) error { - paymentsBucket, err := tx.CreateTopLevelBucket( - paymentsRootBucket, - ) - if err != nil { - return err - } - - indexBucket, err := tx.CreateTopLevelBucket( - paymentsIndexBucket, - ) - if err != nil { - return err - } - - paymentBucket, err := paymentsBucket.CreateBucketIfNotExists( - hash[:], - ) - if err != nil { - return err - } - - var paymentID lntypes.Hash - copy(paymentID[:], hash[:]) - - creationInfo := &PaymentCreationInfo{ - PaymentIdentifier: paymentID, - Value: lnwire.MilliSatoshi(1000000), - CreationTime: time.Now().Add(-24 * time.Hour), - PaymentRequest: []byte("lnbc1test"), - } - - var b bytes.Buffer - if err = serializePaymentCreationInfo( - &b, creationInfo, - ); err != nil { - return err - } - if err = paymentBucket.Put( - paymentCreationInfoKey, b.Bytes(), - ); err != nil { - return err - } - - seqBytes := make([]byte, 8) - byteOrder.PutUint64(seqBytes, 0) - if err = paymentBucket.Put( - paymentSequenceKey, seqBytes, - ); err != nil { - return err - } - - htlcBucket, err := paymentBucket.CreateBucketIfNotExists( - paymentHtlcsBucket, - ) - if err != nil { - return err - } - - hop1Key, err := btcec.NewPrivateKey() - if err != nil { - return err - } - - hop2Key, err := btcec.NewPrivateKey() - if err != nil { - return err - } - - sessionKey, err := btcec.NewPrivateKey() - if err != nil { - return err - } - - var sourcePubKey Vertex - copy(sourcePubKey[:], sessionKey.PubKey().SerializeCompressed()) - - var sessionKeyBytes [32]byte - copy(sessionKeyBytes[:], sessionKey.Serialize()) - - const attemptID = uint64(1) - attemptInfo := &HTLCAttemptInfo{ - AttemptID: attemptID, - sessionKey: sessionKeyBytes, - Route: Route{ - TotalTimeLock: 500000, - TotalAmount: lnwire.MilliSatoshi(1000000), - SourcePubKey: sourcePubKey, - Hops: []*Hop{ - { - PubKeyBytes: NewVertex( - hop1Key.PubKey(), - ), - ChannelID: 123456, - OutgoingTimeLock: 499500, - AmtToForward: 900000, - LegacyPayload: true, - }, - { - PubKeyBytes: NewVertex( - hop2Key.PubKey(), - ), - ChannelID: 789012, - OutgoingTimeLock: 499000, - AmtToForward: 800000, - LegacyPayload: true, - }, - }, - }, - AttemptTime: time.Now().Add(-2 * time.Hour), - Hash: &paymentID, - } - - if err = writeHTLCAttempt( - htlcBucket, attemptID, attemptInfo, - ); err != nil { - return err - } - - settleInfo := &HTLCSettleInfo{ - Preimage: lntypes.Preimage(paymentID), - SettleTime: time.Now().Add(-1 * time.Hour), - } - if err = writeHTLCSettle( - htlcBucket, attemptID, settleInfo, - ); err != nil { - return err - } - - return createIndexEntry(indexBucket, seqBytes, hash) - }, func() {}) - require.NoError(t, err) - - // Fetch the payment from KV and verify LegacyPayload is true so we - // know the test data is correct before migration. - var kvPayment *MPPayment - err = kvdb.View(kvDB, func(tx kvdb.RTx) error { - bucket := tx.ReadBucket(paymentsRootBucket). - NestedReadBucket(hash[:]) - var err error - kvPayment, err = fetchPayment(bucket) - return err - }, func() {}) - require.NoError(t, err) - require.True(t, kvPayment.HTLCs[0].Route.Hops[0].LegacyPayload) - require.True(t, kvPayment.HTLCs[0].Route.Hops[1].LegacyPayload) - - sqlStore := setupTestSQLDB(t) - require.NoError(t, runPaymentsMigration(ctx, kvDB, sqlStore)) - - // comparePaymentData normalizes both sides (clearing LegacyPayload) - // before the equality check, so this must pass. - comparePaymentData(t, ctx, sqlStore, kvPayment) -} - -// TestMigrationWithDuplicates tests migration of duplicate payments into -// the payment_duplicates table. -func TestMigrationWithDuplicates(t *testing.T) { - t.Parallel() - - ctx := context.Background() - - // Setup KV database. - kvDB := setupTestKVDB(t) - - // Create a payment with duplicates. - hash := createTestPaymentHash(t, 0) - err := kvdb.Update(kvDB, func(tx kvdb.RwTx) error { - // Create root buckets. - paymentsBucket, err := tx.CreateTopLevelBucket( - paymentsRootBucket, - ) - if err != nil { - return err - } - - indexBucket, err := tx.CreateTopLevelBucket(paymentsIndexBucket) - if err != nil { - return err - } - - // Create primary payment with sequence 0 and globally unique - // attempt ID. - var globalAttemptID uint64 - err = createTestPaymentInKV( - t, paymentsBucket, indexBucket, 0, hash, - &globalAttemptID, - ) - if err != nil { - return err - } - - // Add 2 duplicate payments for the same hash. - paymentBucket := paymentsBucket.NestedReadWriteBucket(hash[:]) - require.NotNil(t, paymentBucket) - - dupBucket, err := paymentBucket.CreateBucketIfNotExists( - duplicatePaymentsBucket, - ) - if err != nil { - return err - } - - // Create duplicate with sequence 1 using global attempt ID. - err = createTestDuplicatePayment( - t, dupBucket, hash, 1, true, &globalAttemptID, - ) - if err != nil { - return err - } - - // Create duplicate with sequence 2 using global attempt ID. - err = createTestDuplicatePayment( - t, dupBucket, hash, 2, false, &globalAttemptID, - ) - if err != nil { - return err - } - - return nil - }, func() {}) - require.NoError(t, err) - - sqlStore := setupTestSQLDB(t) - - // Run migration. - err = runPaymentsMigration(ctx, kvDB, sqlStore) - require.NoError(t, err) - - // Verify in SQL database. - var count int64 - err = sqlStore.db.ExecTx( - ctx, sqldb.ReadTxOpt(), func(q SQLQueries) error { - var err error - count, err = q.CountPayments(ctx) - return err - }, sqldb.NoOpReset, - ) - require.NoError(t, err) - require.Equal( - t, int64(1), count, "SQL DB should have 1 payment", - ) - - var ( - dbPayment sqlc.FetchPaymentRow - duplicates []sqlc.PaymentDuplicate - ) - err = sqlStore.db.ExecTx( - ctx, sqldb.ReadTxOpt(), func(q SQLQueries) error { - var err error - dbPayment, err = q.FetchPayment(ctx, hash[:]) - if err != nil { - return err - } - - duplicates, err = q.FetchPaymentDuplicates( - ctx, dbPayment.Payment.ID, - ) - return err - }, sqldb.NoOpReset, - ) - require.NoError(t, err) - - require.Len(t, duplicates, 2) - sort.SliceStable(duplicates, func(i, j int) bool { - return duplicates[i].AmountMsat < duplicates[j].AmountMsat - }) - - require.Equal(t, int64(2001000), duplicates[0].AmountMsat) - require.False(t, duplicates[0].FailReason.Valid) - require.NotEmpty(t, duplicates[0].SettlePreimage) - - require.Equal(t, int64(2002000), duplicates[1].AmountMsat) - require.True(t, duplicates[1].FailReason.Valid) - require.Equal( - t, int32(FailureReasonError), - duplicates[1].FailReason.Int32, - ) - require.Empty(t, duplicates[1].SettlePreimage) -} - -// TestMigrationWithLegacyZeroAttemptIDs verifies that very old payments whose -// HTLC attempt ID was migrated as the legacy "unknown" value zero do not -// collide in the SQL attempt index. -func TestMigrationWithLegacyZeroAttemptIDs(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - switchSequence uint64 - expectedAttempts []uint64 - expectedSequence uint64 - }{ - { - name: "zero switch sequence", - expectedAttempts: []uint64{1, 2}, - expectedSequence: 3, - }, - { - name: "advanced switch sequence", - switchSequence: 10, - expectedAttempts: []uint64{10, 11}, - expectedSequence: 12, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - ctx := context.Background() - kvDB := setupTestKVDB(t) - - hash1 := createTestPaymentHash(t, 10) - hash2 := createTestPaymentHash(t, 11) - - err := kvdb.Update(kvDB, func(tx kvdb.RwTx) error { - paymentsBucket, err := tx.CreateTopLevelBucket( - paymentsRootBucket, - ) - if err != nil { - return err - } - - indexBucket, err := tx.CreateTopLevelBucket( - paymentsIndexBucket, - ) - if err != nil { - return err - } - - seqBucket, err := tx.CreateTopLevelBucket( - switchNextPaymentIDKey, - ) - if err != nil { - return err - } - if testCase.switchSequence > 0 { - err := seqBucket.SetSequence( - testCase.switchSequence, - ) - if err != nil { - return err - } - } - - if err := createTestPaymentInKVWithAttemptID( - t, paymentsBucket, indexBucket, 3, hash1, 0, - ); err != nil { - return err - } - - return createTestPaymentInKVWithAttemptID( - t, paymentsBucket, indexBucket, 6, hash2, 0, - ) - }, func() {}) - require.NoError(t, err) - - sqlStore := setupTestSQLDB(t) - err = runPaymentsMigration(ctx, kvDB, sqlStore) - require.NoError(t, err) - - payment1, err := sqlStore.FetchPayment(ctx, hash1) - require.NoError(t, err) - require.Len(t, payment1.HTLCs, 1) - - payment2, err := sqlStore.FetchPayment(ctx, hash2) - require.NoError(t, err) - require.Len(t, payment2.HTLCs, 1) - - attempt1 := payment1.HTLCs[0].AttemptID - attempt2 := payment2.HTLCs[0].AttemptID - require.NotZero(t, attempt1) - require.NotZero(t, attempt2) - require.Equal(t, testCase.expectedAttempts[0], attempt1) - require.Equal(t, testCase.expectedAttempts[1], attempt2) - require.NotEqual(t, attempt1, attempt2) - - var seq uint64 - err = kvdb.View(kvDB, func(tx kvdb.RTx) error { - seqBucket := tx.ReadBucket(switchNextPaymentIDKey) - require.NotNil(t, seqBucket) - seq = seqBucket.Sequence() - - return nil - }, func() {}) - require.NoError(t, err) - - maxAttemptID := attempt1 - if attempt2 > maxAttemptID { - maxAttemptID = attempt2 - } - require.Greater(t, seq, maxAttemptID) - require.Equal(t, testCase.expectedSequence, seq) - - for _, kvPayment := range fetchAllPaymentsFromKV(t, kvDB) { - comparePaymentData(t, ctx, sqlStore, kvPayment) - } - }) - } -} - -// TestMigrationWithUnresolvedLegacyZeroAttemptID verifies that the migration -// fails unresolved legacy zero-ID HTLC attempts instead of remapping them into -// resumable synthetic attempts. -func TestMigrationWithUnresolvedLegacyZeroAttemptID(t *testing.T) { - t.Parallel() - - ctx := context.Background() - kvDB := setupTestKVDB(t) - - hash := createTestPaymentHash(t, 13) - - err := kvdb.Update(kvDB, func(tx kvdb.RwTx) error { - paymentsBucket, err := tx.CreateTopLevelBucket( - paymentsRootBucket, - ) - if err != nil { - return err - } - - indexBucket, err := tx.CreateTopLevelBucket(paymentsIndexBucket) - if err != nil { - return err - } - - _, err = tx.CreateTopLevelBucket( - switchNextPaymentIDKey, - ) - if err != nil { - return err - } - - return createInFlightPaymentWithAttemptID( - t, paymentsBucket, indexBucket, hash, 0, - ) - }, func() {}) - require.NoError(t, err) - - sqlStore := setupTestSQLDB(t) - err = runPaymentsMigration(ctx, kvDB, sqlStore) - require.NoError(t, err) - - payment, err := sqlStore.FetchPayment(ctx, hash) - require.NoError(t, err) - require.Equal(t, StatusFailed, payment.Status) - require.NotNil(t, payment.FailureReason) - require.Equal(t, FailureReasonError, *payment.FailureReason) - require.Len(t, payment.HTLCs, 1) - require.NotZero(t, payment.HTLCs[0].AttemptID) - require.Nil(t, payment.HTLCs[0].Settle) - require.NotNil(t, payment.HTLCs[0].Failure) - require.Equal(t, HTLCFailUnknown, payment.HTLCs[0].Failure.Reason) - - inFlight, err := sqlStore.FetchInFlightPayments(ctx) - require.NoError(t, err) - require.Empty(t, inFlight) -} - -// TestDuplicatePaymentsWithoutAttemptInfo verifies duplicate payments without -// attempt info are migrated with terminal failure reasons. -func TestDuplicatePaymentsWithoutAttemptInfo(t *testing.T) { - t.Parallel() - - ctx := context.Background() - kvDB := setupTestKVDB(t) - - hash := createTestPaymentHash(t, 0) - - err := kvdb.Update(kvDB, func(tx kvdb.RwTx) error { - paymentsBucket, err := tx.CreateTopLevelBucket( - paymentsRootBucket, - ) - if err != nil { - return err - } - - indexBucket, err := tx.CreateTopLevelBucket(paymentsIndexBucket) - if err != nil { - return err - } - - var globalAttemptID uint64 - err = createTestPaymentInKV( - t, paymentsBucket, indexBucket, 1, hash, - &globalAttemptID, - ) - if err != nil { - return err - } - - paymentBucket := paymentsBucket.NestedReadWriteBucket( - hash[:], - ) - require.NotNil(t, paymentBucket) - - dupBucket, err := paymentBucket.CreateBucketIfNotExists( - duplicatePaymentsBucket, - ) - if err != nil { - return err - } - - if err := createDuplicateWithoutAttemptInfo( - t, dupBucket, hash, 2, true, false, - ); err != nil { - return err - } - if err := createDuplicateWithoutAttemptInfo( - t, dupBucket, hash, 3, false, true, - ); err != nil { - return err - } - if err := createDuplicateWithoutAttemptInfo( - t, dupBucket, hash, 4, false, false, - ); err != nil { - return err - } - - return nil - }, func() {}) - require.NoError(t, err) - - sqlStore := setupTestSQLDB(t) - err = runPaymentsMigration(ctx, kvDB, sqlStore) - require.NoError(t, err) - - var ( - dbPayment sqlc.FetchPaymentRow - duplicates []sqlc.PaymentDuplicate - ) - err = sqlStore.db.ExecTx( - ctx, sqldb.ReadTxOpt(), func(q SQLQueries) error { - var err error - dbPayment, err = q.FetchPayment(ctx, hash[:]) - if err != nil { - return err - } - - duplicates, err = q.FetchPaymentDuplicates( - ctx, dbPayment.Payment.ID, - ) - return err - }, sqldb.NoOpReset, - ) - require.NoError(t, err) - - require.Len(t, duplicates, 3) - sort.SliceStable(duplicates, func(i, j int) bool { - return duplicates[i].AmountMsat < duplicates[j].AmountMsat - }) - - require.Equal(t, int64(2002000), duplicates[0].AmountMsat) - require.NotEmpty(t, duplicates[0].SettlePreimage) - require.False(t, duplicates[0].FailReason.Valid) - - require.Equal(t, int64(2003000), duplicates[1].AmountMsat) - require.True(t, duplicates[1].FailReason.Valid) - require.Equal( - t, int32(FailureReasonNoRoute), - duplicates[1].FailReason.Int32, - ) - require.Empty(t, duplicates[1].SettlePreimage) - - require.Equal(t, int64(2004000), duplicates[2].AmountMsat) - require.True(t, duplicates[2].FailReason.Valid) - require.Equal( - t, int32(FailureReasonError), - duplicates[2].FailReason.Int32, - ) - require.Empty(t, duplicates[2].SettlePreimage) -} - -// TestMigratePaymentWithMPP tests migration of a payment with MPP (multi-path -// payment) records. -func TestMigratePaymentWithMPP(t *testing.T) { - t.Parallel() - - ctx := context.Background() - kvDB := setupTestKVDB(t) - - // Create a payment with MPP. - var paymentHash [32]byte - copy(paymentHash[:], []byte("test_mpp_payment_hash_12345")) - - err := kvdb.Update(kvDB, func(tx kvdb.RwTx) error { - paymentsBucket, err := tx.CreateTopLevelBucket( - paymentsRootBucket, - ) - if err != nil { - return err - } - - indexBucket, err := tx.CreateTopLevelBucket(paymentsIndexBucket) - if err != nil { - return err - } - - return createPaymentWithMPP( - t, paymentsBucket, indexBucket, paymentHash, - ) - }, func() {}) - require.NoError(t, err) - - // Run migration. - sqlStore := setupTestSQLDB(t) - - err = runPaymentsMigration(ctx, kvDB, sqlStore) - require.NoError(t, err) - - // Verify payment matches. - assertPaymentDataMatches(t, ctx, kvDB, sqlStore, paymentHash) -} - -// TestMigratePaymentWithAMP tests migration of a payment with AMP (atomic -// multi-path) records. -func TestMigratePaymentWithAMP(t *testing.T) { - t.Parallel() - - ctx := context.Background() - kvDB := setupTestKVDB(t) - - var paymentHash [32]byte - copy(paymentHash[:], []byte("test_amp_payment_hash_12345")) - - err := kvdb.Update(kvDB, func(tx kvdb.RwTx) error { - paymentsBucket, err := tx.CreateTopLevelBucket( - paymentsRootBucket, - ) - if err != nil { - return err - } - - indexBucket, err := tx.CreateTopLevelBucket(paymentsIndexBucket) - if err != nil { - return err - } - - return createPaymentWithAMP( - t, paymentsBucket, indexBucket, paymentHash, - ) - }, func() {}) - require.NoError(t, err) - - sqlStore := setupTestSQLDB(t) - - err = runPaymentsMigration(ctx, kvDB, sqlStore) - require.NoError(t, err) - - assertPaymentDataMatches(t, ctx, kvDB, sqlStore, paymentHash) -} - -// TestMigratePaymentWithAMPSignedChildIndex tests migration of an AMP payment -// where the child index has the signed bit set. -func TestMigratePaymentWithAMPSignedChildIndex(t *testing.T) { - t.Parallel() - - ctx := context.Background() - kvDB := setupTestKVDB(t) - - var paymentHash [32]byte - copy(paymentHash[:], []byte("test_amp_child_idx_8000")) - - const childIndex = uint32(0x80000001) - - err := kvdb.Update(kvDB, func(tx kvdb.RwTx) error { - paymentsBucket, err := tx.CreateTopLevelBucket( - paymentsRootBucket, - ) - if err != nil { - return err - } - - indexBucket, err := tx.CreateTopLevelBucket(paymentsIndexBucket) - if err != nil { - return err - } - - return createPaymentWithAMPChildIndex( - t, paymentsBucket, indexBucket, paymentHash, childIndex, - ) - }, func() {}) - require.NoError(t, err) - - sqlStore := setupTestSQLDB(t) - - err = runPaymentsMigration(ctx, kvDB, sqlStore) - require.NoError(t, err) - - assertPaymentDataMatches(t, ctx, kvDB, sqlStore, paymentHash) -} - -// TestMigratePaymentWithCustomRecords tests migration of a payment with custom -// records. -func TestMigratePaymentWithCustomRecords(t *testing.T) { - t.Parallel() - - ctx := context.Background() - kvDB := setupTestKVDB(t) - - var paymentHash [32]byte - copy(paymentHash[:], []byte("test_custom_records_hash_12")) - - err := kvdb.Update(kvDB, func(tx kvdb.RwTx) error { - paymentsBucket, err := tx.CreateTopLevelBucket( - paymentsRootBucket, - ) - if err != nil { - return err - } - - indexBucket, err := tx.CreateTopLevelBucket(paymentsIndexBucket) - if err != nil { - return err - } - - return createPaymentWithCustomRecords( - t, paymentsBucket, indexBucket, paymentHash, - ) - }, func() {}) - require.NoError(t, err) - - sqlStore := setupTestSQLDB(t) - - err = runPaymentsMigration(ctx, kvDB, sqlStore) - require.NoError(t, err) - - assertPaymentDataMatches(t, ctx, kvDB, sqlStore, paymentHash) -} - -// TestMigratePaymentWithBlindedRoute tests migration of a payment with blinded -// route. -func TestMigratePaymentWithBlindedRoute(t *testing.T) { - t.Parallel() - - ctx := context.Background() - kvDB := setupTestKVDB(t) - - var paymentHash [32]byte - copy(paymentHash[:], []byte("test_blinded_route_hash_123")) - - err := kvdb.Update(kvDB, func(tx kvdb.RwTx) error { - paymentsBucket, err := tx.CreateTopLevelBucket( - paymentsRootBucket, - ) - if err != nil { - return err - } - - indexBucket, err := tx.CreateTopLevelBucket(paymentsIndexBucket) - if err != nil { - return err - } - - return createPaymentWithBlindedRoute( - t, paymentsBucket, indexBucket, paymentHash, - ) - }, func() {}) - require.NoError(t, err) - - sqlStore := setupTestSQLDB(t) - - err = runPaymentsMigration(ctx, kvDB, sqlStore) - require.NoError(t, err) - - assertPaymentDataMatches(t, ctx, kvDB, sqlStore, paymentHash) -} - -// TestMigrateOrphanedBlindedTotalAmount tests that a blinded total amount -// without encrypted recipient data is normalized during migration. -func TestMigrateOrphanedBlindedTotalAmount(t *testing.T) { - t.Parallel() - - runTest := func(t *testing.T, hashString string, - encryptedData []byte) { - - t.Helper() - - ctx := context.Background() - kvDB := setupTestKVDB(t) - - var paymentHash [32]byte - copy(paymentHash[:], []byte(hashString)) - - err := kvdb.Update(kvDB, func(tx kvdb.RwTx) error { - paymentsBucket, err := tx.CreateTopLevelBucket( - paymentsRootBucket, - ) - if err != nil { - return err - } - - indexBucket, err := tx.CreateTopLevelBucket( - paymentsIndexBucket, - ) - if err != nil { - return err - } - - return createTestPayment( - t, paymentsBucket, indexBucket, - paymentTestConfig{ - hash: paymentHash, - seqNum: 1, - value: 120000, - creationTime: time.Unix(1, 0), - paymentRequest: hashString, - attemptID: 1, - numHops: 1, - baseChannelID: 400000, - baseTimeLock: 800000, - hopConfigurator: func(hop *Hop, _ int, - _ bool) { - - hop.EncryptedData = encryptedData - hop.TotalAmtMsat = 119400 - }, - }, - ) - }, func() {}) - require.NoError(t, err) - - sqlStore := setupTestSQLDB(t) - err = runPaymentsMigration(ctx, kvDB, sqlStore) - require.NoError(t, err) - - var hash lntypes.Hash - copy(hash[:], paymentHash[:]) - payment, err := sqlStore.FetchPayment(ctx, hash) - require.NoError(t, err) - require.Zero( - t, payment.HTLCs[0].Route.Hops[0].TotalAmtMsat, - ) - - assertPaymentDataMatches(t, ctx, kvDB, sqlStore, paymentHash) - } - - t.Run("nil encrypted data", func(t *testing.T) { - runTest(t, "orphaned_total_nil", nil) - }) - t.Run("empty encrypted data", func(t *testing.T) { - runTest(t, "orphaned_total_empty", []byte{}) - }) -} - -// TestMigrateBlindingPointWithoutEncryptedData tests that migration reports a -// malformed blinded hop with enough context to locate the affected attempt. -func TestMigrateBlindingPointWithoutEncryptedData(t *testing.T) { - t.Parallel() - - ctx := context.Background() - kvDB := setupTestKVDB(t) - - var paymentHash [32]byte - copy(paymentHash[:], []byte("blinding_point_without_data")) - var attemptHash lntypes.Hash - copy(attemptHash[:], []byte("individual_amp_htlc_hash")) - - err := kvdb.Update(kvDB, func(tx kvdb.RwTx) error { - paymentsBucket, err := tx.CreateTopLevelBucket( - paymentsRootBucket, - ) - if err != nil { - return err - } - - indexBucket, err := tx.CreateTopLevelBucket( - paymentsIndexBucket, - ) - if err != nil { - return err - } - - return createTestPayment( - t, paymentsBucket, indexBucket, paymentTestConfig{ - hash: paymentHash, - attemptHash: &attemptHash, - seqNum: 1, - value: 120000, - creationTime: time.Unix(1, 0), - paymentRequest: "blinding-point-without-data", - attemptID: 1, - numHops: 1, - baseChannelID: 400000, - baseTimeLock: 800000, - hopConfigurator: func(hop *Hop, _ int, _ bool) { - blindingKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - hop.BlindingPoint = blindingKey.PubKey() - }, - }, - ) - }, func() {}) - require.NoError(t, err) - - sqlStore := setupTestSQLDB(t) - err = runPaymentsMigration(ctx, kvDB, sqlStore) - require.ErrorContains(t, err, "blinding point requires encrypted "+ - "recipient data") - require.ErrorContains(t, err, "attempt_index=1, hop=0") - require.ErrorContains(t, err, fmt.Sprintf( - "payment_hash=%x", paymentHash[:8], - )) - require.NotContains(t, err.Error(), fmt.Sprintf( - "payment_hash=%x", attemptHash[:8], - )) -} - -// TestMigratePaymentWithMetadata tests migration of a payment with hop -// metadata. -func TestMigratePaymentWithMetadata(t *testing.T) { - t.Parallel() - - ctx := context.Background() - kvDB := setupTestKVDB(t) - - var paymentHash [32]byte - copy(paymentHash[:], []byte("test_metadata_payment_hash_")) - - err := kvdb.Update(kvDB, func(tx kvdb.RwTx) error { - paymentsBucket, err := tx.CreateTopLevelBucket( - paymentsRootBucket, - ) - if err != nil { - return err - } - - indexBucket, err := tx.CreateTopLevelBucket(paymentsIndexBucket) - if err != nil { - return err - } - - return createPaymentWithMetadata( - t, paymentsBucket, indexBucket, paymentHash, - ) - }, func() {}) - require.NoError(t, err) - - sqlStore := setupTestSQLDB(t) - - err = runPaymentsMigration(ctx, kvDB, sqlStore) - require.NoError(t, err) - - assertPaymentDataMatches(t, ctx, kvDB, sqlStore, paymentHash) -} - -// TestMigratePaymentWithAllFeatures tests migration with all optional -// features enabled. -func TestMigratePaymentWithAllFeatures(t *testing.T) { - t.Parallel() - - ctx := context.Background() - kvDB := setupTestKVDB(t) - - var paymentHash [32]byte - copy(paymentHash[:], []byte("test_all_features_hash_1234")) - - err := kvdb.Update(kvDB, func(tx kvdb.RwTx) error { - paymentsBucket, err := tx.CreateTopLevelBucket( - paymentsRootBucket, - ) - if err != nil { - return err - } - - indexBucket, err := tx.CreateTopLevelBucket(paymentsIndexBucket) - if err != nil { - return err - } - - return createPaymentWithAllFeatures( - t, paymentsBucket, indexBucket, paymentHash, - ) - }, func() {}) - require.NoError(t, err) - - sqlStore := setupTestSQLDB(t) - - err = runPaymentsMigration(ctx, kvDB, sqlStore) - require.NoError(t, err) - - assertPaymentDataMatches(t, ctx, kvDB, sqlStore, paymentHash) -} - -// TestMigratePaymentFeatureCombinations tests selected feature combinations -// in a single migration to cover interactions without random data. -func TestMigratePaymentFeatureCombinations(t *testing.T) { - t.Parallel() - - ctx := context.Background() - kvDB := setupTestKVDB(t) - - cases := []paymentFeatureSet{ - { - name: "mpp_custom", - mpp: true, - customRecords: true, - }, - { - name: "amp_blinded", - amp: true, - blindedRoute: true, - }, - { - name: "custom_metadata", - customRecords: true, - hopMetadata: true, - }, - { - name: "blinded_metadata", - blindedRoute: true, - hopMetadata: true, - }, - { - name: "mpp_metadata", - mpp: true, - hopMetadata: true, - }, - } - - hashes := make([][32]byte, 0, len(cases)) - err := kvdb.Update(kvDB, func(tx kvdb.RwTx) error { - paymentsBucket, err := tx.CreateTopLevelBucket( - paymentsRootBucket, - ) - if err != nil { - return err - } - - indexBucket, err := tx.CreateTopLevelBucket(paymentsIndexBucket) - if err != nil { - return err - } - - var globalAttemptID uint64 - for i, c := range cases { - hash := sha256.Sum256([]byte(c.name)) - hashes = append(hashes, hash) - - err := createPaymentWithFeatureSet( - t, paymentsBucket, indexBucket, hash, - uint64(10+i), c, &globalAttemptID, - ) - if err != nil { - return err - } - } - - return nil - }, func() {}) - require.NoError(t, err) - - sqlStore := setupTestSQLDB(t) - - err = runPaymentsMigration(ctx, kvDB, sqlStore) - require.NoError(t, err) - - for _, hash := range hashes { - assertPaymentDataMatches(t, ctx, kvDB, sqlStore, hash) - } -} - -// TestMigratePaymentWithFailureMessage tests migration of a payment with a -// failed HTLC that includes a failure message. -func TestMigratePaymentWithFailureMessage(t *testing.T) { - t.Parallel() - - ctx := context.Background() - kvDB := setupTestKVDB(t) - - var paymentHash [32]byte - copy(paymentHash[:], []byte("test_fail_msg_hash_123456789")) - - // Create a payment with a failed HTLC. - err := kvdb.Update(kvDB, func(tx kvdb.RwTx) error { - paymentsBucket, err := tx.CreateTopLevelBucket( - paymentsRootBucket, - ) - if err != nil { - return err - } - - indexBucket, err := tx.CreateTopLevelBucket(paymentsIndexBucket) - if err != nil { - return err - } - - // Create payment bucket. - paymentBucket, err := paymentsBucket.CreateBucketIfNotExists( - paymentHash[:], - ) - if err != nil { - return err - } - - // Add creation info. - var paymentID lntypes.Hash - copy(paymentID[:], paymentHash[:]) - - creationInfo := &PaymentCreationInfo{ - PaymentIdentifier: paymentID, - Value: lnwire.MilliSatoshi(1000000), - CreationTime: time.Now().Add(-24 * time.Hour), - PaymentRequest: []byte("lnbc10utest"), - } - - // Use a separate buffer for payment creation info to avoid - // reuse issues when serializing HTLC attempts later. - var creationInfoBuf bytes.Buffer - err = serializePaymentCreationInfo( - &creationInfoBuf, creationInfo, - ) - if err != nil { - return err - } - - serialized := creationInfoBuf.Bytes() - - err = paymentBucket.Put( - paymentCreationInfoKey, serialized, - ) - if err != nil { - return err - } - - // Add sequence number. - seqBytes := make([]byte, 8) - byteOrder.PutUint64(seqBytes, 50) - err = paymentBucket.Put(paymentSequenceKey, seqBytes) - if err != nil { - return err - } - - // Add payment-level failure reason. - failReasonBytes := []byte{byte(FailureReasonNoRoute)} - err = paymentBucket.Put( - paymentFailInfoKey, failReasonBytes, - ) - if err != nil { - return err - } - - // Create HTLC bucket with one failed attempt. - htlcBucket, err := paymentBucket.CreateBucketIfNotExists( - paymentHtlcsBucket, - ) - if err != nil { - return err - } - - // Create the failed attempt with a failure message. - attemptID := uint64(500) - sessionKey, err := btcec.NewPrivateKey() - if err != nil { - return err - } - - var sessionKeyBytes [32]byte - copy(sessionKeyBytes[:], sessionKey.Serialize()) - - var sourcePubKey Vertex - copy(sourcePubKey[:], sessionKey.PubKey().SerializeCompressed()) - - hopKey, err := btcec.NewPrivateKey() - if err != nil { - return err - } - - // Create a proper copy of the hash instead of referencing - // the local variable directly. - attemptHash := new(lntypes.Hash) - copy(attemptHash[:], paymentHash[:]) - - //nolint:ll - attemptInfo := &HTLCAttemptInfo{ - AttemptID: attemptID, - sessionKey: sessionKeyBytes, - Route: Route{ - TotalTimeLock: 500000, - TotalAmount: 900, - SourcePubKey: sourcePubKey, - Hops: []*Hop{ - { - PubKeyBytes: NewVertex(hopKey.PubKey()), - ChannelID: 12345, - OutgoingTimeLock: 499500, - AmtToForward: 850, - }, - }, - }, - AttemptTime: time.Now().Add(-2 * time.Hour), - Hash: attemptHash, - } - - // Write attempt info. - attemptKey := make([]byte, len(htlcAttemptInfoKey)+8) - copy(attemptKey, htlcAttemptInfoKey) - byteOrder.PutUint64( - attemptKey[len(htlcAttemptInfoKey):], attemptID, - ) - - var b bytes.Buffer - err = serializeHTLCAttemptInfo(&b, attemptInfo) - if err != nil { - return err - } - err = htlcBucket.Put(attemptKey, b.Bytes()) - if err != nil { - return err - } - - // Add failure info with a message. - //nolint:ll - failInfo := &HTLCFailInfo{ - FailTime: time.Now().Add(-1 * time.Hour), - Message: &lnwire.FailTemporaryChannelFailure{}, - Reason: HTLCFailMessage, - FailureSourceIndex: 1, - } - - failKey := make([]byte, len(htlcFailInfoKey)+8) - copy(failKey, htlcFailInfoKey) - byteOrder.PutUint64(failKey[len(htlcFailInfoKey):], attemptID) - - b.Reset() - if err := serializeHTLCFailInfo(&b, failInfo); err != nil { - return err - } - if err := htlcBucket.Put(failKey, b.Bytes()); err != nil { - return err - } - - // Create index entry. - var idx bytes.Buffer - if err := WriteElements( - &idx, paymentIndexTypeHash, paymentHash[:], - ); err != nil { - return err - } - return indexBucket.Put(seqBytes, idx.Bytes()) - }, func() {}) - require.NoError(t, err) - - // Migrate to SQL. - sqlStore := setupTestSQLDB(t) - - err = runPaymentsMigration(ctx, kvDB, sqlStore) - require.NoError(t, err) - - // Verify data matches. - assertPaymentDataMatches(t, ctx, kvDB, sqlStore, paymentHash) -} - -// setupTestKVDB creates a temporary KV database for testing. -func setupTestKVDB(t *testing.T) kvdb.Backend { - t.Helper() - - backend, cleanup, err := kvdb.GetTestBackend(t.TempDir(), "payments") - require.NoError(t, err) - t.Cleanup(cleanup) - - return backend -} - -// populateTestPayments populates the KV database with test payment data. -func populateTestPayments(t *testing.T, db kvdb.Backend, numPayments int) int { - t.Helper() - - err := kvdb.Update(db, func(tx kvdb.RwTx) error { - // Create root buckets. - paymentsBucket, err := tx.CreateTopLevelBucket( - paymentsRootBucket, - ) - if err != nil { - return err - } - - indexBucket, err := tx.CreateTopLevelBucket(paymentsIndexBucket) - if err != nil { - return err - } - - // Create test payments with globally unique attempt IDs. - var globalAttemptID uint64 - for i := 0; i < numPayments; i++ { - hash := createTestPaymentHash(t, i) - - err := createTestPaymentInKV( - t, paymentsBucket, indexBucket, uint64(i), hash, - &globalAttemptID, - ) - if err != nil { - return err - } - } - - return nil - }, func() {}) - - require.NoError(t, err) - return numPayments -} - -// serializeDuplicatePaymentCreationInfo serializes PaymentCreationInfo for -// duplicate payments. The time is stored in seconds (not nanoseconds) to match -// the format used by deserializeDuplicatePaymentCreationInfo in the KV store. -func serializeDuplicatePaymentCreationInfo(w io.Writer, - c *PaymentCreationInfo) error { - - var scratch [8]byte - - if _, err := w.Write(c.PaymentIdentifier[:]); err != nil { - return err - } - - byteOrder.PutUint64(scratch[:], uint64(c.Value)) - if _, err := w.Write(scratch[:]); err != nil { - return err - } - - // Store time in seconds (not nanoseconds) for duplicate payments. - // This matches the deserialization format used in - // deserializeDuplicatePaymentCreationInfo. - var unixSec int64 - if !c.CreationTime.IsZero() { - unixSec = c.CreationTime.Unix() - } - byteOrder.PutUint64(scratch[:], uint64(unixSec)) - if _, err := w.Write(scratch[:]); err != nil { - return err - } - - byteOrder.PutUint32(scratch[:4], uint32(len(c.PaymentRequest))) - if _, err := w.Write(scratch[:4]); err != nil { - return err - } - - if _, err := w.Write(c.PaymentRequest); err != nil { - return err - } - - return nil -} - -// createTestPaymentHash creates a deterministic payment hash for testing. -func createTestPaymentHash(t *testing.T, seed int) [32]byte { - t.Helper() - - hash := sha256.Sum256([]byte{byte(seed)}) - return hash -} - -// createTestPaymentInKV creates a single payment in the KV store. -func createTestPaymentInKV(t *testing.T, paymentsBucket, - indexBucket kvdb.RwBucket, seqNum uint64, hash [32]byte, - globalAttemptID *uint64) error { - - t.Helper() - - // Increment global attempt ID and create HTLC attempt. So we have a - // globally unique attempt ID for the HTLC attempt. - *globalAttemptID++ - - return createTestPaymentInKVWithAttemptID( - t, paymentsBucket, indexBucket, seqNum, hash, *globalAttemptID, - ) -} - -// createTestPaymentInKVWithAttemptID creates a single payment in the KV store -// with a specific HTLC attempt ID. -func createTestPaymentInKVWithAttemptID(t *testing.T, paymentsBucket, - indexBucket kvdb.RwBucket, seqNum uint64, hash [32]byte, - attemptID uint64) error { - - t.Helper() - - // Create payment bucket. - paymentBucket, err := paymentsBucket.CreateBucketIfNotExists(hash[:]) - if err != nil { - return err - } - - // Create payment creation info. - var paymentID lntypes.Hash - copy(paymentID[:], hash[:]) - - creationInfo := &PaymentCreationInfo{ - PaymentIdentifier: paymentID, - Value: lnwire.MilliSatoshi(1000000 + seqNum*1000), - CreationTime: time.Now().Add(-24 * time.Hour), - PaymentRequest: []byte("lnbc1test"), - } - - // Serialize and write creation info. - var b bytes.Buffer - err = serializePaymentCreationInfo(&b, creationInfo) - if err != nil { - return err - } - err = paymentBucket.Put(paymentCreationInfoKey, b.Bytes()) - if err != nil { - return err - } - - // Store sequence number. - seqBytes := make([]byte, 8) - byteOrder.PutUint64(seqBytes, seqNum) - err = paymentBucket.Put(paymentSequenceKey, seqBytes) - if err != nil { - return err - } - - // Add one HTLC attempt for each payment with globally unique ID. - htlcBucket, err := paymentBucket.CreateBucketIfNotExists( - paymentHtlcsBucket, - ) - if err != nil { - return err - } - - err = createTestHTLCAttempt( - t, htlcBucket, hash, attemptID, seqNum%3 == 0, - ) - if err != nil { - return err - } - - var idx bytes.Buffer - err = WriteElements(&idx, paymentIndexTypeHash, hash[:]) - if err != nil { - return err - } - - return indexBucket.Put(seqBytes, idx.Bytes()) -} - -// createTestHTLCAttempt creates a test HTLC attempt in the KV store. -func createTestHTLCAttempt(t *testing.T, htlcBucket kvdb.RwBucket, - paymentHash [32]byte, attemptID uint64, shouldSettle bool) error { - t.Helper() - - // Generate a session key. - sessionKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - // Create a simple 2-hop route. - hop1Key, err := btcec.NewPrivateKey() - require.NoError(t, err) - - hop2Key, err := btcec.NewPrivateKey() - require.NoError(t, err) - - var sourcePubKey Vertex - copy(sourcePubKey[:], sessionKey.PubKey().SerializeCompressed()) - - // Convert session key to [32]byte. - var sessionKeyBytes [32]byte - copy(sessionKeyBytes[:], sessionKey.Serialize()) - - attemptInfo := &HTLCAttemptInfo{ - AttemptID: attemptID, - sessionKey: sessionKeyBytes, - Route: Route{ - TotalTimeLock: 500000, - TotalAmount: 900, - SourcePubKey: sourcePubKey, - Hops: []*Hop{ - { - PubKeyBytes: NewVertex( - hop1Key.PubKey(), - ), - ChannelID: 12345, - OutgoingTimeLock: 499500, - AmtToForward: 850, - }, - { - PubKeyBytes: NewVertex( - hop2Key.PubKey(), - ), - ChannelID: 67890, - OutgoingTimeLock: 499000, - AmtToForward: 800, - }, - }, - }, - AttemptTime: time.Now().Add(-2 * time.Hour), - Hash: (*lntypes.Hash)(&paymentHash), - } - - // Serialize and write attempt info. - attemptKey := make([]byte, len(htlcAttemptInfoKey)+8) - copy(attemptKey, htlcAttemptInfoKey) - byteOrder.PutUint64(attemptKey[len(htlcAttemptInfoKey):], attemptID) - - var b bytes.Buffer - err = serializeHTLCAttemptInfo(&b, attemptInfo) - if err != nil { - return err - } - err = htlcBucket.Put(attemptKey, b.Bytes()) - if err != nil { - return err - } - - // Add settlement if requested. - if shouldSettle { - settleInfo := &HTLCSettleInfo{ - Preimage: lntypes.Preimage(paymentHash), - SettleTime: time.Now().Add(-1 * time.Hour), - } - - settleKey := make([]byte, len(htlcSettleInfoKey)+8) - copy(settleKey, htlcSettleInfoKey) - byteOrder.PutUint64( - settleKey[len(htlcSettleInfoKey):], attemptID, - ) - - var sb bytes.Buffer - err = serializeHTLCSettleInfo(&sb, settleInfo) - if err != nil { - return err - } - err = htlcBucket.Put(settleKey, sb.Bytes()) - if err != nil { - return err - } - } - - return nil -} - -// createTestDuplicatePaymentWithIndex creates a duplicate payment and adds -// a matching entry into the global payment sequence index. -func createTestDuplicatePaymentWithIndex(t *testing.T, - paymentsBucket kvdb.RwBucket, indexBucket kvdb.RwBucket, - paymentHash [32]byte, seqNum uint64, shouldSettle bool, - globalAttemptID *uint64) error { - t.Helper() - - paymentBucket, err := paymentsBucket.CreateBucketIfNotExists( - paymentHash[:], - ) - if err != nil { - return err - } - - dupBucket, err := paymentBucket.CreateBucketIfNotExists( - duplicatePaymentsBucket, - ) - if err != nil { - return err - } - - if err := createTestDuplicatePayment( - t, dupBucket, paymentHash, seqNum, shouldSettle, - globalAttemptID, - ); err != nil { - return err - } - - seqBytes := make([]byte, 8) - byteOrder.PutUint64(seqBytes, seqNum) - var idx bytes.Buffer - if err := WriteElements( - &idx, paymentIndexTypeHash, paymentHash[:], - ); err != nil { - return err - } - - return indexBucket.Put(seqBytes, idx.Bytes()) -} - -// createTestDuplicatePayment creates a duplicate payment in the KV store. -func createTestDuplicatePayment(t *testing.T, - dupBucket kvdb.RwBucket, paymentHash [32]byte, seqNum uint64, - shouldSettle bool, globalAttemptID *uint64) error { - - t.Helper() - - // Create bucket for this duplicate using sequence number as key. - seqBytes := make([]byte, 8) - byteOrder.PutUint64(seqBytes, seqNum) - - dupPaymentBucket, err := dupBucket.CreateBucketIfNotExists(seqBytes) - if err != nil { - return err - } - - // Store sequence number. - err = dupPaymentBucket.Put(duplicatePaymentSequenceKey, seqBytes) - if err != nil { - return err - } - - // Create payment creation info. - var paymentID lntypes.Hash - copy(paymentID[:], paymentHash[:]) - - creationInfo := &PaymentCreationInfo{ - PaymentIdentifier: paymentID, - Value: lnwire.MilliSatoshi(2000000 + seqNum*1000), - CreationTime: time.Now().Add(-48 * time.Hour), - PaymentRequest: []byte("lnbc1duplicate"), - } - - var b bytes.Buffer - err = serializeDuplicatePaymentCreationInfo(&b, creationInfo) - if err != nil { - return err - } - err = dupPaymentBucket.Put(duplicatePaymentCreationInfoKey, b.Bytes()) - if err != nil { - return err - } - - // Generate a session key for the duplicate attempt. - sessionKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - // Create route for duplicate. - hop1Key, err := btcec.NewPrivateKey() - require.NoError(t, err) - - hop2Key, err := btcec.NewPrivateKey() - require.NoError(t, err) - - var sourcePubKey Vertex - copy(sourcePubKey[:], sessionKey.PubKey().SerializeCompressed()) - - var sessionKeyBytes [32]byte - copy(sessionKeyBytes[:], sessionKey.Serialize()) - - // Use globally unique attempt ID. - *globalAttemptID++ - attemptID := *globalAttemptID - - duplicateAttempt := &duplicateHTLCAttemptInfo{ - attemptID: attemptID, - sessionKey: sessionKeyBytes, - route: Route{ - TotalTimeLock: 500000, - TotalAmount: 900, - SourcePubKey: sourcePubKey, - Hops: []*Hop{ - { - PubKeyBytes: NewVertex( - hop1Key.PubKey(), - ), - ChannelID: 12345, - OutgoingTimeLock: 499500, - AmtToForward: 850, - }, - { - PubKeyBytes: NewVertex( - hop2Key.PubKey(), - ), - ChannelID: 67890, - OutgoingTimeLock: 499000, - AmtToForward: 800, - }, - }, - }, - } - - // Serialize and write attempt info (using existing WriteElements - // and SerializeRoute). - var ab bytes.Buffer - if err := WriteElements( - &ab, duplicateAttempt.attemptID, - duplicateAttempt.sessionKey, - ); err != nil { - return err - } - if err := SerializeRoute(&ab, duplicateAttempt.route); err != nil { - return err - } - err = dupPaymentBucket.Put(duplicatePaymentAttemptInfoKey, ab.Bytes()) - if err != nil { - return err - } - - // Add settlement if requested. - if shouldSettle { - settleInfo := &HTLCSettleInfo{ - Preimage: lntypes.Preimage(paymentHash), - SettleTime: time.Now().Add(-1 * time.Hour), - } - - var sb bytes.Buffer - err = serializeHTLCSettleInfo(&sb, settleInfo) - if err != nil { - return err - } - err = dupPaymentBucket.Put( - duplicatePaymentSettleInfoKey, sb.Bytes(), - ) - if err != nil { - return err - } - } - - return nil -} - -// createDuplicateWithoutAttemptInfo creates a duplicate payment bucket with -// settle/fail info but without attempt info. -func createDuplicateWithoutAttemptInfo(t *testing.T, - dupBucket kvdb.RwBucket, paymentHash [32]byte, seqNum uint64, - shouldSettle bool, shouldFail bool) error { - t.Helper() - - seqBytes := make([]byte, 8) - byteOrder.PutUint64(seqBytes, seqNum) - - dupPaymentBucket, err := dupBucket.CreateBucketIfNotExists(seqBytes) - if err != nil { - return err - } - - if err := dupPaymentBucket.Put( - duplicatePaymentSequenceKey, seqBytes, - ); err != nil { - return err - } - - var paymentID lntypes.Hash - copy(paymentID[:], paymentHash[:]) - - creationInfo := &PaymentCreationInfo{ - PaymentIdentifier: paymentID, - Value: lnwire.MilliSatoshi(2000000 + seqNum*1000), - CreationTime: time.Now().Add(-48 * time.Hour), - PaymentRequest: []byte("lnbc1duplicate"), - } - - var b bytes.Buffer - err = serializeDuplicatePaymentCreationInfo(&b, creationInfo) - if err != nil { - return err - } - if err := dupPaymentBucket.Put( - duplicatePaymentCreationInfoKey, b.Bytes(), - ); err != nil { - return err - } - - switch { - case shouldSettle && shouldFail: - return fmt.Errorf("invalid duplicate state") - case shouldSettle: - settleInfo := &HTLCSettleInfo{ - Preimage: lntypes.Preimage(paymentHash), - SettleTime: time.Now().Add(-1 * time.Hour), - } - - var sb bytes.Buffer - err = serializeHTLCSettleInfo(&sb, settleInfo) - if err != nil { - return err - } - if err := dupPaymentBucket.Put( - duplicatePaymentSettleInfoKey, sb.Bytes(), - ); err != nil { - return err - } - case shouldFail: - failReasonBytes := []byte{byte(FailureReasonNoRoute)} - if err := dupPaymentBucket.Put( - duplicatePaymentFailInfoKey, failReasonBytes, - ); err != nil { - return err - } - } - - return nil -} - -// fetchAllPaymentsFromKV fetches all payments from the KV store using the -// KVStore implementation. -func fetchAllPaymentsFromKV(t *testing.T, kvDB kvdb.Backend) []*MPPayment { - t.Helper() - - kvStore, err := NewKVStore(kvDB, WithNoMigration(true)) - require.NoError(t, err) - - payments, err := kvStore.FetchPayments() - require.NoError(t, err) - - return payments -} - -// normalizePaymentData makes sure that the payment data is normalized for -// comparison using the same logic as in-migration validation. -func normalizePaymentData(payment *MPPayment) { - if payment.Status == StatusInFlight { - _, _ = terminalizeUnresolvedLegacyZeroAttempts(payment) - } - - normalizePaymentForCompare(payment) -} - -// comparePaymentData compares a KV payment with its SQL counterpart using -// deep equality check (similar to invoice migration). -func comparePaymentData(t *testing.T, ctx context.Context, sqlStore *SQLStore, - kvPayment *MPPayment) { - - t.Helper() - - // Fetch the SQL payment as MPPayment using SQLStore. - var paymentHash lntypes.Hash - copy(paymentHash[:], kvPayment.Info.PaymentIdentifier[:]) - - sqlPayment, err := sqlStore.FetchPayment(ctx, paymentHash) - require.NoError(t, err, "SQL payment should exist for %x", - paymentHash[:8]) - - if kvPayment.Status == StatusInFlight { - // Normalize legacy payment state before remapped zero attempt - // IDs are aligned, because unresolved legacy zero-ID attempts - // are intentionally failed during migration. - _, _ = terminalizeUnresolvedLegacyZeroAttempts(kvPayment) - } - - // Normalize expected KV/SQL representation differences before the - // deep equality check. - normalizeLegacyZeroAttemptIDsForCompare(kvPayment, sqlPayment) - normalizePaymentData(kvPayment) - normalizePaymentData(sqlPayment) - - // Deep equality check - compares all fields recursively. - require.Equal(t, kvPayment, sqlPayment, - "KV and SQL payments should be equal for %x", paymentHash[:8]) -} - -// runPaymentsMigration executes the payment migration from KV to SQL within -// a SQL transaction. -func runPaymentsMigration(ctx context.Context, kvDB kvdb.Backend, - sqlStore *SQLStore) error { - - return sqlStore.db.ExecTx( - ctx, sqldb.WriteTxOpt(), func(tx SQLQueries) error { - migTx, ok := tx.(SQLMigrationQueries) - if !ok { - return fmt.Errorf("db does not implement " + - "SQLMigrationQueries") - } - - return MigratePaymentsKVToSQL( - ctx, kvDB, migTx, &SQLStoreConfig{ - QueryCfg: sqlStore.cfg.QueryCfg, - }, - ) - }, sqldb.NoOpReset, - ) -} - -// assertPaymentDataMatches verifies a payment in KV matches its SQL counterpart -// using deep equality check. -func assertPaymentDataMatches(t *testing.T, ctx context.Context, - kvDB kvdb.Backend, sqlStore *SQLStore, hash [32]byte) { - t.Helper() - - // Fetch from KV. - var kvPayment *MPPayment - err := kvdb.View(kvDB, func(tx kvdb.RTx) error { - paymentsBucket := tx.ReadBucket(paymentsRootBucket) - if paymentsBucket == nil { - return nil - } - - paymentBucket := paymentsBucket.NestedReadBucket(hash[:]) - if paymentBucket == nil { - return nil - } - - var err error - kvPayment, err = fetchPayment(paymentBucket) - return err - }, func() {}) - require.NoError(t, err) - - if kvPayment == nil { - // Payment doesn't exist in KV, should not exist in SQL - // either. - var paymentHash lntypes.Hash - copy(paymentHash[:], hash[:]) - _, err := sqlStore.FetchPayment(ctx, paymentHash) - require.Error( - t, err, "payment should not exist in SQL if not "+ - "in KV", - ) - return - } - - // Use the deep comparison function. - comparePaymentData(t, ctx, sqlStore, kvPayment) -} - -// paymentTestConfig holds configuration for creating test payments with various -// features. -type paymentTestConfig struct { - hash [32]byte - attemptHash *lntypes.Hash - seqNum uint64 - value lnwire.MilliSatoshi - creationTime time.Time - paymentRequest string - attemptID uint64 - numHops int - baseChannelID uint64 - baseTimeLock uint32 - paymentCustomRecs lnwire.CustomRecords - attemptCustomRecs lnwire.CustomRecords - hopConfigurator func(hop *Hop, index int, isFinal bool) -} - -// serializeAndPut serializes data using the provided serializer function and -// writes it to the bucket. -func serializeAndPut(bucket kvdb.RwBucket, key []byte, - serializer func(io.Writer) error) error { - - var b bytes.Buffer - if err := serializer(&b); err != nil { - return err - } - return bucket.Put(key, b.Bytes()) -} - -// generateSessionKey creates a new session key and returns the private key, -// source public key vertex, and serialized key bytes. -func generateSessionKey(t *testing.T) (*btcec.PrivateKey, Vertex, - [32]byte, error) { - - t.Helper() - - sessionKey, err := btcec.NewPrivateKey() - if err != nil { - return nil, Vertex{}, [32]byte{}, err - } - - var sourcePubKey Vertex - copy(sourcePubKey[:], sessionKey.PubKey().SerializeCompressed()) - - var sessionKeyBytes [32]byte - copy(sessionKeyBytes[:], sessionKey.Serialize()) - - return sessionKey, sourcePubKey, sessionKeyBytes, nil -} - -// createTestHops creates the specified number of test hops with the given -// parameters. The configurator function is called for each hop to allow -// feature-specific customization. -func createTestHops(t *testing.T, numHops int, baseAmount lnwire.MilliSatoshi, - baseChannelID uint64, baseTimeLock uint32, - configurator func(*Hop, int, bool)) ([]*Hop, - lnwire.MilliSatoshi, error) { - - t.Helper() - - hops := make([]*Hop, numHops) - currentAmt := baseAmount - - for i := 0; i < numHops; i++ { - hopKey, err := btcec.NewPrivateKey() - if err != nil { - return nil, 0, err - } - - amt := baseAmount - lnwire.MilliSatoshi(uint64(i)*100) - hop := &Hop{ - PubKeyBytes: NewVertex(hopKey.PubKey()), - ChannelID: baseChannelID + uint64(i), - OutgoingTimeLock: baseTimeLock - uint32(i*40), - AmtToForward: amt, - } - - // Apply feature-specific configuration. - if configurator != nil { - configurator(hop, i, i == numHops-1) - } - - hops[i] = hop - currentAmt = amt - } - - return hops, currentAmt, nil -} - -// writeHTLCAttempt writes the HTLC attempt info to the bucket. -func writeHTLCAttempt(bucket kvdb.RwBucket, attemptID uint64, - info *HTLCAttemptInfo) error { - - attemptKey := make([]byte, len(htlcAttemptInfoKey)+8) - copy(attemptKey, htlcAttemptInfoKey) - byteOrder.PutUint64(attemptKey[len(htlcAttemptInfoKey):], attemptID) - - return serializeAndPut(bucket, attemptKey, func(w io.Writer) error { - return serializeHTLCAttemptInfo(w, info) - }) -} - -// writeHTLCSettle writes the HTLC settle info to the bucket. -func writeHTLCSettle(bucket kvdb.RwBucket, attemptID uint64, - info *HTLCSettleInfo) error { - - settleKey := make([]byte, len(htlcSettleInfoKey)+8) - copy(settleKey, htlcSettleInfoKey) - byteOrder.PutUint64(settleKey[len(htlcSettleInfoKey):], attemptID) - - return serializeAndPut(bucket, settleKey, func(w io.Writer) error { - return serializeHTLCSettleInfo(w, info) - }) -} - -// createIndexEntry creates a payment index entry in the index bucket. -func createIndexEntry(indexBucket kvdb.RwBucket, seqBytes []byte, - hash [32]byte) error { - - var idx bytes.Buffer - if err := WriteElements( - &idx, paymentIndexTypeHash, hash[:], - ); err != nil { - return err - } - - return indexBucket.Put(seqBytes, idx.Bytes()) -} - -// createTestPayment creates a test payment with the specified configuration, -// handling all common boilerplate code. -func createTestPayment(t *testing.T, paymentsBucket, indexBucket kvdb.RwBucket, - cfg paymentTestConfig) error { - - t.Helper() - - // Create payment bucket. - paymentBucket, err := paymentsBucket.CreateBucketIfNotExists( - cfg.hash[:], - ) - if err != nil { - return err - } - - // Create payment ID. - var paymentID lntypes.Hash - copy(paymentID[:], cfg.hash[:]) - - // Create and serialize payment creation info. - creationInfo := &PaymentCreationInfo{ - PaymentIdentifier: paymentID, - Value: cfg.value, - CreationTime: cfg.creationTime, - PaymentRequest: []byte(cfg.paymentRequest), - FirstHopCustomRecords: cfg.paymentCustomRecs, - } - - err = serializeAndPut( - paymentBucket, paymentCreationInfoKey, - func(w io.Writer) error { - return serializePaymentCreationInfo(w, creationInfo) - }, - ) - if err != nil { - return err - } - - // Store sequence number. - seqBytes := make([]byte, 8) - byteOrder.PutUint64(seqBytes, cfg.seqNum) - if err := paymentBucket.Put(paymentSequenceKey, seqBytes); err != nil { - return err - } - - // Create HTLC bucket. - htlcBucket, err := paymentBucket.CreateBucketIfNotExists( - paymentHtlcsBucket, - ) - if err != nil { - return err - } - - // Generate session key. - _, sourcePubKey, sessionKeyBytes, err := generateSessionKey(t) - if err != nil { - return err - } - - // Create route with hops. - hops, totalAmount, err := createTestHops( - t, cfg.numHops, cfg.value, cfg.baseChannelID, - cfg.baseTimeLock, cfg.hopConfigurator, - ) - if err != nil { - return err - } - - // Create and serialize attempt info. - attemptHash := (*lntypes.Hash)(&cfg.hash) - if cfg.attemptHash != nil { - attemptHash = cfg.attemptHash - } - - attemptInfo := &HTLCAttemptInfo{ - AttemptID: cfg.attemptID, - sessionKey: sessionKeyBytes, - Route: Route{ - TotalTimeLock: cfg.baseTimeLock, - TotalAmount: totalAmount, - SourcePubKey: sourcePubKey, - Hops: hops, - FirstHopWireCustomRecords: cfg.attemptCustomRecs, - }, - AttemptTime: cfg.creationTime.Add(time.Minute), - Hash: attemptHash, - } - - if err = writeHTLCAttempt( - htlcBucket, cfg.attemptID, attemptInfo, - ); err != nil { - return err - } - - // Add settlement. - settleInfo := &HTLCSettleInfo{ - Preimage: lntypes.Preimage(cfg.hash), - SettleTime: cfg.creationTime.Add(2 * time.Minute), - } - - if err := writeHTLCSettle( - htlcBucket, cfg.attemptID, settleInfo, - ); err != nil { - return err - } - - // Create index entry. - return createIndexEntry(indexBucket, seqBytes, cfg.hash) -} - -// createPaymentWithMPP creates a payment with MPP records on the final hop. -func createPaymentWithMPP(t *testing.T, paymentsBucket, - indexBucket kvdb.RwBucket, hash [32]byte) error { - - t.Helper() - - return createTestPayment( - t, paymentsBucket, indexBucket, - paymentTestConfig{ - hash: hash, - seqNum: 1, - value: 50000, - creationTime: time.Date( - 2024, 1, 1, 12, 0, 0, 0, time.UTC, - ), - paymentRequest: "lnbc500n1test_mpp", - attemptID: 1, - numHops: 3, - baseChannelID: 100000, - baseTimeLock: 500000, - hopConfigurator: func(hop *Hop, index int, - isFinal bool) { - - if isFinal { - var paymentAddr [32]byte - copy( - paymentAddr[:], - []byte( - "test_mpp_payment_"+ - "address_32", - ), - ) - hop.MPP = record.NewMPP( - lnwire.MilliSatoshi(50000), - paymentAddr, - ) - } - }, - }, - ) -} - -// createPaymentWithAMP creates a payment with AMP records on the final hop. -func createPaymentWithAMP(t *testing.T, paymentsBucket, - indexBucket kvdb.RwBucket, hash [32]byte) error { - t.Helper() - - return createPaymentWithAMPChildIndex( - t, paymentsBucket, indexBucket, hash, 0, - ) -} - -// createPaymentWithAMPChildIndex creates a payment with AMP records on the -// final hop and a specific child index. -func createPaymentWithAMPChildIndex(t *testing.T, paymentsBucket, - indexBucket kvdb.RwBucket, hash [32]byte, childIndex uint32) error { - - t.Helper() - - return createTestPayment( - t, paymentsBucket, indexBucket, - paymentTestConfig{ - hash: hash, - seqNum: 2, - value: 75000, - creationTime: time.Date( - 2024, 2, 1, 10, 0, 0, 0, time.UTC, - ), - paymentRequest: "lnbc750n1test_amp", - attemptID: 1, - numHops: 2, - baseChannelID: 200000, - baseTimeLock: 600000, - hopConfigurator: func(hop *Hop, index int, - isFinal bool) { - - if isFinal { - var rootShare [32]byte - copy( - rootShare[:], - []byte( - "test_amp_root_share"+ - "_12345678", - ), - ) - var setID [32]byte - copy( - setID[:], - []byte( - "test_amp_set_id_"+ - "123456789012", - ), - ) - hop.AMP = record.NewAMP( - rootShare, setID, childIndex, - ) - } - }, - }, - ) -} - -// createPaymentWithCustomRecords creates a payment with custom records at all -// levels. -func createPaymentWithCustomRecords(t *testing.T, paymentsBucket, - indexBucket kvdb.RwBucket, hash [32]byte) error { - - t.Helper() - - return createTestPayment( - t, paymentsBucket, indexBucket, - paymentTestConfig{ - hash: hash, - seqNum: 3, - value: 100000, - creationTime: time.Date( - 2024, 3, 1, 14, 0, 0, 0, time.UTC, - ), - paymentRequest: "lnbc1m1test_custom", - attemptID: 1, - numHops: 3, - baseChannelID: 300000, - baseTimeLock: 700000, - paymentCustomRecs: lnwire.CustomRecords{ - 65536: []byte("payment_level_value_1"), - 65537: []byte("payment_level_value_2"), - }, - attemptCustomRecs: lnwire.CustomRecords{ - 65541: []byte("attempt_custom_value_1"), - 65542: []byte("attempt_custom_value_2"), - }, - hopConfigurator: func(hop *Hop, index int, - isFinal bool) { - - hop.CustomRecords = record.CustomSet{ - 65538 + uint64(index): []byte( - fmt.Sprintf( - "hop_%d_custom_value", - index, - ), - ), - } - }, - }, - ) -} - -// createPaymentWithBlindedRoute creates a payment with blinded route data. -func createPaymentWithBlindedRoute(t *testing.T, paymentsBucket, - indexBucket kvdb.RwBucket, hash [32]byte) error { - - t.Helper() - - return createTestPayment( - t, paymentsBucket, indexBucket, - paymentTestConfig{ - hash: hash, - seqNum: 4, - value: 120000, - creationTime: time.Date( - 2024, 4, 1, 16, 0, 0, 0, time.UTC, - ), - paymentRequest: "lnbc1200n1test_blinded", - attemptID: 1, - numHops: 4, - baseChannelID: 400000, - baseTimeLock: 800000, - hopConfigurator: func(hop *Hop, index int, - isFinal bool) { - - if isFinal { - blindingKey, err := btcec. - NewPrivateKey() - - require.NoError(t, err) - - hop.BlindingPoint = blindingKey.PubKey() - hop.EncryptedData = []byte( - "encrypted_blinded_route_" + - "data_test_value_12345", - ) - hop.TotalAmtMsat = lnwire.MilliSatoshi( - 119400, - ) - } - }, - }, - ) -} - -// createPaymentWithMetadata creates a payment with hop metadata. -func createPaymentWithMetadata(t *testing.T, paymentsBucket, - indexBucket kvdb.RwBucket, hash [32]byte) error { - - t.Helper() - - return createTestPayment( - t, paymentsBucket, indexBucket, - paymentTestConfig{ - hash: hash, - seqNum: 5, - value: 80000, - creationTime: time.Date( - 2024, 5, 1, 18, 0, 0, 0, time.UTC, - ), - paymentRequest: "lnbc800n1test_metadata", - attemptID: 1, - numHops: 3, - baseChannelID: 500000, - baseTimeLock: 900000, - hopConfigurator: func(hop *Hop, index int, - isFinal bool) { - - hop.Metadata = []byte( - fmt.Sprintf( - "hop_%d_metadata_value", index, - ), - ) - }, - }, - ) -} - -// createPaymentWithAllFeatures creates a payment with all optional features -// enabled. -func createPaymentWithAllFeatures(t *testing.T, paymentsBucket, - indexBucket kvdb.RwBucket, hash [32]byte) error { - - t.Helper() - - return createTestPayment( - t, paymentsBucket, indexBucket, - paymentTestConfig{ - hash: hash, - seqNum: 6, - value: 150000, - creationTime: time.Date( - 2024, 6, 1, 20, 0, 0, 0, time.UTC, - ), - paymentRequest: "lnbc1500n1test_all_features", - attemptID: 1, - numHops: 4, - baseChannelID: 600000, - baseTimeLock: 1000000, - paymentCustomRecs: lnwire.CustomRecords{ - 65543: []byte("all_features_payment_custom_1"), - 65544: []byte("all_features_payment_custom_2"), - }, - attemptCustomRecs: lnwire.CustomRecords{ - 65549: []byte("all_feat_attempt_custom_1"), - 65550: []byte("all_feat_attempt_custom_2"), - }, - hopConfigurator: func(hop *Hop, index int, - isFinal bool) { - - // Add custom records and metadata to all hops. - hop.CustomRecords = record.CustomSet{ - 65545 + uint64(index): []byte( - fmt.Sprintf( - "all_feat_hop_%d", - index, - ), - ), - } - hop.Metadata = []byte( - fmt.Sprintf( - "all_feat_metadata_%d", index, - ), - ) - - // Add MPP and blinded route data to final hop. - if isFinal { - var paymentAddr [32]byte - copy( - paymentAddr[:], - []byte( - "all_features_mpp_"+ - "addr_123456", - ), - ) - hop.MPP = record.NewMPP( - lnwire.MilliSatoshi(149250), - paymentAddr, - ) - - blindingKey, err := btcec. - NewPrivateKey() - - require.NoError(t, err) - hop.BlindingPoint = blindingKey.PubKey() - hop.EncryptedData = []byte( - "all_features_encrypted_" + - "blinded_data_123456", - ) - hop.TotalAmtMsat = lnwire.MilliSatoshi( - 149250, - ) - } - }, - }, - ) -} - -// paymentFeatureSet defines a combination of optional payment features for -// testing feature interactions. -type paymentFeatureSet struct { - name string - mpp bool - amp bool - customRecords bool - blindedRoute bool - hopMetadata bool -} - -// TestMigrateLegacyPaymentWithNilHash tests migration of a legacy payment where -// the HTLC attempt's Hash field is nil. In older payments, the Hash wasn't -// stored on individual HTLCs; instead, the parent payment hash should be used. -func TestMigrateLegacyPaymentWithNilHash(t *testing.T) { - t.Parallel() - - ctx := context.Background() - kvDB := setupTestKVDB(t) - - var paymentHash [32]byte - copy(paymentHash[:], []byte("test_legacy_nil_hash_payment")) - - err := kvdb.Update(kvDB, func(tx kvdb.RwTx) error { - paymentsBucket, err := tx.CreateTopLevelBucket( - paymentsRootBucket, - ) - if err != nil { - return err - } - - indexBucket, err := tx.CreateTopLevelBucket(paymentsIndexBucket) - if err != nil { - return err - } - - return createLegacyPaymentWithNilHash( - t, paymentsBucket, indexBucket, paymentHash, - ) - }, func() {}) - require.NoError(t, err) - - sqlStore := setupTestSQLDB(t) - - err = runPaymentsMigration(ctx, kvDB, sqlStore) - require.NoError(t, err) - - // Verify the payment was migrated correctly. - var paymentID lntypes.Hash - copy(paymentID[:], paymentHash[:]) - - payment, err := sqlStore.FetchPayment(ctx, paymentID) - require.NoError(t, err) - require.NotNil(t, payment) - require.Len(t, payment.HTLCs, 1) - - // The HTLC should have the parent payment hash since its own Hash was - // nil. - require.NotNil(t, payment.HTLCs[0].Hash) - require.Equal(t, paymentID, *payment.HTLCs[0].Hash) -} - -// createLegacyPaymentWithNilHash creates a payment with an HTLC attempt that -// has a nil Hash field, simulating older payment data format. -func createLegacyPaymentWithNilHash(t *testing.T, paymentsBucket, - indexBucket kvdb.RwBucket, hash [32]byte) error { - - t.Helper() - - paymentBucket, err := paymentsBucket.CreateBucketIfNotExists(hash[:]) - if err != nil { - return err - } - - var paymentID lntypes.Hash - copy(paymentID[:], hash[:]) - - creationInfo := &PaymentCreationInfo{ - PaymentIdentifier: paymentID, - Value: lnwire.MilliSatoshi(50000), - CreationTime: time.Now().Add(-24 * time.Hour), - PaymentRequest: []byte("lnbc500n1legacy_payment"), - } - - var creationBuf bytes.Buffer - err = serializePaymentCreationInfo(&creationBuf, creationInfo) - if err != nil { - return err - } - err = paymentBucket.Put(paymentCreationInfoKey, creationBuf.Bytes()) - if err != nil { - return err - } - - seqBytes := make([]byte, 8) - byteOrder.PutUint64(seqBytes, 100) - err = paymentBucket.Put(paymentSequenceKey, seqBytes) - if err != nil { - return err - } - - htlcBucket, err := paymentBucket.CreateBucketIfNotExists( - paymentHtlcsBucket, - ) - if err != nil { - return err - } - - // Create HTLC attempt with nil Hash (legacy format). - sessionKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - var sessionKeyBytes [32]byte - copy(sessionKeyBytes[:], sessionKey.Serialize()) - - var sourcePubKey Vertex - copy(sourcePubKey[:], sessionKey.PubKey().SerializeCompressed()) - - hopKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - attemptInfo := &HTLCAttemptInfo{ - AttemptID: 1, - sessionKey: sessionKeyBytes, - Route: Route{ - TotalTimeLock: 500000, - TotalAmount: 50000, - SourcePubKey: sourcePubKey, - Hops: []*Hop{ - { - PubKeyBytes: NewVertex(hopKey.PubKey()), - ChannelID: 12345, - OutgoingTimeLock: 499500, - AmtToForward: 49000, - }, - }, - }, - AttemptTime: time.Now().Add(-2 * time.Hour), - Hash: nil, // Legacy: Hash is nil - } - - // Serialize the attempt info. Since Hash is nil, the serialization - // will not write the hash, simulating the legacy format. - attemptKey := make([]byte, len(htlcAttemptInfoKey)+8) - copy(attemptKey, htlcAttemptInfoKey) - byteOrder.PutUint64(attemptKey[len(htlcAttemptInfoKey):], 1) - - var attemptBuf bytes.Buffer - err = serializeHTLCAttemptInfo(&attemptBuf, attemptInfo) - if err != nil { - return err - } - err = htlcBucket.Put(attemptKey, attemptBuf.Bytes()) - if err != nil { - return err - } - - // Add settlement info. - settleInfo := &HTLCSettleInfo{ - Preimage: lntypes.Preimage(hash), - SettleTime: time.Now().Add(-1 * time.Hour), - } - - settleKey := make([]byte, len(htlcSettleInfoKey)+8) - copy(settleKey, htlcSettleInfoKey) - byteOrder.PutUint64(settleKey[len(htlcSettleInfoKey):], 1) - - var settleBuf bytes.Buffer - err = serializeHTLCSettleInfo(&settleBuf, settleInfo) - if err != nil { - return err - } - err = htlcBucket.Put(settleKey, settleBuf.Bytes()) - if err != nil { - return err - } - - // Create index entry. - var idx bytes.Buffer - err = WriteElements(&idx, paymentIndexTypeHash, hash[:]) - if err != nil { - return err - } - - return indexBucket.Put(seqBytes, idx.Bytes()) -} - -// createPaymentWithFeatureSet creates a payment with a selected set of -// optional features for combination testing. -func createPaymentWithFeatureSet(t *testing.T, paymentsBucket, - indexBucket kvdb.RwBucket, hash [32]byte, seqNum uint64, - features paymentFeatureSet, globalAttemptID *uint64) error { - t.Helper() - - if features.mpp && features.amp { - return fmt.Errorf("invalid feature set: mpp and amp") - } - - paymentBucket, err := paymentsBucket.CreateBucketIfNotExists(hash[:]) - if err != nil { - return err - } - - var paymentID lntypes.Hash - copy(paymentID[:], hash[:]) - - creationTime := time.Date(2024, 7, 1, 12, 0, 0, 0, time.UTC). - Add(time.Duration(seqNum) * time.Minute) - creationInfo := &PaymentCreationInfo{ - PaymentIdentifier: paymentID, - Value: lnwire.MilliSatoshi(100000), - CreationTime: creationTime, - PaymentRequest: []byte( - fmt.Sprintf("lnbc_test_%s", features.name), - ), - } - if features.customRecords { - creationInfo.FirstHopCustomRecords = lnwire.CustomRecords{ - 65560: []byte("combo_payment_custom_1"), - 65561: []byte("combo_payment_custom_2"), - } - } - - var b bytes.Buffer - err = serializePaymentCreationInfo(&b, creationInfo) - if err != nil { - return err - } - err = paymentBucket.Put(paymentCreationInfoKey, b.Bytes()) - if err != nil { - return err - } - - seqBytes := make([]byte, 8) - byteOrder.PutUint64(seqBytes, seqNum) - err = paymentBucket.Put(paymentSequenceKey, seqBytes) - if err != nil { - return err - } - - htlcBucket, err := paymentBucket.CreateBucketIfNotExists( - paymentHtlcsBucket, - ) - if err != nil { - return err - } - - sessionKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - var sourcePubKey Vertex - copy(sourcePubKey[:], sessionKey.PubKey().SerializeCompressed()) - - var sessionKeyBytes [32]byte - copy(sessionKeyBytes[:], sessionKey.Serialize()) - - baseAmt := lnwire.MilliSatoshi(100000) - hops := make([]*Hop, 3) - for i := 0; i < 3; i++ { - hopKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - amt := baseAmt - lnwire.MilliSatoshi(uint64(i)*100) - hop := &Hop{ - PubKeyBytes: NewVertex(hopKey.PubKey()), - ChannelID: uint64(700000 + i), - OutgoingTimeLock: uint32(700000 - i*40), - AmtToForward: amt, - } - if features.customRecords { - hop.CustomRecords = record.CustomSet{ - 65562 + uint64(i): []byte(fmt.Sprintf( - "combo_hop_%d", i, - )), - } - } - if features.hopMetadata { - hop.Metadata = []byte( - fmt.Sprintf("combo_metadata_%d", i), - ) - } - - if i == 2 { - if features.mpp { - var paymentAddr [32]byte - copy( - paymentAddr[:], - []byte("combo_mpp_payment_addr_1234"), - ) - hop.MPP = record.NewMPP( - baseAmt-200, paymentAddr, - ) - } - if features.amp { - var rootShare [32]byte - copy( - rootShare[:], - []byte("combo_amp_root_share_123456"), - ) - var setID [32]byte - copy( - setID[:], - []byte("combo_amp_set_id_12345678"), - ) - hop.AMP = record.NewAMP(rootShare, setID, 0) - } - if features.blindedRoute { - blindingKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - hop.BlindingPoint = blindingKey.PubKey() - hop.EncryptedData = []byte( - "combo_encrypted_blinded_data", - ) - hop.TotalAmtMsat = baseAmt - 200 - } - } - - hops[i] = hop - } - - routeInfo := Route{ - TotalTimeLock: 700000, - TotalAmount: baseAmt - 200, - SourcePubKey: sourcePubKey, - Hops: hops, - } - if features.customRecords { - routeInfo.FirstHopWireCustomRecords = lnwire.CustomRecords{ - 65565: []byte("combo_attempt_custom_1"), - 65566: []byte("combo_attempt_custom_2"), - } - } - - *globalAttemptID++ - attemptID := *globalAttemptID - attemptInfo := &HTLCAttemptInfo{ - AttemptID: attemptID, - sessionKey: sessionKeyBytes, - Route: routeInfo, - AttemptTime: creationTime.Add(time.Minute), - Hash: (*lntypes.Hash)(&hash), - } - - attemptKey := make([]byte, len(htlcAttemptInfoKey)+8) - copy(attemptKey, htlcAttemptInfoKey) - byteOrder.PutUint64(attemptKey[len(htlcAttemptInfoKey):], attemptID) - - var ab bytes.Buffer - err = serializeHTLCAttemptInfo(&ab, attemptInfo) - if err != nil { - return err - } - err = htlcBucket.Put(attemptKey, ab.Bytes()) - if err != nil { - return err - } - - settleInfo := &HTLCSettleInfo{ - Preimage: lntypes.Preimage(hash), - SettleTime: creationTime.Add(2 * time.Minute), - } - - settleKey := make([]byte, len(htlcSettleInfoKey)+8) - copy(settleKey, htlcSettleInfoKey) - byteOrder.PutUint64(settleKey[len(htlcSettleInfoKey):], attemptID) - - var sb bytes.Buffer - err = serializeHTLCSettleInfo(&sb, settleInfo) - if err != nil { - return err - } - err = htlcBucket.Put(settleKey, sb.Bytes()) - if err != nil { - return err - } - - var idx bytes.Buffer - err = WriteElements(&idx, paymentIndexTypeHash, hash[:]) - if err != nil { - return err - } - - return indexBucket.Put(seqBytes, idx.Bytes()) -} - -// TestMigrateInFlightPayment tests that a payment with an active (in-flight) -// HTLC attempt, such as a node that upgrades while a payment is still pending -// on the network, is migrated correctly. The HTLC attempt must appear in SQL -// without a settlement or failure resolution, and FetchInFlightPayments must -// return the payment after migration. -func TestMigrateInFlightPayment(t *testing.T) { - t.Parallel() - - ctx := context.Background() - kvDB := setupTestKVDB(t) - - var paymentHash [32]byte - copy(paymentHash[:], []byte("test_inflight_payment_hash_1234")) - - err := kvdb.Update(kvDB, func(tx kvdb.RwTx) error { - paymentsBucket, err := tx.CreateTopLevelBucket( - paymentsRootBucket, - ) - if err != nil { - return err - } - - indexBucket, err := tx.CreateTopLevelBucket( - paymentsIndexBucket, - ) - if err != nil { - return err - } - - return createInFlightPayment( - t, paymentsBucket, indexBucket, paymentHash, - ) - }, func() {}) - require.NoError(t, err) - - sqlStore := setupTestSQLDB(t) - - err = runPaymentsMigration(ctx, kvDB, sqlStore) - require.NoError(t, err) - - // Verify the payment data matches between KV and SQL. - assertPaymentDataMatches(t, ctx, kvDB, sqlStore, paymentHash) - - // Additionally verify that FetchInFlightPayments returns this payment, - // confirming it is treated as in-flight by the SQL store. - inFlight, err := sqlStore.FetchInFlightPayments(ctx) - require.NoError(t, err) - require.Len(t, inFlight, 1) - - var paymentID lntypes.Hash - copy(paymentID[:], paymentHash[:]) - require.Equal(t, paymentID, inFlight[0].Info.PaymentIdentifier) - require.Equal(t, StatusInFlight, inFlight[0].Status) - require.Len(t, inFlight[0].HTLCs, 1) - require.Nil(t, inFlight[0].HTLCs[0].Settle) - require.Nil(t, inFlight[0].HTLCs[0].Failure) -} - -// createInFlightPayment creates a payment with an active (in-flight) HTLC -// attempt in the KV store. Unlike settled payments, no settle or fail -// resolution is written for the HTLC, simulating a payment that is still -// pending on the network at the time of migration. -func createInFlightPayment(t *testing.T, paymentsBucket, - indexBucket kvdb.RwBucket, hash [32]byte) error { - - return createInFlightPaymentWithAttemptID( - t, paymentsBucket, indexBucket, hash, 1, - ) -} - -func createInFlightPaymentWithAttemptID(t *testing.T, paymentsBucket, - indexBucket kvdb.RwBucket, hash [32]byte, attemptID uint64) error { - - t.Helper() - - paymentBucket, err := paymentsBucket.CreateBucketIfNotExists(hash[:]) - if err != nil { - return err - } - - var paymentID lntypes.Hash - copy(paymentID[:], hash[:]) - - creationInfo := &PaymentCreationInfo{ - PaymentIdentifier: paymentID, - Value: lnwire.MilliSatoshi(100000), - CreationTime: time.Now().Add(-1 * time.Hour), - PaymentRequest: []byte("lnbc1inflight_payment"), - } - - err = serializeAndPut( - paymentBucket, paymentCreationInfoKey, - func(w io.Writer) error { - return serializePaymentCreationInfo(w, creationInfo) - }, - ) - if err != nil { - return err - } - - seqBytes := make([]byte, 8) - byteOrder.PutUint64(seqBytes, 42) - if err := paymentBucket.Put(paymentSequenceKey, seqBytes); err != nil { - return err - } - - htlcBucket, err := paymentBucket.CreateBucketIfNotExists( - paymentHtlcsBucket, - ) - if err != nil { - return err - } - - _, sourcePubKey, sessionKeyBytes, err := generateSessionKey(t) - if err != nil { - return err - } - - hopKey, err := btcec.NewPrivateKey() - if err != nil { - return err - } - - attemptInfo := &HTLCAttemptInfo{ - AttemptID: attemptID, - sessionKey: sessionKeyBytes, - Route: Route{ - TotalTimeLock: 500000, - TotalAmount: 100000, - SourcePubKey: sourcePubKey, - Hops: []*Hop{ - { - PubKeyBytes: NewVertex( - hopKey.PubKey(), - ), - ChannelID: 12345, - OutgoingTimeLock: 499500, - AmtToForward: 99000, - }, - }, - }, - AttemptTime: time.Now().Add(-30 * time.Minute), - Hash: (*lntypes.Hash)(&hash), - } - - if err = writeHTLCAttempt(htlcBucket, attemptID, attemptInfo); err != nil { - return err - } - - return createIndexEntry(indexBucket, seqBytes, hash) -} diff --git a/payments/db/migration1/sql_store.go b/payments/db/migration1/sql_store.go deleted file mode 100644 index d984ba9c1..000000000 --- a/payments/db/migration1/sql_store.go +++ /dev/null @@ -1,2029 +0,0 @@ -package migration1 - -import ( - "bytes" - "context" - "database/sql" - "errors" - "fmt" - "math" - "sort" - "strconv" - "time" - - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire" - "github.com/lightningnetwork/lnd/payments/db/migration1/sqlc" - "github.com/lightningnetwork/lnd/sqldb" -) - -// PaymentIntentType represents the type of payment intent. -type PaymentIntentType int16 - -const ( - // PaymentIntentTypeBolt11 indicates a BOLT11 invoice payment. - PaymentIntentTypeBolt11 PaymentIntentType = 0 -) - -// HTLCAttemptResolutionType represents the type of HTLC attempt resolution. -type HTLCAttemptResolutionType int32 - -const ( - // HTLCAttemptResolutionSettled indicates the HTLC attempt was settled - // successfully with a preimage. - HTLCAttemptResolutionSettled HTLCAttemptResolutionType = 1 - - // HTLCAttemptResolutionFailed indicates the HTLC attempt failed. - HTLCAttemptResolutionFailed HTLCAttemptResolutionType = 2 -) - -// SQLQueries is a subset of the sqlc.Querier interface that can be used to -// execute queries against the SQL payments tables. -// -//nolint:ll,interfacebloat -type SQLQueries interface { - /* - Payment DB read operations. - */ - FilterPayments(ctx context.Context, query sqlc.FilterPaymentsParams) ([]sqlc.FilterPaymentsRow, error) - FilterPaymentsDesc(ctx context.Context, query sqlc.FilterPaymentsDescParams) ([]sqlc.FilterPaymentsDescRow, error) - FetchPayment(ctx context.Context, paymentIdentifier []byte) (sqlc.FetchPaymentRow, error) - FetchPaymentsByIDs(ctx context.Context, paymentIDs []int64) ([]sqlc.FetchPaymentsByIDsRow, error) - - CountPayments(ctx context.Context) (int64, error) - - FetchHtlcAttemptsForPayments(ctx context.Context, paymentIDs []int64) ([]sqlc.FetchHtlcAttemptsForPaymentsRow, error) - FetchHtlcAttemptResolutionsForPayments(ctx context.Context, paymentIDs []int64) ([]sqlc.FetchHtlcAttemptResolutionsForPaymentsRow, error) - FetchAllInflightAttempts(ctx context.Context, arg sqlc.FetchAllInflightAttemptsParams) ([]sqlc.PaymentHtlcAttempt, error) - FetchHopsForAttempts(ctx context.Context, htlcAttemptIndices []int64) ([]sqlc.FetchHopsForAttemptsRow, error) - - FetchPaymentDuplicates(ctx context.Context, paymentID int64) ([]sqlc.PaymentDuplicate, error) - - FetchPaymentLevelFirstHopCustomRecords(ctx context.Context, paymentIDs []int64) ([]sqlc.PaymentFirstHopCustomRecord, error) - FetchRouteLevelFirstHopCustomRecords(ctx context.Context, htlcAttemptIndices []int64) ([]sqlc.PaymentAttemptFirstHopCustomRecord, error) - FetchHopLevelCustomRecords(ctx context.Context, hopIDs []int64) ([]sqlc.PaymentHopCustomRecord, error) - - /* - Payment DB write operations. - */ - InsertPaymentIntent(ctx context.Context, arg sqlc.InsertPaymentIntentParams) (int64, error) - InsertPayment(ctx context.Context, arg sqlc.InsertPaymentParams) (int64, error) - InsertPaymentFirstHopCustomRecord(ctx context.Context, arg sqlc.InsertPaymentFirstHopCustomRecordParams) error - - InsertHtlcAttempt(ctx context.Context, arg sqlc.InsertHtlcAttemptParams) (int64, error) - InsertRouteHop(ctx context.Context, arg sqlc.InsertRouteHopParams) (int64, error) - InsertRouteHopMpp(ctx context.Context, arg sqlc.InsertRouteHopMppParams) error - InsertRouteHopAmp(ctx context.Context, arg sqlc.InsertRouteHopAmpParams) error - InsertRouteHopBlinded(ctx context.Context, arg sqlc.InsertRouteHopBlindedParams) error - - InsertPaymentAttemptFirstHopCustomRecord(ctx context.Context, arg sqlc.InsertPaymentAttemptFirstHopCustomRecordParams) error - InsertPaymentHopCustomRecord(ctx context.Context, arg sqlc.InsertPaymentHopCustomRecordParams) error - - SettleAttempt(ctx context.Context, arg sqlc.SettleAttemptParams) error - FailAttempt(ctx context.Context, arg sqlc.FailAttemptParams) error - - FailPayment(ctx context.Context, arg sqlc.FailPaymentParams) (sql.Result, error) - - DeletePayment(ctx context.Context, paymentID int64) error - - // DeleteFailedAttempts removes all failed HTLCs from the db for a - // given payment. - DeleteFailedAttempts(ctx context.Context, paymentID int64) error -} - -// SQLMigrationQueries extends SQLQueries with the additional queries needed -// for the one-time migration from KV to SQL. Keeping them in a separate -// interface makes it clear which code paths are migration-only and prevents -// the regular store from accidentally depending on them. -// -//nolint:ll -type SQLMigrationQueries interface { - SQLQueries - - // FetchPaymentsByIDsMig is a migration-only batch fetch that returns - // payment data along with HTLC attempt counts for structural - // validation. - FetchPaymentsByIDsMig(ctx context.Context, paymentIDs []int64) ([]sqlc.FetchPaymentsByIDsMigRow, error) - - // InsertPaymentMig is a migration-only variant of InsertPayment that - // allows setting fail_reason when inserting historical payments, since - // for real payments they have not failed at creation time and so no - // failure reason would exist yet. - InsertPaymentMig(ctx context.Context, arg sqlc.InsertPaymentMigParams) (int64, error) - - // InsertPaymentDuplicateMig inserts a duplicate payment record during - // migration. - InsertPaymentDuplicateMig(ctx context.Context, arg sqlc.InsertPaymentDuplicateMigParams) (int64, error) -} - -// BatchedSQLQueries is a version of the SQLQueries that's capable -// of batched database operations. -type BatchedSQLQueries interface { - SQLQueries - sqldb.BatchedTx[SQLQueries] -} - -// SQLStore represents a storage backend. -type SQLStore struct { - cfg *SQLStoreConfig - db BatchedSQLQueries -} - -// A compile-time constraint to ensure SQLStore implements DB. -var _ DB = (*SQLStore)(nil) - -// SQLStoreConfig holds the configuration for the SQLStore. -type SQLStoreConfig struct { - // QueryConfig holds configuration values for SQL queries. - QueryCfg *sqldb.QueryConfig -} - -// NewSQLStore creates a new SQLStore instance given an open -// BatchedSQLQueries storage backend. -func NewSQLStore(cfg *SQLStoreConfig, db BatchedSQLQueries, - options ...OptionModifier) (*SQLStore, error) { - - opts := DefaultOptions() - for _, applyOption := range options { - applyOption(opts) - } - - if opts.NoMigration { - return nil, fmt.Errorf("the NoMigration option is not yet " + - "supported for SQL stores") - } - - return &SQLStore{ - cfg: cfg, - db: db, - }, nil -} - -// fetchPaymentWithCompleteData fetches a payment with all its related data -// including attempts, hops, and custom records from the database. -// This is a convenience wrapper around the batch loading functions for single -// payment operations. -func fetchPaymentWithCompleteData(ctx context.Context, - cfg *sqldb.QueryConfig, db SQLQueries, - dbPayment sqlc.PaymentAndIntent) (*MPPayment, error) { - - payment := dbPayment.GetPayment() - - // Load batch data for this single payment. - batchData, err := batchLoadPaymentDetailsData( - ctx, cfg, db, []int64{payment.ID}, - ) - if err != nil { - return nil, fmt.Errorf("failed to load batch data: %w", err) - } - - // Build the payment from the batch data. - return buildPaymentFromBatchData(dbPayment, batchData) -} - -// paymentsCompleteData holds the full payment data when batch loading base -// payment data and all the related data for a payment. -type paymentsCompleteData struct { - *paymentsBaseData - *paymentsDetailsData -} - -// batchLoadPayments loads the full payment data for a batch of payment IDs. -func batchLoadPayments(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, paymentIDs []int64) (*paymentsCompleteData, error) { - - baseData, err := batchLoadpaymentsBaseData(ctx, cfg, db, paymentIDs) - if err != nil { - return nil, fmt.Errorf("failed to load payment base data: %w", - err) - } - - batchData, err := batchLoadPaymentDetailsData(ctx, cfg, db, paymentIDs) - if err != nil { - return nil, fmt.Errorf("failed to load payment batch data: %w", - err) - } - - return &paymentsCompleteData{ - paymentsBaseData: baseData, - paymentsDetailsData: batchData, - }, nil -} - -// paymentsBaseData holds the base payment and intent data for a batch of -// payments. -type paymentsBaseData struct { - // paymentsAndIntents maps payment ID to its payment and intent data. - paymentsAndIntents map[int64]sqlc.PaymentAndIntent -} - -// batchLoadpaymentsBaseData loads the base payment and payment intent data for -// a batch of payment IDs. This complements loadPaymentsBatchData which loads -// related data (attempts, hops, custom records) but not the payment table -// and payment intent table data. -func batchLoadpaymentsBaseData(ctx context.Context, - cfg *sqldb.QueryConfig, db SQLQueries, - paymentIDs []int64) (*paymentsBaseData, error) { - - baseData := &paymentsBaseData{ - paymentsAndIntents: make(map[int64]sqlc.PaymentAndIntent), - } - - if len(paymentIDs) == 0 { - return baseData, nil - } - - err := sqldb.ExecuteBatchQuery( - ctx, cfg, paymentIDs, - func(id int64) int64 { return id }, - func(ctx context.Context, ids []int64) ( - []sqlc.FetchPaymentsByIDsRow, error) { - - records, err := db.FetchPaymentsByIDs( - ctx, ids, - ) - - return records, err - }, - func(ctx context.Context, - payment sqlc.FetchPaymentsByIDsRow) error { - - baseData.paymentsAndIntents[payment.ID] = payment - - return nil - }, - ) - if err != nil { - return nil, fmt.Errorf("failed to fetch payment base "+ - "data: %w", err) - } - - return baseData, nil -} - -// paymentsRelatedData holds all the batch-loaded data for multiple payments. -// This does not include the base payment and intent data which is fetched -// separately. It includes the additional data like attempts, hops, hop custom -// records, and route custom records. -type paymentsDetailsData struct { - // paymentCustomRecords maps payment ID to its custom records. - paymentCustomRecords map[int64][]sqlc.PaymentFirstHopCustomRecord - - // attempts maps payment ID to its HTLC attempts. - attempts map[int64][]sqlc.FetchHtlcAttemptsForPaymentsRow - - // hopsByAttempt maps attempt index to its hops. - hopsByAttempt map[int64][]sqlc.FetchHopsForAttemptsRow - - // hopCustomRecords maps hop ID to its custom records. - hopCustomRecords map[int64][]sqlc.PaymentHopCustomRecord - - // routeCustomRecords maps attempt index to its route-level custom - // records. - routeCustomRecords map[int64][]sqlc.PaymentAttemptFirstHopCustomRecord -} - -// batchLoadPaymentCustomRecords loads payment-level custom records for a given -// set of payment IDs. It uses a batch query to fetch all custom records for -// the given payment IDs. -func batchLoadPaymentCustomRecords(ctx context.Context, - cfg *sqldb.QueryConfig, db SQLQueries, paymentIDs []int64, - batchData *paymentsDetailsData) error { - - return sqldb.ExecuteBatchQuery( - ctx, cfg, paymentIDs, - func(id int64) int64 { return id }, - func(ctx context.Context, ids []int64) ( - []sqlc.PaymentFirstHopCustomRecord, error) { - - //nolint:ll - records, err := db.FetchPaymentLevelFirstHopCustomRecords( - ctx, ids, - ) - - return records, err - }, - func(ctx context.Context, - record sqlc.PaymentFirstHopCustomRecord) error { - - paymentRecords := - batchData.paymentCustomRecords[record.PaymentID] - - batchData.paymentCustomRecords[record.PaymentID] = - append(paymentRecords, record) - - return nil - }, - ) -} - -// batchLoadHtlcAttempts loads HTLC attempts for all payments and returns all -// attempt indices. It uses a batch query to fetch all attempts for the given -// payment IDs. -func batchLoadHtlcAttempts(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, paymentIDs []int64, - batchData *paymentsDetailsData) ([]int64, error) { - - var allAttemptIndices []int64 - - err := sqldb.ExecuteBatchQuery( - ctx, cfg, paymentIDs, - func(id int64) int64 { return id }, - func(ctx context.Context, ids []int64) ( - []sqlc.FetchHtlcAttemptsForPaymentsRow, error) { - - return db.FetchHtlcAttemptsForPayments(ctx, ids) - }, - func(ctx context.Context, - attempt sqlc.FetchHtlcAttemptsForPaymentsRow) error { - - batchData.attempts[attempt.PaymentID] = append( - batchData.attempts[attempt.PaymentID], attempt, - ) - allAttemptIndices = append( - allAttemptIndices, attempt.AttemptIndex, - ) - - return nil - }, - ) - - return allAttemptIndices, err -} - -// batchLoadHopsForAttempts loads hops for all attempts and returns all hop IDs. -// It uses a batch query to fetch all hops for the given attempt indices. -func batchLoadHopsForAttempts(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, attemptIndices []int64, - batchData *paymentsDetailsData) ([]int64, error) { - - var hopIDs []int64 - - err := sqldb.ExecuteBatchQuery( - ctx, cfg, attemptIndices, - func(idx int64) int64 { return idx }, - func(ctx context.Context, indices []int64) ( - []sqlc.FetchHopsForAttemptsRow, error) { - - return db.FetchHopsForAttempts(ctx, indices) - }, - func(ctx context.Context, - hop sqlc.FetchHopsForAttemptsRow) error { - - attemptHops := - batchData.hopsByAttempt[hop.HtlcAttemptIndex] - - batchData.hopsByAttempt[hop.HtlcAttemptIndex] = - append(attemptHops, hop) - - hopIDs = append(hopIDs, hop.ID) - - return nil - }, - ) - - return hopIDs, err -} - -// batchLoadHopCustomRecords loads hop-level custom records for all hops. It -// uses a batch query to fetch all custom records for the given hop IDs. -func batchLoadHopCustomRecords(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, hopIDs []int64, batchData *paymentsDetailsData) error { - - return sqldb.ExecuteBatchQuery( - ctx, cfg, hopIDs, - func(id int64) int64 { return id }, - func(ctx context.Context, ids []int64) ( - []sqlc.PaymentHopCustomRecord, error) { - - return db.FetchHopLevelCustomRecords(ctx, ids) - }, - func(ctx context.Context, - record sqlc.PaymentHopCustomRecord) error { - - // TODO(ziggie): Can we get rid of this? - // This has to be in place otherwise the - // comparison will not match. - if record.Value == nil { - record.Value = []byte{} - } - - batchData.hopCustomRecords[record.HopID] = append( - batchData.hopCustomRecords[record.HopID], - record, - ) - - return nil - }, - ) -} - -// batchLoadRouteCustomRecords loads route-level first hop custom records for -// all attempts. It uses a batch query to fetch all custom records for the given -// attempt indices. -func batchLoadRouteCustomRecords(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, attemptIndices []int64, - batchData *paymentsDetailsData) error { - - return sqldb.ExecuteBatchQuery( - ctx, cfg, attemptIndices, - func(idx int64) int64 { return idx }, - func(ctx context.Context, indices []int64) ( - []sqlc.PaymentAttemptFirstHopCustomRecord, error) { - - return db.FetchRouteLevelFirstHopCustomRecords( - ctx, indices, - ) - }, - func(ctx context.Context, - record sqlc.PaymentAttemptFirstHopCustomRecord) error { - - idx := record.HtlcAttemptIndex - attemptRecords := batchData.routeCustomRecords[idx] - - batchData.routeCustomRecords[idx] = - append(attemptRecords, record) - - return nil - }, - ) -} - -// paymentStatusData holds lightweight resolution data for computing -// payment status efficiently during deletion operations. -type paymentStatusData struct { - // resolutionTypes maps payment ID to a list of resolution types - // for that payment's HTLC attempts. - resolutionTypes map[int64][]sql.NullInt32 -} - -// batchLoadPaymentResolutions loads only HTLC resolution types for multiple -// payments. This is a lightweight alternative to batchLoadPaymentsRelatedData -// that's optimized for operations that only need to determine payment status. -func batchLoadPaymentResolutions(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, paymentIDs []int64) (*paymentStatusData, error) { - - batchStatusData := &paymentStatusData{ - resolutionTypes: make(map[int64][]sql.NullInt32), - } - - if len(paymentIDs) == 0 { - return batchStatusData, nil - } - - // Use a batch query to fetch all resolution types for the given payment - // IDs. - err := sqldb.ExecuteBatchQuery( - ctx, cfg, paymentIDs, - func(id int64) int64 { return id }, - func(ctx context.Context, ids []int64) ( - []sqlc.FetchHtlcAttemptResolutionsForPaymentsRow, - error) { - - return db.FetchHtlcAttemptResolutionsForPayments( - ctx, ids, - ) - }, - //nolint:ll - func(ctx context.Context, - res sqlc.FetchHtlcAttemptResolutionsForPaymentsRow) error { - - // Group resolutions by payment ID. - batchStatusData.resolutionTypes[res.PaymentID] = append( - batchStatusData.resolutionTypes[res.PaymentID], - res.ResolutionType, - ) - - return nil - }, - ) - if err != nil { - return nil, fmt.Errorf("failed to fetch HTLC resolutions: %w", - err) - } - - return batchStatusData, nil -} - -// loadPaymentResolutions is a single-payment wrapper around -// batchLoadPaymentResolutions for convenience and to prevent duplicate queries -// so we reuse the same batch query for all payments. -func loadPaymentResolutions(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, paymentID int64) ([]sql.NullInt32, error) { - - batchData, err := batchLoadPaymentResolutions( - ctx, cfg, db, []int64{paymentID}, - ) - if err != nil { - return nil, err - } - - return batchData.resolutionTypes[paymentID], nil -} - -// computePaymentStatusFromResolutions determines the payment status from -// resolution types and failure reason without building the complete MPPayment -// structure. This is a lightweight version that builds minimal HTLCAttempt -// structures and delegates to decidePaymentStatus for consistency. -func computePaymentStatusFromResolutions(resolutionTypes []sql.NullInt32, - failReason sql.NullInt32) (PaymentStatus, error) { - - // Build minimal HTLCAttempt slice with only resolution info. - htlcs := make([]HTLCAttempt, len(resolutionTypes)) - for i, resType := range resolutionTypes { - if !resType.Valid { - // NULL resolution_type means in-flight (no Settle, no - // Failure). - continue - } - - switch HTLCAttemptResolutionType(resType.Int32) { - case HTLCAttemptResolutionSettled: - // Mark as settled (preimage details not needed for - // status). - htlcs[i].Settle = &HTLCSettleInfo{} - - case HTLCAttemptResolutionFailed: - // Mark as failed (failure details not needed for - // status). - htlcs[i].Failure = &HTLCFailInfo{} - - default: - return 0, fmt.Errorf("unknown resolution type: %v", - resType.Int32) - } - } - - // Convert fail reason to FailureReason pointer. - var failureReason *FailureReason - if failReason.Valid { - reason := FailureReason(failReason.Int32) - failureReason = &reason - } - - // Use the existing status decision logic. - return decidePaymentStatus(htlcs, failureReason) -} - -// batchLoadPaymentDetailsData loads all related data for multiple payments in -// batch. It uses a batch queries to fetch all data for the given payment IDs. -func batchLoadPaymentDetailsData(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, paymentIDs []int64) (*paymentsDetailsData, error) { - - batchData := &paymentsDetailsData{ - paymentCustomRecords: make( - map[int64][]sqlc.PaymentFirstHopCustomRecord, - ), - attempts: make( - map[int64][]sqlc.FetchHtlcAttemptsForPaymentsRow, - ), - hopsByAttempt: make( - map[int64][]sqlc.FetchHopsForAttemptsRow, - ), - hopCustomRecords: make( - map[int64][]sqlc.PaymentHopCustomRecord, - ), - routeCustomRecords: make( - map[int64][]sqlc.PaymentAttemptFirstHopCustomRecord, - ), - } - - if len(paymentIDs) == 0 { - return batchData, nil - } - - // Load payment-level custom records. - err := batchLoadPaymentCustomRecords( - ctx, cfg, db, paymentIDs, batchData, - ) - if err != nil { - return nil, fmt.Errorf("failed to fetch payment custom "+ - "records: %w", err) - } - - // Load HTLC attempts and collect attempt indices. - allAttemptIndices, err := batchLoadHtlcAttempts( - ctx, cfg, db, paymentIDs, batchData, - ) - if err != nil { - return nil, fmt.Errorf("failed to fetch HTLC attempts: %w", - err) - } - - if len(allAttemptIndices) == 0 { - // No attempts, return early. - return batchData, nil - } - - // Load hops for all attempts and collect hop IDs. - hopIDs, err := batchLoadHopsForAttempts( - ctx, cfg, db, allAttemptIndices, batchData, - ) - if err != nil { - return nil, fmt.Errorf("failed to fetch hops for attempts: %w", - err) - } - - // Load hop-level custom records if there are any hops. - if len(hopIDs) > 0 { - err = batchLoadHopCustomRecords(ctx, cfg, db, hopIDs, batchData) - if err != nil { - return nil, fmt.Errorf("failed to fetch hop custom "+ - "records: %w", err) - } - } - - // Load route-level first hop custom records. - err = batchLoadRouteCustomRecords( - ctx, cfg, db, allAttemptIndices, batchData, - ) - if err != nil { - return nil, fmt.Errorf("failed to fetch route custom "+ - "records: %w", err) - } - - return batchData, nil -} - -// buildPaymentFromBatchData builds a complete MPPayment from a database payment -// and pre-loaded batch data. -func buildPaymentFromBatchData(dbPayment sqlc.PaymentAndIntent, - batchData *paymentsDetailsData) (*MPPayment, error) { - - // The query will only return BOLT 11 payment intents or intents with - // no intent type set. - paymentIntent := dbPayment.GetPaymentIntent() - paymentRequest := paymentIntent.IntentPayload - - payment := dbPayment.GetPayment() - - // Get payment-level custom records from batch data. - customRecords := batchData.paymentCustomRecords[payment.ID] - - // Convert to the FirstHopCustomRecords map. - var firstHopCustomRecords lnwire.CustomRecords - if len(customRecords) > 0 { - firstHopCustomRecords = make(lnwire.CustomRecords) - for _, record := range customRecords { - firstHopCustomRecords[uint64(record.Key)] = record.Value - } - } - - // Convert database payment data to the PaymentCreationInfo struct. - info := dbPaymentToCreationInfo( - payment.PaymentIdentifier, payment.AmountMsat, - payment.CreatedAt, paymentRequest, firstHopCustomRecords, - ) - - // Get all HTLC attempts from batch data for a given payment. - dbAttempts := batchData.attempts[payment.ID] - - // Convert all attempts to HTLCAttempt structs using the pre-loaded - // batch data. - attempts := make([]HTLCAttempt, 0, len(dbAttempts)) - for _, dbAttempt := range dbAttempts { - attemptIndex := dbAttempt.AttemptIndex - // Convert the batch row type to the single row type. - attempt, err := dbAttemptToHTLCAttempt( - dbAttempt, batchData.hopsByAttempt[attemptIndex], - batchData.hopCustomRecords, - batchData.routeCustomRecords[attemptIndex], - ) - if err != nil { - return nil, fmt.Errorf("failed to convert attempt "+ - "%d: %w", attemptIndex, err) - } - attempts = append(attempts, *attempt) - } - - // Set the failure reason if present. - // - // TODO(ziggie): Rename it to Payment Memo in the database? - var failureReason *FailureReason - if payment.FailReason.Valid { - reason := FailureReason(payment.FailReason.Int32) - failureReason = &reason - } - - mpPayment := &MPPayment{ - SequenceNum: uint64(payment.ID), - Info: info, - HTLCs: attempts, - FailureReason: failureReason, - } - - // The status and state will be determined by calling - // SetState after construction. - if err := mpPayment.SetState(); err != nil { - return nil, fmt.Errorf("failed to set payment state: %w", err) - } - - return mpPayment, nil -} - -// QueryPayments queries and retrieves payments from the database with support -// for filtering, pagination, and efficient batch loading of related data. -// -// The function accepts a Query parameter that controls: -// - Pagination: IndexOffset specifies where to start (exclusive), and -// MaxPayments limits the number of results returned -// - Ordering: Reversed flag determines if results are returned in reverse -// chronological order -// - Filtering: CreationDateStart/End filter by creation time, and -// IncludeIncomplete controls whether non-succeeded payments are included -// - Metadata: CountTotal flag determines if the total payment count should -// be calculated -// -// The function optimizes performance by loading all related data (HTLCs, -// sequences, failure reasons, etc.) for multiple payments in a single batch -// query, rather than fetching each payment's data individually. -// -// Returns a Response containing: -// - Payments: the list of matching payments with complete data -// - FirstIndexOffset/LastIndexOffset: pagination cursors for the first and -// last payment in the result set -// - TotalCount: total number of payments in the database (if CountTotal was -// requested, otherwise 0) -// -// This is part of the DB interface. -func (s *SQLStore) QueryPayments(ctx context.Context, query Query) (Response, - error) { - - if query.MaxPayments == 0 { - return Response{}, fmt.Errorf("max payments must be non-zero") - } - - var ( - allPayments []*MPPayment - totalCount int64 - initialCursor int64 - ) - - extractCursor := func(row sqlc.FilterPaymentsRow) int64 { - return row.Payment.ID - } - - err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - // We first count all payments to determine the total count - // if requested. - if query.CountTotal { - totalPayments, err := db.CountPayments(ctx) - if err != nil { - return fmt.Errorf("failed to count "+ - "payments: %w", err) - } - totalCount = totalPayments - } - - // collectFunc extracts the payment ID from each payment row. - collectFunc := func(row sqlc.FilterPaymentsRow) (int64, error) { - return row.Payment.ID, nil - } - - // batchDataFunc loads all related data for a batch of payments. - batchDataFunc := func(ctx context.Context, paymentIDs []int64) ( - *paymentsDetailsData, error) { - - return batchLoadPaymentDetailsData( - ctx, s.cfg.QueryCfg, db, paymentIDs, - ) - } - - // processPayment processes each payment with the batch-loaded - // data. - processPayment := func(ctx context.Context, - dbPayment sqlc.FilterPaymentsRow, - batchData *paymentsDetailsData) error { - - // Build the payment from the pre-loaded batch data. - mpPayment, err := buildPaymentFromBatchData( - dbPayment, batchData, - ) - if err != nil { - return fmt.Errorf("failed to fetch payment "+ - "with complete data: %w", err) - } - - // To keep compatibility with the old API, we only - // return non-succeeded payments if requested. - if mpPayment.Status != StatusSucceeded && - !query.IncludeIncomplete { - - return nil - } - - if uint64(len(allPayments)) >= query.MaxPayments { - return errMaxPaymentsReached - } - - allPayments = append(allPayments, mpPayment) - - return nil - } - - //nolint:ll - convertFilterPaymentsDescRows := func( - rows []sqlc.FilterPaymentsDescRow) []sqlc.FilterPaymentsRow { - - out := make([]sqlc.FilterPaymentsRow, len(rows)) - for i, row := range rows { - out[i] = sqlc.FilterPaymentsRow{ - Payment: row.Payment, - IntentType: row.IntentType, - IntentPayload: row.IntentPayload, - } - } - - return out - } - - queryFunc := func(ctx context.Context, lastID int64, - limit int32) ([]sqlc.FilterPaymentsRow, error) { - - // Default date bounds: epoch start and far - // future. These are always provided so the SQL - // query uses simple comparisons instead of - // COALESCE (which causes type mismatch on - // Postgres) or OR-based optional filters (which - // can prevent index usage). - createdAfter := time.Unix(0, 0).UTC() - if query.CreationDateStart != 0 { - createdAfter = time.Unix( - query.CreationDateStart, 0, - ).UTC() - } - - createdBefore := time.Date( - 9999, 12, 31, 23, 59, 59, 0, time.UTC, - ) - if query.CreationDateEnd != 0 { - createdBefore = time.Unix( - query.CreationDateEnd, 0, - ).UTC() - } - - filterParams := sqlc.FilterPaymentsParams{ - NumLimit: limit, - CreatedAfter: createdAfter, - CreatedBefore: createdBefore, - // For now there only BOLT 11 payment intents - // exist. - IntentType: sqldb.SQLInt16( - PaymentIntentTypeBolt11, - ), - } - - if query.Reversed { - filterParams.IndexOffsetLet = sqldb.SQLInt64( - lastID, - ) - } else { - filterParams.IndexOffsetGet = sqldb.SQLInt64( - lastID, - ) - } - - if query.Reversed { - rows, err := db.FilterPaymentsDesc( - ctx, sqlc.FilterPaymentsDescParams( - filterParams, - ), - ) - if err != nil { - return nil, err - } - - return convertFilterPaymentsDescRows(rows), nil - } - - return db.FilterPayments(ctx, filterParams) - } - - if query.Reversed { - if query.IndexOffset == 0 { - initialCursor = int64(math.MaxInt64) - } else { - initialCursor = int64(query.IndexOffset) - } - } else { - initialCursor = int64(query.IndexOffset) - } - - return sqldb.ExecuteCollectAndBatchWithSharedDataQuery( - ctx, s.cfg.QueryCfg, initialCursor, queryFunc, - extractCursor, collectFunc, batchDataFunc, - processPayment, - ) - }, func() { - allPayments = nil - }) - - // We make sure we don't return an error if we reached the maximum - // number of payments. Which is the pagination limit for the query - // itself. - if err != nil && !errors.Is(err, errMaxPaymentsReached) { - return Response{}, fmt.Errorf("failed to query payments: %w", - err) - } - - // Handle case where no payments were found - if len(allPayments) == 0 { - return Response{ - Payments: allPayments, - FirstIndexOffset: 0, - LastIndexOffset: 0, - TotalCount: uint64(totalCount), - }, nil - } - - // If the query was reversed, we need to reverse the payment list - // to match the kvstore behavior and return payments in forward order. - if query.Reversed { - for i, j := 0, len(allPayments)-1; i < j; i, j = i+1, j-1 { - allPayments[i], allPayments[j] = allPayments[j], - allPayments[i] - } - } - - return Response{ - Payments: allPayments, - FirstIndexOffset: allPayments[0].SequenceNum, - LastIndexOffset: allPayments[len(allPayments)-1].SequenceNum, - TotalCount: uint64(totalCount), - }, nil -} - -// fetchPaymentByHash fetches a payment by its hash from the database. It is a -// convenience wrapper around the FetchPayment method and checks for -// no rows error and returns ErrPaymentNotInitiated if no payment is found. -func fetchPaymentByHash(ctx context.Context, db SQLQueries, - paymentHash lntypes.Hash) (sqlc.FetchPaymentRow, error) { - - dbPayment, err := db.FetchPayment(ctx, paymentHash[:]) - if err != nil && !errors.Is(err, sql.ErrNoRows) { - return dbPayment, fmt.Errorf("failed to fetch payment: %w", err) - } - - if errors.Is(err, sql.ErrNoRows) { - return dbPayment, ErrPaymentNotInitiated - } - - return dbPayment, nil -} - -// FetchPayment retrieves a complete payment record from the database by its -// payment hash. The returned MPPayment includes all payment metadata such as -// creation info, payment status, current state, all HTLC attempts (both -// successful and failed), and the failure reason if the payment has been -// marked as failed. -// -// Returns ErrPaymentNotInitiated if no payment with the given hash exists. -// -// This is part of the DB interface. -func (s *SQLStore) FetchPayment(ctx context.Context, - paymentHash lntypes.Hash) (*MPPayment, error) { - - var mpPayment *MPPayment - - err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - dbPayment, err := fetchPaymentByHash(ctx, db, paymentHash) - if err != nil { - return err - } - - mpPayment, err = fetchPaymentWithCompleteData( - ctx, s.cfg.QueryCfg, db, dbPayment, - ) - if err != nil { - return fmt.Errorf("failed to fetch payment with "+ - "complete data: %w", err) - } - - return nil - }, sqldb.NoOpReset) - if err != nil { - return nil, err - } - - return mpPayment, nil -} - -// FetchInFlightPayments retrieves all payments that have HTLC attempts -// currently in flight (not yet settled or failed). These are payments with at -// least one HTLC attempt that has been registered but has no resolution record. -// -// The SQLStore implementation provides a significant performance improvement -// over the KVStore implementation by using targeted SQL queries instead of -// scanning all payments. -// -// This method is part of the PaymentReader interface, which is embedded in the -// DB interface. It's typically called during node startup to resume monitoring -// of pending payments and ensure HTLCs are properly tracked. -// -// TODO(ziggie): Consider changing the interface to use a callback or iterator -// pattern instead of returning all payments at once. This would allow -// processing payments one at a time without holding them all in memory -// simultaneously: -// - Callback: func FetchInFlightPayments(ctx, func(*MPPayment) error) error -// - Iterator: func FetchInFlightPayments(ctx) (PaymentIterator, error) -// -// While inflight payments are typically a small subset, this would improve -// memory efficiency for nodes with unusually high numbers of concurrent -// payments and would better leverage the existing pagination infrastructure. -func (s *SQLStore) FetchInFlightPayments(ctx context.Context) ([]*MPPayment, - error) { - - var mpPayments []*MPPayment - - err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - // Track which payment IDs we've already processed across all - // pages to avoid loading the same payment multiple times when - // multiple inflight attempts belong to the same payment. - processedPayments := make(map[int64]*MPPayment) - - extractCursor := func(row sqlc.PaymentHtlcAttempt) int64 { - return row.AttemptIndex - } - - // collectFunc extracts the payment ID from each attempt row. - collectFunc := func(row sqlc.PaymentHtlcAttempt) ( - int64, error) { - - return row.PaymentID, nil - } - - // batchDataFunc loads payment data for a batch of payment IDs, - // but only for IDs we haven't processed yet. - batchDataFunc := func(ctx context.Context, - paymentIDs []int64) (*paymentsCompleteData, error) { - - // Filter out already-processed payment IDs. - uniqueIDs := make([]int64, 0, len(paymentIDs)) - for _, id := range paymentIDs { - _, processed := processedPayments[id] - if !processed { - uniqueIDs = append(uniqueIDs, id) - } - } - - // If uniqueIDs is empty, the batch load will return - // empty batch data. - return batchLoadPayments( - ctx, s.cfg.QueryCfg, db, uniqueIDs, - ) - } - - // processAttempt processes each attempt. We only build and - // store the payment once per unique payment ID. - processAttempt := func(ctx context.Context, - row sqlc.PaymentHtlcAttempt, - batchData *paymentsCompleteData) error { - - // Skip if we've already processed this payment. - _, processed := processedPayments[row.PaymentID] - if processed { - return nil - } - - dbPayment := batchData.paymentsAndIntents[row.PaymentID] - - // Build the payment from batch data. - mpPayment, err := buildPaymentFromBatchData( - dbPayment, batchData.paymentsDetailsData, - ) - if err != nil { - return fmt.Errorf("failed to build payment: %w", - err) - } - - // Store in our processed map. - processedPayments[row.PaymentID] = mpPayment - - return nil - } - - queryFunc := func(ctx context.Context, lastAttemptIndex int64, - limit int32) ([]sqlc.PaymentHtlcAttempt, - error) { - - return db.FetchAllInflightAttempts(ctx, - sqlc.FetchAllInflightAttemptsParams{ - AttemptIndex: lastAttemptIndex, - Limit: limit, - }, - ) - } - - err := sqldb.ExecuteCollectAndBatchWithSharedDataQuery( - ctx, s.cfg.QueryCfg, int64(-1), queryFunc, - extractCursor, collectFunc, batchDataFunc, - processAttempt, - ) - if err != nil { - return err - } - - // Convert map to slice and sort by sequence number to - // produce a deterministic ordering. - mpPayments = make([]*MPPayment, 0, len(processedPayments)) - for _, payment := range processedPayments { - mpPayments = append(mpPayments, payment) - } - - sort.Slice(mpPayments, func(i, j int) bool { - return mpPayments[i].SequenceNum < - mpPayments[j].SequenceNum - }) - - return nil - }, func() { - mpPayments = nil - }) - if err != nil { - return nil, fmt.Errorf("failed to fetch inflight "+ - "payments: %w", err) - } - - return mpPayments, nil -} - -// DeleteFailedAttempts removes all failed HTLC attempts from the database for -// the specified payment, while preserving the payment record itself and any -// successful or in-flight attempts. -// -// The method performs the following validations before deletion: -// - StatusInitiated: Can delete failed attempts -// - StatusInFlight: Cannot delete, returns ErrPaymentInFlight (active HTLCs -// still on the network) -// - StatusSucceeded: Can delete failed attempts (payment completed) -// - StatusFailed: Can delete failed attempts (payment permanently failed) -// -// This method is idempotent - calling it multiple times on the same payment -// has no adverse effects. -// -// This method is part of the PaymentControl interface, which is embedded in -// the PaymentWriter interface and ultimately the DB interface. It represents -// the final step (step 5) in the payment lifecycle control flow and should be -// called after a payment reaches a terminal state (succeeded or permanently -// failed) to clean up historical failed attempts. -func (s *SQLStore) DeleteFailedAttempts(ctx context.Context, - paymentHash lntypes.Hash) error { - - err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { - dbPayment, err := fetchPaymentByHash(ctx, db, paymentHash) - if err != nil { - return err - } - - paymentStatus, err := computePaymentStatusFromDB( - ctx, s.cfg.QueryCfg, db, dbPayment, - ) - if err != nil { - return fmt.Errorf("failed to compute payment "+ - "status: %w", err) - } - - if err := paymentStatus.removable(); err != nil { - return fmt.Errorf("cannot delete failed "+ - "attempts for payment %v: %w", paymentHash, err) - } - - // Then we delete the failed attempts for this payment. - return db.DeleteFailedAttempts(ctx, dbPayment.GetPayment().ID) - }, sqldb.NoOpReset) - if err != nil { - return fmt.Errorf("failed to delete failed attempts for "+ - "payment %v: %w", paymentHash, err) - } - - return nil -} - -// computePaymentStatusFromDB computes the payment status by fetching minimal -// data from the database. This is a lightweight query optimized for SQL that -// doesn't load route data, making it significantly more efficient than -// FetchPayment when only the status is needed. -func computePaymentStatusFromDB(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, dbPayment sqlc.PaymentAndIntent) (PaymentStatus, error) { - - payment := dbPayment.GetPayment() - - // Load the resolution types for the payment. - resolutionTypes, err := loadPaymentResolutions( - ctx, cfg, db, payment.ID, - ) - if err != nil { - return 0, fmt.Errorf("failed to load payment resolutions: %w", - err) - } - - // Use the lightweight status computation. - status, err := computePaymentStatusFromResolutions( - resolutionTypes, payment.FailReason, - ) - if err != nil { - return 0, fmt.Errorf("failed to compute payment status: %w", - err) - } - - return status, nil -} - -// DeletePayment removes a payment or its failed HTLC attempts from the -// database based on the failedAttemptsOnly flag. -// -// If failedAttemptsOnly is true, this method deletes only the failed HTLC -// attempts for the payment while preserving the payment record itself and any -// successful or in-flight attempts. This is useful for cleaning up historical -// failed attempts after a payment reaches a terminal state. -// -// If failedAttemptsOnly is false, this method deletes the entire payment -// record including all payment metadata, payment creation info, all HTLC -// attempts (both failed and successful), and associated data such as payment -// intents and custom records. -// -// Before deletion, this method validates the payment status to ensure it's -// safe to delete: -// - StatusInitiated: Can be deleted (no HTLCs sent yet) -// - StatusInFlight: Cannot be deleted, returns ErrPaymentInFlight (active -// HTLCs on the network) -// - StatusSucceeded: Can be deleted (payment completed successfully) -// - StatusFailed: Can be deleted (payment has failed permanently) -// -// Returns an error if the payment has in-flight HTLCs or if the payment -// doesn't exist. -// -// This method is part of the PaymentWriter interface, which is embedded in -// the DB interface. -func (s *SQLStore) DeletePayment(ctx context.Context, paymentHash lntypes.Hash, - failedHtlcsOnly bool) error { - - err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { - dbPayment, err := fetchPaymentByHash(ctx, db, paymentHash) - if err != nil { - return err - } - - paymentStatus, err := computePaymentStatusFromDB( - ctx, s.cfg.QueryCfg, db, dbPayment, - ) - if err != nil { - return fmt.Errorf("failed to compute payment "+ - "status: %w", err) - } - - if err := paymentStatus.removable(); err != nil { - return fmt.Errorf("payment %v cannot be deleted: %w", - paymentHash, err) - } - - // If we are only deleting failed HTLCs, we delete them. - if failedHtlcsOnly { - return db.DeleteFailedAttempts( - ctx, dbPayment.GetPayment().ID, - ) - } - - // In case we are not deleting failed HTLCs, we delete the - // payment which will cascade delete all related data. - return db.DeletePayment(ctx, dbPayment.GetPayment().ID) - }, sqldb.NoOpReset) - if err != nil { - return fmt.Errorf("failed to delete failed attempts for "+ - "payment %v: %w", paymentHash, err) - } - - return nil -} - -// InitPayment creates a new payment record in the database with the given -// payment hash and creation info. -// -// Before creating the payment, this method checks if a payment with the same -// hash already exists and validates whether initialization is allowed based on -// the existing payment's status: -// - StatusInitiated: Returns ErrPaymentExists (payment already created, -// HTLCs may be in flight) -// - StatusInFlight: Returns ErrPaymentInFlight (payment currently being -// attempted) -// - StatusSucceeded: Returns ErrAlreadyPaid (payment already succeeded) -// - StatusFailed: Allows retry by deleting the old payment record and -// creating a new one -// -// If no existing payment is found, a new payment record is created with -// StatusInitiated and stored with all associated metadata. -// -// This method is part of the PaymentControl interface, which is embedded in -// the PaymentWriter interface and ultimately the DB interface, representing -// the first step in the payment lifecycle control flow. -func (s *SQLStore) InitPayment(ctx context.Context, paymentHash lntypes.Hash, - paymentCreationInfo *PaymentCreationInfo) error { - - // Create the payment in the database. - err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { - existingPayment, err := db.FetchPayment(ctx, paymentHash[:]) - switch { - // A payment with this hash already exists. We need to check its - // status to see if we can re-initialize. - case err == nil: - paymentStatus, err := computePaymentStatusFromDB( - ctx, s.cfg.QueryCfg, db, existingPayment, - ) - if err != nil { - return fmt.Errorf("failed to compute payment "+ - "status: %w", err) - } - - // Check if the payment is initializable otherwise - // we'll return early. - if err := paymentStatus.initializable(); err != nil { - return fmt.Errorf("payment is not "+ - "initializable: %w", err) - } - - // If the initializable check above passes, then the - // existing payment has failed. So we delete it and - // all of its previous artifacts. We rely on - // cascading deletes to clean up the rest. - err = db.DeletePayment(ctx, existingPayment.Payment.ID) - if err != nil { - return fmt.Errorf("failed to delete "+ - "payment: %w", err) - } - - // An unexpected error occurred while fetching the payment. - case !errors.Is(err, sql.ErrNoRows): - // Some other error occurred - return fmt.Errorf("failed to check existing "+ - "payment: %w", err) - - // The payment does not yet exist, so we can proceed. - default: - } - - // Insert the payment first to get its ID. - paymentID, err := db.InsertPayment( - ctx, sqlc.InsertPaymentParams{ - AmountMsat: int64( - paymentCreationInfo.Value, - ), - CreatedAt: paymentCreationInfo. - CreationTime.UTC(), - PaymentIdentifier: paymentHash[:], - }, - ) - if err != nil { - return fmt.Errorf("failed to insert payment: %w", err) - } - - // If there's a payment request, insert the payment intent. - if len(paymentCreationInfo.PaymentRequest) > 0 { - _, err = db.InsertPaymentIntent( - ctx, sqlc.InsertPaymentIntentParams{ - PaymentID: paymentID, - IntentType: int16( - PaymentIntentTypeBolt11, - ), - IntentPayload: paymentCreationInfo. - PaymentRequest, - }, - ) - if err != nil { - return fmt.Errorf("failed to insert "+ - "payment intent: %w", err) - } - } - - firstHopCustomRecords := paymentCreationInfo. - FirstHopCustomRecords - - for key, value := range firstHopCustomRecords { - err = db.InsertPaymentFirstHopCustomRecord( - ctx, - sqlc.InsertPaymentFirstHopCustomRecordParams{ - PaymentID: paymentID, - Key: int64(key), - Value: value, - }, - ) - if err != nil { - return fmt.Errorf("failed to insert "+ - "payment first hop custom "+ - "record: %w", err) - } - } - - return nil - }, sqldb.NoOpReset) - if err != nil { - return fmt.Errorf("failed to initialize payment: %w", err) - } - - return nil -} - -// insertRouteHops inserts all route hop data for a given set of hops. -func (s *SQLStore) insertRouteHops(ctx context.Context, db SQLQueries, - hops []*Hop, attemptID uint64) error { - - for i, hop := range hops { - // Insert the basic route hop data and get the generated ID. - hopID, err := db.InsertRouteHop(ctx, sqlc.InsertRouteHopParams{ - HtlcAttemptIndex: int64(attemptID), - HopIndex: int32(i), - PubKey: hop.PubKeyBytes[:], - Scid: strconv.FormatUint( - hop.ChannelID, 10, - ), - OutgoingTimeLock: int32(hop.OutgoingTimeLock), - AmtToForward: int64(hop.AmtToForward), - MetaData: hop.Metadata, - }) - if err != nil { - return fmt.Errorf("failed to insert route hop: %w", err) - } - - // Insert the per-hop custom records. - if len(hop.CustomRecords) > 0 { - for key, value := range hop.CustomRecords { - err = db.InsertPaymentHopCustomRecord( - ctx, - sqlc.InsertPaymentHopCustomRecordParams{ - HopID: hopID, - Key: int64(key), - Value: value, - }) - if err != nil { - return fmt.Errorf("failed to insert "+ - "payment hop custom record: %w", - err) - } - } - } - - // Insert MPP data if present. - if hop.MPP != nil { - paymentAddr := hop.MPP.PaymentAddr() - err = db.InsertRouteHopMpp( - ctx, sqlc.InsertRouteHopMppParams{ - HopID: hopID, - PaymentAddr: paymentAddr[:], - TotalMsat: int64(hop.MPP.TotalMsat()), - }) - if err != nil { - return fmt.Errorf("failed to insert "+ - "route hop MPP: %w", err) - } - } - - // Insert AMP data if present. - if hop.AMP != nil { - rootShare := hop.AMP.RootShare() - setID := hop.AMP.SetID() - err = db.InsertRouteHopAmp( - ctx, sqlc.InsertRouteHopAmpParams{ - HopID: hopID, - RootShare: rootShare[:], - SetID: setID[:], - ChildIndex: int32(hop.AMP.ChildIndex()), - }) - if err != nil { - return fmt.Errorf("failed to insert "+ - "route hop AMP: %w", err) - } - } - - // Insert blinded route data if present. Every hop in the - // blinded path must have an encrypted data record. If the - // encrypted data is not present, we skip the insertion. - if hop.EncryptedData == nil { - continue - } - - // The introduction point has a blinding point set. - var blindingPointBytes []byte - if hop.BlindingPoint != nil { - blindingPointBytes = hop.BlindingPoint. - SerializeCompressed() - } - - // The total amount is only set for the final hop in a - // blinded path. - totalAmtMsat := sql.NullInt64{} - if i == len(hops)-1 { - totalAmtMsat = sql.NullInt64{ - Int64: int64(hop.TotalAmtMsat), - Valid: true, - } - } - - err = db.InsertRouteHopBlinded(ctx, - sqlc.InsertRouteHopBlindedParams{ - HopID: hopID, - EncryptedData: hop.EncryptedData, - BlindingPoint: blindingPointBytes, - BlindedPathTotalAmt: totalAmtMsat, - }, - ) - if err != nil { - return fmt.Errorf("failed to insert "+ - "route hop blinded: %w", err) - } - } - - return nil -} - -// RegisterAttempt atomically records a new HTLC attempt for the specified -// payment. The attempt includes the attempt ID, session key, route information -// (hops, timelocks, amounts), and optional data such as MPP/AMP parameters, -// blinded route data, and custom records. -// -// Returns the updated MPPayment with the new attempt appended to the HTLCs -// slice, and the payment state recalculated. Returns an error if the payment -// doesn't exist or validation fails. -// -// This method is part of the PaymentControl interface, which is embedded in -// the PaymentWriter interface and ultimately the DB interface. It represents -// step 2 in the payment lifecycle control flow, called after InitPayment and -// potentially multiple times for multi-path payments. -func (s *SQLStore) RegisterAttempt(ctx context.Context, - paymentHash lntypes.Hash, attempt *HTLCAttemptInfo) (*MPPayment, - error) { - - var mpPayment *MPPayment - - err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { - // Make sure the payment exists. - dbPayment, err := db.FetchPayment(ctx, paymentHash[:]) - if err != nil { - return err - } - - // We fetch the complete payment to determine if the payment is - // registrable. - // - // TODO(ziggie): We could improve the query here since only - // the last hop data is needed here not the complete payment - // data. - mpPayment, err = fetchPaymentWithCompleteData( - ctx, s.cfg.QueryCfg, db, dbPayment, - ) - if err != nil { - return fmt.Errorf("failed to fetch payment with "+ - "complete data: %w", err) - } - - if err := mpPayment.Registrable(); err != nil { - return fmt.Errorf("htlc attempt not registrable: %w", - err) - } - - // Verify the attempt is compatible with the existing payment. - if err := verifyAttempt(mpPayment, attempt); err != nil { - return fmt.Errorf("failed to verify attempt: %w", err) - } - - // Register the plain HTLC attempt next. - sessionKey := attempt.SessionKey() - sessionKeyBytes := sessionKey.Serialize() - - _, err = db.InsertHtlcAttempt(ctx, sqlc.InsertHtlcAttemptParams{ - PaymentID: dbPayment.Payment.ID, - AttemptIndex: int64(attempt.AttemptID), - SessionKey: sessionKeyBytes, - AttemptTime: attempt.AttemptTime, - PaymentHash: paymentHash[:], - FirstHopAmountMsat: int64( - attempt.Route.FirstHopAmount.Val.Int(), - ), - RouteTotalTimeLock: int32(attempt.Route.TotalTimeLock), - RouteTotalAmount: int64(attempt.Route.TotalAmount), - RouteSourceKey: attempt.Route.SourcePubKey[:], - }) - if err != nil { - return fmt.Errorf("failed to insert HTLC "+ - "attempt: %w", err) - } - - // Insert the route level first hop custom records. - attemptFirstHopCustomRecords := attempt.Route. - FirstHopWireCustomRecords - - for key, value := range attemptFirstHopCustomRecords { - //nolint:ll - err = db.InsertPaymentAttemptFirstHopCustomRecord( - ctx, - sqlc.InsertPaymentAttemptFirstHopCustomRecordParams{ - HtlcAttemptIndex: int64(attempt.AttemptID), - Key: int64(key), - Value: value, - }, - ) - if err != nil { - return fmt.Errorf("failed to insert "+ - "payment attempt first hop custom "+ - "record: %w", err) - } - } - - // Insert the route hops. - err = s.insertRouteHops( - ctx, db, attempt.Route.Hops, attempt.AttemptID, - ) - if err != nil { - return fmt.Errorf("failed to insert route hops: %w", - err) - } - - // We fetch the HTLC attempts again to recalculate the payment - // state after the attempt is registered. This also makes sure - // we have the right data in case multiple attempts are - // registered concurrently. - // - // NOTE: While the caller is responsible for serializing calls - // to RegisterAttempt per payment hash (see PaymentControl - // interface), we still refetch here to guarantee we return - // consistent, up-to-date data that reflects all changes made - // within this transaction. - mpPayment, err = fetchPaymentWithCompleteData( - ctx, s.cfg.QueryCfg, db, dbPayment, - ) - if err != nil { - return fmt.Errorf("failed to fetch payment with "+ - "complete data: %w", err) - } - - return nil - }, func() { - mpPayment = nil - }) - if err != nil { - return nil, fmt.Errorf("failed to register attempt: %w", err) - } - - return mpPayment, nil -} - -// SettleAttempt marks the specified HTLC attempt as successfully settled, -// recording the payment preimage and settlement time. The preimage serves as -// cryptographic proof of payment and is atomically saved to the database. -// -// This method is part of the PaymentControl interface, which is embedded in -// the PaymentWriter interface and ultimately the DB interface. It represents -// step 3a in the payment lifecycle control flow (step 3b is FailAttempt), -// called after RegisterAttempt when an HTLC successfully completes. -func (s *SQLStore) SettleAttempt(ctx context.Context, paymentHash lntypes.Hash, - attemptID uint64, settleInfo *HTLCSettleInfo) (*MPPayment, error) { - - var mpPayment *MPPayment - - err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { - dbPayment, err := fetchPaymentByHash(ctx, db, paymentHash) - if err != nil { - return err - } - - paymentStatus, err := computePaymentStatusFromDB( - ctx, s.cfg.QueryCfg, db, dbPayment, - ) - if err != nil { - return fmt.Errorf("failed to compute payment "+ - "status: %w", err) - } - - if err := paymentStatus.updatable(); err != nil { - return fmt.Errorf("payment is not updatable: %w", err) - } - - err = db.SettleAttempt(ctx, sqlc.SettleAttemptParams{ - AttemptIndex: int64(attemptID), - ResolutionTime: settleInfo.SettleTime.UTC(), - ResolutionType: int32(HTLCAttemptResolutionSettled), - SettlePreimage: settleInfo.Preimage[:], - }) - if err != nil { - return fmt.Errorf("failed to settle attempt: %w", err) - } - - // Fetch the complete payment after we settled the attempt. - mpPayment, err = fetchPaymentWithCompleteData( - ctx, s.cfg.QueryCfg, db, dbPayment, - ) - if err != nil { - return fmt.Errorf("failed to fetch payment with "+ - "complete data: %w", err) - } - - return nil - }, func() { - mpPayment = nil - }) - if err != nil { - return nil, fmt.Errorf("failed to settle attempt: %w", err) - } - - return mpPayment, nil -} - -// FailAttempt marks the specified HTLC attempt as failed, recording the -// failure reason, failure time, optional failure message, and the index of the -// node in the route that generated the failure. This information is atomically -// saved to the database for debugging and route optimization purposes. -// -// For single-path payments, failing the only attempt may lead to the payment -// being retried or ultimately failed via the Fail method. For multi-shard -// (MPP/AMP) payments, individual shard failures don't necessarily fail the -// entire payment; additional attempts can be registered until sufficient shards -// succeed or the payment is permanently failed. -// -// Returns the updated MPPayment with the attempt marked as failed and the -// payment state recalculated. The payment status remains StatusInFlight if -// other attempts are still in flight, or may transition based on the overall -// payment state. -// -// This method is part of the PaymentControl interface, which is embedded in -// the PaymentWriter interface and ultimately the DB interface. It represents -// step 3b in the payment lifecycle control flow (step 3a is SettleAttempt), -// called after RegisterAttempt when an HTLC fails. -func (s *SQLStore) FailAttempt(ctx context.Context, paymentHash lntypes.Hash, - attemptID uint64, failInfo *HTLCFailInfo) (*MPPayment, error) { - - var mpPayment *MPPayment - - err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { - // Make sure the payment exists. - dbPayment, err := fetchPaymentByHash(ctx, db, paymentHash) - if err != nil { - return err - } - - paymentStatus, err := computePaymentStatusFromDB( - ctx, s.cfg.QueryCfg, db, dbPayment, - ) - if err != nil { - return fmt.Errorf("failed to compute payment "+ - "status: %w", err) - } - - // We check if the payment is updatable before failing the - // attempt. - if err := paymentStatus.updatable(); err != nil { - return fmt.Errorf("payment is not updatable: %w", err) - } - - var failureMsg bytes.Buffer - if failInfo.Message != nil { - err := lnwire.EncodeFailureMessage( - &failureMsg, failInfo.Message, 0, - ) - if err != nil { - return fmt.Errorf("failed to encode "+ - "failure message: %w", err) - } - } - - err = db.FailAttempt(ctx, sqlc.FailAttemptParams{ - AttemptIndex: int64(attemptID), - ResolutionTime: failInfo.FailTime.UTC(), - ResolutionType: int32(HTLCAttemptResolutionFailed), - FailureSourceIndex: sqldb.SQLInt32( - failInfo.FailureSourceIndex, - ), - HtlcFailReason: sqldb.SQLInt32(failInfo.Reason), - FailureMsg: failureMsg.Bytes(), - }) - if err != nil { - return fmt.Errorf("failed to fail attempt: %w", err) - } - - mpPayment, err = fetchPaymentWithCompleteData( - ctx, s.cfg.QueryCfg, db, dbPayment, - ) - if err != nil { - return fmt.Errorf("failed to fetch payment with "+ - "complete data: %w", err) - } - - return nil - }, func() { - mpPayment = nil - }) - if err != nil { - return nil, fmt.Errorf("failed to fail attempt: %w", err) - } - - return mpPayment, nil -} - -// Fail records the ultimate reason why a payment failed. This method stores -// the failure reason for record keeping but does not enforce that all HTLC -// attempts are resolved - HTLCs may still be in flight when this is called. -// -// The payment's actual status transition to StatusFailed is determined by the -// payment state calculation, which considers both the recorded failure reason -// and the current state of all HTLC attempts. The status will transition to -// StatusFailed once all HTLCs are resolved and/or a failure reason is recorded. -// -// NOTE: According to the interface contract, this should only be called when -// all active attempts are already failed. However, the implementation allows -// concurrent calls and does not validate this precondition, enabling the last -// failing attempt to record the failure reason without synchronization. -// -// This method is part of the PaymentControl interface, which is embedded in -// the PaymentWriter interface and ultimately the DB interface. It represents -// step 4 in the payment lifecycle control flow. -func (s *SQLStore) Fail(ctx context.Context, paymentHash lntypes.Hash, - reason FailureReason) (*MPPayment, error) { - - var mpPayment *MPPayment - - err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { - result, err := db.FailPayment(ctx, sqlc.FailPaymentParams{ - PaymentIdentifier: paymentHash[:], - FailReason: sqldb.SQLInt32(reason), - }) - if err != nil { - return err - } - - rowsAffected, err := result.RowsAffected() - if err != nil { - return err - } - if rowsAffected == 0 { - return ErrPaymentNotInitiated - } - - payment, err := db.FetchPayment(ctx, paymentHash[:]) - if err != nil { - return fmt.Errorf("failed to fetch payment: %w", err) - } - mpPayment, err = fetchPaymentWithCompleteData( - ctx, s.cfg.QueryCfg, db, payment, - ) - if err != nil { - return fmt.Errorf("failed to fetch payment with "+ - "complete data: %w", err) - } - - return nil - }, func() { - mpPayment = nil - }) - if err != nil { - return nil, fmt.Errorf("failed to fail payment: %w", err) - } - - return mpPayment, nil -} - -// DeletePayments performs a batch deletion of payments or their failed HTLC -// attempts from the database based on the specified flags. This is a bulk -// operation that iterates through all payments and selectively deletes based -// on the criteria. -// The behavior is controlled by two flags: -// -// If failedAttemptsOnly is true, only failed HTLC attempts are deleted while -// preserving the payment records and any successful or in-flight attempts. -// The return value is always 0 when deleting attempts only. -// -// If failedAttemptsOnly is false, entire payment records are deleted including -// all associated data (HTLCs, metadata, intents). The return value is the -// number of payments deleted. -// -// The failedOnly flag further filters which payments are processed: -// - failedOnly=true, failedAttemptsOnly=true: Delete failed attempts for -// StatusFailed payments only -// - failedOnly=false, failedAttemptsOnly=true: Delete failed attempts for -// all removable payments -// - failedOnly=true, failedAttemptsOnly=false: Delete entire payment records -// for StatusFailed payments only -// - failedOnly=false, failedAttemptsOnly=false: Delete all removable payment -// records (StatusInitiated, StatusSucceeded, StatusFailed) -// -// Safety checks applied to all operations: -// - Payments with StatusInFlight are always skipped (cannot be safely deleted -// while HTLCs are on the network) -// - The payment status must pass the removable() check -// -// Returns the number of complete payments deleted (0 if only deleting failed -// attempts). This is useful for cleanup operations, administrative maintenance, -// or freeing up database storage. -// -// This method is part of the PaymentWriter interface, which is embedded in -// the DB interface. -// -// TODO(ziggie): batch and use iterator instead, moreover we dont need to fetch -// the complete payment data for each payment, we can just fetch the payment ID -// and the resolution types to decide if the payment is removable. -func (s *SQLStore) DeletePayments(ctx context.Context, failedOnly, - failedHtlcsOnly bool) (int, error) { - - var numPayments int - - extractCursor := func(row sqlc.FilterPaymentsRow) int64 { - return row.Payment.ID - } - - err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { - // collectFunc extracts the payment ID from each payment row. - collectFunc := func(row sqlc.FilterPaymentsRow) (int64, error) { - return row.Payment.ID, nil - } - - // batchDataFunc loads only HTLC resolution types for a batch - // of payments, which is sufficient to determine payment status. - batchDataFunc := func(ctx context.Context, paymentIDs []int64) ( - *paymentStatusData, error) { - - return batchLoadPaymentResolutions( - ctx, s.cfg.QueryCfg, db, paymentIDs, - ) - } - - // processPayment processes each payment with the lightweight - // batch-loaded resolution data. - processPayment := func(ctx context.Context, - dbPayment sqlc.FilterPaymentsRow, - batchData *paymentStatusData) error { - - payment := dbPayment.Payment - - // Compute the payment status from resolution types and - // failure reason without building the complete payment. - resolutionTypes := batchData.resolutionTypes[payment.ID] - status, err := computePaymentStatusFromResolutions( - resolutionTypes, payment.FailReason, - ) - if err != nil { - return fmt.Errorf("failed to compute payment "+ - "status: %w", err) - } - - // Payments which are not final yet cannot be deleted. - // we skip them. - if err := status.removable(); err != nil { - return nil - } - - // If we are only deleting failed payments, we skip - // if the payment is not failed. - if failedOnly && status != StatusFailed { - return nil - } - - // If we are only deleting failed HTLCs, we delete them - // and return early. - if failedHtlcsOnly { - return db.DeleteFailedAttempts( - ctx, payment.ID, - ) - } - - // Otherwise we delete the payment. - err = db.DeletePayment(ctx, payment.ID) - if err != nil { - return fmt.Errorf("failed to delete "+ - "payment: %w", err) - } - - numPayments++ - - return nil - } - - queryFunc := func(ctx context.Context, lastID int64, - limit int32) ([]sqlc.FilterPaymentsRow, error) { - - filterParams := sqlc.FilterPaymentsParams{ - NumLimit: limit, - CreatedAfter: time.Unix(0, 0).UTC(), - CreatedBefore: time.Date( - 9999, 12, 31, 23, 59, 59, - 0, time.UTC, - ), - IndexOffsetGet: sqldb.SQLInt64( - lastID, - ), - } - - return db.FilterPayments(ctx, filterParams) - } - - return sqldb.ExecuteCollectAndBatchWithSharedDataQuery( - ctx, s.cfg.QueryCfg, int64(-1), queryFunc, - extractCursor, collectFunc, batchDataFunc, - processPayment, - ) - }, func() { - numPayments = 0 - }) - if err != nil { - return 0, fmt.Errorf("failed to delete payments "+ - "(failedOnly: %v, failedHtlcsOnly: %v): %w", - failedOnly, failedHtlcsOnly, err) - } - - return numPayments, nil -} diff --git a/payments/db/migration1/sqlc/db.go b/payments/db/migration1/sqlc/db.go deleted file mode 100644 index e4d78283b..000000000 --- a/payments/db/migration1/sqlc/db.go +++ /dev/null @@ -1,31 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.29.0 - -package sqlc - -import ( - "context" - "database/sql" -) - -type DBTX interface { - ExecContext(context.Context, string, ...interface{}) (sql.Result, error) - PrepareContext(context.Context, string) (*sql.Stmt, error) - QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) - QueryRowContext(context.Context, string, ...interface{}) *sql.Row -} - -func New(db DBTX) *Queries { - return &Queries{db: db} -} - -type Queries struct { - db DBTX -} - -func (q *Queries) WithTx(tx *sql.Tx) *Queries { - return &Queries{ - db: tx, - } -} diff --git a/payments/db/migration1/sqlc/db_custom.go b/payments/db/migration1/sqlc/db_custom.go deleted file mode 100644 index 625e65adc..000000000 --- a/payments/db/migration1/sqlc/db_custom.go +++ /dev/null @@ -1,123 +0,0 @@ -package sqlc - -import ( - "fmt" - "strings" -) - -// GetTx returns the underlying DBTX (either *sql.DB or *sql.Tx) used by the -// Queries struct. -func (q *Queries) GetTx() DBTX { - return q.db -} - -// makeQueryParams generates a string of query parameters for a SQL query. It is -// meant to replace the `?` placeholders in a SQL query with numbered parameters -// like `$1`, `$2`, etc. This is required for the sqlc /*SLICE:*/ -// workaround. See scripts/gen_sqlc_docker.sh for more details. -func makeQueryParams(numTotalArgs, numListArgs int) string { - if numListArgs == 0 { - return "" - } - - var b strings.Builder - - // Pre-allocate a rough estimation of the buffer size to avoid - // re-allocations. A parameter like $1000, takes 6 bytes. - b.Grow(numListArgs * 6) - - diff := numTotalArgs - numListArgs - for i := 0; i < numListArgs; i++ { - if i > 0 { - // We don't need to check the error here because the - // WriteString method of strings.Builder always returns - // nil. - _, _ = b.WriteString(",") - } - - // We don't need to check the error here because the - // Write method (called by fmt.Fprintf) of strings.Builder - // always returns nil. - _, _ = fmt.Fprintf(&b, "$%d", i+diff+1) - } - - return b.String() -} - -// PaymentAndIntent is an interface that provides access to a payment and its -// associated payment intent. -type PaymentAndIntent interface { - // GetPayment returns the Payment associated with this interface. - GetPayment() Payment - - // GetPaymentIntent returns the PaymentIntent associated with this - // payment. - GetPaymentIntent() PaymentIntent -} - -// GetPayment returns the Payment associated with this interface. -// -// NOTE: This method is part of the PaymentAndIntent interface. -func (r FilterPaymentsRow) GetPayment() Payment { - return r.Payment -} - -// GetPaymentIntent returns the PaymentIntent associated with this payment. -// If the payment has no intent (IntentType is NULL), this returns a zero-value -// PaymentIntent. -// -// NOTE: This method is part of the PaymentAndIntent interface. -func (r FilterPaymentsRow) GetPaymentIntent() PaymentIntent { - if !r.IntentType.Valid { - return PaymentIntent{} - } - - return PaymentIntent{ - IntentType: r.IntentType.Int16, - IntentPayload: r.IntentPayload, - } -} - -// GetPayment returns the Payment associated with this interface. -// -// NOTE: This method is part of the PaymentAndIntent interface. -func (r FetchPaymentRow) GetPayment() Payment { - return r.Payment -} - -// GetPaymentIntent returns the PaymentIntent associated with this payment. -// If the payment has no intent (IntentType is NULL), this returns a zero-value -// PaymentIntent. -// -// NOTE: This method is part of the PaymentAndIntent interface. -func (r FetchPaymentRow) GetPaymentIntent() PaymentIntent { - if !r.IntentType.Valid { - return PaymentIntent{} - } - - return PaymentIntent{ - IntentType: r.IntentType.Int16, - IntentPayload: r.IntentPayload, - } -} - -func (r FetchPaymentsByIDsRow) GetPayment() Payment { - return Payment{ - ID: r.ID, - AmountMsat: r.AmountMsat, - CreatedAt: r.CreatedAt, - PaymentIdentifier: r.PaymentIdentifier, - FailReason: r.FailReason, - } -} - -func (r FetchPaymentsByIDsRow) GetPaymentIntent() PaymentIntent { - if !r.IntentType.Valid { - return PaymentIntent{} - } - - return PaymentIntent{ - IntentType: r.IntentType.Int16, - IntentPayload: r.IntentPayload, - } -} diff --git a/payments/db/migration1/sqlc/models.go b/payments/db/migration1/sqlc/models.go deleted file mode 100644 index afb448090..000000000 --- a/payments/db/migration1/sqlc/models.go +++ /dev/null @@ -1,110 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.29.0 - -package sqlc - -import ( - "database/sql" - "time" -) - -type Payment struct { - ID int64 - AmountMsat int64 - CreatedAt time.Time - PaymentIdentifier []byte - FailReason sql.NullInt32 -} - -type PaymentAttemptFirstHopCustomRecord struct { - ID int64 - HtlcAttemptIndex int64 - Key int64 - Value []byte -} - -type PaymentDuplicate struct { - ID int64 - PaymentID int64 - AmountMsat int64 - CreatedAt time.Time - FailReason sql.NullInt32 - SettlePreimage []byte - SettleTime sql.NullTime -} - -type PaymentFirstHopCustomRecord struct { - ID int64 - PaymentID int64 - Key int64 - Value []byte -} - -type PaymentHopCustomRecord struct { - ID int64 - HopID int64 - Key int64 - Value []byte -} - -type PaymentHtlcAttempt struct { - ID int64 - AttemptIndex int64 - PaymentID int64 - SessionKey []byte - AttemptTime time.Time - PaymentHash []byte - FirstHopAmountMsat int64 - RouteTotalTimeLock int32 - RouteTotalAmount int64 - RouteSourceKey []byte -} - -type PaymentHtlcAttemptResolution struct { - AttemptIndex int64 - ResolutionTime time.Time - ResolutionType int32 - SettlePreimage []byte - FailureSourceIndex sql.NullInt32 - HtlcFailReason sql.NullInt32 - FailureMsg []byte -} - -type PaymentIntent struct { - ID int64 - PaymentID int64 - IntentType int16 - IntentPayload []byte -} - -type PaymentRouteHop struct { - ID int64 - HtlcAttemptIndex int64 - HopIndex int32 - PubKey []byte - Scid string - OutgoingTimeLock int32 - AmtToForward int64 - MetaData []byte -} - -type PaymentRouteHopAmp struct { - HopID int64 - RootShare []byte - SetID []byte - ChildIndex int32 -} - -type PaymentRouteHopBlinded struct { - HopID int64 - EncryptedData []byte - BlindingPoint []byte - BlindedPathTotalAmt sql.NullInt64 -} - -type PaymentRouteHopMpp struct { - HopID int64 - PaymentAddr []byte - TotalMsat int64 -} diff --git a/payments/db/migration1/sqlc/payments.sql.go b/payments/db/migration1/sqlc/payments.sql.go deleted file mode 100644 index e17bb3cfb..000000000 --- a/payments/db/migration1/sqlc/payments.sql.go +++ /dev/null @@ -1,1357 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.29.0 -// source: payments.sql - -package sqlc - -import ( - "context" - "database/sql" - "strings" - "time" -) - -const countPayments = `-- name: CountPayments :one -SELECT COUNT(*) FROM payments -` - -func (q *Queries) CountPayments(ctx context.Context) (int64, error) { - row := q.db.QueryRowContext(ctx, countPayments) - var count int64 - err := row.Scan(&count) - return count, err -} - -const deleteFailedAttempts = `-- name: DeleteFailedAttempts :exec -DELETE FROM payment_htlc_attempts WHERE payment_id = $1 AND attempt_index IN ( - SELECT attempt_index FROM payment_htlc_attempt_resolutions WHERE resolution_type = 2 -) -` - -// Delete all failed HTLC attempts for the given payment. Resolution type 2 -// indicates a failed attempt. -func (q *Queries) DeleteFailedAttempts(ctx context.Context, paymentID int64) error { - _, err := q.db.ExecContext(ctx, deleteFailedAttempts, paymentID) - return err -} - -const deletePayment = `-- name: DeletePayment :exec -DELETE FROM payments WHERE id = $1 -` - -func (q *Queries) DeletePayment(ctx context.Context, id int64) error { - _, err := q.db.ExecContext(ctx, deletePayment, id) - return err -} - -const failAttempt = `-- name: FailAttempt :exec -INSERT INTO payment_htlc_attempt_resolutions ( - attempt_index, - resolution_time, - resolution_type, - failure_source_index, - htlc_fail_reason, - failure_msg -) -VALUES ( - $1, - $2, - $3, - $4, - $5, - $6 -) -` - -type FailAttemptParams struct { - AttemptIndex int64 - ResolutionTime time.Time - ResolutionType int32 - FailureSourceIndex sql.NullInt32 - HtlcFailReason sql.NullInt32 - FailureMsg []byte -} - -func (q *Queries) FailAttempt(ctx context.Context, arg FailAttemptParams) error { - _, err := q.db.ExecContext(ctx, failAttempt, - arg.AttemptIndex, - arg.ResolutionTime, - arg.ResolutionType, - arg.FailureSourceIndex, - arg.HtlcFailReason, - arg.FailureMsg, - ) - return err -} - -const failPayment = `-- name: FailPayment :execresult -UPDATE payments SET fail_reason = $1 WHERE payment_identifier = $2 -` - -type FailPaymentParams struct { - FailReason sql.NullInt32 - PaymentIdentifier []byte -} - -func (q *Queries) FailPayment(ctx context.Context, arg FailPaymentParams) (sql.Result, error) { - return q.db.ExecContext(ctx, failPayment, arg.FailReason, arg.PaymentIdentifier) -} - -const fetchAllInflightAttempts = `-- name: FetchAllInflightAttempts :many -SELECT - ha.id, - ha.attempt_index, - ha.payment_id, - ha.session_key, - ha.attempt_time, - ha.payment_hash, - ha.first_hop_amount_msat, - ha.route_total_time_lock, - ha.route_total_amount, - ha.route_source_key -FROM payment_htlc_attempts ha -WHERE NOT EXISTS ( - SELECT 1 FROM payment_htlc_attempt_resolutions hr - WHERE hr.attempt_index = ha.attempt_index -) -AND ha.attempt_index > $1 -ORDER BY ha.attempt_index ASC -LIMIT $2 -` - -type FetchAllInflightAttemptsParams struct { - AttemptIndex int64 - Limit int32 -} - -// Fetch all inflight attempts with their payment data using pagination. -// Returns attempt data joined with payment and intent data to avoid separate queries. -func (q *Queries) FetchAllInflightAttempts(ctx context.Context, arg FetchAllInflightAttemptsParams) ([]PaymentHtlcAttempt, error) { - rows, err := q.db.QueryContext(ctx, fetchAllInflightAttempts, arg.AttemptIndex, arg.Limit) - if err != nil { - return nil, err - } - defer rows.Close() - var items []PaymentHtlcAttempt - for rows.Next() { - var i PaymentHtlcAttempt - if err := rows.Scan( - &i.ID, - &i.AttemptIndex, - &i.PaymentID, - &i.SessionKey, - &i.AttemptTime, - &i.PaymentHash, - &i.FirstHopAmountMsat, - &i.RouteTotalTimeLock, - &i.RouteTotalAmount, - &i.RouteSourceKey, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const fetchHopLevelCustomRecords = `-- name: FetchHopLevelCustomRecords :many -SELECT - l.id, - l.hop_id, - l.key, - l.value -FROM payment_hop_custom_records l -WHERE l.hop_id IN (/*SLICE:hop_ids*/?) -ORDER BY l.hop_id ASC, l.key ASC -` - -func (q *Queries) FetchHopLevelCustomRecords(ctx context.Context, hopIds []int64) ([]PaymentHopCustomRecord, error) { - query := fetchHopLevelCustomRecords - var queryParams []interface{} - if len(hopIds) > 0 { - for _, v := range hopIds { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:hop_ids*/?", makeQueryParams(len(queryParams), len(hopIds)), 1) - } else { - query = strings.Replace(query, "/*SLICE:hop_ids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []PaymentHopCustomRecord - for rows.Next() { - var i PaymentHopCustomRecord - if err := rows.Scan( - &i.ID, - &i.HopID, - &i.Key, - &i.Value, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const fetchHopsForAttempts = `-- name: FetchHopsForAttempts :many -SELECT - h.id, - h.htlc_attempt_index, - h.hop_index, - h.pub_key, - h.scid, - h.outgoing_time_lock, - h.amt_to_forward, - h.meta_data, - m.payment_addr AS mpp_payment_addr, - m.total_msat AS mpp_total_msat, - a.root_share AS amp_root_share, - a.set_id AS amp_set_id, - a.child_index AS amp_child_index, - b.encrypted_data, - b.blinding_point, - b.blinded_path_total_amt -FROM payment_route_hops h -LEFT JOIN payment_route_hop_mpp m ON m.hop_id = h.id -LEFT JOIN payment_route_hop_amp a ON a.hop_id = h.id -LEFT JOIN payment_route_hop_blinded b ON b.hop_id = h.id -WHERE h.htlc_attempt_index IN (/*SLICE:htlc_attempt_indices*/?) -ORDER BY h.htlc_attempt_index ASC, h.hop_index ASC -` - -type FetchHopsForAttemptsRow struct { - ID int64 - HtlcAttemptIndex int64 - HopIndex int32 - PubKey []byte - Scid string - OutgoingTimeLock int32 - AmtToForward int64 - MetaData []byte - MppPaymentAddr []byte - MppTotalMsat sql.NullInt64 - AmpRootShare []byte - AmpSetID []byte - AmpChildIndex sql.NullInt32 - EncryptedData []byte - BlindingPoint []byte - BlindedPathTotalAmt sql.NullInt64 -} - -func (q *Queries) FetchHopsForAttempts(ctx context.Context, htlcAttemptIndices []int64) ([]FetchHopsForAttemptsRow, error) { - query := fetchHopsForAttempts - var queryParams []interface{} - if len(htlcAttemptIndices) > 0 { - for _, v := range htlcAttemptIndices { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:htlc_attempt_indices*/?", makeQueryParams(len(queryParams), len(htlcAttemptIndices)), 1) - } else { - query = strings.Replace(query, "/*SLICE:htlc_attempt_indices*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []FetchHopsForAttemptsRow - for rows.Next() { - var i FetchHopsForAttemptsRow - if err := rows.Scan( - &i.ID, - &i.HtlcAttemptIndex, - &i.HopIndex, - &i.PubKey, - &i.Scid, - &i.OutgoingTimeLock, - &i.AmtToForward, - &i.MetaData, - &i.MppPaymentAddr, - &i.MppTotalMsat, - &i.AmpRootShare, - &i.AmpSetID, - &i.AmpChildIndex, - &i.EncryptedData, - &i.BlindingPoint, - &i.BlindedPathTotalAmt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const fetchHtlcAttemptResolutionsForPayments = `-- name: FetchHtlcAttemptResolutionsForPayments :many -SELECT - ha.payment_id, - hr.resolution_type -FROM payment_htlc_attempts ha -LEFT JOIN payment_htlc_attempt_resolutions hr ON hr.attempt_index = ha.attempt_index -WHERE ha.payment_id IN (/*SLICE:payment_ids*/?) -` - -type FetchHtlcAttemptResolutionsForPaymentsRow struct { - PaymentID int64 - ResolutionType sql.NullInt32 -} - -// Batch query to fetch only HTLC resolution status for multiple payments. -// We don't need to order by payment_id and attempt_time because we will -// group the resolutions by payment_id in the background. -func (q *Queries) FetchHtlcAttemptResolutionsForPayments(ctx context.Context, paymentIds []int64) ([]FetchHtlcAttemptResolutionsForPaymentsRow, error) { - query := fetchHtlcAttemptResolutionsForPayments - var queryParams []interface{} - if len(paymentIds) > 0 { - for _, v := range paymentIds { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:payment_ids*/?", makeQueryParams(len(queryParams), len(paymentIds)), 1) - } else { - query = strings.Replace(query, "/*SLICE:payment_ids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []FetchHtlcAttemptResolutionsForPaymentsRow - for rows.Next() { - var i FetchHtlcAttemptResolutionsForPaymentsRow - if err := rows.Scan(&i.PaymentID, &i.ResolutionType); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const fetchHtlcAttemptsForPayments = `-- name: FetchHtlcAttemptsForPayments :many -SELECT - ha.id, - ha.attempt_index, - ha.payment_id, - ha.session_key, - ha.attempt_time, - ha.payment_hash, - ha.first_hop_amount_msat, - ha.route_total_time_lock, - ha.route_total_amount, - ha.route_source_key, - hr.resolution_type, - hr.resolution_time, - hr.failure_source_index, - hr.htlc_fail_reason, - hr.failure_msg, - hr.settle_preimage -FROM payment_htlc_attempts ha -LEFT JOIN payment_htlc_attempt_resolutions hr ON hr.attempt_index = ha.attempt_index -WHERE ha.payment_id IN (/*SLICE:payment_ids*/?) -ORDER BY ha.payment_id ASC, ha.attempt_time ASC -` - -type FetchHtlcAttemptsForPaymentsRow struct { - ID int64 - AttemptIndex int64 - PaymentID int64 - SessionKey []byte - AttemptTime time.Time - PaymentHash []byte - FirstHopAmountMsat int64 - RouteTotalTimeLock int32 - RouteTotalAmount int64 - RouteSourceKey []byte - ResolutionType sql.NullInt32 - ResolutionTime sql.NullTime - FailureSourceIndex sql.NullInt32 - HtlcFailReason sql.NullInt32 - FailureMsg []byte - SettlePreimage []byte -} - -func (q *Queries) FetchHtlcAttemptsForPayments(ctx context.Context, paymentIds []int64) ([]FetchHtlcAttemptsForPaymentsRow, error) { - query := fetchHtlcAttemptsForPayments - var queryParams []interface{} - if len(paymentIds) > 0 { - for _, v := range paymentIds { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:payment_ids*/?", makeQueryParams(len(queryParams), len(paymentIds)), 1) - } else { - query = strings.Replace(query, "/*SLICE:payment_ids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []FetchHtlcAttemptsForPaymentsRow - for rows.Next() { - var i FetchHtlcAttemptsForPaymentsRow - if err := rows.Scan( - &i.ID, - &i.AttemptIndex, - &i.PaymentID, - &i.SessionKey, - &i.AttemptTime, - &i.PaymentHash, - &i.FirstHopAmountMsat, - &i.RouteTotalTimeLock, - &i.RouteTotalAmount, - &i.RouteSourceKey, - &i.ResolutionType, - &i.ResolutionTime, - &i.FailureSourceIndex, - &i.HtlcFailReason, - &i.FailureMsg, - &i.SettlePreimage, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const fetchPayment = `-- name: FetchPayment :one -SELECT - p.id, p.amount_msat, p.created_at, p.payment_identifier, p.fail_reason, - i.intent_type AS "intent_type", - i.intent_payload AS "intent_payload" -FROM payments p -LEFT JOIN payment_intents i ON i.payment_id = p.id -WHERE p.payment_identifier = $1 -` - -type FetchPaymentRow struct { - Payment Payment - IntentType sql.NullInt16 - IntentPayload []byte -} - -func (q *Queries) FetchPayment(ctx context.Context, paymentIdentifier []byte) (FetchPaymentRow, error) { - row := q.db.QueryRowContext(ctx, fetchPayment, paymentIdentifier) - var i FetchPaymentRow - err := row.Scan( - &i.Payment.ID, - &i.Payment.AmountMsat, - &i.Payment.CreatedAt, - &i.Payment.PaymentIdentifier, - &i.Payment.FailReason, - &i.IntentType, - &i.IntentPayload, - ) - return i, err -} - -const fetchPaymentDuplicates = `-- name: FetchPaymentDuplicates :many -SELECT - id, - payment_id, - amount_msat, - created_at, - fail_reason, - settle_preimage, - settle_time -FROM payment_duplicates -WHERE payment_id = $1 -ORDER BY id ASC -` - -// Fetch all duplicate payment records from the payment_duplicates table for -// a given payment ID. -func (q *Queries) FetchPaymentDuplicates(ctx context.Context, paymentID int64) ([]PaymentDuplicate, error) { - rows, err := q.db.QueryContext(ctx, fetchPaymentDuplicates, paymentID) - if err != nil { - return nil, err - } - defer rows.Close() - var items []PaymentDuplicate - for rows.Next() { - var i PaymentDuplicate - if err := rows.Scan( - &i.ID, - &i.PaymentID, - &i.AmountMsat, - &i.CreatedAt, - &i.FailReason, - &i.SettlePreimage, - &i.SettleTime, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const fetchPaymentLevelFirstHopCustomRecords = `-- name: FetchPaymentLevelFirstHopCustomRecords :many -SELECT - l.id, - l.payment_id, - l.key, - l.value -FROM payment_first_hop_custom_records l -WHERE l.payment_id IN (/*SLICE:payment_ids*/?) -ORDER BY l.payment_id ASC, l.key ASC -` - -func (q *Queries) FetchPaymentLevelFirstHopCustomRecords(ctx context.Context, paymentIds []int64) ([]PaymentFirstHopCustomRecord, error) { - query := fetchPaymentLevelFirstHopCustomRecords - var queryParams []interface{} - if len(paymentIds) > 0 { - for _, v := range paymentIds { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:payment_ids*/?", makeQueryParams(len(queryParams), len(paymentIds)), 1) - } else { - query = strings.Replace(query, "/*SLICE:payment_ids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []PaymentFirstHopCustomRecord - for rows.Next() { - var i PaymentFirstHopCustomRecord - if err := rows.Scan( - &i.ID, - &i.PaymentID, - &i.Key, - &i.Value, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const fetchPaymentsByIDs = `-- name: FetchPaymentsByIDs :many -SELECT - p.id, - p.amount_msat, - p.created_at, - p.payment_identifier, - p.fail_reason, - pi.intent_type, - pi.intent_payload -FROM payments p -LEFT JOIN payment_intents pi ON pi.payment_id = p.id -WHERE p.id IN (/*SLICE:payment_ids*/?) -ORDER BY p.id ASC -` - -type FetchPaymentsByIDsRow struct { - ID int64 - AmountMsat int64 - CreatedAt time.Time - PaymentIdentifier []byte - FailReason sql.NullInt32 - IntentType sql.NullInt16 - IntentPayload []byte -} - -// Batch fetch payment and intent data for a set of payment IDs. -// Used to avoid fetching redundant payment data when processing multiple -// attempts for the same payment. -func (q *Queries) FetchPaymentsByIDs(ctx context.Context, paymentIds []int64) ([]FetchPaymentsByIDsRow, error) { - query := fetchPaymentsByIDs - var queryParams []interface{} - if len(paymentIds) > 0 { - for _, v := range paymentIds { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:payment_ids*/?", makeQueryParams(len(queryParams), len(paymentIds)), 1) - } else { - query = strings.Replace(query, "/*SLICE:payment_ids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []FetchPaymentsByIDsRow - for rows.Next() { - var i FetchPaymentsByIDsRow - if err := rows.Scan( - &i.ID, - &i.AmountMsat, - &i.CreatedAt, - &i.PaymentIdentifier, - &i.FailReason, - &i.IntentType, - &i.IntentPayload, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const fetchPaymentsByIDsMig = `-- name: FetchPaymentsByIDsMig :many -SELECT - p.id, - p.amount_msat, - p.created_at, - p.payment_identifier, - p.fail_reason, - COUNT(ha.id) AS htlc_attempt_count -FROM payments p -LEFT JOIN payment_htlc_attempts ha ON ha.payment_id = p.id -WHERE p.id IN (/*SLICE:payment_ids*/?) -GROUP BY p.id, p.amount_msat, p.created_at, p.payment_identifier, p.fail_reason -ORDER BY p.id ASC -` - -type FetchPaymentsByIDsMigRow struct { - ID int64 - AmountMsat int64 - CreatedAt time.Time - PaymentIdentifier []byte - FailReason sql.NullInt32 - HtlcAttemptCount int64 -} - -// Migration-specific batch fetch that returns payment data along with HTLC -// attempt counts for structural validation during KV to SQL migration. -func (q *Queries) FetchPaymentsByIDsMig(ctx context.Context, paymentIds []int64) ([]FetchPaymentsByIDsMigRow, error) { - query := fetchPaymentsByIDsMig - var queryParams []interface{} - if len(paymentIds) > 0 { - for _, v := range paymentIds { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:payment_ids*/?", makeQueryParams(len(queryParams), len(paymentIds)), 1) - } else { - query = strings.Replace(query, "/*SLICE:payment_ids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []FetchPaymentsByIDsMigRow - for rows.Next() { - var i FetchPaymentsByIDsMigRow - if err := rows.Scan( - &i.ID, - &i.AmountMsat, - &i.CreatedAt, - &i.PaymentIdentifier, - &i.FailReason, - &i.HtlcAttemptCount, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const fetchRouteLevelFirstHopCustomRecords = `-- name: FetchRouteLevelFirstHopCustomRecords :many -SELECT - l.id, - l.htlc_attempt_index, - l.key, - l.value -FROM payment_attempt_first_hop_custom_records l -WHERE l.htlc_attempt_index IN (/*SLICE:htlc_attempt_indices*/?) -ORDER BY l.htlc_attempt_index ASC, l.key ASC -` - -func (q *Queries) FetchRouteLevelFirstHopCustomRecords(ctx context.Context, htlcAttemptIndices []int64) ([]PaymentAttemptFirstHopCustomRecord, error) { - query := fetchRouteLevelFirstHopCustomRecords - var queryParams []interface{} - if len(htlcAttemptIndices) > 0 { - for _, v := range htlcAttemptIndices { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:htlc_attempt_indices*/?", makeQueryParams(len(queryParams), len(htlcAttemptIndices)), 1) - } else { - query = strings.Replace(query, "/*SLICE:htlc_attempt_indices*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []PaymentAttemptFirstHopCustomRecord - for rows.Next() { - var i PaymentAttemptFirstHopCustomRecord - if err := rows.Scan( - &i.ID, - &i.HtlcAttemptIndex, - &i.Key, - &i.Value, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const filterPayments = `-- name: FilterPayments :many -/* ───────────────────────────────────────────── - fetch queries - ───────────────────────────────────────────── -*/ - -SELECT - p.id, p.amount_msat, p.created_at, p.payment_identifier, p.fail_reason, - i.intent_type AS "intent_type", - i.intent_payload AS "intent_payload" -FROM payments p -LEFT JOIN payment_intents i ON i.payment_id = p.id -WHERE p.id > COALESCE($1, -1) - AND p.id < COALESCE($2, 9223372036854775807) - -- NOTE: We use non-nullable time params with Go-side defaults instead of - -- COALESCE, because COALESCE with text fallback causes type mismatch on - -- Postgres (timestamp vs text), and OR-based optional filters can prevent - -- the planner from using the created_at index. - AND p.created_at >= $3 - AND p.created_at <= $4 - AND ( - i.intent_type = $5 OR - $5 IS NULL OR i.intent_type IS NULL - ) -ORDER BY p.id ASC -LIMIT $6 -` - -type FilterPaymentsParams struct { - IndexOffsetGet sql.NullInt64 - IndexOffsetLet sql.NullInt64 - CreatedAfter time.Time - CreatedBefore time.Time - IntentType sql.NullInt16 - NumLimit int32 -} - -type FilterPaymentsRow struct { - Payment Payment - IntentType sql.NullInt16 - IntentPayload []byte -} - -func (q *Queries) FilterPayments(ctx context.Context, arg FilterPaymentsParams) ([]FilterPaymentsRow, error) { - rows, err := q.db.QueryContext(ctx, filterPayments, - arg.IndexOffsetGet, - arg.IndexOffsetLet, - arg.CreatedAfter, - arg.CreatedBefore, - arg.IntentType, - arg.NumLimit, - ) - if err != nil { - return nil, err - } - defer rows.Close() - var items []FilterPaymentsRow - for rows.Next() { - var i FilterPaymentsRow - if err := rows.Scan( - &i.Payment.ID, - &i.Payment.AmountMsat, - &i.Payment.CreatedAt, - &i.Payment.PaymentIdentifier, - &i.Payment.FailReason, - &i.IntentType, - &i.IntentPayload, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const filterPaymentsDesc = `-- name: FilterPaymentsDesc :many -SELECT - p.id, p.amount_msat, p.created_at, p.payment_identifier, p.fail_reason, - i.intent_type AS "intent_type", - i.intent_payload AS "intent_payload" -FROM payments p -LEFT JOIN payment_intents i ON i.payment_id = p.id -WHERE p.id > COALESCE($1, -1) - AND p.id < COALESCE($2, 9223372036854775807) - -- NOTE: We use non-nullable time params with Go-side defaults instead of - -- COALESCE, because COALESCE with text fallback causes type mismatch on - -- Postgres (timestamp vs text), and OR-based optional filters can prevent - -- the planner from using the created_at index. - AND p.created_at >= $3 - AND p.created_at <= $4 - AND ( - i.intent_type = $5 OR - $5 IS NULL OR i.intent_type IS NULL - ) -ORDER BY p.id DESC -LIMIT $6 -` - -type FilterPaymentsDescParams struct { - IndexOffsetGet sql.NullInt64 - IndexOffsetLet sql.NullInt64 - CreatedAfter time.Time - CreatedBefore time.Time - IntentType sql.NullInt16 - NumLimit int32 -} - -type FilterPaymentsDescRow struct { - Payment Payment - IntentType sql.NullInt16 - IntentPayload []byte -} - -func (q *Queries) FilterPaymentsDesc(ctx context.Context, arg FilterPaymentsDescParams) ([]FilterPaymentsDescRow, error) { - rows, err := q.db.QueryContext(ctx, filterPaymentsDesc, - arg.IndexOffsetGet, - arg.IndexOffsetLet, - arg.CreatedAfter, - arg.CreatedBefore, - arg.IntentType, - arg.NumLimit, - ) - if err != nil { - return nil, err - } - defer rows.Close() - var items []FilterPaymentsDescRow - for rows.Next() { - var i FilterPaymentsDescRow - if err := rows.Scan( - &i.Payment.ID, - &i.Payment.AmountMsat, - &i.Payment.CreatedAt, - &i.Payment.PaymentIdentifier, - &i.Payment.FailReason, - &i.IntentType, - &i.IntentPayload, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const insertHtlcAttempt = `-- name: InsertHtlcAttempt :one -INSERT INTO payment_htlc_attempts ( - payment_id, - attempt_index, - session_key, - attempt_time, - payment_hash, - first_hop_amount_msat, - route_total_time_lock, - route_total_amount, - route_source_key) -VALUES ( - $1, - $2, - $3, - $4, - $5, - $6, - $7, - $8, - $9) -RETURNING id -` - -type InsertHtlcAttemptParams struct { - PaymentID int64 - AttemptIndex int64 - SessionKey []byte - AttemptTime time.Time - PaymentHash []byte - FirstHopAmountMsat int64 - RouteTotalTimeLock int32 - RouteTotalAmount int64 - RouteSourceKey []byte -} - -func (q *Queries) InsertHtlcAttempt(ctx context.Context, arg InsertHtlcAttemptParams) (int64, error) { - row := q.db.QueryRowContext(ctx, insertHtlcAttempt, - arg.PaymentID, - arg.AttemptIndex, - arg.SessionKey, - arg.AttemptTime, - arg.PaymentHash, - arg.FirstHopAmountMsat, - arg.RouteTotalTimeLock, - arg.RouteTotalAmount, - arg.RouteSourceKey, - ) - var id int64 - err := row.Scan(&id) - return id, err -} - -const insertPayment = `-- name: InsertPayment :one -INSERT INTO payments ( - amount_msat, - created_at, - payment_identifier, - fail_reason) -VALUES ( - $1, - $2, - $3, - NULL -) -RETURNING id -` - -type InsertPaymentParams struct { - AmountMsat int64 - CreatedAt time.Time - PaymentIdentifier []byte -} - -// Insert a new payment and return its ID. -// When creating a payment we don't have a fail reason because we start the -// payment process. -func (q *Queries) InsertPayment(ctx context.Context, arg InsertPaymentParams) (int64, error) { - row := q.db.QueryRowContext(ctx, insertPayment, arg.AmountMsat, arg.CreatedAt, arg.PaymentIdentifier) - var id int64 - err := row.Scan(&id) - return id, err -} - -const insertPaymentAttemptFirstHopCustomRecord = `-- name: InsertPaymentAttemptFirstHopCustomRecord :exec -INSERT INTO payment_attempt_first_hop_custom_records ( - htlc_attempt_index, - key, - value -) -VALUES ( - $1, - $2, - $3 -) -` - -type InsertPaymentAttemptFirstHopCustomRecordParams struct { - HtlcAttemptIndex int64 - Key int64 - Value []byte -} - -func (q *Queries) InsertPaymentAttemptFirstHopCustomRecord(ctx context.Context, arg InsertPaymentAttemptFirstHopCustomRecordParams) error { - _, err := q.db.ExecContext(ctx, insertPaymentAttemptFirstHopCustomRecord, arg.HtlcAttemptIndex, arg.Key, arg.Value) - return err -} - -const insertPaymentDuplicateMig = `-- name: InsertPaymentDuplicateMig :one -INSERT INTO payment_duplicates ( - payment_id, - amount_msat, - created_at, - fail_reason, - settle_preimage, - settle_time -) -VALUES ( - $1, - $2, - $3, - $4, - $5, - $6 -) -RETURNING id -` - -type InsertPaymentDuplicateMigParams struct { - PaymentID int64 - AmountMsat int64 - CreatedAt time.Time - FailReason sql.NullInt32 - SettlePreimage []byte - SettleTime sql.NullTime -} - -// Insert a duplicate payment record into the payment_duplicates table and -// return its ID. -func (q *Queries) InsertPaymentDuplicateMig(ctx context.Context, arg InsertPaymentDuplicateMigParams) (int64, error) { - row := q.db.QueryRowContext(ctx, insertPaymentDuplicateMig, - arg.PaymentID, - arg.AmountMsat, - arg.CreatedAt, - arg.FailReason, - arg.SettlePreimage, - arg.SettleTime, - ) - var id int64 - err := row.Scan(&id) - return id, err -} - -const insertPaymentFirstHopCustomRecord = `-- name: InsertPaymentFirstHopCustomRecord :exec -INSERT INTO payment_first_hop_custom_records ( - payment_id, - key, - value -) -VALUES ( - $1, - $2, - $3 -) -` - -type InsertPaymentFirstHopCustomRecordParams struct { - PaymentID int64 - Key int64 - Value []byte -} - -func (q *Queries) InsertPaymentFirstHopCustomRecord(ctx context.Context, arg InsertPaymentFirstHopCustomRecordParams) error { - _, err := q.db.ExecContext(ctx, insertPaymentFirstHopCustomRecord, arg.PaymentID, arg.Key, arg.Value) - return err -} - -const insertPaymentHopCustomRecord = `-- name: InsertPaymentHopCustomRecord :exec -INSERT INTO payment_hop_custom_records ( - hop_id, - key, - value -) -VALUES ( - $1, - $2, - $3 -) -` - -type InsertPaymentHopCustomRecordParams struct { - HopID int64 - Key int64 - Value []byte -} - -func (q *Queries) InsertPaymentHopCustomRecord(ctx context.Context, arg InsertPaymentHopCustomRecordParams) error { - _, err := q.db.ExecContext(ctx, insertPaymentHopCustomRecord, arg.HopID, arg.Key, arg.Value) - return err -} - -const insertPaymentIntent = `-- name: InsertPaymentIntent :one -INSERT INTO payment_intents ( - payment_id, - intent_type, - intent_payload) -VALUES ( - $1, - $2, - $3 -) -RETURNING id -` - -type InsertPaymentIntentParams struct { - PaymentID int64 - IntentType int16 - IntentPayload []byte -} - -// Insert a payment intent for a given payment and return its ID. -func (q *Queries) InsertPaymentIntent(ctx context.Context, arg InsertPaymentIntentParams) (int64, error) { - row := q.db.QueryRowContext(ctx, insertPaymentIntent, arg.PaymentID, arg.IntentType, arg.IntentPayload) - var id int64 - err := row.Scan(&id) - return id, err -} - -const insertPaymentMig = `-- name: InsertPaymentMig :one -/* ───────────────────────────────────────────── - Migration-specific queries - - These queries are used ONLY for the one-time migration from KV to SQL. - ───────────────────────────────────────────── -*/ - -INSERT INTO payments ( - amount_msat, - created_at, - payment_identifier, - fail_reason) -VALUES ( - $1, - $2, - $3, - $4 -) -RETURNING id -` - -type InsertPaymentMigParams struct { - AmountMsat int64 - CreatedAt time.Time - PaymentIdentifier []byte - FailReason sql.NullInt32 -} - -// Migration-specific payment insert that allows setting fail_reason. -// Normal InsertPayment forces fail_reason to NULL since new payments -// aren't failed yet. During migration, we're inserting historical data -// that may already be failed. -func (q *Queries) InsertPaymentMig(ctx context.Context, arg InsertPaymentMigParams) (int64, error) { - row := q.db.QueryRowContext(ctx, insertPaymentMig, - arg.AmountMsat, - arg.CreatedAt, - arg.PaymentIdentifier, - arg.FailReason, - ) - var id int64 - err := row.Scan(&id) - return id, err -} - -const insertRouteHop = `-- name: InsertRouteHop :one -INSERT INTO payment_route_hops ( - htlc_attempt_index, - hop_index, - pub_key, - scid, - outgoing_time_lock, - amt_to_forward, - meta_data -) -VALUES ( - $1, - $2, - $3, - $4, - $5, - $6, - $7 -) -RETURNING id -` - -type InsertRouteHopParams struct { - HtlcAttemptIndex int64 - HopIndex int32 - PubKey []byte - Scid string - OutgoingTimeLock int32 - AmtToForward int64 - MetaData []byte -} - -func (q *Queries) InsertRouteHop(ctx context.Context, arg InsertRouteHopParams) (int64, error) { - row := q.db.QueryRowContext(ctx, insertRouteHop, - arg.HtlcAttemptIndex, - arg.HopIndex, - arg.PubKey, - arg.Scid, - arg.OutgoingTimeLock, - arg.AmtToForward, - arg.MetaData, - ) - var id int64 - err := row.Scan(&id) - return id, err -} - -const insertRouteHopAmp = `-- name: InsertRouteHopAmp :exec -INSERT INTO payment_route_hop_amp ( - hop_id, - root_share, - set_id, - child_index -) -VALUES ( - $1, - $2, - $3, - $4 -) -` - -type InsertRouteHopAmpParams struct { - HopID int64 - RootShare []byte - SetID []byte - ChildIndex int32 -} - -func (q *Queries) InsertRouteHopAmp(ctx context.Context, arg InsertRouteHopAmpParams) error { - _, err := q.db.ExecContext(ctx, insertRouteHopAmp, - arg.HopID, - arg.RootShare, - arg.SetID, - arg.ChildIndex, - ) - return err -} - -const insertRouteHopBlinded = `-- name: InsertRouteHopBlinded :exec -INSERT INTO payment_route_hop_blinded ( - hop_id, - encrypted_data, - blinding_point, - blinded_path_total_amt -) -VALUES ( - $1, - $2, - $3, - $4 -) -` - -type InsertRouteHopBlindedParams struct { - HopID int64 - EncryptedData []byte - BlindingPoint []byte - BlindedPathTotalAmt sql.NullInt64 -} - -func (q *Queries) InsertRouteHopBlinded(ctx context.Context, arg InsertRouteHopBlindedParams) error { - _, err := q.db.ExecContext(ctx, insertRouteHopBlinded, - arg.HopID, - arg.EncryptedData, - arg.BlindingPoint, - arg.BlindedPathTotalAmt, - ) - return err -} - -const insertRouteHopMpp = `-- name: InsertRouteHopMpp :exec -INSERT INTO payment_route_hop_mpp ( - hop_id, - payment_addr, - total_msat -) -VALUES ( - $1, - $2, - $3 -) -` - -type InsertRouteHopMppParams struct { - HopID int64 - PaymentAddr []byte - TotalMsat int64 -} - -func (q *Queries) InsertRouteHopMpp(ctx context.Context, arg InsertRouteHopMppParams) error { - _, err := q.db.ExecContext(ctx, insertRouteHopMpp, arg.HopID, arg.PaymentAddr, arg.TotalMsat) - return err -} - -const settleAttempt = `-- name: SettleAttempt :exec -INSERT INTO payment_htlc_attempt_resolutions ( - attempt_index, - resolution_time, - resolution_type, - settle_preimage -) -VALUES ( - $1, - $2, - $3, - $4 -) -` - -type SettleAttemptParams struct { - AttemptIndex int64 - ResolutionTime time.Time - ResolutionType int32 - SettlePreimage []byte -} - -func (q *Queries) SettleAttempt(ctx context.Context, arg SettleAttemptParams) error { - _, err := q.db.ExecContext(ctx, settleAttempt, - arg.AttemptIndex, - arg.ResolutionTime, - arg.ResolutionType, - arg.SettlePreimage, - ) - return err -} diff --git a/payments/db/migration1/test_harness.go b/payments/db/migration1/test_harness.go deleted file mode 100644 index b25867a4f..000000000 --- a/payments/db/migration1/test_harness.go +++ /dev/null @@ -1,26 +0,0 @@ -package migration1 - -import ( - "testing" - - "github.com/lightningnetwork/lnd/lntypes" -) - -// TestHarness provides implementation-specific test utilities for the payments -// database. Different database backends (KV, SQL) have different internal -// structures and indexing mechanisms, so this interface allows tests to verify -// implementation-specific behavior without coupling the test logic to a -// particular backend. -type TestHarness interface { - // AssertPaymentIndex checks that a payment is correctly indexed. - // For KV: verifies the payment index bucket entry exists and points - // to the correct payment hash. - // For SQL: no-op (SQL doesn't use a separate index bucket). - AssertPaymentIndex(t *testing.T, expectedHash lntypes.Hash) - - // AssertNoIndex checks that an index for a sequence number doesn't - // exist. - // For KV: verifies the index bucket entry is deleted. - // For SQL: no-op. - AssertNoIndex(t *testing.T, seqNr uint64) -} diff --git a/payments/db/migration1/test_postgres.go b/payments/db/migration1/test_postgres.go deleted file mode 100644 index 7055fb885..000000000 --- a/payments/db/migration1/test_postgres.go +++ /dev/null @@ -1,94 +0,0 @@ -//go:build test_db_postgres && !test_db_sqlite - -package migration1 - -import ( - "testing" - - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/payments/db/migration1/sqlc" - "github.com/lightningnetwork/lnd/sqldb" - "github.com/stretchr/testify/require" -) - -// NewTestDB is a helper function that creates a SQLStore backed by a SQL -// database for testing. -func NewTestDB(t testing.TB, opts ...OptionModifier) (DB, TestHarness) { - db := NewTestDBWithFixture(t, nil, opts...) - return db, &noopTestHarness{} -} - -// NewTestDBFixture creates a new sqldb.TestPgFixture for testing purposes. -func NewTestDBFixture(t *testing.T) *sqldb.TestPgFixture { - pgFixture := sqldb.NewTestPgFixture( - t, sqldb.DefaultPostgresFixtureLifetime, - ) - t.Cleanup(func() { - pgFixture.TearDown(t) - }) - return pgFixture -} - -// NewTestDBWithFixture is a helper function that creates a SQLStore backed by a -// SQL database for testing. -func NewTestDBWithFixture(t testing.TB, - pgFixture *sqldb.TestPgFixture, opts ...OptionModifier) DB { - - var querier BatchedSQLQueries - if pgFixture == nil { - querier = newBatchQuerier(t) - } else { - querier = newBatchQuerierWithFixture(t, pgFixture) - } - - store, err := NewSQLStore( - &SQLStoreConfig{ - QueryCfg: sqldb.DefaultPostgresConfig(), - }, querier, opts..., - ) - require.NoError(t, err) - - return store -} - -// newBatchQuerier creates a new BatchedSQLQueries instance for testing -// using a PostgreSQL database fixture. -func newBatchQuerier(t testing.TB) BatchedSQLQueries { - pgFixture := sqldb.NewTestPgFixture( - t, sqldb.DefaultPostgresFixtureLifetime, - ) - t.Cleanup(func() { - pgFixture.TearDown(t) - }) - - return newBatchQuerierWithFixture(t, pgFixture) -} - -// newBatchQuerierWithFixture creates a new BatchedSQLQueries instance for -// testing using a PostgreSQL database fixture. -func newBatchQuerierWithFixture(t testing.TB, - pgFixture *sqldb.TestPgFixture) BatchedSQLQueries { - - rawDB := sqldb.NewTestPostgresDB(t, pgFixture).BaseDB.DB - - return &testBatchedSQLQueries{ - db: rawDB, - Queries: sqlc.New(rawDB), - } -} - -// noopTestHarness is the SQL test harness implementation. Since SQL doesn't -// use a separate payment index bucket like KV, these assertions are no-ops. -type noopTestHarness struct{} - -// AssertPaymentIndex is a no-op for SQL implementations. -func (h *noopTestHarness) AssertPaymentIndex(t *testing.T, - expectedHash lntypes.Hash) { - - // No-op: SQL doesn't use a separate index bucket. -} - -// AssertNoIndex is a no-op for SQL implementations. -func (h *noopTestHarness) AssertNoIndex(t *testing.T, seqNr uint64) { - // No-op: SQL doesn't use a separate index bucket. -} diff --git a/payments/db/migration1/test_sql.go b/payments/db/migration1/test_sql.go deleted file mode 100644 index f2d4d078b..000000000 --- a/payments/db/migration1/test_sql.go +++ /dev/null @@ -1,58 +0,0 @@ -//go:build test_db_postgres || test_db_sqlite - -package migration1 - -import ( - "context" - "database/sql" - "testing" - - "github.com/lightningnetwork/lnd/payments/db/migration1/sqlc" - "github.com/lightningnetwork/lnd/sqldb" - "github.com/stretchr/testify/require" -) - -// setupTestSQLDB creates a SQLStore-backed test database. -func setupTestSQLDB(t testing.TB, opts ...OptionModifier) *SQLStore { - t.Helper() - - db, _ := NewTestDB(t, opts...) - sqlStore, ok := db.(*SQLStore) - require.True(t, ok) - - return sqlStore -} - -// testBatchedSQLQueries is a simple implementation of BatchedSQLQueries for -// testing. -type testBatchedSQLQueries struct { - db *sql.DB - *sqlc.Queries -} - -// ExecTx implements the transaction execution logic. -func (t *testBatchedSQLQueries) ExecTx(ctx context.Context, - txOpts sqldb.TxOptions, txBody func(SQLQueries) error, - reset func()) error { - - sqlOptions := sql.TxOptions{ - Isolation: sql.LevelSerializable, - ReadOnly: txOpts.ReadOnly(), - } - - tx, err := t.db.BeginTx(ctx, &sqlOptions) - if err != nil { - return err - } - - reset() - queries := sqlc.New(tx) - - if err := txBody(queries); err != nil { - _ = tx.Rollback() - - return err - } - - return tx.Commit() -} diff --git a/payments/db/migration1/test_sqlite.go b/payments/db/migration1/test_sqlite.go deleted file mode 100644 index b84c9c2d9..000000000 --- a/payments/db/migration1/test_sqlite.go +++ /dev/null @@ -1,73 +0,0 @@ -//go:build !test_db_postgres && test_db_sqlite - -package migration1 - -import ( - "testing" - - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/payments/db/migration1/sqlc" - "github.com/lightningnetwork/lnd/sqldb" - "github.com/stretchr/testify/require" -) - -// NewTestDB is a helper function that creates a SQLStore backed by a SQL -// database for testing. -func NewTestDB(t testing.TB, opts ...OptionModifier) (DB, TestHarness) { - db := NewTestDBWithFixture(t, nil, opts...) - return db, &noopTestHarness{} -} - -// NewTestDBFixture is a no-op for the sqlite build. -func NewTestDBFixture(_ *testing.T) *sqldb.TestPgFixture { - return nil -} - -// NewTestDBWithFixture is a helper function that creates a SQLStore backed by a -// SQL database for testing. -func NewTestDBWithFixture(t testing.TB, _ *sqldb.TestPgFixture, - opts ...OptionModifier) DB { - - store, err := NewSQLStore( - &SQLStoreConfig{ - QueryCfg: sqldb.DefaultSQLiteConfig(), - }, newBatchQuerier(t), opts..., - ) - require.NoError(t, err) - return store -} - -// newBatchQuerier creates a new BatchedSQLQueries instance for testing -// using a SQLite database. -func newBatchQuerier(t testing.TB) BatchedSQLQueries { - return newBatchQuerierWithFixture(t, nil) -} - -// newBatchQuerierWithFixture creates a new BatchedSQLQueries instance for -// testing using a SQLite database. -func newBatchQuerierWithFixture(t testing.TB, - _ *sqldb.TestPgFixture) BatchedSQLQueries { - - rawDB := sqldb.NewTestSqliteDB(t).BaseDB.DB - - return &testBatchedSQLQueries{ - db: rawDB, - Queries: sqlc.New(rawDB), - } -} - -// noopTestHarness is the SQL test harness implementation. Since SQL doesn't -// use a separate payment index bucket like KV, these assertions are no-ops. -type noopTestHarness struct{} - -// AssertPaymentIndex is a no-op for SQL implementations. -func (h *noopTestHarness) AssertPaymentIndex(t *testing.T, - expectedHash lntypes.Hash) { - - // No-op: SQL doesn't use a separate index bucket. -} - -// AssertNoIndex is a no-op for SQL implementations. -func (h *noopTestHarness) AssertNoIndex(t *testing.T, seqNr uint64) { - // No-op: SQL doesn't use a separate index bucket. -} diff --git a/payments/db/migration1/testdata/README.md b/payments/db/migration1/testdata/README.md deleted file mode 100644 index 08c5fb06b..000000000 --- a/payments/db/migration1/testdata/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Payment Migration External Testdata - -This directory holds a real `channel.db` (bbolt) or `channel.sqlite` file for -testing the payments KV to SQL migration locally. You can also point the test -at an existing Postgres-backed kvdb instance. - -## How to use - -1. Copy your `channel.db` or `channel.sqlite` file into this folder. -2. Edit `migration_external_test.go`: - - ```go - // Comment out this line to enable the test - t.Skipf("skipping test meant for local debugging only") - - // Set to your database filename - const fileName = "channel.db" // or "channel.sqlite" - ``` - -3. Run the test: - - ```bash - # For Postgres backend - go test -v -tags="test_db_postgres" -run TestMigrationWithExternalDB - ``` - -## SQLite kvdb source - -To migrate from a `channel.sqlite` file, run with the `kvdb_sqlite` build -tag: - -```bash -go test -v -tags="test_db_sqlite kvdb_sqlite" \ - -run TestMigrationWithExternalDB -``` - -## Postgres kvdb source - -To migrate from an existing Postgres-backed kvdb instance, edit -`postgresKVDSN` in `migration_external_test.go` (set it non-empty), then -run with the `kvdb_postgres` build tag: - -```bash -go test -v -tags="kvdb_postgres test_db_postgres" \ - -run TestMigrationWithExternalDB -``` - -## Notes - -- The external database is opened read-only. -- The test creates a fresh SQL database for each run. -- Do not commit production data; keep the file local. diff --git a/payments/db/options.go b/payments/db/options.go index efceb2f9b..9e98aafa3 100644 --- a/payments/db/options.go +++ b/payments/db/options.go @@ -4,12 +4,17 @@ package paymentsdb type StoreOptions struct { // NoMigration allows to open the database in readonly mode NoMigration bool + + // KeepFailedPaymentAttempts is a flag that determines whether to keep + // failed payment attempts for a settled payment in the db. + KeepFailedPaymentAttempts bool } // DefaultOptions returns a StoreOptions populated with default values. func DefaultOptions() *StoreOptions { return &StoreOptions{ - NoMigration: false, + KeepFailedPaymentAttempts: false, + NoMigration: false, } } @@ -17,6 +22,13 @@ func DefaultOptions() *StoreOptions { // StoreOptions. type OptionModifier func(*StoreOptions) +// WithKeepFailedPaymentAttempts sets the KeepFailedPaymentAttempts to n. +func WithKeepFailedPaymentAttempts(n bool) OptionModifier { + return func(o *StoreOptions) { + o.KeepFailedPaymentAttempts = n + } +} + // WithNoMigration allows the database to be opened in read only mode by // disabling migrations. func WithNoMigration(b bool) OptionModifier { diff --git a/payments/db/payment.go b/payments/db/payment.go index 0c928b4d6..f6d998344 100644 --- a/payments/db/payment.go +++ b/payments/db/payment.go @@ -90,8 +90,7 @@ type PaymentCreationInfo struct { // FirstHopCustomRecords are the TLV records that are to be sent to the // first hop of this payment. These records will be transmitted via the - // wire message (UpdateAddHTLC) only and therefore do not affect the - // onion payload size. + // wire message only and therefore do not affect the onion payload size. FirstHopCustomRecords lnwire.CustomRecords } @@ -416,6 +415,7 @@ func (m *MPPayment) InFlightHTLCs() []HTLCAttempt { func (m *MPPayment) GetAttempt(id uint64) (*HTLCAttempt, error) { // TODO(yy): iteration can be slow, make it into a tree or use BS. for _, htlc := range m.HTLCs { + htlc := htlc if htlc.AttemptID == id { return &htlc, nil } @@ -743,13 +743,6 @@ func verifyAttempt(payment *MPPayment, attempt *HTLCAttemptInfo) error { // in the split payment is correct. isBlinded := len(attempt.Route.FinalHop().EncryptedData) != 0 - // For blinded payments, the last hop must set the total amount. - if isBlinded { - if attempt.Route.FinalHop().TotalAmtMsat == 0 { - return ErrBlindedPaymentMissingTotalAmount - } - } - // Make sure any existing shards match the new one with regards // to MPP options. mpp := attempt.Route.FinalHop().MPP @@ -761,7 +754,6 @@ func verifyAttempt(payment *MPPayment, attempt *HTLCAttemptInfo) error { for _, h := range payment.InFlightHTLCs() { hMpp := h.Route.FinalHop().MPP - hBlinded := len(h.Route.FinalHop().EncryptedData) != 0 // If this is a blinded payment, then no existing HTLCs // should have MPP records. @@ -769,13 +761,6 @@ func verifyAttempt(payment *MPPayment, attempt *HTLCAttemptInfo) error { return ErrMPPRecordInBlindedPayment } - // If the payment is blinded (previous attempts used blinded - // paths) and the attempt is not, or vice versa, return an - // error. - if isBlinded != hBlinded { - return ErrMixedBlindedAndNonBlindedPayments - } - // If this is a blinded payment, then we just need to // check that the TotalAmtMsat field for this shard // is equal to that of any other shard in the same diff --git a/payments/db/payment_status_test.go b/payments/db/payment_status_test.go index b5c762d07..1bb4dc388 100644 --- a/payments/db/payment_status_test.go +++ b/payments/db/payment_status_test.go @@ -168,6 +168,7 @@ func TestDecidePaymentStatus(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() @@ -227,6 +228,7 @@ func TestPaymentStatusActions(t *testing.T) { } for i, tc := range testCases { + i, tc := i, tc ps := tc.status name := fmt.Sprintf("test_%d_%s", i, ps.String()) diff --git a/payments/db/payment_test.go b/payments/db/payment_test.go index c87efa9f7..534a1b1e5 100644 --- a/payments/db/payment_test.go +++ b/payments/db/payment_test.go @@ -6,19 +6,17 @@ import ( "errors" "fmt" "io" - "math" "reflect" "testing" "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing/route" - "github.com/lightningnetwork/lnd/tlv" "github.com/stretchr/testify/require" ) @@ -60,10 +58,7 @@ var ( ChannelID: 12345, OutgoingTimeLock: 111, AmtToForward: 555, - - // Only tlv payloads are now supported in LND therefore we set - // LegacyPayload to false. - LegacyPayload: false, + LegacyPayload: true, } testRoute = route.Route{ @@ -82,39 +77,28 @@ var ( SourcePubKey: vertex, Hops: []*route.Hop{ { - PubKeyBytes: vertex, - EncryptedData: []byte{1, 3, 3}, - BlindingPoint: pub, + PubKeyBytes: vertex, + ChannelID: 9876, + OutgoingTimeLock: 120, + AmtToForward: 900, + EncryptedData: []byte{1, 3, 3}, + BlindingPoint: pub, }, { PubKeyBytes: vertex, EncryptedData: []byte{3, 2, 1}, }, { - // Final hop must have AmtToForward, - // OutgoingTimeLock, and TotalAmtMsat per - // BOLT spec. We use the correct values here - // although it is not tested in this test. PubKeyBytes: vertex, - EncryptedData: []byte{2, 2, 2}, - AmtToForward: 1000, + Metadata: []byte{4, 5, 6}, + AmtToForward: 500, OutgoingTimeLock: 100, - TotalAmtMsat: 1000, + TotalAmtMsat: 500, }, }, } ) -// htlcStatus is a helper structure used in tests to track the status of an HTLC -// attempt, including whether it was settled or failed. -type htlcStatus struct { - *HTLCAttemptInfo - settle *lntypes.Preimage - settleTime time.Time - failure *HTLCFailReason - failTime time.Time -} - // payment is a helper structure that holds basic information on a test payment, // such as the payment id, the status and the total number of HTLCs attempted. type payment struct { @@ -129,36 +113,29 @@ type payment struct { func createTestPayments(t *testing.T, p DB, payments []*payment) { t.Helper() - ctx := t.Context() - attemptID := uint64(0) for i := 0; i < len(payments); i++ { - preimg := genPreimage(t) - - rhash := sha256.Sum256(preimg[:]) - info := genPaymentCreationInfo(t, rhash) + info, attempt, preimg, err := genInfo(t) + require.NoError(t, err, "unable to generate htlc message") // Set the payment id accordingly in the payments slice. payments[i].id = info.PaymentIdentifier - attempt := genAttemptWithHash( - t, attemptID, genSessionKey(t), rhash, - ) - + attempt.AttemptID = attemptID attemptID++ // Init the payment. - err := p.InitPayment(ctx, info.PaymentIdentifier, info) + err = p.InitPayment(info.PaymentIdentifier, info) require.NoError(t, err, "unable to send htlc message") // Register and fail the first attempt for all payments. - _, err = p.RegisterAttempt(ctx, info.PaymentIdentifier, attempt) + _, err = p.RegisterAttempt(info.PaymentIdentifier, attempt) require.NoError(t, err, "unable to send htlc message") htlcFailure := HTLCFailUnreadable _, err = p.FailAttempt( - ctx, info.PaymentIdentifier, attempt.AttemptID, + info.PaymentIdentifier, attempt.AttemptID, &HTLCFailInfo{ Reason: htlcFailure, }, @@ -171,20 +148,18 @@ func createTestPayments(t *testing.T, p DB, payments []*payment) { // Depending on the test case, fail or succeed the next // attempt. - attempt = genAttemptWithHash( - t, attemptID, genSessionKey(t), rhash, - ) + attempt.AttemptID = attemptID attemptID++ - _, err = p.RegisterAttempt(ctx, info.PaymentIdentifier, attempt) + _, err = p.RegisterAttempt(info.PaymentIdentifier, attempt) require.NoError(t, err, "unable to send htlc message") switch payments[i].status { // Fail the attempt and the payment overall. case StatusFailed: htlcFailure := HTLCFailUnreadable - _, err := p.FailAttempt( - ctx, info.PaymentIdentifier, attempt.AttemptID, + _, err = p.FailAttempt( + info.PaymentIdentifier, attempt.AttemptID, &HTLCFailInfo{ Reason: htlcFailure, }, @@ -192,15 +167,14 @@ func createTestPayments(t *testing.T, p DB, payments []*payment) { require.NoError(t, err, "unable to fail htlc") failReason := FailureReasonNoRoute - _, err = p.Fail( - ctx, info.PaymentIdentifier, failReason, - ) + _, err = p.Fail(info.PaymentIdentifier, + failReason) require.NoError(t, err, "unable to fail payment hash") // Settle the attempt case StatusSucceeded: _, err := p.SettleAttempt( - ctx, info.PaymentIdentifier, attempt.AttemptID, + info.PaymentIdentifier, attempt.AttemptID, &HTLCSettleInfo{ Preimage: preimg, }, @@ -234,71 +208,69 @@ func assertRouteEqual(t *testing.T, a, b *route.Route) error { // assertPaymentInfo retrieves the payment referred to by hash and verifies the // expected values. func assertPaymentInfo(t *testing.T, p DB, hash lntypes.Hash, - c *PaymentCreationInfo, f *FailureReason, a *htlcStatus) { + c *PaymentCreationInfo, f *FailureReason, + a *htlcStatus) { t.Helper() - payment, err := p.FetchPayment(t.Context(), hash) - require.NoError(t, err) - require.Equal(t, c, payment.Info, "PaymentCreationInfos don't match") + payment, err := p.FetchPayment(hash) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(payment.Info, c) { + t.Fatalf("PaymentCreationInfos don't match: %v vs %v", + spew.Sdump(payment.Info), spew.Sdump(c)) + } if f != nil { - require.NotNil( - t, payment.FailureReason, "expected failure reason", - ) - require.Equal( - t, *f, *payment.FailureReason, - "unexpected failure reason", - ) + if *payment.FailureReason != *f { + t.Fatal("unexpected failure reason") + } } else { - require.Nil( - t, payment.FailureReason, "expected no failure reason", - ) + if payment.FailureReason != nil { + t.Fatal("unexpected failure reason") + } } if a == nil { - require.Empty(t, payment.HTLCs, "expected no htlcs") + if len(payment.HTLCs) > 0 { + t.Fatal("expected no htlcs") + } + return } - require.GreaterOrEqual(t, len(payment.HTLCs), int(a.AttemptID)+1, - "HTLC with attempt ID %v not found", a.AttemptID) htlc := payment.HTLCs[a.AttemptID] - require.NoError(t, assertRouteEqual(t, &htlc.Route, &a.Route), - "routes do not match") - require.Equal(t, a.AttemptID, htlc.AttemptID, "unexpected attempt ID") + if err := assertRouteEqual(t, &htlc.Route, &a.Route); err != nil { + t.Fatal("routes do not match") + } + + if htlc.AttemptID != a.AttemptID { + t.Fatalf("unnexpected attempt ID %v, expected %v", + htlc.AttemptID, a.AttemptID) + } if a.failure != nil { - require.NotNil(t, htlc.Failure, "expected HTLC to be failed") - require.Equal(t, *a.failure, htlc.Failure.Reason, - "expected HTLC failure") - } else { - require.Nil(t, htlc.Failure, "expected no HTLC failure") + if htlc.Failure == nil { + t.Fatalf("expected HTLC to be failed") + } + + if htlc.Failure.Reason != *a.failure { + t.Fatalf("expected HTLC failure %v, had %v", + *a.failure, htlc.Failure.Reason) + } + } else if htlc.Failure != nil { + t.Fatalf("expected no HTLC failure") } if a.settle != nil { - require.Equal( - t, *a.settle, htlc.Settle.Preimage, - "expected HTLC settle preimage", - ) - } else { - require.Nil(t, htlc.Settle, "expected no settle info") - } - - if !a.settleTime.IsZero() { - // Normalize to UTC to ensure consistent timezone comparison. - require.Equal( - t, a.settleTime.UTC(), htlc.Settle.SettleTime.UTC(), - "SettleTimes don't match", - ) - } - - if !a.failTime.IsZero() { - // Normalize to UTC to ensure consistent timezone comparison. - require.Equal( - t, htlc.Failure.FailTime.UTC(), a.failTime.UTC(), - "FailTimes don't match", - ) + if htlc.Settle.Preimage != *a.settle { + t.Fatalf("Preimages don't match: %x vs %x", + htlc.Settle.Preimage, a.settle) + } + } else if htlc.Settle != nil { + t.Fatal("expected no settle info") } } @@ -309,9 +281,7 @@ func assertDBPaymentstatus(t *testing.T, p DB, hash lntypes.Hash, t.Helper() - ctx := t.Context() - - payment, err := p.FetchPayment(ctx, hash) + payment, err := p.FetchPayment(hash) if errors.Is(err, ErrPaymentNotInitiated) { return } @@ -364,90 +334,42 @@ func assertDBPayments(t *testing.T, paymentDB DB, payments []*payment) { } // genPreimage generates a random preimage. -func genPreimage(t *testing.T) lntypes.Preimage { +func genPreimage(t *testing.T) ([32]byte, error) { t.Helper() var preimage [32]byte - _, err := io.ReadFull(rand.Reader, preimage[:]) - require.NoError(t, err, "unable to generate preimage") - - return preimage -} - -// genSessionKey generates a new random private key for use as a session key. -func genSessionKey(t *testing.T) *btcec.PrivateKey { - t.Helper() - - key, err := btcec.NewPrivateKey() - require.NoError(t, err) - - return key -} - -// genPaymentCreationInfo generates a payment creation info. -func genPaymentCreationInfo(t *testing.T, - paymentHash lntypes.Hash) *PaymentCreationInfo { - - t.Helper() - - // Add constant first hop custom records for testing for testing - // purposes. - firstHopCustomRecords := lnwire.CustomRecords{ - lnwire.MinCustomRecordsTlvType + 1: []byte("test_record_1"), - lnwire.MinCustomRecordsTlvType + 2: []byte("test_record_2"), - lnwire.MinCustomRecordsTlvType + 3: []byte{ - 0x01, 0x02, 0x03, 0x04, 0x05, - }, + if _, err := io.ReadFull(rand.Reader, preimage[:]); err != nil { + return preimage, err } - return &PaymentCreationInfo{ - PaymentIdentifier: paymentHash, - Value: testRoute.ReceiverAmt(), - CreationTime: time.Unix(time.Now().Unix(), 0), - PaymentRequest: []byte("hola"), - FirstHopCustomRecords: firstHopCustomRecords, - } + return preimage, nil } -// genPreimageAndHash generates a random preimage and its corresponding hash. -func genPreimageAndHash(t *testing.T) (lntypes.Preimage, lntypes.Hash) { - t.Helper() +// genInfo generates a payment creation info, an attempt info and a preimage. +func genInfo(t *testing.T) (*PaymentCreationInfo, *HTLCAttemptInfo, + lntypes.Preimage, error) { - preimage := genPreimage(t) + preimage, err := genPreimage(t) + if err != nil { + return nil, nil, preimage, fmt.Errorf("unable to "+ + "generate preimage: %v", err) + } rhash := sha256.Sum256(preimage[:]) var hash lntypes.Hash copy(hash[:], rhash[:]) - return preimage, hash -} - -// genAttemptWithPreimage generates an HTLC attempt and returns both the -// attempt and preimage. -func genAttemptWithHash(t *testing.T, attemptID uint64, - sessionKey *btcec.PrivateKey, hash lntypes.Hash) *HTLCAttemptInfo { - - t.Helper() - attempt, err := NewHtlcAttempt( - attemptID, sessionKey, *testRoute.Copy(), time.Time{}, - &hash, + 0, priv, *testRoute.Copy(), time.Time{}, &hash, ) - require.NoError(t, err, "unable to generate htlc attempt") + require.NoError(t, err) - return &attempt.HTLCAttemptInfo -} - -// genInfo generates a payment creation info and the corresponding preimage. -func genInfo(t *testing.T) (*PaymentCreationInfo, lntypes.Preimage) { - t.Helper() - - preimage, _ := genPreimageAndHash(t) - - rhash := sha256.Sum256(preimage[:]) - creationInfo := genPaymentCreationInfo(t, rhash) - - return creationInfo, preimage + return &PaymentCreationInfo{ + PaymentIdentifier: rhash, + Value: testRoute.ReceiverAmt(), + CreationTime: time.Unix(time.Now().Unix(), 0), + PaymentRequest: []byte("hola"), + }, &attempt.HTLCAttemptInfo, preimage, nil } // TestDeleteFailedAttempts checks that DeleteFailedAttempts properly removes @@ -455,7 +377,20 @@ func genInfo(t *testing.T) (*PaymentCreationInfo, lntypes.Preimage) { func TestDeleteFailedAttempts(t *testing.T) { t.Parallel() - paymentDB, _ := NewTestDB(t) + t.Run("keep failed payment attempts", func(t *testing.T) { + testDeleteFailedAttempts(t, true) + }) + t.Run("remove failed payment attempts", func(t *testing.T) { + testDeleteFailedAttempts(t, false) + }) +} + +// testDeleteFailedAttempts tests the DeleteFailedAttempts method with the +// given keepFailedPaymentAttempts flag as argument. +func testDeleteFailedAttempts(t *testing.T, keepFailedPaymentAttempts bool) { + paymentDB := NewTestDB( + t, WithKeepFailedPaymentAttempts(keepFailedPaymentAttempts), + ) // Register three payments: // All payments will have one failed HTLC attempt and one HTLC attempt @@ -485,63 +420,72 @@ func TestDeleteFailedAttempts(t *testing.T) { // Calling DeleteFailedAttempts on a failed payment should delete all // HTLCs. - require.NoError(t, paymentDB.DeleteFailedAttempts( - t.Context(), payments[0].id, - )) + require.NoError(t, paymentDB.DeleteFailedAttempts(payments[0].id)) - // Expect all HTLCs to be deleted. - payments[0].htlcs = 0 + // Expect all HTLCs to be deleted if the config is set to delete them. + if !keepFailedPaymentAttempts { + payments[0].htlcs = 0 + } assertDBPayments(t, paymentDB, payments) // Calling DeleteFailedAttempts on an in-flight payment should return // an error. - err := paymentDB.DeleteFailedAttempts( - t.Context(), payments[1].id, - ) - require.Error(t, err) + // + // NOTE: In case the option keepFailedPaymentAttempts is set no delete + // operation are performed in general therefore we do NOT expect an + // error in this case. + if keepFailedPaymentAttempts { + require.NoError( + t, paymentDB.DeleteFailedAttempts(payments[1].id), + ) + } else { + require.Error(t, paymentDB.DeleteFailedAttempts(payments[1].id)) + } // Since DeleteFailedAttempts returned an error, we should expect the // payment to be unchanged. assertDBPayments(t, paymentDB, payments) // Cleaning up a successful payment should remove failed htlcs. - require.NoError(t, paymentDB.DeleteFailedAttempts( - t.Context(), payments[2].id, - )) + require.NoError(t, paymentDB.DeleteFailedAttempts(payments[2].id)) - // Expect all HTLCs except for the settled one to be deleted. - payments[2].htlcs = 1 + // Expect all HTLCs except for the settled one to be deleted if the + // config is set to delete them. + if !keepFailedPaymentAttempts { + payments[2].htlcs = 1 + } assertDBPayments(t, paymentDB, payments) - // Attempting to cleanup a non-existent payment returns an error. - require.Error( - t, paymentDB.DeleteFailedAttempts( - t.Context(), lntypes.ZeroHash, - ), - ) + // NOTE: In case the option keepFailedPaymentAttempts is set no delete + // operation are performed in general therefore we do NOT expect an + // error in this case. + if keepFailedPaymentAttempts { + // DeleteFailedAttempts is ignored, even for non-existent + // payments, if the control tower is configured to keep failed + // HTLCs. + require.NoError( + t, paymentDB.DeleteFailedAttempts(lntypes.ZeroHash), + ) + } else { + // Attempting to cleanup a non-existent payment returns an + // error. + require.Error( + t, paymentDB.DeleteFailedAttempts(lntypes.ZeroHash), + ) + } } // TestMPPRecordValidation tests MPP record validation. func TestMPPRecordValidation(t *testing.T) { t.Parallel() - ctx := t.Context() + paymentDB := NewTestDB(t) - paymentDB, _ := NewTestDB(t) - - preimg := genPreimage(t) - - rhash := sha256.Sum256(preimg[:]) - info := genPaymentCreationInfo(t, rhash) - - attemptID := uint64(0) - - attempt := genAttemptWithHash( - t, attemptID, genSessionKey(t), rhash, - ) + info, attempt, _, err := genInfo(t) + require.NoError(t, err, "unable to generate htlc message") // Init the payment. - err := paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) require.NoError(t, err, "unable to send htlc message") // Create three unique attempts we'll use for the test, and @@ -554,74 +498,50 @@ func TestMPPRecordValidation(t *testing.T) { info.Value, [32]byte{1}, ) - _, err = paymentDB.RegisterAttempt(ctx, info.PaymentIdentifier, attempt) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, attempt) require.NoError(t, err, "unable to send htlc message") // Now try to register a non-MPP attempt, which should fail. - attemptID++ - attempt2 := genAttemptWithHash( - t, attemptID, genSessionKey(t), rhash, - ) - - attempt2.Route.FinalHop().MPP = nil - - _, err = paymentDB.RegisterAttempt( - ctx, info.PaymentIdentifier, attempt2, - ) + b := *attempt + b.AttemptID = 1 + b.Route.FinalHop().MPP = nil + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, &b) require.ErrorIs(t, err, ErrMPPayment) // Try to register attempt one with a different payment address. - attempt2.Route.FinalHop().MPP = record.NewMPP( + b.Route.FinalHop().MPP = record.NewMPP( info.Value, [32]byte{2}, ) - _, err = paymentDB.RegisterAttempt( - ctx, info.PaymentIdentifier, attempt2, - ) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, &b) require.ErrorIs(t, err, ErrMPPPaymentAddrMismatch) // Try registering one with a different total amount. - attempt2.Route.FinalHop().MPP = record.NewMPP( + b.Route.FinalHop().MPP = record.NewMPP( info.Value/2, [32]byte{1}, ) - _, err = paymentDB.RegisterAttempt( - ctx, info.PaymentIdentifier, attempt2, - ) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, &b) require.ErrorIs(t, err, ErrMPPTotalAmountMismatch) // Create and init a new payment. This time we'll check that we cannot // register an MPP attempt if we already registered a non-MPP one. - preimg = genPreimage(t) + info, attempt, _, err = genInfo(t) + require.NoError(t, err, "unable to generate htlc message") - rhash = sha256.Sum256(preimg[:]) - info = genPaymentCreationInfo(t, rhash) - - attemptID++ - attempt = genAttemptWithHash( - t, attemptID, genSessionKey(t), rhash, - ) - - err = paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) require.NoError(t, err, "unable to send htlc message") attempt.Route.FinalHop().MPP = nil - _, err = paymentDB.RegisterAttempt( - ctx, info.PaymentIdentifier, attempt, - ) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, attempt) require.NoError(t, err, "unable to send htlc message") // Attempt to register an MPP attempt, which should fail. - attemptID++ - attempt2 = genAttemptWithHash( - t, attemptID, genSessionKey(t), rhash, - ) - - attempt2.Route.FinalHop().MPP = record.NewMPP( + b = *attempt + b.AttemptID = 1 + b.Route.FinalHop().MPP = record.NewMPP( info.Value, [32]byte{1}, ) - _, err = paymentDB.RegisterAttempt( - ctx, info.PaymentIdentifier, attempt2, - ) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, &b) require.ErrorIs(t, err, ErrNonMPPayment) } @@ -630,9 +550,7 @@ func TestMPPRecordValidation(t *testing.T) { func TestDeleteSinglePayment(t *testing.T) { t.Parallel() - ctx := t.Context() - - paymentDB, _ := NewTestDB(t) + paymentDB := NewTestDB(t) // Register four payments: // All payments will have one failed HTLC attempt and one HTLC attempt @@ -663,9 +581,7 @@ func TestDeleteSinglePayment(t *testing.T) { assertDBPayments(t, paymentDB, payments) // Delete HTLC attempts for first payment only. - require.NoError(t, paymentDB.DeletePayment( - ctx, payments[0].id, true, - )) + require.NoError(t, paymentDB.DeletePayment(payments[0].id, true)) // The first payment is the only altered one as its failed HTLC should // have been removed but is still present as payment. @@ -673,25 +589,19 @@ func TestDeleteSinglePayment(t *testing.T) { assertDBPayments(t, paymentDB, payments) // Delete the first payment completely. - require.NoError(t, paymentDB.DeletePayment( - ctx, payments[0].id, false, - )) + require.NoError(t, paymentDB.DeletePayment(payments[0].id, false)) // The first payment should have been deleted. assertDBPayments(t, paymentDB, payments[1:]) // Now delete the second payment completely. - require.NoError(t, paymentDB.DeletePayment( - ctx, payments[1].id, false, - )) + require.NoError(t, paymentDB.DeletePayment(payments[1].id, false)) // The Second payment should have been deleted. assertDBPayments(t, paymentDB, payments[2:]) // Delete failed HTLC attempts for the third payment. - require.NoError(t, paymentDB.DeletePayment( - ctx, payments[2].id, true, - )) + require.NoError(t, paymentDB.DeletePayment(payments[2].id, true)) // Only the successful HTLC attempt should be left for the third // payment. @@ -699,27 +609,21 @@ func TestDeleteSinglePayment(t *testing.T) { assertDBPayments(t, paymentDB, payments[2:]) // Now delete the third payment completely. - require.NoError(t, paymentDB.DeletePayment( - ctx, payments[2].id, false, - )) + require.NoError(t, paymentDB.DeletePayment(payments[2].id, false)) // Only the last payment should be left. assertDBPayments(t, paymentDB, payments[3:]) // Deleting HTLC attempts from InFlight payments should not work and an // error returned. - require.Error(t, paymentDB.DeletePayment( - ctx, payments[3].id, true, - )) + require.Error(t, paymentDB.DeletePayment(payments[3].id, true)) // The payment is InFlight and therefore should not have been altered. assertDBPayments(t, paymentDB, payments[3:]) // Finally deleting the InFlight payment should also not work and an // error returned. - require.Error(t, paymentDB.DeletePayment( - ctx, payments[3].id, false, - )) + require.Error(t, paymentDB.DeletePayment(payments[3].id, false)) // The payment is InFlight and therefore should not have been altered. assertDBPayments(t, paymentDB, payments[3:]) @@ -783,6 +687,8 @@ func TestPaymentRegistrable(t *testing.T) { } for i, tc := range testCases { + i, tc := i, tc + p := &MPPayment{ Status: tc.status, State: &MPPaymentState{ @@ -899,6 +805,8 @@ func TestPaymentSetState(t *testing.T) { } for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { t.Parallel() @@ -1030,6 +938,8 @@ func TestNeedWaitAttempts(t *testing.T) { } for _, tc := range testCases { + tc := tc + p := &MPPayment{ Info: &PaymentCreationInfo{ PaymentIdentifier: [32]byte{1, 2, 3}, @@ -1206,6 +1116,8 @@ func TestAllowMoreAttempts(t *testing.T) { } for i, tc := range testCases { + tc := tc + p := &MPPayment{ Info: &PaymentCreationInfo{ PaymentIdentifier: [32]byte{1, 2, 3}, @@ -1266,304 +1178,6 @@ func makeAttemptInfo(total, amtForwarded int) HTLCAttemptInfo { } } -// lastHopArgs is a helper struct that holds the arguments for the last hop -// when creating an attempt with a route with a single hop (last hop). -type lastHopArgs struct { - amt lnwire.MilliSatoshi - total lnwire.MilliSatoshi - mpp *record.MPP - encrypted []byte -} - -// makeLastHopAttemptInfo creates an HTLCAttemptInfo with a route with a single -// hop (last hop). -func makeLastHopAttemptInfo(id uint64, args lastHopArgs) HTLCAttemptInfo { - lastHop := &route.Hop{ - PubKeyBytes: vertex, - ChannelID: 1, - AmtToForward: args.amt, - MPP: args.mpp, - EncryptedData: args.encrypted, - TotalAmtMsat: args.total, - } - - return HTLCAttemptInfo{ - AttemptID: id, - Route: route.Route{ - SourcePubKey: vertex, - TotalAmount: args.amt, - Hops: []*route.Hop{lastHop}, - }, - } -} - -// makePayment creates an MPPayment with set of attempts. -func makePayment(total lnwire.MilliSatoshi, - attempts ...HTLCAttempt) *MPPayment { - - return &MPPayment{ - Info: &PaymentCreationInfo{ - Value: total, - }, - HTLCs: attempts, - } -} - -// TestVerifyAttemptNonMPPAmountMismatch tests that we return an error if the -// attempted amount doesn't match the payment amount. -func TestVerifyAttemptNonMPPAmountMismatch(t *testing.T) { - t.Parallel() - - payment := makePayment(1000) - attempt := makeLastHopAttemptInfo(1, lastHopArgs{amt: 900}) - - require.ErrorIs(t, verifyAttempt(payment, &attempt), ErrValueMismatch) -} - -// TestVerifyAttemptNonMPPSuccess tests that we don't return an error if the -// attempted amount matches the payment amount. -func TestVerifyAttemptNonMPPSuccess(t *testing.T) { - t.Parallel() - - payment := makePayment(1200) - attempt := makeLastHopAttemptInfo(1, lastHopArgs{amt: 1200}) - - require.NoError(t, verifyAttempt(payment, &attempt)) -} - -// TestVerifyAttemptMPPTransitionErrors tests cases where we cannot transition -// from a non-MPP payment to an MPP payment or vice versa. -func TestVerifyAttemptMPPTransitionErrors(t *testing.T) { - t.Parallel() - - total := lnwire.MilliSatoshi(2000) - mpp := record.NewMPP(total, testHash) - - paymentWithMPP := makePayment( - total, - HTLCAttempt{ - HTLCAttemptInfo: makeLastHopAttemptInfo( - 1, - lastHopArgs{amt: 1000, mpp: mpp}, - ), - }, - ) - nonMPP := makeLastHopAttemptInfo(2, lastHopArgs{amt: 1000}) - require.ErrorIs(t, verifyAttempt(paymentWithMPP, &nonMPP), ErrMPPayment) - - paymentWithNonMPP := makePayment( - total, - HTLCAttempt{ - HTLCAttemptInfo: makeLastHopAttemptInfo( - 1, - lastHopArgs{amt: total}, - ), - }, - ) - mppAttempt := makeLastHopAttemptInfo( - 2, lastHopArgs{amt: 1000, mpp: mpp}, - ) - require.ErrorIs( - t, - verifyAttempt(paymentWithNonMPP, &mppAttempt), - ErrNonMPPayment, - ) -} - -// TestVerifyAttemptMPPOptionMismatch tests that we return an error if the -// MPP options don't match the payment options. -func TestVerifyAttemptMPPOptionMismatch(t *testing.T) { - t.Parallel() - - total := lnwire.MilliSatoshi(3000) - goodMPP := record.NewMPP(total, testHash) - payment := makePayment( - total, - HTLCAttempt{ - HTLCAttemptInfo: makeLastHopAttemptInfo( - 1, - lastHopArgs{amt: 1500, mpp: goodMPP}, - ), - }, - ) - - badAddr := record.NewMPP(total, rev) - attemptBadAddr := makeLastHopAttemptInfo( - 2, - lastHopArgs{amt: 1500, mpp: badAddr}, - ) - require.ErrorIs( - t, - verifyAttempt(payment, &attemptBadAddr), - ErrMPPPaymentAddrMismatch, - ) - - badTotal := record.NewMPP(total-1, testHash) - attemptBadTotal := makeLastHopAttemptInfo( - 3, - lastHopArgs{amt: 1500, mpp: badTotal}, - ) - require.ErrorIs( - t, - verifyAttempt(payment, &attemptBadTotal), - ErrMPPTotalAmountMismatch, - ) - - matching := makeLastHopAttemptInfo( - 4, - lastHopArgs{amt: 1500, mpp: record.NewMPP(total, testHash)}, - ) - require.NoError(t, verifyAttempt(payment, &matching)) -} - -// TestVerifyAttemptBlindedValidation tests that we return an error if we try -// to register an MPP attempt for a blinded payment. -func TestVerifyAttemptBlindedValidation(t *testing.T) { - t.Parallel() - - total := lnwire.MilliSatoshi(5000) - - // Payment with a blinded attempt. - existing := makeLastHopAttemptInfo( - 1, - lastHopArgs{amt: 2500, total: total, encrypted: []byte{1}}, - ) - payment := makePayment( - total, - HTLCAttempt{HTLCAttemptInfo: existing}, - ) - - // Attempt with a normal MPP record should fail because a payment - // cannot have a mix of blinded and non-blinded attempts. - goodMPP := makeLastHopAttemptInfo( - 2, - lastHopArgs{amt: 2500, mpp: record.NewMPP(total, testHash)}, - ) - require.ErrorIs( - t, verifyAttempt(payment, &goodMPP), - ErrMixedBlindedAndNonBlindedPayments, - ) - - blindedMPP := makeLastHopAttemptInfo( - 2, - lastHopArgs{ - amt: 2500, - total: total, - mpp: record.NewMPP(total, testHash), - encrypted: []byte{2}, - }, - ) - require.ErrorIs( - t, - verifyAttempt(payment, &blindedMPP), - ErrMPPRecordInBlindedPayment, - ) - - mismatchedTotal := makeLastHopAttemptInfo( - 3, - lastHopArgs{amt: 2500, total: total + 1, encrypted: []byte{3}}, - ) - require.ErrorIs( - t, - verifyAttempt(payment, &mismatchedTotal), - ErrBlindedPaymentTotalAmountMismatch, - ) - - matching := makeLastHopAttemptInfo( - 4, - lastHopArgs{amt: 2500, total: total, encrypted: []byte{4}}, - ) - require.NoError(t, verifyAttempt(payment, &matching)) -} - -// TestVerifyAttemptBlindedMissingTotalAmount tests that we return an error if -// we try to register a blinded payment attempt where the final hop doesn't set -// the total amount. -func TestVerifyAttemptBlindedMissingTotalAmount(t *testing.T) { - t.Parallel() - - total := lnwire.MilliSatoshi(5000) - - // Payment with no existing attempts. - payment := makePayment(total) - - // Attempt with encrypted data (blinded payment) but missing total - // amount. - attemptMissingTotal := makeLastHopAttemptInfo( - 1, - lastHopArgs{ - amt: 2500, - total: 0, - encrypted: []byte{1, 2, 3}, - }, - ) - require.ErrorIs( - t, - verifyAttempt(payment, &attemptMissingTotal), - ErrBlindedPaymentMissingTotalAmount, - ) - - // Attempt with encrypted data and valid total amount should succeed. - attemptWithTotal := makeLastHopAttemptInfo( - 2, - lastHopArgs{ - amt: 2500, - total: total, - encrypted: []byte{4, 5, 6}, - }, - ) - require.NoError(t, verifyAttempt(payment, &attemptWithTotal)) -} - -// TestVerifyAttemptBlindedMixedWithNonBlinded tests that we return an error if -// we try to register a non-MPP attempt for a blinded payment. -func TestVerifyAttemptBlindedMixedWithNonBlinded(t *testing.T) { - t.Parallel() - - total := lnwire.MilliSatoshi(4000) - - // Payment with a blinded attempt. - existing := makeLastHopAttemptInfo( - 1, - lastHopArgs{amt: 2000, total: total, encrypted: []byte{1}}, - ) - payment := makePayment( - total, - HTLCAttempt{HTLCAttemptInfo: existing}, - ) - - partial := makeLastHopAttemptInfo(2, lastHopArgs{amt: 2000}) - require.ErrorIs( - t, - verifyAttempt(payment, &partial), - ErrMixedBlindedAndNonBlindedPayments, - ) - - full := makeLastHopAttemptInfo(3, lastHopArgs{amt: total}) - require.ErrorIs( - t, - verifyAttempt(payment, &full), - ErrMixedBlindedAndNonBlindedPayments, - ) -} - -// TestVerifyAttemptAmountExceedsTotal tests that we return an error if the -// attempted amount exceeds the payment amount. -func TestVerifyAttemptAmountExceedsTotal(t *testing.T) { - t.Parallel() - - total := lnwire.MilliSatoshi(1000) - mpp := record.NewMPP(total, testHash) - existing := makeLastHopAttemptInfo(1, lastHopArgs{amt: 800, mpp: mpp}) - payment := makePayment( - total, - HTLCAttempt{HTLCAttemptInfo: existing}, - ) - - attempt := makeLastHopAttemptInfo(2, lastHopArgs{amt: 300, mpp: mpp}) - require.ErrorIs(t, verifyAttempt(payment, &attempt), ErrValueExceedsAmt) -} - // TestEmptyRoutesGenerateSphinxPacket tests that the generateSphinxPacket // function is able to gracefully handle being passed a nil set of hops for the // route by the caller. @@ -1581,16 +1195,13 @@ func TestEmptyRoutesGenerateSphinxPacket(t *testing.T) { func TestSuccessesWithoutInFlight(t *testing.T) { t.Parallel() - paymentDB, _ := NewTestDB(t) + paymentDB := NewTestDB(t) - preimg := genPreimage(t) - - rhash := sha256.Sum256(preimg[:]) - info := genPaymentCreationInfo(t, rhash) + info, _, preimg, err := genInfo(t) + require.NoError(t, err, "unable to generate htlc message") // Attempt to complete the payment should fail. - _, err := paymentDB.SettleAttempt( - t.Context(), + _, err = paymentDB.SettleAttempt( info.PaymentIdentifier, 0, &HTLCSettleInfo{ Preimage: preimg, @@ -1604,16 +1215,14 @@ func TestSuccessesWithoutInFlight(t *testing.T) { func TestFailsWithoutInFlight(t *testing.T) { t.Parallel() - paymentDB, _ := NewTestDB(t) + paymentDB := NewTestDB(t) - preimg := genPreimage(t) - - rhash := sha256.Sum256(preimg[:]) - info := genPaymentCreationInfo(t, rhash) + info, _, _, err := genInfo(t) + require.NoError(t, err, "unable to generate htlc message") // Calling Fail should return an error. - _, err := paymentDB.Fail( - t.Context(), info.PaymentIdentifier, FailureReasonNoRoute, + _, err = paymentDB.Fail( + info.PaymentIdentifier, FailureReasonNoRoute, ) require.ErrorIs(t, err, ErrPaymentNotInitiated) } @@ -1623,9 +1232,7 @@ func TestFailsWithoutInFlight(t *testing.T) { func TestDeletePayments(t *testing.T) { t.Parallel() - ctx := t.Context() - - paymentDB, _ := NewTestDB(t) + paymentDB := NewTestDB(t) // Register three payments: // 1. A payment with two failed attempts. @@ -1645,7 +1252,7 @@ func TestDeletePayments(t *testing.T) { assertDBPayments(t, paymentDB, payments) // Delete HTLC attempts for failed payments only. - numPayments, err := paymentDB.DeletePayments(ctx, true, true) + numPayments, err := paymentDB.DeletePayments(true, true) require.NoError(t, err) require.EqualValues(t, 0, numPayments) @@ -1654,7 +1261,7 @@ func TestDeletePayments(t *testing.T) { assertDBPayments(t, paymentDB, payments) // Delete failed attempts for all payments. - numPayments, err = paymentDB.DeletePayments(ctx, false, true) + numPayments, err = paymentDB.DeletePayments(false, true) require.NoError(t, err) require.EqualValues(t, 0, numPayments) @@ -1664,201 +1271,36 @@ func TestDeletePayments(t *testing.T) { assertDBPayments(t, paymentDB, payments) // Now delete all failed payments. - numPayments, err = paymentDB.DeletePayments(ctx, true, false) + numPayments, err = paymentDB.DeletePayments(true, false) require.NoError(t, err) require.EqualValues(t, 1, numPayments) assertDBPayments(t, paymentDB, payments[1:]) // Finally delete all completed payments. - numPayments, err = paymentDB.DeletePayments(ctx, false, false) + numPayments, err = paymentDB.DeletePayments(false, false) require.NoError(t, err) require.EqualValues(t, 1, numPayments) assertDBPayments(t, paymentDB, payments[2:]) } -// TestDeleteNonInFlight checks that calling DeletePayments only deletes -// payments from the database that are not in-flight. -func TestDeleteNonInFlight(t *testing.T) { - t.Parallel() - - ctx := t.Context() - - paymentDB, _ := NewTestDB(t) - - var ( - numSuccess, numInflight int - attemptID uint64 = 0 - ) - - // Create payments with different statuses: failed, success, inflight, - // and another success. - payments := []struct { - failed bool - success bool - }{ - // Payment 0: failed. - {failed: true, success: false}, - // Payment 1: success. - {failed: false, success: true}, - // Payment 2: inflight. - {failed: false, success: false}, - // Payment 3: success. - {failed: false, success: true}, - } - - for _, p := range payments { - preimg := genPreimage(t) - - rhash := sha256.Sum256(preimg[:]) - info := genPaymentCreationInfo(t, rhash) - attempt := genAttemptWithHash( - t, attemptID, genSessionKey(t), rhash, - ) - - // After generating the attempt, increment the attempt ID to - // have unique attempt IDs for each attempt otherwise the unique - // constraint on the attempt ID will be violated. - attemptID++ - - // Init payment which initiates StatusInFlight. - err := paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) - require.NoError(t, err, "unable to init payment") - - _, err = paymentDB.RegisterAttempt( - ctx, info.PaymentIdentifier, attempt, - ) - require.NoError(t, err, "unable to register attempt") - - switch { - case p.failed: - // Fail the payment attempt. - htlcFailure := HTLCFailUnreadable - _, err := paymentDB.FailAttempt( - ctx, info.PaymentIdentifier, attempt.AttemptID, - &HTLCFailInfo{ - Reason: htlcFailure, - }, - ) - require.NoError(t, err, "unable to fail htlc") - - // Fail the payment, which should move it to Failed. - failReason := FailureReasonNoRoute - _, err = paymentDB.Fail( - ctx, info.PaymentIdentifier, failReason, - ) - require.NoError(t, err, "unable to fail payment") - - // Verify the status is indeed Failed. - assertDBPaymentstatus( - t, paymentDB, info.PaymentIdentifier, - StatusFailed, - ) - - case p.success: - // Settle the attempt. - _, err := paymentDB.SettleAttempt( - ctx, info.PaymentIdentifier, attempt.AttemptID, - &HTLCSettleInfo{ - Preimage: preimg, - }, - ) - require.NoError(t, err, "unable to settle attempt") - - assertDBPaymentstatus( - t, paymentDB, info.PaymentIdentifier, - StatusSucceeded, - ) - - numSuccess++ - - default: - // Leave as inflight. - assertDBPaymentstatus( - t, paymentDB, info.PaymentIdentifier, - StatusInFlight, - ) - - numInflight++ - } - } - - // Delete all failed payments. - numPayments, err := paymentDB.DeletePayments(ctx, true, false) - require.NoError(t, err) - require.EqualValues(t, 1, numPayments) - - // This should leave the succeeded and in-flight payments. - resp, err := paymentDB.QueryPayments(ctx, Query{ - IndexOffset: 0, - MaxPayments: math.MaxUint64, - IncludeIncomplete: true, - }) - require.NoError(t, err) - - require.Equal(t, numSuccess+numInflight, len(resp.Payments), - "expected %d payments, got %d", numSuccess+numInflight, - len(resp.Payments)) - - var s, i int - for _, p := range resp.Payments { - switch p.Status { - case StatusSucceeded: - s++ - case StatusInFlight: - i++ - } - } - - require.Equal(t, numSuccess, s, - "expected %d succeeded payments, got %d", numSuccess, s) - require.Equal(t, numInflight, i, - "expected %d in-flight payments, got %d", numInflight, i) - - // Now delete all payments except in-flight. - numPayments, err = paymentDB.DeletePayments(ctx, false, false) - require.NoError(t, err) - require.EqualValues(t, 2, numPayments) - - // This should leave the in-flight payment. - resp, err = paymentDB.QueryPayments(ctx, Query{ - IndexOffset: 0, - MaxPayments: math.MaxUint64, - IncludeIncomplete: true, - }) - require.NoError(t, err) - - require.Equal(t, numInflight, len(resp.Payments), - "expected %d payments, got %d", numInflight, len(resp.Payments)) - - for _, p := range resp.Payments { - require.Equal(t, StatusInFlight, p.Status, - "expected in-flight status, got %v", p.Status) - } -} - // TestSwitchDoubleSend checks the ability of payment control to // prevent double sending of htlc message, when message is in StatusInFlight. func TestSwitchDoubleSend(t *testing.T) { t.Parallel() - ctx := t.Context() + paymentDB := NewTestDB(t) - paymentDB, harness := NewTestDB(t) - - preimg := genPreimage(t) - - rhash := sha256.Sum256(preimg[:]) - info := genPaymentCreationInfo(t, rhash) - attempt := genAttemptWithHash(t, 0, genSessionKey(t), rhash) + info, attempt, preimg, err := genInfo(t) + require.NoError(t, err, "unable to generate htlc message") // Sends base htlc message which initiate base status and move it to // StatusInFlight and verifies that it was changed. - err := paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) require.NoError(t, err, "unable to send htlc message") - harness.AssertPaymentIndex(t, info.PaymentIdentifier) + assertPaymentIndex(t, paymentDB, info.PaymentIdentifier) assertDBPaymentstatus( t, paymentDB, info.PaymentIdentifier, StatusInitiated, ) @@ -1869,11 +1311,11 @@ func TestSwitchDoubleSend(t *testing.T) { // Try to initiate double sending of htlc message with the same // payment hash, should result in error indicating that payment has // already been sent. - err = paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) require.ErrorIs(t, err, ErrPaymentExists) // Record an attempt. - _, err = paymentDB.RegisterAttempt(ctx, info.PaymentIdentifier, attempt) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, attempt) require.NoError(t, err, "unable to send htlc message") assertDBPaymentstatus( t, paymentDB, info.PaymentIdentifier, StatusInFlight, @@ -1887,7 +1329,7 @@ func TestSwitchDoubleSend(t *testing.T) { ) // Sends base htlc message which initiate StatusInFlight. - err = paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) if !errors.Is(err, ErrPaymentInFlight) { t.Fatalf("payment control wrong behaviour: " + "double sending must trigger ErrPaymentInFlight error") @@ -1895,7 +1337,7 @@ func TestSwitchDoubleSend(t *testing.T) { // After settling, the error should be ErrAlreadyPaid. _, err = paymentDB.SettleAttempt( - ctx, info.PaymentIdentifier, attempt.AttemptID, + info.PaymentIdentifier, attempt.AttemptID, &HTLCSettleInfo{ Preimage: preimg, }, @@ -1910,7 +1352,7 @@ func TestSwitchDoubleSend(t *testing.T) { t, paymentDB, info.PaymentIdentifier, info, nil, htlc, ) - err = paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) if !errors.Is(err, ErrAlreadyPaid) { t.Fatalf("unable to send htlc message: %v", err) } @@ -1921,21 +1363,16 @@ func TestSwitchDoubleSend(t *testing.T) { func TestSwitchFail(t *testing.T) { t.Parallel() - ctx := t.Context() + paymentDB := NewTestDB(t) - paymentDB, harness := NewTestDB(t) - - preimg := genPreimage(t) - - rhash := sha256.Sum256(preimg[:]) - info := genPaymentCreationInfo(t, rhash) - attempt := genAttemptWithHash(t, 0, genSessionKey(t), rhash) + info, attempt, preimg, err := genInfo(t) + require.NoError(t, err, "unable to generate htlc message") // Sends base htlc message which initiate StatusInFlight. - err := paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) require.NoError(t, err, "unable to send htlc message") - harness.AssertPaymentIndex(t, info.PaymentIdentifier) + assertPaymentIndex(t, paymentDB, info.PaymentIdentifier) assertDBPaymentstatus( t, paymentDB, info.PaymentIdentifier, StatusInitiated, ) @@ -1945,7 +1382,7 @@ func TestSwitchFail(t *testing.T) { // Fail the payment, which should moved it to Failed. failReason := FailureReasonNoRoute - _, err = paymentDB.Fail(ctx, info.PaymentIdentifier, failReason) + _, err = paymentDB.Fail(info.PaymentIdentifier, failReason) require.NoError(t, err, "unable to fail payment hash") // Verify the status is indeed Failed. @@ -1959,18 +1396,18 @@ func TestSwitchFail(t *testing.T) { // Lookup the payment so we can get its old sequence number before it is // overwritten. - payment, err := paymentDB.FetchPayment(ctx, info.PaymentIdentifier) + payment, err := paymentDB.FetchPayment(info.PaymentIdentifier) require.NoError(t, err) // Sends the htlc again, which should succeed since the prior payment // failed. - err = paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) require.NoError(t, err, "unable to send htlc message") // Check that our index has been updated, and the old index has been // removed. - harness.AssertPaymentIndex(t, info.PaymentIdentifier) - harness.AssertNoIndex(t, payment.SequenceNum) + assertPaymentIndex(t, paymentDB, info.PaymentIdentifier) + assertNoIndex(t, paymentDB, payment.SequenceNum) assertDBPaymentstatus( t, paymentDB, info.PaymentIdentifier, StatusInitiated, @@ -1982,16 +1419,14 @@ func TestSwitchFail(t *testing.T) { // Record a new attempt. In this test scenario, the attempt fails. // However, this is not communicated to control tower in the current // implementation. It only registers the initiation of the attempt. - _, err = paymentDB.RegisterAttempt(ctx, info.PaymentIdentifier, attempt) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, attempt) require.NoError(t, err, "unable to register attempt") htlcReason := HTLCFailUnreadable - htlcFailTime := time.Unix(5000, 0) _, err = paymentDB.FailAttempt( - ctx, info.PaymentIdentifier, attempt.AttemptID, + info.PaymentIdentifier, attempt.AttemptID, &HTLCFailInfo{ - Reason: htlcReason, - FailTime: htlcFailTime, + Reason: htlcReason, }, ) if err != nil { @@ -2004,18 +1439,13 @@ func TestSwitchFail(t *testing.T) { htlc := &htlcStatus{ HTLCAttemptInfo: attempt, failure: &htlcReason, - failTime: htlcFailTime, } assertPaymentInfo(t, paymentDB, info.PaymentIdentifier, info, nil, htlc) // Record another attempt. - attempt = genAttemptWithHash( - t, 1, genSessionKey(t), rhash, - ) - require.NoError(t, err) - - _, err = paymentDB.RegisterAttempt(ctx, info.PaymentIdentifier, attempt) + attempt.AttemptID = 1 + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, attempt) require.NoError(t, err, "unable to send htlc message") assertDBPaymentstatus( t, paymentDB, info.PaymentIdentifier, StatusInFlight, @@ -2032,7 +1462,7 @@ func TestSwitchFail(t *testing.T) { // Settle the attempt and verify that status was changed to // StatusSucceeded. payment, err = paymentDB.SettleAttempt( - ctx, info.PaymentIdentifier, attempt.AttemptID, + info.PaymentIdentifier, attempt.AttemptID, &HTLCSettleInfo{ Preimage: preimg, }, @@ -2062,7 +1492,7 @@ func TestSwitchFail(t *testing.T) { // Attempt a final payment, which should now fail since the prior // payment succeed. - err = paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) if !errors.Is(err, ErrAlreadyPaid) { t.Fatalf("unable to send htlc message: %v", err) } @@ -2073,8 +1503,6 @@ func TestSwitchFail(t *testing.T) { func TestMultiShard(t *testing.T) { t.Parallel() - ctx := t.Context() - // We will register three HTLC attempts, and always fail the second // one. We'll generate all combinations of settling/failing the first // and third HTLC, and assert that the payment status end up as we @@ -2092,18 +1520,20 @@ func TestMultiShard(t *testing.T) { } runSubTest := func(t *testing.T, test testCase) { - paymentDB, harness := NewTestDB(t) + paymentDB := NewTestDB(t) - preimg := genPreimage(t) - - rhash := sha256.Sum256(preimg[:]) - info := genPaymentCreationInfo(t, rhash) + info, attempt, preimg, err := genInfo(t) + if err != nil { + t.Fatalf("unable to generate htlc message: %v", err) + } // Init the payment, moving it to the StatusInFlight state. - err := paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) - require.NoError(t, err) + err = paymentDB.InitPayment(info.PaymentIdentifier, info) + if err != nil { + t.Fatalf("unable to send htlc message: %v", err) + } - harness.AssertPaymentIndex(t, info.PaymentIdentifier) + assertPaymentIndex(t, paymentDB, info.PaymentIdentifier) assertDBPaymentstatus( t, paymentDB, info.PaymentIdentifier, StatusInitiated, ) @@ -2116,22 +1546,19 @@ func TestMultiShard(t *testing.T) { // attempts's value to one third of the payment amount, and // populate the MPP options. shardAmt := info.Value / 3 + attempt.Route.FinalHop().AmtToForward = shardAmt + attempt.Route.FinalHop().MPP = record.NewMPP( + info.Value, [32]byte{1}, + ) var attempts []*HTLCAttemptInfo for i := uint64(0); i < 3; i++ { - a := genAttemptWithHash( - t, i, genSessionKey(t), rhash, - ) - - a.Route.FinalHop().AmtToForward = shardAmt - a.Route.FinalHop().MPP = record.NewMPP( - info.Value, [32]byte{1}, - ) - - attempts = append(attempts, a) + a := *attempt + a.AttemptID = i + attempts = append(attempts, &a) _, err = paymentDB.RegisterAttempt( - ctx, info.PaymentIdentifier, a, + info.PaymentIdentifier, &a, ) if err != nil { t.Fatalf("unable to send htlc message: %v", err) @@ -2142,7 +1569,7 @@ func TestMultiShard(t *testing.T) { ) htlc := &htlcStatus{ - HTLCAttemptInfo: a, + HTLCAttemptInfo: &a, } assertPaymentInfo( t, paymentDB, info.PaymentIdentifier, info, nil, @@ -2153,29 +1580,18 @@ func TestMultiShard(t *testing.T) { // For a fourth attempt, check that attempting to // register it will fail since the total sent amount // will be too large. - b := genAttemptWithHash( - t, 3, genSessionKey(t), rhash, - ) - - b.Route.FinalHop().AmtToForward = shardAmt - b.Route.FinalHop().MPP = record.NewMPP( - info.Value, [32]byte{1}, - ) - - _, err = paymentDB.RegisterAttempt( - ctx, info.PaymentIdentifier, b, - ) + b := *attempt + b.AttemptID = 3 + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, &b) require.ErrorIs(t, err, ErrValueExceedsAmt) // Fail the second attempt. a := attempts[1] htlcFail := HTLCFailUnreadable - secondFailTime := time.Unix(6000, 0) _, err = paymentDB.FailAttempt( - ctx, info.PaymentIdentifier, a.AttemptID, + info.PaymentIdentifier, a.AttemptID, &HTLCFailInfo{ - Reason: htlcFail, - FailTime: secondFailTime, + Reason: htlcFail, }, ) if err != nil { @@ -2185,7 +1601,6 @@ func TestMultiShard(t *testing.T) { htlc := &htlcStatus{ HTLCAttemptInfo: a, failure: &htlcFail, - failTime: secondFailTime, } assertPaymentInfo( t, paymentDB, info.PaymentIdentifier, info, nil, htlc, @@ -2204,12 +1619,10 @@ func TestMultiShard(t *testing.T) { var firstFailReason *FailureReason if test.settleFirst { - firstSettleTime := time.Unix(1000, 0) _, err := paymentDB.SettleAttempt( - ctx, info.PaymentIdentifier, a.AttemptID, + info.PaymentIdentifier, a.AttemptID, &HTLCSettleInfo{ - Preimage: preimg, - SettleTime: firstSettleTime, + Preimage: preimg, }, ) if err != nil { @@ -2217,21 +1630,17 @@ func TestMultiShard(t *testing.T) { "received, got: %v", err) } - // Assert that the HTLC has had the preimage and - // settle time recorded. + // Assert that the HTLC has had the preimage recorded. htlc.settle = &preimg - htlc.settleTime = firstSettleTime assertPaymentInfo( t, paymentDB, info.PaymentIdentifier, info, nil, htlc, ) } else { - firstFailTime := time.Unix(2000, 0) _, err := paymentDB.FailAttempt( - ctx, info.PaymentIdentifier, a.AttemptID, + info.PaymentIdentifier, a.AttemptID, &HTLCFailInfo{ - Reason: htlcFail, - FailTime: firstFailTime, + Reason: htlcFail, }, ) if err != nil { @@ -2241,7 +1650,6 @@ func TestMultiShard(t *testing.T) { // Assert the failure was recorded. htlc.failure = &htlcFail - htlc.failTime = firstFailTime assertPaymentInfo( t, paymentDB, info.PaymentIdentifier, info, nil, htlc, @@ -2251,7 +1659,7 @@ func TestMultiShard(t *testing.T) { // a terminal state. failReason := FailureReasonNoRoute _, err = paymentDB.Fail( - ctx, info.PaymentIdentifier, failReason, + info.PaymentIdentifier, failReason, ) if err != nil { t.Fatalf("unable to fail payment hash: %v", err) @@ -2271,18 +1679,9 @@ func TestMultiShard(t *testing.T) { // Try to register yet another attempt. This should fail now // that the payment has reached a terminal condition. - b = genAttemptWithHash( - t, 3, genSessionKey(t), rhash, - ) - - b.Route.FinalHop().AmtToForward = shardAmt - b.Route.FinalHop().MPP = record.NewMPP( - info.Value, [32]byte{1}, - ) - - _, err = paymentDB.RegisterAttempt( - ctx, info.PaymentIdentifier, b, - ) + b = *attempt + b.AttemptID = 3 + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, &b) if test.settleFirst { require.ErrorIs( t, err, ErrPaymentPendingSettled, @@ -2304,30 +1703,25 @@ func TestMultiShard(t *testing.T) { } if test.settleLast { // Settle the last outstanding attempt. - lastSettleTime := time.Unix(3000, 0) _, err = paymentDB.SettleAttempt( - ctx, info.PaymentIdentifier, a.AttemptID, + info.PaymentIdentifier, a.AttemptID, &HTLCSettleInfo{ - Preimage: preimg, - SettleTime: lastSettleTime, + Preimage: preimg, }, ) require.NoError(t, err, "unable to settle") htlc.settle = &preimg - htlc.settleTime = lastSettleTime assertPaymentInfo( t, paymentDB, info.PaymentIdentifier, info, firstFailReason, htlc, ) } else { // Fail the attempt. - lastFailTime := time.Unix(4000, 0) _, err := paymentDB.FailAttempt( - ctx, info.PaymentIdentifier, a.AttemptID, + info.PaymentIdentifier, a.AttemptID, &HTLCFailInfo{ - Reason: htlcFail, - FailTime: lastFailTime, + Reason: htlcFail, }, ) if err != nil { @@ -2337,7 +1731,6 @@ func TestMultiShard(t *testing.T) { // Assert the failure was recorded. htlc.failure = &htlcFail - htlc.failTime = lastFailTime assertPaymentInfo( t, paymentDB, info.PaymentIdentifier, info, firstFailReason, htlc, @@ -2349,7 +1742,7 @@ func TestMultiShard(t *testing.T) { // syncing. failReason := FailureReasonPaymentDetails _, err = paymentDB.Fail( - ctx, info.PaymentIdentifier, failReason, + info.PaymentIdentifier, failReason, ) require.NoError(t, err, "unable to fail") } @@ -2387,10 +1780,8 @@ func TestMultiShard(t *testing.T) { ) // Finally assert we cannot register more attempts. - _, err = paymentDB.RegisterAttempt( - ctx, info.PaymentIdentifier, b, - ) - require.ErrorIs(t, err, registerErr) + _, err = paymentDB.RegisterAttempt(info.PaymentIdentifier, &b) + require.Equal(t, registerErr, err) } for _, test := range tests { @@ -2402,1054 +1793,3 @@ func TestMultiShard(t *testing.T) { }) } } - -// TestQueryPayments tests retrieval of payments with forwards and reversed -// queries. -func TestQueryPayments(t *testing.T) { - // Define table driven test for QueryPayments. - // Test payments have sequence indices [1, 3, 4, 5, 6]. - // Note that payment with index 2 is deleted to create a gap in the - // sequence numbers. - tests := []struct { - name string - query Query - firstIndex uint64 - lastIndex uint64 - - // expectedSeqNrs contains the set of sequence numbers we expect - // our query to return. - expectedSeqNrs []uint64 - }{ - { - name: "IndexOffset at the end of the payments range", - query: Query{ - IndexOffset: 6, - MaxPayments: 7, - Reversed: false, - IncludeIncomplete: true, - }, - firstIndex: 0, - lastIndex: 0, - expectedSeqNrs: nil, - }, - { - name: "query in forwards order, start at beginning", - query: Query{ - IndexOffset: 0, - MaxPayments: 2, - Reversed: false, - IncludeIncomplete: true, - }, - firstIndex: 1, - lastIndex: 3, - expectedSeqNrs: []uint64{1, 3}, - }, - { - name: "query in forwards order, start at end, overflow", - query: Query{ - IndexOffset: 5, - MaxPayments: 2, - Reversed: false, - IncludeIncomplete: true, - }, - firstIndex: 6, - lastIndex: 6, - expectedSeqNrs: []uint64{6}, - }, - { - name: "start at offset index outside of payments", - query: Query{ - IndexOffset: 20, - MaxPayments: 2, - Reversed: false, - IncludeIncomplete: true, - }, - firstIndex: 0, - lastIndex: 0, - expectedSeqNrs: nil, - }, - { - name: "overflow in forwards order", - query: Query{ - IndexOffset: 4, - MaxPayments: math.MaxUint64, - Reversed: false, - IncludeIncomplete: true, - }, - firstIndex: 5, - lastIndex: 6, - expectedSeqNrs: []uint64{5, 6}, - }, - { - name: "start at offset index outside of payments, " + - "reversed order", - query: Query{ - IndexOffset: 9, - MaxPayments: 2, - Reversed: true, - IncludeIncomplete: true, - }, - firstIndex: 5, - lastIndex: 6, - expectedSeqNrs: []uint64{5, 6}, - }, - { - name: "query in reverse order, start at end", - query: Query{ - IndexOffset: 0, - MaxPayments: 2, - Reversed: true, - IncludeIncomplete: true, - }, - firstIndex: 5, - lastIndex: 6, - expectedSeqNrs: []uint64{5, 6}, - }, - { - name: "query in reverse order, starting in middle", - query: Query{ - IndexOffset: 4, - MaxPayments: 2, - Reversed: true, - IncludeIncomplete: true, - }, - firstIndex: 1, - lastIndex: 3, - expectedSeqNrs: []uint64{1, 3}, - }, - { - name: "query in reverse order, starting in middle, " + - "with underflow", - query: Query{ - IndexOffset: 4, - MaxPayments: 5, - Reversed: true, - IncludeIncomplete: true, - }, - firstIndex: 1, - lastIndex: 3, - expectedSeqNrs: []uint64{1, 3}, - }, - { - name: "all payments in reverse, order maintained", - query: Query{ - IndexOffset: 0, - MaxPayments: 7, - Reversed: true, - IncludeIncomplete: true, - }, - firstIndex: 1, - lastIndex: 6, - expectedSeqNrs: []uint64{1, 3, 4, 5, 6}, - }, - { - name: "exclude incomplete payments", - query: Query{ - IndexOffset: 0, - MaxPayments: 7, - Reversed: false, - IncludeIncomplete: false, - }, - firstIndex: 6, - lastIndex: 6, - expectedSeqNrs: []uint64{6}, - }, - { - name: "query payments at index gap", - query: Query{ - IndexOffset: 1, - MaxPayments: 7, - Reversed: false, - IncludeIncomplete: true, - }, - firstIndex: 3, - lastIndex: 6, - expectedSeqNrs: []uint64{3, 4, 5, 6}, - }, - { - name: "query payments reverse before index gap", - query: Query{ - IndexOffset: 3, - MaxPayments: 7, - Reversed: true, - IncludeIncomplete: true, - }, - firstIndex: 1, - lastIndex: 1, - expectedSeqNrs: []uint64{1}, - }, - { - name: "query payments reverse on index gap", - query: Query{ - IndexOffset: 2, - MaxPayments: 7, - Reversed: true, - IncludeIncomplete: true, - }, - firstIndex: 1, - lastIndex: 1, - expectedSeqNrs: []uint64{1}, - }, - { - name: "query payments forward on index gap", - query: Query{ - IndexOffset: 2, - MaxPayments: 2, - Reversed: false, - IncludeIncomplete: true, - }, - firstIndex: 3, - lastIndex: 4, - expectedSeqNrs: []uint64{3, 4}, - }, - { - name: "query in forwards order, with start creation " + - "time", - query: Query{ - IndexOffset: 0, - MaxPayments: 2, - Reversed: false, - IncludeIncomplete: true, - CreationDateStart: 5, - }, - firstIndex: 5, - lastIndex: 6, - expectedSeqNrs: []uint64{5, 6}, - }, - { - name: "query in forwards order, with start creation " + - "time at end, overflow", - query: Query{ - IndexOffset: 0, - MaxPayments: 2, - Reversed: false, - IncludeIncomplete: true, - CreationDateStart: 6, - }, - firstIndex: 6, - lastIndex: 6, - expectedSeqNrs: []uint64{6}, - }, - { - name: "query with start and end creation time", - query: Query{ - IndexOffset: 9, - MaxPayments: math.MaxUint64, - Reversed: true, - IncludeIncomplete: true, - CreationDateStart: 3, - CreationDateEnd: 5, - }, - firstIndex: 3, - lastIndex: 5, - expectedSeqNrs: []uint64{3, 4, 5}, - }, - { - name: "query with only end creation time", - query: Query{ - IndexOffset: 0, - MaxPayments: math.MaxUint64, - Reversed: false, - IncludeIncomplete: true, - CreationDateEnd: 4, - }, - firstIndex: 1, - lastIndex: 4, - expectedSeqNrs: []uint64{1, 3, 4}, - }, - { - name: "query reversed with creation date start", - query: Query{ - IndexOffset: 0, - MaxPayments: 3, - Reversed: true, - IncludeIncomplete: true, - CreationDateStart: 3, - }, - firstIndex: 4, - lastIndex: 6, - expectedSeqNrs: []uint64{4, 5, 6}, - }, - { - name: "count total with forward pagination", - query: Query{ - IndexOffset: 0, - MaxPayments: 2, - Reversed: false, - IncludeIncomplete: true, - CountTotal: true, - }, - firstIndex: 1, - lastIndex: 3, - expectedSeqNrs: []uint64{1, 3}, - }, - { - name: "count total with reverse pagination", - query: Query{ - IndexOffset: 0, - MaxPayments: 2, - Reversed: true, - IncludeIncomplete: true, - CountTotal: true, - }, - firstIndex: 5, - lastIndex: 6, - expectedSeqNrs: []uint64{5, 6}, - }, - { - name: "count total with filters", - query: Query{ - IndexOffset: 0, - MaxPayments: math.MaxUint64, - Reversed: false, - IncludeIncomplete: false, - CountTotal: true, - }, - firstIndex: 6, - lastIndex: 6, - expectedSeqNrs: []uint64{6}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - ctx := t.Context() - - paymentDB, harness := NewTestDB(t) - - // Make a preliminary query to make sure it's ok to - // query when we have no payments. - resp, err := paymentDB.QueryPayments(ctx, tt.query) - require.NoError(t, err) - require.Len(t, resp.Payments, 0) - - // Populate the database with a set of test payments. - // We create 6 payments, deleting the payment at index - // 2 so that we cover the case where sequence numbers - // are missing. - numberOfPayments := 6 - - // Store payment info for all payments so we can delete - // one after all are created. - var paymentInfos []*PaymentCreationInfo - - // First, create all payments. - for i := range numberOfPayments { - // Generate a test payment. - info, _ := genInfo(t) - - // Override creation time to allow for testing - // of CreationDateStart and CreationDateEnd. - info.CreationTime = time.Unix(int64(i+1), 0) - - paymentInfos = append(paymentInfos, info) - - // Create a new payment entry in the database. - err = paymentDB.InitPayment( - ctx, info.PaymentIdentifier, info, - ) - require.NoError(t, err) - } - - // Now delete the payment at index 1 (the second - // payment). - pmt, err := paymentDB.FetchPayment( - ctx, paymentInfos[1].PaymentIdentifier, - ) - require.NoError(t, err) - - // We delete the whole payment. - err = paymentDB.DeletePayment( - ctx, paymentInfos[1].PaymentIdentifier, false, - ) - require.NoError(t, err) - - // Verify the payment is deleted. - _, err = paymentDB.FetchPayment( - ctx, paymentInfos[1].PaymentIdentifier, - ) - require.ErrorIs( - t, err, ErrPaymentNotInitiated, - ) - - // Verify the index is removed (KV store only). - harness.AssertNoIndex( - t, pmt.SequenceNum, - ) - - // For the last payment, settle it so we have at least - // one completed payment for the "exclude incomplete" - // test case. - lastPaymentInfo := paymentInfos[numberOfPayments-1] - attempt, err := NewHtlcAttempt( - 1, priv, testRoute, - time.Unix(100, 0), - &lastPaymentInfo.PaymentIdentifier, - ) - require.NoError(t, err) - - _, err = paymentDB.RegisterAttempt( - ctx, lastPaymentInfo.PaymentIdentifier, - &attempt.HTLCAttemptInfo, - ) - require.NoError(t, err) - - var preimg lntypes.Preimage - copy(preimg[:], rev[:]) - - _, err = paymentDB.SettleAttempt( - ctx, lastPaymentInfo.PaymentIdentifier, - attempt.AttemptID, - &HTLCSettleInfo{ - Preimage: preimg, - }, - ) - require.NoError(t, err) - - // Fetch all payments in the database. - resp, err = paymentDB.QueryPayments( - ctx, Query{ - IndexOffset: 0, - MaxPayments: math.MaxUint64, - IncludeIncomplete: true, - }, - ) - require.NoError(t, err) - - allPayments := resp.Payments - - if len(allPayments) != 5 { - t.Fatalf("Number of payments received does "+ - "not match expected one. Got %v, "+ - "want %v.", len(allPayments), 5) - } - - querySlice, err := paymentDB.QueryPayments( - ctx, tt.query, - ) - require.NoError(t, err) - - if tt.firstIndex != querySlice.FirstIndexOffset || - tt.lastIndex != querySlice.LastIndexOffset { - - t.Errorf("First or last index does not match "+ - "expected index. Want (%d, %d), "+ - "got (%d, %d).", - tt.firstIndex, tt.lastIndex, - querySlice.FirstIndexOffset, - querySlice.LastIndexOffset) - } - - if len(querySlice.Payments) != len(tt.expectedSeqNrs) { - t.Errorf("expected: %v payments, got: %v", - len(tt.expectedSeqNrs), - len(querySlice.Payments)) - } - - for i, seqNr := range tt.expectedSeqNrs { - q := querySlice.Payments[i] - if seqNr != q.SequenceNum { - t.Errorf("sequence numbers do not "+ - "match, got %v, want %v", - q.SequenceNum, seqNr) - } - } - - // Verify CountTotal is set correctly when requested. - if tt.query.CountTotal { - // We should have 5 total payments - // (6 created - 1 deleted). - expectedTotal := uint64(5) - require.Equal( - t, expectedTotal, querySlice.TotalCount, - "expected total count %v, got %v", - expectedTotal, querySlice.TotalCount) - } else { - require.Equal( - t, uint64(0), querySlice.TotalCount, - "expected total count 0 when "+ - "CountTotal=false") - } - }) - } -} - -// TestFetchInFlightPayments tests that FetchInFlightPayments correctly returns -// only payments that are in-flight. -func TestFetchInFlightPayments(t *testing.T) { - t.Parallel() - - ctx := t.Context() - - paymentDB, _ := NewTestDB(t) - - // Register payments with different statuses: - // 1. A payment with two failed attempts (StatusFailed). - // 2. A payment with one failed and one settled attempt - // (StatusSucceeded). - // 3. A payment with one failed and one in-flight attempt - // (StatusInFlight). - // 4. Another payment with one failed and one in-flight attempt - // (StatusInFlight). - payments := []*payment{ - {status: StatusFailed}, - {status: StatusSucceeded}, - {status: StatusInFlight}, - {status: StatusInFlight}, - } - - // Use helper function to register the test payments in the database and - // populate the data to the payments slice. - createTestPayments(t, paymentDB, payments) - - // Check that all payments are there as we added them. - assertDBPayments(t, paymentDB, payments) - - // Fetch in-flight payments. - inFlightPayments, err := paymentDB.FetchInFlightPayments(ctx) - require.NoError(t, err) - - // We should only get the two in-flight payments. - require.Len(t, inFlightPayments, 2) - - // Verify that the returned payments are the in-flight ones. - inFlightHashes := make(map[lntypes.Hash]struct{}) - for _, p := range inFlightPayments { - require.Equal(t, StatusInFlight, p.Status) - inFlightHashes[p.Info.PaymentIdentifier] = struct{}{} - } - - // Check that the in-flight payments match the expected ones. - require.Contains(t, inFlightHashes, payments[2].id) - require.Contains(t, inFlightHashes, payments[3].id) - - // Now settle one of the in-flight payments. - preimg := genPreimage(t) - - _, err = paymentDB.SettleAttempt( - ctx, payments[2].id, 5, - &HTLCSettleInfo{ - Preimage: preimg, - }, - ) - require.NoError(t, err) - - // Fetch in-flight payments again. - inFlightPayments, err = paymentDB.FetchInFlightPayments(ctx) - require.NoError(t, err) - - // We should now only get one in-flight payment. - require.Len(t, inFlightPayments, 1) - require.Equal( - t, payments[3].id, - inFlightPayments[0].Info.PaymentIdentifier, - ) - require.Equal(t, StatusInFlight, inFlightPayments[0].Status) -} - -// TestFetchInFlightPaymentsMultipleAttempts tests that when fetching in-flight -// payments, a payment with multiple in-flight attempts is only returned once. -func TestFetchInFlightPaymentsMultipleAttempts(t *testing.T) { - t.Parallel() - - ctx := t.Context() - - paymentDB, _ := NewTestDB(t) - - preimg := genPreimage(t) - - rhash := sha256.Sum256(preimg[:]) - info := genPaymentCreationInfo(t, rhash) - - // Init payment with double the amount to allow two attempts. - info.Value *= 2 - err := paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) - require.NoError(t, err) - - // Register two attempts for the same payment. - attempt1 := genAttemptWithHash(t, 0, genSessionKey(t), rhash) - - _, err = paymentDB.RegisterAttempt( - ctx, info.PaymentIdentifier, attempt1, - ) - require.NoError(t, err) - - attempt2 := genAttemptWithHash(t, 1, genSessionKey(t), rhash) - - _, err = paymentDB.RegisterAttempt( - ctx, info.PaymentIdentifier, attempt2, - ) - require.NoError(t, err) - - // Both attempts are in-flight. Fetch in-flight payments. - inFlightPayments, err := paymentDB.FetchInFlightPayments(ctx) - require.NoError(t, err) - - // We should only get one payment even though it has 2 in-flight - // attempts. - require.Len(t, inFlightPayments, 1) - require.Equal( - t, info.PaymentIdentifier, - inFlightPayments[0].Info.PaymentIdentifier, - ) - require.Equal(t, StatusInFlight, inFlightPayments[0].Status) - - // Verify the payment has both attempts. - require.Len(t, inFlightPayments[0].HTLCs, 2) -} - -// TestFetchInFlightPaymentsIncludesRetryablePayments tests that payments with -// only failed HTLCs but no payment-level failure reason are still returned as -// in-flight. This matches the shared payment state machine used by the router. -func TestFetchInFlightPaymentsIncludesRetryablePayments(t *testing.T) { - t.Parallel() - - ctx := t.Context() - - paymentDB, _ := NewTestDB(t) - - preimg := genPreimage(t) - rhash := sha256.Sum256(preimg[:]) - info := genPaymentCreationInfo(t, rhash) - - err := paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) - require.NoError(t, err) - - attempt := genAttemptWithHash(t, 0, genSessionKey(t), rhash) - _, err = paymentDB.RegisterAttempt(ctx, info.PaymentIdentifier, attempt) - require.NoError(t, err) - - _, err = paymentDB.FailAttempt( - ctx, info.PaymentIdentifier, attempt.AttemptID, - &HTLCFailInfo{Reason: HTLCFailUnreadable}, - ) - require.NoError(t, err) - - payment, err := paymentDB.FetchPayment(ctx, info.PaymentIdentifier) - require.NoError(t, err) - require.Equal(t, StatusInFlight, payment.Status) - - inFlightPayments, err := paymentDB.FetchInFlightPayments(ctx) - require.NoError(t, err) - - inFlightHashes := make(map[lntypes.Hash]struct{}, len(inFlightPayments)) - for _, p := range inFlightPayments { - inFlightHashes[p.Info.PaymentIdentifier] = struct{}{} - } - - require.Contains(t, inFlightHashes, info.PaymentIdentifier) -} - -// TestFetchInFlightPaymentsIncludesInitiatedPayments tests that payments which -// have been initialized but have not yet registered an HTLC are still returned -// as non-terminal payments. -func TestFetchInFlightPaymentsIncludesInitiatedPayments(t *testing.T) { - t.Parallel() - - ctx := t.Context() - - paymentDB, _ := NewTestDB(t) - - preimg := genPreimage(t) - rhash := sha256.Sum256(preimg[:]) - info := genPaymentCreationInfo(t, rhash) - - err := paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) - require.NoError(t, err) - - payment, err := paymentDB.FetchPayment(ctx, info.PaymentIdentifier) - require.NoError(t, err) - require.Equal(t, StatusInitiated, payment.Status) - - inFlightPayments, err := paymentDB.FetchInFlightPayments(ctx) - require.NoError(t, err) - - inFlightHashes := make(map[lntypes.Hash]struct{}, len(inFlightPayments)) - for _, p := range inFlightPayments { - inFlightHashes[p.Info.PaymentIdentifier] = struct{}{} - } - - require.Contains(t, inFlightHashes, info.PaymentIdentifier) -} - -// TestRouteFirstHopData tests that Route.FirstHopAmount and -// Route.FirstHopWireCustomRecords are correctly stored and retrieved. -func TestRouteFirstHopData(t *testing.T) { - t.Parallel() - - ctx := t.Context() - - paymentDB, _ := NewTestDB(t) - - preimg := genPreimage(t) - rhash := sha256.Sum256(preimg[:]) - info := genPaymentCreationInfo(t, rhash) - firstHopAmount := lnwire.MilliSatoshi(1234) - - // Init payment. - err := paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) - require.NoError(t, err) - - // Create an attempt with both FirstHopAmount and - // FirstHopWireCustomRecords set on the route. - attempt := genAttemptWithHash(t, 0, genSessionKey(t), rhash) - attempt.Route.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0]( - tlv.NewBigSizeT(firstHopAmount), - ) - typeIdx1 := uint64(lnwire.MinCustomRecordsTlvType + 10) - typeIdx2 := uint64(lnwire.MinCustomRecordsTlvType + 20) - attempt.Route.FirstHopWireCustomRecords = lnwire.CustomRecords{ - typeIdx1: []byte("wire_record_1"), - typeIdx2: []byte("wire_record_2"), - } - - _, err = paymentDB.RegisterAttempt(ctx, info.PaymentIdentifier, attempt) - require.NoError(t, err) - - // Fetch the payment and verify first hop data was stored. - payment, err := paymentDB.FetchPayment(ctx, info.PaymentIdentifier) - require.NoError(t, err) - - require.Len(t, payment.HTLCs, 1) - htlc := payment.HTLCs[0] - - // Verify the FirstHopAmount matches what we set. - require.NotNil(t, htlc.Route.FirstHopAmount) - require.Equal( - t, firstHopAmount, - htlc.Route.FirstHopAmount.Val.Int(), - ) - - // Verify the FirstHopWireCustomRecords match what we set. - require.NotEmpty(t, htlc.Route.FirstHopWireCustomRecords) - require.Len(t, htlc.Route.FirstHopWireCustomRecords, 2) - require.Equal( - t, []byte("wire_record_1"), - htlc.Route.FirstHopWireCustomRecords[typeIdx1], - ) - require.Equal( - t, []byte("wire_record_2"), - htlc.Route.FirstHopWireCustomRecords[typeIdx2], - ) -} - -// TestRegisterAttemptWithAMP tests that AMP data is correctly stored and -// retrieved on route hops. -func TestRegisterAttemptWithAMP(t *testing.T) { - t.Parallel() - - ctx := t.Context() - - paymentDB, _ := NewTestDB(t) - - preimg := genPreimage(t) - rhash := sha256.Sum256(preimg[:]) - info := genPaymentCreationInfo(t, rhash) - - // Init payment. - err := paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) - require.NoError(t, err) - - // Create a basic attempt, then modify the route to include AMP data. - // This bypasses the route validation in NewHtlcAttempt. - attempt := genAttemptWithHash(t, 0, genSessionKey(t), rhash) - - // Add AMP data to the final hop. - rootShare := [32]byte{1, 2, 3, 4} - setID := [32]byte{5, 6, 7, 8} - childIndex := uint32(42) - - finalHopIdx := len(attempt.Route.Hops) - 1 - attempt.Route.Hops[finalHopIdx].AMP = record.NewAMP( - rootShare, setID, childIndex, - ) - - _, err = paymentDB.RegisterAttempt(ctx, info.PaymentIdentifier, attempt) - require.NoError(t, err) - - // Fetch the payment and verify AMP data was stored. - payment, err := paymentDB.FetchPayment(ctx, info.PaymentIdentifier) - require.NoError(t, err) - - require.Len(t, payment.HTLCs, 1) - htlc := payment.HTLCs[0] - - // Verify the AMP data on the final hop matches what we set. - finalHop := htlc.Route.Hops[finalHopIdx] - require.NotNil(t, finalHop.AMP) - require.Equal(t, rootShare, finalHop.AMP.RootShare()) - require.Equal(t, setID, finalHop.AMP.SetID()) - require.Equal(t, childIndex, finalHop.AMP.ChildIndex()) -} - -// TestRegisterAttemptPreservesAttemptHash tests that an attempt's own hash is -// preserved independently from the payment identifier. This is especially -// important for AMP payments where the payment identifier is the SetID and the -// individual HTLC attempts each use their own payment hash. -func TestRegisterAttemptPreservesAttemptHash(t *testing.T) { - t.Parallel() - - ctx := t.Context() - - paymentDB, _ := NewTestDB(t) - - setID := lntypes.Hash{1, 2, 3, 4} - attemptHash := lntypes.Hash{5, 6, 7, 8} - info := genPaymentCreationInfo(t, setID) - - err := paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) - require.NoError(t, err) - - attempt := genAttemptWithHash(t, 0, genSessionKey(t), attemptHash) - finalHopIdx := len(attempt.Route.Hops) - 1 - attempt.Route.Hops[finalHopIdx].AMP = record.NewAMP( - [32]byte{9, 10, 11, 12}, setID, 99, - ) - - _, err = paymentDB.RegisterAttempt(ctx, info.PaymentIdentifier, attempt) - require.NoError(t, err) - - payment, err := paymentDB.FetchPayment(ctx, info.PaymentIdentifier) - require.NoError(t, err) - require.Len(t, payment.HTLCs, 1) - require.NotNil(t, payment.HTLCs[0].Hash) - require.Equal(t, attemptHash, *payment.HTLCs[0].Hash) - require.NotEqual(t, info.PaymentIdentifier, *payment.HTLCs[0].Hash) -} - -// TestRegisterAttemptWithBlindedRoute tests that blinded route data -// (EncryptedData, BlindingPoint, TotalAmtMsat) is correctly stored and -// retrieved. -func TestRegisterAttemptWithBlindedRoute(t *testing.T) { - t.Parallel() - - ctx := t.Context() - - paymentDB, _ := NewTestDB(t) - - preimg := genPreimage(t) - rhash := sha256.Sum256(preimg[:]) - - // Create payment info with amount matching - // testBlindedRoute.TotalAmount. - info := &PaymentCreationInfo{ - PaymentIdentifier: rhash, - Value: testBlindedRoute.TotalAmount, - CreationTime: time.Unix(time.Now().Unix(), 0), - PaymentRequest: []byte("blinded"), - } - - // Init payment. - err := paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) - require.NoError(t, err) - - // Create a basic attempt, then replace the route with testBlindedRoute. - // This bypasses the route validation in NewHtlcAttempt. - attempt := genAttemptWithHash(t, 0, genSessionKey(t), rhash) - - // Replace with testBlindedRoute which has the correct blinded route - // structure. - attempt.Route = testBlindedRoute - - _, err = paymentDB.RegisterAttempt(ctx, info.PaymentIdentifier, attempt) - require.NoError(t, err) - - // Fetch the payment and verify blinded route data was stored. - payment, err := paymentDB.FetchPayment(ctx, info.PaymentIdentifier) - require.NoError(t, err) - - require.Len(t, payment.HTLCs, 1) - htlc := payment.HTLCs[0] - - // Verify the blinded route data. - require.Len(t, htlc.Route.Hops, 3) - - // First hop (introduction point) should have BlindingPoint and - // EncryptedData. - hop0 := htlc.Route.Hops[0] - require.Equal(t, []byte{1, 3, 3}, hop0.EncryptedData) - require.NotNil(t, hop0.BlindingPoint) - require.True(t, hop0.BlindingPoint.IsEqual(pub)) - - // Second hop (intermediate) should have only EncryptedData. - hop1 := htlc.Route.Hops[1] - require.Equal(t, []byte{3, 2, 1}, hop1.EncryptedData) - require.Nil(t, hop1.BlindingPoint) - - // Third hop (final) should have EncryptedData, AmtToForward, - // OutgoingTimeLock, and TotalAmtMsat. - hop2 := htlc.Route.Hops[2] - require.Equal(t, []byte{2, 2, 2}, hop2.EncryptedData) - require.Equal(t, lnwire.MilliSatoshi(1000), hop2.AmtToForward) - require.Equal(t, uint32(100), hop2.OutgoingTimeLock) - require.Equal(t, lnwire.MilliSatoshi(1000), hop2.TotalAmtMsat) -} - -// TestFailAttemptWithoutMessage tests that FailAttempt works correctly when -// no failure message is provided. -func TestFailAttemptWithoutMessage(t *testing.T) { - t.Parallel() - - ctx := t.Context() - - paymentDB, _ := NewTestDB(t) - - preimg := genPreimage(t) - rhash := sha256.Sum256(preimg[:]) - info := genPaymentCreationInfo(t, rhash) - - // Init payment. - err := paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) - require.NoError(t, err) - - // Register an attempt. - attempt := genAttemptWithHash(t, 0, genSessionKey(t), rhash) - - _, err = paymentDB.RegisterAttempt(ctx, info.PaymentIdentifier, attempt) - require.NoError(t, err) - - // Fail the attempt without a failure message (nil Message). - failInfo := &HTLCFailInfo{ - Reason: HTLCFailUnreadable, - FailureSourceIndex: 2, - Message: nil, // No message. - } - - payment, err := paymentDB.FailAttempt( - ctx, info.PaymentIdentifier, attempt.AttemptID, failInfo, - ) - require.NoError(t, err) - require.NotNil(t, payment) - - // Verify the attempt was failed. - require.Len(t, payment.HTLCs, 1) - htlc := payment.HTLCs[0] - require.NotNil(t, htlc.Failure) - require.Equal(t, HTLCFailUnreadable, htlc.Failure.Reason) - require.Equal(t, uint32(2), htlc.Failure.FailureSourceIndex) - require.Nil(t, htlc.Failure.Message) -} - -// TestFailAttemptWithMessage tests that FailAttempt correctly stores and -// retrieves a failure message. -func TestFailAttemptWithMessage(t *testing.T) { - t.Parallel() - - ctx := t.Context() - - paymentDB, _ := NewTestDB(t) - - preimg := genPreimage(t) - rhash := sha256.Sum256(preimg[:]) - info := genPaymentCreationInfo(t, rhash) - - // Init payment. - err := paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) - require.NoError(t, err) - - // Register an attempt. - attempt := genAttemptWithHash(t, 0, genSessionKey(t), rhash) - - _, err = paymentDB.RegisterAttempt(ctx, info.PaymentIdentifier, attempt) - require.NoError(t, err) - - // Create a failure message. - failureMsg := lnwire.NewTemporaryChannelFailure(nil) - - // Fail the attempt with a failure message. - failInfo := &HTLCFailInfo{ - Reason: HTLCFailUnreadable, - FailureSourceIndex: 1, - Message: failureMsg, - } - - payment, err := paymentDB.FailAttempt( - ctx, info.PaymentIdentifier, attempt.AttemptID, failInfo, - ) - require.NoError(t, err) - require.NotNil(t, payment) - - // Verify the attempt was failed. - require.Len(t, payment.HTLCs, 1) - htlc := payment.HTLCs[0] - require.NotNil(t, htlc.Failure) - require.Equal(t, HTLCFailUnreadable, htlc.Failure.Reason) -} - -// TestFailAttemptOnSucceededPayment tests that FailAttempt returns an error -// when trying to fail an attempt on an already succeeded payment. -func TestFailAttemptOnSucceededPayment(t *testing.T) { - t.Parallel() - - ctx := t.Context() - - paymentDB, _ := NewTestDB(t) - - preimg := genPreimage(t) - rhash := sha256.Sum256(preimg[:]) - info := genPaymentCreationInfo(t, rhash) - - // Init payment. - err := paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) - require.NoError(t, err) - - // Register an attempt. - attempt := genAttemptWithHash(t, 0, genSessionKey(t), rhash) - - _, err = paymentDB.RegisterAttempt(ctx, info.PaymentIdentifier, attempt) - require.NoError(t, err) - - // Settle the attempt, which makes the payment succeed. - _, err = paymentDB.SettleAttempt( - ctx, info.PaymentIdentifier, attempt.AttemptID, - &HTLCSettleInfo{Preimage: preimg}, - ) - require.NoError(t, err) - - // Now try to fail the same attempt - this should fail because the - // payment is already succeeded. - failInfo := &HTLCFailInfo{ - Reason: HTLCFailUnreadable, - } - - _, err = paymentDB.FailAttempt( - ctx, info.PaymentIdentifier, attempt.AttemptID, failInfo, - ) - require.Error(t, err) - require.ErrorIs(t, err, ErrPaymentAlreadySucceeded) -} - -// TestFetchPaymentWithNoAttempts tests that FetchPayment correctly returns a -// payment that has been initialized but has no HTLC attempts yet. This tests -// the early return path in batchLoadPaymentDetailsData when there are no -// attempts. -func TestFetchPaymentWithNoAttempts(t *testing.T) { - t.Parallel() - - ctx := t.Context() - - paymentDB, _ := NewTestDB(t) - - preimg := genPreimage(t) - rhash := sha256.Sum256(preimg[:]) - info := genPaymentCreationInfo(t, rhash) - - // Init payment but don't register any attempts. - err := paymentDB.InitPayment(ctx, info.PaymentIdentifier, info) - require.NoError(t, err) - - // Fetch the payment - it should have no HTLCs. - payment, err := paymentDB.FetchPayment(ctx, info.PaymentIdentifier) - require.NoError(t, err) - require.NotNil(t, payment) - - // Verify the payment has no HTLCs. - require.Empty(t, payment.HTLCs) - - // Verify the payment info is correct. - require.Equal(t, info.PaymentIdentifier, payment.Info.PaymentIdentifier) - require.Equal(t, info.Value, payment.Info.Value) - require.Equal(t, StatusInitiated, payment.Status) -} diff --git a/payments/db/query.go b/payments/db/query.go index a45b10a4f..40dfd4321 100644 --- a/payments/db/query.go +++ b/payments/db/query.go @@ -44,10 +44,6 @@ type Query struct { // CreationDateEnd, expressed in Unix seconds, if set, filters out all // payments with a creation date less than or equal to it. CreationDateEnd int64 - - // OmitHops skips loading hop and hop-level custom record data for - // HTLC attempts when set to true. - OmitHops bool } // Response contains the result of a query to the payments database. diff --git a/payments/db/sql_converters.go b/payments/db/sql_converters.go deleted file mode 100644 index 7e19e333b..000000000 --- a/payments/db/sql_converters.go +++ /dev/null @@ -1,303 +0,0 @@ -package paymentsdb - -import ( - "bytes" - "fmt" - "strconv" - "time" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/record" - "github.com/lightningnetwork/lnd/routing/route" - "github.com/lightningnetwork/lnd/sqldb/sqlc" - "github.com/lightningnetwork/lnd/tlv" -) - -// dbPaymentToCreationInfo converts database payment data to the -// PaymentCreationInfo struct. -func dbPaymentToCreationInfo(paymentIdentifier []byte, amountMsat int64, - createdAt time.Time, intentPayload []byte, - firstHopCustomRecords lnwire.CustomRecords) *PaymentCreationInfo { - - // This is the payment hash for non-AMP payments and the SetID for AMP - // payments. - var identifier lntypes.Hash - copy(identifier[:], paymentIdentifier) - - return &PaymentCreationInfo{ - PaymentIdentifier: identifier, - Value: lnwire.MilliSatoshi(amountMsat), - // The creation time is stored in the database as UTC but here - // we convert it to local time. - CreationTime: createdAt.Local(), - PaymentRequest: intentPayload, - FirstHopCustomRecords: firstHopCustomRecords, - } -} - -// dbAttemptToHTLCAttempt converts a database HTLC attempt to an HTLCAttempt. -func dbAttemptToHTLCAttempt(dbAttempt sqlc.FetchHtlcAttemptsForPaymentsRow, - hops []sqlc.FetchHopsForAttemptsRow, - hopCustomRecords map[int64][]sqlc.PaymentHopCustomRecord, - routeCustomRecords []sqlc.PaymentAttemptFirstHopCustomRecord, - includeHops bool) ( - *HTLCAttempt, error) { - - // Convert route-level first hop custom records to CustomRecords map. - var firstHopWireCustomRecords lnwire.CustomRecords - if includeHops && len(routeCustomRecords) > 0 { - firstHopWireCustomRecords = make(lnwire.CustomRecords) - for _, record := range routeCustomRecords { - firstHopWireCustomRecords[uint64(record.Key)] = - record.Value - } - } - - // Build the route from the database data. - route, err := dbDataToRoute( - hops, hopCustomRecords, dbAttempt.FirstHopAmountMsat, - dbAttempt.RouteTotalTimeLock, dbAttempt.RouteTotalAmount, - dbAttempt.RouteSourceKey, firstHopWireCustomRecords, - !includeHops, - ) - if err != nil { - return nil, fmt.Errorf("failed to convert to route: %w", - err) - } - - hash, err := lntypes.MakeHash(dbAttempt.PaymentHash) - if err != nil { - return nil, fmt.Errorf("failed to parse payment "+ - "hash: %w", err) - } - - // Create the attempt info. - var sessionKey [32]byte - copy(sessionKey[:], dbAttempt.SessionKey) - - info := HTLCAttemptInfo{ - AttemptID: uint64(dbAttempt.AttemptIndex), - sessionKey: sessionKey, - Route: *route, - AttemptTime: dbAttempt.AttemptTime, - Hash: &hash, - } - - attempt := &HTLCAttempt{ - HTLCAttemptInfo: info, - } - - // If there's no resolution type, the attempt is still in-flight. - // Return early without processing settlement or failure info. - if !dbAttempt.ResolutionType.Valid { - return attempt, nil - } - - // Add settlement info if present. - if HTLCAttemptResolutionType(dbAttempt.ResolutionType.Int32) == - HTLCAttemptResolutionSettled { - - var preimage lntypes.Preimage - copy(preimage[:], dbAttempt.SettlePreimage) - - attempt.Settle = &HTLCSettleInfo{ - Preimage: preimage, - SettleTime: dbAttempt.ResolutionTime.Time, - } - } - - // Add failure info if present. - if HTLCAttemptResolutionType(dbAttempt.ResolutionType.Int32) == - HTLCAttemptResolutionFailed { - - failure := &HTLCFailInfo{ - FailTime: dbAttempt.ResolutionTime.Time, - } - - if dbAttempt.HtlcFailReason.Valid { - failure.Reason = HTLCFailReason( - dbAttempt.HtlcFailReason.Int32, - ) - } - - if dbAttempt.FailureSourceIndex.Valid { - failure.FailureSourceIndex = uint32( - dbAttempt.FailureSourceIndex.Int32, - ) - } - - // Decode the failure message if present. - if len(dbAttempt.FailureMsg) > 0 { - msg, err := lnwire.DecodeFailureMessage( - bytes.NewReader(dbAttempt.FailureMsg), 0, - ) - if err != nil { - return nil, fmt.Errorf("failed to decode "+ - "failure message: %w", err) - } - failure.Message = msg - } - - attempt.Failure = failure - } - - return attempt, nil -} - -// dbDataToRoute converts database route data to a route.Route. -func dbDataToRoute(hops []sqlc.FetchHopsForAttemptsRow, - hopCustomRecords map[int64][]sqlc.PaymentHopCustomRecord, - firstHopAmountMsat int64, totalTimeLock int32, totalAmount int64, - sourceKey []byte, firstHopWireCustomRecords lnwire.CustomRecords, - allowEmpty bool) ( - *route.Route, error) { - - if len(hops) == 0 { - if !allowEmpty { - return nil, fmt.Errorf("no hops provided") - } - - var sourceNode route.Vertex - copy(sourceNode[:], sourceKey) - - route := &route.Route{ - TotalTimeLock: uint32(totalTimeLock), - TotalAmount: lnwire.MilliSatoshi( - totalAmount, - ), - SourcePubKey: sourceNode, - Hops: nil, - FirstHopWireCustomRecords: firstHopWireCustomRecords, - } - - if firstHopAmountMsat != 0 { - route.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0]( - tlv.NewBigSizeT(lnwire.MilliSatoshi( - firstHopAmountMsat, - )), - ) - } - - return route, nil - } - - // Hops are already sorted by hop_index from the SQL query. - routeHops := make([]*route.Hop, len(hops)) - - for i, hop := range hops { - pubKey, err := route.NewVertexFromBytes(hop.PubKey) - if err != nil { - return nil, fmt.Errorf("failed to parse pub key: %w", - err) - } - - var channelID uint64 - if hop.Scid != "" { - // The SCID is stored as a string representation - // of the uint64. - var err error - channelID, err = strconv.ParseUint(hop.Scid, 10, 64) - if err != nil { - return nil, fmt.Errorf("failed to parse "+ - "scid: %w", err) - } - } - - routeHop := &route.Hop{ - PubKeyBytes: pubKey, - ChannelID: channelID, - OutgoingTimeLock: uint32(hop.OutgoingTimeLock), - AmtToForward: lnwire.MilliSatoshi(hop.AmtToForward), - } - - // Add MPP record if present. - if len(hop.MppPaymentAddr) > 0 { - var paymentAddr [32]byte - copy(paymentAddr[:], hop.MppPaymentAddr) - routeHop.MPP = record.NewMPP( - lnwire.MilliSatoshi(hop.MppTotalMsat.Int64), - paymentAddr, - ) - } - - // Add AMP record if present. - if len(hop.AmpRootShare) > 0 { - var rootShare [32]byte - copy(rootShare[:], hop.AmpRootShare) - var setID [32]byte - copy(setID[:], hop.AmpSetID) - - routeHop.AMP = record.NewAMP( - rootShare, setID, - uint32(hop.AmpChildIndex.Int32), - ) - } - - // Add blinding point if present (only for introduction node - // in blinded route). - if len(hop.BlindingPoint) > 0 { - pubKey, err := btcec.ParsePubKey(hop.BlindingPoint) - if err != nil { - return nil, fmt.Errorf("failed to parse "+ - "blinding point: %w", err) - } - routeHop.BlindingPoint = pubKey - } - - // Add encrypted data if present (for all blinded hops). - if len(hop.EncryptedData) > 0 { - routeHop.EncryptedData = hop.EncryptedData - } - - // Add total amount if present (only for final hop in blinded - // route). - if hop.BlindedPathTotalAmt.Valid { - routeHop.TotalAmtMsat = lnwire.MilliSatoshi( - hop.BlindedPathTotalAmt.Int64, - ) - } - - // Add hop-level custom records. - if records, ok := hopCustomRecords[hop.ID]; ok { - routeHop.CustomRecords = make( - record.CustomSet, - ) - for _, rec := range records { - routeHop.CustomRecords[uint64(rec.Key)] = - rec.Value - } - } - - // Add metadata if present. - if len(hop.MetaData) > 0 { - routeHop.Metadata = hop.MetaData - } - - routeHops[i] = routeHop - } - - // Parse the source node public key. - var sourceNode route.Vertex - copy(sourceNode[:], sourceKey) - - route := &route.Route{ - TotalTimeLock: uint32(totalTimeLock), - TotalAmount: lnwire.MilliSatoshi(totalAmount), - SourcePubKey: sourceNode, - Hops: routeHops, - FirstHopWireCustomRecords: firstHopWireCustomRecords, - } - - // Set the first hop amount if it is set. - if firstHopAmountMsat != 0 { - route.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0]( - tlv.NewBigSizeT(lnwire.MilliSatoshi( - firstHopAmountMsat, - )), - ) - } - - return route, nil -} diff --git a/payments/db/sql_converters_test.go b/payments/db/sql_converters_test.go deleted file mode 100644 index e28da1e75..000000000 --- a/payments/db/sql_converters_test.go +++ /dev/null @@ -1,246 +0,0 @@ -//go:build test_db_sqlite || test_db_postgres - -package paymentsdb - -import ( - "database/sql" - "testing" - "time" - - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/record" - "github.com/lightningnetwork/lnd/sqldb/sqlc" - "github.com/stretchr/testify/require" -) - -// TestOmitHopsRouteBuilding tests the omit_hops behavior in dbDataToRoute. -// When allowEmpty is true (omit_hops=true) and no hops are provided, a minimal -// route with only route-level fields should be returned. When allowEmpty is -// false, the same scenario should return an error. -func TestOmitHopsRouteBuilding(t *testing.T) { - t.Parallel() - - sourceKey := vertex[:] - - // With allowEmpty=true (omit_hops), empty hops should return a - // minimal route preserving route-level fields. - t.Run("omit hops returns minimal route", func(t *testing.T) { - t.Parallel() - - r, err := dbDataToRoute( - nil, nil, 0, 123, 1000, sourceKey, nil, true, - ) - require.NoError(t, err) - require.Nil(t, r.Hops) - require.Equal(t, uint32(123), r.TotalTimeLock) - require.Equal(t, lnwire.MilliSatoshi(1000), r.TotalAmount) - require.Equal(t, vertex, r.SourcePubKey) - }) - - // With allowEmpty=false (include hops), empty hops should error. - t.Run("include hops errors on empty hops", func(t *testing.T) { - t.Parallel() - - _, err := dbDataToRoute( - nil, nil, 0, 123, 1000, sourceKey, nil, false, - ) - require.Error(t, err) - }) -} - -// TestOmitHopsAttemptConversion tests that dbAttemptToHTLCAttempt correctly -// handles the includeHops flag. When false, route custom records should be -// skipped and the route should have no hops. When true, hops and custom -// records should be fully populated. -func TestOmitHopsAttemptConversion(t *testing.T) { - t.Parallel() - - var paymentHash lntypes.Hash - copy(paymentHash[:], testHash[:]) - - sessionKey := genSessionKey(t) - var sessionKeyBytes [32]byte - copy(sessionKeyBytes[:], sessionKey.Serialize()) - - baseAttempt := sqlc.FetchHtlcAttemptsForPaymentsRow{ - ID: 1, - AttemptIndex: 1, - PaymentID: 1, - SessionKey: sessionKeyBytes[:], - AttemptTime: time.Now(), - PaymentHash: paymentHash[:], - RouteTotalTimeLock: 123, - RouteTotalAmount: 1000, - RouteSourceKey: vertex[:], - } - - hops := []sqlc.FetchHopsForAttemptsRow{ - { - ID: 10, - HtlcAttemptIndex: 1, - HopIndex: 0, - PubKey: vertex[:], - Scid: "12345", - OutgoingTimeLock: 111, - AmtToForward: 555, - }, - } - - hopCustomRecords := map[int64][]sqlc.PaymentHopCustomRecord{ - 10: {{ID: 1, HopID: 10, Key: 65536, Value: []byte("val")}}, - } - - routeCustomRecords := []sqlc.PaymentAttemptFirstHopCustomRecord{ - {ID: 1, HtlcAttemptIndex: 1, Key: 65537, Value: []byte("rcr")}, - } - - t.Run("include hops populates route fully", func(t *testing.T) { - t.Parallel() - - attempt, err := dbAttemptToHTLCAttempt( - baseAttempt, hops, hopCustomRecords, - routeCustomRecords, true, - ) - require.NoError(t, err) - require.Len(t, attempt.Route.Hops, 1) - require.Equal(t, uint64(12345), - attempt.Route.Hops[0].ChannelID) - require.Equal(t, - record.CustomSet{65536: []byte("val")}, - attempt.Route.Hops[0].CustomRecords, - ) - require.Equal(t, - lnwire.CustomRecords{65537: []byte("rcr")}, - attempt.Route.FirstHopWireCustomRecords, - ) - }) - - t.Run("omit hops skips route data", func(t *testing.T) { - t.Parallel() - - attempt, err := dbAttemptToHTLCAttempt( - baseAttempt, nil, nil, - routeCustomRecords, false, - ) - require.NoError(t, err) - require.Nil(t, attempt.Route.Hops) - require.Nil(t, attempt.Route.FirstHopWireCustomRecords) - require.Equal(t, uint32(123), attempt.Route.TotalTimeLock) - require.Equal(t, lnwire.MilliSatoshi(1000), - attempt.Route.TotalAmount) - }) -} - -// TestOmitHopsBuildPayment tests that buildPaymentFromBatchData passes the -// includeHops flag correctly, producing payments with or without hop data -// while preserving payment-level information in both cases. -func TestOmitHopsBuildPayment(t *testing.T) { - t.Parallel() - - var paymentHash lntypes.Hash - copy(paymentHash[:], testHash[:]) - - sessionKey := genSessionKey(t) - var sessionKeyBytes [32]byte - copy(sessionKeyBytes[:], sessionKey.Serialize()) - - now := time.Now().Truncate(time.Second) - - dbPayment := sqlc.FilterPaymentsRow{ - Payment: sqlc.Payment{ - ID: 1, - PaymentIdentifier: paymentHash[:], - AmountMsat: 1000, - CreatedAt: now.UTC(), - }, - IntentPayload: []byte("test_payload"), - } - - attemptRow := sqlc.FetchHtlcAttemptsForPaymentsRow{ - ID: 1, - AttemptIndex: 10, - PaymentID: 1, - SessionKey: sessionKeyBytes[:], - AttemptTime: now, - PaymentHash: paymentHash[:], - RouteTotalTimeLock: 123, - RouteTotalAmount: 1000, - RouteSourceKey: vertex[:], - ResolutionType: sql.NullInt32{ - Int32: int32(HTLCAttemptResolutionSettled), - Valid: true, - }, - ResolutionTime: sql.NullTime{ - Time: now, Valid: true, - }, - SettlePreimage: rev[:], - } - - hopRow := sqlc.FetchHopsForAttemptsRow{ - ID: 100, HtlcAttemptIndex: 10, HopIndex: 0, - PubKey: vertex[:], Scid: "12345", - OutgoingTimeLock: 111, AmtToForward: 555, - } - - makeBatchData := func( - withHops bool) *paymentsDetailsData { - - //nolint:ll - bd := &paymentsDetailsData{ - paymentCustomRecords: make( - map[int64][]sqlc.PaymentFirstHopCustomRecord, - ), - attempts: map[int64][]sqlc.FetchHtlcAttemptsForPaymentsRow{ - 1: {attemptRow}, - }, - hopsByAttempt: make( - map[int64][]sqlc.FetchHopsForAttemptsRow, - ), - hopCustomRecords: make( - map[int64][]sqlc.PaymentHopCustomRecord, - ), - routeCustomRecords: make( - map[int64][]sqlc.PaymentAttemptFirstHopCustomRecord, - ), - } - if withHops { - bd.hopsByAttempt[10] = []sqlc.FetchHopsForAttemptsRow{ - hopRow, - } - } - - return bd - } - - t.Run("include hops builds full payment", func(t *testing.T) { - t.Parallel() - - mp, err := buildPaymentFromBatchData( - dbPayment, makeBatchData(true), true, - ) - require.NoError(t, err) - require.Len(t, mp.HTLCs, 1) - require.Len(t, mp.HTLCs[0].Route.Hops, 1) - require.Equal(t, paymentHash, mp.Info.PaymentIdentifier) - require.NotNil(t, mp.HTLCs[0].Settle) - }) - - t.Run("omit hops preserves payment info without route data", - func(t *testing.T) { - t.Parallel() - - mp, err := buildPaymentFromBatchData( - dbPayment, makeBatchData(false), false, - ) - require.NoError(t, err) - require.Len(t, mp.HTLCs, 1) - require.Nil(t, mp.HTLCs[0].Route.Hops) - require.Equal(t, paymentHash, - mp.Info.PaymentIdentifier) - require.Equal(t, lnwire.MilliSatoshi(1000), - mp.Info.Value) - require.NotNil(t, mp.HTLCs[0].Settle) - }, - ) -} diff --git a/payments/db/sql_store.go b/payments/db/sql_store.go deleted file mode 100644 index 3d92385fd..000000000 --- a/payments/db/sql_store.go +++ /dev/null @@ -1,1924 +0,0 @@ -package paymentsdb - -import ( - "bytes" - "context" - "database/sql" - "errors" - "fmt" - "math" - "strconv" - "time" - - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/routing/route" - "github.com/lightningnetwork/lnd/sqldb" - "github.com/lightningnetwork/lnd/sqldb/sqlc" -) - -// PaymentIntentType represents the type of payment intent. -type PaymentIntentType int16 - -const ( - // PaymentIntentTypeBolt11 indicates a BOLT11 invoice payment. - PaymentIntentTypeBolt11 PaymentIntentType = 0 -) - -// HTLCAttemptResolutionType represents the type of HTLC attempt resolution. -type HTLCAttemptResolutionType int32 - -const ( - // HTLCAttemptResolutionSettled indicates the HTLC attempt was settled - // successfully with a preimage. - HTLCAttemptResolutionSettled HTLCAttemptResolutionType = 1 - - // HTLCAttemptResolutionFailed indicates the HTLC attempt failed. - HTLCAttemptResolutionFailed HTLCAttemptResolutionType = 2 -) - -// SQLQueries is a subset of the sqlc.Querier interface that can be used to -// execute queries against the SQL payments tables. -// -//nolint:ll,interfacebloat -type SQLQueries interface { - /* - Payment DB read operations. - */ - FilterPayments(ctx context.Context, query sqlc.FilterPaymentsParams) ([]sqlc.FilterPaymentsRow, error) - FilterPaymentsDesc(ctx context.Context, query sqlc.FilterPaymentsDescParams) ([]sqlc.FilterPaymentsDescRow, error) - FetchPayment(ctx context.Context, paymentIdentifier []byte) (sqlc.FetchPaymentRow, error) - FetchPaymentsByIDs(ctx context.Context, paymentIDs []int64) ([]sqlc.FetchPaymentsByIDsRow, error) - FetchNonTerminalPayments(ctx context.Context, arg sqlc.FetchNonTerminalPaymentsParams) ([]sqlc.FetchNonTerminalPaymentsRow, error) - - CountPayments(ctx context.Context) (int64, error) - - FetchHtlcAttemptsForPayments(ctx context.Context, paymentIDs []int64) ([]sqlc.FetchHtlcAttemptsForPaymentsRow, error) - FetchHtlcAttemptResolutionsForPayments(ctx context.Context, paymentIDs []int64) ([]sqlc.FetchHtlcAttemptResolutionsForPaymentsRow, error) - FetchHopsForAttempts(ctx context.Context, htlcAttemptIndices []int64) ([]sqlc.FetchHopsForAttemptsRow, error) - - FetchPaymentDuplicates(ctx context.Context, paymentID int64) ([]sqlc.PaymentDuplicate, error) - - FetchPaymentLevelFirstHopCustomRecords(ctx context.Context, paymentIDs []int64) ([]sqlc.PaymentFirstHopCustomRecord, error) - FetchRouteLevelFirstHopCustomRecords(ctx context.Context, htlcAttemptIndices []int64) ([]sqlc.PaymentAttemptFirstHopCustomRecord, error) - FetchHopLevelCustomRecords(ctx context.Context, hopIDs []int64) ([]sqlc.PaymentHopCustomRecord, error) - - /* - Payment DB write operations. - */ - InsertPaymentIntent(ctx context.Context, arg sqlc.InsertPaymentIntentParams) (int64, error) - InsertPayment(ctx context.Context, arg sqlc.InsertPaymentParams) (int64, error) - InsertPaymentFirstHopCustomRecord(ctx context.Context, arg sqlc.InsertPaymentFirstHopCustomRecordParams) error - - InsertHtlcAttempt(ctx context.Context, arg sqlc.InsertHtlcAttemptParams) (int64, error) - InsertRouteHop(ctx context.Context, arg sqlc.InsertRouteHopParams) (int64, error) - InsertRouteHopMpp(ctx context.Context, arg sqlc.InsertRouteHopMppParams) error - InsertRouteHopAmp(ctx context.Context, arg sqlc.InsertRouteHopAmpParams) error - InsertRouteHopBlinded(ctx context.Context, arg sqlc.InsertRouteHopBlindedParams) error - - InsertPaymentAttemptFirstHopCustomRecord(ctx context.Context, arg sqlc.InsertPaymentAttemptFirstHopCustomRecordParams) error - InsertPaymentHopCustomRecord(ctx context.Context, arg sqlc.InsertPaymentHopCustomRecordParams) error - - SettleAttempt(ctx context.Context, arg sqlc.SettleAttemptParams) error - FailAttempt(ctx context.Context, arg sqlc.FailAttemptParams) error - - FailPayment(ctx context.Context, arg sqlc.FailPaymentParams) (sql.Result, error) - - DeletePayment(ctx context.Context, paymentID int64) error - - // DeleteFailedAttempts removes all failed HTLCs from the db for a - // given payment. - DeleteFailedAttempts(ctx context.Context, paymentID int64) error -} - -// SQLMigrationQueries extends SQLQueries with the additional queries needed -// for the one-time migration from KV to SQL. Keeping them in a separate -// interface makes it clear which code paths are migration-only and prevents -// the regular store from accidentally depending on them. -// -//nolint:ll -type SQLMigrationQueries interface { - SQLQueries - - // FetchPaymentsByIDsMig is a migration-only batch fetch that returns - // payment data along with HTLC attempt counts for structural - // validation. - FetchPaymentsByIDsMig(ctx context.Context, paymentIDs []int64) ([]sqlc.FetchPaymentsByIDsMigRow, error) - - // InsertPaymentMig is a migration-only variant of InsertPayment that - // allows setting fail_reason when inserting historical payments, since - // for real payments they have not failed at creation time and so no - // failure reason would exist yet. - InsertPaymentMig(ctx context.Context, arg sqlc.InsertPaymentMigParams) (int64, error) - - // InsertPaymentDuplicateMig inserts a duplicate payment record during - // migration. - InsertPaymentDuplicateMig(ctx context.Context, arg sqlc.InsertPaymentDuplicateMigParams) (int64, error) -} - -// BatchedSQLQueries is a version of the SQLQueries that's capable -// of batched database operations. -type BatchedSQLQueries interface { - SQLQueries - sqldb.BatchedTx[SQLQueries] -} - -// SQLStore represents a storage backend. -type SQLStore struct { - cfg *SQLStoreConfig - db BatchedSQLQueries -} - -// A compile-time constraint to ensure SQLStore implements DB. -var _ DB = (*SQLStore)(nil) - -// SQLStoreConfig holds the configuration for the SQLStore. -type SQLStoreConfig struct { - // QueryConfig holds configuration values for SQL queries. - QueryCfg *sqldb.QueryConfig -} - -// NewSQLStore creates a new SQLStore instance given an open -// BatchedSQLQueries storage backend. -func NewSQLStore(cfg *SQLStoreConfig, db BatchedSQLQueries, - options ...OptionModifier) (*SQLStore, error) { - - opts := DefaultOptions() - for _, applyOption := range options { - applyOption(opts) - } - - if opts.NoMigration { - return nil, fmt.Errorf("the NoMigration option is not yet " + - "supported for SQL stores") - } - - return &SQLStore{ - cfg: cfg, - db: db, - }, nil -} - -// fetchPaymentWithCompleteData fetches a payment with all its related data -// including attempts, hops, and custom records from the database. -// This is a convenience wrapper around the batch loading functions for single -// payment operations. -func fetchPaymentWithCompleteData(ctx context.Context, - cfg *sqldb.QueryConfig, db SQLQueries, - dbPayment sqlc.PaymentAndIntent) (*MPPayment, error) { - - payment := dbPayment.GetPayment() - - // Load batch data for this single payment. - batchData, err := batchLoadPaymentDetailsData( - ctx, cfg, db, []int64{payment.ID}, true, - ) - if err != nil { - return nil, fmt.Errorf("failed to load batch data: %w", err) - } - - // Build the payment from the batch data. - return buildPaymentFromBatchData(dbPayment, batchData, true) -} - -// paymentsRelatedData holds all the batch-loaded data for multiple payments. -// This does not include the base payment and intent data which is fetched -// separately. It includes the additional data like attempts, hops, hop custom -// records, and route custom records. -type paymentsDetailsData struct { - // paymentCustomRecords maps payment ID to its custom records. - paymentCustomRecords map[int64][]sqlc.PaymentFirstHopCustomRecord - - // attempts maps payment ID to its HTLC attempts. - attempts map[int64][]sqlc.FetchHtlcAttemptsForPaymentsRow - - // hopsByAttempt maps attempt index to its hops. - hopsByAttempt map[int64][]sqlc.FetchHopsForAttemptsRow - - // hopCustomRecords maps hop ID to its custom records. - hopCustomRecords map[int64][]sqlc.PaymentHopCustomRecord - - // routeCustomRecords maps attempt index to its route-level custom - // records. - routeCustomRecords map[int64][]sqlc.PaymentAttemptFirstHopCustomRecord -} - -// batchLoadPaymentCustomRecords loads payment-level custom records for a given -// set of payment IDs. It uses a batch query to fetch all custom records for -// the given payment IDs. -func batchLoadPaymentCustomRecords(ctx context.Context, - cfg *sqldb.QueryConfig, db SQLQueries, paymentIDs []int64, - batchData *paymentsDetailsData) error { - - return sqldb.ExecuteBatchQuery( - ctx, cfg, paymentIDs, - func(id int64) int64 { return id }, - func(ctx context.Context, ids []int64) ( - []sqlc.PaymentFirstHopCustomRecord, error) { - - //nolint:ll - records, err := db.FetchPaymentLevelFirstHopCustomRecords( - ctx, ids, - ) - - return records, err - }, - func(ctx context.Context, - record sqlc.PaymentFirstHopCustomRecord) error { - - paymentRecords := - batchData.paymentCustomRecords[record.PaymentID] - - batchData.paymentCustomRecords[record.PaymentID] = - append(paymentRecords, record) - - return nil - }, - ) -} - -// batchLoadHtlcAttempts loads HTLC attempts for all payments and returns all -// attempt indices. It uses a batch query to fetch all attempts for the given -// payment IDs. -func batchLoadHtlcAttempts(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, paymentIDs []int64, - batchData *paymentsDetailsData) ([]int64, error) { - - var allAttemptIndices []int64 - - err := sqldb.ExecuteBatchQuery( - ctx, cfg, paymentIDs, - func(id int64) int64 { return id }, - func(ctx context.Context, ids []int64) ( - []sqlc.FetchHtlcAttemptsForPaymentsRow, error) { - - return db.FetchHtlcAttemptsForPayments(ctx, ids) - }, - func(ctx context.Context, - attempt sqlc.FetchHtlcAttemptsForPaymentsRow) error { - - batchData.attempts[attempt.PaymentID] = append( - batchData.attempts[attempt.PaymentID], attempt, - ) - allAttemptIndices = append( - allAttemptIndices, attempt.AttemptIndex, - ) - - return nil - }, - ) - - return allAttemptIndices, err -} - -// batchLoadHopsForAttempts loads hops for all attempts and returns all hop IDs. -// It uses a batch query to fetch all hops for the given attempt indices. -func batchLoadHopsForAttempts(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, attemptIndices []int64, - batchData *paymentsDetailsData) ([]int64, error) { - - var hopIDs []int64 - - err := sqldb.ExecuteBatchQuery( - ctx, cfg, attemptIndices, - func(idx int64) int64 { return idx }, - func(ctx context.Context, indices []int64) ( - []sqlc.FetchHopsForAttemptsRow, error) { - - return db.FetchHopsForAttempts(ctx, indices) - }, - func(ctx context.Context, - hop sqlc.FetchHopsForAttemptsRow) error { - - attemptHops := - batchData.hopsByAttempt[hop.HtlcAttemptIndex] - - batchData.hopsByAttempt[hop.HtlcAttemptIndex] = - append(attemptHops, hop) - - hopIDs = append(hopIDs, hop.ID) - - return nil - }, - ) - - return hopIDs, err -} - -// batchLoadHopCustomRecords loads hop-level custom records for all hops. It -// uses a batch query to fetch all custom records for the given hop IDs. -func batchLoadHopCustomRecords(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, hopIDs []int64, batchData *paymentsDetailsData) error { - - return sqldb.ExecuteBatchQuery( - ctx, cfg, hopIDs, - func(id int64) int64 { return id }, - func(ctx context.Context, ids []int64) ( - []sqlc.PaymentHopCustomRecord, error) { - - return db.FetchHopLevelCustomRecords(ctx, ids) - }, - func(ctx context.Context, - record sqlc.PaymentHopCustomRecord) error { - - // TODO(ziggie): Can we get rid of this? - // This has to be in place otherwise the - // comparison will not match. - if record.Value == nil { - record.Value = []byte{} - } - - batchData.hopCustomRecords[record.HopID] = append( - batchData.hopCustomRecords[record.HopID], - record, - ) - - return nil - }, - ) -} - -// batchLoadRouteCustomRecords loads route-level first hop custom records for -// all attempts. It uses a batch query to fetch all custom records for the given -// attempt indices. -func batchLoadRouteCustomRecords(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, attemptIndices []int64, - batchData *paymentsDetailsData) error { - - return sqldb.ExecuteBatchQuery( - ctx, cfg, attemptIndices, - func(idx int64) int64 { return idx }, - func(ctx context.Context, indices []int64) ( - []sqlc.PaymentAttemptFirstHopCustomRecord, error) { - - return db.FetchRouteLevelFirstHopCustomRecords( - ctx, indices, - ) - }, - func(ctx context.Context, - record sqlc.PaymentAttemptFirstHopCustomRecord) error { - - idx := record.HtlcAttemptIndex - attemptRecords := batchData.routeCustomRecords[idx] - - batchData.routeCustomRecords[idx] = - append(attemptRecords, record) - - return nil - }, - ) -} - -// paymentStatusData holds lightweight resolution data for computing -// payment status efficiently during deletion operations. -type paymentStatusData struct { - // resolutionTypes maps payment ID to a list of resolution types - // for that payment's HTLC attempts. - resolutionTypes map[int64][]sql.NullInt32 -} - -// batchLoadPaymentResolutions loads only HTLC resolution types for multiple -// payments. This is a lightweight alternative to batchLoadPaymentsRelatedData -// that's optimized for operations that only need to determine payment status. -func batchLoadPaymentResolutions(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, paymentIDs []int64) (*paymentStatusData, error) { - - batchStatusData := &paymentStatusData{ - resolutionTypes: make(map[int64][]sql.NullInt32), - } - - if len(paymentIDs) == 0 { - return batchStatusData, nil - } - - // Use a batch query to fetch all resolution types for the given payment - // IDs. - err := sqldb.ExecuteBatchQuery( - ctx, cfg, paymentIDs, - func(id int64) int64 { return id }, - func(ctx context.Context, ids []int64) ( - []sqlc.FetchHtlcAttemptResolutionsForPaymentsRow, - error) { - - return db.FetchHtlcAttemptResolutionsForPayments( - ctx, ids, - ) - }, - //nolint:ll - func(ctx context.Context, - res sqlc.FetchHtlcAttemptResolutionsForPaymentsRow) error { - - // Group resolutions by payment ID. - batchStatusData.resolutionTypes[res.PaymentID] = append( - batchStatusData.resolutionTypes[res.PaymentID], - res.ResolutionType, - ) - - return nil - }, - ) - if err != nil { - return nil, fmt.Errorf("failed to fetch HTLC resolutions: %w", - err) - } - - return batchStatusData, nil -} - -// loadPaymentResolutions is a single-payment wrapper around -// batchLoadPaymentResolutions for convenience and to prevent duplicate queries -// so we reuse the same batch query for all payments. -func loadPaymentResolutions(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, paymentID int64) ([]sql.NullInt32, error) { - - batchData, err := batchLoadPaymentResolutions( - ctx, cfg, db, []int64{paymentID}, - ) - if err != nil { - return nil, err - } - - return batchData.resolutionTypes[paymentID], nil -} - -// computePaymentStatusFromResolutions determines the payment status from -// resolution types and failure reason without building the complete MPPayment -// structure. This is a lightweight version that builds minimal HTLCAttempt -// structures and delegates to decidePaymentStatus for consistency. -func computePaymentStatusFromResolutions(resolutionTypes []sql.NullInt32, - failReason sql.NullInt32) (PaymentStatus, error) { - - // Build minimal HTLCAttempt slice with only resolution info. - htlcs := make([]HTLCAttempt, len(resolutionTypes)) - for i, resType := range resolutionTypes { - if !resType.Valid { - // NULL resolution_type means in-flight (no Settle, no - // Failure). - continue - } - - switch HTLCAttemptResolutionType(resType.Int32) { - case HTLCAttemptResolutionSettled: - // Mark as settled (preimage details not needed for - // status). - htlcs[i].Settle = &HTLCSettleInfo{} - - case HTLCAttemptResolutionFailed: - // Mark as failed (failure details not needed for - // status). - htlcs[i].Failure = &HTLCFailInfo{} - - default: - return 0, fmt.Errorf("unknown resolution type: %v", - resType.Int32) - } - } - - // Convert fail reason to FailureReason pointer. - var failureReason *FailureReason - if failReason.Valid { - reason := FailureReason(failReason.Int32) - failureReason = &reason - } - - // Use the existing status decision logic. - return decidePaymentStatus(htlcs, failureReason) -} - -// batchLoadPaymentDetailsData loads all related data for multiple payments in -// batch. It uses a batch queries to fetch all data for the given payment IDs. -func batchLoadPaymentDetailsData(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, paymentIDs []int64, includeHops bool) ( - *paymentsDetailsData, error) { - - batchData := &paymentsDetailsData{ - paymentCustomRecords: make( - map[int64][]sqlc.PaymentFirstHopCustomRecord, - ), - attempts: make( - map[int64][]sqlc.FetchHtlcAttemptsForPaymentsRow, - ), - hopsByAttempt: make( - map[int64][]sqlc.FetchHopsForAttemptsRow, - ), - hopCustomRecords: make( - map[int64][]sqlc.PaymentHopCustomRecord, - ), - routeCustomRecords: make( - map[int64][]sqlc.PaymentAttemptFirstHopCustomRecord, - ), - } - - if len(paymentIDs) == 0 { - return batchData, nil - } - - // Load payment-level custom records. - err := batchLoadPaymentCustomRecords( - ctx, cfg, db, paymentIDs, batchData, - ) - if err != nil { - return nil, fmt.Errorf("failed to fetch payment custom "+ - "records: %w", err) - } - - // Load HTLC attempts and collect attempt indices. - allAttemptIndices, err := batchLoadHtlcAttempts( - ctx, cfg, db, paymentIDs, batchData, - ) - if err != nil { - return nil, fmt.Errorf("failed to fetch HTLC attempts: %w", - err) - } - - if len(allAttemptIndices) == 0 { - // No attempts, return early. - return batchData, nil - } - - if includeHops { - // Load hops for all attempts and collect hop IDs. - hopIDs, err := batchLoadHopsForAttempts( - ctx, cfg, db, allAttemptIndices, batchData, - ) - if err != nil { - return nil, fmt.Errorf("failed to fetch hops "+ - "for attempts: %w", err) - } - - // Load hop-level custom records if there are any hops. - if len(hopIDs) > 0 { - err = batchLoadHopCustomRecords( - ctx, cfg, db, hopIDs, batchData, - ) - if err != nil { - return nil, fmt.Errorf("failed to fetch "+ - "hop custom records: %w", err) - } - } - - // Load route-level first hop custom records. - err = batchLoadRouteCustomRecords( - ctx, cfg, db, allAttemptIndices, batchData, - ) - if err != nil { - return nil, fmt.Errorf("failed to fetch route "+ - "custom records: %w", err) - } - } - - return batchData, nil -} - -// buildPaymentFromBatchData builds a complete MPPayment from a database payment -// and pre-loaded batch data. -func buildPaymentFromBatchData(dbPayment sqlc.PaymentAndIntent, - batchData *paymentsDetailsData, includeHops bool) ( - *MPPayment, error) { - - // The query will only return BOLT 11 payment intents or intents with - // no intent type set. - paymentIntent := dbPayment.GetPaymentIntent() - paymentRequest := paymentIntent.IntentPayload - - payment := dbPayment.GetPayment() - - // Get payment-level custom records from batch data. - customRecords := batchData.paymentCustomRecords[payment.ID] - - // Convert to the FirstHopCustomRecords map. - var firstHopCustomRecords lnwire.CustomRecords - if len(customRecords) > 0 { - firstHopCustomRecords = make(lnwire.CustomRecords) - for _, record := range customRecords { - firstHopCustomRecords[uint64(record.Key)] = record.Value - } - } - - // Convert database payment data to the PaymentCreationInfo struct. - info := dbPaymentToCreationInfo( - payment.PaymentIdentifier, payment.AmountMsat, - payment.CreatedAt, paymentRequest, firstHopCustomRecords, - ) - - // Get all HTLC attempts from batch data for a given payment. - dbAttempts := batchData.attempts[payment.ID] - - // Convert all attempts to HTLCAttempt structs using the pre-loaded - // batch data. - attempts := make([]HTLCAttempt, 0, len(dbAttempts)) - for _, dbAttempt := range dbAttempts { - attemptIndex := dbAttempt.AttemptIndex - // Convert the batch row type to the single row type. - attempt, err := dbAttemptToHTLCAttempt( - dbAttempt, batchData.hopsByAttempt[attemptIndex], - batchData.hopCustomRecords, - batchData.routeCustomRecords[attemptIndex], - includeHops, - ) - if err != nil { - return nil, fmt.Errorf("failed to convert attempt "+ - "%d: %w", attemptIndex, err) - } - attempts = append(attempts, *attempt) - } - - // Set the failure reason if present. - // - // TODO(ziggie): Rename it to Payment Memo in the database? - var failureReason *FailureReason - if payment.FailReason.Valid { - reason := FailureReason(payment.FailReason.Int32) - failureReason = &reason - } - - mpPayment := &MPPayment{ - SequenceNum: uint64(payment.ID), - Info: info, - HTLCs: attempts, - FailureReason: failureReason, - } - - // The status and state will be determined by calling - // SetState after construction. - if err := mpPayment.SetState(); err != nil { - return nil, fmt.Errorf("failed to set payment state: %w", err) - } - - return mpPayment, nil -} - -// QueryPayments queries and retrieves payments from the database with support -// for filtering, pagination, and efficient batch loading of related data. -// -// The function accepts a Query parameter that controls: -// - Pagination: IndexOffset specifies where to start (exclusive), and -// MaxPayments limits the number of results returned -// - Ordering: Reversed flag determines if results are returned in reverse -// chronological order -// - Filtering: CreationDateStart/End filter by creation time, and -// IncludeIncomplete controls whether non-succeeded payments are included -// - Metadata: CountTotal flag determines if the total payment count should -// be calculated -// -// The function optimizes performance by loading all related data (HTLCs, -// sequences, failure reasons, etc.) for multiple payments in a single batch -// query, rather than fetching each payment's data individually. -// -// Returns a Response containing: -// - Payments: the list of matching payments with complete data -// - FirstIndexOffset/LastIndexOffset: pagination cursors for the first and -// last payment in the result set -// - TotalCount: total number of payments in the database (if CountTotal was -// requested, otherwise 0) -// -// This is part of the DB interface. -func (s *SQLStore) QueryPayments(ctx context.Context, query Query) (Response, - error) { - - if query.MaxPayments == 0 { - return Response{}, fmt.Errorf("max payments must be non-zero") - } - - var ( - allPayments []*MPPayment - totalCount int64 - initialCursor int64 - ) - - extractCursor := func(row sqlc.FilterPaymentsRow) int64 { - return row.Payment.ID - } - - err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - // We first count all payments to determine the total count - // if requested. - if query.CountTotal { - totalPayments, err := db.CountPayments(ctx) - if err != nil { - return fmt.Errorf("failed to count "+ - "payments: %w", err) - } - totalCount = totalPayments - } - - // collectFunc extracts the payment ID from each payment row. - collectFunc := func(row sqlc.FilterPaymentsRow) (int64, error) { - return row.Payment.ID, nil - } - - // batchDataFunc loads all related data for a batch of payments. - batchDataFunc := func(ctx context.Context, paymentIDs []int64) ( - *paymentsDetailsData, error) { - - return batchLoadPaymentDetailsData( - ctx, s.cfg.QueryCfg, db, paymentIDs, - !query.OmitHops, - ) - } - - // processPayment processes each payment with the batch-loaded - // data. - processPayment := func(ctx context.Context, - dbPayment sqlc.FilterPaymentsRow, - batchData *paymentsDetailsData) error { - - // Build the payment from the pre-loaded batch data. - mpPayment, err := buildPaymentFromBatchData( - dbPayment, batchData, !query.OmitHops, - ) - if err != nil { - return fmt.Errorf("failed to fetch payment "+ - "with complete data: %w", err) - } - - // To keep compatibility with the old API, we only - // return non-succeeded payments if requested. - if mpPayment.Status != StatusSucceeded && - !query.IncludeIncomplete { - - return nil - } - - if uint64(len(allPayments)) >= query.MaxPayments { - return errMaxPaymentsReached - } - - allPayments = append(allPayments, mpPayment) - - return nil - } - - //nolint:ll - convertFilterPaymentsDescRows := func( - rows []sqlc.FilterPaymentsDescRow) []sqlc.FilterPaymentsRow { - - out := make([]sqlc.FilterPaymentsRow, len(rows)) - for i, row := range rows { - out[i] = sqlc.FilterPaymentsRow{ - Payment: row.Payment, - IntentType: row.IntentType, - IntentPayload: row.IntentPayload, - } - } - - return out - } - - queryFunc := func(ctx context.Context, lastID int64, - limit int32) ([]sqlc.FilterPaymentsRow, error) { - - // Default date bounds: epoch start and far - // future. These are always provided so the SQL - // query uses simple comparisons instead of - // COALESCE (which causes type mismatch on - // Postgres) or OR-based optional filters (which - // can prevent index usage). - createdAfter := time.Unix(0, 0).UTC() - if query.CreationDateStart != 0 { - createdAfter = time.Unix( - query.CreationDateStart, 0, - ).UTC() - } - - createdBefore := time.Date( - 9999, 12, 31, 23, 59, 59, 0, time.UTC, - ) - if query.CreationDateEnd != 0 { - createdBefore = time.Unix( - query.CreationDateEnd, 0, - ).UTC() - } - - filterParams := sqlc.FilterPaymentsParams{ - NumLimit: limit, - CreatedAfter: createdAfter, - CreatedBefore: createdBefore, - // For now there only BOLT 11 payment intents - // exist. - IntentType: sqldb.SQLInt16( - PaymentIntentTypeBolt11, - ), - } - - if query.Reversed { - filterParams.IndexOffsetLet = sqldb.SQLInt64( - lastID, - ) - } else { - filterParams.IndexOffsetGet = sqldb.SQLInt64( - lastID, - ) - } - - if query.Reversed { - rows, err := db.FilterPaymentsDesc( - ctx, sqlc.FilterPaymentsDescParams( - filterParams, - ), - ) - if err != nil { - return nil, err - } - - return convertFilterPaymentsDescRows(rows), nil - } - - return db.FilterPayments(ctx, filterParams) - } - - if query.Reversed { - if query.IndexOffset == 0 { - initialCursor = int64(math.MaxInt64) - } else { - initialCursor = int64(query.IndexOffset) - } - } else { - initialCursor = int64(query.IndexOffset) - } - - return sqldb.ExecuteCollectAndBatchWithSharedDataQuery( - ctx, s.cfg.QueryCfg, initialCursor, queryFunc, - extractCursor, collectFunc, batchDataFunc, - processPayment, - ) - }, func() { - allPayments = nil - }) - - // We make sure we don't return an error if we reached the maximum - // number of payments. Which is the pagination limit for the query - // itself. - if err != nil && !errors.Is(err, errMaxPaymentsReached) { - return Response{}, fmt.Errorf("failed to query payments: %w", - err) - } - - // Handle case where no payments were found - if len(allPayments) == 0 { - return Response{ - Payments: allPayments, - FirstIndexOffset: 0, - LastIndexOffset: 0, - TotalCount: uint64(totalCount), - }, nil - } - - // If the query was reversed, we need to reverse the payment list - // to match the kvstore behavior and return payments in forward order. - if query.Reversed { - for i, j := 0, len(allPayments)-1; i < j; i, j = i+1, j-1 { - allPayments[i], allPayments[j] = allPayments[j], - allPayments[i] - } - } - - return Response{ - Payments: allPayments, - FirstIndexOffset: allPayments[0].SequenceNum, - LastIndexOffset: allPayments[len(allPayments)-1].SequenceNum, - TotalCount: uint64(totalCount), - }, nil -} - -// fetchPaymentByHash fetches a payment by its hash from the database. It is a -// convenience wrapper around the FetchPayment method and checks for -// no rows error and returns ErrPaymentNotInitiated if no payment is found. -func fetchPaymentByHash(ctx context.Context, db SQLQueries, - paymentHash lntypes.Hash) (sqlc.FetchPaymentRow, error) { - - dbPayment, err := db.FetchPayment(ctx, paymentHash[:]) - if err != nil && !errors.Is(err, sql.ErrNoRows) { - return dbPayment, fmt.Errorf("failed to fetch payment: %w", err) - } - - if errors.Is(err, sql.ErrNoRows) { - return dbPayment, ErrPaymentNotInitiated - } - - return dbPayment, nil -} - -// FetchPayment retrieves a complete payment record from the database by its -// payment hash. The returned MPPayment includes all payment metadata such as -// creation info, payment status, current state, all HTLC attempts (both -// successful and failed), and the failure reason if the payment has been -// marked as failed. -// -// Returns ErrPaymentNotInitiated if no payment with the given hash exists. -// -// This is part of the DB interface. -func (s *SQLStore) FetchPayment(ctx context.Context, - paymentHash lntypes.Hash) (*MPPayment, error) { - - var mpPayment *MPPayment - - err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - dbPayment, err := fetchPaymentByHash(ctx, db, paymentHash) - if err != nil { - return err - } - - mpPayment, err = fetchPaymentWithCompleteData( - ctx, s.cfg.QueryCfg, db, dbPayment, - ) - if err != nil { - return fmt.Errorf("failed to fetch payment with "+ - "complete data: %w", err) - } - - return nil - }, sqldb.NoOpReset) - if err != nil { - return nil, err - } - - return mpPayment, nil -} - -// FetchInFlightPayments retrieves all payments that have HTLC attempts -// currently in flight (not yet settled or failed). These are payments with at -// least one HTLC attempt that has been registered but has no resolution record. -// -// The SQLStore implementation provides a significant performance improvement -// over the KVStore implementation by using targeted SQL queries instead of -// scanning all payments. -// -// This method is part of the PaymentReader interface, which is embedded in the -// DB interface. It's typically called during node startup to resume monitoring -// of pending payments and ensure HTLCs are properly tracked. -// -// TODO(ziggie): Consider changing the interface to use a callback or iterator -// pattern instead of returning all payments at once. This would allow -// processing payments one at a time without holding them all in memory -// simultaneously: -// - Callback: func FetchInFlightPayments(ctx, func(*MPPayment) error) error -// - Iterator: func FetchInFlightPayments(ctx) (PaymentIterator, error) -// -// While inflight payments are typically a small subset, this would improve -// memory efficiency for nodes with unusually high numbers of concurrent -// payments and would better leverage the existing pagination infrastructure. -func (s *SQLStore) FetchInFlightPayments(ctx context.Context) ([]*MPPayment, - error) { - - var mpPayments []*MPPayment - - err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - extractCursor := func( - row sqlc.FetchNonTerminalPaymentsRow) int64 { - - return row.ID - } - - collectFunc := func(row sqlc.FetchNonTerminalPaymentsRow) ( - int64, error) { - - return row.ID, nil - } - - batchDataFunc := func(ctx context.Context, - paymentIDs []int64) (*paymentsDetailsData, error) { - - return batchLoadPaymentDetailsData( - ctx, s.cfg.QueryCfg, db, paymentIDs, true, - ) - } - - processPayment := func(ctx context.Context, - row sqlc.FetchNonTerminalPaymentsRow, - batchData *paymentsDetailsData) error { - - payment, err := buildPaymentFromBatchData( - row, batchData, true, - ) - if err != nil { - return fmt.Errorf("failed to build payment: %w", - err) - } - - mpPayments = append(mpPayments, payment) - - return nil - } - - queryFunc := func(ctx context.Context, lastPaymentID int64, - limit int32) ([]sqlc.FetchNonTerminalPaymentsRow, - error) { - - return db.FetchNonTerminalPayments(ctx, - sqlc.FetchNonTerminalPaymentsParams{ - ID: lastPaymentID, - Limit: limit, - }, - ) - } - - err := sqldb.ExecuteCollectAndBatchWithSharedDataQuery( - ctx, s.cfg.QueryCfg, int64(0), queryFunc, - extractCursor, collectFunc, batchDataFunc, - processPayment, - ) - if err != nil { - return err - } - - return nil - }, func() { - mpPayments = nil - }) - if err != nil { - return nil, fmt.Errorf("failed to fetch inflight "+ - "payments: %w", err) - } - - return mpPayments, nil -} - -// DeleteFailedAttempts removes all failed HTLC attempts from the database for -// the specified payment, while preserving the payment record itself and any -// successful or in-flight attempts. -// -// The method performs the following validations before deletion: -// - StatusInitiated: Can delete failed attempts -// - StatusInFlight: Cannot delete, returns ErrPaymentInFlight (active HTLCs -// still on the network) -// - StatusSucceeded: Can delete failed attempts (payment completed) -// - StatusFailed: Can delete failed attempts (payment permanently failed) -// -// This method is idempotent - calling it multiple times on the same payment -// has no adverse effects. -// -// This method is part of the PaymentControl interface, which is embedded in -// the PaymentWriter interface and ultimately the DB interface. It represents -// the final step (step 5) in the payment lifecycle control flow and should be -// called after a payment reaches a terminal state (succeeded or permanently -// failed) to clean up historical failed attempts. -func (s *SQLStore) DeleteFailedAttempts(ctx context.Context, - paymentHash lntypes.Hash) error { - - err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { - dbPayment, err := fetchPaymentByHash(ctx, db, paymentHash) - if err != nil { - return err - } - - paymentStatus, err := computePaymentStatusFromDB( - ctx, s.cfg.QueryCfg, db, dbPayment, - ) - if err != nil { - return fmt.Errorf("failed to compute payment "+ - "status: %w", err) - } - - if err := paymentStatus.removable(); err != nil { - return fmt.Errorf("cannot delete failed "+ - "attempts for payment %v: %w", paymentHash, err) - } - - // Then we delete the failed attempts for this payment. - return db.DeleteFailedAttempts(ctx, dbPayment.GetPayment().ID) - }, sqldb.NoOpReset) - if err != nil { - return fmt.Errorf("failed to delete failed attempts for "+ - "payment %v: %w", paymentHash, err) - } - - return nil -} - -// computePaymentStatusFromDB computes the payment status by fetching minimal -// data from the database. This is a lightweight query optimized for SQL that -// doesn't load route data, making it significantly more efficient than -// FetchPayment when only the status is needed. -func computePaymentStatusFromDB(ctx context.Context, cfg *sqldb.QueryConfig, - db SQLQueries, dbPayment sqlc.PaymentAndIntent) (PaymentStatus, error) { - - payment := dbPayment.GetPayment() - - // Load the resolution types for the payment. - resolutionTypes, err := loadPaymentResolutions( - ctx, cfg, db, payment.ID, - ) - if err != nil { - return 0, fmt.Errorf("failed to load payment resolutions: %w", - err) - } - - // Use the lightweight status computation. - status, err := computePaymentStatusFromResolutions( - resolutionTypes, payment.FailReason, - ) - if err != nil { - return 0, fmt.Errorf("failed to compute payment status: %w", - err) - } - - return status, nil -} - -// DeletePayment removes a payment or its failed HTLC attempts from the -// database based on the failedAttemptsOnly flag. -// -// If failedAttemptsOnly is true, this method deletes only the failed HTLC -// attempts for the payment while preserving the payment record itself and any -// successful or in-flight attempts. This is useful for cleaning up historical -// failed attempts after a payment reaches a terminal state. -// -// If failedAttemptsOnly is false, this method deletes the entire payment -// record including all payment metadata, payment creation info, all HTLC -// attempts (both failed and successful), and associated data such as payment -// intents and custom records. -// -// Before deletion, this method validates the payment status to ensure it's -// safe to delete: -// - StatusInitiated: Can be deleted (no HTLCs sent yet) -// - StatusInFlight: Cannot be deleted, returns ErrPaymentInFlight (active -// HTLCs on the network) -// - StatusSucceeded: Can be deleted (payment completed successfully) -// - StatusFailed: Can be deleted (payment has failed permanently) -// -// Returns an error if the payment has in-flight HTLCs or if the payment -// doesn't exist. -// -// This method is part of the PaymentWriter interface, which is embedded in -// the DB interface. -func (s *SQLStore) DeletePayment(ctx context.Context, paymentHash lntypes.Hash, - failedHtlcsOnly bool) error { - - err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { - dbPayment, err := fetchPaymentByHash(ctx, db, paymentHash) - if err != nil { - return err - } - - paymentStatus, err := computePaymentStatusFromDB( - ctx, s.cfg.QueryCfg, db, dbPayment, - ) - if err != nil { - return fmt.Errorf("failed to compute payment "+ - "status: %w", err) - } - - if err := paymentStatus.removable(); err != nil { - return fmt.Errorf("payment %v cannot be deleted: %w", - paymentHash, err) - } - - // If we are only deleting failed HTLCs, we delete them. - if failedHtlcsOnly { - return db.DeleteFailedAttempts( - ctx, dbPayment.GetPayment().ID, - ) - } - - // In case we are not deleting failed HTLCs, we delete the - // payment which will cascade delete all related data. - return db.DeletePayment(ctx, dbPayment.GetPayment().ID) - }, sqldb.NoOpReset) - if err != nil { - return fmt.Errorf("failed to delete failed attempts for "+ - "payment %v: %w", paymentHash, err) - } - - return nil -} - -// InitPayment creates a new payment record in the database with the given -// payment hash and creation info. -// -// Before creating the payment, this method checks if a payment with the same -// hash already exists and validates whether initialization is allowed based on -// the existing payment's status: -// - StatusInitiated: Returns ErrPaymentExists (payment already created, -// HTLCs may be in flight) -// - StatusInFlight: Returns ErrPaymentInFlight (payment currently being -// attempted) -// - StatusSucceeded: Returns ErrAlreadyPaid (payment already succeeded) -// - StatusFailed: Allows retry by deleting the old payment record and -// creating a new one -// -// If no existing payment is found, a new payment record is created with -// StatusInitiated and stored with all associated metadata. -// -// This method is part of the PaymentControl interface, which is embedded in -// the PaymentWriter interface and ultimately the DB interface, representing -// the first step in the payment lifecycle control flow. -func (s *SQLStore) InitPayment(ctx context.Context, paymentHash lntypes.Hash, - paymentCreationInfo *PaymentCreationInfo) error { - - // Create the payment in the database. - err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { - existingPayment, err := db.FetchPayment(ctx, paymentHash[:]) - switch { - // A payment with this hash already exists. We need to check its - // status to see if we can re-initialize. - case err == nil: - paymentStatus, err := computePaymentStatusFromDB( - ctx, s.cfg.QueryCfg, db, existingPayment, - ) - if err != nil { - return fmt.Errorf("failed to compute payment "+ - "status: %w", err) - } - - // Check if the payment is initializable otherwise - // we'll return early. - if err := paymentStatus.initializable(); err != nil { - return fmt.Errorf("payment is not "+ - "initializable: %w", err) - } - - // If the initializable check above passes, then the - // existing payment has failed. So we delete it and - // all of its previous artifacts. We rely on - // cascading deletes to clean up the rest. - err = db.DeletePayment(ctx, existingPayment.Payment.ID) - if err != nil { - return fmt.Errorf("failed to delete "+ - "payment: %w", err) - } - - // An unexpected error occurred while fetching the payment. - case !errors.Is(err, sql.ErrNoRows): - // Some other error occurred - return fmt.Errorf("failed to check existing "+ - "payment: %w", err) - - // The payment does not yet exist, so we can proceed. - default: - } - - // Insert the payment first to get its ID. - paymentID, err := db.InsertPayment( - ctx, sqlc.InsertPaymentParams{ - AmountMsat: int64( - paymentCreationInfo.Value, - ), - CreatedAt: paymentCreationInfo. - CreationTime.UTC(), - PaymentIdentifier: paymentHash[:], - }, - ) - if err != nil { - return fmt.Errorf("failed to insert payment: %w", err) - } - - // If there's a payment request, insert the payment intent. - if len(paymentCreationInfo.PaymentRequest) > 0 { - _, err = db.InsertPaymentIntent( - ctx, sqlc.InsertPaymentIntentParams{ - PaymentID: paymentID, - IntentType: int16( - PaymentIntentTypeBolt11, - ), - IntentPayload: paymentCreationInfo. - PaymentRequest, - }, - ) - if err != nil { - return fmt.Errorf("failed to insert "+ - "payment intent: %w", err) - } - } - - firstHopCustomRecords := paymentCreationInfo. - FirstHopCustomRecords - - for key, value := range firstHopCustomRecords { - err = db.InsertPaymentFirstHopCustomRecord( - ctx, - sqlc.InsertPaymentFirstHopCustomRecordParams{ - PaymentID: paymentID, - Key: int64(key), - Value: value, - }, - ) - if err != nil { - return fmt.Errorf("failed to insert "+ - "payment first hop custom "+ - "record: %w", err) - } - } - - return nil - }, sqldb.NoOpReset) - if err != nil { - return fmt.Errorf("failed to initialize payment: %w", err) - } - - return nil -} - -// insertRouteHops inserts all route hop data for a given set of hops. -func (s *SQLStore) insertRouteHops(ctx context.Context, db SQLQueries, - hops []*route.Hop, attemptID uint64) error { - - for i, hop := range hops { - // Insert the basic route hop data and get the generated ID. - hopID, err := db.InsertRouteHop(ctx, sqlc.InsertRouteHopParams{ - HtlcAttemptIndex: int64(attemptID), - HopIndex: int32(i), - PubKey: hop.PubKeyBytes[:], - Scid: strconv.FormatUint( - hop.ChannelID, 10, - ), - OutgoingTimeLock: int32(hop.OutgoingTimeLock), - AmtToForward: int64(hop.AmtToForward), - MetaData: hop.Metadata, - }) - if err != nil { - return fmt.Errorf("failed to insert route hop: %w", err) - } - - // Insert the per-hop custom records. - if len(hop.CustomRecords) > 0 { - for key, value := range hop.CustomRecords { - err = db.InsertPaymentHopCustomRecord( - ctx, - sqlc.InsertPaymentHopCustomRecordParams{ - HopID: hopID, - Key: int64(key), - Value: value, - }) - if err != nil { - return fmt.Errorf("failed to insert "+ - "payment hop custom record: %w", - err) - } - } - } - - // Insert MPP data if present. - if hop.MPP != nil { - paymentAddr := hop.MPP.PaymentAddr() - err = db.InsertRouteHopMpp( - ctx, sqlc.InsertRouteHopMppParams{ - HopID: hopID, - PaymentAddr: paymentAddr[:], - TotalMsat: int64(hop.MPP.TotalMsat()), - }) - if err != nil { - return fmt.Errorf("failed to insert "+ - "route hop MPP: %w", err) - } - } - - // Insert AMP data if present. - if hop.AMP != nil { - rootShare := hop.AMP.RootShare() - setID := hop.AMP.SetID() - err = db.InsertRouteHopAmp( - ctx, sqlc.InsertRouteHopAmpParams{ - HopID: hopID, - RootShare: rootShare[:], - SetID: setID[:], - ChildIndex: int32(hop.AMP.ChildIndex()), - }) - if err != nil { - return fmt.Errorf("failed to insert "+ - "route hop AMP: %w", err) - } - } - - // Insert blinded route data if present. Every hop in the - // blinded path must have an encrypted data record. If the - // encrypted data is not present, we skip the insertion. - if hop.EncryptedData == nil { - continue - } - - // The introduction point has a blinding point set. - var blindingPointBytes []byte - if hop.BlindingPoint != nil { - blindingPointBytes = hop.BlindingPoint. - SerializeCompressed() - } - - // The total amount is only set for the final hop in a - // blinded path. - totalAmtMsat := sql.NullInt64{} - if i == len(hops)-1 { - totalAmtMsat = sql.NullInt64{ - Int64: int64(hop.TotalAmtMsat), - Valid: true, - } - } - - err = db.InsertRouteHopBlinded(ctx, - sqlc.InsertRouteHopBlindedParams{ - HopID: hopID, - EncryptedData: hop.EncryptedData, - BlindingPoint: blindingPointBytes, - BlindedPathTotalAmt: totalAmtMsat, - }, - ) - if err != nil { - return fmt.Errorf("failed to insert "+ - "route hop blinded: %w", err) - } - } - - return nil -} - -// RegisterAttempt atomically records a new HTLC attempt for the specified -// payment. The attempt includes the attempt ID, session key, route information -// (hops, timelocks, amounts), and optional data such as MPP/AMP parameters, -// blinded route data, and custom records. -// -// Returns the updated MPPayment with the new attempt appended to the HTLCs -// slice, and the payment state recalculated. Returns an error if the payment -// doesn't exist or validation fails. -// -// This method is part of the PaymentControl interface, which is embedded in -// the PaymentWriter interface and ultimately the DB interface. It represents -// step 2 in the payment lifecycle control flow, called after InitPayment and -// potentially multiple times for multi-path payments. -func (s *SQLStore) RegisterAttempt(ctx context.Context, - paymentHash lntypes.Hash, attempt *HTLCAttemptInfo) (*MPPayment, - error) { - - var mpPayment *MPPayment - - err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { - // Make sure the payment exists. - dbPayment, err := db.FetchPayment(ctx, paymentHash[:]) - if err != nil { - return err - } - - // We fetch the complete payment to determine if the payment is - // registrable. - // - // TODO(ziggie): We could improve the query here since only - // the last hop data is needed here not the complete payment - // data. - mpPayment, err = fetchPaymentWithCompleteData( - ctx, s.cfg.QueryCfg, db, dbPayment, - ) - if err != nil { - return fmt.Errorf("failed to fetch payment with "+ - "complete data: %w", err) - } - - if err := mpPayment.Registrable(); err != nil { - return fmt.Errorf("htlc attempt not registrable: %w", - err) - } - - // Verify the attempt is compatible with the existing payment. - if err := verifyAttempt(mpPayment, attempt); err != nil { - return fmt.Errorf("failed to verify attempt: %w", err) - } - - // Register the plain HTLC attempt next. - sessionKey := attempt.SessionKey() - sessionKeyBytes := sessionKey.Serialize() - attemptHash := paymentHash[:] - if attempt.Hash != nil { - attemptHash = attempt.Hash[:] - } else { - log.Errorf("RegisterAttempt: attempt %d has nil hash, "+ - "falling back to payment identifier %x", - attempt.AttemptID, paymentHash) - } - - _, err = db.InsertHtlcAttempt(ctx, sqlc.InsertHtlcAttemptParams{ - PaymentID: dbPayment.Payment.ID, - AttemptIndex: int64(attempt.AttemptID), - SessionKey: sessionKeyBytes, - AttemptTime: attempt.AttemptTime, - PaymentHash: attemptHash, - FirstHopAmountMsat: int64( - attempt.Route.FirstHopAmount.Val.Int(), - ), - RouteTotalTimeLock: int32(attempt.Route.TotalTimeLock), - RouteTotalAmount: int64(attempt.Route.TotalAmount), - RouteSourceKey: attempt.Route.SourcePubKey[:], - }) - if err != nil { - return fmt.Errorf("failed to insert HTLC "+ - "attempt: %w", err) - } - - // Insert the route level first hop custom records. - attemptFirstHopCustomRecords := attempt.Route. - FirstHopWireCustomRecords - - for key, value := range attemptFirstHopCustomRecords { - //nolint:ll - err = db.InsertPaymentAttemptFirstHopCustomRecord( - ctx, - sqlc.InsertPaymentAttemptFirstHopCustomRecordParams{ - HtlcAttemptIndex: int64(attempt.AttemptID), - Key: int64(key), - Value: value, - }, - ) - if err != nil { - return fmt.Errorf("failed to insert "+ - "payment attempt first hop custom "+ - "record: %w", err) - } - } - - // Insert the route hops. - err = s.insertRouteHops( - ctx, db, attempt.Route.Hops, attempt.AttemptID, - ) - if err != nil { - return fmt.Errorf("failed to insert route hops: %w", - err) - } - - // We fetch the HTLC attempts again to recalculate the payment - // state after the attempt is registered. This also makes sure - // we have the right data in case multiple attempts are - // registered concurrently. - // - // NOTE: While the caller is responsible for serializing calls - // to RegisterAttempt per payment hash (see PaymentControl - // interface), we still refetch here to guarantee we return - // consistent, up-to-date data that reflects all changes made - // within this transaction. - mpPayment, err = fetchPaymentWithCompleteData( - ctx, s.cfg.QueryCfg, db, dbPayment, - ) - if err != nil { - return fmt.Errorf("failed to fetch payment with "+ - "complete data: %w", err) - } - - return nil - }, func() { - mpPayment = nil - }) - if err != nil { - return nil, fmt.Errorf("failed to register attempt: %w", err) - } - - return mpPayment, nil -} - -// SettleAttempt marks the specified HTLC attempt as successfully settled, -// recording the payment preimage and settlement time. The preimage serves as -// cryptographic proof of payment and is atomically saved to the database. -// -// This method is part of the PaymentControl interface, which is embedded in -// the PaymentWriter interface and ultimately the DB interface. It represents -// step 3a in the payment lifecycle control flow (step 3b is FailAttempt), -// called after RegisterAttempt when an HTLC successfully completes. -func (s *SQLStore) SettleAttempt(ctx context.Context, paymentHash lntypes.Hash, - attemptID uint64, settleInfo *HTLCSettleInfo) (*MPPayment, error) { - - var mpPayment *MPPayment - - err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { - dbPayment, err := fetchPaymentByHash(ctx, db, paymentHash) - if err != nil { - return err - } - - paymentStatus, err := computePaymentStatusFromDB( - ctx, s.cfg.QueryCfg, db, dbPayment, - ) - if err != nil { - return fmt.Errorf("failed to compute payment "+ - "status: %w", err) - } - - if err := paymentStatus.updatable(); err != nil { - return fmt.Errorf("payment is not updatable: %w", err) - } - - err = db.SettleAttempt(ctx, sqlc.SettleAttemptParams{ - AttemptIndex: int64(attemptID), - ResolutionTime: settleInfo.SettleTime.UTC(), - ResolutionType: int32(HTLCAttemptResolutionSettled), - SettlePreimage: settleInfo.Preimage[:], - }) - if err != nil { - return fmt.Errorf("failed to settle attempt: %w", err) - } - - // Fetch the complete payment after we settled the attempt. - mpPayment, err = fetchPaymentWithCompleteData( - ctx, s.cfg.QueryCfg, db, dbPayment, - ) - if err != nil { - return fmt.Errorf("failed to fetch payment with "+ - "complete data: %w", err) - } - - return nil - }, func() { - mpPayment = nil - }) - if err != nil { - return nil, fmt.Errorf("failed to settle attempt: %w", err) - } - - return mpPayment, nil -} - -// FailAttempt marks the specified HTLC attempt as failed, recording the -// failure reason, failure time, optional failure message, and the index of the -// node in the route that generated the failure. This information is atomically -// saved to the database for debugging and route optimization purposes. -// -// For single-path payments, failing the only attempt may lead to the payment -// being retried or ultimately failed via the Fail method. For multi-shard -// (MPP/AMP) payments, individual shard failures don't necessarily fail the -// entire payment; additional attempts can be registered until sufficient shards -// succeed or the payment is permanently failed. -// -// Returns the updated MPPayment with the attempt marked as failed and the -// payment state recalculated. The payment status remains StatusInFlight if -// other attempts are still in flight, or may transition based on the overall -// payment state. -// -// This method is part of the PaymentControl interface, which is embedded in -// the PaymentWriter interface and ultimately the DB interface. It represents -// step 3b in the payment lifecycle control flow (step 3a is SettleAttempt), -// called after RegisterAttempt when an HTLC fails. -func (s *SQLStore) FailAttempt(ctx context.Context, paymentHash lntypes.Hash, - attemptID uint64, failInfo *HTLCFailInfo) (*MPPayment, error) { - - var mpPayment *MPPayment - - err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { - // Make sure the payment exists. - dbPayment, err := fetchPaymentByHash(ctx, db, paymentHash) - if err != nil { - return err - } - - paymentStatus, err := computePaymentStatusFromDB( - ctx, s.cfg.QueryCfg, db, dbPayment, - ) - if err != nil { - return fmt.Errorf("failed to compute payment "+ - "status: %w", err) - } - - // We check if the payment is updatable before failing the - // attempt. - if err := paymentStatus.updatable(); err != nil { - return fmt.Errorf("payment is not updatable: %w", err) - } - - var failureMsg bytes.Buffer - if failInfo.Message != nil { - err := lnwire.EncodeFailureMessage( - &failureMsg, failInfo.Message, 0, - ) - if err != nil { - return fmt.Errorf("failed to encode "+ - "failure message: %w", err) - } - } - - err = db.FailAttempt(ctx, sqlc.FailAttemptParams{ - AttemptIndex: int64(attemptID), - ResolutionTime: failInfo.FailTime.UTC(), - ResolutionType: int32(HTLCAttemptResolutionFailed), - FailureSourceIndex: sqldb.SQLInt32( - failInfo.FailureSourceIndex, - ), - HtlcFailReason: sqldb.SQLInt32(failInfo.Reason), - FailureMsg: failureMsg.Bytes(), - }) - if err != nil { - return fmt.Errorf("failed to fail attempt: %w", err) - } - - mpPayment, err = fetchPaymentWithCompleteData( - ctx, s.cfg.QueryCfg, db, dbPayment, - ) - if err != nil { - return fmt.Errorf("failed to fetch payment with "+ - "complete data: %w", err) - } - - return nil - }, func() { - mpPayment = nil - }) - if err != nil { - return nil, fmt.Errorf("failed to fail attempt: %w", err) - } - - return mpPayment, nil -} - -// Fail records the ultimate reason why a payment failed. This method stores -// the failure reason for record keeping but does not enforce that all HTLC -// attempts are resolved - HTLCs may still be in flight when this is called. -// -// The payment's actual status transition to StatusFailed is determined by the -// payment state calculation, which considers both the recorded failure reason -// and the current state of all HTLC attempts. The status will transition to -// StatusFailed once all HTLCs are resolved and/or a failure reason is recorded. -// -// NOTE: According to the interface contract, this should only be called when -// all active attempts are already failed. However, the implementation allows -// concurrent calls and does not validate this precondition, enabling the last -// failing attempt to record the failure reason without synchronization. -// -// This method is part of the PaymentControl interface, which is embedded in -// the PaymentWriter interface and ultimately the DB interface. It represents -// step 4 in the payment lifecycle control flow. -func (s *SQLStore) Fail(ctx context.Context, paymentHash lntypes.Hash, - reason FailureReason) (*MPPayment, error) { - - var mpPayment *MPPayment - - err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { - result, err := db.FailPayment(ctx, sqlc.FailPaymentParams{ - PaymentIdentifier: paymentHash[:], - FailReason: sqldb.SQLInt32(reason), - }) - if err != nil { - return err - } - - rowsAffected, err := result.RowsAffected() - if err != nil { - return err - } - if rowsAffected == 0 { - return ErrPaymentNotInitiated - } - - payment, err := db.FetchPayment(ctx, paymentHash[:]) - if err != nil { - return fmt.Errorf("failed to fetch payment: %w", err) - } - mpPayment, err = fetchPaymentWithCompleteData( - ctx, s.cfg.QueryCfg, db, payment, - ) - if err != nil { - return fmt.Errorf("failed to fetch payment with "+ - "complete data: %w", err) - } - - return nil - }, func() { - mpPayment = nil - }) - if err != nil { - return nil, fmt.Errorf("failed to fail payment: %w", err) - } - - return mpPayment, nil -} - -// DeletePayments performs a batch deletion of payments or their failed HTLC -// attempts from the database based on the specified flags. This is a bulk -// operation that iterates through all payments and selectively deletes based -// on the criteria. -// The behavior is controlled by two flags: -// -// If failedAttemptsOnly is true, only failed HTLC attempts are deleted while -// preserving the payment records and any successful or in-flight attempts. -// The return value is always 0 when deleting attempts only. -// -// If failedAttemptsOnly is false, entire payment records are deleted including -// all associated data (HTLCs, metadata, intents). The return value is the -// number of payments deleted. -// -// The failedOnly flag further filters which payments are processed: -// - failedOnly=true, failedAttemptsOnly=true: Delete failed attempts for -// StatusFailed payments only -// - failedOnly=false, failedAttemptsOnly=true: Delete failed attempts for -// all removable payments -// - failedOnly=true, failedAttemptsOnly=false: Delete entire payment records -// for StatusFailed payments only -// - failedOnly=false, failedAttemptsOnly=false: Delete all removable payment -// records (StatusInitiated, StatusSucceeded, StatusFailed) -// -// Safety checks applied to all operations: -// - Payments with StatusInFlight are always skipped (cannot be safely deleted -// while HTLCs are on the network) -// - The payment status must pass the removable() check -// -// Returns the number of complete payments deleted (0 if only deleting failed -// attempts). This is useful for cleanup operations, administrative maintenance, -// or freeing up database storage. -// -// This method is part of the PaymentWriter interface, which is embedded in -// the DB interface. -// -// TODO(ziggie): batch and use iterator instead, moreover we dont need to fetch -// the complete payment data for each payment, we can just fetch the payment ID -// and the resolution types to decide if the payment is removable. -func (s *SQLStore) DeletePayments(ctx context.Context, failedOnly, - failedHtlcsOnly bool) (int, error) { - - var numPayments int - - extractCursor := func(row sqlc.FilterPaymentsRow) int64 { - return row.Payment.ID - } - - err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { - // collectFunc extracts the payment ID from each payment row. - collectFunc := func(row sqlc.FilterPaymentsRow) (int64, error) { - return row.Payment.ID, nil - } - - // batchDataFunc loads only HTLC resolution types for a batch - // of payments, which is sufficient to determine payment status. - batchDataFunc := func(ctx context.Context, paymentIDs []int64) ( - *paymentStatusData, error) { - - return batchLoadPaymentResolutions( - ctx, s.cfg.QueryCfg, db, paymentIDs, - ) - } - - // processPayment processes each payment with the lightweight - // batch-loaded resolution data. - processPayment := func(ctx context.Context, - dbPayment sqlc.FilterPaymentsRow, - batchData *paymentStatusData) error { - - payment := dbPayment.Payment - - // Compute the payment status from resolution types and - // failure reason without building the complete payment. - resolutionTypes := batchData.resolutionTypes[payment.ID] - status, err := computePaymentStatusFromResolutions( - resolutionTypes, payment.FailReason, - ) - if err != nil { - return fmt.Errorf("failed to compute payment "+ - "status: %w", err) - } - - // Payments which are not final yet cannot be deleted. - // we skip them. - if err := status.removable(); err != nil { - return nil - } - - // If we are only deleting failed payments, we skip - // if the payment is not failed. - if failedOnly && status != StatusFailed { - return nil - } - - // If we are only deleting failed HTLCs, we delete them - // and return early. - if failedHtlcsOnly { - return db.DeleteFailedAttempts( - ctx, payment.ID, - ) - } - - // Otherwise we delete the payment. - err = db.DeletePayment(ctx, payment.ID) - if err != nil { - return fmt.Errorf("failed to delete "+ - "payment: %w", err) - } - - numPayments++ - - return nil - } - - queryFunc := func(ctx context.Context, lastID int64, - limit int32) ([]sqlc.FilterPaymentsRow, error) { - - filterParams := sqlc.FilterPaymentsParams{ - NumLimit: limit, - CreatedAfter: time.Unix(0, 0).UTC(), - CreatedBefore: time.Date( - 9999, 12, 31, 23, 59, 59, - 0, time.UTC, - ), - IndexOffsetGet: sqldb.SQLInt64( - lastID, - ), - } - - return db.FilterPayments(ctx, filterParams) - } - - return sqldb.ExecuteCollectAndBatchWithSharedDataQuery( - ctx, s.cfg.QueryCfg, int64(-1), queryFunc, - extractCursor, collectFunc, batchDataFunc, - processPayment, - ) - }, func() { - numPayments = 0 - }) - if err != nil { - return 0, fmt.Errorf("failed to delete payments "+ - "(failedOnly: %v, failedHtlcsOnly: %v): %w", - failedOnly, failedHtlcsOnly, err) - } - - return numPayments, nil -} diff --git a/payments/db/sql_store_test.go b/payments/db/sql_store_test.go deleted file mode 100644 index 0f4f310f3..000000000 --- a/payments/db/sql_store_test.go +++ /dev/null @@ -1,229 +0,0 @@ -//go:build test_db_sqlite || test_db_postgres - -package paymentsdb - -import ( - "database/sql" - "testing" - - "github.com/stretchr/testify/require" -) - -// TestComputePaymentStatus tests the SQL to domain type conversion logic in -// computePaymentStatusFromResolutions. This is a pure unit test with no -// database interaction. However the function is only used in the SQL store and -// used sql data types so we test it in a sql specific file. -func TestComputePaymentStatus(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - resolutionTypes []sql.NullInt32 - failReason sql.NullInt32 - expectedStatus PaymentStatus - expectError bool - }{ - { - name: "all NULL resolutions means in-flight", - resolutionTypes: []sql.NullInt32{ - {Valid: false}, // NULL = in-flight - {Valid: false}, - }, - failReason: sql.NullInt32{Valid: false}, - expectedStatus: StatusInFlight, - }, - { - name: "settled resolution without fail reason", - resolutionTypes: []sql.NullInt32{{ - Int32: int32(HTLCAttemptResolutionSettled), - Valid: true, - }}, - failReason: sql.NullInt32{Valid: false}, - expectedStatus: StatusSucceeded, - }, - { - name: "failed resolution without fail reason", - resolutionTypes: []sql.NullInt32{{ - Int32: int32(HTLCAttemptResolutionFailed), - Valid: true, - }}, - failReason: sql.NullInt32{Valid: false}, - expectedStatus: StatusInFlight, - }, - { - name: "failed resolution with fail reason", - resolutionTypes: []sql.NullInt32{{ - Int32: int32(HTLCAttemptResolutionFailed), - Valid: true, - }}, - failReason: sql.NullInt32{ - Int32: int32(FailureReasonNoRoute), - Valid: true, - }, - expectedStatus: StatusFailed, - }, - { - name: "mixed: in-flight and settled", - resolutionTypes: []sql.NullInt32{ - {Valid: false}, // in-flight - { - Int32: int32( - HTLCAttemptResolutionSettled, - ), - Valid: true, - }, - }, - failReason: sql.NullInt32{Valid: false}, - expectedStatus: StatusInFlight, - }, - { - name: "mixed: in-flight and failed", - resolutionTypes: []sql.NullInt32{ - {Valid: false}, // in-flight - { - Int32: int32( - HTLCAttemptResolutionFailed, - ), - Valid: true, - }, - }, - failReason: sql.NullInt32{Valid: false}, - expectedStatus: StatusInFlight, - }, - { - name: "mixed: settled and failed", - resolutionTypes: []sql.NullInt32{ - { - Int32: int32( - HTLCAttemptResolutionSettled, - ), - Valid: true, - }, - { - Int32: int32( - HTLCAttemptResolutionFailed, - ), - Valid: true, - }, - }, - failReason: sql.NullInt32{Valid: false}, - expectedStatus: StatusSucceeded, - }, - { - name: "no resolutions, no fail reason, " + - "means initiated", - resolutionTypes: []sql.NullInt32{}, - failReason: sql.NullInt32{Valid: false}, - expectedStatus: StatusInitiated, - }, - { - name: "no resolutions with fail reason, " + - "means failed", - resolutionTypes: []sql.NullInt32{}, - failReason: sql.NullInt32{ - Int32: int32(FailureReasonNoRoute), - Valid: true, - }, - expectedStatus: StatusFailed, - }, - { - name: "unknown resolution type returns error", - resolutionTypes: []sql.NullInt32{ - {Int32: 999, Valid: true}, // invalid type - }, - failReason: sql.NullInt32{Valid: false}, - expectError: true, - }, - { - name: "all three states: in-flight, settled, failed", - resolutionTypes: []sql.NullInt32{ - { - Valid: false, // in-flight - }, - { - Int32: int32( - HTLCAttemptResolutionSettled, - ), - Valid: true, - }, - { - Int32: int32( - HTLCAttemptResolutionFailed, - ), - Valid: true, - }, - }, - failReason: sql.NullInt32{ - Int32: int32(FailureReasonTimeout), - Valid: true, - }, - expectedStatus: StatusInFlight, - }, - { - name: "multiple settled HTLCs", - resolutionTypes: []sql.NullInt32{ - { - Int32: int32( - HTLCAttemptResolutionSettled, - ), - Valid: true, - }, - { - Int32: int32( - HTLCAttemptResolutionSettled, - ), - Valid: true, - }, - { - Int32: int32( - HTLCAttemptResolutionSettled, - ), - Valid: true, - }, - }, - failReason: sql.NullInt32{Valid: false}, - expectedStatus: StatusSucceeded, - }, - { - name: "multiple failed HTLCs with fail reason", - resolutionTypes: []sql.NullInt32{ - { - Int32: int32( - HTLCAttemptResolutionFailed, - ), - Valid: true, - }, - { - Int32: int32( - HTLCAttemptResolutionFailed, - ), - Valid: true, - }, - }, - failReason: sql.NullInt32{ - Int32: int32(FailureReasonNoRoute), - Valid: true, - }, - expectedStatus: StatusFailed, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - status, err := computePaymentStatusFromResolutions( - tc.resolutionTypes, tc.failReason, - ) - - if tc.expectError { - require.Error(t, err) - return - } - - require.NoError(t, err) - require.Equal(t, tc.expectedStatus, status, - "got %s, want %s", status, tc.expectedStatus) - }) - } -} diff --git a/payments/db/test_harness.go b/payments/db/test_harness.go deleted file mode 100644 index 11f88c3f8..000000000 --- a/payments/db/test_harness.go +++ /dev/null @@ -1,26 +0,0 @@ -package paymentsdb - -import ( - "testing" - - "github.com/lightningnetwork/lnd/lntypes" -) - -// TestHarness provides implementation-specific test utilities for the payments -// database. Different database backends (KV, SQL) have different internal -// structures and indexing mechanisms, so this interface allows tests to verify -// implementation-specific behavior without coupling the test logic to a -// particular backend. -type TestHarness interface { - // AssertPaymentIndex checks that a payment is correctly indexed. - // For KV: verifies the payment index bucket entry exists and points - // to the correct payment hash. - // For SQL: no-op (SQL doesn't use a separate index bucket). - AssertPaymentIndex(t *testing.T, expectedHash lntypes.Hash) - - // AssertNoIndex checks that an index for a sequence number doesn't - // exist. - // For KV: verifies the index bucket entry is deleted. - // For SQL: no-op. - AssertNoIndex(t *testing.T, seqNr uint64) -} diff --git a/payments/db/test_kvdb.go b/payments/db/test_kvdb.go index c2de0b43f..e0ee1738d 100644 --- a/payments/db/test_kvdb.go +++ b/payments/db/test_kvdb.go @@ -1,20 +1,14 @@ -//go:build !test_db_sqlite && !test_db_postgres - package paymentsdb import ( - "bytes" "testing" - "github.com/btcsuite/btcwallet/walletdb" "github.com/lightningnetwork/lnd/kvdb" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // NewTestDB is a helper function that creates an BBolt database for testing. -func NewTestDB(t *testing.T, opts ...OptionModifier) (DB, TestHarness) { +func NewTestDB(t *testing.T, opts ...OptionModifier) DB { backend, backendCleanup, err := kvdb.GetTestBackend( t.TempDir(), "paymentsDB", ) @@ -25,7 +19,7 @@ func NewTestDB(t *testing.T, opts ...OptionModifier) (DB, TestHarness) { paymentDB, err := NewKVStore(backend, opts...) require.NoError(t, err) - return paymentDB, &kvTestHarness{db: paymentDB} + return paymentDB } // NewKVTestDB is a helper function that creates an BBolt database for testing @@ -44,70 +38,3 @@ func NewKVTestDB(t *testing.T, opts ...OptionModifier) *KVStore { return paymentDB } - -// kvTestHarness is the KV-specific test harness implementation. -type kvTestHarness struct { - db *KVStore -} - -// AssertPaymentIndex looks up the index for a payment in the db and checks -// that its payment hash matches the expected hash passed in. -func (h *kvTestHarness) AssertPaymentIndex(t *testing.T, - expectedHash lntypes.Hash) { - - t.Helper() - - ctx := t.Context() - - // Lookup the payment so that we have its sequence number and check - // that it has correctly been indexed in the payment indexes bucket. - pmt, err := h.db.FetchPayment(ctx, expectedHash) - require.NoError(t, err) - - hash, err := h.fetchPaymentIndexEntry(t, pmt.SequenceNum) - require.NoError(t, err) - assert.Equal(t, expectedHash, *hash) -} - -// AssertNoIndex checks that an index for the sequence number provided does not -// exist. -func (h *kvTestHarness) AssertNoIndex(t *testing.T, seqNr uint64) { - t.Helper() - - _, err := h.fetchPaymentIndexEntry(t, seqNr) - require.Equal(t, ErrNoSequenceNrIndex, err) -} - -// fetchPaymentIndexEntry gets the payment hash for the sequence number -// provided from the payment indexes bucket. -func (h *kvTestHarness) fetchPaymentIndexEntry(t *testing.T, - sequenceNumber uint64) (*lntypes.Hash, error) { - - t.Helper() - - var hash lntypes.Hash - - if err := kvdb.View(h.db.db, func(tx walletdb.ReadTx) error { - indexBucket := tx.ReadBucket(paymentsIndexBucket) - key := make([]byte, 8) - byteOrder.PutUint64(key, sequenceNumber) - - indexValue := indexBucket.Get(key) - if indexValue == nil { - return ErrNoSequenceNrIndex - } - - r := bytes.NewReader(indexValue) - - var err error - hash, err = deserializePaymentIndex(r) - - return err - }, func() { - hash = lntypes.Hash{} - }); err != nil { - return nil, err - } - - return &hash, nil -} diff --git a/payments/db/test_postgres.go b/payments/db/test_postgres.go deleted file mode 100644 index bd22703f1..000000000 --- a/payments/db/test_postgres.go +++ /dev/null @@ -1,95 +0,0 @@ -//go:build test_db_postgres && !test_db_sqlite - -package paymentsdb - -import ( - "database/sql" - "testing" - - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/sqldb" - "github.com/stretchr/testify/require" -) - -// NewTestDB is a helper function that creates a SQLStore backed by a SQL -// database for testing. -func NewTestDB(t testing.TB, opts ...OptionModifier) (DB, TestHarness) { - db := NewTestDBWithFixture(t, nil, opts...) - return db, &noopTestHarness{} -} - -// NewTestDBFixture creates a new sqldb.TestPgFixture for testing purposes. -func NewTestDBFixture(t *testing.T) *sqldb.TestPgFixture { - pgFixture := sqldb.NewTestPgFixture( - t, sqldb.DefaultPostgresFixtureLifetime, - ) - t.Cleanup(func() { - pgFixture.TearDown(t) - }) - return pgFixture -} - -// NewTestDBWithFixture is a helper function that creates a SQLStore backed by a -// SQL database for testing. -func NewTestDBWithFixture(t testing.TB, - pgFixture *sqldb.TestPgFixture, opts ...OptionModifier) DB { - - var querier BatchedSQLQueries - if pgFixture == nil { - querier = newBatchQuerier(t) - } else { - querier = newBatchQuerierWithFixture(t, pgFixture) - } - - store, err := NewSQLStore( - &SQLStoreConfig{ - QueryCfg: sqldb.DefaultPostgresConfig(), - }, querier, opts..., - ) - require.NoError(t, err) - - return store -} - -// newBatchQuerier creates a new BatchedSQLQueries instance for testing -// using a PostgreSQL database fixture. -func newBatchQuerier(t testing.TB) BatchedSQLQueries { - pgFixture := sqldb.NewTestPgFixture( - t, sqldb.DefaultPostgresFixtureLifetime, - ) - t.Cleanup(func() { - pgFixture.TearDown(t) - }) - - return newBatchQuerierWithFixture(t, pgFixture) -} - -// newBatchQuerierWithFixture creates a new BatchedSQLQueries instance for -// testing using a PostgreSQL database fixture. -func newBatchQuerierWithFixture(t testing.TB, - pgFixture *sqldb.TestPgFixture) BatchedSQLQueries { - - db := sqldb.NewTestPostgresDB(t, pgFixture).BaseDB - - return sqldb.NewTransactionExecutor( - db, func(tx *sql.Tx) SQLQueries { - return db.WithTx(tx) - }, - ) -} - -// noopTestHarness is the SQL test harness implementation. Since SQL doesn't -// use a separate payment index bucket like KV, these assertions are no-ops. -type noopTestHarness struct{} - -// AssertPaymentIndex is a no-op for SQL implementations. -func (h *noopTestHarness) AssertPaymentIndex(t *testing.T, - expectedHash lntypes.Hash) { - - // No-op: SQL doesn't use a separate index bucket. -} - -// AssertNoIndex is a no-op for SQL implementations. -func (h *noopTestHarness) AssertNoIndex(t *testing.T, seqNr uint64) { - // No-op: SQL doesn't use a separate index bucket. -} diff --git a/payments/db/test_sqlite.go b/payments/db/test_sqlite.go deleted file mode 100644 index 99d104780..000000000 --- a/payments/db/test_sqlite.go +++ /dev/null @@ -1,74 +0,0 @@ -//go:build !test_db_postgres && test_db_sqlite - -package paymentsdb - -import ( - "database/sql" - "testing" - - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/sqldb" - "github.com/stretchr/testify/require" -) - -// NewTestDB is a helper function that creates a SQLStore backed by a SQL -// database for testing. -func NewTestDB(t testing.TB, opts ...OptionModifier) (DB, TestHarness) { - db := NewTestDBWithFixture(t, nil, opts...) - return db, &noopTestHarness{} -} - -// NewTestDBFixture is a no-op for the sqlite build. -func NewTestDBFixture(_ *testing.T) *sqldb.TestPgFixture { - return nil -} - -// NewTestDBWithFixture is a helper function that creates a SQLStore backed by a -// SQL database for testing. -func NewTestDBWithFixture(t testing.TB, _ *sqldb.TestPgFixture, - opts ...OptionModifier) DB { - - store, err := NewSQLStore( - &SQLStoreConfig{ - QueryCfg: sqldb.DefaultSQLiteConfig(), - }, newBatchQuerier(t), opts..., - ) - require.NoError(t, err) - return store -} - -// newBatchQuerier creates a new BatchedSQLQueries instance for testing -// using a SQLite database. -func newBatchQuerier(t testing.TB) BatchedSQLQueries { - return newBatchQuerierWithFixture(t, nil) -} - -// newBatchQuerierWithFixture creates a new BatchedSQLQueries instance for -// testing using a SQLite database. -func newBatchQuerierWithFixture(t testing.TB, - _ *sqldb.TestPgFixture) BatchedSQLQueries { - - db := sqldb.NewTestSqliteDB(t).BaseDB - - return sqldb.NewTransactionExecutor( - db, func(tx *sql.Tx) SQLQueries { - return db.WithTx(tx) - }, - ) -} - -// noopTestHarness is the SQL test harness implementation. Since SQL doesn't -// use a separate payment index bucket like KV, these assertions are no-ops. -type noopTestHarness struct{} - -// AssertPaymentIndex is a no-op for SQL implementations. -func (h *noopTestHarness) AssertPaymentIndex(t *testing.T, - expectedHash lntypes.Hash) { - - // No-op: SQL doesn't use a separate index bucket. -} - -// AssertNoIndex is a no-op for SQL implementations. -func (h *noopTestHarness) AssertNoIndex(t *testing.T, seqNr uint64) { - // No-op: SQL doesn't use a separate index bucket. -} diff --git a/peer/brontide.go b/peer/brontide.go index 6b2a3ad33..9191cbb2e 100644 --- a/peer/brontide.go +++ b/peer/brontide.go @@ -14,19 +14,17 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/connmgr" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btclog/v2" - "github.com/lightningnetwork/lnd/actor" "github.com/lightningnetwork/lnd/aliasmgr" "github.com/lightningnetwork/lnd/brontide" "github.com/lightningnetwork/lnd/buffer" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channelnotifier" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/discovery" "github.com/lightningnetwork/lnd/feature" @@ -46,15 +44,12 @@ import ( "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chancloser" - "github.com/lightningnetwork/lnd/lnwallet/types" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/msgmux" "github.com/lightningnetwork/lnd/netann" - "github.com/lightningnetwork/lnd/onionmessage" "github.com/lightningnetwork/lnd/pool" "github.com/lightningnetwork/lnd/protofsm" "github.com/lightningnetwork/lnd/queue" - "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/subscribe" "github.com/lightningnetwork/lnd/ticker" "github.com/lightningnetwork/lnd/tlv" @@ -71,10 +66,6 @@ const ( // This MUST be a smaller value than the pingInterval. pingTimeout = 30 * time.Second - // tellTimeout is the amount of time we will wait for a response to a - // tell. - tellTimeout = 30 * time.Second - // idleTimeout is the duration of inactivity before we time out a peer. idleTimeout = 5 * time.Minute @@ -123,7 +114,7 @@ type outgoingMsg struct { errChan chan error // MUST be buffered. } -// newChannelMsg packages a chanstate.OpenChannel with a channel that allows +// newChannelMsg packages a channeldb.OpenChannel with a channel that allows // the receiver of the request to report when the channel creation process has // completed. type newChannelMsg struct { @@ -177,12 +168,12 @@ type ChannelCloseUpdate struct { // LocalCloseOutput is an optional, additional output on the closing // transaction that the local party should be paid to. This will only be // populated if the local balance isn't dust. - LocalCloseOutput fn.Option[types.CloseOutput] + LocalCloseOutput fn.Option[chancloser.CloseOutput] // RemoteCloseOutput is an optional, additional output on the closing // transaction that the remote party should be paid to. This will only // be populated if the remote balance isn't dust. - RemoteCloseOutput fn.Option[types.CloseOutput] + RemoteCloseOutput fn.Option[chancloser.CloseOutput] // AuxOutputs is an optional set of additional outputs that might be // included in the closing transaction. These are used for custom @@ -260,8 +251,8 @@ type Config struct { // ChannelLinkConfig. InterceptSwitch *htlcswitch.InterceptableSwitch - // ChannelDB is used to fetch channel state needed by the peer. - ChannelDB chanstate.Store + // ChannelDB is used to fetch opened channels, and closed channels. + ChannelDB *channeldb.ChannelStateDB // ChannelGraph is a pointer to the channel graph which is used to // query information about the set of known active channels. @@ -308,45 +299,9 @@ type Config struct { // the Brontide. RoutingPolicy models.ForwardingPolicy - // SphinxPayment is used when setting up ChannelLinks so they can decode - // sphinx onion blobs. - SphinxPayment *hop.OnionProcessor - - // SpawnOnionActor is a factory function that spawns a per-peer onion - // message actor. If nil, onion messaging is disabled. - SpawnOnionActor onionmessage.OnionActorFactory - - // OnionLimiter is the combined per-peer + global onion message - // ingress rate limiter. It hides the split between the two - // underlying buckets behind a single interface: callers invoke - // OnionLimiter.AllowN on every incoming onion message and it - // consults the per-peer bucket first (so a hostile peer whose own - // budget is empty cannot drain the shared budget on rejected - // attempts) and then the global bucket. Per-peer state is retained - // across disconnect so a peer cannot reset its bucket by cycling - // the connection; see the PeerRateLimiter doc for the memory-bound - // argument. A nil value means onion message rate limiting is - // disabled. - OnionLimiter onionmessage.IngressLimiter - - // OnionRelayAll, when true, disables the channel-presence gate on - // incoming onion messages: messages from peers with no fully open - // channel are admitted to the rate-limiter pipeline instead of - // being dropped at ingress. The default (false) keeps the gate in - // place so that a no-cost Sybil identity cannot burn a full - // per-peer byte budget on each of many connections and saturate - // the global limiter through sheer identity count. - OnionRelayAll bool - - // OnionActorOpts returns ActorOptions for the onion peer actor - // being spawned for the given peer. This allows per-peer - // customization of mailbox size, drop predicates, etc. - OnionActorOpts func(peerPubKey [33]byte) []actor.ActorOption[ - *onionmessage.Request, *onionmessage.Response, - ] - - // ActorSystem is the actor system tasked with managing actors. - ActorSystem *actor.ActorSystem + // Sphinx is used when setting up ChannelLinks so they can decode sphinx + // onion blobs. + Sphinx *hop.OnionProcessor // WitnessBeacon is used when setting up ChannelLinks so they can add any // preimages that they learn. @@ -415,12 +370,6 @@ type Config struct { // closure initiated by the remote peer. CoopCloseTargetConfs uint32 - // ChannelCloseConfs is an optional override for the number of - // confirmations required for channel closes. When set, this overrides - // the normal capacity-based scaling. This is only available in - // dev/integration builds for testing purposes. - ChannelCloseConfs fn.Option[uint32] - // ServerPubKey is the serialized, compressed public key of our lnd node. // It is used to determine which policy (channel edge) to pass to the // ChannelLink. @@ -514,9 +463,9 @@ type Config struct { // related wire messages. AuxChannelNegotiator fn.Option[lnwallet.AuxChannelNegotiator] - // ShouldFwdExpAccountability is a closure that indicates whether - // experimental accountability signals should be set. - ShouldFwdExpAccountability func() bool + // ShouldFwdExpEndorsement is a closure that indicates whether + // experimental endorsement signals should be set. + ShouldFwdExpEndorsement func() bool // NoDisconnectOnPongFailure indicates whether the peer should *not* be // disconnected if a pong is not received in time or is mismatched. @@ -578,11 +527,6 @@ type Brontide struct { // this heuristic is good enough for your use case. isTorConnection bool - // onionActorRef holds the reference to the onion peer actor spawned - // for this peer connection. The actor handles all incoming onion - // message processing for this peer. - onionActorRef fn.Option[onionmessage.OnionPeerActorRef] - pingManager *PingManager // lastPingPayload stores an unsafe pointer wrapped as an atomic @@ -623,17 +567,6 @@ type Brontide struct { activeChannels *lnutils.SyncMap[ lnwire.ChannelID, *lnwallet.LightningChannel] - // numActiveChans shadows the count of non-pending entries in - // activeChannels as an atomic integer. It exists so that hot-path - // callers — notably the onion message ingress gate, which runs on - // every incoming onion packet — can ask "does this peer have any - // active channel with us" in O(1) instead of iterating the - // activeChannels registry. It is maintained in lockstep with - // activeChannels via Swap and LoadAndDelete at every mutation - // site, so transitions from pending (nil value) to active and - // from active to closed are reflected atomically. - numActiveChans atomic.Int32 - // addedChannels tracks any new channels opened during this peer's // lifecycle. We use this to filter out these new channels when the time // comes to request a reenable for active channels, since they will have @@ -675,14 +608,6 @@ type Brontide struct { // well as lnwire.ClosingSigned messages. chanCloseMsgs chan *closeMsg - // chanCloseFlushed carries the ID of a channel whose link has finished - // draining its HTLCs, which is the point a legacy cooperative close can - // move on to fee negotiation. The link notices this from its own - // goroutine, so it hands the channel over here rather than advance the - // closer itself, which keeps every step of the negotiation on the - // channelManager goroutine. - chanCloseFlushed chan lnwire.ChannelID - // remoteFeatures is the feature vector received from the peer during // the connection handshake. remoteFeatures *lnwire.FeatureVector @@ -761,7 +686,6 @@ func NewBrontide(cfg Config) *Brontide { localCloseChanReqs: make(chan *htlcswitch.ChanClose), linkFailures: make(chan linkFailureReport), chanCloseMsgs: make(chan *closeMsg), - chanCloseFlushed: make(chan lnwire.ChannelID), resentChanSyncMsg: make(map[lnwire.ChannelID]struct{}), startReady: make(chan struct{}), log: peerLog.WithPrefix(logPrefix), @@ -788,7 +712,7 @@ func NewBrontide(cfg Config) *Brontide { // used to cross-check our own view of the network to mitigate // various types of eclipse attacks. header, err := p.cfg.BestBlockView.BestBlockHeader() - if err != nil || header == lastBlockHeader { + if err != nil && header == lastBlockHeader { return lastSerializedBlockHeader[:] } @@ -974,44 +898,6 @@ func (p *Brontide) Start() error { return fmt.Errorf("unable to load channels: %w", err) } - // If the remote peer supports onion messages and we have a factory - // configured, spawn the onion peer actor for this connection. The - // actor handles the full processing pipeline for incoming onion - // messages from this peer. - if p.remoteFeatures.HasFeature(lnwire.OnionMessagesOptional) && - p.cfg.SpawnOnionActor != nil { - - p.log.Infof("Remote peer supports onion messages, " + - "spawning onion message actor") - - // Fetch per-peer actor options. The OnionActorOpts - // callback is the extension point for per-peer - // customization of the drop predicate (e.g. choosing - // RED thresholds based on channel capacity or routing - // importance). Today the callback returns identical - // defaults for every peer; to differentiate, supply a - // callback that inspects peerPubKey and returns - // tailored options via - // onionmessage.DefaultOnionActorOpts with a - // peer-specific DropCheckFunc. - var opts []actor.ActorOption[ - *onionmessage.Request, *onionmessage.Response, - ] - if p.cfg.OnionActorOpts != nil { - opts = p.cfg.OnionActorOpts(p.PubKey()) - } - - ref, spawnErr := p.cfg.SpawnOnionActor( - p.cfg.ActorSystem, p.PubKey(), opts..., - ) - if spawnErr != nil { - return fmt.Errorf("unable to spawn onion peer "+ - "actor: %w", spawnErr) - } - - p.onionActorRef = fn.Some(ref) - } - p.startTime = time.Now() // Before launching the writeHandler goroutine, we send any channel @@ -1098,26 +984,16 @@ func (p *Brontide) taprootShutdownAllowed() bool { p.LocalFeatures().HasFeature(lnwire.ShutdownAnySegwitOptional) } -// rbfCoopCloseAllowed returns true if the new RBF coop close flow can be -// used for a channel of the given type: both parties must have negotiated -// the RBF coop close feature, and the channel must not be an aux channel. -// Aux channels (taproot overlay channels, marked by a tapscript root) are -// excluded even when both peers signal the RBF feature bit: the RBF close -// state machine does not invoke any of the aux closer hooks, so closing an -// aux channel through it would produce a close transaction without the aux -// outputs (destroying the committed assets), which the aux closer is then -// unable to finalize once the transaction confirms. Such channels fall back -// to the legacy negotiate closer, which is aux-aware. -func (p *Brontide) rbfCoopCloseAllowed(chanType chanstate.ChannelType) bool { +// rbfCoopCloseAllowed returns true if both parties have negotiated the new RBF +// coop close feature. +func (p *Brontide) rbfCoopCloseAllowed() bool { bothHaveBit := func(bit lnwire.FeatureBit) bool { return p.RemoteFeatures().HasFeature(bit) && p.LocalFeatures().HasFeature(bit) } - featureNegotiated := bothHaveBit(lnwire.RbfCoopCloseOptional) || + return bothHaveBit(lnwire.RbfCoopCloseOptional) || bothHaveBit(lnwire.RbfCoopCloseOptionalStaging) - - return featureNegotiated && !chanType.HasTapscriptRoot() } // QuitSignal is a method that should return a channel which will be sent upon @@ -1161,9 +1037,7 @@ func (p *Brontide) addrWithInternalKey( // channels returned by the database. It returns a slice of channel reestablish // messages that should be sent to the peer immediately, in case we have borked // channels that haven't been closed yet. -// -//nolint:funlen -func (p *Brontide) loadActiveChannels(chans []*chanstate.OpenChannel) ( +func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) ( []lnwire.Message, error) { // Return a slice of messages to send to the peers in case the channel @@ -1245,16 +1119,6 @@ func (p *Brontide) loadActiveChannels(chans []*chanstate.OpenChannel) ( }, ) - p.cfg.AuxTrafficShaper.WhenSome( - func(ts htlcswitch.AuxTrafficShaper) { - val := p.createHtlcValidator(dbChan, ts) - chanOpts = append( - chanOpts, - lnwallet.WithAuxHtlcValidator(val), - ) - }, - ) - lnChan, err := lnwallet.NewLightningChannel( p.cfg.Signer, dbChan, p.cfg.SigPool, chanOpts..., ) @@ -1326,7 +1190,7 @@ func (p *Brontide) loadActiveChannels(chans []*chanstate.OpenChannel) ( // the database. graph := p.cfg.ChannelGraph info, p1, p2, err := graph.FetchChannelEdgesByOutpoint( - context.TODO(), &chanPoint, + &chanPoint, ) if err != nil && !errors.Is(err, graphdb.ErrEdgeNotFound) { return nil, err @@ -1381,7 +1245,7 @@ func (p *Brontide) loadActiveChannels(chans []*chanstate.OpenChannel) ( // channels. Adding them here would just be extra work as we'll // tear them down when creating + adding the final link. if lnChan.IsPending() { - p.markPendingChannel(chanID) + p.activeChannels.Store(chanID, nil) continue } @@ -1391,14 +1255,17 @@ func (p *Brontide) loadActiveChannels(chans []*chanstate.OpenChannel) ( return nil, err } + isTaprootChan := lnChan.ChanType().IsTaproot() + var ( shutdownMsg fn.Option[lnwire.Shutdown] shutdownInfoErr error ) shutdownInfo.WhenSome(func(info channeldb.ShutdownInfo) { - // If we can use the new RBF close feature for this - // channel, we don't need to create the legacy closer. - if p.rbfCoopCloseAllowed(dbChan.ChanType) { + // If we can use the new RBF close feature, we don't + // need to create the legacy closer. However for taproot + // channels, we'll continue to use the legacy closer. + if p.rbfCoopCloseAllowed() && !isTaprootChan { return } @@ -1443,7 +1310,7 @@ func (p *Brontide) loadActiveChannels(chans []*chanstate.OpenChannel) ( // Create the Shutdown message. shutdown, err := negotiateChanCloser.ShutdownChan() if err != nil { - p.deleteActiveChanCloser(chanID, chanPoint) + p.activeChanCloses.Delete(chanID) shutdownInfoErr = err return @@ -1472,11 +1339,13 @@ func (p *Brontide) loadActiveChannels(chans []*chanstate.OpenChannel) ( "switch: %v", chanPoint, err) } - p.storeActiveChannel(chanID, lnChan) + p.activeChannels.Store(chanID, lnChan) - // We're using the old co-op close for this channel, so we - // don't need to init the new RBF chan closer. - if !p.rbfCoopCloseAllowed(dbChan.ChanType) { + // We're using the old co-op close, so we don't need to init + // the new RBF chan closer. If we have a taproot chan, then + // we'll also use the legacy type, so we don't need to make the + // new closer. + if !p.rbfCoopCloseAllowed() || isTaprootChan { continue } @@ -1487,7 +1356,7 @@ func (p *Brontide) loadActiveChannels(chans []*chanstate.OpenChannel) ( // Creating this here ensures that any shutdown messages sent // will be automatically routed by the msg router. if _, err := p.initRbfChanCloser(lnChan); err != nil { - p.deleteActiveChanCloser(chanID, chanPoint) + p.activeChanCloses.Delete(chanID) return nil, fmt.Errorf("unable to init RBF chan "+ "closer during peer connect: %w", err) @@ -1550,8 +1419,8 @@ func (p *Brontide) addLink(chanPoint *wire.OutPoint, //nolint:ll linkCfg := htlcswitch.ChannelLinkConfig{ Peer: p, - DecodeHopIterators: p.cfg.SphinxPayment.DecodeHopIterators, - ExtractErrorEncrypter: p.cfg.SphinxPayment.ExtractErrorEncrypter, + DecodeHopIterators: p.cfg.Sphinx.DecodeHopIterators, + ExtractErrorEncrypter: p.cfg.Sphinx.ExtractErrorEncrypter, FetchLastChannelUpdate: p.cfg.FetchLastChanUpdate, HodlMask: p.cfg.Hodl.Mask(), Registry: p.cfg.Invoices, @@ -1571,26 +1440,25 @@ func (p *Brontide) addLink(chanPoint *wire.OutPoint, PendingCommitTicker: ticker.New( p.cfg.PendingCommitInterval, ), - BatchSize: p.cfg.ChannelCommitBatchSize, - UnsafeReplay: p.cfg.UnsafeReplay, - MinUpdateTimeout: htlcswitch.DefaultMinLinkFeeUpdateTimeout, - MaxUpdateTimeout: htlcswitch.DefaultMaxLinkFeeUpdateTimeout, - OutgoingCltvRejectDelta: p.cfg.OutgoingCltvRejectDelta, - TowerClient: p.cfg.TowerClient, - MaxOutgoingCltvExpiry: p.cfg.MaxOutgoingCltvExpiry, - MaxFeeAllocation: p.cfg.MaxChannelFeeAllocation, - MaxAnchorsCommitFeeRate: p.cfg.MaxAnchorsCommitFeeRate, - NotifyActiveLink: p.cfg.ChannelNotifier.NotifyActiveLinkEvent, - NotifyActiveChannel: p.cfg.ChannelNotifier.NotifyActiveChannelEvent, - NotifyInactiveChannel: p.cfg.ChannelNotifier.NotifyInactiveChannelEvent, - NotifyInactiveLinkEvent: p.cfg.ChannelNotifier.NotifyInactiveLinkEvent, - NotifyChannelUpdate: p.cfg.ChannelNotifier.NotifyChannelUpdateEvent, - HtlcNotifier: p.cfg.HtlcNotifier, - GetAliases: p.cfg.GetAliases, - PreviouslySentShutdown: shutdownMsg, - DisallowRouteBlinding: p.cfg.DisallowRouteBlinding, - MaxFeeExposure: p.cfg.MaxFeeExposure, - ShouldFwdExpAccountability: p.cfg.ShouldFwdExpAccountability, + BatchSize: p.cfg.ChannelCommitBatchSize, + UnsafeReplay: p.cfg.UnsafeReplay, + MinUpdateTimeout: htlcswitch.DefaultMinLinkFeeUpdateTimeout, + MaxUpdateTimeout: htlcswitch.DefaultMaxLinkFeeUpdateTimeout, + OutgoingCltvRejectDelta: p.cfg.OutgoingCltvRejectDelta, + TowerClient: p.cfg.TowerClient, + MaxOutgoingCltvExpiry: p.cfg.MaxOutgoingCltvExpiry, + MaxFeeAllocation: p.cfg.MaxChannelFeeAllocation, + MaxAnchorsCommitFeeRate: p.cfg.MaxAnchorsCommitFeeRate, + NotifyActiveLink: p.cfg.ChannelNotifier.NotifyActiveLinkEvent, + NotifyActiveChannel: p.cfg.ChannelNotifier.NotifyActiveChannelEvent, + NotifyInactiveChannel: p.cfg.ChannelNotifier.NotifyInactiveChannelEvent, + NotifyInactiveLinkEvent: p.cfg.ChannelNotifier.NotifyInactiveLinkEvent, + HtlcNotifier: p.cfg.HtlcNotifier, + GetAliases: p.cfg.GetAliases, + PreviouslySentShutdown: shutdownMsg, + DisallowRouteBlinding: p.cfg.DisallowRouteBlinding, + MaxFeeExposure: p.cfg.MaxFeeExposure, + ShouldFwdExpEndorsement: p.cfg.ShouldFwdExpEndorsement, DisallowQuiescence: p.cfg.DisallowQuiescence || !p.remoteFeatures.HasFeature(lnwire.QuiescenceOptional), AuxTrafficShaper: p.cfg.AuxTrafficShaper, @@ -1613,7 +1481,7 @@ func (p *Brontide) addLink(chanPoint *wire.OutPoint, // maybeSendNodeAnn sends our node announcement to the remote peer if at least // one confirmed public channel exists with them. -func (p *Brontide) maybeSendNodeAnn(channels []*chanstate.OpenChannel) { +func (p *Brontide) maybeSendNodeAnn(channels []*channeldb.OpenChannel) { defer p.cg.WgDone() hasConfirmedPublicChan := false @@ -1753,23 +1621,13 @@ func (p *Brontide) Disconnect(reason error) { // started, otherwise we will skip reading it as this chan won't be // closed, hence blocks forever. if atomic.LoadInt32(&p.started) == 1 { - // First check if startup has already completed (non-blocking). + p.log.Debugf("Peer hasn't finished starting up yet, waiting " + + "on startReady signal before closing connection") + select { case <-p.startReady: - // Startup already completed, no need to wait. - - default: - // Still starting up, need to wait. - p.log.Debugf("Peer hasn't finished starting up yet, " + - "waiting on startReady signal before " + - "closing connection") - - select { - case <-p.startReady: - - case <-p.cg.Done(): - return - } + case <-p.cg.Done(): + return } } @@ -1781,13 +1639,6 @@ func (p *Brontide) Disconnect(reason error) { // Stop PingManager before closing TCP connection. p.pingManager.Stop() - // Stop the onion peer actor if one was spawned. - p.StopOnionActorIfExists() - - // Unregister any RBF close actors registered for channels of this - // peer so we don't leave stale entries in the actor system. - p.unregisterRbfCloseActors() - // Ensure that the TCP connection is properly closed before continuing. p.cfg.Conn.Close() @@ -1807,66 +1658,6 @@ func (p *Brontide) String() string { return fmt.Sprintf("%x@%s", p.cfg.PubKeyBytes, p.cfg.Conn.RemoteAddr()) } -// StopOnionActorIfExists stops the onion peer actor if one was spawned for -// this peer. This is idempotent and safe to call multiple times. -func (p *Brontide) StopOnionActorIfExists() { - p.onionActorRef.WhenSome( - func(ref onionmessage.OnionPeerActorRef) { - onionmessage.StopOnionActor( - p.cfg.ActorSystem, p.PubKey(), ref, - ) - }, - ) -} - -// unregisterRbfCloseActor removes any RBF close actor registered for the -// given channel point from the actor system. This is idempotent and safe to -// call whether or not an actor was registered for the channel point. -func (p *Brontide) unregisterRbfCloseActor(chanPoint wire.OutPoint) { - if p.cfg.ActorSystem == nil { - return - } - - actorKey := NewRbfCloserPeerServiceKey(chanPoint) - actorKey.UnregisterAll(p.cfg.ActorSystem) -} - -// deleteActiveChanCloser removes the chan closer for the given channel ID and -// also unregisters any RBF close actor associated with the channel point from -// the actor system. Callers should prefer this over calling -// activeChanCloses.Delete directly so the actor registry stays in sync with -// the active closers map. -func (p *Brontide) deleteActiveChanCloser(chanID lnwire.ChannelID, - chanPoint wire.OutPoint) { - - p.activeChanCloses.Delete(chanID) - p.unregisterRbfCloseActor(chanPoint) -} - -// unregisterRbfCloseActors removes any RBF close actors registered for this -// peer's active channels from the actor system. This should be called on -// disconnect so we don't leave stale RBF close actors for a peer that is no -// longer connected. This is idempotent and safe to call multiple times. -func (p *Brontide) unregisterRbfCloseActors() { - if p.cfg.ActorSystem == nil { - return - } - - p.activeChannels.Range(func(_ lnwire.ChannelID, - channel *lnwallet.LightningChannel) bool { - - // Pending channels are tracked with a nil value in the map, - // so skip those as they have no channel point to look up. - if channel == nil { - return true - } - - p.unregisterRbfCloseActor(channel.ChannelPoint()) - - return true - }) -} - // readNextMessage reads, and returns the next message on the wire along with // any additional raw payload. func (p *Brontide) readNextMessage() (lnwire.Message, error) { @@ -2221,11 +2012,13 @@ func newDiscMsgStream(p *Brontide) *msgStream { // deleted. p.log.Debugf("Processing remote msg %T", msg) - // The returned Future[error] is intentionally not awaited - // here. Remote gossip messages are fire-and-forget from the - // peer's perspective: the gossiper processes them - // asynchronously, and an unawaited Future carries no cost - // (no goroutine, no channel leak). + // TODO(ziggie): ProcessRemoteAnnouncement returns an error + // channel, but we cannot rely on it being written to. + // Because some messages might never be processed (e.g. + // premature channel updates). We should change the design here + // and use the actor model pattern as soon as it is available. + // So for now we should NOT use the error channel. + // See https://github.com/lightningnetwork/lnd/pull/9820. p.cfg.AuthGossiper.ProcessRemoteAnnouncement(ctx, msg, p) } @@ -2348,13 +2141,6 @@ out: // the relevant atomic variable. p.lastPingPayload.Store(msg.PaddingBytes[:]) - // BOLT 1 requires us to ignore pings requesting 65532 - // or more pong bytes instead of replying or - // disconnecting. - if msg.NumPongBytes > lnwire.MaxPongBytes { - continue - } - // Next, we'll send over the amount of specified pong // bytes. pong := lnwire.NewPong(p.cfg.PongBuf[0:msg.NumPongBytes]) @@ -2437,57 +2223,6 @@ out: discStream.AddMsg(msg) - case *lnwire.OnionMessage: - // Charge the limiter the on-the-wire size of the - // message so the byte-granular bucket reflects - // actual ingress bandwidth rather than raw message - // counts. The channel-gate hint is sourced from the - // atomic active-channel counter so the check is - // O(1) on the hot path. A rejection surfaces as a - // sentinel error wrapped in fn.Result; errors.Is - // lets us pick the right first-drop log path. - result := allowOnionMessage( - p.cfg.OnionLimiter, p.PubKey(), - msg.WireSize(), p.hasActiveChannels(), - p.cfg.OnionRelayAll, - ) - if err := result.Err(); err != nil { - logFirstOnionDrop( - peerLog, p.log, err, - p.cfg.OnionLimiter, - ) - // Keep repeated drops at trace so a - // sustained attack does not flood debug; - // the first-drop info log above already - // gives operators a clear "limiter - // engaged" signal. - p.log.Tracef("dropping onion message: %v", - err) - - break - } - - p.onionActorRef.WhenSome( - func(ref onionmessage.OnionPeerActorRef) { - // TODO(elle): thread contexts through - // the peer system properly so that a - // parent context can be passed in here. - - // Use a timeout context to prevent - // the readHandler from blocking - // indefinitely if the actor's mailbox - // is full. - ctx, cancel := context.WithTimeout( - context.Background(), - tellTimeout, - ) - defer cancel() - - req := onionmessage.NewRequest(*msg) - ref.Tell(ctx, req) - }, - ) - case *lnwire.Custom: err := p.handleCustomMessage(msg) if err != nil { @@ -2583,51 +2318,6 @@ func (p *Brontide) hasChannel(chanID lnwire.ChannelID) bool { return ok } -// hasActiveChannels reports whether this peer has at least one fully open -// (non-pending) channel with us. Pending channels are excluded because -// they do not yet provide the Sybil-resistance guarantees the onion -// message ingress gate relies on. The check reads an atomic counter -// maintained alongside activeChannels at every mutation site, so it is -// O(1) and cheap enough to run on every incoming onion message without -// iterating a map. -func (p *Brontide) hasActiveChannels() bool { - return p.numActiveChans.Load() > 0 -} - -// markPendingChannel records chanID in activeChannels as a pending (nil) -// entry; numActiveChans is unchanged. -func (p *Brontide) markPendingChannel(chanID lnwire.ChannelID) { - p.activeChannels.Store(chanID, nil) -} - -// storeActiveChannel installs lnChan under chanID and bumps -// numActiveChans iff the prior entry was absent or pending (nil). -func (p *Brontide) storeActiveChannel(chanID lnwire.ChannelID, - lnChan *lnwallet.LightningChannel) { - - prev, loaded := p.activeChannels.Swap(chanID, lnChan) - if !loaded || prev == nil { - p.numActiveChans.Add(1) - } -} - -// removeActiveChannel deletes chanID and decrements numActiveChans only -// when the removed entry was a fully open (non-nil) channel. -func (p *Brontide) removeActiveChannel(chanID lnwire.ChannelID) { - prev, loaded := p.activeChannels.LoadAndDelete(chanID) - if loaded && prev != nil { - p.numActiveChans.Add(-1) - } -} - -// deletePendingChannel deletes a pending entry owned by the -// channelManager goroutine; no counter check since pending entries never -// contribute to numActiveChans. External callers must use -// removeActiveChannel so a racing promotion is handled correctly. -func (p *Brontide) deletePendingChannel(chanID lnwire.ChannelID) { - p.activeChannels.Delete(chanID) -} - // storeError stores an error in our peer's buffer of recent errors with the // current timestamp. Errors are only stored if we have at least one active // channel with the peer to mitigate a dos vector where a peer costlessly @@ -2809,8 +2499,7 @@ func messageSummary(msg lnwire.Message) string { msg.NodeID, time.Unix(int64(msg.Timestamp), 0)) case *lnwire.Ping: - return fmt.Sprintf("num_pong_bytes=%d, len(ping_bytes)=%d", - msg.NumPongBytes, len(msg.PaddingBytes[:])) + return fmt.Sprintf("ping_bytes=%x", msg.PaddingBytes[:]) case *lnwire.Pong: return fmt.Sprintf("len(pong_bytes)=%d", len(msg.PongBytes[:])) @@ -2849,15 +2538,6 @@ func messageSummary(msg lnwire.Message) string { time.Unix(int64(msg.FirstTimestamp), 0), msg.TimestampRange) - case *lnwire.OnionMessage: - var pathKey []byte - if msg.PathKey != nil { - pathKey = msg.PathKey.SerializeCompressed() - } - - return fmt.Sprintf("path_key=%x, onion_len=%v", pathKey, - len(msg.OnionBlob)) - case *lnwire.Stfu: return fmt.Sprintf("chan_id=%v, initiator=%v", msg.ChanID, msg.Initiator) @@ -3287,11 +2967,6 @@ out: case closeMsg := <-p.chanCloseMsgs: p.handleCloseMsg(closeMsg) - // A link has finished draining the HTLCs from a channel we're - // cooperatively closing, so we can now start fee negotiation. - case cid := <-p.chanCloseFlushed: - p.handleChanFlushed(cid) - // The channel reannounce delay has elapsed, broadcast the // reenabled channel updates to the network. This should only // fire once, so we set the reenableTimeout channel to nil to @@ -3653,26 +3328,24 @@ func chooseDeliveryScript(upfront, requested lnwire.DeliveryAddress, func (p *Brontide) restartCoopClose(lnChan *lnwallet.LightningChannel) ( *lnwire.Shutdown, error) { - // If this channel has status ChanStatusCoopBroadcasted and a closing - // transaction was recorded, we just need to rebroadcast (handled by - // the chain arbitrator) and exit. If the status is set but no closing - // tx exists, fall through and re-drive the close negotiation via - // ShutdownInfo or LocalUpfrontShutdownScript. - // - // BOLT#2 requires that we retransmit Shutdown exactly, but doing so - // would mean persisting the RPC-provided close script. Instead use - // the LocalUpfrontShutdownScript or generate a script. + isTaprootChan := lnChan.ChanType().IsTaproot() + + // If this channel has status ChanStatusCoopBroadcasted and does not + // have a closing transaction, then the cooperative close process was + // started but never finished. We'll re-create the chanCloser state + // machine and resend Shutdown. BOLT#2 requires that we retransmit + // Shutdown exactly, but doing so would mean persisting the RPC + // provided close script. Instead use the LocalUpfrontShutdownScript + // or generate a script. c := lnChan.State() _, err := c.BroadcastedCooperative() - - // Any error other than "no close tx" is a real failure. - if err != nil && !errors.Is(err, channeldb.ErrNoCloseTx) { + if err != nil && err != channeldb.ErrNoCloseTx { + // An error other than ErrNoCloseTx was encountered. return nil, err - } - - // A close tx was already broadcast and this channel can't use RBF - // coop close, so all we can do is wait for it to confirm. - if err == nil && !p.rbfCoopCloseAllowed(c.ChanType) { + } else if err == nil && !p.rbfCoopCloseAllowed() { + // This is a channel that doesn't support RBF coop close, and it + // already had a coop close txn broadcast. As a result, we can + // just exit here as all we can do is wait for it to confirm. return nil, nil } @@ -3707,10 +3380,10 @@ func (p *Brontide) restartCoopClose(lnChan *lnwallet.LightningChannel) ( } } - // If the new RBF co-op close is negotiated and usable for this - // channel, then we'll init and start that state machine, skipping the - // steps for the negotiate machine below. - if p.rbfCoopCloseAllowed(c.ChanType) { + // If the new RBF co-op close is negotiated, then we'll init and start + // that state machine, skipping the steps for the negotiate machine + // below. We don't support this close type for taproot channels though. + if p.rbfCoopCloseAllowed() && !isTaprootChan { _, err := p.initRbfChanCloser(lnChan) if err != nil { return nil, fmt.Errorf("unable to init rbf chan "+ @@ -3762,7 +3435,7 @@ func (p *Brontide) restartCoopClose(lnChan *lnwallet.LightningChannel) ( shutdownMsg, err := chanCloser.ShutdownChan() if err != nil { p.log.Errorf("unable to create shutdown message: %v", err) - p.deleteActiveChanCloser(chanID, c.FundingOutpoint) + p.activeChanCloses.Delete(chanID) return nil, err } @@ -3867,7 +3540,7 @@ func (p *Brontide) initNegotiateChanCloser(req *htlcswitch.ChanClose, // back to its normal state. defer channel.ResetState() - p.deleteActiveChanCloser(chanID, channel.ChannelPoint()) + p.activeChanCloses.Delete(chanID) return fmt.Errorf("unable to shutdown channel: %w", err) } @@ -3896,9 +3569,9 @@ func (p *Brontide) initNegotiateChanCloser(req *htlcswitch.ChanClose, return nil } -// ChooseAddr returns the provided address if it is non-zero length, otherwise +// chooseAddr returns the provided address if it is non-zero length, otherwise // None. -func ChooseAddr(addr lnwire.DeliveryAddress) fn.Option[lnwire.DeliveryAddress] { +func chooseAddr(addr lnwire.DeliveryAddress) fn.Option[lnwire.DeliveryAddress] { if len(addr) == 0 { return fn.None[lnwire.DeliveryAddress]() } @@ -4038,9 +3711,7 @@ func (p *Brontide) observeRbfCloseUpdates(chanCloser *chancloser.RbfChanCloser, chanID := lnwire.NewChanIDFromOutPoint( *closeReq.ChanPoint, ) - p.deleteActiveChanCloser( - chanID, *closeReq.ChanPoint, - ) + p.activeChanCloses.Delete(chanID) return } @@ -4114,9 +3785,7 @@ func (c *chanErrorReporter) ReportError(chanErr error) { } if _, err := c.peer.initRbfChanCloser(lnChan); err != nil { - c.peer.deleteActiveChanCloser( - c.chanID, lnChan.ChannelPoint(), - ) + c.peer.activeChanCloses.Delete(c.chanID) c.peer.log.Errorf("unable to init RBF chan closer after "+ "error case: %v", err) @@ -4159,6 +3828,7 @@ func (p *Brontide) chanFlushEventSentinel(chanCloser *chancloser.RbfChanCloser, ctx := context.Background() chanCloser.SendEvent(ctx, &chancloser.ChannelFlushed{ ShutdownBalances: chanBalances, + FreshFlush: true, }) } @@ -4198,15 +3868,6 @@ func (p *Brontide) chanFlushEventSentinel(chanCloser *chancloser.RbfChanCloser, func (p *Brontide) initRbfChanCloser( channel *lnwallet.LightningChannel) (*chancloser.RbfChanCloser, error) { - // Aux channels can't use the RBF coop close flow, as the state - // machine doesn't invoke the aux closer hooks needed to construct an - // aux-aware close transaction. - if channel.ChanType().HasTapscriptRoot() { - return nil, fmt.Errorf("ChannelPoint(%v): RBF coop close "+ - "not supported for aux channels", - channel.ChannelPoint()) - } - chanID := lnwire.NewChanIDFromOutPoint(channel.ChannelPoint()) link := p.fetchLinkFromKeyAndCid(chanID) @@ -4231,18 +3892,7 @@ func (p *Brontide) initRbfChanCloser( peerPub := *p.IdentityKey() msgMapper := chancloser.NewRbfMsgMapper( - func() uint32 { - _, height, err := p.cfg.ChainIO.GetBestBlock() - if err != nil { - peerLog.Errorf("Unable to get best block "+ - "height: %v", err) - - return uint32(startingHeight) - } - - return uint32(height) - }, - chanID, peerPub, + uint32(startingHeight), chanID, peerPub, ) initialState := chancloser.ChannelActive{} @@ -4260,10 +3910,10 @@ func (p *Brontide) initRbfChanCloser( ChanType: channel.ChanType(), DefaultFeeRate: defaultFeePerKw.FeePerVByte(), ThawHeight: fn.Some(thawHeight), - RemoteUpfrontShutdown: ChooseAddr( + RemoteUpfrontShutdown: chooseAddr( channel.RemoteUpfrontShutdownScript(), ), - LocalUpfrontShutdown: ChooseAddr( + LocalUpfrontShutdown: chooseAddr( channel.LocalUpfrontShutdownScript(), ), NewDeliveryScript: func() (lnwire.DeliveryAddress, error) { @@ -4276,14 +3926,6 @@ func (p *Brontide) initRbfChanCloser( ), } - // For taproot channels, we need to set both LocalMusigSession and - // RemoteMusigSession to handle nonce exchange during RBF cooperative - // close. - if channel.ChanType().IsTaproot() { - env.LocalMusigSession = NewMusigChanCloser(channel) - env.RemoteMusigSession = NewMusigChanCloser(channel) - } - spendEvent := protofsm.RegisterSpend[chancloser.ProtocolEvent]{ OutPoint: channel.ChannelPoint(), PkScript: channel.FundingTxOut().PkScript, @@ -4328,30 +3970,8 @@ func (p *Brontide) initRbfChanCloser( "close: %w", err) } - // We store the closer first so that any lookups that race with actor - // registration will find the chan closer already in place. p.activeChanCloses.Store(chanID, makeRbfCloser(&chanCloser)) - // In addition to the message router, we'll register the state machine - // with the actor system. - if p.cfg.ActorSystem != nil { - p.log.Infof("Registering RBF actor for channel %v", - channel.ChannelPoint()) - - actorWrapper := newRbfCloseActor( - channel.ChannelPoint(), p, p.cfg.ActorSystem, - ) - if err := actorWrapper.registerActor(); err != nil { - chanCloser.Stop() - p.deleteActiveChanCloser( - chanID, channel.ChannelPoint(), - ) - - return nil, fmt.Errorf("unable to register RBF close "+ - "actor: %w", err) - } - } - // Now that we've created the rbf closer state machine, we'll launch a // new goroutine to eventually send in the ChannelFlushed event once // needed. @@ -4614,10 +4234,11 @@ func (p *Brontide) handleLocalCloseReq(req *htlcswitch.ChanClose) { "unknown", chanID) p.log.Errorf(err.Error()) req.Err <- err - return } + isTaprootChan := channel.ChanType().IsTaproot() + switch req.CloseType { // A type of CloseRegular indicates that the user has opted to close // out this channel on-chain, so we execute the cooperative channel @@ -4630,7 +4251,9 @@ func (p *Brontide) handleLocalCloseReq(req *htlcswitch.ChanClose) { // iteration, in which case we'll be obtaining a new // transaction w/ a higher fee rate. // - case p.rbfCoopCloseAllowed(channel.ChanType()): + // We don't support this close type for taproot channels yet + // however. + case !isTaprootChan && p.rbfCoopCloseAllowed(): err = p.startRbfChanCloser( newRPCShutdownInit(req), channel.ChannelPoint(), ) @@ -4783,12 +4406,9 @@ func (p *Brontide) finalizeChanClosure(chanCloser *chancloser.ChanCloser) { chanPoint := chanCloser.Channel().ChannelPoint() p.WipeChannel(&chanPoint) - // Also clear the activeChanCloses map of this channel, and unregister - // any RBF close actor that was registered for this channel point. - // - // TODO(roasbeef): existing race. + // Also clear the activeChanCloses map of this channel. cid := lnwire.NewChanIDFromOutPoint(chanPoint) - p.deleteActiveChanCloser(cid, chanPoint) + p.activeChanCloses.Delete(cid) // TODO(roasbeef): existing race // Next, we'll launch a goroutine which will request to be notified by // the ChainNotifier once the closure transaction obtains a single @@ -4824,22 +4444,9 @@ func (p *Brontide) finalizeChanClosure(chanCloser *chancloser.ChanCloser) { localOut := chanCloser.LocalCloseOutput() remoteOut := chanCloser.RemoteCloseOutput() auxOut := chanCloser.AuxOutputs() - - // Determine the number of confirmations to wait before signaling a - // successful cooperative close, scaled by channel capacity (see - // CloseConfsForCapacity). Check if we have a config override for - // testing purposes. - chanCapacity := chanCloser.Channel().Capacity - numConfs := p.cfg.ChannelCloseConfs.UnwrapOrFunc(func() uint32 { - // No override, use normal capacity-based scaling. - return lnwallet.CloseConfsForCapacity(chanCapacity) - }) - - // Register for full confirmation to send the final update. - closeScript := closingTx.TxOut[0].PkScript go WaitForChanToClose( chanCloser.NegotiationHeight(), notifier, errChan, - &chanPoint, &closingTxid, closeScript, numConfs, func() { + &chanPoint, &closingTxid, closingTx.TxOut[0].PkScript, func() { // Respond to the local subsystem which requested the // channel closure. if closeReq != nil { @@ -4862,14 +4469,14 @@ func (p *Brontide) finalizeChanClosure(chanCloser *chancloser.ChanCloser) { // the function, then it will be sent over the errChan. func WaitForChanToClose(bestHeight uint32, notifier chainntnfs.ChainNotifier, errChan chan error, chanPoint *wire.OutPoint, - closingTxID *chainhash.Hash, closeScript []byte, numConfs uint32, - cb func()) { + closingTxID *chainhash.Hash, closeScript []byte, cb func()) { peerLog.Infof("Waiting for confirmation of close of ChannelPoint(%v) "+ "with txid: %v", chanPoint, closingTxID) + // TODO(roasbeef): add param for num needed confs confNtfn, err := notifier.RegisterConfirmationsNtfn( - closingTxID, closeScript, numConfs, bestHeight, + closingTxID, closeScript, 1, bestHeight, ) if err != nil { if errChan != nil { @@ -4900,10 +4507,7 @@ func WaitForChanToClose(bestHeight uint32, notifier chainntnfs.ChainNotifier, func (p *Brontide) WipeChannel(chanPoint *wire.OutPoint) { chanID := lnwire.NewChanIDFromOutPoint(*chanPoint) - // Remove the entry and adjust the active-channel counter atomically - // via the helper; it skips the decrement for pending (nil) entries - // since they never contributed to the counter in the first place. - p.removeActiveChannel(chanID) + p.activeChannels.Delete(chanID) // Instruct the HtlcSwitch to close this link as the channel is no // longer active. @@ -5320,7 +4924,21 @@ func (p *Brontide) handleCloseMsg(msg *closeMsg) { chanCloser = c }) - handleErr := p.negotiateCloseErrHandler(msg.cid, chanCloser) + handleErr := func(err error) { + err = fmt.Errorf("unable to process close msg: %w", err) + p.log.Error(err) + + // As the negotiations failed, we'll reset the channel state + // machine to ensure we act to on-chain events as normal. + chanCloser.Channel().ResetState() + if chanCloser.CloseRequest() != nil { + chanCloser.CloseRequest().Err <- err + } + + p.activeChanCloses.Delete(msg.cid) + + p.Disconnect(err) + } // Next, we'll process the next message using the target state machine. // We'll either continue negotiation, or halt. @@ -5362,34 +4980,30 @@ func (p *Brontide) handleCloseMsg(msg *closeMsg) { }) }) - // Without a link there's no commitment traffic left to drain, - // so the channel is already flushed as far as we're concerned. - if link == nil { - p.beginNegotiation(chanCloser, handleErr) + beginNegotiation := func() { + oClosingSigned, err := chanCloser.BeginNegotiation() + if err != nil { + handleErr(err) + return + } - return + oClosingSigned.WhenSome(func(msg lnwire.ClosingSigned) { + p.queueMsg(&msg, nil) + }) } - // Otherwise, we register a flush hook so we hear about it once - // the link finishes draining. - link.OnFlushedOnce(func() { - // Remove link in goroutine to prevent deadlock. - go p.cfg.Switch.RemoveLink(msg.cid) - - // The link runs this hook on its own goroutine, and may - // well hold its lock while it does, so we hand the - // channel to the channelManager instead of advancing - // the closer from here. That keeps the state machine - // owned by a single goroutine, and it means we can't - // block the link on work the channelManager is doing, - // which may itself be waiting on the link's lock. - go func() { - select { - case p.chanCloseFlushed <- msg.cid: - case <-p.cg.Done(): - } - }() - }) + if link == nil { + beginNegotiation() + } else { + // Now we register a flush hook to advance the + // ChanCloser and possibly send out a ClosingSigned + // when the link finishes draining. + link.OnFlushedOnce(func() { + // Remove link in goroutine to prevent deadlock. + go p.cfg.Switch.RemoveLink(msg.cid) + beginNegotiation() + }) + } case *lnwire.ClosingSigned: oClosingSigned, err := chanCloser.ReceiveClosingSigned(*typed) @@ -5406,73 +5020,6 @@ func (p *Brontide) handleCloseMsg(msg *closeMsg) { panic("impossible closeMsg type") } - p.maybeFinalizeChanClosure(chanCloser) -} - -// handleChanFlushed is called once a link has drained the HTLCs from a channel -// we're cooperatively closing, which is our cue to move the negotiation along. -// The link notices the flush from its own goroutine and hands the channel to us -// over chanCloseFlushed, so that the closer only ever advances here. -// -// NOTE: MUST be called from the channelManager goroutine. -func (p *Brontide) handleChanFlushed(cid lnwire.ChannelID) { - // We deliberately don't go through fetchActiveChanCloser here, as that - // would build a fresh closer if the negotiation has already been torn - // down while we were waiting on the link. - chanCloserE, found := p.activeChanCloses.Load(cid) - if !found { - p.log.Debugf("ChannelID(%v) flushed, but no chan closer is "+ - "active", cid) - - return - } - - // The RBF closer drives its own flush handling, so there's nothing for - // us to do if that's the one closing this channel. - if chanCloserE.IsRight() { - return - } - - var chanCloser *chancloser.ChanCloser - chanCloserE.WhenLeft(func(c *chancloser.ChanCloser) { - chanCloser = c - }) - - p.beginNegotiation( - chanCloser, p.negotiateCloseErrHandler(cid, chanCloser), - ) -} - -// beginNegotiation starts the fee negotiation phase of a legacy cooperative -// close, sending out our opening offer if it falls to us to make one, and wraps -// the closure up if the negotiation ran all the way through to a broadcast -// transaction. -// -// NOTE: MUST be called from the channelManager goroutine. -func (p *Brontide) beginNegotiation(chanCloser *chancloser.ChanCloser, - handleErr func(error)) { - - oClosingSigned, err := chanCloser.BeginNegotiation() - if err != nil { - handleErr(err) - - return - } - - oClosingSigned.WhenSome(func(msg lnwire.ClosingSigned) { - p.queueMsg(&msg, nil) - }) - - p.maybeFinalizeChanClosure(chanCloser) -} - -// maybeFinalizeChanClosure wraps up a cooperative closure if the negotiation -// has run to completion, and does nothing if it hasn't. -// -// NOTE: MUST be called from the channelManager goroutine. -func (p *Brontide) maybeFinalizeChanClosure( - chanCloser *chancloser.ChanCloser) { - // If we haven't finished close negotiations, then we'll continue as we // can't yet finalize the closure. if _, err := chanCloser.ClosingTx(); err != nil { @@ -5485,32 +5032,6 @@ func (p *Brontide) maybeFinalizeChanClosure( p.finalizeChanClosure(chanCloser) } -// negotiateCloseErrHandler returns the function used to tear down a legacy -// close negotiation once one of the steps we drive it through has failed. -// -// NOTE: MUST be called from the channelManager goroutine. -func (p *Brontide) negotiateCloseErrHandler(cid lnwire.ChannelID, - chanCloser *chancloser.ChanCloser) func(error) { - - return func(err error) { - err = fmt.Errorf("unable to process close msg: %w", err) - p.log.Error(err) - - // As the negotiations failed, we'll reset the channel state - // machine to ensure we act to on-chain events as normal. - chanCloser.Channel().ResetState() - if chanCloser.CloseRequest() != nil { - chanCloser.CloseRequest().Err <- err - } - - p.deleteActiveChanCloser( - cid, chanCloser.Channel().ChannelPoint(), - ) - - p.Disconnect(err) - } -} - // HandleLocalCloseChanReqs accepts a *htlcswitch.ChanClose and passes it onto // the channelManager goroutine, which will shut down the link and possibly // close the channel. @@ -5614,7 +5135,7 @@ func (p *Brontide) attachChannelEventSubscription() error { // updateNextRevocation updates the existing channel's next revocation if it's // nil. -func (p *Brontide) updateNextRevocation(c *chanstate.OpenChannel) error { +func (p *Brontide) updateNextRevocation(c *channeldb.OpenChannel) error { chanPoint := c.FundingOutpoint chanID := lnwire.NewChanIDFromOutPoint(chanPoint) @@ -5656,7 +5177,7 @@ func (p *Brontide) updateNextRevocation(c *chanstate.OpenChannel) error { } // addActiveChannel adds a new active channel to the `activeChannels` map. It -// takes a `chanstate.OpenChannel`, creates a `lnwallet.LightningChannel` from +// takes a `channeldb.OpenChannel`, creates a `lnwallet.LightningChannel` from // it and assembles it with a channel link. func (p *Brontide) addActiveChannel(c *lnpeer.NewChannel) error { chanPoint := c.FundingOutpoint @@ -5687,16 +5208,6 @@ func (p *Brontide) addActiveChannel(c *lnpeer.NewChannel) error { chanOpts = append(chanOpts, lnwallet.WithAuxResolver(s)) }) - p.cfg.AuxTrafficShaper.WhenSome( - func(ts htlcswitch.AuxTrafficShaper) { - val := p.createHtlcValidator(c.OpenChannel, ts) - chanOpts = append( - chanOpts, - lnwallet.WithAuxHtlcValidator(val), - ) - }, - ) - // If not already active, we'll add this channel to the set of active // channels, so we can look it up later easily according to its channel // ID. @@ -5707,11 +5218,8 @@ func (p *Brontide) addActiveChannel(c *lnpeer.NewChannel) error { return fmt.Errorf("unable to create LightningChannel: %w", err) } - // Install the channel via the helper so the active-channel counter - // stays in lockstep: new inserts and pending-to-active promotions - // both bump the counter by exactly one, while the rare - // already-present case is a no-op. - p.storeActiveChannel(chanID, lnChan) + // Store the channel in the activeChannels map. + p.activeChannels.Store(chanID, lnChan) p.log.Infof("New channel active ChannelPoint(%v) with peer", chanPoint) @@ -5741,9 +5249,12 @@ func (p *Brontide) addActiveChannel(c *lnpeer.NewChannel) error { "peer", chanPoint) } - // We're using the old co-op close for this channel, so we don't need - // to init the new RBF chan closer. - if !p.rbfCoopCloseAllowed(lnChan.ChanType()) { + isTaprootChan := c.ChanType.IsTaproot() + + // We're using the old co-op close, so we don't need to init the new RBF + // chan closer. If this is a taproot channel, then we'll also fall + // through, as we don't support this type yet w/ rbf close. + if !p.rbfCoopCloseAllowed() || isTaprootChan { return nil } @@ -5754,7 +5265,7 @@ func (p *Brontide) addActiveChannel(c *lnpeer.NewChannel) error { // Creating this here ensures that any shutdown messages sent will be // automatically routed by the msg router. if _, err := p.initRbfChanCloser(lnChan); err != nil { - p.deleteActiveChanCloser(chanID, lnChan.ChannelPoint()) + p.activeChanCloses.Delete(chanID) return fmt.Errorf("unable to init RBF chan closer for new "+ "chan: %w", err) @@ -5834,7 +5345,7 @@ func (p *Brontide) handleNewPendingChannel(req *newChannelMsg) { // This is a new channel, we now add it to the map `activeChannels` // with nil value and mark it as a newly added channel in // `addedChannels`. - p.markPendingChannel(chanID) + p.activeChannels.Store(chanID, nil) p.addedChannels.Store(chanID, struct{}{}) } @@ -5861,16 +5372,8 @@ func (p *Brontide) handleRemovePendingChannel(req *newChannelMsg) { p.log.Warnf("Channel(%v) not found, removing it anyway", chanID) } - // Delete the pending entry. handleRemovePendingChannel and - // handleNewActiveChannel are both arms of the channelManager - // select loop, so the Go runtime serializes them and the entry we - // delete here is guaranteed to be the pending (nil) one we stored - // via markPendingChannel — it cannot have been promoted behind our - // back. That rules out any numActiveChans decrement on this path, - // so we drop the defensive LoadAndDelete + conditional check the - // refactor left in place and use a plain Delete via - // deletePendingChannel. - p.deletePendingChannel(chanID) + // Remove the record of this pending channel. + p.activeChannels.Delete(chanID) p.addedChannels.Delete(chanID) } @@ -5911,96 +5414,6 @@ func (p *Brontide) scaleTimeout(timeout time.Duration) time.Duration { return timeout } -// auxHtlcValidator implements lnwallet.AuxHtlcValidator by checking HTLC -// bandwidth against the traffic shaper. -type auxHtlcValidator struct { - peer *Brontide - dbChan *chanstate.OpenChannel - ts htlcswitch.AuxTrafficShaper -} - -// ValidateHtlc performs final aux balance validation before an HTLC is added -// to the channel state. It calls into the traffic shaper's PaymentBandwidth -// method to check external balance against the most up-to-date channel state, -// preventing race conditions where multiple HTLCs could be approved based on -// stale bandwidth. -func (v *auxHtlcValidator) ValidateHtlc(amount, - linkBandwidth lnwire.MilliSatoshi, - customRecords lnwire.CustomRecords, - view lnwallet.AuxHtlcView) error { - - // Get the short channel ID for logging. - scid := v.dbChan.ShortChannelID - - // Extract the HTLC custom records to pass to the traffic shaper. - var htlcBlob fn.Option[tlv.Blob] - if len(customRecords) > 0 { - blob, err := customRecords.Serialize() - if err != nil { - return fmt.Errorf("unable to serialize "+ - "custom records: %w", err) - } - htlcBlob = fn.Some(blob) - } - - // Get the funding and commitment blobs for this channel. - fundingBlob := v.dbChan.CustomBlob - commitmentBlob := v.dbChan.LocalCommitment.CustomBlob - - // Check if this channel should be handled by the traffic shaper. If - // not, we skip the aux validation entirely and allow the HTLC to - // proceed through normal validation. - shouldHandle, err := v.ts.ShouldHandleTraffic( - scid, fundingBlob, htlcBlob, - ) - if err != nil { - return fmt.Errorf("traffic shaper failed to decide "+ - "whether to handle traffic: %w", err) - } - if !shouldHandle { - return nil - } - - peer := route.NewVertex(v.peer.IdentityKey()) - - // Call the traffic shaper's PaymentBandwidth method with the current - // state. This performs the same bandwidth checks as during - // pathfinding/forwarding, but against the absolute latest channel - // state. - // - // The linkBandwidth is provided by the channel and represents the - // current available balance, which is used by the traffic shaper to - // ensure we don't dip below channel reserves. - bandwidth, err := v.ts.PaymentBandwidth( - fundingBlob, htlcBlob, commitmentBlob, - linkBandwidth, amount, view, peer, - ) - if err != nil { - return fmt.Errorf("traffic shaper bandwidth check "+ - "failed: %w", err) - } - - if amount > bandwidth { - return fmt.Errorf("insufficient aux bandwidth: "+ - "need %v, have %v (scid=%v)", amount, - bandwidth, scid) - } - - return nil -} - -// createHtlcValidator creates an HTLC validator that performs final aux balance -// validation before HTLCs are added to the channel state. -func (p *Brontide) createHtlcValidator(dbChan *chanstate.OpenChannel, - ts htlcswitch.AuxTrafficShaper) lnwallet.AuxHtlcValidator { - - return &auxHtlcValidator{ - peer: p, - dbChan: dbChan, - ts: ts, - } -} - // CoopCloseUpdates is a struct used to communicate updates for an active close // to the caller. type CoopCloseUpdates struct { @@ -6020,3 +5433,42 @@ func (p *Brontide) ChanHasRbfCoopCloser(chanPoint wire.OutPoint) bool { return chanCloser.IsRight() } + +// TriggerCoopCloseRbfBump given a chan ID, and the params needed to trigger a +// new RBF co-op close update, a bump is attempted. A channel used for updates, +// along with one used to o=communicate any errors is returned. If no chan +// closer is found, then false is returned for the second argument. +func (p *Brontide) TriggerCoopCloseRbfBump(ctx context.Context, + chanPoint wire.OutPoint, feeRate chainfee.SatPerKWeight, + deliveryScript lnwire.DeliveryAddress) (*CoopCloseUpdates, error) { + + // If RBF coop close isn't permitted, then we'll an error. + if !p.rbfCoopCloseAllowed() { + return nil, fmt.Errorf("rbf coop close not enabled for " + + "channel") + } + + closeUpdates := &CoopCloseUpdates{ + UpdateChan: make(chan interface{}, 1), + ErrChan: make(chan error, 1), + } + + // We'll re-use the existing switch struct here, even though we're + // bypassing the switch entirely. + closeReq := htlcswitch.ChanClose{ + CloseType: contractcourt.CloseRegular, + ChanPoint: &chanPoint, + TargetFeePerKw: feeRate, + DeliveryScript: deliveryScript, + Updates: closeUpdates.UpdateChan, + Err: closeUpdates.ErrChan, + Ctx: ctx, + } + + err := p.startRbfChanCloser(newRPCShutdownInit(&closeReq), chanPoint) + if err != nil { + return nil, err + } + + return closeUpdates, nil +} diff --git a/peer/brontide_test.go b/peer/brontide_test.go index c9dd25dbf..3d8023b1a 100644 --- a/peer/brontide_test.go +++ b/peer/brontide_test.go @@ -6,14 +6,12 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/address/v2" - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/htlcswitch" @@ -21,9 +19,6 @@ import ( "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chancloser" "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/routing/route" - "github.com/lightningnetwork/lnd/tlv" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -180,131 +175,6 @@ func TestPeerChannelClosureAcceptFeeResponder(t *testing.T) { notifier.ConfChan <- &chainntnfs.TxConfirmation{} } -// TestPeerChannelClosureFlushDrivesNegotiation checks that a legacy cooperative -// close holds off on fee negotiation until the link reports that the channel -// has drained, and that the report is what carries the negotiation forward. The -// link notices the flush on its own goroutine, so it hands the channel to the -// channelManager rather than advancing the closer itself. -func TestPeerChannelClosureFlushDrivesNegotiation(t *testing.T) { - t.Parallel() - - harness, err := createTestPeerWithChannel(t, noUpdate) - require.NoError(t, err, "unable to create test channels") - - var ( - alicePeer = harness.peer - bobChan = harness.channel - mockSwitch = harness.mockSwitch - broadcastTxChan = harness.publishTx - notifier = harness.notifier - ) - - chanPoint := bobChan.ChannelPoint() - chanID := lnwire.NewChanIDFromOutPoint(chanPoint) - - // The link holds on to the flush hook rather than running it inline, so - // we get to say when the channel looks drained. - mockLink := newDeferredFlushUpdateHandler(chanID) - mockSwitch.links = append(mockSwitch.links, mockLink) - - dummyDeliveryScript := genScript(t, p2wshAddress) - - // We send a shutdown request to Alice, and expect her own Shutdown in - // response. - alicePeer.chanCloseMsgs <- &closeMsg{ - cid: chanID, - msg: lnwire.NewShutdown(chanID, dummyDeliveryScript), - } - - var msg lnwire.Message - select { - case outMsg := <-alicePeer.outgoingQueue: - msg = outMsg.msg - case <-time.After(timeout): - t.Fatalf("did not receive shutdown message") - } - - shutdownMsg, ok := msg.(*lnwire.Shutdown) - require.True(t, ok, "expected Shutdown message, got %T", msg) - - respDeliveryScript := shutdownMsg.Address - - // The channel hasn't drained yet, so Alice shouldn't have opened fee - // negotiation, even though she's the one that funded the channel. - select { - case outMsg := <-alicePeer.outgoingQueue: - t.Fatalf("negotiation started before the channel flushed: %T", - outMsg.msg) - - case <-time.After(shortTimeout): - } - - // A flush report for a channel we have no closer for should be dropped - // on the floor rather than start anything. - var unknownChanID lnwire.ChannelID - select { - case alicePeer.chanCloseFlushed <- unknownChanID: - case <-time.After(timeout): - t.Fatalf("channelManager not reading flush reports") - } - - // Now we let the link report the flush, which is what should carry the - // negotiation into its fee phase. - select { - case hook := <-mockLink.flushHooks: - go hook() - case <-time.After(timeout): - t.Fatalf("no flush hook was registered") - } - - select { - case outMsg := <-alicePeer.outgoingQueue: - msg = outMsg.msg - case <-time.After(timeout): - t.Fatalf("did not receive ClosingSigned message") - } - - respClosingSigned, ok := msg.(*lnwire.ClosingSigned) - require.True(t, ok, "expected ClosingSigned message, got %T", msg) - - // We accept the fee, and send a ClosingSigned with the same fee back so - // she knows we agreed. - aliceFee := respClosingSigned.FeeSatoshis - bobSig, _, _, err := bobChan.CreateCloseProposal( - aliceFee, dummyDeliveryScript, respDeliveryScript, - ) - require.NoError(t, err, "error creating close proposal") - - parsedSig, err := lnwire.NewSigFromSignature(bobSig) - require.NoError(t, err, "error parsing signature") - - alicePeer.chanCloseMsgs <- &closeMsg{ - cid: chanID, - msg: lnwire.NewClosingSigned(chanID, aliceFee, parsedSig), - } - - // Alice should now see that we agreed on the fee, and broadcast the - // closing transaction. - select { - case <-broadcastTxChan: - case <-time.After(timeout): - t.Fatalf("closing tx not broadcast") - } - - // Need to pull the remaining message off of Alice's outgoing queue. - select { - case outMsg := <-alicePeer.outgoingQueue: - msg = outMsg.msg - case <-time.After(timeout): - t.Fatalf("did not receive ClosingSigned message") - } - _, ok = msg.(*lnwire.ClosingSigned) - require.True(t, ok, "expected ClosingSigned message, got %T", msg) - - // Alice should be waiting in a goroutine for a confirmation. - notifier.ConfChan <- &chainntnfs.TxConfirmation{} -} - // TestPeerChannelClosureAcceptFeeInitiator tests the shutdown initiator's // behavior if we can agree on the fee immediately. func TestPeerChannelClosureAcceptFeeInitiator(t *testing.T) { @@ -863,6 +733,7 @@ func TestChooseDeliveryScript(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { script, err := chooseDeliveryScript( @@ -890,7 +761,7 @@ func TestCustomShutdownScript(t *testing.T) { // setShutdown is a function which sets the upfront shutdown address for // the local channel. - setShutdown := func(a, b *chanstate.OpenChannel) { + setShutdown := func(a, b *channeldb.OpenChannel) { a.LocalShutdownScript = script b.RemoteShutdownScript = script } @@ -900,7 +771,7 @@ func TestCustomShutdownScript(t *testing.T) { // update is a function used to set values on the channel set up for the // test. It is used to set values for upfront shutdown addresses. - update func(a, b *chanstate.OpenChannel) + update func(a, b *channeldb.OpenChannel) // userCloseScript is the address specified by the user. userCloseScript lnwire.DeliveryAddress @@ -942,6 +813,7 @@ func TestCustomShutdownScript(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { // Open a channel. @@ -1109,6 +981,7 @@ func TestStaticRemoteDowngrade(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { params := createTestPeer(t) @@ -1137,10 +1010,11 @@ func TestStaticRemoteDowngrade(t *testing.T) { // genScript creates a script paying out to the address provided, which must // be a valid address. -func genScript(t *testing.T, addr string) lnwire.DeliveryAddress { +func genScript(t *testing.T, address string) lnwire.DeliveryAddress { // Generate an address which can be used for testing. - deliveryAddr, err := address.DecodeAddress( - addr, &chaincfg.TestNet3Params, + deliveryAddr, err := btcutil.DecodeAddress( + address, + &chaincfg.TestNet3Params, ) require.NoError(t, err, "invalid delivery address") @@ -1195,96 +1069,6 @@ func TestPeerCustomMessage(t *testing.T) { require.Equal(t, receivedCustomMsg, &receivedCustom.msg) } -// TestPeerIgnoresPingWithoutPongReply ensures we keep the connection alive for -// pings using the BOLT 1 no-reply sentinel range. -func TestPeerIgnoresPingWithoutPongReply(t *testing.T) { - t.Parallel() - - // Arrange: Start a peer using the mock connection so we can - // inject incoming pings and observe any outgoing responses. - params := createTestPeer(t) - - var ( - mockConn = params.mockConn - alicePeer = params.peer - ) - - startPeerDone := startPeer(t, mockConn, alicePeer) - _, err := fn.RecvOrTimeout(startPeerDone, 2*timeout) - require.NoError(t, err) - - writePing := func(msg *lnwire.Ping) { - t.Helper() - - var b bytes.Buffer - _, err := lnwire.WriteMessage(&b, msg, 0) - require.NoError(t, err) - - select { - case mockConn.readMessages <- b.Bytes(): - case <-time.After(timeout): - t.Fatal("timeout sending ping to peer") - } - } - - // Act: Deliver a ping in the BOLT 1 no-reply range. - ignoredPayload := []byte{1, 2, 3} - writePing(&lnwire.Ping{ - NumPongBytes: 65535, - PaddingBytes: ignoredPayload, - }) - - // Assert: The peer records the latest ping payload for observability. - require.Eventually(t, func() bool { - return bytes.Equal( - alicePeer.LastRemotePingPayload(), ignoredPayload, - ) - }, timeout, 10*time.Millisecond) - - // Assert: No pong is sent for the no-reply sentinel range. - select { - case rawMsg := <-mockConn.writtenMessages: - t.Fatalf("expected no pong reply, got %x", rawMsg) - case <-time.After(100 * time.Millisecond): - } - - // Act: Send a normal ping afterward to prove the peer - // stayed connected and still handles standard ping/pong - // traffic. - writePing(&lnwire.Ping{NumPongBytes: 1}) - - rawMsg, err := fn.RecvOrTimeout(mockConn.writtenMessages, timeout) - require.NoError(t, err) - - msg, err := lnwire.ReadMessage(bytes.NewReader(rawMsg), 0) - require.NoError(t, err) - - // Assert: The follow-up ping receives the requested pong reply. - pong, ok := msg.(*lnwire.Pong) - require.True(t, ok) - require.Len(t, pong.PongBytes, 1) -} - -// TestMessageSummaryPingIncludesNumPongBytes ensures the debug summary for a -// ping exposes the requested pong size, which makes ignored no-reply pings -// visible without requiring trace-level logging. -func TestMessageSummaryPingIncludesNumPongBytes(t *testing.T) { - t.Parallel() - - // Arrange: Build a ping that uses the BOLT 1 no-reply sentinel range. - msg := &lnwire.Ping{ - NumPongBytes: 65535, - PaddingBytes: []byte{1, 2, 3}, - } - - // Act: Generate the human-readable message summary. - summary := messageSummary(msg) - - // Assert: The summary includes both the requested pong size and payload - // length so debug logs can explain why no pong was sent. - require.Equal(t, "num_pong_bytes=65535, len(ping_bytes)=3", summary) -} - // TestUpdateNextRevocation checks that the method `updateNextRevocation` is // behave as expected. func TestUpdateNextRevocation(t *testing.T) { @@ -1347,8 +1131,8 @@ func assertMsgSent(t *testing.T, conn *mockMessageConn, func TestAlwaysSendChannelUpdate(t *testing.T) { require := require.New(t) - var channel *chanstate.OpenChannel - channelIntercept := func(a, b *chanstate.OpenChannel) { + var channel *channeldb.OpenChannel + channelIntercept := func(a, b *channeldb.OpenChannel) { channel = a } @@ -1410,6 +1194,7 @@ func TestHandleNewPendingChannel(t *testing.T) { } for _, tc := range testCases { + tc := tc // Create a request for testing. errChan := make(chan error, 1) @@ -1494,6 +1279,7 @@ func TestHandleRemovePendingChannel(t *testing.T) { } for _, tc := range testCases { + tc := tc // Create a request for testing. errChan := make(chan error, 1) @@ -1557,8 +1343,8 @@ func TestStartupWriteMessageRace(t *testing.T) { // createTestPeerWithChannel, so we can mark it borked below. // We can't mark it borked within the callback, since the channel hasn't // been saved to the DB yet when the callback executes. - var channel *chanstate.OpenChannel - getChannels := func(a, b *chanstate.OpenChannel) { + var channel *channeldb.OpenChannel + getChannels := func(a, b *channeldb.OpenChannel) { channel = a } @@ -1680,383 +1466,3 @@ func TestRemovePendingChannel(t *testing.T) { require.NoError(t, err) } - -// mockAuxTrafficShaper is a mock implementation of htlcswitch.AuxTrafficShaper -// for testing the createHtlcValidator function. -type mockAuxTrafficShaper struct { - mock.Mock -} - -// ShouldHandleTraffic returns the configured mock values. -func (m *mockAuxTrafficShaper) ShouldHandleTraffic( - cid lnwire.ShortChannelID, - fundingBlob, htlcBlob fn.Option[tlv.Blob]) (bool, error) { - - args := m.Called(cid, fundingBlob, htlcBlob) - return args.Bool(0), args.Error(1) -} - -// PaymentBandwidth returns the configured mock values. -func (m *mockAuxTrafficShaper) PaymentBandwidth(fundingBlob, htlcBlob, - commitmentBlob fn.Option[tlv.Blob], linkBandwidth, - htlcAmt lnwire.MilliSatoshi, htlcView lnwallet.AuxHtlcView, - peer route.Vertex) (lnwire.MilliSatoshi, error) { - - args := m.Called( - fundingBlob, htlcBlob, commitmentBlob, linkBandwidth, - htlcAmt, htlcView, peer, - ) - - bw, _ := args.Get(0).(lnwire.MilliSatoshi) - - return bw, args.Error(1) -} - -// ProduceHtlcExtraData is part of the AuxTrafficShaper interface. -func (m *mockAuxTrafficShaper) ProduceHtlcExtraData( - totalAmount lnwire.MilliSatoshi, - htlcCustomRecords lnwire.CustomRecords, - peer route.Vertex) (lnwire.MilliSatoshi, lnwire.CustomRecords, - error) { - - args := m.Called(totalAmount, htlcCustomRecords, peer) - - amt, _ := args.Get(0).(lnwire.MilliSatoshi) - records, _ := args.Get(1).(lnwire.CustomRecords) - - return amt, records, args.Error(2) -} - -// IsCustomHTLC is part of the AuxTrafficShaper interface. -func (m *mockAuxTrafficShaper) IsCustomHTLC( - htlcRecords lnwire.CustomRecords) bool { - - args := m.Called(htlcRecords) - return args.Bool(0) -} - -// Compile-time check that mockAuxTrafficShaper implements AuxTrafficShaper. -var _ htlcswitch.AuxTrafficShaper = (*mockAuxTrafficShaper)(nil) - -// TestCreateHtlcValidator tests that the HTLC validator created by -// createHtlcValidator respects the ShouldHandleTraffic check. When -// ShouldHandleTraffic returns false, the validator should return nil without -// calling PaymentBandwidth. -func TestCreateHtlcValidator(t *testing.T) { - t.Parallel() - - // Create a minimal Brontide with just the identity key set. - privKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - peer := &Brontide{ - cfg: Config{ - Addr: &lnwire.NetAddress{ - IdentityKey: privKey.PubKey(), - }, - }, - } - - // Create a mock channel with minimal required fields. - dbChan := &chanstate.OpenChannel{ - ShortChannelID: lnwire.NewShortChanIDFromInt(123), - } - - anyArg := mock.Anything - - testCases := []struct { - name string - setupMock func(*mockAuxTrafficShaper) - htlcAmount lnwire.MilliSatoshi - linkBw lnwire.MilliSatoshi - expectError bool - }{ - { - name: "non-custom channel skips check", - setupMock: func(m *mockAuxTrafficShaper) { - m.On( - "ShouldHandleTraffic", - anyArg, anyArg, anyArg, - ).Return(false, nil) - }, - htlcAmount: 1000, - linkBw: 5000, - expectError: false, - }, - { - name: "sufficient bandwidth", - setupMock: func(m *mockAuxTrafficShaper) { - m.On( - "ShouldHandleTraffic", - anyArg, anyArg, anyArg, - ).Return(true, nil) - m.On( - "PaymentBandwidth", - anyArg, anyArg, anyArg, - anyArg, anyArg, anyArg, - anyArg, - ).Return( - lnwire.MilliSatoshi(10000), - nil, - ) - }, - htlcAmount: 1000, - linkBw: 5000, - expectError: false, - }, - { - name: "insufficient bandwidth", - setupMock: func(m *mockAuxTrafficShaper) { - m.On( - "ShouldHandleTraffic", - anyArg, anyArg, anyArg, - ).Return(true, nil) - m.On( - "PaymentBandwidth", - anyArg, anyArg, anyArg, - anyArg, anyArg, anyArg, - anyArg, - ).Return( - lnwire.MilliSatoshi(500), - nil, - ) - }, - htlcAmount: 1000, - linkBw: 5000, - expectError: true, - }, - { - name: "ShouldHandleTraffic error", - setupMock: func(m *mockAuxTrafficShaper) { - m.On( - "ShouldHandleTraffic", - anyArg, anyArg, anyArg, - ).Return( - false, - fmt.Errorf("shaper error"), - ) - }, - htlcAmount: 1000, - linkBw: 5000, - expectError: true, - }, - { - name: "PaymentBandwidth error", - setupMock: func(m *mockAuxTrafficShaper) { - m.On( - "ShouldHandleTraffic", - anyArg, anyArg, anyArg, - ).Return(true, nil) - m.On( - "PaymentBandwidth", - anyArg, anyArg, anyArg, - anyArg, anyArg, anyArg, - anyArg, - ).Return( - lnwire.MilliSatoshi(0), - fmt.Errorf("bandwidth error"), - ) - }, - htlcAmount: 1000, - linkBw: 5000, - expectError: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - m := &mockAuxTrafficShaper{} - tc.setupMock(m) - - validator := peer.createHtlcValidator( - dbChan, m, - ) - - err := validator.ValidateHtlc( - tc.htlcAmount, tc.linkBw, - nil, lnwallet.AuxHtlcView{}, - ) - - if tc.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) - } - - m.AssertExpectations(t) - }) - } -} - -// TestHasActiveChannels exercises the atomic active-channel counter that -// backs hasActiveChannels(). hasActiveChannels is on the hot path for -// every incoming onion message — the onion message ingress gate calls -// it per packet — so a correct, O(1) shadow of activeChannels is a -// load-bearing invariant. This test walks the three state transitions -// that have to keep numActiveChans in lockstep with activeChannels: -// initial emptiness, pending entries (nil values) that must not count, -// and pending-delete paths that must not decrement. -func TestHasActiveChannels(t *testing.T) { - t.Parallel() - - peer := NewBrontide(Config{}) - - // Initial state: no channels, counter is zero, gate is closed. - require.False(t, peer.hasActiveChannels()) - require.Equal(t, int32(0), peer.numActiveChans.Load()) - - // Simulate the loadActiveChannels active-channel path: the entry - // is stored and the counter is incremented in lockstep. After - // this, hasActiveChannels must flip to true because the peer now - // holds a non-pending channel. - activeID := lnwire.ChannelID{0x01} - peer.activeChannels.Store(activeID, &lnwallet.LightningChannel{}) - peer.numActiveChans.Add(1) - require.True(t, peer.hasActiveChannels()) - require.Equal(t, int32(1), peer.numActiveChans.Load()) - - // Simulate the loadActiveChannels pending path: the entry is - // stored as nil and the counter must NOT move. This is the - // invariant the onion message gate relies on — pending channels - // are cheap to open and get stuck, so they must not satisfy the - // Sybil-resistance gate on their own. - pendingID := lnwire.ChannelID{0x02} - peer.activeChannels.Store(pendingID, nil) - require.Equal(t, int32(1), peer.numActiveChans.Load()) - require.True(t, peer.hasActiveChannels()) - - // handleRemovePendingChannel walks the pending-delete path. It - // uses LoadAndDelete and must skip the counter decrement when - // the previous value was nil (pending). If this invariant ever - // broke, the counter would underflow every time a pending - // channel was cancelled and hasActiveChannels would return the - // wrong answer until the next reconnect. - errChan := make(chan error, 1) - peer.handleRemovePendingChannel(&newChannelMsg{ - channelID: pendingID, - err: errChan, - }) - require.Equal(t, int32(1), peer.numActiveChans.Load()) - require.True(t, peer.hasActiveChannels()) - - // The pending entry must have been removed from the map. - _, found := peer.activeChannels.Load(pendingID) - require.False(t, found) - - // Drain the request error channel so the test leaves no loose - // ends. handleRemovePendingChannel closes the err chan via - // defer, so we expect a closed-channel receive here. - _, reqOk := <-errChan - require.False(t, reqOk) - - // Finally, simulate WipeChannel's decrement path directly via - // LoadAndDelete. We cannot call WipeChannel in this - // dummy-config harness because it also calls - // p.cfg.Switch.RemoveLink, but the counter-maintenance half of - // WipeChannel is exactly the LoadAndDelete + conditional Add(-1) - // we exercise here. - prev, loaded := peer.activeChannels.LoadAndDelete(activeID) - require.True(t, loaded) - require.NotNil(t, prev) - peer.numActiveChans.Add(-1) - - require.False(t, peer.hasActiveChannels()) - require.Equal(t, int32(0), peer.numActiveChans.Load()) -} - -// TestRbfCoopCloseAllowed asserts that the per-channel RBF coop close -// predicate excludes aux channels (channel types carrying a tapscript root) -// even when both peers have negotiated the RBF coop close feature, while -// permitting it for all other channel types. -func TestRbfCoopCloseAllowed(t *testing.T) { - t.Parallel() - - newPeer := func(local, remote *lnwire.RawFeatureVector) *Brontide { - return &Brontide{ - cfg: Config{ - Features: lnwire.NewFeatureVector( - local, lnwire.Features, - ), - }, - remoteFeatures: lnwire.NewFeatureVector( - remote, lnwire.Features, - ), - } - } - - var ( - noBits = lnwire.NewRawFeatureVector() - rbfBit = lnwire.NewRawFeatureVector( - lnwire.RbfCoopCloseOptional, - ) - stagingBit = lnwire.NewRawFeatureVector( - lnwire.RbfCoopCloseOptionalStaging, - ) - - overlayChan = chanstate.SimpleTaprootFeatureBit | - chanstate.TapscriptRootBit - ) - - tests := []struct { - name string - peer *Brontide - chanType chanstate.ChannelType - allowed bool - }{ - { - name: "both signal, plain channel", - peer: newPeer(rbfBit, rbfBit), - chanType: chanstate.SingleFunderTweaklessBit, - allowed: true, - }, - { - name: "both signal staging, plain channel", - peer: newPeer(stagingBit, stagingBit), - chanType: chanstate.SingleFunderTweaklessBit, - allowed: true, - }, - { - name: "both signal, simple taproot channel", - peer: newPeer(rbfBit, rbfBit), - chanType: chanstate.SimpleTaprootFeatureBit, - allowed: true, - }, - { - name: "both signal, aux (overlay) channel", - peer: newPeer(rbfBit, rbfBit), - chanType: overlayChan, - allowed: false, - }, - { - name: "both signal staging, aux (overlay) channel", - peer: newPeer(stagingBit, stagingBit), - chanType: overlayChan, - allowed: false, - }, - { - name: "only local signals, plain channel", - peer: newPeer(rbfBit, noBits), - chanType: chanstate.SingleFunderTweaklessBit, - allowed: false, - }, - { - name: "neither signals, aux (overlay) channel", - peer: newPeer(noBits, noBits), - chanType: overlayChan, - allowed: false, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - require.Equal( - t, test.allowed, - test.peer.rbfCoopCloseAllowed( - test.chanType, - ), - ) - }) - } -} diff --git a/peer/chan_observer.go b/peer/chan_observer.go index 5482dd073..7570bcf4b 100644 --- a/peer/chan_observer.go +++ b/peer/chan_observer.go @@ -1,7 +1,7 @@ package peer import ( - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/htlcswitch" @@ -119,12 +119,7 @@ func (l *chanObserver) DisableOutgoingAdds() error { // MarkCoopBroadcasted persistently marks that the channel close transaction // has been broadcast. func (l *chanObserver) MarkCoopBroadcasted(tx *wire.MsgTx, local bool) error { - party := lntypes.Remote - if local { - party = lntypes.Local - } - - return l.chanView.MarkCoopBroadcasted(tx, party) + return l.chanView.MarkCoopBroadcasted(tx, lntypes.Local) } // MarkShutdownSent persists the given ShutdownInfo. The existence of the diff --git a/peer/daemon_adapters.go b/peer/daemon_adapters.go index dc14efd15..3c5bfe9f8 100644 --- a/peer/daemon_adapters.go +++ b/peer/daemon_adapters.go @@ -4,8 +4,8 @@ import ( "fmt" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/protofsm" diff --git a/peer/musig_chan_closer.go b/peer/musig_chan_closer.go index 78911ec5a..149ebcfa0 100644 --- a/peer/musig_chan_closer.go +++ b/peer/musig_chan_closer.go @@ -2,7 +2,6 @@ package peer import ( "fmt" - "io" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/lightningnetwork/lnd/fn/v2" @@ -54,7 +53,6 @@ func (m *MusigChanCloser) ProposalClosingOpts() ( *m.remoteNonce, localKey, remoteKey, m.channel.Signer, m.channel.FundingTxOut(), lnwallet.RemoteMusigCommit, tapscriptTweak, - fn.None[io.Reader](), ) err := m.musigSession.FinalizeSession(*m.localNonce) @@ -106,9 +104,13 @@ func (m *MusigChanCloser) CombineClosingOpts(localSig, return localMuSig, remoteMuSig, opts, nil } -// ClosingNonce generates a fresh nonce for our partial signature. A new nonce -// is generated on every call to prevent nonce reuse across RBF iterations. +// ClosingNonce returns the nonce that should be used when generating the our +// partial signature for the remote party. func (m *MusigChanCloser) ClosingNonce() (*musig2.Nonces, error) { + if m.localNonce != nil { + return m.localNonce, nil + } + localKey, _ := m.channel.MultiSigKeys() nonce, err := musig2.GenNonces( musig2.WithPublicKey(localKey.PubKey), @@ -128,14 +130,6 @@ func (m *MusigChanCloser) InitRemoteNonce(nonce *musig2.Nonces) { m.remoteNonce = nonce } -// InvalidateNonce clears the cached local nonce, forcing a fresh nonce to be -// generated on the next call to ClosingNonce. This prevents nonce reuse across -// RBF iterations. -func (m *MusigChanCloser) InvalidateNonce() { - m.localNonce = nil - m.musigSession = nil -} - // A compile-time assertion to ensure MusigChanCloser implements the // chancloser.MusigSession interface. var _ chancloser.MusigSession = (*MusigChanCloser)(nil) diff --git a/peer/musig_nonce_order_test.go b/peer/musig_nonce_order_test.go deleted file mode 100644 index 962223e08..000000000 --- a/peer/musig_nonce_order_test.go +++ /dev/null @@ -1,310 +0,0 @@ -package peer - -import ( - "bytes" - "testing" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/input" - "github.com/lightningnetwork/lnd/lnwallet" - "github.com/lightningnetwork/lnd/lnwallet/chainfee" - "github.com/lightningnetwork/lnd/lnwallet/chancloser" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/tlv" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" -) - -// TestRemoteCloseStartTaprootIntegration tests the full flow of -// RemoteCloseStart handling a ClosingComplete message with taproot signatures. -// -// This is a regression test for a bug where the remote nonce was not properly -// created before sending over our signature. -func TestRemoteCloseStartTaprootIntegration(t *testing.T) { - t.Parallel() - - chanType := channeldb.SingleFunderTweaklessBit | - channeldb.AnchorOutputsBit | channeldb.SimpleTaprootFeatureBit - - aliceChan, bobChan, err := lnwallet.CreateTestChannels(t, chanType) - require.NoError(t, err) - - // Create TWO SEPARATE MusigChanCloser instances. This is the key to - // exposing the bug - in production where the issue - // existed, they share one. - localSession := NewMusigChanCloser(aliceChan) - remoteSession := NewMusigChanCloser(bobChan) - - // Initialize local session with nonces (simulating LocalCloseStart path - // during shutdown exchange). - _, err = localSession.ClosingNonce() - require.NoError(t, err) - - // Give local session the remote's closee nonce. - remoteCloseeNonce, err := musig2.GenNonces( - musig2.WithPublicKey( - bobChan.State().LocalChanCfg.MultiSigKey.PubKey, - ), - ) - require.NoError(t, err) - localSession.InitRemoteNonce(remoteCloseeNonce) - - // For remoteSession, generate the Local nonce (simulating what would - // happen during the shutdown exchange when we act as closee). - _, err = remoteSession.ClosingNonce() - require.NoError(t, err) - - // NOTE: remoteSession has its local nonce but NO remote nonce yet. - // This is the setup that exposes the bug. In production, both - // sessions point to the same object, so nonces set via localSession - // would also be visible to remoteSession. Here they are separate. - // - // The bug was that ProcessEvent calls ProposalClosingOpts() BEFORE - // processRemoteTaprootSig() which would initialize the remote nonce. - - // Make some fake shutdown scripts for both sides. - localDeliveryScript := bytes.Repeat([]byte{0x01}, 34) - localDeliveryScript[0] = txscript.OP_1 - localDeliveryScript[1] = txscript.OP_DATA_32 - - remoteDeliveryScript := bytes.Repeat([]byte{0x02}, 34) - remoteDeliveryScript[0] = txscript.OP_1 - remoteDeliveryScript[1] = txscript.OP_DATA_32 - - // Create a minimal MusigPartialSig for the mock close signer. - partialSig := musig2.NewPartialSignature( - new(btcec.ModNScalar), new(btcec.PublicKey), - ) - musigSig := lnwallet.NewMusigPartialSig( - &partialSig, - lnwire.Musig2Nonce{}, - lnwire.Musig2Nonce{}, - nil, - fn.None[chainhash.Hash](), - ) - - // Create mocks and other set up configs. - closeSigner := &mockCloseSigner{} - closeSigner.On( - "CreateCloseProposal", mock.Anything, mock.Anything, - mock.Anything, mock.Anything, - ).Return( - input.Signature(musigSig), wire.NewMsgTx(2), - btcutil.Amount(1000), nil, - ) - closeSigner.On( - "CompleteCooperativeClose", mock.Anything, mock.Anything, - mock.Anything, mock.Anything, mock.Anything, mock.Anything, - ).Return(wire.NewMsgTx(2), btcutil.Amount(0), nil) - - feeEstimator := &mockCoopFeeEstimator{} - feeEstimator.On( - "EstimateFee", mock.Anything, mock.Anything, - mock.Anything, mock.Anything, - ).Return(btcutil.Amount(1000)) - - chanObserver := &mockChanObserver{} - chanObserver.On("MarkCoopBroadcasted", mock.Anything, mock.Anything). - Return(nil) - chanObserver.On("FinalBalances").Return( - fn.None[chancloser.ShutdownBalances](), - ) - - peerPub := bobChan.State().IdentityPub - env := chancloser.Environment{ - ChainParams: chaincfg.RegressionNetParams, - ChanPeer: *peerPub, - ChanPoint: aliceChan.ChannelPoint(), - ChanID: lnwire.NewChanIDFromOutPoint( - aliceChan.ChannelPoint(), - ), - ChanType: chanType, - DefaultFeeRate: chainfee.SatPerVByte(10), - FeeEstimator: feeEstimator, - ChanObserver: chanObserver, - CloseSigner: closeSigner, - LocalMusigSession: localSession, - RemoteMusigSession: remoteSession, - } - localBalance := lnwire.NewMSatFromSatoshis(btcutil.Amount(500000000)) - remoteBalance := lnwire.NewMSatFromSatoshis(btcutil.Amount(500000000)) - - // Create RemoteCloseStart state, this is where the state machine will - // start from. - state := &chancloser.RemoteCloseStart{ - CloseChannelTerms: &chancloser.CloseChannelTerms{ - ShutdownScripts: chancloser.ShutdownScripts{ - LocalDeliveryScript: localDeliveryScript, - RemoteDeliveryScript: remoteDeliveryScript, - }, - ShutdownBalances: chancloser.ShutdownBalances{ - LocalBalance: localBalance, - RemoteBalance: remoteBalance, - }, - }, - } - - // Generate a valid JIT nonce for the ClosingComplete message. - // This simulates the remote party's closer nonce. - jitNonce, err := musig2.GenNonces( - musig2.WithPublicKey( - aliceChan.State().LocalChanCfg.MultiSigKey.PubKey, - ), - ) - require.NoError(t, err) - - var dummySig btcec.ModNScalar - dummySig.SetInt(12345) - partialSigWithNonce := lnwire.PartialSigWithNonce{ - PartialSig: lnwire.NewPartialSig(dummySig), - Nonce: jitNonce.PubNonce, - } - - // Create OfferReceivedEvent with taproot sig (ClosingComplete). Since - // both local and remote balances are above dust, we need the - // CloserAndClosee variant. - closingComplete := lnwire.ClosingComplete{ - ChannelID: env.ChanID, - CloserScript: remoteDeliveryScript, - CloseeScript: localDeliveryScript, - FeeSatoshis: btcutil.Amount(1000), - LockTime: 0, - TaprootClosingSigs: lnwire.TaprootClosingSigs{ - CloserAndClosee: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType7]( - partialSigWithNonce, - ), - ), - }, - } - - event := &chancloser.OfferReceivedEvent{ - SigMsg: closingComplete, - } - - // Call ProcessEvent. Before the bug fix, this will fail with: "failed - // to get musig closing opts: remote nonce not generated" - // - // This is because ProposalClosingOpts() is called on line 1932 BEFORE - // processRemoteTaprootSig() initializes the nonce on line 1943. - // - // After the fix (swapping the order), this should succeed. - _, err = state.ProcessEvent(event, &env) - - require.NoError( - t, err, "ProcessEvent should not fail - if it fails "+ - "with 'remote nonce not generated', the bug still "+ - "exists", - ) -} - -type mockCloseSigner struct { - mock.Mock -} - -func (m *mockCloseSigner) CreateCloseProposal( - proposedFee btcutil.Amount, localDeliveryScript, - remoteDeliveryScript []byte, closeOpt ...lnwallet.ChanCloseOpt, -) (input.Signature, *wire.MsgTx, btcutil.Amount, error) { - - args := m.Called(proposedFee, localDeliveryScript, - remoteDeliveryScript, closeOpt) - - sig, _ := args.Get(0).(input.Signature) - tx, _ := args.Get(1).(*wire.MsgTx) - amt, _ := args.Get(2).(btcutil.Amount) - - return sig, tx, amt, args.Error(3) -} - -func (m *mockCloseSigner) CompleteCooperativeClose( - localSig, remoteSig input.Signature, - localDeliveryScript, remoteDeliveryScript []byte, - proposedFee btcutil.Amount, closeOpts ...lnwallet.ChanCloseOpt, -) (*wire.MsgTx, btcutil.Amount, error) { - - args := m.Called(localSig, remoteSig, localDeliveryScript, - remoteDeliveryScript, proposedFee, closeOpts) - - tx, _ := args.Get(0).(*wire.MsgTx) - amt, _ := args.Get(1).(btcutil.Amount) - - return tx, amt, args.Error(2) -} - -type mockCoopFeeEstimator struct { - mock.Mock -} - -func (m *mockCoopFeeEstimator) EstimateFee( - chanType channeldb.ChannelType, localTxOut, remoteTxOut *wire.TxOut, - idealFeeRate chainfee.SatPerKWeight) btcutil.Amount { - - args := m.Called(chanType, localTxOut, remoteTxOut, idealFeeRate) - - amt, _ := args.Get(0).(btcutil.Amount) - - return amt -} - -type mockChanObserver struct { - mock.Mock -} - -func (m *mockChanObserver) NoDanglingUpdates() bool { - args := m.Called() - - return args.Bool(0) -} - -func (m *mockChanObserver) DisableIncomingAdds() error { - args := m.Called() - - return args.Error(0) -} - -func (m *mockChanObserver) DisableOutgoingAdds() error { - args := m.Called() - - return args.Error(0) -} - -func (m *mockChanObserver) DisableChannel() error { - args := m.Called() - - return args.Error(0) -} - -func (m *mockChanObserver) MarkCoopBroadcasted(tx *wire.MsgTx, - local bool) error { - - args := m.Called(tx, local) - - return args.Error(0) -} - -func (m *mockChanObserver) MarkShutdownSent(deliveryAddr []byte, - isInitiator bool) error { - - args := m.Called(deliveryAddr, isInitiator) - - return args.Error(0) -} - -//nolint:ll -func (m *mockChanObserver) FinalBalances() fn.Option[chancloser.ShutdownBalances] { - args := m.Called() - - //nolint:forcetypeassert - val := args.Get(0).(fn.Option[chancloser.ShutdownBalances]) - - return val -} diff --git a/peer/onion_ratelimit.go b/peer/onion_ratelimit.go deleted file mode 100644 index d6a41ada6..000000000 --- a/peer/onion_ratelimit.go +++ /dev/null @@ -1,78 +0,0 @@ -package peer - -import ( - "errors" - - "github.com/btcsuite/btclog/v2" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/onionmessage" -) - -// ErrNoChannel is the sentinel error returned by allowOnionMessage when -// the incoming peer has no fully open channel with us. It is the -// primary Sybil-resistance layer on top of the byte-granular rate -// limiters: an attacker that can cheaply spin up new identities cannot -// burn any per-peer or global token budget because the channel gate -// runs before the IngressLimiter is consulted at all. -var ErrNoChannel = errors.New("peer has no open channel") - -// allowOnionMessage applies the channel-presence gate and then, if the -// peer has at least one fully open channel with us, delegates to the -// IngressLimiter for the per-peer-then-global byte-granular rate limit -// check. The channel gate runs first on purpose: if it rejects, no rate -// limiter state is allocated for the no-channel peer and neither bucket -// is debited. A successful result wraps fn.Unit; a rejection wraps one -// of the sentinel errors ErrNoChannel, -// onionmessage.ErrPeerRateLimit, or onionmessage.ErrGlobalRateLimit so -// that callers can distinguish the drop reason via errors.Is. -// -// When relayAll is true, the channel gate is skipped entirely and the -// message is admitted to the IngressLimiter regardless of hasChannel. -// This is the opt-in policy for operators who want to accept onion -// messages from peers with no channel, trading the Sybil-resistance -// property of the gate for broader reachability. -// -// A nil IngressLimiter is treated as "disabled" and always accepts the -// message once the channel gate passes. This preserves the behavior of -// test and disabled-onion-messaging configurations without forcing -// callers to construct a real limiter. -func allowOnionMessage(limiter onionmessage.IngressLimiter, - peerKey [33]byte, msgBytes int, - hasChannel, relayAll bool) fn.Result[fn.Unit] { - - if !relayAll && !hasChannel { - return fn.Err[fn.Unit](ErrNoChannel) - } - if limiter == nil { - return fn.Ok(fn.Unit{}) - } - - return limiter.AllowN(peerKey, msgBytes) -} - -// logFirstOnionDrop emits a one-shot info log the first time the limiter -// identified by err trips. Per-peer drops go to peerLog (caller's -// peer-prefixed log) so the operator can see which peer first tripped -// the limiter; global drops go to pkgLog (typically the package-level -// peerLog) since they are not attributable to any single peer. -func logFirstOnionDrop(pkgLog, peerLog btclog.Logger, err error, - limiter onionmessage.IngressLimiter) { - - if limiter == nil { - return - } - - switch { - case errors.Is(err, onionmessage.ErrGlobalRateLimit): - if limiter.FirstGlobalDropClaim() { - pkgLog.Infof("onion message global rate limiter " + - "engaged; further drops logged at trace") - } - - case errors.Is(err, onionmessage.ErrPeerRateLimit): - if limiter.FirstPeerDropClaim() { - peerLog.Infof("onion message per-peer rate limiter " + - "engaged; further drops logged at trace") - } - } -} diff --git a/peer/onion_ratelimit_log_test.go b/peer/onion_ratelimit_log_test.go deleted file mode 100644 index 002008bde..000000000 --- a/peer/onion_ratelimit_log_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package peer - -import ( - "bytes" - "testing" - - "github.com/btcsuite/btclog/v2" - "github.com/lightningnetwork/lnd/onionmessage" - "github.com/stretchr/testify/require" -) - -// newCapturingLogger builds a btclog.Logger backed by an in-memory buffer -// so tests can assert whether a given log line was emitted. -func newCapturingLogger() (btclog.Logger, *bytes.Buffer) { - buf := &bytes.Buffer{} - handler := btclog.NewDefaultHandler(buf, btclog.WithNoTimestamp()) - return btclog.NewSLogger(handler), buf -} - -// newRealIngressLimiter constructs a real ingressLimiter backed by real -// per-peer and global limiters sized so the first message passes and -// every subsequent one trips the named side of the limiter. It is used -// by the log tests to exercise the one-shot claim path against real -// FirstDropClaim bookkeeping rather than a stub. -func newRealIngressLimiter(t *testing.T) onionmessage.IngressLimiter { - t.Helper() - - // Burst == one max-sized message for both sides; rate of 1 Kbps - // ensures neither bucket refills within the test window. - peerLim := onionmessage.NewPeerRateLimiter(1, testMsgBytes) - globalLim := onionmessage.NewGlobalLimiter(1, testMsgBytes) - - return onionmessage.NewIngressLimiter(peerLim, globalLim) -} - -// TestLogFirstOnionDropGlobalOneShot verifies that logFirstOnionDrop -// emits exactly one info-level line for the global limiter's first -// drop and is silent on subsequent drops, so operators get a single -// "engaged" signal without log flooding under sustained attack. The -// global first-drop line must land on the package-level logger, not -// the per-peer one, since a global drop is not attributable to any -// single peer. -func TestLogFirstOnionDropGlobalOneShot(t *testing.T) { - t.Parallel() - - pkgLog, pkgBuf := newCapturingLogger() - peerLog, peerBuf := newCapturingLogger() - limiter := newRealIngressLimiter(t) - - // First drop log: must emit to the package-level logger. - logFirstOnionDrop( - pkgLog, peerLog, onionmessage.ErrGlobalRateLimit, limiter, - ) - require.Contains(t, pkgBuf.String(), "global rate limiter") - require.Empty(t, peerBuf.String(), - "global drop must not land on the peer-prefix log") - - // Second drop log: must be silent (both buffer sizes unchanged). - sizeAfterFirst := pkgBuf.Len() - logFirstOnionDrop( - pkgLog, peerLog, onionmessage.ErrGlobalRateLimit, limiter, - ) - require.Equal(t, sizeAfterFirst, pkgBuf.Len(), - "second drop must not re-log the first-drop line") - require.Empty(t, peerBuf.String()) -} - -// TestLogFirstOnionDropPeerOneShot verifies the same one-shot property -// for the per-peer limiter and that the nil-limiter guard prevents a -// panic when onion message rate limiting is entirely disabled. The -// per-peer first-drop line must land on the peer-prefix logger so -// operators can see which peer tripped the limiter. -func TestLogFirstOnionDropPeerOneShot(t *testing.T) { - t.Parallel() - - pkgLog, pkgBuf := newCapturingLogger() - peerLog, peerBuf := newCapturingLogger() - - // Nil limiter: must not panic and must not log to either logger. - logFirstOnionDrop( - pkgLog, peerLog, onionmessage.ErrPeerRateLimit, nil, - ) - require.Empty(t, pkgBuf.String()) - require.Empty(t, peerBuf.String()) - - // Real limiter: emit once to the peer logger, then silent. - limiter := newRealIngressLimiter(t) - logFirstOnionDrop( - pkgLog, peerLog, onionmessage.ErrPeerRateLimit, limiter, - ) - require.Contains(t, peerBuf.String(), "per-peer rate limiter") - require.Empty(t, pkgBuf.String(), - "per-peer drop must not land on the package-level log") - sizeAfterFirst := peerBuf.Len() - logFirstOnionDrop( - pkgLog, peerLog, onionmessage.ErrPeerRateLimit, limiter, - ) - require.Equal(t, sizeAfterFirst, peerBuf.Len()) -} - -// TestLogFirstOnionDropUnknownReason verifies that an error that does -// not match any known drop reason is a no-op — neither limiter's -// first-drop flag is consumed. This guards against a typo or a new -// drop reason being added without a matching log case. -func TestLogFirstOnionDropUnknownReason(t *testing.T) { - t.Parallel() - - pkgLog, pkgBuf := newCapturingLogger() - peerLog, peerBuf := newCapturingLogger() - limiter := newRealIngressLimiter(t) - - logFirstOnionDrop(pkgLog, peerLog, ErrNoChannel, limiter) - require.Empty(t, pkgBuf.String()) - require.Empty(t, peerBuf.String()) - - // Both limiters' first-drop flags must still be unclaimed, so a - // follow-up call with a valid reason still emits the info line. - logFirstOnionDrop( - pkgLog, peerLog, onionmessage.ErrGlobalRateLimit, limiter, - ) - require.Contains(t, pkgBuf.String(), "global rate limiter") -} diff --git a/peer/onion_ratelimit_test.go b/peer/onion_ratelimit_test.go deleted file mode 100644 index 380f9790b..000000000 --- a/peer/onion_ratelimit_test.go +++ /dev/null @@ -1,344 +0,0 @@ -package peer - -import ( - "errors" - "sync" - "sync/atomic" - "testing" - - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/onionmessage" - "github.com/stretchr/testify/require" -) - -// testMsgBytes is the on-the-wire size we charge the bucket per call in -// these tests. It is sized to approximate a spec-max onion message so -// that burst budgets scale naturally with the per-message cost. -const testMsgBytes = 32 * 1024 - -// stubIngressLimiter is a test double for onionmessage.IngressLimiter -// that records every call and delegates the accept/reject decision to a -// caller-supplied predicate. It is used to exercise allowOnionMessage's -// composition logic (channel gate → limiter) without standing up a -// real token bucket. -type stubIngressLimiter struct { - // decide is invoked for every AllowN call. It receives the peer - // key and byte count and returns the error to embed in the - // fn.Result — nil for accept. - decide func(peer [33]byte, n int) error - - calls atomic.Uint64 -} - -// AllowN records the call and dispatches to the configured predicate. -func (s *stubIngressLimiter) AllowN(peer [33]byte, - n int) fn.Result[fn.Unit] { - - s.calls.Add(1) - if err := s.decide(peer, n); err != nil { - return fn.Err[fn.Unit](err) - } - - return fn.Ok(fn.Unit{}) -} - -// FirstPeerDropClaim always returns true so the log-path test can -// observe the one-shot dispatch. Tests that care about the one-shot -// invariant use a real IngressLimiter instead. -func (s *stubIngressLimiter) FirstPeerDropClaim() bool { return true } - -// FirstGlobalDropClaim always returns true for the same reason. -func (s *stubIngressLimiter) FirstGlobalDropClaim() bool { return true } - -// acceptAll constructs a stubIngressLimiter whose AllowN always accepts. -func acceptAll() *stubIngressLimiter { - return &stubIngressLimiter{ - decide: func(_ [33]byte, _ int) error { return nil }, - } -} - -// TestAllowOnionMessageNilLimiter verifies that allowOnionMessage treats -// a nil IngressLimiter as "disabled" and unconditionally accepts -// messages, as long as the channel gate passes. -func TestAllowOnionMessageNilLimiter(t *testing.T) { - t.Parallel() - - var peer [33]byte - result := allowOnionMessage(nil, peer, testMsgBytes, true, false) - require.NoError(t, result.Err()) -} - -// TestAllowOnionMessageNoChannel verifies that messages from a peer -// that does not have a fully open channel with us are dropped -// unconditionally with ErrNoChannel, even when a real IngressLimiter -// is configured. The stub records whether AllowN was consulted; it -// must remain at zero to prove the channel gate runs before the -// IngressLimiter. -func TestAllowOnionMessageNoChannel(t *testing.T) { - t.Parallel() - - limiter := acceptAll() - - var key [33]byte - key[0] = 0x07 - - result := allowOnionMessage(limiter, key, testMsgBytes, false, false) - require.Error(t, result.Err()) - require.True(t, errors.Is(result.Err(), ErrNoChannel)) - require.Equal(t, uint64(0), limiter.calls.Load(), - "no-channel drop must not consult the IngressLimiter") - - // Once the channel gate flips, the same key is accepted and the - // limiter is now consulted exactly once. - result = allowOnionMessage(limiter, key, testMsgBytes, true, false) - require.NoError(t, result.Err()) - require.Equal(t, uint64(1), limiter.calls.Load()) -} - -// TestAllowOnionMessageRelayAll verifies that enabling relayAll skips -// the channel-presence gate: a peer with no fully open channel is -// admitted into the IngressLimiter instead of being rejected at the -// gate. Exercising the same (key, hasChannel=false) input with -// relayAll flipped on and off proves the flag is the only thing that -// decides the gate outcome. -func TestAllowOnionMessageRelayAll(t *testing.T) { - t.Parallel() - - limiter := acceptAll() - - var key [33]byte - key[0] = 0x08 - - // Gate enforced: no-channel peer is rejected without consulting - // the IngressLimiter. - result := allowOnionMessage(limiter, key, testMsgBytes, false, false) - require.Error(t, result.Err()) - require.True(t, errors.Is(result.Err(), ErrNoChannel)) - require.Equal(t, uint64(0), limiter.calls.Load()) - - // Gate skipped: the same no-channel peer is now admitted and the - // IngressLimiter is consulted. - result = allowOnionMessage(limiter, key, testMsgBytes, false, true) - require.NoError(t, result.Err()) - require.Equal(t, uint64(1), limiter.calls.Load()) - - // relayAll with a peer that also has a channel: the gate is - // trivially satisfied and the limiter is consulted again. - result = allowOnionMessage(limiter, key, testMsgBytes, true, true) - require.NoError(t, result.Err()) - require.Equal(t, uint64(2), limiter.calls.Load()) - - // Nil limiter with relayAll is still accepted: disabled limiter + - // skipped gate = unconditional accept. - result = allowOnionMessage(nil, key, testMsgBytes, false, true) - require.NoError(t, result.Err()) -} - -// TestAllowOnionMessagePeerRejectsFirst verifies that a real -// IngressLimiter consults the per-peer limiter before the global -// limiter: once the per-peer bucket is drained, the global bucket -// must not be touched on subsequent calls, preserving the shared -// budget against a hostile peer burning global tokens via rejected -// attempts. -func TestAllowOnionMessagePeerRejectsFirst(t *testing.T) { - t.Parallel() - - // Real per-peer limiter with burst of exactly one message; very - // low rate so it does not refill during the test. - peerLim := onionmessage.NewPeerRateLimiter(1, testMsgBytes) - - // Stub "global" that records whether it was consulted. It wraps - // the global side of the IngressLimiter. - globalCalls := atomic.Uint64{} - global := &countingGlobalStub{ - allow: func() bool { return true }, - calls: &globalCalls, - } - - limiter := onionmessage.NewIngressLimiter(peerLim, global) - - var key [33]byte - key[0] = 0x03 - - // First call drains the per-peer bucket; both limiters are - // consulted so global.calls bumps to 1. - result := allowOnionMessage(limiter, key, testMsgBytes, true, false) - require.NoError(t, result.Err()) - require.Equal(t, uint64(1), globalCalls.Load()) - - // Second call trips the per-peer limiter and must NOT consult - // the global limiter — globalCalls stays at 1. - result = allowOnionMessage(limiter, key, testMsgBytes, true, false) - require.Error(t, result.Err()) - require.True(t, - errors.Is(result.Err(), onionmessage.ErrPeerRateLimit), - ) - require.Equal(t, uint64(1), peerLim.Dropped()) - require.Equal(t, uint64(1), globalCalls.Load(), - "global limiter must not be consulted when per-peer rejects") -} - -// countingGlobalStub is a minimal RateLimiter test double that counts -// calls to AllowN and delegates the accept/reject decision to a -// caller-supplied predicate. It exists so tests can feed a real -// ingressLimiter a controllable global side. -type countingGlobalStub struct { - allow func() bool - calls *atomic.Uint64 -} - -func (s *countingGlobalStub) AllowN(_ int) bool { - s.calls.Add(1) - - return s.allow() -} - -// TestAllowOnionMessageGlobalRejects verifies that when the per-peer -// limiter permits traffic but the global bucket is exhausted, -// allowOnionMessage surfaces ErrGlobalRateLimit. -func TestAllowOnionMessageGlobalRejects(t *testing.T) { - t.Parallel() - - peerLim := onionmessage.NewPeerRateLimiter( - 1_000_000, 100*testMsgBytes, - ) - - globalCalls := atomic.Uint64{} - global := &countingGlobalStub{ - allow: func() bool { return false }, - calls: &globalCalls, - } - limiter := onionmessage.NewIngressLimiter(peerLim, global) - - var key [33]byte - key[0] = 0x02 - - result := allowOnionMessage(limiter, key, testMsgBytes, true, false) - require.Error(t, result.Err()) - require.True(t, - errors.Is(result.Err(), onionmessage.ErrGlobalRateLimit), - ) - require.Equal(t, uint64(0), peerLim.Dropped()) - require.Equal(t, uint64(1), globalCalls.Load()) -} - -// TestAllowOnionMessageHappyPath verifies that a fully-configured -// IngressLimiter accepts a stream of messages when neither bucket is -// under pressure. -func TestAllowOnionMessageHappyPath(t *testing.T) { - t.Parallel() - - peerLim := onionmessage.NewPeerRateLimiter( - 1_000_000, 100*testMsgBytes, - ) - globalCalls := atomic.Uint64{} - global := &countingGlobalStub{ - allow: func() bool { return true }, - calls: &globalCalls, - } - limiter := onionmessage.NewIngressLimiter(peerLim, global) - - var key [33]byte - key[0] = 0x04 - - for i := 0; i < 10; i++ { - result := allowOnionMessage( - limiter, key, testMsgBytes, true, false, - ) - require.NoError(t, result.Err(), "iter %d", i) - } - require.Equal(t, uint64(0), peerLim.Dropped()) -} - -// TestAllowOnionMessagePeerIsolation verifies at the peer-package level -// that exhausting one peer's bucket through allowOnionMessage does not -// affect a different peer's allowance — guarding against a regression -// where the helper might key the bucket incorrectly. -func TestAllowOnionMessagePeerIsolation(t *testing.T) { - t.Parallel() - - peerLim := onionmessage.NewPeerRateLimiter(1, 2*testMsgBytes) - globalCalls := atomic.Uint64{} - global := &countingGlobalStub{ - allow: func() bool { return true }, - calls: &globalCalls, - } - limiter := onionmessage.NewIngressLimiter(peerLim, global) - - var keyA, keyB [33]byte - keyA[0] = 0x02 - keyB[0] = 0x03 - - // Drain peer A. - for i := 0; i < 2; i++ { - result := allowOnionMessage( - limiter, keyA, testMsgBytes, true, false, - ) - require.NoError(t, result.Err()) - } - result := allowOnionMessage(limiter, keyA, testMsgBytes, true, false) - require.Error(t, result.Err()) - - // Peer B must still have its full burst available. - for i := 0; i < 2; i++ { - result := allowOnionMessage( - limiter, keyB, testMsgBytes, true, false, - ) - require.NoError(t, result.Err(), "peer B slot %d", i) - } -} - -// TestAllowOnionMessageConcurrent exercises concurrent access to -// allowOnionMessage across many goroutines. It asserts that the sum of -// accepted calls plus the per-peer dropped counter equals the total -// number of attempts, and that no race or panic occurs. Run with -race -// for the strongest signal. -func TestAllowOnionMessageConcurrent(t *testing.T) { - t.Parallel() - - const burstMessages = 32 - peerLim := onionmessage.NewPeerRateLimiter( - 1, burstMessages*testMsgBytes, - ) - globalCalls := atomic.Uint64{} - global := &countingGlobalStub{ - allow: func() bool { return true }, - calls: &globalCalls, - } - limiter := onionmessage.NewIngressLimiter(peerLim, global) - - var key [33]byte - key[0] = 0x05 - - const workers = 16 - const perWorker = 64 - var wg sync.WaitGroup - var accepted atomic.Uint64 - - for w := 0; w < workers; w++ { - wg.Add(1) - go func() { - defer wg.Done() - for i := 0; i < perWorker; i++ { - result := allowOnionMessage( - limiter, key, testMsgBytes, true, false, - ) - if result.Err() == nil { - accepted.Add(1) - } - } - }() - } - wg.Wait() - - total := uint64(workers * perWorker) - require.Equal( - t, total, accepted.Load()+peerLim.Dropped(), - "every attempt must be counted as accepted or dropped", - ) - // With a near-zero refill rate the bucket can only issue at most - // burstMessages accepts before refill; since the test runs much - // faster than the refill interval, accepted should equal the - // burst. - require.Equal(t, uint64(burstMessages), accepted.Load()) -} diff --git a/peer/rbf_close_wrapper_actor.go b/peer/rbf_close_wrapper_actor.go deleted file mode 100644 index 90adf2f13..000000000 --- a/peer/rbf_close_wrapper_actor.go +++ /dev/null @@ -1,186 +0,0 @@ -package peer - -import ( - "context" - "fmt" - - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/actor" - "github.com/lightningnetwork/lnd/contractcourt" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/htlcswitch" - "github.com/lightningnetwork/lnd/lnwallet/chainfee" - "github.com/lightningnetwork/lnd/lnwire" -) - -// rbfCloseMessage is a message type that is used to trigger a cooperative fee -// bump, or initiate a close for the first time. -type rbfCloseMessage struct { - actor.BaseMessage - - // Ctx is the context of the caller that initiated the RBF close. This - // is propagated to the underlying close request so that cancellation - // of the caller (e.g. RPC stream disconnect) tears down the associated - // observer goroutine. The caller's context is distinct from the - // actor's own lifecycle context that is passed to Receive. - Ctx context.Context //nolint:containedctx - - // ChanPoint is the channel point of the channel to be closed. - ChanPoint wire.OutPoint - - // FeeRate is the fee rate to use for the transaction. - FeeRate chainfee.SatPerKWeight - - // DeliveryScript is the script to use for the transaction. - DeliveryScript lnwire.DeliveryAddress -} - -// MessageType returns the type of the message. -// -// NOTE: This is part of the actor.Message interface. -func (r rbfCloseMessage) MessageType() string { - return fmt.Sprintf("RbfCloseMessage(%v)", r.ChanPoint) -} - -// NewRbfBumpCloseMsg returns a message that can be sent to the RBF actor to -// initiate a new fee bump. -func NewRbfBumpCloseMsg(ctx context.Context, op wire.OutPoint, - feeRate chainfee.SatPerKWeight, - deliveryScript lnwire.DeliveryAddress) rbfCloseMessage { - - return rbfCloseMessage{ - Ctx: ctx, - ChanPoint: op, - FeeRate: feeRate, - DeliveryScript: deliveryScript, - } -} - -// RbfCloseActorServiceKey is a service key that can be used to reach an RBF -// chan closer. -type RbfCloseActorServiceKey = actor.ServiceKey[ - rbfCloseMessage, *CoopCloseUpdates, -] - -// NewRbfCloserPeerServiceKey returns a new service key that can be used to -// reach an RBF chan closer, via an active peer. -func NewRbfCloserPeerServiceKey(op wire.OutPoint) RbfCloseActorServiceKey { - opStr := op.String() - - // Just using the channel point here is enough, as we have a unique - // type here rbfCloseMessage which will handle the final actor - // selection. - actorKey := fmt.Sprintf("Peer(RbfChanCloser(%v))", opStr) - - return actor.NewServiceKey[rbfCloseMessage, *CoopCloseUpdates](actorKey) -} - -// rbfCloseActor is a wrapper around the Brontide peer to expose the internal -// RBF close state machine as an actor. This is intended for callers that need -// to obtain streaming close updates related to the RBF close process. -type rbfCloseActor struct { - chanPeer *Brontide - actorSystem *actor.ActorSystem - chanPoint wire.OutPoint -} - -// newRbfCloseActor creates a new instance of the RBF close wrapper actor. -func newRbfCloseActor(chanPoint wire.OutPoint, - chanPeer *Brontide, actorSystem *actor.ActorSystem) *rbfCloseActor { - - return &rbfCloseActor{ - chanPeer: chanPeer, - actorSystem: actorSystem, - chanPoint: chanPoint, - } -} - -// registerActor registers a new RBF close actor with the actor system. If an -// instance with the same service key and types are registered, we'll -// unregister before proceeding. -func (r *rbfCloseActor) registerActor() error { - // First, we'll make the service key of this RBF actor. This'll allow - // us to spawn the actor in the actor system. - actorKey := NewRbfCloserPeerServiceKey(r.chanPoint) - - // We only want to have a single actor instance for this rbf closer, - // so we'll now attempt to unregister any other instances. - actorKey.UnregisterAll(r.actorSystem) - - // Now that we know that no instances of the actor are present, let's - // register a new instance. We don't actually need the ref though, as - // any interested parties can look up the actor via the service key. - actorID := fmt.Sprintf( - "PeerWrapper(RbfChanCloser(%s))", r.chanPoint, - ) - if _, err := actorKey.Spawn(r.actorSystem, actorID, r); err != nil { - return fmt.Errorf("unable to spawn RBF close actor for "+ - "channel %v: %w", r.chanPoint, err) - } - - return nil -} - -// Receive implements the actor.ActorBehavior interface for the rbf closer -// wrapper. This allows us to expose our specific processes around the coop -// close flow as an actor. -// -// NOTE: This implements the actor.ActorBehavior interface. -func (r *rbfCloseActor) Receive(_ context.Context, - msg rbfCloseMessage) fn.Result[*CoopCloseUpdates] { - - type retType = *CoopCloseUpdates - - // Note that no eligibility check is needed here: an actor is only - // ever registered after initRbfChanCloser has vetted the channel for - // RBF coop close. - closeUpdates := &CoopCloseUpdates{ - UpdateChan: make(chan interface{}, 1), - ErrChan: make(chan error, 1), - } - - // We'll re-use the existing switch struct here, even though we're - // bypassing the switch entirely. We use the caller's context from the - // message so that canceling the caller (e.g., RPC stream close) also - // tears down the observer goroutine. - closeReq := htlcswitch.ChanClose{ - CloseType: contractcourt.CloseRegular, - ChanPoint: &msg.ChanPoint, - TargetFeePerKw: msg.FeeRate, - DeliveryScript: msg.DeliveryScript, - Updates: closeUpdates.UpdateChan, - Err: closeUpdates.ErrChan, - Ctx: msg.Ctx, - } - - err := r.chanPeer.startRbfChanCloser( - newRPCShutdownInit(&closeReq), msg.ChanPoint, - ) - if err != nil { - peerLog.Errorf("unable to start RBF chan closer for "+ - "channel %v: %v", msg.ChanPoint, err) - - return fn.Errf[retType]("unable to start RBF chan "+ - "closer: %w", err) - } - - return fn.Ok(closeUpdates) -} - -// RbfChanCloseActor is a router that will route messages to the relevant RBF -// chan closer. -type RbfChanCloseActor = actor.Router[rbfCloseMessage, *CoopCloseUpdates] - -// RbfChanCloserRouter creates a new router that will route messages to the -// relevant RBF chan closer. -func RbfChanCloserRouter(actorSystem *actor.ActorSystem, - serviceKey RbfCloseActorServiceKey) *RbfChanCloseActor { - - strategy := actor.NewRoundRobinStrategy[ - rbfCloseMessage, *CoopCloseUpdates, - ]() - - return actor.NewRouter( - actorSystem.Receptionist(), serviceKey, strategy, nil, - ) -} diff --git a/peer/rbf_close_wrapper_actor_test.go b/peer/rbf_close_wrapper_actor_test.go deleted file mode 100644 index 62b14292f..000000000 --- a/peer/rbf_close_wrapper_actor_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package peer - -import ( - "testing" - - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/actor" - "github.com/stretchr/testify/require" -) - -// TestRbfCloseActorSingleton verifies that registering an RBF close actor for -// the same channel point twice results in only a single registered actor. The -// second call to registerActor should unregister the first actor before -// spawning a replacement. -func TestRbfCloseActorSingleton(t *testing.T) { - t.Parallel() - - actorSystem := actor.NewActorSystem() - t.Cleanup(func() { - require.NoError(t, actorSystem.Shutdown()) - }) - - chanPoint := wire.OutPoint{Index: 1} - serviceKey := NewRbfCloserPeerServiceKey(chanPoint) - - // Register the actor for the first time. - actor1 := newRbfCloseActor(chanPoint, nil, actorSystem) - require.NoError(t, actor1.registerActor()) - - // Verify exactly one actor is registered. - refs := actor.FindInReceptionist( - actorSystem.Receptionist(), serviceKey, - ) - require.Len(t, refs, 1) - - // Register the actor again for the same channel point. - actor2 := newRbfCloseActor(chanPoint, nil, actorSystem) - require.NoError(t, actor2.registerActor()) - - // Verify there is still exactly one actor registered (the second one - // replaced the first). - refs = actor.FindInReceptionist( - actorSystem.Receptionist(), serviceKey, - ) - require.Len(t, refs, 1) -} diff --git a/peer/test_utils.go b/peer/test_utils.go index 8c8e0ce9f..673eceed8 100644 --- a/peer/test_utils.go +++ b/peer/test_utils.go @@ -12,13 +12,12 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channelnotifier" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/htlcswitch" @@ -44,10 +43,6 @@ const ( // a return value on a channel. timeout = time.Second * 5 - // shortTimeout is the window a test waits for when it expects nothing - // to show up on a channel. - shortTimeout = time.Millisecond * 250 - // testCltvRejectDelta is the minimum delta between expiry and current // height below which htlcs are rejected. testCltvRejectDelta = 13 @@ -60,7 +55,7 @@ var ( // noUpdate is a function which can be used as a parameter in // createTestPeerWithChannel to call the setup code with no custom values on // the channels set up. -var noUpdate = func(a, b *chanstate.OpenChannel) {} +var noUpdate = func(a, b *channeldb.OpenChannel) {} type peerTestCtx struct { peer *Brontide @@ -80,7 +75,7 @@ type peerTestCtx struct { // It takes an updateChan function which can be used to modify the default // values on the channel states for each peer. func createTestPeerWithChannel(t *testing.T, updateChan func(a, - b *chanstate.OpenChannel)) (*peerTestCtx, error) { + b *channeldb.OpenChannel)) (*peerTestCtx, error) { params := createTestPeer(t) @@ -243,7 +238,7 @@ func createTestPeerWithChannel(t *testing.T, updateChan func(a, binary.BigEndian.Uint64(chanIDBytes[:]), ) - aliceChannelState := &chanstate.OpenChannel{ + aliceChannelState := &channeldb.OpenChannel{ LocalChanCfg: aliceCfg, RemoteChanCfg: bobCfg, IdentityPub: aliceKeyPub, @@ -258,9 +253,10 @@ func createTestPeerWithChannel(t *testing.T, updateChan func(a, LocalCommitment: aliceCommit, RemoteCommitment: aliceCommit, Db: dbAlice.ChannelStateDB(), + Packager: channeldb.NewChannelPackager(shortChanID), FundingTxn: channels.TestFundingTx, } - bobChannelState := &chanstate.OpenChannel{ + bobChannelState := &channeldb.OpenChannel{ LocalChanCfg: bobCfg, RemoteChanCfg: aliceCfg, IdentityPub: bobKeyPub, @@ -274,6 +270,7 @@ func createTestPeerWithChannel(t *testing.T, updateChan func(a, LocalCommitment: bobCommit, RemoteCommitment: bobCommit, Db: dbBob.ChannelStateDB(), + Packager: channeldb.NewChannelPackager(shortChanID), } // Set custom values on the channel states. @@ -391,12 +388,6 @@ type mockUpdateHandler struct { cid lnwire.ChannelID isOutgoingAddBlocked atomic.Bool isIncomingAddBlocked atomic.Bool - - // flushHooks receives the hooks registered through OnFlushedOnce when - // the handler was built with deferFlush set. Tests that want to control - // when the channel looks flushed read the hook from here and call it - // themselves, standing in for the link's own goroutine. - flushHooks chan func() } // newMockUpdateHandler creates a new mockUpdateHandler. @@ -406,18 +397,6 @@ func newMockUpdateHandler(cid lnwire.ChannelID) *mockUpdateHandler { } } -// newDeferredFlushUpdateHandler creates a mock link that holds on to the hooks -// registered through OnFlushedOnce instead of running them inline, so a test -// can decide when the channel becomes flushed. -func newDeferredFlushUpdateHandler( - cid lnwire.ChannelID) *mockUpdateHandler { - - return &mockUpdateHandler{ - cid: cid, - flushHooks: make(chan func(), 1), - } -} - // HandleChannelUpdate currently does nothing. func (m *mockUpdateHandler) HandleChannelUpdate(msg lnwire.Message) {} @@ -486,12 +465,6 @@ func (m *mockUpdateHandler) IsFlushing(dir htlcswitch.LinkDirection) bool { } func (m *mockUpdateHandler) OnFlushedOnce(hook func()) { - if m.flushHooks != nil { - m.flushHooks <- hook - - return - } - hook() } func (m *mockUpdateHandler) OnCommitOnce( @@ -586,20 +559,6 @@ func (m *mockMessageConn) Close() error { return nil } -// mockBestBlockView is a mock implementation of chainntnfs.BestBlockView for -// testing. -type mockBestBlockView struct{} - -// BestHeight returns a dummy block height. -func (m *mockBestBlockView) BestHeight() (uint32, error) { - return 0, nil -} - -// BestBlockHeader returns a dummy block header. -func (m *mockBestBlockView) BestBlockHeader() (*wire.BlockHeader, error) { - return &wire.BlockHeader{}, nil -} - // createTestPeer creates a new peer for testing and returns a context struct // containing necessary handles and mock objects for conducting tests on peer // functionalities. @@ -735,19 +694,11 @@ func createTestPeer(t *testing.T) *peerTestCtx { var pubKey [33]byte copy(pubKey[:], aliceKeyPub.SerializeCompressed()) - // We have to have a valid server key for brontide to start up properly. - serverKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - var serverKeyArr [33]byte - copy(serverKeyArr[:], serverKey.PubKey().SerializeCompressed()) - estimator := chainfee.NewStaticEstimator(12500, 0) cfg := &Config{ Addr: cfgAddr, PubKeyBytes: pubKey, - ServerPubKey: serverKeyArr, ErrorBuffer: errBuffer, ChainIO: chainIO, Switch: mockSwitch, @@ -780,8 +731,7 @@ func createTestPeer(t *testing.T) *peerTestCtx { return nil }, - PongBuf: make([]byte, lnwire.MaxPongBytes), - BestBlockView: &mockBestBlockView{}, + PongBuf: make([]byte, lnwire.MaxPongBytes), FetchLastChanUpdate: func(chanID lnwire.ShortChannelID, ) (*lnwire.ChannelUpdate1, error) { diff --git a/pilot.go b/pilot.go index 58d971ca8..8cbf23cc6 100644 --- a/pilot.go +++ b/pilot.go @@ -6,8 +6,8 @@ import ( "net" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/autopilot" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/funding" @@ -185,7 +185,7 @@ func initAutoPilot(svr *server, cfg *lncfg.AutoPilot, cfg.MinConfs, lnwallet.DefaultAccountName, ) }, - Graph: autopilot.ChannelGraphFromDatabase(svr.v1Graph), + Graph: autopilot.ChannelGraphFromDatabase(svr.graphDB), Constraints: atplConstraints, ConnectToPeer: func(target *btcec.PublicKey, addrs []net.Addr) (bool, error) { // First, we'll check if we're already connected to the @@ -195,11 +195,6 @@ func initAutoPilot(svr *server, cfg *lncfg.AutoPilot, return true, nil } - // Strip persisted Tor v2 .onion entries: Tor stopped - // serving them in 2021 and the dial would never - // succeed. Covered by TestWithoutV2Onion. - addrs = withoutV2Onion(addrs) - // We can't establish a channel if no addresses were // provided for the peer. if len(addrs) == 0 { diff --git a/protofsm/actor_wrapper.go b/protofsm/actor_wrapper.go deleted file mode 100644 index b0be9a07e..000000000 --- a/protofsm/actor_wrapper.go +++ /dev/null @@ -1,23 +0,0 @@ -package protofsm - -import ( - "fmt" - - "github.com/lightningnetwork/lnd/actor" -) - -// ActorMessage wraps an Event, in order to create a new message that can be -// used with the actor package. -type ActorMessage[Event any] struct { - actor.BaseMessage - - // Event is the event that is being sent to the actor. - Event Event -} - -// MessageType returns the type of the message. -// -// NOTE: This implements the actor.Message interface. -func (a ActorMessage[Event]) MessageType() string { - return fmt.Sprintf("ActorMessage(%T)", a.Event) -} diff --git a/protofsm/daemon_events.go b/protofsm/daemon_events.go index 991080788..3b4ca9b4d 100644 --- a/protofsm/daemon_events.go +++ b/protofsm/daemon_events.go @@ -2,8 +2,8 @@ package protofsm import ( "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwire" diff --git a/protofsm/state_machine.go b/protofsm/state_machine.go index 5d3d87a0b..b3e16f5fd 100644 --- a/protofsm/state_machine.go +++ b/protofsm/state_machine.go @@ -8,8 +8,8 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btclog/v2" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/fn/v2" @@ -259,26 +259,6 @@ func (s *StateMachine[Event, Env]) SendEvent(ctx context.Context, event Event) { } } -// Receive processes a message and returns a Result. The provided context is the -// actor's internal context, which can be used to detect actor shutdown -// requests. -// -// NOTE: This implements the actor.ActorBehavior interface. -func (s *StateMachine[Event, Env]) Receive(ctx context.Context, - e ActorMessage[Event]) fn.Result[bool] { - - select { - case s.events <- e.Event: - return fn.Ok(true) - - case <-ctx.Done(): - return fn.Err[bool](ctx.Err()) - - case <-s.quit: - return fn.Err[bool](ErrStateMachineShutdown) - } -} - // CanHandle returns true if the target message can be routed to the state // machine. func (s *StateMachine[Event, Env]) CanHandle(msg msgmux.PeerMsg) bool { diff --git a/protofsm/state_machine_test.go b/protofsm/state_machine_test.go index ca4fdfac1..ca060614f 100644 --- a/protofsm/state_machine_test.go +++ b/protofsm/state_machine_test.go @@ -7,8 +7,8 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwire" diff --git a/queue/back_pressure.go b/queue/back_pressure.go deleted file mode 100644 index 570fb83c2..000000000 --- a/queue/back_pressure.go +++ /dev/null @@ -1,266 +0,0 @@ -package queue - -import ( - "context" - "errors" - "math/rand" - "sync" - "sync/atomic" - - "github.com/lightningnetwork/lnd/fn/v2" -) - -// ErrQueueClosed is returned by Enqueue/TryEnqueue when the queue has already -// been closed. -var ErrQueueClosed = errors.New("queue closed") - -// DropCheckFunc decides whether to drop an item based solely on the current -// queue depth. This is the natural return type for length-only strategies such -// as RandomEarlyDrop. -type DropCheckFunc func(queueLen int) bool - -// DropPredicate decides whether to drop an item based on the current queue -// depth and the item itself. It returns true to drop, false to enqueue. Use -// this when the drop decision depends on the item itself; for length-only -// checks prefer DropCheckFunc. -type DropPredicate[T any] func(queueLen int, item T) bool - -// AsDropPredicate adapts a length-only DropCheckFunc into a DropPredicate[T], -// ignoring the item. -func AsDropPredicate[T any](f DropCheckFunc) DropPredicate[T] { - return func(queueLen int, _ T) bool { - return f(queueLen) - } -} - -// ErrItemDropped is returned by Enqueue when the item is dropped by the -// DropPredicate. This can happen before the queue is actually full (e.g. with -// RED-style early drops). -var ErrItemDropped = errors.New("item dropped by drop predicate") - -// ErrNegativeMinThreshold is returned by RandomEarlyDrop when minThreshold -// is negative. -var ErrNegativeMinThreshold = errors.New( - "queue: minThreshold must be >= 0", -) - -// ErrInvalidThresholdOrder is returned by RandomEarlyDrop when maxThreshold -// is not strictly greater than minThreshold. -var ErrInvalidThresholdOrder = errors.New( - "queue: maxThreshold must be > minThreshold", -) - -// BackpressureQueue is a generic, fixed-capacity queue with predicate-based -// drop behavior. When full, it uses the DropPredicate to perform early drops -// (e.g., RED-style). -type BackpressureQueue[T any] struct { - ch chan T - dropPredicate DropPredicate[T] - - closed atomic.Bool - closeOnce sync.Once -} - -// NewBackpressureQueue creates a new BackpressureQueue with the given capacity -// and drop predicate. Panics if capacity <= 0 or predicate is nil. -func NewBackpressureQueue[T any](capacity int, - predicate DropPredicate[T]) *BackpressureQueue[T] { - - if capacity <= 0 { - panic("queue: NewBackpressureQueue requires capacity > 0") - } - if predicate == nil { - panic("queue: NewBackpressureQueue requires " + - "a non-nil predicate") - } - - return &BackpressureQueue[T]{ - ch: make(chan T, capacity), - dropPredicate: predicate, - } -} - -// Enqueue attempts to add an item to the queue, respecting context -// cancellation. Returns ErrItemDropped if dropped, or context error if ctx is -// done before enqueue. Otherwise, `nil` is returned on success. -func (q *BackpressureQueue[T]) Enqueue(ctx context.Context, item T) error { - if q.closed.Load() { - return ErrQueueClosed - } - - // Consult the drop predicate based on the current queue length. - // - // NOTE: There is a TOCTOU gap here — the queue length snapshot can - // become stale between this check and the channel send below if - // there are multiple concurrent writers. This is acceptable because - // RED is inherently probabilistic and approximate; a slightly - // outdated length does not compromise correctness. - if q.dropPredicate(len(q.ch), item) { - return ErrItemDropped - } - - // If the predicate decides not to drop, attempt to enqueue the item. - select { - case q.ch <- item: - return nil - - default: - // Channel is full, and the predicate decided not to drop. We - // must block until space is available or context is cancelled. - select { - case q.ch <- item: - return nil - - case <-ctx.Done(): - return ctx.Err() - } - } -} - -// TryEnqueue attempts to add an item to the queue without blocking. Returns -// true if successfully enqueued, false if the drop predicate rejected the item -// or the queue is at capacity. -func (q *BackpressureQueue[T]) TryEnqueue(item T) bool { - if q.closed.Load() { - return false - } - - if q.dropPredicate(len(q.ch), item) { - return false - } - - select { - case q.ch <- item: - return true - default: - return false - } -} - -// Dequeue retrieves the next item from the queue, blocking until available or -// context done. Returns the item or an error if ctx is done before an item is -// available. -func (q *BackpressureQueue[T]) Dequeue(ctx context.Context) fn.Result[T] { - select { - - case item, ok := <-q.ch: - if !ok { - return fn.Err[T](ErrQueueClosed) - } - return fn.Ok(item) - - case <-ctx.Done(): - return fn.Err[T](ctx.Err()) - } -} - -// Len returns the current number of items buffered in the queue. -func (q *BackpressureQueue[T]) Len() int { - return len(q.ch) -} - -// ReceiveChan returns the receive-only end of the internal channel, allowing -// callers to select on it alongside other channels (e.g., context.Done). -func (q *BackpressureQueue[T]) ReceiveChan() <-chan T { - return q.ch -} - -// Close closes the internal channel. It is safe to call multiple times; -// only the first call has any effect. After Close, no more items can be -// enqueued. Remaining items can still be received via ReceiveChan. -func (q *BackpressureQueue[T]) Close() { - q.closeOnce.Do(func() { - q.closed.Store(true) - close(q.ch) - }) -} - -// redConfig holds configuration for RandomEarlyDrop. -type redConfig struct { - // randSrc returns a float64 in [0.0, 1.0). It must be safe for - // concurrent use if the returned DropCheckFunc will be called from - // multiple goroutines. The default (math/rand.Float64) is safe since - // Go 1.20+. - randSrc func() float64 -} - -// REDOption is a functional option for configuring RandomEarlyDrop. -type REDOption func(*redConfig) - -// WithRandSource provides a custom random number source (a function that -// returns a float64 between 0.0 and 1.0). -func WithRandSource(src func() float64) REDOption { - return func(cfg *redConfig) { - cfg.randSrc = src - } -} - -// RandomEarlyDrop returns a DropCheckFunc that implements Random Early -// Detection (RED), inspired by TCP-RED queue management. -// -// RED prevents sudden buffer overflows by proactively dropping packets before -// the queue is full. It establishes two thresholds: -// -// 1. minThreshold: queue length below which no drops occur. -// 2. maxThreshold: queue length at or above which all items are dropped. -// -// Between these points, the drop probability p increases linearly: -// -// p = (queueLen - minThreshold) / (maxThreshold - minThreshold) -// -// For example, with minThreshold=15 and maxThreshold=35: -// - At queueLen=15, p=0.0 (0% drop chance) -// - At queueLen=25, p=0.5 (50% drop chance) -// - At queueLen=35, p=1.0 (100% drop chance) -// -// This smooth ramp helps avoid tail-drop spikes, smooths queue occupancy, -// and gives early back-pressure signals to senders. -func RandomEarlyDrop(minThreshold, maxThreshold int, - opts ...REDOption) (DropCheckFunc, error) { - - if minThreshold < 0 { - return nil, ErrNegativeMinThreshold - } - if maxThreshold <= minThreshold { - return nil, ErrInvalidThresholdOrder - } - - cfg := redConfig{ - randSrc: rand.Float64, - } - - for _, opt := range opts { - opt(&cfg) - } - if cfg.randSrc == nil { - cfg.randSrc = rand.Float64 - } - - // Precompute the denominator for the linear drop probability - // scaling. Since minThreshold < maxThreshold is enforced above, - // this is always positive. - denominator := float64(maxThreshold - minThreshold) - - dropFn := func(queueLen int) bool { - // If the queue is below the minimum threshold, then we never - // drop. - if queueLen < minThreshold { - return false - } - - // If the queue is at or above the maximum threshold, then we - // always drop. - if queueLen >= maxThreshold { - return true - } - - // If we're in the middle, then we implement linear scaling of - // the drop probability based on our thresholds. At this point, - // minThreshold <= queueLen < maxThreshold. - p := float64(queueLen-minThreshold) / denominator - - return cfg.randSrc() < p - } - - return dropFn, nil -} diff --git a/queue/back_pressure_test.go b/queue/back_pressure_test.go deleted file mode 100644 index c122a9f03..000000000 --- a/queue/back_pressure_test.go +++ /dev/null @@ -1,488 +0,0 @@ -package queue - -import ( - "context" - "errors" - "math/rand" - "testing" - "time" - - "github.com/stretchr/testify/require" - "pgregory.net/rapid" -) - -// queueMachine is the generic state machine logic for testing -// BackpressureQueue. T must be comparable for use in assertions. -type queueMachine[T comparable] struct { - tb rapid.TB - - capacity int - - queue *BackpressureQueue[T] - - modelQueue []T - - dropPredicate DropPredicate[T] - - itemGenerator *rapid.Generator[T] -} - -// Enqueue is a state machine action. It enqueues an item and updates the model. -func (m *queueMachine[T]) Enqueue(t *rapid.T) { - item := m.itemGenerator.Draw(t, "item") - - err := m.queue.Enqueue(context.Background(), item) - - actualDrop := false - if errors.Is(err, ErrItemDropped) { - actualDrop = true - } else if err != nil { - // If Enqueue with background context returns an error other than - // ErrItemDropped, it's unexpected. - m.tb.Fatalf("Enqueue with background context returned "+ - "unexpected error: %v", err) - } - - if !actualDrop { - // If the item was not dropped, it must have been enqueued. Add - // it to the model. The modelQueue should not exceed capacity. - // This is also checked in Check(). - m.modelQueue = append(m.modelQueue, item) - } -} - -// Dequeue is a state machine action. It dequeues an item and updates the model. -func (m *queueMachine[T]) Dequeue(t *rapid.T) { - if len(m.modelQueue) == 0 { - // If the model is empty, the actual queue channel should also - // be empty. - require.Zero( - m.tb, len(m.queue.ch), "actual queue channel not "+ - "empty when model is empty", - ) - - // Attempting to dequeue from an empty queue should block. We - // verify this by trying to dequeue with a very short timeout. - ctx, cancel := context.WithTimeout( - context.Background(), 5*time.Millisecond, - ) - defer cancel() - - result := m.queue.Dequeue(ctx) - require.True( - m.tb, result.IsErr(), "dequeue "+ - "should return error on empty queue with timeout", - ) - require.ErrorIs( - m.tb, result.Err(), - context.DeadlineExceeded, "dequeue should "+ - "block on empty queue", - ) - - return - } - - // The model is not empty, so we expect to dequeue an item. - expectedItem := m.modelQueue[0] - m.modelQueue = m.modelQueue[1:] - - // Perform the dequeue operation, this should succeed. - result := m.queue.Dequeue(context.Background()) - actualItem, err := result.Unpack() - require.NoError(t, err) - require.Equal( - m.tb, expectedItem, actualItem, "dequeued item does not "+ - "match model (FIFO violation or model error)", - ) -} - -// Check is called by rapid after each action to verify invariants. -func (m *queueMachine[T]) Check(t *rapid.T) { - // Invariant 1: The length of the internal channel must not exceed - // capacity. - require.LessOrEqual( - m.tb, len(m.queue.ch), m.capacity, - "queue channel length exceeds capacity", - ) - - // Invariant 2: The length of our model queue must match the length of - // the actual queue's channel. - require.Equal( - m.tb, len(m.modelQueue), len(m.queue.ch), - "model queue length mismatch with actual queue channel length", - ) -} - -// intQueueMachine is a concrete wrapper for queueMachine[int] for rapid. -type intQueueMachine struct { - *queueMachine[int] -} - -// NewIntQueueMachine creates a new queueMachine specialized for int items. -func NewIntQueueMachine(rt *rapid.T) *intQueueMachine { - // Draw from the rapid distribution for the made params of our queue. - capacity := rapid.IntRange(1, 50).Draw(rt, "capacity") - minThreshold := rapid.IntRange( - 0, capacity-1, - ).Draw(rt, "minThreshold") - maxThreshold := rapid.IntRange( - minThreshold+1, capacity, - ).Draw(rt, "maxThreshold") - - // Draw a seed for this machine's local RNG using rapid. This makes the - // predicate's randomness part of rapid's generated test case. - machineSeed := rapid.Int64().Draw(rt, "machine_rng_seed") - localRngFixed := rand.New(rand.NewSource(machineSeed)) - - rt.Logf("NewIntQueueMachine: capacity=%d, minT=%d, maxT=%d, "+ - "machineSeed=%d", capacity, minThreshold, maxThreshold, - machineSeed) - - redCheck, err := RandomEarlyDrop( - minThreshold, maxThreshold, - WithRandSource(localRngFixed.Float64), - ) - require.NoError(rt, err) - predicate := AsDropPredicate[int](redCheck) - - q := NewBackpressureQueue(capacity, predicate) - - return &intQueueMachine{ - queueMachine: &queueMachine[int]{ - tb: rt, - capacity: capacity, - queue: q, - modelQueue: make([]int, 0, capacity), - dropPredicate: predicate, - itemGenerator: rapid.IntRange(-1000, 1000), - }, - } -} - -// Enqueue forwards the call to the generic queueMachine. -func (m *intQueueMachine) Enqueue(t *rapid.T) { m.queueMachine.Enqueue(t) } - -// Dequeue forwards the call to the generic queueMachine. -func (m *intQueueMachine) Dequeue(t *rapid.T) { m.queueMachine.Dequeue(t) } - -// Check forwards the call to the generic queueMachine. -func (m *intQueueMachine) Check(t *rapid.T) { m.queueMachine.Check(t) } - -// TestBackpressureQueueRapidInt is the main property-based test for -// BackpressureQueue using the IntQueueMachine state machine. -func TestBackpressureQueueRapidInt(t *testing.T) { - rapid.Check(t, func(rt *rapid.T) { - // Initialize the state machine instance within the property - // function. NewIntQueueMachine expects *rapid.T, which rt is. - machine := NewIntQueueMachine(rt) - - // Generate the actions map from the machine's methods. Rapid - // will randomly call the methods, and then use the `Check` - // method to verify invariants. - rt.Repeat(rapid.StateMachineActions(machine)) - }) -} - -// TestBackpressureQueueEnqueueCancellation tests that Enqueue respects context -// cancellation when it would otherwise block. -func TestBackpressureQueueEnqueueCancellation(t *testing.T) { - rapid.Check(t, func(rt *rapid.T) { - capacity := rapid.IntRange(1, 20).Draw(rt, "capacity") - - // Use a predicate that never drops when full, to force blocking - // behavior. - q := NewBackpressureQueue(capacity, - func(_ int, _ int) bool { return false }, - ) - - // Fill the queue to its capacity. The predicate always returns - // false, so no drops expected. - for i := range capacity { - err := q.Enqueue(context.Background(), i) - require.NoError( - rt, err, "enqueue failed during setup: %v", err, - ) - } - require.Equal( - rt, capacity, len(q.ch), "queue "+ - "should be full after setup", - ) - - // Attempt to enqueue one more item with an immediately cancelled - // context. - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - err := q.Enqueue(ctx, 999) - require.Error( - rt, err, "enqueue should have "+ - "returned an error for cancelled context", - ) - require.ErrorIs( - rt, err, context.Canceled, - "error should be context.Canceled", - ) - - // Ensure the queue state (length) is unchanged. - require.Equal( - rt, capacity, len(q.ch), "queue length changed "+ - "after cancelled enqueue attempt", - ) - }) -} - -// TestBackpressureQueueDequeueCancellation tests that Dequeue respects context -// cancellation when the queue is empty and it would otherwise block. -func TestBackpressureQueueDequeueCancellation(t *testing.T) { - rapid.Check(t, func(rt *rapid.T) { - capacity := rapid.IntRange(1, 20).Draw(rt, "capacity") - - // The predicate doesn't matter much here as the queue will be - // empty. Use a never-drop predicate for simplicity. - q := NewBackpressureQueue(capacity, - func(_ int, _ int) bool { return false }, - ) - - require.Zero( - rt, len(q.ch), "queue should be empty initially for "+ - "Dequeue cancellation test", - ) - - // Attempt to dequeue from the empty queue with an immediately - // cancelled context. - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - result := q.Dequeue(ctx) - require.ErrorIs( - rt, result.UnwrapRightOr(nil), - context.Canceled, - "error should be context.Canceled", - ) - }) -} - -// TestBackpressureQueueComposedPredicate demonstrates testing with a composed -// predicate. This is a scenario-based test rather than a full property-based -// state machine. -func TestBackpressureQueueComposedPredicate(t *testing.T) { - capacity := 10 - minThresh, maxThresh := 3, 7 - - // Use a deterministic random source for this specific test case to - // ensure predictable behavior of RandomEarlyDrop. - const testSeed = int64(12345) - localRng := rand.New(rand.NewSource(testSeed)) - - redCheck, err := RandomEarlyDrop( - minThresh, maxThresh, WithRandSource(localRng.Float64), - ) - require.NoError(t, err) - - // Next, we'll define a custom predicate: drop items with value 42. - customValuePredicate := func(_ int, item int) bool { - return item == 42 - } - - // We'll also make a composed predicate: drop if RED says so OR if item - // is 42. - composedPredicate := func(queueLen int, item int) bool { - isRedDrop := redCheck(queueLen) - isCustomDrop := customValuePredicate(queueLen, item) - return isRedDrop || isCustomDrop - } - - q := NewBackpressureQueue(capacity, composedPredicate) - - // Scenario 1: Enqueue item 42 when queue length is between min/max - // thresholds. As we're below the max threshold, we shouldn't drop - // anything. - for i := range minThresh { - // All items aren't 42, and queue is not full enough for RED to - // drop. - err := q.Enqueue(context.Background(), i) - require.NoErrorf(t, err, "enqueue S1 setup "+ - "item %d (qLen before: %d) should not be dropped. "+ - "Predicate was redCheck(%d) || customPred(%d,%d)", - i, len(q.ch)-1, len(q.ch)-1, len(q.ch)-1, i) - - } - - currentLen := len(q.ch) - require.Equal(t, minThresh, currentLen, "queue length after S1 setup") - - // Enqueue item 42. customValuePredicate is true, so composedPredicate - // is true. Item 42 should be dropped regardless of what redCheck - // decides. - err = q.Enqueue(context.Background(), 42) - require.ErrorIs( - t, err, ErrItemDropped, - "item 42 should have been dropped by composed predicate", - ) - require.Equal( - t, currentLen, len(q.ch), "queue length should not change "+ - "after dropping 42", - ) - - // Re-create the main SUT queue with the composedPredicate. We will - // manually fill its channel to capacity to bypass Enqueue logic for - // setup. - q = NewBackpressureQueue(capacity, composedPredicate) - for i := range capacity { - q.ch <- i - } - require.Equal( - t, capacity, len(q.ch), "queue manually filled to capacity "+ - "for S2 test", - ) - - err = q.Enqueue(context.Background(), 100) - - // Expect drop because queue is full (len=capacity), so - // redCheck(capacity) is true. customValuePredicate(capacity, 100) - // is false. Thus, composedPredicate should be true. - require.ErrorIs( - t, err, ErrItemDropped, - "item 100 should be dropped (due to RED part "+ - "of composed predicate) when queue full", - ) - require.Equal( - t, capacity, len(q.ch), "queue length should not change "+ - "after dropping 100", - ) -} - -// TestBackpressureQueueTryEnqueue verifies non-blocking enqueue with drop -// predicate checks. -func TestBackpressureQueueTryEnqueue(t *testing.T) { - t.Parallel() - - const capacity = 5 - const dropThreshold = 3 - - alwaysDropAboveThreshold := DropPredicate[int]( - func(queueLen int, _ int) bool { - return queueLen >= dropThreshold - }, - ) - - q := NewBackpressureQueue(capacity, alwaysDropAboveThreshold) - - // Fill up to the drop threshold — all should succeed. - for i := range dropThreshold { - ok := q.TryEnqueue(i) - require.True(t, ok, "TryEnqueue(%d) should succeed", i) - } - - require.Equal(t, dropThreshold, q.Len()) - - // Next TryEnqueue should be dropped by predicate. - ok := q.TryEnqueue(99) - require.False(t, ok, "should be dropped at threshold") - require.Equal(t, dropThreshold, q.Len()) - - // With a never-drop predicate, fill to capacity and verify TryEnqueue - // returns false when the channel is full. - q2 := NewBackpressureQueue(capacity, - func(_ int, _ int) bool { return false }, - ) - for i := range capacity { - ok := q2.TryEnqueue(i) - require.True(t, ok, "TryEnqueue(%d) should succeed", i) - } - ok = q2.TryEnqueue(999) - require.False(t, ok, "TryEnqueue should fail when channel is full") -} - -// TestBackpressureQueueLenAndReceiveChan verifies Len and ReceiveChan. -func TestBackpressureQueueLenAndReceiveChan(t *testing.T) { - t.Parallel() - - neverDrop := DropPredicate[int](func(_ int, _ int) bool { - return false - }) - q := NewBackpressureQueue(10, neverDrop) - - require.Equal(t, 0, q.Len()) - - for i := range 3 { - require.NoError(t, q.Enqueue(context.Background(), i)) - } - require.Equal(t, 3, q.Len()) - - // ReceiveChan should yield the items. - ch := q.ReceiveChan() - val := <-ch - require.Equal(t, 0, val) - require.Equal(t, 2, q.Len()) -} - -// TestBackpressureQueueClose verifies that Close shuts down the channel. -func TestBackpressureQueueClose(t *testing.T) { - t.Parallel() - - neverDrop := DropPredicate[int](func(_ int, _ int) bool { - return false - }) - q := NewBackpressureQueue(10, neverDrop) - - for i := range 3 { - require.NoError(t, q.Enqueue(context.Background(), i)) - } - - q.Close() - - // Remaining items should still be readable. - var items []int - for v := range q.ReceiveChan() { - items = append(items, v) - } - require.Equal(t, []int{0, 1, 2}, items) -} - -// TestBackpressureQueueDoubleClose verifies that calling Close twice does not -// panic. -func TestBackpressureQueueDoubleClose(t *testing.T) { - t.Parallel() - - neverDrop := DropPredicate[int](func(_ int, _ int) bool { - return false - }) - q := NewBackpressureQueue(5, neverDrop) - - q.Close() - q.Close() // must not panic -} - -// TestBackpressureQueueEnqueueAfterClose verifies that Enqueue returns -// ErrQueueClosed after the queue has been closed. -func TestBackpressureQueueEnqueueAfterClose(t *testing.T) { - t.Parallel() - - neverDrop := DropPredicate[int](func(_ int, _ int) bool { - return false - }) - q := NewBackpressureQueue(5, neverDrop) - - require.NoError(t, q.Enqueue(context.Background(), 1)) - q.Close() - - err := q.Enqueue(context.Background(), 2) - require.ErrorIs(t, err, ErrQueueClosed) -} - -// TestBackpressureQueueTryEnqueueAfterClose verifies that TryEnqueue returns -// false after the queue has been closed. -func TestBackpressureQueueTryEnqueueAfterClose(t *testing.T) { - t.Parallel() - - neverDrop := DropPredicate[int](func(_ int, _ int) bool { - return false - }) - q := NewBackpressureQueue(5, neverDrop) - - q.Close() - - ok := q.TryEnqueue(1) - require.False(t, ok, "TryEnqueue after Close should return false") -} diff --git a/queue/go.mod b/queue/go.mod index 36cc31f33..590bd7d68 100644 --- a/queue/go.mod +++ b/queue/go.mod @@ -1,22 +1,7 @@ module github.com/lightningnetwork/lnd/queue -go 1.25.11 +require github.com/lightningnetwork/lnd/ticker v1.0.0 -require ( - github.com/lightningnetwork/lnd/fn/v2 v2.0.8 - github.com/lightningnetwork/lnd/ticker v1.0.0 - github.com/stretchr/testify v1.8.1 - pgregory.net/rapid v1.2.0 -) +replace github.com/lightningnetwork/lnd/ticker v1.0.0 => ../ticker -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/exp v0.0.0-20231226003508-02704c960a9b // indirect - golang.org/x/sync v0.7.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) - -replace github.com/lightningnetwork/lnd/ticker => ../ticker - -replace github.com/lightningnetwork/lnd/fn/v2 => ../fn +go 1.24.11 diff --git a/queue/go.sum b/queue/go.sum index 8368c6325..e69de29bb 100644 --- a/queue/go.sum +++ b/queue/go.sum @@ -1,23 +0,0 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4= -golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= -pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/record/blinded_data.go b/record/blinded_data.go index 59929577d..3d9b17c27 100644 --- a/record/blinded_data.go +++ b/record/blinded_data.go @@ -6,7 +6,6 @@ import ( "io" "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/tlv" ) @@ -32,9 +31,7 @@ type BlindedRouteData struct { // NextNodeID is the node ID of the next node on the path. In the // context of blinded path payments, this is used to indicate the - // presence of dummy hops that need to be peeled from the onion, or to - // identify a real next-node forwarding target when the public key is - // not ours. + // presence of dummy hops that need to be peeled from the onion. NextNodeID tlv.OptionalRecordT[tlv.TlvType4, *btcec.PublicKey] // PathID is a secret set of bytes that the blinded path creator will @@ -93,47 +90,6 @@ func NewNonFinalBlindedRouteData(chanID lnwire.ShortChannelID, return info } -// NewNonFinalBlindedRouteData creates the data that's provided for hops within -// a blinded route. -func NewNonFinalBlindedRouteDataOnionMessage( - nextNode fn.Either[*btcec.PublicKey, lnwire.ShortChannelID], - blindingOverride *btcec.PublicKey, - features *lnwire.FeatureVector) *BlindedRouteData { - - info := fn.ElimEither( - nextNode, - func(nextNodeID *btcec.PublicKey) *BlindedRouteData { - return &BlindedRouteData{ - NextNodeID: tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType4]( - nextNodeID, - ), - ), - } - }, - func(chanID lnwire.ShortChannelID) *BlindedRouteData { - return &BlindedRouteData{ - ShortChannelID: tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType2](chanID), - ), - } - }, - ) - - if blindingOverride != nil { - info.NextBlindingOverride = tlv.SomeRecordT( - tlv.NewPrimitiveRecord[tlv.TlvType8](blindingOverride)) - } - - if features != nil { - info.Features = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType14](*features), - ) - } - - return info -} - // NewFinalHopBlindedRouteData creates the data that's provided for the final // hop in a blinded route. func NewFinalHopBlindedRouteData(constraints *PaymentConstraints, diff --git a/record/blinded_data_test.go b/record/blinded_data_test.go index 0620e9ba5..bc1230be8 100644 --- a/record/blinded_data_test.go +++ b/record/blinded_data_test.go @@ -79,6 +79,7 @@ func TestBlindedDataEncoding(t *testing.T) { } for _, testCase := range tests { + testCase := testCase t.Run(testCase.name, func(t *testing.T) { t.Parallel() @@ -142,6 +143,7 @@ func TestBlindedDataFinalHopEncoding(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/record/hop.go b/record/hop.go index baedb3187..e5c0884f1 100644 --- a/record/hop.go +++ b/record/hop.go @@ -6,8 +6,6 @@ import ( ) const ( - // Onion Routing Packet types. - // AmtOnionType is the type used in the onion to reference the amount to // send to the next hop. AmtOnionType tlv.Type = 2 @@ -35,28 +33,6 @@ const ( // TotalAmtMsatBlindedType is the type used in the onion for the total // amount field that is included in the final hop for blinded payments. TotalAmtMsatBlindedType tlv.Type = 18 - - // Onion Message Packet types. - - // ReplyPathType is the type used in the onion message to indicate the - // blinded path to be used for replies. - ReplyPathType tlv.Type = 2 - - // EncryptedDataTLVType is the type used in the onion message to - // include encrypted data in the onion for use in blinded paths. - EncryptedDataTLVType tlv.Type = 4 - - // InvoiceRequestNamespaceType is the type used in the onion message to - // include invoice requests. - InvoiceRequestNamespaceType tlv.Type = 64 - - // InvoiceNamespaceType is the type used in the onion message to include - // invoices. - InvoiceNamespaceType tlv.Type = 66 - - // InvoiceErrorNamespaceType is the type used in the onion message to - // include invoice errors. - InvoiceErrorNamespaceType tlv.Type = 68 ) // NewAmtToFwdRecord creates a tlv.Record that encodes the amount_to_forward @@ -93,36 +69,6 @@ func NewEncryptedDataRecord(data *[]byte) tlv.Record { return tlv.MakePrimitiveRecord(EncryptedDataOnionType, data) } -// NewEncryptedRecipientDataRecord creates a tlv.Record that encodes the -// encrypted_data (type 4) record for an onion message payload. -func NewEncryptedRecipientDataRecord(data *[]byte) tlv.Record { - return tlv.MakePrimitiveRecord(EncryptedDataTLVType, data) -} - -// NewReplyPathRecord creates a tlv.Record that encodes the reply_path (type 2) -// record for an onion message payload. -func NewReplyPathRecord(data *[]byte) tlv.Record { - return tlv.MakePrimitiveRecord(ReplyPathType, data) -} - -// NewInvoiceRequestRecord creates a tlv.Record that encodes the -// invoice_request (type 64) record for an onion message payload. -func NewInvoiceRequestRecord(data *[]byte) tlv.Record { - return tlv.MakePrimitiveRecord(InvoiceRequestNamespaceType, data) -} - -// NewInvoiceRecord creates a tlv.Record that encodes the -// invoice (type 66) record for an onion message payload. -func NewInvoiceRecord(data *[]byte) tlv.Record { - return tlv.MakePrimitiveRecord(InvoiceNamespaceType, data) -} - -// NewInvoiceErrorRecord creates a tlv.Record that encodes the -// invoice_error (type 68) record for an onion message payload. -func NewInvoiceErrorRecord(data *[]byte) tlv.Record { - return tlv.MakePrimitiveRecord(InvoiceErrorNamespaceType, data) -} - // NewBlindingPointRecord creates a tlv.Record that encodes the blinding_point // (type 12) record for an onion payload. func NewBlindingPointRecord(point **btcec.PublicKey) tlv.Record { diff --git a/record/record_test.go b/record/record_test.go index 92902fa31..45faa9f73 100644 --- a/record/record_test.go +++ b/record/record_test.go @@ -73,6 +73,7 @@ var recordEncDecTests = []recordEncDecTest{ // the original record matches the decoded record. func TestRecordEncodeDecode(t *testing.T) { for _, test := range recordEncDecTests { + test := test t.Run(test.name, func(t *testing.T) { r := test.encRecord() r2 := test.decRecord() diff --git a/routing/additional_edge_test.go b/routing/additional_edge_test.go index 34fd2197a..0324e2e10 100644 --- a/routing/additional_edge_test.go +++ b/routing/additional_edge_test.go @@ -68,6 +68,7 @@ func TestIntermediatePayloadSize(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.name, func(t *testing.T) { t.Parallel() diff --git a/routing/bandwidth.go b/routing/bandwidth.go index 32bc45e2e..df68cea4f 100644 --- a/routing/bandwidth.go +++ b/routing/bandwidth.go @@ -1,7 +1,6 @@ package routing import ( - "context" "fmt" "github.com/lightningnetwork/lnd/fn/v2" @@ -65,8 +64,7 @@ func newBandwidthManager(graph Graph, sourceNode route.Vertex, // First, we'll collect the set of outbound edges from the target // source node and add them to our bandwidth manager's map of channels. err := graph.ForEachNodeDirectedChannel( - context.TODO(), sourceNode, - func(channel *graphdb.DirectedChannel) error { + sourceNode, func(channel *graphdb.DirectedChannel) error { shortID := lnwire.NewShortChanIDFromInt( channel.ChannelID, ) diff --git a/routing/bandwidth_test.go b/routing/bandwidth_test.go index 8c08d2986..b7f6e3f13 100644 --- a/routing/bandwidth_test.go +++ b/routing/bandwidth_test.go @@ -4,7 +4,7 @@ import ( "errors" "testing" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lnwallet" @@ -102,6 +102,7 @@ func TestBandwidthManager(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.name, func(t *testing.T) { g := newMockGraph(t) diff --git a/routing/blindedpath/blinded_path.go b/routing/blindedpath/blinded_path.go index 044a87e65..ce5a1420c 100644 --- a/routing/blindedpath/blinded_path.go +++ b/routing/blindedpath/blinded_path.go @@ -8,9 +8,9 @@ import ( "sort" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" sphinx "github.com/lightningnetwork/lightning-onion" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" @@ -46,7 +46,7 @@ type BuildBlindedPathCfg struct { *models.ChannelEdgePolicy, *models.ChannelEdgePolicy, error) // FetchOurOpenChannels fetches this node's set of open channels. - FetchOurOpenChannels func() ([]*chanstate.OpenChannel, error) + FetchOurOpenChannels func() ([]*channeldb.OpenChannel, error) // BestHeight can be used to fetch the best block height that this node // is aware of. @@ -529,7 +529,7 @@ func buildDummyRouteData(node route.Vertex, relayInfo *record.PaymentRelayInfo, // we use the provided default policy values, and we get the average capacity of // this node's channels to compute a MaxHTLC value. func computeDummyHopPolicy(defaultPolicy *BlindedHopPolicy, - fetchOurChannels func() ([]*chanstate.OpenChannel, error), + fetchOurChannels func() ([]*channeldb.OpenChannel, error), policies map[uint64]*BlindedHopPolicy) (*BlindedHopPolicy, error) { numPolicies := len(policies) diff --git a/routing/blindedpath/blinded_path_test.go b/routing/blindedpath/blinded_path_test.go index 3f4cb7597..0020f381c 100644 --- a/routing/blindedpath/blinded_path_test.go +++ b/routing/blindedpath/blinded_path_test.go @@ -161,6 +161,7 @@ func TestApplyBlindedPathPolicyBuffer(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -350,6 +351,7 @@ func TestPadBlindedHopInfo(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/routing/blinding_test.go b/routing/blinding_test.go index 2ca9b631d..0a8846adb 100644 --- a/routing/blinding_test.go +++ b/routing/blinding_test.go @@ -64,6 +64,7 @@ func TestBlindedPathValidation(t *testing.T) { } for _, testCase := range tests { + testCase := testCase t.Run(testCase.name, func(t *testing.T) { t.Parallel() diff --git a/routing/chainview/bitcoind.go b/routing/chainview/bitcoind.go index 33a6a96ea..b528091ac 100644 --- a/routing/chainview/bitcoind.go +++ b/routing/chainview/bitcoind.go @@ -2,15 +2,14 @@ package chainview import ( "bytes" - "context" "encoding/hex" "fmt" "sync" "sync/atomic" "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/wtxmgr" "github.com/lightningnetwork/lnd/blockcache" @@ -94,7 +93,7 @@ func (b *BitcoindFilteredChainView) Start() error { log.Infof("FilteredChainView starting") - err := b.chainClient.Start(context.Background()) + err := b.chainClient.Start() if err != nil { return err } diff --git a/routing/chainview/btcd.go b/routing/chainview/btcd.go index 65cc87be3..2a06fd179 100644 --- a/routing/chainview/btcd.go +++ b/routing/chainview/btcd.go @@ -7,12 +7,11 @@ import ( "sync" "sync/atomic" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/blockcache" graphdb "github.com/lightningnetwork/lnd/graph/db" ) @@ -344,12 +343,8 @@ func (b *BtcdFilteredChainView) chainFilterer() { // Apply the new TX filter to btcd, which will cause // all following notifications from and calls to it // return blocks filtered with the new filter. - err := b.btcdConn.LoadTxFilter( - false, []address.Address{}, update.newUtxos, - ) - if err != nil { - log.Errorf("Unable to load tx filter: %v", err) - } + b.btcdConn.LoadTxFilter(false, []btcutil.Address{}, + update.newUtxos) // All blocks gotten after we loaded the filter will // have the filter applied, but we will need to rescan diff --git a/routing/chainview/interface.go b/routing/chainview/interface.go index 67a8954be..454c2ee61 100644 --- a/routing/chainview/interface.go +++ b/routing/chainview/interface.go @@ -1,8 +1,8 @@ package chainview import ( - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" graphdb "github.com/lightningnetwork/lnd/graph/db" ) diff --git a/routing/chainview/interface_test.go b/routing/chainview/interface_test.go index b3d187c92..ecd8bd597 100644 --- a/routing/chainview/interface_test.go +++ b/routing/chainview/interface_test.go @@ -8,16 +8,15 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/walletdb" _ "github.com/btcsuite/btcwallet/walletdb/bdb" // Required to register the boltdb walletdb implementation. "github.com/lightninglabs/neutrino" @@ -40,7 +39,7 @@ var ( } privKey, pubKey = btcec.PrivKeyFromBytes(testPrivKey) - addrPk, _ = address.NewAddressPubKey(pubKey.SerializeCompressed(), + addrPk, _ = btcutil.NewAddressPubKey(pubKey.SerializeCompressed(), netParams) testAddr = addrPk.AddressPubKeyHash() @@ -780,7 +779,7 @@ var interfaceImpls = []struct { // Wait until the node has fully synced up to the local // btcd node. err = wait.NoError(func() error { - err := spvNode.Start(t.Context()) + err := spvNode.Start() if err != nil { return err } diff --git a/routing/chainview/neutrino.go b/routing/chainview/neutrino.go index b05d75df4..8a6d41836 100644 --- a/routing/chainview/neutrino.go +++ b/routing/chainview/neutrino.go @@ -5,11 +5,11 @@ import ( "sync" "sync/atomic" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/btcutil/v2/gcs/builder" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/gcs/builder" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/neutrino" "github.com/lightningnetwork/lnd/blockcache" graphdb "github.com/lightningnetwork/lnd/graph/db" diff --git a/routing/control_tower.go b/routing/control_tower.go index 791faa516..2b9e7dd9d 100644 --- a/routing/control_tower.go +++ b/routing/control_tower.go @@ -1,7 +1,6 @@ package routing import ( - "context" "sync" "github.com/lightningnetwork/lnd/lntypes" @@ -20,19 +19,17 @@ type ControlTower interface { // also notifies subscribers of the payment creation. // // NOTE: Subscribers should be notified by the new state of the payment. - InitPayment(context.Context, lntypes.Hash, - *paymentsdb.PaymentCreationInfo) error + InitPayment(lntypes.Hash, *paymentsdb.PaymentCreationInfo) error // DeleteFailedAttempts removes all failed HTLCs from the db. It should // be called for a given payment whenever all inflight htlcs are // completed, and the payment has reached a final settled state. - DeleteFailedAttempts(context.Context, lntypes.Hash) error + DeleteFailedAttempts(lntypes.Hash) error // RegisterAttempt atomically records the provided HTLCAttemptInfo. // // NOTE: Subscribers should be notified by the new state of the payment. - RegisterAttempt(context.Context, lntypes.Hash, - *paymentsdb.HTLCAttemptInfo) error + RegisterAttempt(lntypes.Hash, *paymentsdb.HTLCAttemptInfo) error // SettleAttempt marks the given attempt settled with the preimage. If // this is a multi shard payment, this might implicitly mean the the @@ -44,19 +41,18 @@ type ControlTower interface { // for record keeping. // // NOTE: Subscribers should be notified by the new state of the payment. - SettleAttempt(context.Context, lntypes.Hash, uint64, - *paymentsdb.HTLCSettleInfo) (*paymentsdb.HTLCAttempt, error) + SettleAttempt(lntypes.Hash, uint64, *paymentsdb.HTLCSettleInfo) ( + *paymentsdb.HTLCAttempt, error) // FailAttempt marks the given payment attempt failed. // // NOTE: Subscribers should be notified by the new state of the payment. - FailAttempt(context.Context, lntypes.Hash, uint64, - *paymentsdb.HTLCFailInfo) (*paymentsdb.HTLCAttempt, error) + FailAttempt(lntypes.Hash, uint64, *paymentsdb.HTLCFailInfo) ( + *paymentsdb.HTLCAttempt, error) // FetchPayment fetches the payment corresponding to the given payment // hash. - FetchPayment(ctx context.Context, - paymentHash lntypes.Hash) (paymentsdb.DBMPPayment, error) + FetchPayment(paymentHash lntypes.Hash) (paymentsdb.DBMPPayment, error) // FailPayment transitions a payment into the Failed state, and records // the ultimate reason the payment failed. Note that this should only @@ -66,12 +62,10 @@ type ControlTower interface { // payment. // // NOTE: Subscribers should be notified by the new state of the payment. - FailPayment(context.Context, lntypes.Hash, - paymentsdb.FailureReason) error + FailPayment(lntypes.Hash, paymentsdb.FailureReason) error // FetchInFlightPayments returns all payments with status InFlight. - FetchInFlightPayments(ctx context.Context) ([]*paymentsdb.MPPayment, - error) + FetchInFlightPayments() ([]*paymentsdb.MPPayment, error) // SubscribePayment subscribes to updates for the payment with the given // hash. A first update with the current state of the payment is always @@ -167,10 +161,10 @@ func NewControlTower(db paymentsdb.DB) ControlTower { // making sure it does not already exist as an in-flight payment. Then this // method returns successfully, the payment is guaranteed to be in the // Initiated state. -func (p *controlTower) InitPayment(ctx context.Context, - paymentHash lntypes.Hash, info *paymentsdb.PaymentCreationInfo) error { +func (p *controlTower) InitPayment(paymentHash lntypes.Hash, + info *paymentsdb.PaymentCreationInfo) error { - err := p.db.InitPayment(ctx, paymentHash, info) + err := p.db.InitPayment(paymentHash, info) if err != nil { return err } @@ -180,7 +174,7 @@ func (p *controlTower) InitPayment(ctx context.Context, p.paymentsMtx.Lock(paymentHash) defer p.paymentsMtx.Unlock(paymentHash) - payment, err := p.db.FetchPayment(ctx, paymentHash) + payment, err := p.db.FetchPayment(paymentHash) if err != nil { return err } @@ -192,21 +186,19 @@ func (p *controlTower) InitPayment(ctx context.Context, // DeleteFailedAttempts deletes all failed htlcs if the payment was // successfully settled. -func (p *controlTower) DeleteFailedAttempts(ctx context.Context, - paymentHash lntypes.Hash) error { - - return p.db.DeleteFailedAttempts(ctx, paymentHash) +func (p *controlTower) DeleteFailedAttempts(paymentHash lntypes.Hash) error { + return p.db.DeleteFailedAttempts(paymentHash) } // RegisterAttempt atomically records the provided HTLCAttemptInfo to the // DB. -func (p *controlTower) RegisterAttempt(ctx context.Context, - paymentHash lntypes.Hash, attempt *paymentsdb.HTLCAttemptInfo) error { +func (p *controlTower) RegisterAttempt(paymentHash lntypes.Hash, + attempt *paymentsdb.HTLCAttemptInfo) error { p.paymentsMtx.Lock(paymentHash) defer p.paymentsMtx.Unlock(paymentHash) - payment, err := p.db.RegisterAttempt(ctx, paymentHash, attempt) + payment, err := p.db.RegisterAttempt(paymentHash, attempt) if err != nil { return err } @@ -220,17 +212,14 @@ func (p *controlTower) RegisterAttempt(ctx context.Context, // SettleAttempt marks the given attempt settled with the preimage. If // this is a multi shard payment, this might implicitly mean the the // full payment succeeded. -func (p *controlTower) SettleAttempt(ctx context.Context, - paymentHash lntypes.Hash, attemptID uint64, - settleInfo *paymentsdb.HTLCSettleInfo) (*paymentsdb.HTLCAttempt, - error) { +func (p *controlTower) SettleAttempt(paymentHash lntypes.Hash, + attemptID uint64, settleInfo *paymentsdb.HTLCSettleInfo) ( + *paymentsdb.HTLCAttempt, error) { p.paymentsMtx.Lock(paymentHash) defer p.paymentsMtx.Unlock(paymentHash) - payment, err := p.db.SettleAttempt( - ctx, paymentHash, attemptID, settleInfo, - ) + payment, err := p.db.SettleAttempt(paymentHash, attemptID, settleInfo) if err != nil { return nil, err } @@ -242,14 +231,14 @@ func (p *controlTower) SettleAttempt(ctx context.Context, } // FailAttempt marks the given payment attempt failed. -func (p *controlTower) FailAttempt(ctx context.Context, - paymentHash lntypes.Hash, attemptID uint64, - failInfo *paymentsdb.HTLCFailInfo) (*paymentsdb.HTLCAttempt, error) { +func (p *controlTower) FailAttempt(paymentHash lntypes.Hash, + attemptID uint64, failInfo *paymentsdb.HTLCFailInfo) ( + *paymentsdb.HTLCAttempt, error) { p.paymentsMtx.Lock(paymentHash) defer p.paymentsMtx.Unlock(paymentHash) - payment, err := p.db.FailAttempt(ctx, paymentHash, attemptID, failInfo) + payment, err := p.db.FailAttempt(paymentHash, attemptID, failInfo) if err != nil { return nil, err } @@ -261,11 +250,10 @@ func (p *controlTower) FailAttempt(ctx context.Context, } // FetchPayment fetches the payment corresponding to the given payment hash. -func (p *controlTower) FetchPayment(ctx context.Context, - paymentHash lntypes.Hash) ( +func (p *controlTower) FetchPayment(paymentHash lntypes.Hash) ( paymentsdb.DBMPPayment, error) { - return p.db.FetchPayment(ctx, paymentHash) + return p.db.FetchPayment(paymentHash) } // FailPayment transitions a payment into the Failed state, and records the @@ -275,13 +263,13 @@ func (p *controlTower) FetchPayment(ctx context.Context, // // NOTE: This method will overwrite the failure reason if the payment is already // failed. -func (p *controlTower) FailPayment(ctx context.Context, - paymentHash lntypes.Hash, reason paymentsdb.FailureReason) error { +func (p *controlTower) FailPayment(paymentHash lntypes.Hash, + reason paymentsdb.FailureReason) error { p.paymentsMtx.Lock(paymentHash) defer p.paymentsMtx.Unlock(paymentHash) - payment, err := p.db.Fail(ctx, paymentHash, reason) + payment, err := p.db.Fail(paymentHash, reason) if err != nil { return err } @@ -293,10 +281,10 @@ func (p *controlTower) FailPayment(ctx context.Context, } // FetchInFlightPayments returns all payments with status InFlight. -func (p *controlTower) FetchInFlightPayments( - ctx context.Context) ([]*paymentsdb.MPPayment, error) { +func (p *controlTower) FetchInFlightPayments() ([]*paymentsdb.MPPayment, + error) { - return p.db.FetchInFlightPayments(ctx) + return p.db.FetchInFlightPayments() } // SubscribePayment subscribes to updates for the payment with the given hash. A @@ -305,14 +293,12 @@ func (p *controlTower) FetchInFlightPayments( func (p *controlTower) SubscribePayment(paymentHash lntypes.Hash) ( ControlTowerSubscriber, error) { - ctx := context.TODO() - // Take lock before querying the db to prevent missing or duplicating an // update. p.paymentsMtx.Lock(paymentHash) defer p.paymentsMtx.Unlock(paymentHash) - payment, err := p.db.FetchPayment(ctx, paymentHash) + payment, err := p.db.FetchPayment(paymentHash) if err != nil { return nil, err } @@ -349,8 +335,6 @@ func (p *controlTower) SubscribePayment(paymentHash lntypes.Hash) ( func (p *controlTower) SubscribeAllPayments() (ControlTowerSubscriber, error) { subscriber := newControlTowerSubscriber() - ctx := context.TODO() - // Add the subscriber to the list before fetching in-flight payments, so // no events are missed. If a payment attempt update occurs after // appending and before fetching in-flight payments, an out-of-order @@ -362,12 +346,12 @@ func (p *controlTower) SubscribeAllPayments() (ControlTowerSubscriber, error) { p.subscribersMtx.Unlock() log.Debugf("Scanning for inflight payments") - inflightPayments, err := p.db.FetchInFlightPayments(ctx) + inflightPayments, err := p.db.FetchInFlightPayments() if err != nil { return nil, err } - log.Debugf("Scanning for inflight payments finished: "+ - "found_inflight=%d", len(inflightPayments)) + log.Debugf("Scanning for inflight payments finished", + len(inflightPayments)) for index := range inflightPayments { // Always write current payment state to the channel. diff --git a/routing/control_tower_test.go b/routing/control_tower_test.go index 697770ff5..de0aacf88 100644 --- a/routing/control_tower_test.go +++ b/routing/control_tower_test.go @@ -50,7 +50,10 @@ func TestControlTowerSubscribeUnknown(t *testing.T) { db := initDB(t) - paymentDB, err := paymentsdb.NewKVStore(db) + paymentDB, err := paymentsdb.NewKVStore( + db, + paymentsdb.WithKeepFailedPaymentAttempts(true), + ) require.NoError(t, err) pControl := NewControlTower(paymentDB) @@ -78,7 +81,7 @@ func TestControlTowerSubscribeSuccess(t *testing.T) { t.Fatal(err) } - err = pControl.InitPayment(t.Context(), info.PaymentIdentifier, info) + err = pControl.InitPayment(info.PaymentIdentifier, info) if err != nil { t.Fatal(err) } @@ -89,9 +92,7 @@ func TestControlTowerSubscribeSuccess(t *testing.T) { require.NoError(t, err, "expected subscribe to succeed, but got") // Register an attempt. - err = pControl.RegisterAttempt( - t.Context(), info.PaymentIdentifier, attempt, - ) + err = pControl.RegisterAttempt(info.PaymentIdentifier, attempt) if err != nil { t.Fatal(err) } @@ -105,8 +106,7 @@ func TestControlTowerSubscribeSuccess(t *testing.T) { Preimage: preimg, } htlcAttempt, err := pControl.SettleAttempt( - t.Context(), info.PaymentIdentifier, attempt.AttemptID, - &settleInfo, + info.PaymentIdentifier, attempt.AttemptID, &settleInfo, ) if err != nil { t.Fatal(err) @@ -179,11 +179,17 @@ func TestControlTowerSubscribeSuccess(t *testing.T) { func TestKVStoreSubscribeFail(t *testing.T) { t.Parallel() - t.Run("register attempt", func(t *testing.T) { - testKVStoreSubscribeFail(t, true) + t.Run("register attempt, keep failed payments", func(t *testing.T) { + testKVStoreSubscribeFail(t, true, true) }) - t.Run("no register attempt", func(t *testing.T) { - testKVStoreSubscribeFail(t, false) + t.Run("register attempt, delete failed payments", func(t *testing.T) { + testKVStoreSubscribeFail(t, true, false) + }) + t.Run("no register attempt, keep failed payments", func(t *testing.T) { + testKVStoreSubscribeFail(t, false, true) + }) + t.Run("no register attempt, delete failed payments", func(t *testing.T) { + testKVStoreSubscribeFail(t, false, false) }) } @@ -194,7 +200,10 @@ func TestKVStoreSubscribeAllSuccess(t *testing.T) { db := initDB(t) - paymentDB, err := paymentsdb.NewKVStore(db) + paymentDB, err := paymentsdb.NewKVStore( + db, + paymentsdb.WithKeepFailedPaymentAttempts(true), + ) require.NoError(t, err) pControl := NewControlTower(paymentDB) @@ -203,7 +212,7 @@ func TestKVStoreSubscribeAllSuccess(t *testing.T) { info1, attempt1, preimg1, err := genInfo() require.NoError(t, err) - err = pControl.InitPayment(t.Context(), info1.PaymentIdentifier, info1) + err = pControl.InitPayment(info1.PaymentIdentifier, info1) require.NoError(t, err) // Subscription should succeed and immediately report the Initiated @@ -212,22 +221,18 @@ func TestKVStoreSubscribeAllSuccess(t *testing.T) { require.NoError(t, err, "expected subscribe to succeed, but got: %v") // Register an attempt. - err = pControl.RegisterAttempt( - t.Context(), info1.PaymentIdentifier, attempt1, - ) + err = pControl.RegisterAttempt(info1.PaymentIdentifier, attempt1) require.NoError(t, err) // Initiate a second payment after the subscription is already active. info2, attempt2, preimg2, err := genInfo() require.NoError(t, err) - err = pControl.InitPayment(t.Context(), info2.PaymentIdentifier, info2) + err = pControl.InitPayment(info2.PaymentIdentifier, info2) require.NoError(t, err) // Register an attempt on the second payment. - err = pControl.RegisterAttempt( - t.Context(), info2.PaymentIdentifier, attempt2, - ) + err = pControl.RegisterAttempt(info2.PaymentIdentifier, attempt2) require.NoError(t, err) // Mark the first payment as successful. @@ -235,8 +240,7 @@ func TestKVStoreSubscribeAllSuccess(t *testing.T) { Preimage: preimg1, } htlcAttempt1, err := pControl.SettleAttempt( - t.Context(), info1.PaymentIdentifier, attempt1.AttemptID, - &settleInfo1, + info1.PaymentIdentifier, attempt1.AttemptID, &settleInfo1, ) require.NoError(t, err) require.Equal( @@ -249,8 +253,7 @@ func TestKVStoreSubscribeAllSuccess(t *testing.T) { Preimage: preimg2, } htlcAttempt2, err := pControl.SettleAttempt( - t.Context(), info2.PaymentIdentifier, attempt2.AttemptID, - &settleInfo2, + info2.PaymentIdentifier, attempt2.AttemptID, &settleInfo2, ) require.NoError(t, err) require.Equal( @@ -322,7 +325,10 @@ func TestKVStoreSubscribeAllImmediate(t *testing.T) { db := initDB(t) - paymentDB, err := paymentsdb.NewKVStore(db) + paymentDB, err := paymentsdb.NewKVStore( + db, + paymentsdb.WithKeepFailedPaymentAttempts(true), + ) require.NoError(t, err) pControl := NewControlTower(paymentDB) @@ -331,13 +337,11 @@ func TestKVStoreSubscribeAllImmediate(t *testing.T) { info, attempt, _, err := genInfo() require.NoError(t, err) - err = pControl.InitPayment(t.Context(), info.PaymentIdentifier, info) + err = pControl.InitPayment(info.PaymentIdentifier, info) require.NoError(t, err) // Register a payment update. - err = pControl.RegisterAttempt( - t.Context(), info.PaymentIdentifier, attempt, - ) + err = pControl.RegisterAttempt(info.PaymentIdentifier, attempt) require.NoError(t, err) subscription, err := pControl.SubscribeAllPayments() @@ -370,7 +374,10 @@ func TestKVStoreUnsubscribeSuccess(t *testing.T) { db := initDB(t) - paymentDB, err := paymentsdb.NewKVStore(db) + paymentDB, err := paymentsdb.NewKVStore( + db, + paymentsdb.WithKeepFailedPaymentAttempts(true), + ) require.NoError(t, err) pControl := NewControlTower(paymentDB) @@ -385,7 +392,7 @@ func TestKVStoreUnsubscribeSuccess(t *testing.T) { info, attempt, _, err := genInfo() require.NoError(t, err) - err = pControl.InitPayment(t.Context(), info.PaymentIdentifier, info) + err = pControl.InitPayment(info.PaymentIdentifier, info) require.NoError(t, err) // Assert all subscriptions receive the update. @@ -407,9 +414,7 @@ func TestKVStoreUnsubscribeSuccess(t *testing.T) { subscription1.Close() // Register a payment update. - err = pControl.RegisterAttempt( - t.Context(), info.PaymentIdentifier, attempt, - ) + err = pControl.RegisterAttempt(info.PaymentIdentifier, attempt) require.NoError(t, err) // Assert only subscription 2 receives the update. @@ -430,8 +435,7 @@ func TestKVStoreUnsubscribeSuccess(t *testing.T) { Reason: paymentsdb.HTLCFailInternal, } _, err = pControl.FailAttempt( - t.Context(), info.PaymentIdentifier, attempt.AttemptID, - &failInfo, + info.PaymentIdentifier, attempt.AttemptID, &failInfo, ) require.NoError(t, err, "unable to fail htlc") @@ -440,10 +444,17 @@ func TestKVStoreUnsubscribeSuccess(t *testing.T) { require.Len(t, subscription2.Updates(), 0) } -func testKVStoreSubscribeFail(t *testing.T, registerAttempt bool) { +func testKVStoreSubscribeFail(t *testing.T, registerAttempt, + keepFailedPaymentAttempts bool) { + db := initDB(t) - paymentDB, err := paymentsdb.NewKVStore(db) + paymentDB, err := paymentsdb.NewKVStore( + db, + paymentsdb.WithKeepFailedPaymentAttempts( + keepFailedPaymentAttempts, + ), + ) require.NoError(t, err) pControl := NewControlTower(paymentDB) @@ -454,7 +465,7 @@ func testKVStoreSubscribeFail(t *testing.T, registerAttempt bool) { t.Fatal(err) } - err = pControl.InitPayment(t.Context(), info.PaymentIdentifier, info) + err = pControl.InitPayment(info.PaymentIdentifier, info) if err != nil { t.Fatal(err) } @@ -468,18 +479,17 @@ func testKVStoreSubscribeFail(t *testing.T, registerAttempt bool) { // making any attempts at all. if registerAttempt { // Register an attempt. - err = pControl.RegisterAttempt( - t.Context(), info.PaymentIdentifier, attempt, - ) - require.NoError(t, err) + err = pControl.RegisterAttempt(info.PaymentIdentifier, attempt) + if err != nil { + t.Fatal(err) + } // Fail the payment attempt. failInfo := paymentsdb.HTLCFailInfo{ Reason: paymentsdb.HTLCFailInternal, } htlcAttempt, err := pControl.FailAttempt( - t.Context(), info.PaymentIdentifier, attempt.AttemptID, - &failInfo, + info.PaymentIdentifier, attempt.AttemptID, &failInfo, ) if err != nil { t.Fatalf("unable to fail htlc: %v", err) @@ -491,8 +501,7 @@ func testKVStoreSubscribeFail(t *testing.T, registerAttempt bool) { // Mark the payment as failed. err = pControl.FailPayment( - t.Context(), info.PaymentIdentifier, - paymentsdb.FailureReasonTimeout, + info.PaymentIdentifier, paymentsdb.FailureReasonTimeout, ) if err != nil { t.Fatal(err) diff --git a/routing/graph.go b/routing/graph.go index e79a69bf6..4be34def9 100644 --- a/routing/graph.go +++ b/routing/graph.go @@ -1,10 +1,9 @@ package routing import ( - "context" "fmt" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" @@ -15,13 +14,12 @@ import ( type Graph interface { // ForEachNodeDirectedChannel calls the callback for every channel of // the given node. - ForEachNodeDirectedChannel(ctx context.Context, nodePub route.Vertex, + ForEachNodeDirectedChannel(nodePub route.Vertex, cb func(channel *graphdb.DirectedChannel) error, reset func()) error // FetchNodeFeatures returns the features of the given node. - FetchNodeFeatures(ctx context.Context, - nodePub route.Vertex) (*lnwire.FeatureVector, error) + FetchNodeFeatures(nodePub route.Vertex) (*lnwire.FeatureVector, error) } // GraphSessionFactory can be used to gain access to a graphdb.NodeTraverser @@ -32,8 +30,7 @@ type GraphSessionFactory interface { // GraphSession will provide the call-back with access to a // graphdb.NodeTraverser instance which can be used to perform queries // against the channel graph. - GraphSession(ctx context.Context, - cb func(graph graphdb.NodeTraverser) error, + GraphSession(cb func(graph graphdb.NodeTraverser) error, reset func()) error } diff --git a/routing/integrated_routing_test.go b/routing/integrated_routing_test.go index 1ad968604..9636b10f7 100644 --- a/routing/integrated_routing_test.go +++ b/routing/integrated_routing_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/zpay32" @@ -270,6 +270,7 @@ func TestBadFirstHopHint(t *testing.T) { // TestMppSend tests that a payment can be completed using multiple shards. func TestMppSend(t *testing.T) { for _, testCase := range mppTestCases { + testCase := testCase t.Run(testCase.name, func(t *testing.T) { testMppSend(t, &testCase) diff --git a/routing/localchans/manager.go b/routing/localchans/manager.go index 1c9f906bb..a48486e7b 100644 --- a/routing/localchans/manager.go +++ b/routing/localchans/manager.go @@ -9,9 +9,8 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/discovery" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/funding" @@ -19,7 +18,6 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing" - "github.com/lightningnetwork/lnd/routing/route" ) // Manager manages the node's local channels. The only operation that is @@ -49,7 +47,7 @@ type Manager struct { // FetchChannel is used to query local channel parameters. Optionally an // existing db tx can be supplied. - FetchChannel func(chanPoint wire.OutPoint) (*chanstate.OpenChannel, + FetchChannel func(chanPoint wire.OutPoint) (*channeldb.OpenChannel, error) // AddEdge is used to add edge/channel to the topology of the router. @@ -248,7 +246,7 @@ func (r *Manager) UpdatePolicy(ctx context.Context, } func (r *Manager) createMissingEdge(ctx context.Context, - channel *chanstate.OpenChannel, + channel *channeldb.OpenChannel, newSchema routing.ChannelPolicy) (*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *lnrpc.FailedUpdate) { @@ -295,7 +293,7 @@ func (r *Manager) createMissingEdge(ctx context.Context, } // createEdge recreates an edge and policy from an open channel in-memory. -func (r *Manager) createEdge(channel *chanstate.OpenChannel, +func (r *Manager) createEdge(channel *channeldb.OpenChannel, timestamp time.Time) (*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, error) { @@ -330,42 +328,24 @@ func (r *Manager) createEdge(channel *chanstate.OpenChannel, "script: %v", err) } - nodeKey1, err := route.NewVertexFromBytes(nodeKey1Bytes) - if err != nil { - return nil, nil, err - } - nodeKey2, err := route.NewVertexFromBytes(nodeKey2Bytes) - if err != nil { - return nil, nil, err - } - bitcoinKey1, err := route.NewVertexFromBytes(bitcoinKey1Bytes) - if err != nil { - return nil, nil, err - } - bitcoinKey2, err := route.NewVertexFromBytes(bitcoinKey2Bytes) - if err != nil { - return nil, nil, err + info := &models.ChannelEdgeInfo{ + ChannelID: shortChanID.ToUint64(), + ChainHash: channel.ChainHash, + Features: lnwire.EmptyFeatureVector(), + Capacity: channel.Capacity, + ChannelPoint: channel.FundingOutpoint, + FundingScript: fn.Some(fundingScript), } - info, err := models.NewV1Channel( - shortChanID.ToUint64(), channel.ChainHash, nodeKey1, nodeKey2, - &models.ChannelV1Fields{ - BitcoinKey1Bytes: bitcoinKey1, - BitcoinKey2Bytes: bitcoinKey2, - }, - models.WithCapacity(channel.Capacity), - models.WithChannelPoint(channel.FundingOutpoint), - models.WithFundingScript(fundingScript), - ) - if err != nil { - return nil, nil, err - } + copy(info.NodeKey1Bytes[:], nodeKey1Bytes) + copy(info.NodeKey2Bytes[:], nodeKey2Bytes) + copy(info.BitcoinKey1Bytes[:], bitcoinKey1Bytes) + copy(info.BitcoinKey2Bytes[:], bitcoinKey2Bytes) // Construct a dummy channel edge policy with default values that will // be updated with the new values in the call to processChan below. timeLockDelta := uint16(r.DefaultRoutingPolicy.TimeLockDelta) edge := &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, ChannelID: shortChanID.ToUint64(), LastUpdate: timestamp, TimeLockDelta: timeLockDelta, @@ -469,14 +449,14 @@ func (r *Manager) updateEdge(chanPoint wire.OutPoint, } // Clear signature to help prevent usage of the previous signature. - edge.SigBytes = nil + edge.SetSigBytes(nil) return nil } // getHtlcAmtLimits retrieves the negotiated channel min and max htlc amount // constraints. -func (r *Manager) getHtlcAmtLimits(ch *chanstate.OpenChannel) ( +func (r *Manager) getHtlcAmtLimits(ch *channeldb.OpenChannel) ( lnwire.MilliSatoshi, lnwire.MilliSatoshi, error) { // The max htlc policy field must be less than or equal to the channel diff --git a/routing/localchans/manager_test.go b/routing/localchans/manager_test.go index 4f0bc78ae..5df344bba 100644 --- a/routing/localchans/manager_test.go +++ b/routing/localchans/manager_test.go @@ -7,20 +7,19 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/discovery" + "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing" - "github.com/lightningnetwork/lnd/routing/route" "github.com/stretchr/testify/require" ) @@ -65,7 +64,6 @@ func TestManager(t *testing.T) { } currentPolicy := models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, MinHTLC: minHTLC, MessageFlags: lnwire.ChanUpdateRequiredMaxHtlc, } @@ -139,29 +137,28 @@ func TestManager(t *testing.T) { return nil } - fetchChannel := func(chanPoint wire.OutPoint) (*chanstate.OpenChannel, + fetchChannel := func(chanPoint wire.OutPoint) (*channeldb.OpenChannel, error) { if chanPoint == chanPointMissing { - return &chanstate.OpenChannel{}, - channeldb.ErrChannelNotFound + return &channeldb.OpenChannel{}, channeldb.ErrChannelNotFound } - bounds := chanstate.ChannelStateBounds{ + bounds := channeldb.ChannelStateBounds{ MaxPendingAmount: maxPendingAmount, MinHTLC: minHTLC, } - return &chanstate.OpenChannel{ + return &channeldb.OpenChannel{ FundingOutpoint: chanPointValid, IdentityPub: remotepub, - LocalChanCfg: chanstate.ChannelConfig{ + LocalChanCfg: channeldb.ChannelConfig{ ChannelStateBounds: bounds, MultiSigKey: keychain.KeyDescriptor{ PubKey: localMultisigKey, }, }, - RemoteChanCfg: chanstate.ChannelConfig{ + RemoteChanCfg: channeldb.ChannelConfig{ ChannelStateBounds: bounds, MultiSigKey: keychain.KeyDescriptor{ PubKey: remoteMultisigKey, @@ -214,21 +211,10 @@ func TestManager(t *testing.T) { newPolicy: newPolicy, channelSet: []channel{ { - //nolint:ll - edgeInfo: func() *models.ChannelEdgeInfo { - info, err := models.NewV1Channel( - 0, - chainhash.Hash{}, - route.Vertex{}, - route.Vertex{}, - &models.ChannelV1Fields{}, - models.WithCapacity(chanCap), - models.WithChannelPoint(chanPointValid), - ) - require.NoError(t, err) - - return info - }(), + edgeInfo: &models.ChannelEdgeInfo{ + Capacity: chanCap, + ChannelPoint: chanPointValid, + }, }, }, specifiedChanPoints: []wire.OutPoint{chanPointValid}, @@ -243,21 +229,10 @@ func TestManager(t *testing.T) { newPolicy: newPolicy, channelSet: []channel{ { - //nolint:ll - edgeInfo: func() *models.ChannelEdgeInfo { - info, err := models.NewV1Channel( - 0, - chainhash.Hash{}, - route.Vertex{}, - route.Vertex{}, - &models.ChannelV1Fields{}, - models.WithCapacity(chanCap), - models.WithChannelPoint(chanPointValid), - ) - require.NoError(t, err) - - return info - }(), + edgeInfo: &models.ChannelEdgeInfo{ + Capacity: chanCap, + ChannelPoint: chanPointValid, + }, }, }, specifiedChanPoints: []wire.OutPoint{}, @@ -272,22 +247,10 @@ func TestManager(t *testing.T) { newPolicy: newPolicy, channelSet: []channel{ { - //nolint:ll - edgeInfo: func() *models.ChannelEdgeInfo { - info, err := models.NewV1Channel( - 0, - chainhash.Hash{}, - route.Vertex{}, - route.Vertex{}, - &models.ChannelV1Fields{}, - models.WithCapacity(chanCap), - models.WithChannelPoint(chanPointValid), - ) - - require.NoError(t, err) - - return info - }(), + edgeInfo: &models.ChannelEdgeInfo{ + Capacity: chanCap, + ChannelPoint: chanPointValid, + }, }, }, specifiedChanPoints: []wire.OutPoint{chanPointMissing}, @@ -306,22 +269,10 @@ func TestManager(t *testing.T) { newPolicy: noMaxHtlcPolicy, channelSet: []channel{ { - //nolint:ll - edgeInfo: func() *models.ChannelEdgeInfo { - info, err := models.NewV1Channel( - 0, - chainhash.Hash{}, - route.Vertex{}, - route.Vertex{}, - &models.ChannelV1Fields{}, - models.WithCapacity(chanCap), - models.WithChannelPoint(chanPointValid), - ) - - require.NoError(t, err) - - return info - }(), + edgeInfo: &models.ChannelEdgeInfo{ + Capacity: chanCap, + ChannelPoint: chanPointValid, + }, }, }, specifiedChanPoints: []wire.OutPoint{chanPointValid}, @@ -361,6 +312,7 @@ func TestManager(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { currentPolicy = test.currentPolicy channelSet = test.channelSet @@ -415,14 +367,14 @@ func TestCreateEdgeLower(t *testing.T) { TimeLockDelta: 7, } - channel := &chanstate.OpenChannel{ + channel := &channeldb.OpenChannel{ IdentityPub: remotepub, - LocalChanCfg: chanstate.ChannelConfig{ + LocalChanCfg: channeldb.ChannelConfig{ MultiSigKey: keychain.KeyDescriptor{ PubKey: localMultisigKey, }, }, - RemoteChanCfg: chanstate.ChannelConfig{ + RemoteChanCfg: channeldb.ChannelConfig{ MultiSigKey: keychain.KeyDescriptor{ PubKey: remoteMultisigKey, }, @@ -439,21 +391,23 @@ func TestCreateEdgeLower(t *testing.T) { fundingScript, err := funding.MakeFundingScript(channel) require.NoError(t, err) - btcKey1 := route.NewVertex(localMultisigKey) - btcKey2 := route.NewVertex(remoteMultisigKey) - expectedInfo, err := models.NewV1Channel( - 8, channel.ChainHash, sp, rp, &models.ChannelV1Fields{ - BitcoinKey1Bytes: btcKey1, - BitcoinKey2Bytes: btcKey2, - }, - models.WithCapacity(9), - models.WithChannelPoint(channel.FundingOutpoint), - models.WithFundingScript(fundingScript), - ) - require.NoError(t, err) - + expectedInfo := &models.ChannelEdgeInfo{ + ChannelID: 8, + ChainHash: channel.ChainHash, + Features: lnwire.EmptyFeatureVector(), + Capacity: 9, + ChannelPoint: channel.FundingOutpoint, + NodeKey1Bytes: sp, + NodeKey2Bytes: rp, + BitcoinKey1Bytes: [33]byte( + localMultisigKey.SerializeCompressed()), + BitcoinKey2Bytes: [33]byte( + remoteMultisigKey.SerializeCompressed()), + AuthProof: nil, + ExtraOpaqueData: nil, + FundingScript: fn.Some(fundingScript), + } expectedEdge := &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, ChannelID: 8, LastUpdate: timestamp, TimeLockDelta: 7, @@ -506,14 +460,14 @@ func TestCreateEdgeHigher(t *testing.T) { TimeLockDelta: 7, } - channel := &chanstate.OpenChannel{ + channel := &channeldb.OpenChannel{ IdentityPub: remotepub, - LocalChanCfg: chanstate.ChannelConfig{ + LocalChanCfg: channeldb.ChannelConfig{ MultiSigKey: keychain.KeyDescriptor{ PubKey: localMultisigKey, }, }, - RemoteChanCfg: chanstate.ChannelConfig{ + RemoteChanCfg: channeldb.ChannelConfig{ MultiSigKey: keychain.KeyDescriptor{ PubKey: remoteMultisigKey, }, @@ -530,22 +484,23 @@ func TestCreateEdgeHigher(t *testing.T) { fundingScript, err := funding.MakeFundingScript(channel) require.NoError(t, err) - btcKey1 := route.NewVertex(remoteMultisigKey) - btcKey2 := route.NewVertex(localMultisigKey) - expectedInfo, err := models.NewV1Channel( - 8, channel.ChainHash, rp, sp, - &models.ChannelV1Fields{ - BitcoinKey1Bytes: btcKey1, - BitcoinKey2Bytes: btcKey2, - }, - models.WithCapacity(9), - models.WithChannelPoint(channel.FundingOutpoint), - models.WithFundingScript(fundingScript), - ) - require.NoError(t, err) - + expectedInfo := &models.ChannelEdgeInfo{ + ChannelID: 8, + ChainHash: channel.ChainHash, + Features: lnwire.EmptyFeatureVector(), + Capacity: 9, + ChannelPoint: channel.FundingOutpoint, + NodeKey1Bytes: rp, + NodeKey2Bytes: sp, + BitcoinKey1Bytes: [33]byte( + remoteMultisigKey.SerializeCompressed()), + BitcoinKey2Bytes: [33]byte( + localMultisigKey.SerializeCompressed()), + AuthProof: nil, + ExtraOpaqueData: nil, + FundingScript: fn.Some(fundingScript), + } expectedEdge := &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, ChannelID: 8, LastUpdate: timestamp, TimeLockDelta: 7, diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go index 80b553233..b03724a2e 100644 --- a/routing/missioncontrol.go +++ b/routing/missioncontrol.go @@ -8,7 +8,7 @@ import ( "sync" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btclog/v2" "github.com/btcsuite/btcwallet/walletdb" "github.com/lightningnetwork/lnd/clock" @@ -709,6 +709,7 @@ func (m *MissionControl) applyPaymentResult( } for pair, pairResult := range i.pairResults { + pairResult := pairResult if pairResult.success { m.log.Debugf("Reporting pair success to Mission "+ diff --git a/routing/missioncontrol_store_test.go b/routing/missioncontrol_store_test.go index 9cfd118a0..889dca071 100644 --- a/routing/missioncontrol_store_test.go +++ b/routing/missioncontrol_store_test.go @@ -276,6 +276,7 @@ func BenchmarkMissionControlStoreFlushing(b *testing.B) { const testMaxRecords = 1000 for _, tc := range tests { + tc := tc name := fmt.Sprintf("%v additional results", tc) b.Run(name, func(b *testing.B) { h := newMCStoreTestHarness( diff --git a/routing/mock_graph_test.go b/routing/mock_graph_test.go index ee50419be..d9b16f646 100644 --- a/routing/mock_graph_test.go +++ b/routing/mock_graph_test.go @@ -2,12 +2,11 @@ package routing import ( "bytes" - "context" "fmt" "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lnwire" @@ -166,9 +165,8 @@ func (m *mockGraph) addChannel(id uint64, node1id, node2id byte, // forEachNodeChannel calls the callback for every channel of the given node. // // NOTE: Part of the Graph interface. -func (m *mockGraph) ForEachNodeDirectedChannel(_ context.Context, - nodePub route.Vertex, cb func(channel *graphdb.DirectedChannel) error, - _ func()) error { +func (m *mockGraph) ForEachNodeDirectedChannel(nodePub route.Vertex, + cb func(channel *graphdb.DirectedChannel) error, _ func()) error { // Look up the mock node. node, ok := m.nodes[nodePub] @@ -223,8 +221,8 @@ func (m *mockGraph) sourceNode() route.Vertex { // fetchNodeFeatures returns the features of the given node. // // NOTE: Part of the Graph interface. -func (m *mockGraph) FetchNodeFeatures(_ context.Context, - _ route.Vertex) (*lnwire.FeatureVector, error) { +func (m *mockGraph) FetchNodeFeatures(nodePub route.Vertex) ( + *lnwire.FeatureVector, error) { return lnwire.EmptyFeatureVector(), nil } @@ -234,8 +232,8 @@ func (m *mockGraph) FetchNodeFeatures(_ context.Context, // the channel graph. // // NOTE: Part of the GraphSessionFactory interface. -func (m *mockGraph) GraphSession(_ context.Context, - cb func(graph graphdb.NodeTraverser) error, _ func()) error { +func (m *mockGraph) GraphSession(cb func(graph graphdb.NodeTraverser) error, + _ func()) error { return cb(m) } diff --git a/routing/mock_test.go b/routing/mock_test.go index 212ff30a9..19a76ee90 100644 --- a/routing/mock_test.go +++ b/routing/mock_test.go @@ -1,13 +1,12 @@ package routing import ( - "context" "errors" "fmt" "sync" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/htlcswitch" @@ -297,8 +296,8 @@ func makeMockControlTower() *mockControlTowerOld { } } -func (m *mockControlTowerOld) InitPayment(_ context.Context, - phash lntypes.Hash, c *paymentsdb.PaymentCreationInfo) error { +func (m *mockControlTowerOld) InitPayment(phash lntypes.Hash, + c *paymentsdb.PaymentCreationInfo) error { if m.init != nil { m.init <- initArgs{c} @@ -328,9 +327,7 @@ func (m *mockControlTowerOld) InitPayment(_ context.Context, return nil } -func (m *mockControlTowerOld) DeleteFailedAttempts(_ context.Context, - phash lntypes.Hash) error { - +func (m *mockControlTowerOld) DeleteFailedAttempts(phash lntypes.Hash) error { p, ok := m.payments[phash] if !ok { return paymentsdb.ErrPaymentNotInitiated @@ -356,8 +353,8 @@ func (m *mockControlTowerOld) DeleteFailedAttempts(_ context.Context, return nil } -func (m *mockControlTowerOld) RegisterAttempt(_ context.Context, - phash lntypes.Hash, a *paymentsdb.HTLCAttemptInfo) error { +func (m *mockControlTowerOld) RegisterAttempt(phash lntypes.Hash, + a *paymentsdb.HTLCAttemptInfo) error { if m.registerAttempt != nil { m.registerAttempt <- registerAttemptArgs{a} @@ -410,8 +407,8 @@ func (m *mockControlTowerOld) RegisterAttempt(_ context.Context, return nil } -func (m *mockControlTowerOld) SettleAttempt(_ context.Context, - phash lntypes.Hash, pid uint64, settleInfo *paymentsdb.HTLCSettleInfo) ( +func (m *mockControlTowerOld) SettleAttempt(phash lntypes.Hash, + pid uint64, settleInfo *paymentsdb.HTLCSettleInfo) ( *paymentsdb.HTLCAttempt, error) { if m.settleAttempt != nil { @@ -453,9 +450,8 @@ func (m *mockControlTowerOld) SettleAttempt(_ context.Context, return nil, fmt.Errorf("pid not found") } -func (m *mockControlTowerOld) FailAttempt(_ context.Context, phash lntypes.Hash, - pid uint64, failInfo *paymentsdb.HTLCFailInfo) (*paymentsdb.HTLCAttempt, - error) { +func (m *mockControlTowerOld) FailAttempt(phash lntypes.Hash, pid uint64, + failInfo *paymentsdb.HTLCFailInfo) (*paymentsdb.HTLCAttempt, error) { if m.failAttempt != nil { m.failAttempt <- failAttemptArgs{failInfo} @@ -493,7 +489,7 @@ func (m *mockControlTowerOld) FailAttempt(_ context.Context, phash lntypes.Hash, return nil, fmt.Errorf("pid not found") } -func (m *mockControlTowerOld) FailPayment(_ context.Context, phash lntypes.Hash, +func (m *mockControlTowerOld) FailPayment(phash lntypes.Hash, reason paymentsdb.FailureReason) error { m.Lock() @@ -513,8 +509,8 @@ func (m *mockControlTowerOld) FailPayment(_ context.Context, phash lntypes.Hash, return nil } -func (m *mockControlTowerOld) FetchPayment(_ context.Context, - phash lntypes.Hash) (paymentsdb.DBMPPayment, error) { +func (m *mockControlTowerOld) FetchPayment(phash lntypes.Hash) ( + paymentsdb.DBMPPayment, error) { m.Lock() defer m.Unlock() @@ -549,7 +545,7 @@ func (m *mockControlTowerOld) fetchPayment(phash lntypes.Hash) ( return mp, nil } -func (m *mockControlTowerOld) FetchInFlightPayments(_ context.Context) ( +func (m *mockControlTowerOld) FetchInFlightPayments() ( []*paymentsdb.MPPayment, error) { if m.fetchInFlight != nil { @@ -737,28 +733,26 @@ type mockControlTower struct { var _ ControlTower = (*mockControlTower)(nil) -func (m *mockControlTower) InitPayment(_ context.Context, phash lntypes.Hash, +func (m *mockControlTower) InitPayment(phash lntypes.Hash, c *paymentsdb.PaymentCreationInfo) error { args := m.Called(phash, c) return args.Error(0) } -func (m *mockControlTower) DeleteFailedAttempts(_ context.Context, - phash lntypes.Hash) error { - +func (m *mockControlTower) DeleteFailedAttempts(phash lntypes.Hash) error { args := m.Called(phash) return args.Error(0) } -func (m *mockControlTower) RegisterAttempt(_ context.Context, - phash lntypes.Hash, a *paymentsdb.HTLCAttemptInfo) error { +func (m *mockControlTower) RegisterAttempt(phash lntypes.Hash, + a *paymentsdb.HTLCAttemptInfo) error { args := m.Called(phash, a) return args.Error(0) } -func (m *mockControlTower) SettleAttempt(_ context.Context, phash lntypes.Hash, +func (m *mockControlTower) SettleAttempt(phash lntypes.Hash, pid uint64, settleInfo *paymentsdb.HTLCSettleInfo) ( *paymentsdb.HTLCAttempt, error) { @@ -772,9 +766,8 @@ func (m *mockControlTower) SettleAttempt(_ context.Context, phash lntypes.Hash, return attempt.(*paymentsdb.HTLCAttempt), args.Error(1) } -func (m *mockControlTower) FailAttempt(_ context.Context, phash lntypes.Hash, - pid uint64, failInfo *paymentsdb.HTLCFailInfo) (*paymentsdb.HTLCAttempt, - error) { +func (m *mockControlTower) FailAttempt(phash lntypes.Hash, pid uint64, + failInfo *paymentsdb.HTLCFailInfo) (*paymentsdb.HTLCAttempt, error) { args := m.Called(phash, pid, failInfo) @@ -786,15 +779,15 @@ func (m *mockControlTower) FailAttempt(_ context.Context, phash lntypes.Hash, return attempt.(*paymentsdb.HTLCAttempt), args.Error(1) } -func (m *mockControlTower) FailPayment(_ context.Context, phash lntypes.Hash, +func (m *mockControlTower) FailPayment(phash lntypes.Hash, reason paymentsdb.FailureReason) error { args := m.Called(phash, reason) return args.Error(0) } -func (m *mockControlTower) FetchPayment(_ context.Context, - phash lntypes.Hash) (paymentsdb.DBMPPayment, error) { +func (m *mockControlTower) FetchPayment(phash lntypes.Hash) ( + paymentsdb.DBMPPayment, error) { args := m.Called(phash) @@ -807,7 +800,7 @@ func (m *mockControlTower) FetchPayment(_ context.Context, return payment, args.Error(1) } -func (m *mockControlTower) FetchInFlightPayments(_ context.Context) ( +func (m *mockControlTower) FetchInFlightPayments() ( []*paymentsdb.MPPayment, error) { args := m.Called() diff --git a/routing/pathfind.go b/routing/pathfind.go index 0507df929..fab8015dd 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -3,14 +3,13 @@ package routing import ( "bytes" "container/heap" - "context" "errors" "fmt" "math" "sort" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/feature" "github.com/lightningnetwork/lnd/fn/v2" @@ -578,7 +577,7 @@ func getOutgoingBalance(node route.Vertex, outgoingChans map[uint64]struct{}, // Iterate over all channels of the to node. err := g.ForEachNodeDirectedChannel( - context.TODO(), node, cb, func() { + node, cb, func() { max = 0 total = 0 }, @@ -622,9 +621,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, features := r.DestFeatures if features == nil { var err error - features, err = g.graph.FetchNodeFeatures( - context.TODO(), target, - ) + features, err = g.graph.FetchNodeFeatures(target) if err != nil { return nil, 0, err } @@ -978,7 +975,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, routingInfoSize := toNodeDist.routingInfoSize + payloadSize // Skip paths that would exceed the maximum routing info size. - if routingInfoSize > sphinx.MaxRoutingPayloadSize { + if routingInfoSize > sphinx.MaxPayloadSize { return } @@ -1022,9 +1019,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, } // Fetch node features fresh from the graph. - fromFeatures, err := g.graph.FetchNodeFeatures( - context.TODO(), node, - ) + fromFeatures, err := g.graph.FetchNodeFeatures(node) if err != nil { return nil, err } @@ -1291,7 +1286,7 @@ func findBlindedPaths(g Graph, target route.Vertex, nextTargetReset = nextTarget ) err := g.ForEachNodeDirectedChannel( - context.TODO(), nextTarget, + nextTarget, func(channel *graphdb.DirectedChannel) error { // This is not the right channel, continue to // the node's other channels. @@ -1354,7 +1349,7 @@ func findBlindedPaths(g Graph, target route.Vertex, return true, nil } - features, err := g.FetchNodeFeatures(context.TODO(), node) + features, err := g.FetchNodeFeatures(node) if err != nil { return false, err } @@ -1466,7 +1461,7 @@ func processNodeForBlindedPath(g Graph, node route.Vertex, // Now, iterate over the node's channels in search for paths to this // node that can be used for blinded paths err = g.ForEachNodeDirectedChannel( - context.TODO(), node, + node, func(channel *graphdb.DirectedChannel) error { // Keep track of how many incoming channels this node // has. We only use a node as an introduction node if it diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 94942fc50..77bad02e3 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -18,10 +18,9 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/fn/v2" graphdb "github.com/lightningnetwork/lnd/graph/db" @@ -100,12 +99,12 @@ var ( _ = testSScalar.SetByteSlice(testSBytes) testSig = ecdsa.NewSignature(testRScalar, testSScalar) - testAuthProof = *models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ) + testAuthProof = models.ChannelAuthProof{ + NodeSig1Bytes: testSig.Serialize(), + NodeSig2Bytes: testSig.Serialize(), + BitcoinSig1Bytes: testSig.Serialize(), + BitcoinSig2Bytes: testSig.Serialize(), + } ) // noProbabilitySource is used in testing to return the same probability 1 for @@ -229,16 +228,15 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( return nil, err } - pubKey, err := route.NewVertexFromBytes(pubBytes) - require.NoError(t, err) - - dbNode := models.NewV1Node(pubKey, &models.NodeV1Fields{ - AuthSigBytes: testSig.Serialize(), - LastUpdate: testTime, - Addresses: testAddrs, - Alias: node.Alias, - Features: testFeatures.RawFeatureVector, - }) + dbNode := &models.Node{ + HaveNodeAnnouncement: true, + AuthSigBytes: testSig.Serialize(), + LastUpdate: testTime, + Addresses: testAddrs, + Alias: node.Alias, + Features: testFeatures, + } + copy(dbNode.PubKeyBytes[:], pubBytes) // We require all aliases within the graph to be unique for our // tests. @@ -346,29 +344,19 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( // We first insert the existence of the edge between the two // nodes. - var node1Vertex, node2Vertex route.Vertex - copy(node1Vertex[:], node1Bytes) - copy(node2Vertex[:], node2Bytes) - - var btcKey1, btcKey2 route.Vertex - copy(btcKey1[:], node1Bytes) - copy(btcKey2[:], node2Bytes) - - edgeInfo, err := models.NewV1Channel( - edge.ChannelID, *chaincfg.SimNetParams.GenesisHash, - node1Vertex, node2Vertex, - &models.ChannelV1Fields{ - BitcoinKey1Bytes: btcKey1, - BitcoinKey2Bytes: btcKey2, - }, - models.WithChanProof(&testAuthProof), - models.WithChannelPoint(fundingPoint), - models.WithCapacity(btcutil.Amount(edge.Capacity)), - ) - if err != nil { - return nil, err + edgeInfo := models.ChannelEdgeInfo{ + ChannelID: edge.ChannelID, + AuthProof: &testAuthProof, + ChannelPoint: fundingPoint, + Features: lnwire.EmptyFeatureVector(), + Capacity: btcutil.Amount(edge.Capacity), } + copy(edgeInfo.NodeKey1Bytes[:], node1Bytes) + copy(edgeInfo.NodeKey2Bytes[:], node2Bytes) + copy(edgeInfo.BitcoinKey1Bytes[:], node1Bytes) + copy(edgeInfo.BitcoinKey2Bytes[:], node2Bytes) + shortID := lnwire.NewShortChanIDFromInt(edge.ChannelID) links[shortID] = &mockLink{ bandwidth: lnwire.MilliSatoshi( @@ -376,7 +364,7 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( ), } - err = graph.AddChannelEdge(ctx, edgeInfo) + err = graph.AddChannelEdge(ctx, &edgeInfo) if err != nil && !errors.Is(err, graphdb.ErrEdgeAlreadyExist) { return nil, err } @@ -389,7 +377,6 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( } edgePolicy := &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), MessageFlags: lnwire.ChanUpdateMsgFlags(edge.MessageFlags), ChannelFlags: channelFlags, @@ -407,12 +394,12 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( } // We also store the channel IDs info for each of the node. - node1Vertex, err = route.NewVertexFromBytes(node1Bytes) + node1Vertex, err := route.NewVertexFromBytes(node1Bytes) if err != nil { return nil, err } - node2Vertex, err = route.NewVertexFromBytes(node2Bytes) + node2Vertex, err := route.NewVertexFromBytes(node2Bytes) if err != nil { return nil, err } @@ -429,10 +416,7 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( } return &testGraphInstance{ - graph: graph, - v1Graph: graphdb.NewVersionedGraph( - graph, lnwire.GossipVersion1, - ), + graph: graph, mcBackend: mcBackend, aliasMap: aliasMap, privKeyMap: privKeyMap, @@ -502,7 +486,6 @@ type testChannel struct { type testGraphInstance struct { graph *graphdb.ChannelGraph - v1Graph *graphdb.VersionedGraph mcBackend kvdb.Backend // aliasMap is a map from a node's alias to its public key. This type is @@ -582,15 +565,16 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, features = lnwire.EmptyFeatureVector() } - dbNode := models.NewV1Node( - route.NewVertex(pubKey), &models.NodeV1Fields{ - AuthSigBytes: testSig.Serialize(), - LastUpdate: testTime, - Addresses: testAddrs, - Alias: alias, - Features: features.RawFeatureVector, - }, - ) + dbNode := &models.Node{ + HaveNodeAnnouncement: true, + AuthSigBytes: testSig.Serialize(), + LastUpdate: testTime, + Addresses: testAddrs, + Alias: alias, + Features: features, + } + + copy(dbNode.PubKeyBytes[:], pubKey.SerializeCompressed()) privKeyMap[alias] = privKey @@ -689,21 +673,20 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, // We first insert the existence of the edge between the two // nodes. - edgeInfo, err := models.NewV1Channel( - channelID, *chaincfg.SimNetParams.GenesisHash, - node1Vertex, node2Vertex, &models.ChannelV1Fields{ - BitcoinKey1Bytes: node1Vertex, - BitcoinKey2Bytes: node2Vertex, - }, - models.WithChanProof(&testAuthProof), - models.WithChannelPoint(*fundingPoint), - models.WithCapacity(testChannel.Capacity), - ) - if err != nil { - return nil, err + edgeInfo := models.ChannelEdgeInfo{ + ChannelID: channelID, + AuthProof: &testAuthProof, + ChannelPoint: *fundingPoint, + Capacity: testChannel.Capacity, + Features: lnwire.EmptyFeatureVector(), + + NodeKey1Bytes: node1Vertex, + BitcoinKey1Bytes: node1Vertex, + NodeKey2Bytes: node2Vertex, + BitcoinKey2Bytes: node2Vertex, } - err = graph.AddChannelEdge(ctx, edgeInfo) + err = graph.AddChannelEdge(ctx, &edgeInfo) if err != nil && !errors.Is(err, graphdb.ErrEdgeAlreadyExist) { return nil, err } @@ -741,9 +724,7 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, channelFlags |= lnwire.ChanUpdateDisabled } - //nolint:ll edgePolicy := &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), MessageFlags: msgFlags, ChannelFlags: channelFlags, @@ -775,9 +756,7 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, } channelFlags |= lnwire.ChanUpdateDirection - //nolint:ll edgePolicy := &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), MessageFlags: msgFlags, ChannelFlags: channelFlags, @@ -797,13 +776,12 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, return nil, err } } + + channelID++ } return &testGraphInstance{ - graph: graph, - v1Graph: graphdb.NewVersionedGraph( - graph, lnwire.GossipVersion1, - ), + graph: graph, mcBackend: graphBackend, aliasMap: aliasMap, privKeyMap: privKeyMap, @@ -897,6 +875,7 @@ func TestPathFinding(t *testing.T) { // Run with graph cache enabled. for _, tc := range testCases { + tc := tc t.Run("cache=true/"+tc.name, func(tt *testing.T) { tt.Parallel() @@ -908,6 +887,7 @@ func TestPathFinding(t *testing.T) { // And with the DB fallback to make sure everything works the same // still. for _, tc := range testCases { + tc := tc t.Run("cache=false/"+tc.name, func(tt *testing.T) { tt.Parallel() @@ -1115,7 +1095,7 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc expectedHops := test.expectedHops expectedHopCount := len(expectedHops) - sourceNode, err := graphInstance.v1Graph.SourceNode(ctx) + sourceNode, err := graphInstance.graph.SourceNode(ctx) require.NoError(t, err, "unable to fetch source node") sourceVertex := route.Vertex(sourceNode.PubKeyBytes) @@ -1127,7 +1107,7 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc paymentAmt := lnwire.NewMSatFromSatoshis(test.paymentAmt) target := graphInstance.aliasMap[test.target] path, err := dbFindPath( - graphInstance.v1Graph, nil, &mockBandwidthHints{}, + graphInstance.graph, nil, &mockBandwidthHints{}, &RestrictParams{ FeeLimit: test.feeLimit, ProbabilitySource: noProbabilitySource, @@ -1190,9 +1170,7 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc require.Equal( t, route.Hops[i+1].ChannelID, - payload.FwdInfo.NextHopChannel().UnwrapOr( - switchhop.Exit, - ).ToUint64(), + payload.FwdInfo.NextHop.ToUint64(), ) } @@ -1205,11 +1183,7 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc // The final hop should have a next hop value of all zeroes in order // to indicate it's the exit hop. - require.Zero( - t, payload.FwdInfo.NextHopChannel().UnwrapOr( - switchhop.Exit, - ).ToUint64(), - ) + require.Zero(t, payload.FwdInfo.NextHop.ToUint64()) var expectedTotalFee lnwire.MilliSatoshi for i := 0; i < expectedHopCount; i++ { @@ -1263,7 +1237,7 @@ func runPathFindingWithAdditionalEdges(t *testing.T, useCache bool) { ctx := t.Context() - sourceNode, err := graph.v1Graph.SourceNode(ctx) + sourceNode, err := graph.graph.SourceNode(ctx) require.NoError(t, err, "unable to fetch source node") paymentAmt := lnwire.NewMSatFromSatoshis(100) @@ -1276,13 +1250,13 @@ func runPathFindingWithAdditionalEdges(t *testing.T, useCache bool) { dogePubKeyHex := "03dd46ff29a6941b4a2607525b043ec9b020b3f318a1bf281536fd7011ec59c882" dogePubKeyBytes, err := hex.DecodeString(dogePubKeyHex) require.NoError(t, err, "unable to decode public key") + dogePubKey, err := btcec.ParsePubKey(dogePubKeyBytes) + require.NoError(t, err, "unable to parse public key from bytes") - pubKey, err := route.NewVertexFromBytes(dogePubKeyBytes) - require.NoError(t, err) - - doge := models.NewV1Node(pubKey, &models.NodeV1Fields{ - Alias: "doge", - }) + doge := &models.Node{} + doge.AddPubKey(dogePubKey) + doge.Alias = "doge" + copy(doge.PubKeyBytes[:], dogePubKeyBytes) graph.aliasMap["doge"] = doge.PubKeyBytes // Create the channel edge going from songoku to doge and include it in @@ -1308,8 +1282,7 @@ func runPathFindingWithAdditionalEdges(t *testing.T, useCache bool) { []*unifiedEdge, error) { return dbFindPath( - graph.v1Graph, additionalEdges, - &mockBandwidthHints{}, + graph.graph, additionalEdges, &mockBandwidthHints{}, r, testPathFindingConfig, sourceNode.PubKeyBytes, doge.PubKeyBytes, paymentAmt, 0, 0, @@ -1349,7 +1322,7 @@ func runPathFindingWithBlindedPathDuplicateHop(t *testing.T, useCache bool) { ctx := t.Context() - sourceNode, err := graph.v1Graph.SourceNode(ctx) + sourceNode, err := graph.graph.SourceNode(ctx) require.NoError(t, err, "unable to fetch source node") paymentAmt := lnwire.NewMSatFromSatoshis(100) @@ -1423,7 +1396,7 @@ func runPathFindingWithBlindedPathDuplicateHop(t *testing.T, useCache bool) { []*unifiedEdge, error) { return dbFindPath( - graph.v1Graph, blindedPath, &mockBandwidthHints{}, + graph.graph, blindedPath, &mockBandwidthHints{}, r, testPathFindingConfig, sourceNode.PubKeyBytes, dummyTarget, paymentAmt, 0, 0, @@ -1483,7 +1456,7 @@ func runPathFindingWithRedundantAdditionalEdges(t *testing.T, useCache bool) { } path, err := dbFindPath( - ctx.v1Graph, additionalEdges, ctx.bandwidthHints, + ctx.graph, additionalEdges, ctx.bandwidthHints, &ctx.restrictParams, &ctx.pathFindingConfig, ctx.source, target, paymentAmt, ctx.timePref, 0, ) @@ -1688,6 +1661,7 @@ func TestNewRoute(t *testing.T) { }} for _, testCase := range testCases { + testCase := testCase // Overwrite the final hop's features if the test requires a // custom feature vector. @@ -1835,7 +1809,7 @@ func runPathNotAvailable(t *testing.T, useCache bool) { ctx := t.Context() - sourceNode, err := graph.v1Graph.SourceNode(ctx) + sourceNode, err := graph.graph.SourceNode(ctx) require.NoError(t, err, "unable to fetch source node") // With the test graph loaded, we'll test that queries for target that @@ -1848,7 +1822,7 @@ func runPathNotAvailable(t *testing.T, useCache bool) { copy(unknownNode[:], unknownNodeBytes) _, err = dbFindPath( - graph.v1Graph, nil, &mockBandwidthHints{}, + graph.graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, unknownNode, 100, 0, 0, ) @@ -1891,14 +1865,14 @@ func runDestTLVGraphFallback(t *testing.T, useCache bool) { ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef") - sourceNode, err := ctx.v1Graph.SourceNode(t.Context()) + sourceNode, err := ctx.graph.SourceNode(t.Context()) require.NoError(t, err, "unable to fetch source node") find := func(r *RestrictParams, target route.Vertex) ([]*unifiedEdge, error) { return dbFindPath( - ctx.v1Graph, nil, &mockBandwidthHints{}, + ctx.graph, nil, &mockBandwidthHints{}, r, testPathFindingConfig, sourceNode.PubKeyBytes, target, 100, 0, 0, ) @@ -2110,7 +2084,7 @@ func runPathInsufficientCapacity(t *testing.T, useCache bool) { require.NoError(t, err, "unable to create graph") ctx := t.Context() - sourceNode, err := graph.v1Graph.SourceNode(ctx) + sourceNode, err := graph.graph.SourceNode(ctx) require.NoError(t, err, "unable to fetch source node") // Next, test that attempting to find a path in which the current @@ -2125,7 +2099,7 @@ func runPathInsufficientCapacity(t *testing.T, useCache bool) { payAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) _, err = dbFindPath( - graph.v1Graph, nil, &mockBandwidthHints{}, + graph.graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) @@ -2141,7 +2115,7 @@ func runRouteFailMinHTLC(t *testing.T, useCache bool) { require.NoError(t, err, "unable to create graph") ctx := t.Context() - sourceNode, err := graph.v1Graph.SourceNode(ctx) + sourceNode, err := graph.graph.SourceNode(ctx) require.NoError(t, err, "unable to fetch source node") // We'll not attempt to route an HTLC of 10 SAT from roasbeef to Son @@ -2150,7 +2124,7 @@ func runRouteFailMinHTLC(t *testing.T, useCache bool) { target := graph.aliasMap["songoku"] payAmt := lnwire.MilliSatoshi(10) _, err = dbFindPath( - graph.v1Graph, nil, &mockBandwidthHints{}, + graph.graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) @@ -2200,9 +2174,7 @@ func runRouteFailMaxHTLC(t *testing.T, useCache bool) { // Next, update the middle edge policy to only allow payments up to 100k // msat. graph := ctx.testGraphInstance.graph - _, midEdge, _, err := graph.FetchChannelEdgesByID( - t.Context(), firstToSecondID, - ) + _, midEdge, _, err := graph.FetchChannelEdgesByID(firstToSecondID) require.NoError(t, err, "unable to fetch channel edges by ID") midEdge.MessageFlags = 1 midEdge.MaxHTLC = payAmt - 1 @@ -2228,7 +2200,7 @@ func runRouteFailDisabledEdge(t *testing.T, useCache bool) { require.NoError(t, err, "unable to create graph") ctx := t.Context() - sourceNode, err := graph.v1Graph.SourceNode(ctx) + sourceNode, err := graph.graph.SourceNode(ctx) require.NoError(t, err, "unable to fetch source node") // First, we'll try to route from roasbeef -> sophon. This should @@ -2236,7 +2208,7 @@ func runRouteFailDisabledEdge(t *testing.T, useCache bool) { target := graph.aliasMap["sophon"] payAmt := lnwire.NewMSatFromSatoshis(105000) _, err = dbFindPath( - graph.v1Graph, nil, &mockBandwidthHints{}, + graph.graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) @@ -2246,9 +2218,7 @@ func runRouteFailDisabledEdge(t *testing.T, useCache bool) { // path finding, as we don't consider the disable flag for local // channels (and roasbeef is the source). roasToPham := uint64(999991) - _, e1, e2, err := graph.graph.FetchChannelEdgesByID( - t.Context(), roasToPham, - ) + _, e1, e2, err := graph.graph.FetchChannelEdgesByID(roasToPham) require.NoError(t, err, "unable to fetch edge") e1.ChannelFlags |= lnwire.ChanUpdateDisabled e1.LastUpdate = e1.LastUpdate.Add(time.Second) @@ -2262,7 +2232,7 @@ func runRouteFailDisabledEdge(t *testing.T, useCache bool) { } _, err = dbFindPath( - graph.v1Graph, nil, &mockBandwidthHints{}, + graph.graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) @@ -2271,9 +2241,7 @@ func runRouteFailDisabledEdge(t *testing.T, useCache bool) { // Now, we'll modify the edge from phamnuwen -> sophon, to read that // it's disabled. phamToSophon := uint64(99999) - _, e, _, err := graph.graph.FetchChannelEdgesByID( - t.Context(), phamToSophon, - ) + _, e, _, err := graph.graph.FetchChannelEdgesByID(phamToSophon) require.NoError(t, err, "unable to fetch edge") e.ChannelFlags |= lnwire.ChanUpdateDisabled e.LastUpdate = e.LastUpdate.Add(time.Second) @@ -2284,7 +2252,7 @@ func runRouteFailDisabledEdge(t *testing.T, useCache bool) { // If we attempt to route through that edge, we should get a failure as // it is no longer eligible. _, err = dbFindPath( - graph.v1Graph, nil, &mockBandwidthHints{}, + graph.graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) @@ -2301,7 +2269,7 @@ func runPathSourceEdgesBandwidth(t *testing.T, useCache bool) { require.NoError(t, err, "unable to create graph") ctx := t.Context() - sourceNode, err := graph.v1Graph.SourceNode(ctx) + sourceNode, err := graph.graph.SourceNode(ctx) require.NoError(t, err, "unable to fetch source node") // First, we'll try to route from roasbeef -> sophon. This should @@ -2310,7 +2278,7 @@ func runPathSourceEdgesBandwidth(t *testing.T, useCache bool) { target := graph.aliasMap["sophon"] payAmt := lnwire.NewMSatFromSatoshis(50000) path, err := dbFindPath( - graph.v1Graph, nil, &mockBandwidthHints{}, + graph.graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) @@ -2331,7 +2299,7 @@ func runPathSourceEdgesBandwidth(t *testing.T, useCache bool) { // Since both these edges has a bandwidth of zero, no path should be // found. _, err = dbFindPath( - graph.v1Graph, nil, bandwidths, + graph.graph, nil, bandwidths, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) @@ -2346,7 +2314,7 @@ func runPathSourceEdgesBandwidth(t *testing.T, useCache bool) { // Now, if we attempt to route again, we should find the path via // phamnuven, as the other source edge won't be considered. path, err = dbFindPath( - graph.v1Graph, nil, bandwidths, + graph.graph, nil, bandwidths, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) @@ -2356,9 +2324,7 @@ func runPathSourceEdgesBandwidth(t *testing.T, useCache bool) { // Finally, set the roasbeef->songoku bandwidth, but also set its // disable flag. bandwidths.hints[roasToSongoku] = 2 * payAmt - _, e1, e2, err := graph.graph.FetchChannelEdgesByID( - t.Context(), roasToSongoku, - ) + _, e1, e2, err := graph.graph.FetchChannelEdgesByID(roasToSongoku) require.NoError(t, err, "unable to fetch edge") e1.ChannelFlags |= lnwire.ChanUpdateDisabled e1.LastUpdate = e1.LastUpdate.Add(time.Second) @@ -2374,7 +2340,7 @@ func runPathSourceEdgesBandwidth(t *testing.T, useCache bool) { // Since we ignore disable flags for local channels, a path should // still be found. path, err = dbFindPath( - graph.v1Graph, nil, bandwidths, + graph.graph, nil, bandwidths, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) @@ -2805,6 +2771,7 @@ func runProbabilityRouting(t *testing.T, useCache bool) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { testProbabilityRouting( @@ -3217,7 +3184,6 @@ func runInboundFees(t *testing.T, useCache bool) { type pathFindingTestContext struct { t *testing.T graph *graphdb.ChannelGraph - v1Graph *graphdb.VersionedGraph restrictParams RestrictParams bandwidthHints bandwidthHints pathFindingConfig PathFindingConfig @@ -3235,7 +3201,7 @@ func newPathFindingTestContext(t *testing.T, useCache bool, ) require.NoError(t, err, "unable to create graph") - sourceNode, err := testGraphInstance.v1Graph.SourceNode( + sourceNode, err := testGraphInstance.graph.SourceNode( t.Context(), ) require.NoError(t, err, "unable to fetch source node") @@ -3246,7 +3212,6 @@ func newPathFindingTestContext(t *testing.T, useCache bool, source: route.Vertex(sourceNode.PubKeyBytes), pathFindingConfig: *testPathFindingConfig, graph: testGraphInstance.graph, - v1Graph: testGraphInstance.v1Graph, restrictParams: *noRestrictions, bandwidthHints: &mockBandwidthHints{}, } @@ -3282,7 +3247,7 @@ func (c *pathFindingTestContext) findPath(target route.Vertex, error) { return dbFindPath( - c.v1Graph, nil, c.bandwidthHints, &c.restrictParams, + c.graph, nil, c.bandwidthHints, &c.restrictParams, &c.pathFindingConfig, c.source, target, amt, c.timePref, 0, ) } @@ -3290,7 +3255,7 @@ func (c *pathFindingTestContext) findPath(target route.Vertex, func (c *pathFindingTestContext) findBlindedPaths( restrictions *blindedPathRestrictions) ([][]blindedHop, error) { - return dbFindBlindedPaths(c.v1Graph, restrictions) + return dbFindBlindedPaths(c.graph, restrictions) } func (c *pathFindingTestContext) assertPath(path []*unifiedEdge, @@ -3312,7 +3277,7 @@ func (c *pathFindingTestContext) assertPath(path []*unifiedEdge, // dbFindPath calls findPath after getting a db transaction from the database // graph. -func dbFindPath(graph *graphdb.VersionedGraph, +func dbFindPath(graph *graphdb.ChannelGraph, additionalEdges map[route.Vertex][]AdditionalEdge, bandwidthHints bandwidthHints, r *RestrictParams, cfg *PathFindingConfig, @@ -3326,7 +3291,7 @@ func dbFindPath(graph *graphdb.VersionedGraph, } var route []*unifiedEdge - err = graph.GraphSession(ctx, func(graph graphdb.NodeTraverser) error { + err = graph.GraphSession(func(graph graphdb.NodeTraverser) error { route, _, err = findPath( &graphParams{ additionalEdges: additionalEdges, @@ -3350,7 +3315,7 @@ func dbFindPath(graph *graphdb.VersionedGraph, // dbFindBlindedPaths calls findBlindedPaths after getting a db transaction from // the database graph. -func dbFindBlindedPaths(graph *graphdb.VersionedGraph, +func dbFindBlindedPaths(graph *graphdb.ChannelGraph, restrictions *blindedPathRestrictions) ([][]blindedHop, error) { sourceNode, err := graph.SourceNode(context.Background()) @@ -3717,6 +3682,7 @@ func TestLastHopPayloadSize(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() diff --git a/routing/payment_lifecycle.go b/routing/payment_lifecycle.go index 2b8180c23..8353cba15 100644 --- a/routing/payment_lifecycle.go +++ b/routing/payment_lifecycle.go @@ -128,7 +128,7 @@ const ( // results is sent back. then process its result here. When there's no need to // wait for results, the method will exit with `stepExit` such that the payment // lifecycle loop will terminate. -func (p *paymentLifecycle) decideNextStep(ctx context.Context, +func (p *paymentLifecycle) decideNextStep( payment paymentsdb.DBMPPayment) (stateStep, error) { // Check whether we could make new HTLC attempts. @@ -168,7 +168,7 @@ func (p *paymentLifecycle) decideNextStep(ctx context.Context, // stepSkip and move to the next lifecycle iteration, which will // refresh the payment and wait for the next attempt result, if // any. - _, err := p.handleAttemptResult(ctx, r.attempt, r.result) + _, err := p.handleAttemptResult(r.attempt, r.result) // We would only get a DB-related error here, which will cause // us to abort the payment flow. @@ -190,17 +190,6 @@ func (p *paymentLifecycle) decideNextStep(ctx context.Context, func (p *paymentLifecycle) resumePayment(ctx context.Context) ([32]byte, *route.Route, error) { - // We need to make sure we can still do db operations after the context - // is cancelled. - // - // TODO(ziggie): This is a workaround to avoid a greater refactor of the - // payment lifecycle. We can currently not rely on the parent context - // because this method is also collecting the results of inflight HTLCs - // after the context is cancelled. So we need to make sure we only use - // the current context to stop creating new attempts but use this - // cleanupCtx to do all the db operations. - cleanupCtx := context.WithoutCancel(ctx) - // When the payment lifecycle loop exits, we make sure to signal any // sub goroutine of the HTLC attempt to exit, then wait for them to // return. @@ -209,7 +198,7 @@ func (p *paymentLifecycle) resumePayment(ctx context.Context) ([32]byte, // If we had any existing attempts outstanding, we'll start by spinning // up goroutines that'll collect their results and deliver them to the // lifecycle loop below. - payment, err := p.reloadInflightAttempts(ctx) + payment, err := p.reloadInflightAttempts() if err != nil { return [32]byte{}, nil, err } @@ -250,7 +239,7 @@ lifecycle: } // We update the payment state on every iteration. - currentPayment, ps, err := p.reloadPayment(cleanupCtx) + currentPayment, ps, err := p.reloadPayment() if err != nil { return exitWithErr(err) } @@ -271,7 +260,7 @@ lifecycle: // // Now decide the next step of the current lifecycle. - step, err := p.decideNextStep(cleanupCtx, payment) + step, err := p.decideNextStep(payment) if err != nil { return exitWithErr(err) } @@ -295,7 +284,7 @@ lifecycle: } // Now request a route to be used to create our HTLC attempt. - rt, err := p.requestRoute(cleanupCtx, ps) + rt, err := p.requestRoute(ps) if err != nil { return exitWithErr(err) } @@ -314,15 +303,13 @@ lifecycle: log.Tracef("Found route: %s", lnutils.SpewLogClosure(rt.Hops)) // We found a route to try, create a new HTLC attempt to try. - attempt, err := p.registerAttempt( - cleanupCtx, rt, ps.RemainingAmt, - ) + attempt, err := p.registerAttempt(rt, ps.RemainingAmt) if err != nil { return exitWithErr(err) } // Once the attempt is created, send it to the htlcswitch. - result, err := p.sendAttempt(cleanupCtx, attempt) + result, err := p.sendAttempt(attempt) if err != nil { return exitWithErr(err) } @@ -338,16 +325,13 @@ lifecycle: // terminal condition. We either return the settled preimage or the // payment's failure reason. // - // Optionally delete the failed attempts from the database. If we are - // configured to keep failed payment attempts, we skip deletion. - if !p.router.cfg.KeepFailedPaymentAttempts { - err = p.router.cfg.Control.DeleteFailedAttempts( - cleanupCtx, p.identifier, - ) - if err != nil { - log.Errorf("Error deleting failed htlc attempts "+ - "for payment %v: %v", p.identifier, err) - } + // Optionally delete the failed attempts from the database. Depends on + // the database options deleting attempts is not allowed so this will + // just be a no-op. + err = p.router.cfg.Control.DeleteFailedAttempts(p.identifier) + if err != nil { + log.Errorf("Error deleting failed htlc attempts for payment "+ + "%v: %v", p.identifier, err) } htlc, failure := payment.TerminalInfo() @@ -380,18 +364,11 @@ func (p *paymentLifecycle) checkContext(ctx context.Context) error { p.identifier.String()) } - // The context is already cancelled at this point, so we create - // a new context so the payment can successfully be marked as - // failed. - cleanupCtx := context.WithoutCancel(ctx) - // By marking the payment failed, depending on whether it has // inflight HTLCs or not, its status will now either be // `StatusInflight` or `StatusFailed`. In either case, no more // HTLCs will be attempted. - err := p.router.cfg.Control.FailPayment( - cleanupCtx, p.identifier, reason, - ) + err := p.router.cfg.Control.FailPayment(p.identifier, reason) if err != nil { return fmt.Errorf("FailPayment got %w", err) } @@ -409,7 +386,7 @@ func (p *paymentLifecycle) checkContext(ctx context.Context) error { // requestRoute is responsible for finding a route to be used to create an HTLC // attempt. -func (p *paymentLifecycle) requestRoute(ctx context.Context, +func (p *paymentLifecycle) requestRoute( ps *paymentsdb.MPPaymentState) (*route.Route, error) { remainingFees := p.calcFeeBudget(ps.FeesPaid) @@ -453,9 +430,7 @@ func (p *paymentLifecycle) requestRoute(ctx context.Context, log.Warnf("Marking payment %v permanently failed with no route: %v", p.identifier, failureCode) - err = p.router.cfg.Control.FailPayment( - ctx, p.identifier, failureCode, - ) + err = p.router.cfg.Control.FailPayment(p.identifier, failureCode) if err != nil { return nil, fmt.Errorf("FailPayment got: %w", err) } @@ -606,7 +581,7 @@ func (p *paymentLifecycle) collectResult( // registerAttempt is responsible for creating and saving an HTLC attempt in db // by using the route info provided. The `remainingAmt` is used to decide // whether this is the last attempt. -func (p *paymentLifecycle) registerAttempt(ctx context.Context, rt *route.Route, +func (p *paymentLifecycle) registerAttempt(rt *route.Route, remainingAmt lnwire.MilliSatoshi) (*paymentsdb.HTLCAttempt, error) { // If this route will consume the last remaining amount to send @@ -626,7 +601,7 @@ func (p *paymentLifecycle) registerAttempt(ctx context.Context, rt *route.Route, // Switch for its whereabouts. The route is needed to handle the result // when it eventually comes back. err = p.router.cfg.Control.RegisterAttempt( - ctx, p.identifier, &attempt.HTLCAttemptInfo, + p.identifier, &attempt.HTLCAttemptInfo, ) return attempt, err @@ -682,7 +657,7 @@ func (p *paymentLifecycle) createNewPaymentAttempt(rt *route.Route, // sendAttempt attempts to send the current attempt to the switch to complete // the payment. If this attempt fails, then we'll continue on to the next // available route. -func (p *paymentLifecycle) sendAttempt(ctx context.Context, +func (p *paymentLifecycle) sendAttempt( attempt *paymentsdb.HTLCAttempt) (*attemptResult, error) { log.Debugf("Sending HTLC attempt(id=%v, total_amt=%v, first_hop_amt=%d"+ @@ -714,7 +689,7 @@ func (p *paymentLifecycle) sendAttempt(ctx context.Context, "payment=%v, err:%v", attempt.AttemptID, p.identifier, err) - return p.failAttempt(ctx, attempt.AttemptID, err) + return p.failAttempt(attempt.AttemptID, err) } htlcAdd.OnionBlob = onionBlob @@ -728,7 +703,7 @@ func (p *paymentLifecycle) sendAttempt(ctx context.Context, log.Errorf("Failed sending attempt %d for payment %v to "+ "switch: %v", attempt.AttemptID, p.identifier, err) - return p.handleSwitchErr(ctx, attempt, err) + return p.handleSwitchErr(attempt, err) } log.Debugf("Attempt %v for payment %v successfully sent to switch, "+ @@ -819,7 +794,7 @@ func (p *paymentLifecycle) amendFirstHopData(rt *route.Route) error { // failAttemptAndPayment fails both the payment and its attempt via the // router's control tower, which marks the payment as failed in db. -func (p *paymentLifecycle) failPaymentAndAttempt(ctx context.Context, +func (p *paymentLifecycle) failPaymentAndAttempt( attemptID uint64, reason *paymentsdb.FailureReason, sendErr error) (*attemptResult, error) { @@ -831,16 +806,14 @@ func (p *paymentLifecycle) failPaymentAndAttempt(ctx context.Context, // NOTE: we must fail the payment first before failing the attempt. // Otherwise, once the attempt is marked as failed, another goroutine // might make another attempt while we are failing the payment. - err := p.router.cfg.Control.FailPayment( - ctx, p.identifier, *reason, - ) + err := p.router.cfg.Control.FailPayment(p.identifier, *reason) if err != nil { log.Errorf("Unable to fail payment: %v", err) return nil, err } // Fail the attempt. - return p.failAttempt(ctx, attemptID, sendErr) + return p.failAttempt(attemptID, sendErr) } // handleSwitchErr inspects the given error from the Switch and determines @@ -851,8 +824,7 @@ func (p *paymentLifecycle) failPaymentAndAttempt(ctx context.Context, // the error type, the error is either the final outcome of the payment or we // need to continue with an alternative route. A final outcome is indicated by // a non-nil reason value. -func (p *paymentLifecycle) handleSwitchErr(ctx context.Context, - attempt *paymentsdb.HTLCAttempt, +func (p *paymentLifecycle) handleSwitchErr(attempt *paymentsdb.HTLCAttempt, sendErr error) (*attemptResult, error) { internalErrorReason := paymentsdb.FailureReasonError @@ -879,11 +851,11 @@ func (p *paymentLifecycle) handleSwitchErr(ctx context.Context, // Fail the attempt only if there's no reason. if reason == nil { // Fail the attempt. - return p.failAttempt(ctx, attemptID, sendErr) + return p.failAttempt(attemptID, sendErr) } // Otherwise fail both the payment and the attempt. - return p.failPaymentAndAttempt(ctx, attemptID, reason, sendErr) + return p.failPaymentAndAttempt(attemptID, reason, sendErr) } // If this attempt ID is unknown to the Switch, it means it was never @@ -894,7 +866,7 @@ func (p *paymentLifecycle) handleSwitchErr(ctx context.Context, log.Warnf("Failing attempt=%v for payment=%v as it's not "+ "found in the Switch", attempt.AttemptID, p.identifier) - return p.failAttempt(ctx, attemptID, sendErr) + return p.failAttempt(attemptID, sendErr) } if errors.Is(sendErr, htlcswitch.ErrUnreadableFailureMessage) { @@ -916,7 +888,7 @@ func (p *paymentLifecycle) handleSwitchErr(ctx context.Context, ok := errors.As(sendErr, &rtErr) if !ok { return p.failPaymentAndAttempt( - ctx, attemptID, &internalErrorReason, sendErr, + attemptID, &internalErrorReason, sendErr, ) } @@ -942,7 +914,7 @@ func (p *paymentLifecycle) handleSwitchErr(ctx context.Context, ) if err != nil { return p.failPaymentAndAttempt( - ctx, attemptID, &internalErrorReason, sendErr, + attemptID, &internalErrorReason, sendErr, ) } @@ -1026,7 +998,7 @@ func (p *paymentLifecycle) handleFailureMessage(rt *route.Route, } // failAttempt calls control tower to fail the current payment attempt. -func (p *paymentLifecycle) failAttempt(ctx context.Context, attemptID uint64, +func (p *paymentLifecycle) failAttempt(attemptID uint64, sendError error) (*attemptResult, error) { log.Warnf("Attempt %v for payment %v failed: %v", attemptID, @@ -1045,7 +1017,7 @@ func (p *paymentLifecycle) failAttempt(ctx context.Context, attemptID uint64, } attempt, err := p.router.cfg.Control.FailAttempt( - ctx, p.identifier, attemptID, failInfo, + p.identifier, attemptID, failInfo, ) if err != nil { return nil, err @@ -1139,15 +1111,17 @@ func (p *paymentLifecycle) patchLegacyPaymentHash( // reloadInflightAttempts is called when the payment lifecycle is resumed after // a restart. It reloads all inflight attempts from the control tower and // collects the results of the attempts that have been sent before. -func (p *paymentLifecycle) reloadInflightAttempts( - ctx context.Context) (paymentsdb.DBMPPayment, error) { +func (p *paymentLifecycle) reloadInflightAttempts() (paymentsdb.DBMPPayment, + error) { - payment, err := p.router.cfg.Control.FetchPayment(ctx, p.identifier) + payment, err := p.router.cfg.Control.FetchPayment(p.identifier) if err != nil { return nil, err } for _, a := range payment.InFlightHTLCs() { + a := a + log.Infof("Resuming HTLC attempt %v for payment %v", a.AttemptID, p.identifier) @@ -1162,12 +1136,11 @@ func (p *paymentLifecycle) reloadInflightAttempts( } // reloadPayment returns the latest payment found in the db (control tower). -func (p *paymentLifecycle) reloadPayment( - ctx context.Context) (paymentsdb.DBMPPayment, +func (p *paymentLifecycle) reloadPayment() (paymentsdb.DBMPPayment, *paymentsdb.MPPaymentState, error) { // Read the db to get the latest state of the payment. - payment, err := p.router.cfg.Control.FetchPayment(ctx, p.identifier) + payment, err := p.router.cfg.Control.FetchPayment(p.identifier) if err != nil { return nil, nil, err } @@ -1184,14 +1157,13 @@ func (p *paymentLifecycle) reloadPayment( // handleAttemptResult processes the result of an HTLC attempt returned from // the htlcswitch. -func (p *paymentLifecycle) handleAttemptResult(ctx context.Context, - attempt *paymentsdb.HTLCAttempt, +func (p *paymentLifecycle) handleAttemptResult(attempt *paymentsdb.HTLCAttempt, result *htlcswitch.PaymentResult) (*attemptResult, error) { // If the result has an error, we need to further process it by failing // the attempt and maybe fail the payment. if result.Error != nil { - return p.handleSwitchErr(ctx, attempt, result.Error) + return p.handleSwitchErr(attempt, result.Error) } // We got an attempt settled result back from the switch. @@ -1209,7 +1181,7 @@ func (p *paymentLifecycle) handleAttemptResult(ctx context.Context, // In case of success we atomically store settle result to the DB and // move the shard to the settled state. htlcAttempt, err := p.router.cfg.Control.SettleAttempt( - ctx, p.identifier, attempt.AttemptID, + p.identifier, attempt.AttemptID, &paymentsdb.HTLCSettleInfo{ Preimage: result.Preimage, SettleTime: p.router.cfg.Clock.Now(), @@ -1234,7 +1206,7 @@ func (p *paymentLifecycle) handleAttemptResult(ctx context.Context, // available from the Switch, then records the attempt outcome with the control // tower. An attemptResult is returned, indicating the final outcome of this // HTLC attempt. -func (p *paymentLifecycle) collectAndHandleResult(ctx context.Context, +func (p *paymentLifecycle) collectAndHandleResult( attempt *paymentsdb.HTLCAttempt) (*attemptResult, error) { result, err := p.collectResult(attempt) @@ -1242,5 +1214,5 @@ func (p *paymentLifecycle) collectAndHandleResult(ctx context.Context, return nil, err } - return p.handleAttemptResult(ctx, attempt, result) + return p.handleAttemptResult(attempt, result) } diff --git a/routing/payment_lifecycle_test.go b/routing/payment_lifecycle_test.go index 3405821a2..7e94315a7 100644 --- a/routing/payment_lifecycle_test.go +++ b/routing/payment_lifecycle_test.go @@ -393,7 +393,7 @@ func TestRequestRouteSucceed(t *testing.T) { mock.Anything, ).Return(dummyRoute, nil) - result, err := p.requestRoute(t.Context(), ps) + result, err := p.requestRoute(ps) require.NoError(t, err, "expect no error") require.Equal(t, dummyRoute, result, "returned route not matched") @@ -430,7 +430,7 @@ func TestRequestRouteHandleCriticalErr(t *testing.T) { mock.Anything, ).Return(nil, errDummy) - result, err := p.requestRoute(t.Context(), ps) + result, err := p.requestRoute(ps) // Expect an error is returned since it's critical. require.ErrorIs(t, err, errDummy, "error not matched") @@ -470,7 +470,7 @@ func TestRequestRouteHandleNoRouteErr(t *testing.T) { p.identifier, paymentsdb.FailureReasonNoRoute, ).Return(nil).Once() - result, err := p.requestRoute(t.Context(), ps) + result, err := p.requestRoute(ps) // Expect no error is returned since it's not critical. require.NoError(t, err, "expected no error") @@ -513,7 +513,7 @@ func TestRequestRouteFailPaymentError(t *testing.T) { mock.Anything, ).Return(nil, errNoTlvPayload) - result, err := p.requestRoute(t.Context(), ps) + result, err := p.requestRoute(ps) // Expect an error is returned. require.ErrorIs(t, err, errDummy, "error not matched") @@ -574,6 +574,7 @@ func TestDecideNextStep(t *testing.T) { } for _, tc := range testCases { + tc := tc // Create a test paymentLifecycle. p, _ := newTestPaymentLifecycle(t) @@ -598,7 +599,7 @@ func TestDecideNextStep(t *testing.T) { // Once the setup is finished, run the test cases. t.Run(tc.name, func(t *testing.T) { - step, err := p.decideNextStep(t.Context(), payment) + step, err := p.decideNextStep(payment) require.Equal(t, tc.expectedStep, step) require.ErrorIs(t, tc.expectedErr, err) }) @@ -627,7 +628,7 @@ func TestDecideNextStepOnRouterQuit(t *testing.T) { close(p.router.quit) // Call the method under test. - step, err := p.decideNextStep(t.Context(), payment) + step, err := p.decideNextStep(payment) // We expect stepExit and an error to be returned. require.Equal(t, stepExit, step) @@ -656,7 +657,7 @@ func TestDecideNextStepOnLifecycleQuit(t *testing.T) { close(p.quit) // Call the method under test. - step, err := p.decideNextStep(t.Context(), payment) + step, err := p.decideNextStep(payment) // We expect stepExit and an error to be returned. require.Equal(t, stepExit, step) @@ -715,7 +716,7 @@ func TestDecideNextStepHandleAttemptResultSucceed(t *testing.T) { mock.Anything).Return(attempt, nil).Once() // Call the method under test. - step, err := p.decideNextStep(t.Context(), payment) + step, err := p.decideNextStep(payment) // We expect stepSkip and no error to be returned. require.Equal(t, stepSkip, step) @@ -773,7 +774,7 @@ func TestDecideNextStepHandleAttemptResultFail(t *testing.T) { mock.Anything).Return(attempt, errDummy).Once() // Call the method under test. - step, err := p.decideNextStep(t.Context(), payment) + step, err := p.decideNextStep(payment) // We expect stepExit and the above error to be returned. require.Equal(t, stepExit, step) @@ -1279,156 +1280,6 @@ func TestResumePaymentSuccess(t *testing.T) { require.Equal(t, 1, m.collectResultsCount) } -// TestKeepFailedPaymentAttempts tests that DeleteFailedAttempts is -// called or skipped based on the KeepFailedPaymentAttempts -// configuration of the router. -func TestKeepFailedPaymentAttempts(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - keepFailedPaymentAttempts bool - expectDeleteCalled bool - }{ - { - name: "keep failed attempts - " + - "delete not called", - keepFailedPaymentAttempts: true, - expectDeleteCalled: false, - }, - { - name: "delete failed attempts - " + - "delete called", - keepFailedPaymentAttempts: false, - expectDeleteCalled: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - // Create a test paymentLifecycle with the initial two - // calls mocked. - p, m := setupTestPaymentLifecycle(t) - - // Set the KeepFailedPaymentAttempts configuration. - p.router.cfg.KeepFailedPaymentAttempts = - tc.keepFailedPaymentAttempts - - // Create a dummy route that will be returned by - // `RequestRoute`. - paymentAmt := lnwire.MilliSatoshi(10000) - rt := createDummyRoute(t, paymentAmt) - - // We now enter the payment lifecycle loop. - // - // 1.1. calls `FetchPayment` and return the payment. - m.control.On("FetchPayment", p.identifier). - Return(m.payment, nil).Once() - - // 1.2. calls `GetState` and return the state. - ps := &paymentsdb.MPPaymentState{ - RemainingAmt: paymentAmt, - } - m.payment.On("GetState").Return(ps).Once() - - // NOTE: GetStatus is only used to populate the logs - // which is not critical so we loosen the checks on how - // many times it's been called. - m.payment.On("GetStatus"). - Return(paymentsdb.StatusInFlight) - - // 1.3. decideNextStep now returns stepProceed. - m.payment.On("AllowMoreAttempts"). - Return(true, nil).Once() - - // 1.4. mock requestRoute to return an route. - m.paySession.On("RequestRoute", - paymentAmt, p.feeLimit, - uint32(ps.NumAttemptsInFlight), - uint32(p.currentHeight), mock.Anything, - ).Return(rt, nil).Once() - - // 1.5. mock `registerAttempt` to return an attempt. - // - // Mock NextPaymentID to always return the attemptID. - attemptID := uint64(1) - p.router.cfg.NextPaymentID = func() (uint64, error) { - return attemptID, nil - } - - // Mock shardTracker to return the mock shard. - m.shardTracker.On("NewShard", - attemptID, true, - ).Return(m.shard, nil).Once() - - // Mock the methods on the shard. - m.shard.On("MPP").Return(&record.MPP{}).Twice(). - On("AMP").Return(nil).Once(). - On("Hash").Return(p.identifier).Once() - - // Mock the time and expect it to be called. - m.clock.On("Now").Return(time.Now()) - - // We now register attempt and return no error. - m.control.On("RegisterAttempt", - p.identifier, mock.Anything, - ).Return(nil).Once() - - // 1.6. mock `sendAttempt` to succeed, which brings us - // into the next iteration of the lifecycle. - m.payer.On("SendHTLC", - mock.Anything, attemptID, mock.Anything, - ).Return(nil).Once() - - // We now enter the second iteration of the lifecycle - // loop. - // - // 2.1. calls `FetchPayment` and return the payment. - m.control.On("FetchPayment", p.identifier). - Return(m.payment, nil).Once() - - // 2.2. calls `GetState` and return the state. - m.payment.On("GetState").Return(ps). - Run(func(args mock.Arguments) { - ps.RemainingAmt = 0 - }).Once() - - // 2.3. decideNextStep now returns stepExit and exits - // the loop. - m.payment.On("AllowMoreAttempts"). - Return(false, nil).Once(). - On("NeedWaitAttempts").Return(false, nil).Once() - - // Conditionally expect DeleteFailedAttempts to be - // called based on the configuration. - if tc.expectDeleteCalled { - m.control.On("DeleteFailedAttempts", - p.identifier).Return(nil).Once() - } - // If expectDeleteCalled is false, we don't set up the - // expectation, which means the mock will fail if it's - // called. - - // Finally, mock the `TerminalInfo` to return the - // settled attempt. Create a SettleAttempt. - testPreimage := lntypes.Preimage{1, 2, 3} - settledAttempt := makeSettledAttempt( - t, int(paymentAmt), testPreimage, - ) - m.payment.On("TerminalInfo"). - Return(settledAttempt, nil).Once() - - // Send the payment and assert the preimage is matched. - sendPaymentAndAssertSucceeded(t, p, testPreimage) - - // Expected collectResultAsync to called. - require.Equal(t, 1, m.collectResultsCount) - }) - } -} - // TestResumePaymentSuccessWithTwoAttempts checks a successful payment flow // with two HTLC attempts. // @@ -1616,7 +1467,7 @@ func TestCollectResultExitOnErr(t *testing.T) { m.clock.On("Now").Return(time.Now()) // Now call the method under test. - result, err := p.collectAndHandleResult(t.Context(), attempt) + result, err := p.collectAndHandleResult(attempt) require.ErrorIs(t, err, errDummy, "expected dummy error") require.Nil(t, result, "expected nil attempt") } @@ -1662,7 +1513,7 @@ func TestCollectResultExitOnResultErr(t *testing.T) { m.clock.On("Now").Return(time.Now()) // Now call the method under test. - result, err := p.collectAndHandleResult(t.Context(), attempt) + result, err := p.collectAndHandleResult(attempt) require.ErrorIs(t, err, errDummy, "expected dummy error") require.Nil(t, result, "expected nil attempt") } @@ -1688,7 +1539,7 @@ func TestCollectResultExitOnSwitchQuit(t *testing.T) { }) // Now call the method under test. - result, err := p.collectAndHandleResult(t.Context(), attempt) + result, err := p.collectAndHandleResult(attempt) require.ErrorIs(t, err, htlcswitch.ErrSwitchExiting, "expected switch exit") require.Nil(t, result, "expected nil attempt") @@ -1715,7 +1566,7 @@ func TestCollectResultExitOnRouterQuit(t *testing.T) { }) // Now call the method under test. - result, err := p.collectAndHandleResult(t.Context(), attempt) + result, err := p.collectAndHandleResult(attempt) require.ErrorIs(t, err, ErrRouterShuttingDown, "expected router exit") require.Nil(t, result, "expected nil attempt") } @@ -1741,7 +1592,7 @@ func TestCollectResultExitOnLifecycleQuit(t *testing.T) { }) // Now call the method under test. - result, err := p.collectAndHandleResult(t.Context(), attempt) + result, err := p.collectAndHandleResult(attempt) require.ErrorIs(t, err, ErrPaymentLifecycleExiting, "expected lifecycle exit") require.Nil(t, result, "expected nil attempt") @@ -1785,7 +1636,7 @@ func TestCollectResultExitOnSettleErr(t *testing.T) { m.clock.On("Now").Return(time.Now()) // Now call the method under test. - result, err := p.collectAndHandleResult(t.Context(), attempt) + result, err := p.collectAndHandleResult(attempt) require.ErrorIs(t, err, errDummy, "expected settle error") require.Nil(t, result, "expected nil attempt") } @@ -1827,7 +1678,7 @@ func TestCollectResultSuccess(t *testing.T) { m.clock.On("Now").Return(time.Now()) // Now call the method under test. - result, err := p.collectAndHandleResult(t.Context(), attempt) + result, err := p.collectAndHandleResult(attempt) require.NoError(t, err, "expected no error") require.Equal(t, preimage, result.attempt.Settle.Preimage, "preimage mismatch") @@ -1911,9 +1762,7 @@ func TestHandleAttemptResultWithError(t *testing.T) { // Call the method under test and expect the dummy error to be // returned. - attemptResult, err := p.handleAttemptResult( - t.Context(), attempt, result, - ) + attemptResult, err := p.handleAttemptResult(attempt, result) require.ErrorIs(t, err, errDummy, "expected fail error") require.Nil(t, attemptResult, "expected nil attempt result") } @@ -1951,9 +1800,7 @@ func TestHandleAttemptResultSuccess(t *testing.T) { // Call the method under test and expect the dummy error to be // returned. - attemptResult, err := p.handleAttemptResult( - t.Context(), attempt, result, - ) + attemptResult, err := p.handleAttemptResult(attempt, result) require.NoError(t, err, "expected no error") require.Equal(t, attempt, attemptResult.attempt) } @@ -1999,7 +1846,7 @@ func TestReloadInflightAttemptsLegacy(t *testing.T) { }) // Now call the method under test. - payment, err := p.reloadInflightAttempts(t.Context()) + payment, err := p.reloadInflightAttempts() require.NoError(t, err) require.Equal(t, m.payment, payment) diff --git a/routing/payment_session.go b/routing/payment_session.go index 4cddfa2ea..bb795211c 100644 --- a/routing/payment_session.go +++ b/routing/payment_session.go @@ -1,7 +1,6 @@ package routing import ( - "context" "fmt" "github.com/btcsuite/btcd/btcec/v2" @@ -345,7 +344,6 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi, for { err := p.graphSessFactory.GraphSession( - context.TODO(), findPath, func() { path = nil }, diff --git a/routing/payment_session_source.go b/routing/payment_session_source.go index 15820059d..bc1088d7b 100644 --- a/routing/payment_session_source.go +++ b/routing/payment_session_source.go @@ -1,6 +1,7 @@ package routing import ( + "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/htlcswitch" @@ -101,13 +102,17 @@ func RouteHintsToEdges(routeHints [][]zpay32.HopHint, target route.Vertex) ( // we'll need to look at the next hint's start node. If // we've reached the end of the hints list, we can // assume we've reached the destination. - endNode := target + endNode := &models.Node{} if i != len(routeHint)-1 { - nodeID := routeHint[i+1].NodeID - copy( - endNode[:], - nodeID.SerializeCompressed(), + endNode.AddPubKey(routeHint[i+1].NodeID) + } else { + targetPubKey, err := btcec.ParsePubKey( + target[:], ) + if err != nil { + return nil, err + } + endNode.AddPubKey(targetPubKey) } // Finally, create the channel edge from the hop hint @@ -115,7 +120,7 @@ func RouteHintsToEdges(routeHints [][]zpay32.HopHint, target route.Vertex) ( // at the start of the channel. edgePolicy := &models.CachedEdgePolicy{ ToNodePubKey: func() route.Vertex { - return endNode + return endNode.PubKeyBytes }, ToNodeFeatures: lnwire.EmptyFeatureVector(), ChannelID: hopHint.ChannelID, diff --git a/routing/payment_session_test.go b/routing/payment_session_test.go index 7ad44b8be..12d8608a4 100644 --- a/routing/payment_session_test.go +++ b/routing/payment_session_test.go @@ -1,7 +1,6 @@ package routing import ( - "context" "testing" "time" @@ -55,6 +54,7 @@ func TestValidateCLTVLimit(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase success := t.Run(testCase.name, func(t *testing.T) { err := ValidateCLTVLimit( @@ -89,9 +89,8 @@ func TestUpdateAdditionalEdge(t *testing.T) { // Create a minimal test node using the private key priv1. pub := priv1.PubKey().SerializeCompressed() - var pubKey [33]byte - copy(pubKey[:], pub) - testNode := models.NewV1ShellNode(pubKey) + testNode := &models.Node{} + copy(testNode.PubKeyBytes[:], pub) nodeID, err := testNode.PubKey() require.NoError(t, err, "failed to get node id") @@ -260,8 +259,8 @@ func (g *sessionGraph) sourceNode() route.Vertex { return route.Vertex{} } -func (g *sessionGraph) GraphSession(_ context.Context, - cb func(graph graphdb.NodeTraverser) error, _ func()) error { +func (g *sessionGraph) GraphSession(cb func(graph graphdb.NodeTraverser) error, + _ func()) error { return cb(g) } diff --git a/routing/probability_apriori.go b/routing/probability_apriori.go index eb72b5342..d37e3875e 100644 --- a/routing/probability_apriori.go +++ b/routing/probability_apriori.go @@ -6,7 +6,7 @@ import ( "math" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" ) diff --git a/routing/probability_apriori_test.go b/routing/probability_apriori_test.go index 94579477e..b7df8ae6e 100644 --- a/routing/probability_apriori_test.go +++ b/routing/probability_apriori_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" "github.com/stretchr/testify/require" @@ -315,6 +315,7 @@ func TestCapacityCutoff(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/routing/probability_bimodal.go b/routing/probability_bimodal.go index 87740f5a6..748a8d1c1 100644 --- a/routing/probability_bimodal.go +++ b/routing/probability_bimodal.go @@ -6,7 +6,7 @@ import ( "math" "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" ) diff --git a/routing/probability_bimodal_test.go b/routing/probability_bimodal_test.go index b7ecda213..57590a865 100644 --- a/routing/probability_bimodal_test.go +++ b/routing/probability_bimodal_test.go @@ -239,6 +239,7 @@ func TestSuccessProbability(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -368,6 +369,7 @@ func TestIntegral(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -669,6 +671,7 @@ func TestComputeProbability(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -745,6 +748,7 @@ func TestLocalPairProbability(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/routing/probability_estimator.go b/routing/probability_estimator.go index 253878275..c110b748d 100644 --- a/routing/probability_estimator.go +++ b/routing/probability_estimator.go @@ -3,7 +3,7 @@ package routing import ( "time" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" ) diff --git a/routing/route/blindedroute.go b/routing/route/blindedroute.go deleted file mode 100644 index 35ad07031..000000000 --- a/routing/route/blindedroute.go +++ /dev/null @@ -1,87 +0,0 @@ -package route - -import ( - "fmt" - - "github.com/btcsuite/btcd/btcec/v2" - sphinx "github.com/lightningnetwork/lightning-onion" - "github.com/lightningnetwork/lnd/lnwire" -) - -// OnionMessageBlindedPathToSphinxPath converts a complete blinded path intended -// for sending an onion message to a PaymentPath that contains the per-hop -// payloads used to encoding the routing data for each hop in the route. This -// method also accepts final hop payloads. -func OnionMessageBlindedPathToSphinxPath(blindedPath *sphinx.BlindedPath, - replyPath *lnwire.BlindedPath, finalHopTLVs []*lnwire.FinalHopTLV) ( - *sphinx.PaymentPath, error) { - - var path sphinx.PaymentPath - - // We can only construct a route if there are hops provided. - if len(blindedPath.BlindedHops) == 0 { - return nil, ErrNoRouteHopsProvided - } - - // Check maximum route length. We keep the maximum the same as - // sphinx.NumMaxHops for simplicity. In theory the maximum for onion - // messages could be higher, namely 481. See: - // https://delvingbitcoin.org/t/onion-messaging-dos-threat-mitigations - if len(blindedPath.BlindedHops) > sphinx.NumMaxHops { - return nil, ErrMaxRouteHopsExceeded - } - - // For each hop encoded within the route, we'll convert the hop struct - // to an OnionHop with matching per-hop payload within the path as used - // by the sphinx package. - for i, hop := range blindedPath.BlindedHops { - // Create an onionMessagePayload with the encrypted data for - // this hop. - onionMessagePayload := &lnwire.OnionMessagePayload{ - EncryptedData: hop.CipherText, - } - - // If we're on the final hop include the tlvs intended for the - // final hop and the reply path (if provided). - finalHop := i == len(blindedPath.BlindedHops)-1 - if finalHop { - onionMessagePayload.FinalHopTLVs = finalHopTLVs - onionMessagePayload.ReplyPath = replyPath - } - - // create a sphinx hop for this blinded hop. - hop, err := createSphinxHop( - *hop.BlindedNodePub, onionMessagePayload, - ) - if err != nil { - return nil, fmt.Errorf("sphinx hop %v: %w", i, err) - } - path[i] = *hop - } - - return &path, nil -} - -// createSphinxHop encodes an onion message payload and produces a sphinx -// onion hop for it. -func createSphinxHop(nodeID btcec.PublicKey, - onionMessagePayload *lnwire.OnionMessagePayload) (*sphinx.OnionHop, - error) { - - encodeOnionMessagePayload, err := onionMessagePayload.Encode() - if err != nil { - return nil, fmt.Errorf("failed onion message payload encode: "+ - "%w", err) - } - - hopPayload, err := sphinx.NewTLVHopPayload(encodeOnionMessagePayload) - if err != nil { - return nil, fmt.Errorf("failed creation of tlv hop payload: "+ - "%w", err) - } - - return &sphinx.OnionHop{ - NodePub: nodeID, - HopPayload: hopPayload, - }, nil -} diff --git a/routing/route/route.go b/routing/route/route.go index a575c415b..b3e91a6f4 100644 --- a/routing/route/route.go +++ b/routing/route/route.go @@ -112,7 +112,7 @@ func encodeVertex(w io.Writer, val interface{}, _ *[8]byte) error { } func decodeVertex(r io.Reader, val interface{}, _ *[8]byte, l uint64) error { - if b, ok := val.(*Vertex); ok && l == VertexSize { + if b, ok := val.(*Vertex); ok { _, err := io.ReadFull(r, b[:]) return err } @@ -164,9 +164,6 @@ type Hop struct { // The only reason we are keeping this member is that it could be the // case that we have serialised hops persisted to disk where // LegacyPayload is true. - // - // TODO(ziggie): Remove this field once we phase out the kv backend - // for payments. LegacyPayload bool // Metadata is additional data that is sent along with the payment to @@ -527,10 +524,9 @@ type Route struct { ] // FirstHopWireCustomRecords is a set of custom records that should be - // included in the wire message sent to the first hop. This is for - // example used in custom channels. Besides custom channels we use it - // also for the accountable bit. This data will be sent to the first - // hop in the UpdateAddHTLC message. + // included in the wire message sent to the first hop. This is only set + // on custom channels and is used to include additional information + // about the actual value of the payment. // // NOTE: Since these records already represent TLV records, and we // enforce them to be in the custom range (e.g. >= 65536), we don't use diff --git a/routing/route/route_test.go b/routing/route/route_test.go index b7d104d74..99594833f 100644 --- a/routing/route/route_test.go +++ b/routing/route/route_test.go @@ -8,7 +8,6 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" - "github.com/lightningnetwork/lnd/tlv" "github.com/stretchr/testify/require" ) @@ -255,6 +254,7 @@ func TestBlindedHops(t *testing.T) { } for _, testCase := range tests { + testCase := testCase t.Run(testCase.name, func(t *testing.T) { t.Parallel() @@ -357,6 +357,7 @@ func TestPayloadSize(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.name, func(t *testing.T) { t.Parallel() @@ -429,57 +430,3 @@ func TestBlindedHopFee(t *testing.T) { require.Equal(t, lnwire.MilliSatoshi(0), route.HopFee(3)) require.Equal(t, lnwire.MilliSatoshi(0), route.HopFee(4)) } - -// makeVertex creates a test Vertex with sequential byte values for testing -// TLV encoding/decoding. -func makeVertex() Vertex { - var v Vertex - for i := range v { - v[i] = byte(i) - } - - return v -} - -// TestVertexTLVEncodeDecode tests that we're able to properly encode and decode -// Vertex within TLV streams. -func TestVertexTLVEncodeDecode(t *testing.T) { - t.Parallel() - - vertex := makeVertex() - - var extraData lnwire.ExtraOpaqueData - require.NoError(t, extraData.PackRecords(&vertex)) - - var vertex2 Vertex - tlvs, err := extraData.ExtractRecords(&vertex2) - require.NoError(t, err) - - require.Contains(t, tlvs, tlv.Type(0)) - require.Equal(t, vertex, vertex2) -} - -// TestVertexTypeDecodeInvalidLength ensures that decoding a Vertex TLV -// with an invalid length (anything other than 33) fails with an error. -func TestVertexTypeDecodeInvalidLength(t *testing.T) { - t.Parallel() - - vertex := makeVertex() - - var extraData lnwire.ExtraOpaqueData - require.NoError(t, extraData.PackRecords(&vertex)) - - // Corrupt the TLV length field to simulate malformed input. - // Byte 1 contains the varint size encoding. Since 33 bytes fits into - // a single varint byte, we can directly modify extraData[1]. - extraData[1] = VertexSize + 1 - - var out Vertex - _, err := extraData.ExtractRecords(&out) - require.Error(t, err) - - extraData[1] = VertexSize - 1 - - _, err = extraData.ExtractRecords(&out) - require.Error(t, err) -} diff --git a/routing/router.go b/routing/router.go index 7a0b81b70..3c35b7c52 100644 --- a/routing/router.go +++ b/routing/router.go @@ -12,7 +12,7 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/amp" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/fn/v2" @@ -54,8 +54,8 @@ const ( // creating incompatibilities during the upgrade process. For some time // LND has used an explicit default final CLTV delta of 40 blocks for // bitcoin, though we now clamp the lower end of this - // range for user-chosen deltas to 24 blocks to be conservative. - MinCLTVDelta = 24 + // range for user-chosen deltas to 18 blocks to be conservative. + MinCLTVDelta = 18 // MaxCLTVDelta is the maximum CLTV value accepted by LND for all // timelock deltas. @@ -295,10 +295,6 @@ type Config struct { // TrafficShaper is an optional traffic shaper that can be used to // control the outgoing channel of a payment. TrafficShaper fn.Option[htlcswitch.AuxTrafficShaper] - - // KeepFailedPaymentAttempts indicates whether to keep failed payment - // attempts in the database. - KeepFailedPaymentAttempts bool } // EdgeLocator is a struct used to identify a specific edge. @@ -900,8 +896,8 @@ func (l *LightningPayment) Identifier() [32]byte { // will be returned which describes the path the successful payment traversed // within the network to reach the destination. Additionally, the payment // preimage will also be returned. -func (r *ChannelRouter) SendPayment(ctx context.Context, - payment *LightningPayment) ([32]byte, *route.Route, error) { +func (r *ChannelRouter) SendPayment(payment *LightningPayment) ([32]byte, + *route.Route, error) { paySession, shardTracker, err := r.PreparePayment(payment) if err != nil { @@ -912,7 +908,7 @@ func (r *ChannelRouter) SendPayment(ctx context.Context, spewPayment(payment)) return r.sendPayment( - ctx, payment.FeeLimit, payment.Identifier(), + context.Background(), payment.FeeLimit, payment.Identifier(), payment.PayAttemptTimeout, paySession, shardTracker, payment.FirstHopCustomRecords, ) @@ -971,8 +967,6 @@ func spewPayment(payment *LightningPayment) lnutils.LogClosure { func (r *ChannelRouter) PreparePayment(payment *LightningPayment) ( PaymentSession, shards.ShardTracker, error) { - ctx := context.TODO() - // Assemble any custom data we want to send to the first hop only. var firstHopData fn.Option[tlv.Blob] if len(payment.FirstHopCustomRecords) > 0 { @@ -1032,7 +1026,7 @@ func (r *ChannelRouter) PreparePayment(payment *LightningPayment) ( ) } - err = r.cfg.Control.InitPayment(ctx, payment.Identifier(), info) + err = r.cfg.Control.InitPayment(payment.Identifier(), info) if err != nil { return nil, nil, err } @@ -1042,8 +1036,7 @@ func (r *ChannelRouter) PreparePayment(payment *LightningPayment) ( // SendToRoute sends a payment using the provided route and fails the payment // when an error is returned from the attempt. -func (r *ChannelRouter) SendToRoute(_ context.Context, htlcHash lntypes.Hash, - rt *route.Route, +func (r *ChannelRouter) SendToRoute(htlcHash lntypes.Hash, rt *route.Route, firstHopCustomRecords lnwire.CustomRecords) (*paymentsdb.HTLCAttempt, error) { @@ -1052,8 +1045,8 @@ func (r *ChannelRouter) SendToRoute(_ context.Context, htlcHash lntypes.Hash, // SendToRouteSkipTempErr sends a payment using the provided route and fails // the payment ONLY when a terminal error is returned from the attempt. -func (r *ChannelRouter) SendToRouteSkipTempErr(_ context.Context, - htlcHash lntypes.Hash, rt *route.Route, +func (r *ChannelRouter) SendToRouteSkipTempErr(htlcHash lntypes.Hash, + rt *route.Route, firstHopCustomRecords lnwire.CustomRecords) (*paymentsdb.HTLCAttempt, error) { @@ -1071,20 +1064,13 @@ func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route, firstHopCustomRecords lnwire.CustomRecords) (*paymentsdb.HTLCAttempt, error) { - // TODO(ziggie): We cannot easily thread the context from the caller - // of this method because the payment lifecycle depends on the context - // to update the db. The Sending and Receiving of results is currently - // not cleanly separated which is the reason that we cannot easily - // cancel the context and therefore cancel the ongoing payment. - ctx := context.TODO() - // Helper function to fail a payment. It makes sure the payment is only // failed once so that the failure reason is not overwritten. failPayment := func(paymentIdentifier lntypes.Hash, reason paymentsdb.FailureReason) error { payment, fetchErr := r.cfg.Control.FetchPayment( - ctx, paymentIdentifier, + paymentIdentifier, ) if fetchErr != nil { return fetchErr @@ -1098,9 +1084,7 @@ func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route, return nil } - return r.cfg.Control.FailPayment( - ctx, paymentIdentifier, reason, - ) + return r.cfg.Control.FailPayment(paymentIdentifier, reason) } log.Debugf("SendToRoute for payment %v with skipTempErr=%v", @@ -1145,7 +1129,7 @@ func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route, FirstHopCustomRecords: firstHopCustomRecords, } - err := r.cfg.Control.InitPayment(ctx, paymentIdentifier, info) + err := r.cfg.Control.InitPayment(paymentIdentifier, info) switch { // If this is an MPP attempt and the hash is already registered with // the database, we can go on to launch the shard. @@ -1189,7 +1173,7 @@ func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route, // NOTE: we use zero `remainingAmt` here to simulate the same effect of // setting the lastShard to be false, which is used by previous // implementation. - attempt, err := p.registerAttempt(ctx, rt, 0) + attempt, err := p.registerAttempt(rt, 0) if err != nil { return nil, err } @@ -1198,7 +1182,7 @@ func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route, // the `err` returned here has already been processed by // `handleSwitchErr`, which means if there's a terminal failure, the // payment has been failed. - result, err := p.sendAttempt(ctx, attempt) + result, err := p.sendAttempt(attempt) if err != nil { return nil, err } @@ -1226,7 +1210,7 @@ func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route, // The attempt was successfully sent, wait for the result to be // available. - result, err = p.collectAndHandleResult(ctx, attempt) + result, err = p.collectAndHandleResult(attempt) if err != nil { return nil, err } @@ -1431,11 +1415,9 @@ func (r *ChannelRouter) BuildRoute(amt fn.Option[lnwire.MilliSatoshi], // resumePayments fetches inflight payments and resumes their payment // lifecycles. func (r *ChannelRouter) resumePayments() error { - ctx := context.TODO() - // Get all payments that are inflight. log.Debugf("Scanning for inflight payments") - payments, err := r.cfg.Control.FetchInFlightPayments(ctx) + payments, err := r.cfg.Control.FetchInFlightPayments() if err != nil { return err } @@ -1443,11 +1425,6 @@ func (r *ChannelRouter) resumePayments() error { log.Debugf("Scanning finished, found %d inflight payments", len(payments)) - // TODO(ziggie): Also check for payments which have no HTLCs at all - // this can happen because we register an attempt after initializing the - // payment, so there is a small chance that we init a payment but never - // register an attempt for it. - // Before we restart existing payments and start accepting more // payments to be made, we clean the network result store of the // Switch. We do this here at startup to ensure no more payments can be @@ -1476,6 +1453,7 @@ func (r *ChannelRouter) resumePayments() error { // Get the hashes used for the outstanding HTLCs. htlcs := make(map[uint64]lntypes.Hash) for _, a := range payment.HTLCs { + a := a // We check whether the individual attempts have their // HTLC hash set, if not we'll fall back to the overall @@ -1547,8 +1525,6 @@ func (r *ChannelRouter) resumePayments() error { func (r *ChannelRouter) failStaleAttempt(a paymentsdb.HTLCAttempt, payHash lntypes.Hash) { - ctx := context.TODO() - // We can only fail inflight HTLCs so we skip the settled/failed ones. if a.Failure != nil || a.Settle != nil { return @@ -1632,7 +1608,7 @@ func (r *ChannelRouter) failStaleAttempt(a paymentsdb.HTLCAttempt, Reason: paymentsdb.HTLCFailUnknown, FailTime: r.cfg.Clock.Now(), } - _, err = r.cfg.Control.FailAttempt(ctx, payHash, a.AttemptID, failInfo) + _, err = r.cfg.Control.FailAttempt(payHash, a.AttemptID, failInfo) if err != nil { log.Errorf("Fail attempt=%v got error: %v", a.AttemptID, err) } diff --git a/routing/router_test.go b/routing/router_test.go index de99af841..b811793d2 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -16,13 +16,14 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/fn/v2" + "github.com/lightningnetwork/lnd/graph" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/htlcswitch" @@ -61,8 +62,7 @@ type testCtx struct { graphBuilder *mockGraphBuilder - graph *graphdb.ChannelGraph - v1Graph *graphdb.VersionedGraph + graph *graphdb.ChannelGraph aliases map[string]route.Vertex @@ -132,10 +132,10 @@ func createTestCtxFromGraphInstanceAssumeValid(t *testing.T, ) require.NoError(t, err) - sourceNode, err := graphInstance.v1Graph.SourceNode(t.Context()) + sourceNode, err := graphInstance.graph.SourceNode(t.Context()) require.NoError(t, err) sessionSource := &SessionSource{ - GraphSessionFactory: graphInstance.v1Graph, + GraphSessionFactory: graphInstance.graph, SourceNode: sourceNode, GetLink: graphInstance.getLink, PathFindingConfig: pathFindingConfig, @@ -146,7 +146,7 @@ func createTestCtxFromGraphInstanceAssumeValid(t *testing.T, router, err := New(Config{ SelfNode: sourceNode.PubKeyBytes, - RoutingGraph: graphInstance.v1Graph, + RoutingGraph: graphInstance.graph, Chain: chain, Payer: &mockPaymentAttemptDispatcherOld{}, Control: makeMockControlTower(), @@ -165,19 +165,15 @@ func createTestCtxFromGraphInstanceAssumeValid(t *testing.T, &mockTrafficShaper{}, ), }) - require.NoError(t, err, "unable to create router") require.NoError(t, router.Start(), "unable to start router") ctx := &testCtx{ router: router, graphBuilder: graphBuilder, graph: graphInstance.graph, - v1Graph: graphdb.NewVersionedGraph( - graphInstance.graph, lnwire.GossipVersion1, - ), - aliases: graphInstance.aliasMap, - privKeys: graphInstance.privKeyMap, - channelIDs: graphInstance.channelIDs, + aliases: graphInstance.aliasMap, + privKeys: graphInstance.privKeyMap, + channelIDs: graphInstance.channelIDs, } t.Cleanup(func() { @@ -196,16 +192,16 @@ func createTestNode() (*models.Node, error) { } pub := priv.PubKey().SerializeCompressed() - n := models.NewV1Node( - route.NewVertex(priv.PubKey()), &models.NodeV1Fields{ - LastUpdate: time.Unix(updateTime, 0), - Addresses: testAddrs, - Color: color.RGBA{1, 2, 3, 0}, - Alias: "kek" + string(pub), - AuthSigBytes: testSig.Serialize(), - Features: testFeatures.RawFeatureVector, - }, - ) + n := &models.Node{ + HaveNodeAnnouncement: true, + LastUpdate: time.Unix(updateTime, 0), + Addresses: testAddrs, + Color: color.RGBA{1, 2, 3, 0}, + Alias: "kek" + string(pub), + AuthSigBytes: testSig.Serialize(), + Features: testFeatures, + } + copy(n.PubKeyBytes[:], pub) return n, nil } @@ -329,9 +325,7 @@ func TestSendPaymentRouteFailureFallback(t *testing.T) { // Send off the payment request to the router, route through pham nuwen // should've been selected as a fall back and succeeded correctly. - paymentPreImage, route, err := ctx.router.SendPayment( - t.Context(), payment, - ) + paymentPreImage, route, err := ctx.router.SendPayment(payment) require.NoErrorf(t, err, "unable to send payment: %v", payment.paymentHash) @@ -410,9 +404,7 @@ func TestSendPaymentRouteInfiniteLoopWithBadHopHint(t *testing.T) { // Send off the payment request to the router, should succeed // ignoring the bad channel id hint. - paymentPreImage, route, paymentErr := ctx.router.SendPayment( - t.Context(), payment, - ) + paymentPreImage, route, paymentErr := ctx.router.SendPayment(payment) require.NoErrorf(t, paymentErr, "unable to send payment: %v", payment.paymentHash) @@ -459,7 +451,7 @@ func TestChannelUpdateValidation(t *testing.T) { // Assert that the initially configured fee is retrieved correctly. _, e1, e2, err := ctx.graph.FetchChannelEdgesByID( - t.Context(), lnwire.NewShortChanIDFromInt(1).ToUint64(), + lnwire.NewShortChanIDFromInt(1).ToUint64(), ) require.NoError(t, err, "cannot retrieve channel") @@ -527,11 +519,11 @@ func TestChannelUpdateValidation(t *testing.T) { // Send off the payment request to the router. The specified route // should be attempted and the channel update should be received by // graph and ignored because it is missing a valid signature. - _, err = ctx.router.SendToRoute(t.Context(), payment, rt, nil) + _, err = ctx.router.SendToRoute(payment, rt, nil) require.Error(t, err, "expected route to fail with channel update") _, e1, e2, err = ctx.graph.FetchChannelEdgesByID( - t.Context(), lnwire.NewShortChanIDFromInt(1).ToUint64(), + lnwire.NewShortChanIDFromInt(1).ToUint64(), ) require.NoError(t, err, "cannot retrieve channel") @@ -547,13 +539,13 @@ func TestChannelUpdateValidation(t *testing.T) { ctx.graphBuilder.setNextReject(false) // Retry the payment using the same route as before. - _, err = ctx.router.SendToRoute(t.Context(), payment, rt, nil) + _, err = ctx.router.SendToRoute(payment, rt, nil) require.Error(t, err, "expected route to fail with channel update") // This time a valid signature was supplied and the policy change should // have been applied to the graph. _, e1, e2, err = ctx.graph.FetchChannelEdgesByID( - t.Context(), lnwire.NewShortChanIDFromInt(1).ToUint64(), + lnwire.NewShortChanIDFromInt(1).ToUint64(), ) require.NoError(t, err, "cannot retrieve channel") @@ -594,7 +586,7 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) { // to sophon. We'll obtain this as we'll need to to generate the // FeeInsufficient error that we'll send back. _, _, edgeUpdateToFail, err := ctx.graph.FetchChannelEdgesByID( - t.Context(), songokuSophonChanID, + songokuSophonChanID, ) require.NoError(t, err, "unable to fetch chan id") @@ -643,9 +635,7 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) { // Send off the payment request to the router, route through phamnuwen // should've been selected as a fall back and succeeded correctly. - paymentPreImage, route, err := ctx.router.SendPayment( - t.Context(), payment, - ) + paymentPreImage, route, err := ctx.router.SendPayment(payment) require.NoErrorf(t, err, "unable to send payment: %v", payment.paymentHash) @@ -752,9 +742,7 @@ func TestSendPaymentErrorFeeInsufficientPrivateEdge(t *testing.T) { // Send off the payment request to the router, route through son // goku and then across the private channel to elst. - paymentPreImage, route, err := ctx.router.SendPayment( - t.Context(), payment, - ) + paymentPreImage, route, err := ctx.router.SendPayment(payment) require.NoErrorf(t, err, "unable to send payment: %v", payment.paymentHash) @@ -880,9 +868,7 @@ func TestSendPaymentPrivateEdgeUpdateFeeExceedsLimit(t *testing.T) { // Send off the payment request to the router, route through son // goku and then across the private channel to elst. - paymentPreImage, route, err := ctx.router.SendPayment( - t.Context(), payment, - ) + paymentPreImage, route, err := ctx.router.SendPayment(payment) require.NoErrorf(t, err, "unable to send payment: %v", payment.paymentHash) @@ -947,9 +933,7 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) { chanID := ctx.getChannelIDFromAlias(t, "roasbeef", "songoku") roasbeefSongoku := lnwire.NewShortChanIDFromInt(chanID) - _, _, edgeUpdateToFail, err := ctx.graph.FetchChannelEdgesByID( - t.Context(), chanID, - ) + _, _, edgeUpdateToFail, err := ctx.graph.FetchChannelEdgesByID(chanID) require.NoError(t, err, "unable to fetch chan id") errChanUpdate := lnwire.ChannelUpdate1{ @@ -1007,9 +991,7 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) { // Send off the payment request to the router, this payment should // succeed as we should actually go through Pham Nuwen in order to get // to Sophon, even though he has higher fees. - paymentPreImage, rt, err := ctx.router.SendPayment( - t.Context(), payment, - ) + paymentPreImage, rt, err := ctx.router.SendPayment(payment) require.NoErrorf(t, err, "unable to send payment: %v", payment.paymentHash) @@ -1035,9 +1017,7 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) { // w.r.t to the block height, and instead go through Pham Nuwen. We // flip a bit in the payment hash to allow resending this payment. payment.paymentHash[1] ^= 1 - paymentPreImage, rt, err = ctx.router.SendPayment( - t.Context(), payment, - ) + paymentPreImage, rt, err = ctx.router.SendPayment(payment) require.NoErrorf(t, err, "unable to send payment: %v", payment.paymentHash) @@ -1106,7 +1086,7 @@ func TestSendPaymentErrorPathPruning(t *testing.T) { // When we try to dispatch that payment, we should receive an error as // both attempts should fail and cause both routes to be pruned. - _, _, err = ctx.router.SendPayment(t.Context(), payment) + _, _, err = ctx.router.SendPayment(payment) require.Error(t, err, "payment didn't return error") // The final error returned should also indicate that the peer wasn't @@ -1114,9 +1094,7 @@ func TestSendPaymentErrorPathPruning(t *testing.T) { require.Equal(t, paymentsdb.FailureReasonNoRoute, err) // Inspect the two attempts that were made before the payment failed. - p, err := ctx.router.cfg.Control.FetchPayment( - t.Context(), *payment.paymentHash, - ) + p, err := ctx.router.cfg.Control.FetchPayment(*payment.paymentHash) require.NoError(t, err) htlcs := p.GetHTLCs() @@ -1151,9 +1129,7 @@ func TestSendPaymentErrorPathPruning(t *testing.T) { // This shouldn't return an error, as we'll make a payment attempt via // the pham nuwen channel based on the assumption that there might be an // intermittent issue with the songoku <-> sophon channel. - paymentPreImage, rt, err := ctx.router.SendPayment( - t.Context(), payment, - ) + paymentPreImage, rt, err := ctx.router.SendPayment(payment) require.NoErrorf(t, err, "unable to send payment: %v", payment.paymentHash) @@ -1193,9 +1169,7 @@ func TestSendPaymentErrorPathPruning(t *testing.T) { // We flip a bit in the payment hash to allow resending this payment. payment.paymentHash[1] ^= 1 - paymentPreImage, rt, err = ctx.router.SendPayment( - t.Context(), payment, - ) + paymentPreImage, rt, err = ctx.router.SendPayment(payment) require.NoErrorf(t, err, "unable to send payment: %v", payment.paymentHash) @@ -1228,7 +1202,7 @@ func TestFindPathFeeWeighting(t *testing.T) { var preImage [32]byte copy(preImage[:], bytes.Repeat([]byte{9}, 32)) - sourceNode, err := ctx.v1Graph.SourceNode(t.Context()) + sourceNode, err := ctx.graph.SourceNode(t.Context()) require.NoError(t, err, "unable to fetch source node") amt := lnwire.MilliSatoshi(100) @@ -1239,7 +1213,7 @@ func TestFindPathFeeWeighting(t *testing.T) { // the edge weighting, we should select the direct path over the 2 hop // path even though the direct path has a higher potential time lock. path, err := dbFindPath( - ctx.v1Graph, nil, &mockBandwidthHints{}, + ctx.graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, target, amt, 0, 0, @@ -1327,7 +1301,7 @@ func TestUnknownErrorSource(t *testing.T) { // the route a->b->c is tried first. An unreadable faiure is returned // which should pruning the channel a->b. We expect the payment to // succeed via a->d. - _, _, err = ctx.router.SendPayment(t.Context(), payment) + _, _, err = ctx.router.SendPayment(payment) require.NoErrorf(t, err, "unable to send payment: %v", payment.paymentHash) @@ -1352,7 +1326,7 @@ func TestUnknownErrorSource(t *testing.T) { // Send off the payment request to the router. We expect the payment to // fail because both routes have been pruned. payment.paymentHash[1] ^= 1 - _, _, err = ctx.router.SendPayment(t.Context(), payment) + _, _, err = ctx.router.SendPayment(payment) if err == nil { t.Fatalf("expected payment to fail") } @@ -1424,6 +1398,8 @@ func TestSendToRouteStructuredError(t *testing.T) { } for failIndex, errorType := range testCases { + failIndex := failIndex + errorType := errorType t.Run(fmt.Sprintf("%T", errorType), func(t *testing.T) { // We'll modify the SendToSwitch method so that it @@ -1446,9 +1422,7 @@ func TestSendToRouteStructuredError(t *testing.T) { // update should be received by router and ignored // because it is missing a valid // signature. - _, err = ctx.router.SendToRoute( - t.Context(), payment, rt, nil, - ) + _, err = ctx.router.SendToRoute(payment, rt, nil) fErr, ok := err.(*htlcswitch.ForwardingError) require.True( @@ -1527,7 +1501,7 @@ func TestSendToRouteMaxHops(t *testing.T) { // Send off the payment request to the router. We expect an error back // indicating that the route is too long. var payHash lntypes.Hash - _, err = ctx.router.SendToRoute(t.Context(), payHash, rt, nil) + _, err = ctx.router.SendToRoute(payHash, rt, nil) if err != route.ErrMaxRouteHopsExceeded { t.Fatalf("expected ErrMaxRouteHopsExceeded, but got %v", err) } @@ -2085,6 +2059,7 @@ func TestInboundOutbound(t *testing.T) { } for _, tc := range tests { + tc := tc t.Run(tc.name, func(tt *testing.T) { testInboundOutboundFee( @@ -2241,9 +2216,7 @@ func TestSendToRouteSkipTempErrSuccess(t *testing.T) { ).Return(nil) // Expect a successful send to route. - attempt, err := router.SendToRouteSkipTempErr( - t.Context(), payHash, rt, nil, - ) + attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil) require.NoError(t, err) require.Equal(t, testAttempt, attempt) @@ -2298,9 +2271,7 @@ func TestSendToRouteSkipTempErrNonMPP(t *testing.T) { }} // Expect an error to be returned. - attempt, err := router.SendToRouteSkipTempErr( - t.Context(), payHash, rt, nil, - ) + attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil) require.ErrorIs(t, ErrSkipTempErr, err) require.Nil(t, attempt) @@ -2380,9 +2351,7 @@ func TestSendToRouteSkipTempErrTempFailure(t *testing.T) { ).Return(nil, nil) // Expect a failed send to route. - attempt, err := router.SendToRouteSkipTempErr( - t.Context(), payHash, rt, nil, - ) + attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil) require.Equal(t, tempErr, err) require.Equal(t, testAttempt, attempt) @@ -2466,9 +2435,7 @@ func TestSendToRouteSkipTempErrPermanentFailure(t *testing.T) { ).Return(&failureReason, nil) // Expect a failed send to route. - attempt, err := router.SendToRouteSkipTempErr( - t.Context(), payHash, rt, nil, - ) + attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil) require.Equal(t, permErr, err) require.Equal(t, testAttempt, attempt) @@ -2557,7 +2524,7 @@ func TestSendToRouteTempFailure(t *testing.T) { ).Return(nil, nil) // Expect a failed send to route. - attempt, err := router.SendToRoute(t.Context(), payHash, rt, nil) + attempt, err := router.SendToRoute(payHash, rt, nil) require.Equal(t, tempErr, err) require.Equal(t, testAttempt, attempt) @@ -2690,6 +2657,7 @@ func TestNewRouteRequest(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.name, func(t *testing.T) { t.Parallel() @@ -2750,11 +2718,11 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { copy(pub2[:], priv2.PubKey().SerializeCompressed()) // The two nodes we are about to add should not exist yet. - exists1, err := ctx.v1Graph.HasNode(ctxb, pub1) + _, exists1, err := ctx.graph.HasNode(ctxb, pub1) require.NoError(t, err, "unable to query graph") require.False(t, exists1) - exists2, err := ctx.v1Graph.HasNode(ctxb, pub2) + _, exists2, err := ctx.graph.HasNode(ctxb, pub2) require.NoError(t, err, "unable to query graph") require.False(t, exists2) @@ -2767,20 +2735,20 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { ) require.NoError(t, err, "unable to create channel edge") - edge, err := models.NewV1Channel( - chanID.ToUint64(), chainhash.Hash{}, pub1, pub2, - &models.ChannelV1Fields{ - BitcoinKey1Bytes: pub1, - BitcoinKey2Bytes: pub2, - }, - ) - require.NoError(t, err) + edge := &models.ChannelEdgeInfo{ + ChannelID: chanID.ToUint64(), + NodeKey1Bytes: pub1, + NodeKey2Bytes: pub2, + BitcoinKey1Bytes: pub1, + BitcoinKey2Bytes: pub2, + Features: lnwire.EmptyFeatureVector(), + AuthProof: nil, + } require.NoError(t, ctx.graph.AddChannelEdge(ctxb, edge)) // We must add the edge policy to be able to use the edge for route // finding. edgePolicy := &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: edge.ChannelID, LastUpdate: testTime, @@ -2796,7 +2764,6 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { // Create edge in the other direction as well. edgePolicy = &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: edge.ChannelID, LastUpdate: testTime, @@ -2812,11 +2779,11 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { // After adding the edge between the two previously unknown nodes, they // should have been added to the graph. - exists1, err = ctx.v1Graph.HasNode(ctxb, pub1) + _, exists1, err = ctx.graph.HasNode(ctxb, pub1) require.NoError(t, err, "unable to query graph") require.True(t, exists1) - exists2, err = ctx.v1Graph.HasNode(ctxb, pub2) + _, exists2, err = ctx.graph.HasNode(ctxb, pub2) require.NoError(t, err, "unable to query graph") require.True(t, exists2) @@ -2848,22 +2815,19 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { 10000, 510) require.NoError(t, err, "unable to create channel edge") - node1Vertex, err := route.NewVertexFromBytes(node1Bytes) - require.NoError(t, err) - - edge, err = models.NewV1Channel( - chanID.ToUint64(), chainhash.Hash{}, node1Vertex, node2Bytes, - &models.ChannelV1Fields{ - BitcoinKey1Bytes: node1Vertex, - BitcoinKey2Bytes: node2Bytes, - }, - ) - require.NoError(t, err) + edge = &models.ChannelEdgeInfo{ + ChannelID: chanID.ToUint64(), + Features: lnwire.EmptyFeatureVector(), + AuthProof: nil, + } + copy(edge.NodeKey1Bytes[:], node1Bytes) + edge.NodeKey2Bytes = node2Bytes + copy(edge.BitcoinKey1Bytes[:], node1Bytes) + edge.BitcoinKey2Bytes = node2Bytes require.NoError(t, ctx.graph.AddChannelEdge(ctxb, edge)) edgePolicy = &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: edge.ChannelID, LastUpdate: testTime, @@ -2878,7 +2842,6 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { require.NoError(t, ctx.graph.UpdateEdgePolicy(ctxb, edgePolicy)) edgePolicy = &models.ChannelEdgePolicy{ - Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: edge.ChannelID, LastUpdate: testTime, @@ -2908,29 +2871,29 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { // Now check that we can update the node info for the partial node // without messing up the channel graph. - n1 := models.NewV1Node( - route.NewVertex(priv1.PubKey()), &models.NodeV1Fields{ - LastUpdate: time.Unix(123, 0), - Addresses: testAddrs, - Color: color.RGBA{1, 2, 3, 0}, - Alias: "node11", - AuthSigBytes: testSig.Serialize(), - Features: testFeatures.RawFeatureVector, - }, - ) + n1 := &models.Node{ + HaveNodeAnnouncement: true, + LastUpdate: time.Unix(123, 0), + Addresses: testAddrs, + Color: color.RGBA{1, 2, 3, 0}, + Alias: "node11", + AuthSigBytes: testSig.Serialize(), + Features: testFeatures, + } + copy(n1.PubKeyBytes[:], priv1.PubKey().SerializeCompressed()) require.NoError(t, ctx.graph.AddNode(ctxb, n1)) - n2 := models.NewV1Node( - route.NewVertex(priv2.PubKey()), &models.NodeV1Fields{ - LastUpdate: time.Unix(123, 0), - Addresses: testAddrs, - Color: color.RGBA{1, 2, 3, 0}, - Alias: "node22", - AuthSigBytes: testSig.Serialize(), - Features: testFeatures.RawFeatureVector, - }, - ) + n2 := &models.Node{ + HaveNodeAnnouncement: true, + LastUpdate: time.Unix(123, 0), + Addresses: testAddrs, + Color: color.RGBA{1, 2, 3, 0}, + Alias: "node22", + AuthSigBytes: testSig.Serialize(), + Features: testFeatures, + } + copy(n2.PubKeyBytes[:], priv2.PubKey().SerializeCompressed()) require.NoError(t, ctx.graph.AddNode(ctxb, n2)) @@ -2945,12 +2908,12 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { _, _, err = ctx.router.FindRoute(req) require.NoError(t, err, "unable to find any routes") - copy1, err := ctx.v1Graph.FetchNode(ctxb, pub1) + copy1, err := ctx.graph.FetchNode(ctxb, pub1) require.NoError(t, err, "unable to fetch node") require.Equal(t, n1.Alias, copy1.Alias) - copy2, err := ctx.v1Graph.FetchNode(ctxb, pub2) + copy2, err := ctx.graph.FetchNode(ctxb, pub2) require.NoError(t, err, "unable to fetch node") require.Equal(t, n2.Alias, copy2.Alias) @@ -2978,7 +2941,7 @@ type mockGraphBuilder struct { updateEdge func(update *models.ChannelEdgePolicy) error } -func newMockGraphBuilder(graph *graphdb.ChannelGraph) *mockGraphBuilder { +func newMockGraphBuilder(graph graph.DB) *mockGraphBuilder { return &mockGraphBuilder{ updateEdge: func(update *models.ChannelEdgePolicy) error { return graph.UpdateEdgePolicy( @@ -2997,13 +2960,19 @@ func (m *mockGraphBuilder) ApplyChannelUpdate(msg *lnwire.ChannelUpdate1) bool { return false } - update, err := models.ChanEdgePolicyFromWire( - msg.ShortChannelID.ToUint64(), msg, - ) - if err != nil { - return false - } - err = m.updateEdge(update) + err := m.updateEdge(&models.ChannelEdgePolicy{ + SigBytes: msg.Signature.ToSignatureBytes(), + ChannelID: msg.ShortChannelID.ToUint64(), + LastUpdate: time.Unix(int64(msg.Timestamp), 0), + MessageFlags: msg.MessageFlags, + ChannelFlags: msg.ChannelFlags, + TimeLockDelta: msg.TimeLockDelta, + MinHTLC: msg.HtlcMinimumMsat, + MaxHTLC: msg.HtlcMaximumMsat, + FeeBaseMSat: lnwire.MilliSatoshi(msg.BaseFee), + FeeProportionalMillionths: lnwire.MilliSatoshi(msg.FeeRate), + ExtraOpaqueData: msg.ExtraOpaqueData, + }) return err == nil } diff --git a/routing/unified_edges.go b/routing/unified_edges.go index 7195de17c..9b8f6c5c0 100644 --- a/routing/unified_edges.go +++ b/routing/unified_edges.go @@ -1,10 +1,9 @@ package routing import ( - "context" "math" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lnwire" @@ -117,7 +116,7 @@ func (u *nodeEdgeUnifier) addGraphPolicies(g Graph) error { // Iterate over all channels of the to node. err := g.ForEachNodeDirectedChannel( - context.TODO(), u.toNode, cb, func() { + u.toNode, cb, func() { channels = nil }, ) @@ -189,7 +188,7 @@ func (u *unifiedEdge) amtInRange(amt lnwire.MilliSatoshi) bool { } // Skip channels for which this htlc is too large. - if u.policy.HasMaxHTLC && + if u.policy.MessageFlags.HasMaxHtlc() && amt > u.policy.MaxHTLC { log.Tracef("Exceeds policy's MaxHTLC: amt=%v, MaxHTLC=%v", @@ -377,7 +376,7 @@ func (u *edgeUnifier) getEdgeNetwork(netAmtReceived lnwire.MilliSatoshi, } // For network channels, skip the disabled ones. - if edge.policy.IsDisabled { + if edge.policy.IsDisabled() { log.Debugf("Skipped edge %v due to it being disabled", edge.policy.ChannelID) continue @@ -386,7 +385,7 @@ func (u *edgeUnifier) getEdgeNetwork(netAmtReceived lnwire.MilliSatoshi, // Track the maximal capacity for usable channels. If we don't // know the capacity, we fall back to MaxHTLC. capMsat := lnwire.NewMSatFromSatoshis(edge.capacity) - if capMsat == 0 && edge.policy.HasMaxHTLC { + if capMsat == 0 && edge.policy.MessageFlags.HasMaxHtlc() { log.Tracef("No capacity available for channel %v, "+ "using MaxHtlcMsat (%v) as a fallback.", edge.policy.ChannelID, edge.policy.MaxHTLC) diff --git a/routing/unified_edges_test.go b/routing/unified_edges_test.go index f19fa5875..8fc79031a 100644 --- a/routing/unified_edges_test.go +++ b/routing/unified_edges_test.go @@ -3,7 +3,7 @@ package routing import ( "testing" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" @@ -30,7 +30,7 @@ func TestNodeEdgeUnifier(t *testing.T) { FeeProportionalMillionths: 100000, FeeBaseMSat: 30, TimeLockDelta: 60, - HasMaxHTLC: true, + MessageFlags: lnwire.ChanUpdateRequiredMaxHtlc, MaxHTLC: 5000, MinHTLC: 100, } @@ -39,7 +39,7 @@ func TestNodeEdgeUnifier(t *testing.T) { FeeProportionalMillionths: 190000, FeeBaseMSat: 10, TimeLockDelta: 40, - HasMaxHTLC: true, + MessageFlags: lnwire.ChanUpdateRequiredMaxHtlc, MaxHTLC: 4000, MinHTLC: 100, } @@ -224,6 +224,7 @@ func TestNodeEdgeUnifier(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/rpcperms/interceptor.go b/rpcperms/interceptor.go index d9c9e6f0e..524e4bb7d 100644 --- a/rpcperms/interceptor.go +++ b/rpcperms/interceptor.go @@ -449,9 +449,9 @@ func (r *InterceptorChain) Permissions() map[string][]bakery.Op { // RegisterMiddleware registers a new middleware that will handle request/ // response interception for all RPC messages that are initiated with a custom -// macaroon caveat. Only one read/write middleware can be registered for each -// custom caveat name. Multiple read-only middlewares are permitted since they -// cannot modify responses. +// macaroon caveat. The name of the custom caveat a middleware is handling is +// also its unique identifier. Only one middleware can be registered for each +// custom caveat. func (r *InterceptorChain) RegisterMiddleware(mw *MiddlewareHandler) error { r.Lock() defer r.Unlock() @@ -463,14 +463,11 @@ func (r *InterceptorChain) RegisterMiddleware(mw *MiddlewareHandler) error { "registered", mw.middlewareName) } - // We only want one read/write middleware per custom caveat name since - // multiple could overwrite each other's responses. Read-only - // middlewares are exempt because they cannot modify responses. + // For now, we only want one middleware per custom caveat name. If we + // allowed multiple middlewares handling the same caveat there would be + // a need for extra call chaining logic, and they could overwrite each + // other's responses. for _, middleware := range r.registeredMiddleware { - if middleware.readOnly && mw.readOnly { - continue - } - if middleware.customCaveatName == mw.customCaveatName { return fmt.Errorf("a middleware is already registered "+ "for the custom caveat name '%s': %v", @@ -1211,14 +1208,8 @@ func (r *InterceptorChain) interceptMessage(ctx context.Context, // The message was replaced, make sure the next middleware in // line receives the updated message. - if resp.replace { - if middleware.readOnly { - log.Warnf("Read-only middleware %s attempted "+ - "to replace message, ignoring", - middleware.middlewareName) - } else { - currentMessage = resp.replacement - } + if !middleware.readOnly && resp.replace { + currentMessage = resp.replacement } } diff --git a/rpcperms/middleware_handler.go b/rpcperms/middleware_handler.go index 00d0463bd..fa1e8510f 100644 --- a/rpcperms/middleware_handler.go +++ b/rpcperms/middleware_handler.go @@ -9,7 +9,7 @@ import ( "sync/atomic" "time" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/macaroons" "google.golang.org/grpc/metadata" diff --git a/rpcserver.go b/rpcserver.go index bfedfbf71..886b61813 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -6,7 +6,7 @@ import ( "encoding/hex" "errors" "fmt" - "image/color" + "io" "maps" "math" "net" @@ -21,16 +21,15 @@ import ( "sync/atomic" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/psbt/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/wallet/txauthor" @@ -43,7 +42,6 @@ import ( "github.com/lightningnetwork/lnd/chanfitness" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channelnotifier" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/discovery" @@ -70,13 +68,12 @@ import ( "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chancloser" "github.com/lightningnetwork/lnd/lnwallet/chanfunding" - "github.com/lightningnetwork/lnd/lnwallet/types" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/macaroons" - "github.com/lightningnetwork/lnd/onionmessage" paymentsdb "github.com/lightningnetwork/lnd/payments/db" "github.com/lightningnetwork/lnd/peer" "github.com/lightningnetwork/lnd/peernotifier" + "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/routing/blindedpath" "github.com/lightningnetwork/lnd/routing/route" @@ -398,6 +395,22 @@ func MainRPCServerPermissions() map[string][]bakery.Op { Entity: "offchain", Action: "read", }}, + "/lnrpc.Lightning/SendPayment": {{ + Entity: "offchain", + Action: "write", + }}, + "/lnrpc.Lightning/SendPaymentSync": {{ + Entity: "offchain", + Action: "write", + }}, + "/lnrpc.Lightning/SendToRoute": {{ + Entity: "offchain", + Action: "write", + }}, + "/lnrpc.Lightning/SendToRouteSync": {{ + Entity: "offchain", + Action: "write", + }}, "/lnrpc.Lightning/AddInvoice": {{ Entity: "invoices", Action: "write", @@ -560,14 +573,6 @@ func MainRPCServerPermissions() map[string][]bakery.Op { Entity: "offchain", Action: "read", }}, - "/lnrpc.Lightning/SendOnionMessage": {{ - Entity: "offchain", - Action: "write", - }}, - "/lnrpc.Lightning/SubscribeOnionMessages": {{ - Entity: "offchain", - Action: "read", - }}, "/lnrpc.Lightning/LookupHtlcResolution": {{ Entity: "offchain", Action: "read", @@ -680,6 +685,8 @@ func newRPCServer(cfg *Config, interceptorChain *rpcperms.InterceptorChain, // addDeps populates all dependencies needed by the RPC server, and any // of the sub-servers that it maintains. When this is done, the RPC server can // be started, and start accepting RPC calls. +// +//nolint:funlen func (r *rpcServer) addDeps(ctx context.Context, s *server, macService *macaroons.Service, subServerCgs *subRPCServerConfigs, atpl *autopilot.Manager, @@ -688,11 +695,11 @@ func (r *rpcServer) addDeps(ctx context.Context, s *server, invoiceHtlcModifier *invoices.HtlcModificationInterceptor) error { // Set up router rpc backend. - selfNode, err := s.v1Graph.SourceNode(ctx) + selfNode, err := s.graphDB.SourceNode(ctx) if err != nil { return err } - graph := s.v1Graph + graph := s.graphDB routerBackend := &routerrpc.RouterBackend{ SelfNode: selfNode.PubKeyBytes, @@ -700,9 +707,7 @@ func (r *rpcServer) addDeps(ctx context.Context, s *server, FetchChannelCapacity: func(chanID uint64) (btcutil.Amount, error) { - info, _, _, err := graph.FetchChannelEdgesByID( - ctx, chanID, - ) + info, _, _, err := graph.FetchChannelEdgesByID(chanID) if err != nil { return 0, err } @@ -720,7 +725,7 @@ func (r *rpcServer) addDeps(ctx context.Context, s *server, route.Vertex, error) { info, _, _, err := graph.FetchChannelEdgesByID( - ctx, chanID, + chanID, ) if err != nil { return route.Vertex{}, route.Vertex{}, @@ -732,7 +737,7 @@ func (r *rpcServer) addDeps(ctx context.Context, s *server, return info.NodeKey1Bytes, info.NodeKey2Bytes, nil }, HasNode: func(nodePub route.Vertex) (bool, error) { - exists, err := s.v1Graph.HasNode(ctx, nodePub) + _, exists, err := graph.HasNode(ctx, nodePub) return exists, err }, @@ -766,12 +771,15 @@ func (r *rpcServer) addDeps(ctx context.Context, s *server, return nil }, - ShouldSetExpAccountability: func() bool { - return !s.cfg.ProtocolOptions.NoExpAccountability() + ShouldSetExpEndorsement: func() bool { + if s.cfg.ProtocolOptions.NoExperimentalEndorsement() { + return false + } + + return clock.NewDefaultClock().Now().Before( + EndorsementExperimentEnd, + ) }, - ForwardingLog: s.miscDB.ForwardingLog(), - MinForwardingHistoryAge: s.cfg.Dev.GetMinFwdHistoryAge(), - FwdHistoryDeleteBatchSize: s.cfg.FwdHistoryDeleteBatchSize, } genInvoiceFeatures := func() *lnwire.FeatureVector { @@ -1052,7 +1060,7 @@ func addrPairsToOutputs(addrPairs map[string]int64, outputs := make([]*wire.TxOut, 0, len(addrPairs)) for addr, amt := range addrPairs { - addr, err := address.DecodeAddress(addr, params) + addr, err := btcutil.DecodeAddress(addr, params) if err != nil { return nil, err } @@ -1265,29 +1273,14 @@ func (r *rpcServer) EstimateFee(ctx context.Context, return nil, err } - var selectOutpoints fn.Set[wire.OutPoint] - if len(in.Inputs) != 0 { - wireOutpoints, err := toWireOutpoints(in.Inputs) - if err != nil { - return nil, fmt.Errorf("can't create outpoints %w", err) - } - - if fn.HasDuplicates(wireOutpoints) { - return nil, fmt.Errorf("selected outpoints contain " + - "duplicate values") - } - - selectOutpoints = fn.NewSet(wireOutpoints...) - } - // We will ask the wallet to create a tx using this fee rate. We set // dryRun=true to avoid inflating the change addresses in the db. var tx *txauthor.AuthoredTx wallet := r.server.cc.Wallet err = wallet.WithCoinSelectLock(func() error { tx, err = wallet.CreateSimpleTx( - selectOutpoints, outputs, feePerKw, minConfs, - coinSelectionStrategy, true, + nil, outputs, feePerKw, minConfs, coinSelectionStrategy, + true, ) return err }) @@ -1302,26 +1295,12 @@ func (r *rpcServer) EstimateFee(ctx context.Context, } totalFee := int64(tx.TotalInput) - totalOutput - // Return the inputs the estimate is for. - outStr := make([]string, 0, len(tx.Tx.TxIn)) - for _, txIn := range tx.Tx.TxIn { - outStr = append( - outStr, txIn.PreviousOutPoint.String(), - ) - } - - inputs, err := UtxosToOutpoints(outStr) - if err != nil { - return nil, fmt.Errorf("can't convert outpoints %w", err) - } - resp := &lnrpc.EstimateFeeResponse{ FeeSat: totalFee, SatPerVbyte: uint64(feePerKw.FeePerVByte()), // Deprecated field. FeerateSatPerByte: int64(feePerKw.FeePerVByte()), - Inputs: inputs, } rpcsLog.Debugf("[estimatefee] fee estimate for conf target %d: %v", @@ -1389,7 +1368,7 @@ func (r *rpcServer) SendCoins(ctx context.Context, // Decode the address receiving the coins, we need to check whether the // address is valid for this network. - targetAddr, err := address.DecodeAddress( + targetAddr, err := btcutil.DecodeAddress( in.Addr, r.cfg.ActiveNetParams.Params, ) if err != nil { @@ -1683,7 +1662,7 @@ func (r *rpcServer) NewAddress(ctx context.Context, // Translate the gRPC proto address type to the wallet controller's // available address types. var ( - addr address.Address + addr btcutil.Address err error ) switch in.Type { @@ -1810,8 +1789,8 @@ func (r *rpcServer) VerifyMessage(ctx context.Context, // channels signed the message. // // TODO(phlip9): Require valid nodes to have capital in active channels. - graph := r.server.v1Graph - active, err := graph.HasNode(ctx, pub) + graph := r.server.graphDB + _, active, err := graph.HasNode(ctx, pub) if err != nil { return nil, fmt.Errorf("failed to query graph: %w", err) } @@ -2141,26 +2120,15 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest, "the channel opening") } - // Fetch our own feature set and determine wumbo support early, as it's - // needed for both FundMax and explicit amount validation. - globalFeatureSet := r.server.featureMgr.Get(feature.SetNodeAnn) - wumboEnabled := globalFeatureSet.HasFeature( - lnwire.WumboChannelsOptional, - ) - // If the FundMax flag is set, ensure that the acceptable minimum local // amount adheres to the amount to be pushed to the remote, and to - // current rules, while also respecting the protocol-level maximum + // current rules, while also respecting the settings for the maximum // channel size. var minFundAmt, fundUpToMaxAmt btcutil.Amount if in.FundMax { - // Use the protocol-level maximum as the upper bound for our - // funding attempt. - if wumboEnabled { - fundUpToMaxAmt = funding.MaxBtcFundingAmountWumbo - } else { - fundUpToMaxAmt = MaxFundingAmount - } + // We assume the configured maximum channel size to be the upper + // bound of our "maxed" out funding attempt. + fundUpToMaxAmt = btcutil.Amount(r.cfg.MaxChanSize) // Since the standard non-fundmax flow requires the minimum // funding amount to be at least in the amount of the initial @@ -2186,6 +2154,8 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest, maxHtlcs := uint16(in.RemoteMaxHtlcs) remoteChanReserve := btcutil.Amount(in.RemoteChanReserveSat) + globalFeatureSet := r.server.featureMgr.Get(feature.SetNodeAnn) + // Determine if the user provided channel fees // and if so pass them on to the funding workflow. var channelBaseFee, channelFeeRate *uint64 @@ -2210,6 +2180,9 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest, // in the wallet hence we do not check it here against the maximum // funding amount. Only if the localFundingAmt is specified we can check // if it exceeds the maximum funding amount. + wumboEnabled := globalFeatureSet.HasFeature( + lnwire.WumboChannelsOptional, + ) if !in.FundMax && !wumboEnabled && localFundingAmt > MaxFundingAmount { return nil, fmt.Errorf("funding amount is too large, the max "+ "channel size is: %v", MaxFundingAmount) @@ -2385,29 +2358,6 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest, *channelType = lnwire.ChannelType(*fv) - case lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL: - // If the final taproot channel type is being set, then the - // channel MUST be private (unadvertised) for now. - if !in.Private { - return nil, fmt.Errorf("taproot channels must be " + - "private") - } - - channelType = new(lnwire.ChannelType) - fv := lnwire.NewRawFeatureVector( - lnwire.SimpleTaprootChannelsRequiredFinal, - ) - - if in.ZeroConf { - fv.Set(lnwire.ZeroConfRequired) - } - - if in.ScidAlias { - fv.Set(lnwire.ScidAliasRequired) - } - - *channelType = lnwire.ChannelType(*fv) - case lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY: // If the taproot overlay channel type is being set, then the // channel MUST be private. @@ -2857,16 +2807,9 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest, errChan = make(chan error, 1) notifier := r.server.cc.ChainNotifier - - // For force closes, we notify the RPC client immediately after - // 1 confirmation. The actual security-critical confirmation - // waiting is handled by the channel arbitrator. - numConfs := uint32(1) - go peer.WaitForChanToClose( uint32(bestHeight), notifier, errChan, chanPoint, - &closingTxid, closingTx.TxOut[0].PkScript, numConfs, - func() { + &closingTxid, closingTx.TxOut[0].PkScript, func() { // Respond to the local subsystem which // requested the channel closure. updateChan <- &peer.ChannelCloseUpdate{ @@ -2965,7 +2908,7 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest, // If a delivery address to close out to was specified, decode it. if len(in.DeliveryAddress) > 0 { // Decode the address provided. - addr, err := address.DecodeAddress( + addr, err := btcutil.DecodeAddress( in.DeliveryAddress, r.cfg.ActiveNetParams.Params, ) if err != nil { @@ -3002,27 +2945,13 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest, rpcsLog.Infof("Bypassing Switch to do fee bump "+ "for ChannelPoint(%v)", chanPoint) - // To perform this RBF bump, we'll send a bump message - // to the RBF close actor. We propagate the stream - // context so that cancellation of the RPC client also - // tears down the observer goroutine. - ctx := updateStream.Context() - rbfBumpMsg := peer.NewRbfBumpCloseMsg( - ctx, *chanPoint, feeRate, deliveryScript, + closeUpdates, err := r.server.AttemptRBFCloseUpdate( + updateStream.Context(), *chanPoint, feeRate, + deliveryScript, ) - rbfActorKey := peer.NewRbfCloserPeerServiceKey( - *chanPoint, - ) - rbfRouter := peer.RbfChanCloserRouter( - r.server.actorSystem, rbfActorKey, - ) - - closeUpdates, err := rbfRouter.Ask( - ctx, rbfBumpMsg, - ).Await(ctx).Unpack() if err != nil { - return fmt.Errorf("unable to ask for RBF "+ - "close: %w", err) + return fmt.Errorf("unable to do RBF close "+ + "update: %w", err) } updateChan = closeUpdates.UpdateChan @@ -3128,7 +3057,7 @@ func createRPCCloseUpdate( err := fn.MapOptionZ( u.LocalCloseOutput, - func(closeOut types.CloseOutput) error { + func(closeOut chancloser.CloseOutput) error { cr, err := closeOut.ShutdownRecords.Serialize() if err != nil { return fmt.Errorf("error serializing "+ @@ -3153,7 +3082,7 @@ func createRPCCloseUpdate( err = fn.MapOptionZ( u.RemoteCloseOutput, - func(closeOut types.CloseOutput) error { + func(closeOut chancloser.CloseOutput) error { cr, err := closeOut.ShutdownRecords.Serialize() if err != nil { return fmt.Errorf("error serializing "+ @@ -3222,13 +3151,13 @@ func createRPCCloseUpdate( // abandonChanFromGraph attempts to remove a channel from the channel graph. If // we can't find the chanID in the graph, then we assume it has already been // removed, and will return a nop. -func abandonChanFromGraph(chanGraph *graphdb.VersionedGraph, +func abandonChanFromGraph(chanGraph *graphdb.ChannelGraph, chanPoint *wire.OutPoint) error { // First, we'll obtain the channel ID. If we can't locate this, then // it's the case that the channel may have already been removed from // the graph, so we'll return a nil error. - chanID, err := chanGraph.ChannelID(context.TODO(), chanPoint) + chanID, err := chanGraph.ChannelID(chanPoint) switch { case errors.Is(err, graphdb.ErrEdgeNotFound): return nil @@ -3238,7 +3167,7 @@ func abandonChanFromGraph(chanGraph *graphdb.VersionedGraph, // If the channel ID is still in the graph, then that means the channel // is still open, so we'll now move to purge it from the graph. - return chanGraph.DeleteChannelEdges(context.TODO(), false, true, chanID) + return chanGraph.DeleteChannelEdges(false, true, chanID) } // abandonChan removes a channel from the database, graph and contract court. @@ -3263,8 +3192,7 @@ func (r *rpcServer) abandonChan(chanPoint *wire.OutPoint, if err != nil { return err } - // TODO: update to support deletions for v2 channels. - err = abandonChanFromGraph(r.server.v1Graph, chanPoint) + err = abandonChanFromGraph(r.server.graphDB, chanPoint) if err != nil { return err } @@ -3455,8 +3383,6 @@ func (r *rpcServer) GetInfo(_ context.Context, isTestNet := chainreg.IsTestnet(&r.cfg.ActiveNetParams) nodeColor := graphdb.EncodeHexColor(nodeAnn.RGBColor) version := build.Version() + " commit=" + build.Commit - cacheStatus := r.server.graphDB.GraphCacheStatus() - graphCacheStatus := rpcGraphCacheStatus(cacheStatus) return &lnrpc.GetInfoResponse{ IdentityPubkey: encodedIDPub, @@ -3479,37 +3405,14 @@ func (r *rpcServer) GetInfo(_ context.Context, Features: features, RequireHtlcInterceptor: r.cfg.RequireInterceptor, StoreFinalHtlcResolutions: r.cfg.StoreFinalHtlcResolutions, - WalletSynced: syncInfo.isWalletSynced, - GraphCacheStatus: graphCacheStatus, }, nil } -// rpcGraphCacheStatus maps the graph DB cache status to the lnrpc enum used by -// GetInfo. -func rpcGraphCacheStatus( - status graphdb.GraphCacheStatus) lnrpc.GraphCacheStatus { - - switch status { - case graphdb.GraphCacheStatusDisabled: - return lnrpc.GraphCacheStatus_GRAPH_CACHE_STATUS_DISABLED - - case graphdb.GraphCacheStatusLoaded: - return lnrpc.GraphCacheStatus_GRAPH_CACHE_STATUS_LOADED - - case graphdb.GraphCacheStatusFailed: - return lnrpc.GraphCacheStatus_GRAPH_CACHE_STATUS_FAILED - - default: - return lnrpc.GraphCacheStatus_GRAPH_CACHE_STATUS_LOADING - } -} - // GetDebugInfo returns debug information concerning the state of the daemon -// and its subsystems. By default, this returns only the configuration. If the -// `include_log` flag is set in the request, the latest log entries from the -// log file are also included. +// and its subsystems. This includes the full configuration and the latest log +// entries from the log file. func (r *rpcServer) GetDebugInfo(_ context.Context, - req *lnrpc.GetDebugInfoRequest) (*lnrpc.GetDebugInfoResponse, error) { + _ *lnrpc.GetDebugInfoRequest) (*lnrpc.GetDebugInfoResponse, error) { flatConfig, _, err := configToFlatMap(*r.cfg) if err != nil { @@ -3517,16 +3420,6 @@ func (r *rpcServer) GetDebugInfo(_ context.Context, "%w", err) } - resp := &lnrpc.GetDebugInfoResponse{ - Config: flatConfig, - } - - // If the include_log flag is not set, we only return the config and - // skip the log file content which can be large. - if !req.IncludeLog { - return resp, nil - } - logFileName := filepath.Join(r.cfg.LogDir, defaultLogFilename) logContent, err := os.ReadFile(logFileName) if err != nil { @@ -3534,9 +3427,10 @@ func (r *rpcServer) GetDebugInfo(_ context.Context, logFileName, err) } - resp.Log = strings.Split(string(logContent), "\n") - - return resp, nil + return &lnrpc.GetDebugInfoResponse{ + Config: flatConfig, + Log: strings.Split(string(logContent), "\n"), + }, nil } // GetRecoveryInfo returns a boolean indicating whether the wallet is started @@ -4011,7 +3905,7 @@ type ( // 1. The current blockchain height // 2. The block height at which the funding transaction was first confirmed // 3. The total number of confirmations required for the channel. -func calcRemainingConfs(pendingChan *chanstate.OpenChannel, +func calcRemainingConfs(pendingChan *channeldb.OpenChannel, currentHeight uint32) uint32 { // If the funding transaction hasn't been confirmed yet, @@ -4304,19 +4198,13 @@ func (r *rpcServer) fetchWaitingCloseChannels( return nil, 0, err } - // Get the current block height for calculating remaining confirmations. - _, currentHeight, err := r.server.cc.ChainIO.GetBestBlock() - if err != nil { - return nil, 0, err - } - result := make(waitingCloseChannels, 0) limboBalance := int64(0) // getClosingTx is a helper closure that tries to find the closing tx of // a given waiting close channel. Notice that if the remote closes the // channel, we may not have the closing tx. - getClosingTx := func(c *chanstate.OpenChannel) (*wire.MsgTx, error) { + getClosingTx := func(c *channeldb.OpenChannel) (*wire.MsgTx, error) { var ( tx *wire.MsgTx err error @@ -4471,46 +4359,12 @@ func (r *rpcServer) fetchWaitingCloseChannels( } } - // Calculate remaining confirmations until the channel closure - // is considered resolved/confirmed. - requiredConfs := lnwallet.CloseConfsForCapacity( - waitingClose.Capacity, - ) - if r.cfg.Dev != nil { - requiredConfs = r.cfg.Dev.ChannelCloseConfs(). - UnwrapOr(requiredConfs) - } - - blocksTilCloseConfirmed := fn.ElimOption( - waitingClose.CloseConfirmationHeight, - func() uint32 { - // The closing tx is not yet confirmed, show - // all required confirmations. - return requiredConfs - }, - func(closeConfHeight uint32) uint32 { - // The closing tx has at least one - // confirmation. Calculate how many more are - // needed. - targetHeight := closeConfHeight + - requiredConfs - 1 - if uint32(currentHeight) >= targetHeight { - return 0 - } - - return targetHeight - uint32(currentHeight) - }, - ) - waitingCloseResp := &lnrpc.PendingChannelsResponse_WaitingCloseChannel{ - Channel: channel, - LimboBalance: channel.LocalBalance, - Commitments: &commitments, - ClosingTxid: closingTxid, - ClosingTxHex: closingTxHex, - BlocksTilCloseConfirmed: blocksTilCloseConfirmed, - CloseHeight: waitingClose.CloseConfirmationHeight. - UnwrapOr(0), + Channel: channel, + LimboBalance: channel.LocalBalance, + Commitments: &commitments, + ClosingTxid: closingTxid, + ClosingTxHex: closingTxHex, } // A close tx has been broadcasted, all our balance will be in @@ -4919,9 +4773,6 @@ func rpcCommitmentType(chanType channeldb.ChannelType) lnrpc.CommitmentType { case chanType.HasTapscriptRoot(): return lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY - case chanType.IsTaprootFinal(): - return lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL - case chanType.IsTaproot(): return lnrpc.CommitmentType_SIMPLE_TAPROOT @@ -4956,7 +4807,7 @@ func createChannelConstraint( // isPrivate evaluates the ChannelFlags of the db channel to determine if the // channel is private or not. -func isPrivate(dbChannel *chanstate.OpenChannel) bool { +func isPrivate(dbChannel *channeldb.OpenChannel) bool { if dbChannel == nil { return false } @@ -4965,7 +4816,7 @@ func isPrivate(dbChannel *chanstate.OpenChannel) bool { // encodeCustomChanData encodes the custom channel data for the open channel. // It encodes that data as a pair of var bytes blobs. -func encodeCustomChanData(lnChan *chanstate.OpenChannel) ([]byte, error) { +func encodeCustomChanData(lnChan *channeldb.OpenChannel) ([]byte, error) { customOpenChanData := lnChan.CustomBlob.UnwrapOr(nil) customLocalCommitData := lnChan.LocalCommitment.CustomBlob.UnwrapOr(nil) @@ -4996,7 +4847,7 @@ func encodeCustomChanData(lnChan *chanstate.OpenChannel) ([]byte, error) { // //nolint:funlen func createRPCOpenChannel(ctx context.Context, r *rpcServer, - dbChannel *chanstate.OpenChannel, + dbChannel *channeldb.OpenChannel, isActive, peerAliasLookup bool) (*lnrpc.Channel, error) { nodePub := dbChannel.IdentityPub @@ -5098,7 +4949,7 @@ func createRPCOpenChannel(ctx context.Context, r *rpcServer, // Look up our channel peer's node alias if the caller requests it. if peerAliasLookup { - peerAlias, err := r.server.v1Graph.LookupAlias(ctx, nodePub) + peerAlias, err := r.server.graphDB.LookupAlias(ctx, nodePub) if err != nil { peerAlias = fmt.Sprintf("unable to lookup "+ "peer alias: %v", err) @@ -5448,12 +5299,10 @@ func rpcChannelResolution(report *channeldb.ResolverReport) (*lnrpc.Resolution, } // getInitiators returns an initiator enum that provides information about the -// party that initiated channel's open and close. The information is normally -// read from the historical channel bucket; for early-dispatched coop closes -// the channel is still live in the open bucket at notify time (the historical -// bucket is only populated at MarkChannelClosed time), so we fall back to the -// open channel state in that case. Unknown values are returned when neither -// bucket can provide the channel. +// party that initiated channel's open and close. This information is obtained +// from the historical channel bucket, so unknown values are returned when the +// channel is not present (which indicates that it was closed before we started +// writing channels to the historical close bucket). func (r *rpcServer) getInitiators(chanPoint *wire.OutPoint) ( lnrpc.Initiator, lnrpc.Initiator, error) { @@ -5473,20 +5322,10 @@ func (r *rpcServer) getInitiators(chanPoint *wire.OutPoint) ( case err == channeldb.ErrNoHistoricalBucket: return openInitiator, closeInitiator, nil - // The channel was either closed before we started storing - // historical channels OR the historical bucket has not been - // populated yet because this is an early-dispatched - // CLOSED_CHANNEL event for a coop close that hasn't reached its - // full confirmation depth. Try the open channel bucket so the - // early dispatch still carries close-initiator info. + // The channel was closed before we started storing historical + // channels. Do not return an error, initiator values are unknown. case err == channeldb.ErrChannelNotFound: - openChan, openErr := r.server.chanStateDB.FetchChannel( - *chanPoint, - ) - if openErr != nil { - return openInitiator, closeInitiator, nil - } - histChan = openChan + return openInitiator, closeInitiator, nil case err != nil: return 0, 0, err @@ -5602,24 +5441,6 @@ func (r *rpcServer) SubscribeChannelEvents(req *lnrpc.ChannelEventSubscription, }, } - case channelnotifier.ChannelUpdateEvent: - channel, err := createRPCOpenChannel( - updateStream.Context(), - r, event.Channel, true, false, - ) - if err != nil { - return err - } - - update = &lnrpc.ChannelEventUpdate{ - Type: lnrpc.ChannelEventUpdate_CHANNEL_UPDATE, - Channel: &lnrpc.ChannelEventUpdate_UpdatedChannel{ - UpdatedChannel: &lnrpc.ChannelCommitUpdate{ - Channel: channel, - }, - }, - } - case channelnotifier.InactiveChannelEvent: update = &lnrpc.ChannelEventUpdate{ Type: lnrpc.ChannelEventUpdate_INACTIVE_CHANNEL, @@ -5690,6 +5511,766 @@ func (r *rpcServer) SubscribeChannelEvents(req *lnrpc.ChannelEventSubscription, } } +// paymentStream enables different types of payment streams, such as: +// lnrpc.Lightning_SendPaymentServer and lnrpc.Lightning_SendToRouteServer to +// execute sendPayment. We use this struct as a sort of bridge to enable code +// re-use between SendPayment and SendToRoute. +type paymentStream struct { + recv func() (*rpcPaymentRequest, error) + send func(*lnrpc.SendResponse) error +} + +// rpcPaymentRequest wraps lnrpc.SendRequest so that routes from +// lnrpc.SendToRouteRequest can be passed to sendPayment. +type rpcPaymentRequest struct { + *lnrpc.SendRequest + route *route.Route +} + +// SendPayment dispatches a bi-directional streaming RPC for sending payments +// through the Lightning Network. A single RPC invocation creates a persistent +// bi-directional stream allowing clients to rapidly send payments through the +// Lightning Network with a single persistent connection. +func (r *rpcServer) SendPayment(stream lnrpc.Lightning_SendPaymentServer) error { + var lock sync.Mutex + + return r.sendPayment(&paymentStream{ + recv: func() (*rpcPaymentRequest, error) { + req, err := stream.Recv() + if err != nil { + return nil, err + } + + return &rpcPaymentRequest{ + SendRequest: req, + }, nil + }, + send: func(r *lnrpc.SendResponse) error { + // Calling stream.Send concurrently is not safe. + lock.Lock() + defer lock.Unlock() + return stream.Send(r) + }, + }) +} + +// SendToRoute dispatches a bi-directional streaming RPC for sending payments +// through the Lightning Network via predefined routes passed in. A single RPC +// invocation creates a persistent bi-directional stream allowing clients to +// rapidly send payments through the Lightning Network with a single persistent +// connection. +func (r *rpcServer) SendToRoute(stream lnrpc.Lightning_SendToRouteServer) error { + var lock sync.Mutex + + return r.sendPayment(&paymentStream{ + recv: func() (*rpcPaymentRequest, error) { + req, err := stream.Recv() + if err != nil { + return nil, err + } + + return r.unmarshallSendToRouteRequest(req) + }, + send: func(r *lnrpc.SendResponse) error { + // Calling stream.Send concurrently is not safe. + lock.Lock() + defer lock.Unlock() + return stream.Send(r) + }, + }) +} + +// unmarshallSendToRouteRequest unmarshalls an rpc sendtoroute request +func (r *rpcServer) unmarshallSendToRouteRequest( + req *lnrpc.SendToRouteRequest) (*rpcPaymentRequest, error) { + + if req.Route == nil { + return nil, fmt.Errorf("unable to send, no route provided") + } + + route, err := r.routerBackend.UnmarshallRoute(req.Route) + if err != nil { + return nil, err + } + + return &rpcPaymentRequest{ + SendRequest: &lnrpc.SendRequest{ + PaymentHash: req.PaymentHash, + PaymentHashString: req.PaymentHashString, + }, + route: route, + }, nil +} + +// rpcPaymentIntent is a small wrapper struct around the of values we can +// receive from a client over RPC if they wish to send a payment. We'll either +// extract these fields from a payment request (which may include routing +// hints), or we'll get a fully populated route from the user that we'll pass +// directly to the channel router for dispatching. +type rpcPaymentIntent struct { + msat lnwire.MilliSatoshi + feeLimit lnwire.MilliSatoshi + cltvLimit uint32 + dest route.Vertex + rHash [32]byte + cltvDelta uint16 + routeHints [][]zpay32.HopHint + outgoingChannelIDs []uint64 + lastHop *route.Vertex + destFeatures *lnwire.FeatureVector + paymentAddr fn.Option[[32]byte] + payReq []byte + metadata []byte + blindedPathSet *routing.BlindedPaymentPathSet + + destCustomRecords record.CustomSet + + route *route.Route +} + +// extractPaymentIntent attempts to parse the complete details required to +// dispatch a client from the information presented by an RPC client. There are +// three ways a client can specify their payment details: a payment request, +// via manual details, or via a complete route. +func (r *rpcServer) extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPaymentIntent, error) { + payIntent := rpcPaymentIntent{} + + // If a route was specified, then we can use that directly. + if rpcPayReq.route != nil { + // If the user is using the REST interface, then they'll be + // passing the payment hash as a hex encoded string. + if rpcPayReq.PaymentHashString != "" { + paymentHash, err := hex.DecodeString( + rpcPayReq.PaymentHashString, + ) + if err != nil { + return payIntent, err + } + + copy(payIntent.rHash[:], paymentHash) + } else { + copy(payIntent.rHash[:], rpcPayReq.PaymentHash) + } + + payIntent.route = rpcPayReq.route + return payIntent, nil + } + + // If there are no routes specified, pass along a outgoing channel + // restriction if specified. The main server rpc does not support + // multiple channel restrictions. + if rpcPayReq.OutgoingChanId != 0 { + payIntent.outgoingChannelIDs = []uint64{ + rpcPayReq.OutgoingChanId, + } + } + + // Pass along a last hop restriction if specified. + if len(rpcPayReq.LastHopPubkey) > 0 { + lastHop, err := route.NewVertexFromBytes( + rpcPayReq.LastHopPubkey, + ) + if err != nil { + return payIntent, err + } + payIntent.lastHop = &lastHop + } + + // Take the CLTV limit from the request if set, otherwise use the max. + cltvLimit, err := routerrpc.ValidateCLTVLimit( + rpcPayReq.CltvLimit, r.cfg.MaxOutgoingCltvExpiry, + ) + if err != nil { + return payIntent, err + } + payIntent.cltvLimit = cltvLimit + + customRecords := record.CustomSet(rpcPayReq.DestCustomRecords) + if err := customRecords.Validate(); err != nil { + return payIntent, err + } + payIntent.destCustomRecords = customRecords + + validateDest := func(dest route.Vertex) error { + if rpcPayReq.AllowSelfPayment { + return nil + } + + if dest == r.selfNode { + return errors.New("self-payments not allowed") + } + + return nil + } + + // If the payment request field isn't blank, then the details of the + // invoice are encoded entirely within the encoded payReq. So we'll + // attempt to decode it, populating the payment accordingly. + if rpcPayReq.PaymentRequest != "" { + payReq, err := zpay32.Decode( + rpcPayReq.PaymentRequest, r.cfg.ActiveNetParams.Params, + zpay32.WithErrorOnUnknownFeatureBit(), + ) + if err != nil { + return payIntent, err + } + + // Next, we'll ensure that this payreq hasn't already expired. + err = routerrpc.ValidatePayReqExpiry( + r.routerBackend.Clock, payReq, + ) + if err != nil { + return payIntent, err + } + + // If the amount was not included in the invoice, then we let + // the payer specify the amount of satoshis they wish to send. + // We override the amount to pay with the amount provided from + // the payment request. + if payReq.MilliSat == nil { + amt, err := lnrpc.UnmarshallAmt( + rpcPayReq.Amt, rpcPayReq.AmtMsat, + ) + if err != nil { + return payIntent, err + } + if amt == 0 { + return payIntent, errors.New("amount must be " + + "specified when paying a zero amount " + + "invoice") + } + + payIntent.msat = amt + } else { + payIntent.msat = *payReq.MilliSat + } + + // Calculate the fee limit that should be used for this payment. + payIntent.feeLimit = lnrpc.CalculateFeeLimit( + rpcPayReq.FeeLimit, payIntent.msat, + ) + + copy(payIntent.rHash[:], payReq.PaymentHash[:]) + destKey := payReq.Destination.SerializeCompressed() + copy(payIntent.dest[:], destKey) + payIntent.cltvDelta = uint16(payReq.MinFinalCLTVExpiry()) + payIntent.routeHints = payReq.RouteHints + payIntent.payReq = []byte(rpcPayReq.PaymentRequest) + payIntent.destFeatures = payReq.Features + payIntent.paymentAddr = payReq.PaymentAddr + payIntent.metadata = payReq.Metadata + + if len(payReq.BlindedPaymentPaths) > 0 { + pathSet, err := routerrpc.BuildBlindedPathSet( + payReq.BlindedPaymentPaths, + ) + if err != nil { + return payIntent, err + } + payIntent.blindedPathSet = pathSet + + // Replace the destination node with the target public + // key of the blinded path set. + copy( + payIntent.dest[:], + pathSet.TargetPubKey().SerializeCompressed(), + ) + + pathFeatures := pathSet.Features() + if !pathFeatures.IsEmpty() { + payIntent.destFeatures = pathFeatures.Clone() + } + } + + if err := validateDest(payIntent.dest); err != nil { + return payIntent, err + } + + // Do bounds checking with the block padding. + err = routing.ValidateCLTVLimit( + payIntent.cltvLimit, payIntent.cltvDelta, true, + ) + if err != nil { + return payIntent, err + } + + return payIntent, nil + } + + // At this point, a destination MUST be specified, so we'll convert it + // into the proper representation now. The destination will either be + // encoded as raw bytes, or via a hex string. + var pubBytes []byte + if len(rpcPayReq.Dest) != 0 { + pubBytes = rpcPayReq.Dest + } else { + var err error + pubBytes, err = hex.DecodeString(rpcPayReq.DestString) + if err != nil { + return payIntent, err + } + } + if len(pubBytes) != 33 { + return payIntent, errors.New("invalid key length") + } + copy(payIntent.dest[:], pubBytes) + + if err := validateDest(payIntent.dest); err != nil { + return payIntent, err + } + + // Payment address may not be needed by legacy invoices. + if len(rpcPayReq.PaymentAddr) != 0 && len(rpcPayReq.PaymentAddr) != 32 { + return payIntent, errors.New("invalid payment address length") + } + + // Set the payment address if it was explicitly defined with the + // rpcPaymentRequest. + // Note that the payment address for the payIntent should be nil if none + // was provided with the rpcPaymentRequest. + if len(rpcPayReq.PaymentAddr) != 0 { + var addr [32]byte + copy(addr[:], rpcPayReq.PaymentAddr) + payIntent.paymentAddr = fn.Some(addr) + } + + // Otherwise, If the payment request field was not specified + // (and a custom route wasn't specified), construct the payment + // from the other fields. + payIntent.msat, err = lnrpc.UnmarshallAmt( + rpcPayReq.Amt, rpcPayReq.AmtMsat, + ) + if err != nil { + return payIntent, err + } + + // Calculate the fee limit that should be used for this payment. + payIntent.feeLimit = lnrpc.CalculateFeeLimit( + rpcPayReq.FeeLimit, payIntent.msat, + ) + + if rpcPayReq.FinalCltvDelta != 0 { + payIntent.cltvDelta = uint16(rpcPayReq.FinalCltvDelta) + } else { + // If no final cltv delta is given, assume the default that we + // use when creating an invoice. We do not assume the default of + // 9 blocks that is defined in BOLT-11, because this is never + // enough for other lnd nodes. + payIntent.cltvDelta = uint16(r.cfg.Bitcoin.TimeLockDelta) + } + + // Do bounds checking with the block padding so the router isn't left + // with a zombie payment in case the user messes up. + err = routing.ValidateCLTVLimit( + payIntent.cltvLimit, payIntent.cltvDelta, true, + ) + if err != nil { + return payIntent, err + } + + // If the user is manually specifying payment details, then the payment + // hash may be encoded as a string. + switch { + case rpcPayReq.PaymentHashString != "": + paymentHash, err := hex.DecodeString( + rpcPayReq.PaymentHashString, + ) + if err != nil { + return payIntent, err + } + + copy(payIntent.rHash[:], paymentHash) + + default: + copy(payIntent.rHash[:], rpcPayReq.PaymentHash) + } + + // Unmarshal any custom destination features. + payIntent.destFeatures = routerrpc.UnmarshalFeatures( + rpcPayReq.DestFeatures, + ) + + return payIntent, nil +} + +type paymentIntentResponse struct { + Route *route.Route + Preimage [32]byte + Err error +} + +// dispatchPaymentIntent attempts to fully dispatch an RPC payment intent. +// We'll either pass the payment as a whole to the channel router, or give it a +// pre-built route. The first error this method returns denotes if we were +// unable to save the payment. The second error returned denotes if the payment +// didn't succeed. +func (r *rpcServer) dispatchPaymentIntent( + payIntent *rpcPaymentIntent) (*paymentIntentResponse, error) { + + // Construct a payment request to send to the channel router. If the + // payment is successful, the route chosen will be returned. Otherwise, + // we'll get a non-nil error. + var ( + preImage [32]byte + route *route.Route + routerErr error + ) + + // If a route was specified, then we'll pass the route directly to the + // router, otherwise we'll create a payment session to execute it. + if payIntent.route == nil { + payment := &routing.LightningPayment{ + Target: payIntent.dest, + Amount: payIntent.msat, + FinalCLTVDelta: payIntent.cltvDelta, + FeeLimit: payIntent.feeLimit, + CltvLimit: payIntent.cltvLimit, + RouteHints: payIntent.routeHints, + OutgoingChannelIDs: payIntent.outgoingChannelIDs, + LastHop: payIntent.lastHop, + PaymentRequest: payIntent.payReq, + PayAttemptTimeout: routing.DefaultPayAttemptTimeout, + DestCustomRecords: payIntent.destCustomRecords, + DestFeatures: payIntent.destFeatures, + PaymentAddr: payIntent.paymentAddr, + Metadata: payIntent.metadata, + BlindedPathSet: payIntent.blindedPathSet, + + // Don't enable multi-part payments on the main rpc. + // Users need to use routerrpc for that. + MaxParts: 1, + } + err := payment.SetPaymentHash(payIntent.rHash) + if err != nil { + return nil, err + } + + preImage, route, routerErr = r.server.chanRouter.SendPayment( + payment, + ) + } else { + var attempt *paymentsdb.HTLCAttempt + attempt, routerErr = r.server.chanRouter.SendToRoute( + payIntent.rHash, payIntent.route, nil, + ) + + if routerErr == nil { + preImage = attempt.Settle.Preimage + } + + route = payIntent.route + } + + // If the route failed, then we'll return a nil save err, but a non-nil + // routing err. + if routerErr != nil { + rpcsLog.Warnf("Unable to send payment: %v", routerErr) + + return &paymentIntentResponse{ + Err: routerErr, + }, nil + } + + return &paymentIntentResponse{ + Route: route, + Preimage: preImage, + }, nil +} + +// sendPayment takes a paymentStream (a source of pre-built routes or payment +// requests) and continually attempt to dispatch payment requests written to +// the write end of the stream. Responses will also be streamed back to the +// client via the write end of the stream. This method is by both SendToRoute +// and SendPayment as the logic is virtually identical. +func (r *rpcServer) sendPayment(stream *paymentStream) error { + payChan := make(chan *rpcPaymentIntent) + errChan := make(chan error, 1) + + // We don't allow payments to be sent while the daemon itself is still + // syncing as we may be trying to sent a payment over a "stale" + // channel. + if !r.server.Started() { + return ErrServerNotActive + } + + // TODO(roasbeef): check payment filter to see if already used? + + // In order to limit the level of concurrency and prevent a client from + // attempting to OOM the server, we'll set up a semaphore to create an + // upper ceiling on the number of outstanding payments. + const numOutstandingPayments = 2000 + htlcSema := make(chan struct{}, numOutstandingPayments) + for i := 0; i < numOutstandingPayments; i++ { + htlcSema <- struct{}{} + } + + // We keep track of the running goroutines and set up a quit signal we + // can use to request them to exit if the method returns because of an + // encountered error. + var wg sync.WaitGroup + reqQuit := make(chan struct{}) + defer close(reqQuit) + + // Launch a new goroutine to handle reading new payment requests from + // the client. This way we can handle errors independently of blocking + // and waiting for the next payment request to come through. + // TODO(joostjager): Callers expect result to come in in the same order + // as the request were sent, but this is far from guarantueed in the + // code below. + wg.Add(1) + go func() { + defer wg.Done() + + for { + select { + case <-reqQuit: + return + + default: + // Receive the next pending payment within the + // stream sent by the client. If we read the + // EOF sentinel, then the client has closed the + // stream, and we can exit normally. + nextPayment, err := stream.recv() + if err == io.EOF { + close(payChan) + return + } else if err != nil { + rpcsLog.Errorf("Failed receiving from "+ + "stream: %v", err) + + select { + case errChan <- err: + default: + } + return + } + + // Populate the next payment, either from the + // payment request, or from the explicitly set + // fields. If the payment proto wasn't well + // formed, then we'll send an error reply and + // wait for the next payment. + payIntent, err := r.extractPaymentIntent( + nextPayment, + ) + if err != nil { + if err := stream.send(&lnrpc.SendResponse{ + PaymentError: err.Error(), + PaymentHash: payIntent.rHash[:], + }); err != nil { + rpcsLog.Errorf("Failed "+ + "sending on "+ + "stream: %v", err) + + select { + case errChan <- err: + default: + } + return + } + continue + } + + // If the payment was well formed, then we'll + // send to the dispatch goroutine, or exit, + // which ever comes first. + select { + case payChan <- &payIntent: + case <-reqQuit: + return + } + } + } + }() + +sendLoop: + for { + select { + + // If we encounter and error either during sending or + // receiving, we return directly, closing the stream. + case err := <-errChan: + return err + + case <-r.quit: + return errors.New("rpc server shutting down") + + case payIntent, ok := <-payChan: + // If the receive loop is done, we break the send loop + // and wait for the ongoing payments to finish before + // exiting. + if !ok { + break sendLoop + } + + // We launch a new goroutine to execute the current + // payment so we can continue to serve requests while + // this payment is being dispatched. + wg.Add(1) + go func(payIntent *rpcPaymentIntent) { + defer wg.Done() + + // Attempt to grab a free semaphore slot, using + // a defer to eventually release the slot + // regardless of payment success. + select { + case <-htlcSema: + case <-reqQuit: + return + } + defer func() { + htlcSema <- struct{}{} + }() + + resp, saveErr := r.dispatchPaymentIntent( + payIntent, + ) + + switch { + // If we were unable to save the state of the + // payment, then we'll return the error to the + // user, and terminate. + case saveErr != nil: + rpcsLog.Errorf("Failed dispatching "+ + "payment intent: %v", saveErr) + + select { + case errChan <- saveErr: + default: + } + return + + // If we receive payment error than, instead of + // terminating the stream, send error response + // to the user. + case resp.Err != nil: + err := stream.send(&lnrpc.SendResponse{ + PaymentError: resp.Err.Error(), + PaymentHash: payIntent.rHash[:], + }) + if err != nil { + rpcsLog.Errorf("Failed "+ + "sending error "+ + "response: %v", err) + + select { + case errChan <- err: + default: + } + } + return + } + + backend := r.routerBackend + marshalledRouted, err := backend.MarshallRoute( + resp.Route, + ) + if err != nil { + errChan <- err + return + } + + err = stream.send(&lnrpc.SendResponse{ + PaymentHash: payIntent.rHash[:], + PaymentPreimage: resp.Preimage[:], + PaymentRoute: marshalledRouted, + }) + if err != nil { + rpcsLog.Errorf("Failed sending "+ + "response: %v", err) + + select { + case errChan <- err: + default: + } + return + } + }(payIntent) + } + } + + // Wait for all goroutines to finish before closing the stream. + wg.Wait() + return nil +} + +// SendPaymentSync is the synchronous non-streaming version of SendPayment. +// This RPC is intended to be consumed by clients of the REST proxy. +// Additionally, this RPC expects the destination's public key and the payment +// hash (if any) to be encoded as hex strings. +func (r *rpcServer) SendPaymentSync(ctx context.Context, + nextPayment *lnrpc.SendRequest) (*lnrpc.SendResponse, error) { + + return r.sendPaymentSync(&rpcPaymentRequest{ + SendRequest: nextPayment, + }) +} + +// SendToRouteSync is the synchronous non-streaming version of SendToRoute. +// This RPC is intended to be consumed by clients of the REST proxy. +// Additionally, this RPC expects the payment hash (if any) to be encoded as +// hex strings. +func (r *rpcServer) SendToRouteSync(ctx context.Context, + req *lnrpc.SendToRouteRequest) (*lnrpc.SendResponse, error) { + + if req.Route == nil { + return nil, fmt.Errorf("unable to send, no routes provided") + } + + paymentRequest, err := r.unmarshallSendToRouteRequest(req) + if err != nil { + return nil, err + } + + return r.sendPaymentSync(paymentRequest) +} + +// sendPaymentSync is the synchronous variant of sendPayment. It will block and +// wait until the payment has been fully completed. +func (r *rpcServer) sendPaymentSync( + nextPayment *rpcPaymentRequest) (*lnrpc.SendResponse, error) { + + // We don't allow payments to be sent while the daemon itself is still + // syncing as we may be trying to sent a payment over a "stale" + // channel. + if !r.server.Started() { + return nil, ErrServerNotActive + } + + // First we'll attempt to map the proto describing the next payment to + // an intent that we can pass to local sub-systems. + payIntent, err := r.extractPaymentIntent(nextPayment) + if err != nil { + return nil, err + } + + // With the payment validated, we'll now attempt to dispatch the + // payment. + resp, saveErr := r.dispatchPaymentIntent(&payIntent) + switch { + case saveErr != nil: + return nil, saveErr + + case resp.Err != nil: + return &lnrpc.SendResponse{ + PaymentError: resp.Err.Error(), + PaymentHash: payIntent.rHash[:], + }, nil + } + + rpcRoute, err := r.routerBackend.MarshallRoute(resp.Route) + if err != nil { + return nil, err + } + + return &lnrpc.SendResponse{ + PaymentHash: payIntent.rHash[:], + PaymentPreimage: resp.Preimage[:], + PaymentRoute: rpcRoute, + }, nil +} + // AddInvoice attempts to add a new invoice to the invoice database. Any // duplicated invoices are rejected, therefore all invoices *must* have a // unique payment preimage. @@ -5793,7 +6374,7 @@ func (r *rpcServer) AddInvoice(ctx context.Context, NodeSigner: r.server.nodeSigner, DefaultCLTVExpiry: defaultDelta, ChanDB: r.server.chanStateDB, - Graph: r.server.v1Graph, + Graph: r.server.graphDB, GenInvoiceFeatures: func() *lnwire.FeatureVector { v := r.server.featureMgr.Get(feature.SetInvoice) @@ -6018,6 +6599,7 @@ func (r *rpcServer) ListInvoices(ctx context.Context, LastIndexOffset: invoiceSlice.LastIndexOffset, } for i, invoice := range invoiceSlice.Invoices { + invoice := invoice resp.Invoices[i], err = invoicesrpc.CreateRPCInvoice( &invoice, r.cfg.ActiveNetParams.Params, ) @@ -6224,11 +6806,10 @@ func (r *rpcServer) DescribeGraph(ctx context.Context, } } - // Obtain the pointer to the V1 channel graph. This will provide a - // consistent view of the graph due to bolt db's transactional model. - // - // TODO(elle): switch to a cross-version graph view when available. - graph := r.server.v1Graph + // Obtain the pointer to the global singleton channel graph, this will + // provide a consistent view of the graph due to bolt db's + // transactional model. + graph := r.server.graphDB // First iterate through all the known nodes (connected or unconnected // within the graph), collating their current state into the RPC @@ -6353,10 +6934,10 @@ func marshalDBEdge(edgeInfo *models.ChannelEdgeInfo, // channel announcement. if includeAuthProof && edgeInfo.AuthProof != nil { edge.AuthProof = &lnrpc.ChannelAuthProof{ - NodeSig1: edgeInfo.AuthProof.NodeSig1(), - BitcoinSig1: edgeInfo.AuthProof.BitcoinSig1(), - NodeSig2: edgeInfo.AuthProof.NodeSig2(), - BitcoinSig2: edgeInfo.AuthProof.BitcoinSig2(), + NodeSig1: edgeInfo.AuthProof.NodeSig1Bytes, + BitcoinSig1: edgeInfo.AuthProof.BitcoinSig1Bytes, + NodeSig2: edgeInfo.AuthProof.NodeSig2Bytes, + BitcoinSig2: edgeInfo.AuthProof.BitcoinSig2Bytes, } } @@ -6419,11 +7000,10 @@ func (r *rpcServer) GetNodeMetrics(ctx context.Context, BetweennessCentrality: make(map[string]*lnrpc.FloatMetric), } - // Obtain the pointer to the V1 channel graph, this will provide a - // consistent view of the graph due to bolt db's transactional model. - // - // TODO(elle): switch to a cross-version graph view when available. - graph := r.server.v1Graph + // Obtain the pointer to the global singleton channel graph, this will + // provide a consistent view of the graph due to bolt db's + // transactional model. + graph := r.server.graphDB // Calculate betweenness centrality if requested. Note that depending on the // graph size, this may take up to a few minutes. @@ -6460,7 +7040,7 @@ func (r *rpcServer) GetNodeMetrics(ctx context.Context, // uniquely identify the location of transaction's funding output within the // blockchain. The former is an 8-byte integer, while the latter is a string // formatted as funding_txid:output_index. -func (r *rpcServer) GetChanInfo(ctx context.Context, +func (r *rpcServer) GetChanInfo(_ context.Context, in *lnrpc.ChanInfoRequest) (*lnrpc.ChannelEdge, error) { graph := r.server.graphDB @@ -6474,7 +7054,7 @@ func (r *rpcServer) GetChanInfo(ctx context.Context, switch { case in.ChanId != 0: edgeInfo, edge1, edge2, err = graph.FetchChannelEdgesByID( - ctx, in.ChanId, + in.ChanId, ) case in.ChanPoint != "": @@ -6484,7 +7064,7 @@ func (r *rpcServer) GetChanInfo(ctx context.Context, return nil, err } edgeInfo, edge1, edge2, err = graph.FetchChannelEdgesByOutpoint( - ctx, chanPoint, + chanPoint, ) default: @@ -6517,7 +7097,7 @@ func (r *rpcServer) GetNodeInfo(ctx context.Context, "include_channels") } - graph := r.server.v1Graph + graph := r.server.graphDB // First, parse the hex-encoded public key into a full in-memory public // key object we can work with for querying. @@ -6605,13 +7185,11 @@ func marshalNode(node *models.Node) *lnrpc.LightningNode { customRecords := marshalExtraOpaqueData(node.ExtraOpaqueData) return &lnrpc.LightningNode{ - LastUpdate: uint32(node.LastUpdate.Unix()), - PubKey: hex.EncodeToString(node.PubKeyBytes[:]), - Addresses: nodeAddrs, - Alias: node.Alias.UnwrapOr(""), - Color: graphdb.EncodeHexColor( - node.Color.UnwrapOr(color.RGBA{}), - ), + LastUpdate: uint32(node.LastUpdate.Unix()), + PubKey: hex.EncodeToString(node.PubKeyBytes[:]), + Addresses: nodeAddrs, + Alias: node.Alias, + Color: graphdb.EncodeHexColor(node.Color), Features: features, CustomRecords: customRecords, } @@ -6637,8 +7215,7 @@ func (r *rpcServer) QueryRoutes(ctx context.Context, func (r *rpcServer) GetNetworkInfo(ctx context.Context, _ *lnrpc.NetworkInfoRequest) (*lnrpc.NetworkInfo, error) { - // TODO(elle): switch to a cross-version graph view when available. - graph := r.server.v1Graph + graph := r.server.graphDB var ( numNodes uint32 @@ -6663,8 +7240,8 @@ func (r *rpcServer) GetNetworkInfo(ctx context.Context, // network, tallying up the total number of nodes, and also gathering // each node so we can measure the graph diameter and degree stats // below. - err := graph.ForEachNodeCached(ctx, func(ctx context.Context, - node route.Vertex, + err := graph.ForEachNodeCached(ctx, false, func(ctx context.Context, + node route.Vertex, _ []net.Addr, edges map[uint64]*graphdb.DirectedChannel) error { // Increment the total number of nodes with each iteration. @@ -6681,10 +7258,11 @@ func (r *rpcServer) GetNetworkInfo(ctx context.Context, // channel encountered. outDegree++ - // If we've already seen this channel, skip it to - // ensure that we don't double-count stats. + // If we've already seen this channel, then we'll + // return early to ensure that we don't double-count + // stats. if _, ok := seenChans[edge.ChannelID]; ok { - continue + return nil } // Compare the capacity of this channel against the @@ -6730,7 +7308,7 @@ func (r *rpcServer) GetNetworkInfo(ctx context.Context, } // Query the graph for the current number of zombie channels. - numZombies, err := graph.NumZombies(ctx) + numZombies, err := graph.NumZombies() if err != nil { return nil, err } @@ -6999,7 +7577,6 @@ func (r *rpcServer) ListPayments(ctx context.Context, CountTotal: req.CountTotalPayments, CreationDateStart: int64(req.CreationDateStart), CreationDateEnd: int64(req.CreationDateEnd), - OmitHops: req.OmitHops, } // If the maximum number of payments wasn't specified, we default to @@ -7027,6 +7604,7 @@ func (r *rpcServer) ListPayments(ctx context.Context, } for _, payment := range paymentsQuerySlice.Payments { + payment := payment rpcPayment, err := r.routerBackend.MarshallPayment(payment) if err != nil { @@ -7095,7 +7673,7 @@ func (r *rpcServer) DeletePayment(ctx context.Context, rpcsLog.Infof("[DeletePayment] payment_identifier=%v, "+ "failed_htlcs_only=%v", hash, req.FailedHtlcsOnly) - err = r.server.paymentsDB.DeletePayment(ctx, hash, req.FailedHtlcsOnly) + err = r.server.paymentsDB.DeletePayment(hash, req.FailedHtlcsOnly) if err != nil { return nil, err } @@ -7136,7 +7714,7 @@ func (r *rpcServer) DeleteAllPayments(ctx context.Context, req.FailedHtlcsOnly) numDeletedPayments, err := r.server.paymentsDB.DeletePayments( - ctx, req.FailedPaymentsOnly, req.FailedHtlcsOnly, + req.FailedPaymentsOnly, req.FailedHtlcsOnly, ) if err != nil { return nil, err @@ -7283,7 +7861,7 @@ const feeBase float64 = 1000000 func (r *rpcServer) FeeReport(ctx context.Context, _ *lnrpc.FeeReportRequest) (*lnrpc.FeeReportResponse, error) { - channelGraph := r.server.v1Graph + channelGraph := r.server.graphDB selfNode, err := channelGraph.SourceNode(ctx) if err != nil { return nil, err @@ -7668,15 +8246,15 @@ func (r *rpcServer) ForwardingHistory(ctx context.Context, return "", err } - peer, err := r.server.v1Graph.FetchNode(ctx, vertex) + peer, err := r.server.graphDB.FetchNode(ctx, vertex) if err != nil { return "", err } // Cache the peer alias. - chanToPeerAlias[chanID] = peer.Alias.UnwrapOr("") + chanToPeerAlias[chanID] = peer.Alias - return peer.Alias.UnwrapOr(""), nil + return peer.Alias, nil } // TODO(roasbeef): add settlement latency? @@ -8055,33 +8633,23 @@ func (r *rpcServer) SubscribeChannelBackups(req *lnrpc.ChannelBackupSubscription select { // A new event has been sent by the channel notifier, we'll // assemble, then sling out a new event to the client. - case e, ok := <-chanSubscription.Updates(): - if !ok { - // The subscription server closes the updates - // channel during shutdown or cancellation, so - // end the stream gracefully. - return nil - } - + case e := <-chanSubscription.Updates(): // TODO(roasbeef): batch dispatch ntnfs switch e.(type) { - // Only channel lifecycle events should trigger this - // subscription. Commitment updates can affect - // close-tx inputs embedded in an exported SCB, but - // emitting on that frequency would make this stream - // too noisy. To make the subscription behave the same - // way as the synchronous call and the file based - // backup, we also include pending channels in the - // update. - case channelnotifier.PendingOpenChannelEvent, - channelnotifier.OpenChannelEvent, - channelnotifier.ClosedChannelEvent, - channelnotifier.FullyResolvedChannelEvent, - channelnotifier.FundingTimeoutEvent: - - default: + // We only care about new/closed channels, so we'll + // skip any events for active/inactive channels. + // To make the subscription behave the same way as the + // synchronous call and the file based backup, we also + // include pending channels in the update. + case channelnotifier.ActiveChannelEvent: + continue + case channelnotifier.InactiveChannelEvent: + continue + case channelnotifier.ActiveLinkEvent: + continue + case channelnotifier.InactiveLinkEvent: continue } @@ -8677,7 +9245,7 @@ func (r *rpcServer) RegisterRPCMiddleware( } // SendCustomMessage sends a custom peer message. -func (r *rpcServer) SendCustomMessage(ctx context.Context, +func (r *rpcServer) SendCustomMessage(_ context.Context, req *lnrpc.SendCustomMessageRequest) (*lnrpc.SendCustomMessageResponse, error) { @@ -8687,7 +9255,7 @@ func (r *rpcServer) SendCustomMessage(ctx context.Context, } err = r.server.SendCustomMessage( - ctx, peer, lnwire.MessageType(req.Type), req.Data, + peer, lnwire.MessageType(req.Type), req.Data, ) switch { case errors.Is(err, ErrPeerNotConnected): @@ -8736,111 +9304,6 @@ func (r *rpcServer) SubscribeCustomMessages( } } -// SendOnionMessage sends a custom peer message. -func (r *rpcServer) SendOnionMessage(ctx context.Context, - req *lnrpc.SendOnionMessageRequest) (*lnrpc.SendOnionMessageResponse, - error) { - - // First we'll validate the string passed in within the request to - // ensure that it's a valid hex-string, and also a valid compressed - // public key. - pathKey, err := btcec.ParsePubKey(req.PathKey) - if err != nil { - return nil, fmt.Errorf("unable to decode path key bytes: %w", - err) - } - - peer, err := route.NewVertexFromBytes(req.Peer) - if err != nil { - return nil, err - } - - err = r.server.SendOnionMessage(ctx, peer, pathKey, req.Onion) - switch { - case errors.Is(err, ErrPeerNotConnected): - return nil, status.Error(codes.NotFound, err.Error()) - case err != nil: - return nil, err - } - - return &lnrpc.SendOnionMessageResponse{ - Status: "onion message sent successfully", - }, nil -} - -// SubscribeOnionMessages subscribes to a stream of incoming onion messages. -func (r *rpcServer) SubscribeOnionMessages( - _ *lnrpc.SubscribeOnionMessagesRequest, - server lnrpc.Lightning_SubscribeOnionMessagesServer) error { - - client, err := r.server.SubscribeOnionMessages() - if err != nil { - return err - } - defer client.Cancel() - - for { - select { - case <-client.Quit(): - return errors.New("shutdown") - - case <-server.Context().Done(): - return server.Context().Err() - - case update := <-client.Updates(): - oMsg, ok := update.(*onionmessage.OnionMessageUpdate) - if !ok { - return fmt.Errorf("onion message update "+ - "failed type assertion: %T", update) - } - - // Perform a verbatim pass-through of any reply path. - bp := marshallBlindedPath(oMsg.ReplyPath) - - //nolint:ll - err := server.Send(&lnrpc.OnionMessageUpdate{ - Peer: oMsg.Peer[:], - PathKey: oMsg.PathKey[:], - Onion: oMsg.OnionBlob, - ReplyPath: bp, - EncryptedRecipientData: oMsg.EncryptedRecipientData, - CustomRecords: oMsg.CustomRecords, - }) - if err != nil { - return err - } - } - } -} - -// marshallBlindedPath converts a wire-form blinded path into its RPC -// counterpart. If the input is nil, nil is returned. -func marshallBlindedPath(p *lnwire.BlindedPath) *lnrpc.BlindedPath { - if p == nil { - return nil - } - - bp := &lnrpc.BlindedPath{ - // The introduction node may be a short-channel-id direction - // rather than a node public key. We pass it through verbatim - // instead of resolving it, which would need a graph db query. - IntroductionNode: p.IntroductionNode.Bytes(), - BlindingPoint: p.BlindingPoint.SerializeCompressed(), - } - - for _, hop := range p.Hops { - blindedNode := hop.BlindedNodeID.SerializeCompressed() - bp.BlindedHops = append( - bp.BlindedHops, &lnrpc.BlindedHop{ - BlindedNode: blindedNode, - EncryptedData: hop.EncryptedData, - }, - ) - } - - return bp -} - // ListAliases returns the set of all aliases we have ever allocated along with // their base SCIDs and possibly a separate confirmed SCID in the case of // zero-conf. @@ -8884,10 +9347,6 @@ type chainSyncInfo struct { // - blockbeat dispatcher. isSynced bool - // isWalletSynced specifies whether the wallet is synced to - // our chain view. - isWalletSynced bool - // bestHeight is the current height known to the chain backend. bestHeight int32 @@ -8908,23 +9367,22 @@ func (r *rpcServer) getChainSyncInfo() (*chainSyncInfo, error) { return nil, fmt.Errorf("unable to get best block info: %w", err) } - isWalletSynced, bestHeaderTimestamp, err := - r.server.cc.Wallet.IsSynced() + isSynced, bestHeaderTimestamp, err := r.server.cc.Wallet.IsSynced() if err != nil { return nil, fmt.Errorf("unable to sync PoV of the wallet "+ "with current best block in the main chain: %v", err) } - // Create info to be returned. + // Create an info to be returned. info := &chainSyncInfo{ - isWalletSynced: isWalletSynced, - bestHeight: bestHeight, - blockHash: *bestHash, - timestamp: bestHeaderTimestamp, + isSynced: isSynced, + bestHeight: bestHeight, + blockHash: *bestHash, + timestamp: bestHeaderTimestamp, } // Exit early if the wallet is not synced. - if !isWalletSynced { + if !isSynced { rpcsLog.Debugf("Wallet is not synced to height %v yet", bestHeight) @@ -8939,7 +9397,6 @@ func (r *rpcServer) getChainSyncInfo() (*chainSyncInfo, error) { // by many wallets (and also our itests) to make sure everything's up to // date, we add the router's state to it. So the flag will only toggle // to true once the router was also able to catch up. - isSynced := isWalletSynced if !r.cfg.Routing.AssumeChannelValid { routerHeight := r.server.graphBuilder.SyncedHeight() isSynced = uint32(bestHeight) == routerHeight diff --git a/sample-lnd.conf b/sample-lnd.conf index f881c1174..f20035c86 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -405,11 +405,6 @@ ; a new commitment. ; channel-commit-batch-size=10 -; The number of forwarding events deleted per database transaction when running -; deletefwdhistory. Lower this on resource-constrained nodes (e.g. Raspberry Pi) -; to reduce lock contention. Maximum value is 50000. -; fwd-history-delete-batch-size=10000 - ; Keeps persistent record of all failed payment attempts for successfully ; settled payments. ; keep-failed-payment-attempts=false @@ -594,15 +589,6 @@ ; pong failure. ; no-disconnect-on-pong-failure=false -; The address to which funds will be paid out during a cooperative channel -; close. This applies to all channels opened after this option is set, unless -; overridden for a specific channel opening. -; -; Note: If this option is set, any channel opening will fail if the peer does -; not explicitly advertise support for the upfront-shutdown feature bit. -; upfront-shutdown-address= - - [fee] ; Optional URL for external fee estimation. If no URL is specified, the method @@ -902,7 +888,7 @@ ; neutrino.useragentname=neutrino ; Used to help identify ourselves to other bitcoin peers. -; neutrino.useragentversion=0.17.1 +; neutrino.useragentversion=0.12.0-beta ; The amount of time to wait before giving up on a transaction broadcast attempt. ; Default: @@ -921,25 +907,6 @@ ; Neutrino is used. ; neutrino.validatechannels=false -; Headers import for fast initial sync. When configured, neutrino imports block -; and filter headers from the specified sources before falling back to P2P sync. -; Both block and filter header sources must be specified together. Sources can -; be local file paths or HTTP(S) URLs. -; -; For mainnet, the block-dn.org service provides pre-built header files. -; The end_block parameter must be divisible by 100,000 and should be the highest -; such value below the current chain tip. -; Default: -; neutrino.blockheaderssource= -; Default: -; neutrino.filterheaderssource= -; URL example: -; neutrino.blockheaderssource=https://block-dn.org/headers/import/900000 -; neutrino.filterheaderssource=https://block-dn.org/filter-headers/import/900000 -; File path example: -; neutrino.blockheaderssource=/path/to/block_headers.bin -; neutrino.filterheaderssource=/path/to/filter_headers.bin - [autopilot] ; If the autopilot agent should be active or not. The autopilot agent will @@ -1031,6 +998,9 @@ ; Example: ; tor.password=plsdonthackme +; Automatically set up a v2 onion service to listen for inbound connections. +; tor.v2=false + ; Automatically set up a v3 onion service to listen for inbound connections. ; tor.v3=false @@ -1461,71 +1431,12 @@ ; Set to disable blinded route forwarding. ; protocol.no-route-blinding=false -; Set to disable experimental accountability signaling. -; protocol.no-experimental-accountability=false - -; DEPRECATED: Use protocol.no-experimental-accountability instead. ; Set to disable experimental endorsement signaling. ; protocol.no-experimental-endorsement=false ; Set to enable support for RBF based coop close. ; protocol.rbf-coop-close=false -; set to disable onion message support. -; protocol.no-onion-messages=false - -; Maximum sustained onion message ingress bandwidth from any single peer, -; in decimal kilobits per second (1 Kbps = 1000 bits/s). Tokens in the -; underlying bucket are bytes, so small onion messages pay less of the -; budget than spec-max ones. To disable the per-peer limiter, set both -; this and protocol.onion-msg-peer-burst-bytes to 0; setting only one of -; the pair to 0 is rejected at startup as a configuration error. Defaults -; to ~0.5 Mbps (roughly two spec-max onion messages per second). -; protocol.onion-msg-peer-kbps=512 - -; Token bucket depth for the per-peer onion message rate limiter, in -; bytes. Must be at least 65535 (max on-the-wire onion message size: -; 2-byte type prefix + lnwire.MaxMsgBody) so that a single maximum-sized -; wire message can always fit in the bucket; otherwise the limiter would -; reject every call and silently disable onion forwarding. -; The default is 8 * 32 KiB = 262144, enough to absorb a small burst of -; spec-max messages. To disable the per-peer limiter, set both this and -; protocol.onion-msg-peer-kbps to 0. -; protocol.onion-msg-peer-burst-bytes=262144 - -; Maximum sustained aggregate onion message ingress bandwidth across all -; peers combined, in decimal kilobits per second. To disable the global -; limiter, set both this and protocol.onion-msg-global-burst-bytes to 0; -; setting only one of the pair to 0 is rejected at startup as a -; configuration error. The default of ~5 Mbps is sized so that onion -; message traffic cannot dwarf a typical routing node's payment traffic. -; -; Note on starvation: the global limit is shared across all peers, so if -; you run a routing node with many well-behaved peers simultaneously -; sending at their full per-peer allowance the global budget can be -; saturated. With the default 0.5 Mbps peer and 5 Mbps global, 10 peers -; at their full per-peer rate exactly fill the global budget. If you -; expect many concurrent onion-message-active peers, scale this value up -; (or the per-peer rate down) to preserve headroom. -; protocol.onion-msg-global-kbps=5120 - -; Token bucket depth for the global onion message rate limiter, in -; bytes. Must be at least 65535 (max on-the-wire onion message size: -; 2-byte type prefix + lnwire.MaxMsgBody). The default is -; 50 * 32 KiB = 1638400, enough to absorb a burst of spec-max messages -; while keeping the long-term rate bounded by onion-msg-global-kbps. To -; disable the global limiter, set both this and -; protocol.onion-msg-global-kbps to 0. -; protocol.onion-msg-global-burst-bytes=1638400 - -; If set, accept incoming onion messages from peers that do not have a -; fully open channel with us. By default only peers with at least one -; active channel are admitted to the onion message ingress path, so -; that a new peer identity cannot burn onion bandwidth without first -; paying the capital cost of opening a channel. Enable this only if -; you want your node to accept onion messages from arbitrary peers. -; protocol.onion-msg-relay-all=false - ; Set to handle messages of a particular type that falls outside of the ; custom message number range (i.e. 513 is onion messages). Note that you can ; set this option as many times as you want to support more than one custom @@ -1574,12 +1485,6 @@ ; less RAM. Can only be used with a bolt database backend. ; db.no-graph-cache=false -; Block the start-up of LND until the graph cache has been fully populated. -; If not set, the graph cache will be populated asynchronously and any read -; calls made before the cache is fully populated will fall back to the -; database. -; db.sync-graph-cache-load=false - ; Specify whether the optional migration for pruning old revocation logs ; should be applied. This migration will only save disk space if there are open ; channels prior to lnd@v0.15.0. @@ -1960,7 +1865,7 @@ ; DefaultIncomingBroadcastDelta set by lnd, otherwise the channel will be force ; closed anyway. A warning will be logged on startup if this value is not large ; enough to prevent force closes. -; invoices.holdexpirydelta=18 +; invoices.holdexpirydelta=12 [routing] diff --git a/scripts/apply-pr-severity.sh b/scripts/apply-pr-severity.sh deleted file mode 100755 index 311aac8df..000000000 --- a/scripts/apply-pr-severity.sh +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env bash -# -# Applies the PR severity label and posts the classifier's comment. -# -# Reads the classifier's verdict from a result directory (arg $1, default -# "result") and, in this order: -# - validates the severity against the known set, -# - reconciles the severity-* label in a single gh edit, and -# - sanitizes and posts the model-authored comment. -# -# The classify job runs a model on untrusted PR text, so the comment body is -# treated as tainted and passed through sanitize_comment() before posting. -# -# Env: GH_TOKEN (gh auth), PR_NUMBER, REPO. -# Usage: ./apply-pr-severity.sh [RESULT_DIR] - -set -euo pipefail - -# sanitize_comment reads an untrusted comment body on stdin and writes a safe -# version to stdout. GitHub already strips scripts and unsafe HTML from rendered -# comment bodies, so this targets the notification/spam class: @-mentions that -# ping arbitrary users, `#N` cross-references that notify other issues/PRs, -# auto-linked URLs used for phishing, and Markdown links. It deliberately leaves -# the model's
/ markup intact so the comment still renders. -# -# The zero-width non-joiner is spliced in as a literal byte (not a `\xNN` sed -# escape) so the rules work identically under both GNU and BSD sed. Rule order -# matters: the `#`-before-digit defang runs before the bracket rules, which -# themselves emit `[`/`]` — running it after would corrupt those -# entities. Matching only `#` followed by a digit leaves `##`/`###` Markdown -# headings untouched. -sanitize_comment() { - local z - z="$(printf '\xe2\x80\x8c')" - sed -E \ - -e "s/@/@${z}/g" \ - -e "s/www\./www${z}./g" \ - -e 's,[hH][tT][tT][pP][sS]?://,hxxp://,g' \ - -e "s/#([0-9])/#${z}\1/g" \ - -e 's/\[/\[/g' \ - -e 's/\]/\]/g' -} - -main() { - local result_dir="${1:-result}" - : "${GH_TOKEN:?GH_TOKEN is required}" - : "${PR_NUMBER:?PR_NUMBER is required}" - : "${REPO:?REPO is required}" - - # Treat a missing severity.txt and an empty/whitespace-only one the same way: - # both mean the classify job produced no usable verdict (model crash or - # timeout, a missing artifact tolerated by the download step, or a truncated - # write). Warn and degrade rather than laundering a broken run into a silent - # green or failing the check red, since severity classification is advisory. - # A non-empty but unrecognized value still fails below. - # - # Lowercase on read so a stray `Low`/`HIGH` from the model still matches. - local severity="" - if [[ -f "$result_dir/severity.txt" ]]; then - severity="$(tr -d '[:space:]' < "$result_dir/severity.txt" | tr '[:upper:]' '[:lower:]')" - fi - if [[ -z "$severity" ]]; then - echo "::warning::PR severity classifier produced no verdict; PR left unlabeled." - return 0 - fi - - # Strictly validate the severity against the known set — it is the only - # privileged, semantically meaningful output. - case "$severity" in - critical|high|medium|low) ;; - *) - echo "Invalid severity '$severity'; refusing to apply." >&2 - return 1 - ;; - esac - - # Read the PR's current severity-* labels so the reconciliation below removes - # exactly the ones present. (The severity-change banner is authored by the - # model in comment.md, so no previous severity is needed here.) - # - # Fail closed on a read error (rate limit, 5xx): swallowing it would yield - # empty labels, so the loop below would remove nothing yet still add the new - # label — the exact two-label state this single-edit design prevents. With - # set -e, a failed read aborts before any edit. (local is declared separately - # so the assignment's own exit status, not local's, drives set -e.) - local cur_labels - cur_labels="$(gh pr view "$PR_NUMBER" --repo "$REPO" \ - --json labels --jq '.labels[].name')" - - # Reconcile the severity label in a SINGLE gh edit: add the target and remove - # exactly the other severity-* labels currently present. Doing it in one call - # means: - # - if the add fails (label missing from the repo, transient API error), the - # whole edit fails and the PR keeps its prior label rather than being left - # unlabeled; and - # - a run cancelled by `concurrency.cancel-in-progress` can't stop between an - # add and a separate remove, so the PR is never left carrying two labels. - # Only present labels are removed, so gh never errors on a missing one. - local edit_args level - edit_args=(--add-label "severity-$severity") - for level in critical high medium low; do - [[ "$level" == "$severity" ]] && continue - if grep -qx "severity-$level" <<< "$cur_labels"; then - edit_args+=(--remove-label "severity-$level") - fi - done - gh pr edit "$PR_NUMBER" --repo "$REPO" "${edit_args[@]}" - - # Lowercase on read (as with severity) so a `True`/`TRUE` slip from the model - # is honored rather than silently dropping the comment. - local should_comment="false" - if [[ -f "$result_dir/should_comment.txt" ]]; then - should_comment="$(tr -d '[:space:]' < "$result_dir/should_comment.txt" | tr '[:upper:]' '[:lower:]')" - fi - - # Use -s (exists and non-empty): a 0-byte comment.md would otherwise reach - # `gh pr comment --body-file`, which rejects an empty body and would abort - # after the label was already applied. - if [[ "$should_comment" != "true" || ! -s "$result_dir/comment.md" ]]; then - echo "No comment requested; done." - return 0 - fi - - # Sanitize first, then enforce the size cap on the SANITIZED body. Every - # sanitizer rule only grows the byte count (e.g. `[` -> `[`), so a body - # that fits before sanitizing can exceed GitHub's comment limit afterward; - # measuring the post-sanitization size is what actually bounds the request. - sanitize_comment < "$result_dir/comment.md" > "$result_dir/comment.sanitized.md" - - local max_bytes=16384 - if [[ "$(wc -c < "$result_dir/comment.sanitized.md")" -gt "$max_bytes" ]]; then - echo "::warning::classifier comment exceeds ${max_bytes} bytes after sanitization; skipping comment." - return 0 - fi - - # Post from a file (never interpolated into the shell) so the body is handled - # purely as data. - gh pr comment "$PR_NUMBER" --repo "$REPO" \ - --body-file "$result_dir/comment.sanitized.md" -} - -# Allow sourcing (e.g. from the test) without executing main. -if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then - main "$@" -fi diff --git a/scripts/apply-pr-severity_test.sh b/scripts/apply-pr-severity_test.sh deleted file mode 100755 index b2fdc1e11..000000000 --- a/scripts/apply-pr-severity_test.sh +++ /dev/null @@ -1,125 +0,0 @@ -#!/usr/bin/env bash -# -# Tests for scripts/apply-pr-severity.sh: the sanitize_comment() filter and the -# main() control flow (label reconciliation and comment gating), the latter with -# gh stubbed via a PATH shim that records its argument lists. -# -# Run: bash scripts/apply-pr-severity_test.sh - -set -euo pipefail - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# shellcheck source=scripts/apply-pr-severity.sh -source "$DIR/apply-pr-severity.sh" - -ZWNJ="$(printf '\xe2\x80\x8c')" -fail=0 - -want() { grep -qF -- "$2" <<< "$OUT" && echo "ok: $1" || { echo "FAIL: $1"; fail=1; }; } -absent() { grep -qF -- "$2" <<< "$OUT" && { echo "FAIL: $1"; fail=1; } || echo "ok: $1"; } -eq() { [[ "$2" == "$3" ]] && echo "ok: $1" || { echo "FAIL: $1 (got '$2' want '$3')"; fail=1; }; } -has() { grep -qF -- "$2" "$GH_CALLS" && echo "ok: $1" || { echo "FAIL: $1 (no gh call: $2)"; fail=1; }; } -lacks() { grep -qF -- "$2" "$GH_CALLS" && { echo "FAIL: $1 (unexpected gh call: $2)"; fail=1; } || echo "ok: $1"; } -in_file(){ grep -qF -- "$3" "$2" && echo "ok: $1" || { echo "FAIL: $1"; fail=1; }; } - -echo "# sanitize_comment" -INPUT='## 🟢 PR Severity: **LOW** -### Analysis -Ping @user, see #123 and org/repo#4567, [x](https://evil), www.evil.net, http://Bad.io. -' -OUT="$(printf '%s' "$INPUT" | sanitize_comment)" -want "## heading preserved" '## 🟢 PR Severity' -want "### heading preserved" '### Analysis' -want "@-mention defanged" "@${ZWNJ}user" -want "#123 defanged" "#${ZWNJ}123" -want "repo#4567 defanged" "#${ZWNJ}4567" -want "https scheme defanged" 'hxxp://evil' -want "http scheme (case-insensitive)" 'hxxp://Bad.io' -want "www. auto-link defanged" "www${ZWNJ}.evil.net" -want "markdown brackets escaped" '[x]' -want "bot marker preserved" '' -absent "no live http(s):// scheme" 'https://' -absent "bracket entity not corrupted by #" "&#${ZWNJ}" - -echo "# main()" -WORK="$(mktemp -d)" -trap 'rm -rf "$WORK"' EXIT -mkdir -p "$WORK/bin" -# Fake gh: record the full argument list, and for `pr view` emit the fixture -# labels so main()'s reconciliation has something to diff against. -cat > "$WORK/bin/gh" <<'SH' -#!/usr/bin/env bash -echo "$*" >> "$GH_CALLS" -[[ "$1 $2" == "pr view" ]] && printf '%s' "${GH_FAKE_LABELS:-}" -exit 0 -SH -chmod +x "$WORK/bin/gh" -export PATH="$WORK/bin:$PATH" -export GH_TOKEN=x PR_NUMBER=42 REPO=owner/repo - -# run_main RESULT_DIR: run main() capturing its return code in RC, with a fresh -# GH_CALLS log for the case. -run_main() { export GH_CALLS="$WORK/calls"; : > "$GH_CALLS"; RC=0; main "$1" >/dev/null 2>&1 || RC=$?; } - -# Invalid severity: reject before any gh mutation. -r="$WORK/invalid"; mkdir -p "$r"; printf 'bogus\n' > "$r/severity.txt" -GH_FAKE_LABELS='' run_main "$r" -eq "invalid severity returns 1" "$RC" "1" -lacks "invalid severity: no label edit" "pr edit" - -# No verdict at all: warn and no-op, no gh mutation. -r="$WORK/noverdict"; mkdir -p "$r" -GH_FAKE_LABELS='' run_main "$r" -eq "missing severity.txt returns 0" "$RC" "0" -lacks "missing verdict: no label edit" "pr edit" - -# Empty/whitespace-only severity.txt is handled like a missing one (F30). -r="$WORK/emptysev"; mkdir -p "$r"; printf ' \n' > "$r/severity.txt" -GH_FAKE_LABELS='' run_main "$r" -eq "empty severity.txt returns 0" "$RC" "0" -lacks "empty severity: no label edit" "pr edit" - -# Valid severity, no comment: reconcile labels in one edit, remove only present. -r="$WORK/label"; mkdir -p "$r"; printf 'high\n' > "$r/severity.txt"; printf 'false\n' > "$r/should_comment.txt" -GH_FAKE_LABELS=$'severity-low\nkeep-me\nseverity-medium' run_main "$r" -eq "label-only returns 0" "$RC" "0" -has "adds target label" "--add-label severity-high" -has "removes present severity-low" "--remove-label severity-low" -has "removes present severity-medium" "--remove-label severity-medium" -lacks "does not touch absent critical" "severity-critical" -lacks "no comment when should_comment false" "pr comment" - -# should_comment true with a real body: sanitize and post it. -r="$WORK/comment"; mkdir -p "$r"; printf 'low\n' > "$r/severity.txt"; printf 'true\n' > "$r/should_comment.txt" -printf '## x\nhi @bob\n' > "$r/comment.md" -GH_FAKE_LABELS='' run_main "$r" -eq "comment path returns 0" "$RC" "0" -has "posts sanitized comment" "pr comment 42 --repo owner/repo --body-file" -in_file "posted body defangs @-mention" "$r/comment.sanitized.md" "@${ZWNJ}bob" - -# Capitalized should_comment is honored, not silently dropped (F31). -r="$WORK/truecase"; mkdir -p "$r"; printf 'low\n' > "$r/severity.txt"; printf 'TRUE\n' > "$r/should_comment.txt" -printf '## x\nhi\n' > "$r/comment.md" -GH_FAKE_LABELS='' run_main "$r" -eq "should_comment TRUE returns 0" "$RC" "0" -has "TRUE still posts the comment" "pr comment 42 --repo owner/repo --body-file" - -# should_comment true but empty body (F15): no post. -r="$WORK/empty"; mkdir -p "$r"; printf 'low\n' > "$r/severity.txt"; printf 'true\n' > "$r/should_comment.txt" -: > "$r/comment.md" -GH_FAKE_LABELS='' run_main "$r" -eq "empty comment returns 0" "$RC" "0" -lacks "empty comment: no post" "pr comment" - -# Oversized after sanitization (F22): brackets expand ~5x past the cap; no post. -r="$WORK/big"; mkdir -p "$r"; printf 'low\n' > "$r/severity.txt"; printf 'true\n' > "$r/should_comment.txt" -{ printf '## x\n'; head -c 20000 /dev/zero | tr '\0' '['; } > "$r/comment.md" -GH_FAKE_LABELS='' run_main "$r" -eq "oversized comment returns 0" "$RC" "0" -lacks "oversized comment: no post" "pr comment" - -if [[ "$fail" -ne 0 ]]; then - echo "TESTS FAILED" - exit 1 -fi -echo "ALL TESTS PASSED" diff --git a/scripts/bw-compatibility-test/.env b/scripts/bw-compatibility-test/.env index 534faca6a..7fbd8d106 100644 --- a/scripts/bw-compatibility-test/.env +++ b/scripts/bw-compatibility-test/.env @@ -1,3 +1,3 @@ BITCOIND_VERSION=26 -LND_LATEST_VERSION=v0.20.0-beta +LND_LATEST_VERSION=v0.18.5-beta TIMEOUT=15 diff --git a/scripts/bw-compatibility-test/docker-compose.override.yaml b/scripts/bw-compatibility-test/docker-compose.override.yaml index 59dc9830f..51ca758b9 100644 --- a/scripts/bw-compatibility-test/docker-compose.override.yaml +++ b/scripts/bw-compatibility-test/docker-compose.override.yaml @@ -42,7 +42,6 @@ services: --protocol.zero-conf --protocol.simple-taproot-chans --trickledelay=50 - --historicalsyncinterval=10s dave-pr: image: lnd-dev:backward-compat-test-build @@ -84,6 +83,5 @@ services: --protocol.zero-conf --protocol.simple-taproot-chans --trickledelay=50 - --historicalsyncinterval=10s --db.backend=sqlite --db.use-native-sql diff --git a/scripts/bw-compatibility-test/docker-compose.yaml b/scripts/bw-compatibility-test/docker-compose.yaml index ea901e699..7860b4415 100644 --- a/scripts/bw-compatibility-test/docker-compose.yaml +++ b/scripts/bw-compatibility-test/docker-compose.yaml @@ -71,7 +71,6 @@ services: - "--protocol.zero-conf" - "--protocol.simple-taproot-chans" - "--trickledelay=50" - - "--historicalsyncinterval=10s" bob: image: lightninglabs/lnd:${LND_LATEST_VERSION} @@ -112,7 +111,6 @@ services: - "--protocol.zero-conf" - "--protocol.simple-taproot-chans" - "--trickledelay=50" - - "--historicalsyncinterval=10s" charlie: image: lightninglabs/lnd:${LND_LATEST_VERSION} @@ -150,7 +148,6 @@ services: - "--tlsextradomain=charlie" - "--accept-keysend" - "--trickledelay=50" - - "--historicalsyncinterval=10s" dave: image: lightninglabs/lnd:${LND_LATEST_VERSION} @@ -188,7 +185,6 @@ services: - "--tlsextradomain=dave" - "--accept-keysend" - "--trickledelay=50" - - "--historicalsyncinterval=10s" - "--db.backend=sqlite" - "--db.use-native-sql" diff --git a/scripts/bw-compatibility-test/network.sh b/scripts/bw-compatibility-test/network.sh index e58622960..98eca675b 100644 --- a/scripts/bw-compatibility-test/network.sh +++ b/scripts/bw-compatibility-test/network.sh @@ -298,22 +298,6 @@ wait_for_active_chans() { echo "🟢 $node now has exactly $expected_channels active channels!" } -# collect_logs copies the lnd log file from each running container into a -# local ./logs directory. Call this before compose_down so logs are available -# for CI artifact upload even after the cluster is torn down. -function collect_logs() { - local log_dir="$DIR/logs" - mkdir -p "$log_dir" - - for node in alice bob charlie dave bob-pr dave-pr; do - if docker ps -a --format '{{.Names}}' | grep -q "^${node}$"; then - docker logs "$node" > "$log_dir/${node}.log" 2>&1 || true - fi - done - - echo "📋 Logs collected in $log_dir" -} - # mine mines a number of blocks on the regtest network. If no # argument is provided, it defaults to 6 blocks. function mine() { diff --git a/scripts/bw-compatibility-test/test.sh b/scripts/bw-compatibility-test/test.sh index 7b9f2f7bf..d8bb6660e 100755 --- a/scripts/bw-compatibility-test/test.sh +++ b/scripts/bw-compatibility-test/test.sh @@ -16,9 +16,8 @@ cd $DIR compose_up # Ensure that the cluster is shut down when the script exits -# regardless of success. Logs are collected first so they are -# available for CI artifact upload after the cluster is torn down. -trap 'collect_logs; compose_down' EXIT +# regardless of success +trap compose_down EXIT # Set up the network. setup_network diff --git a/scripts/check-each-commit.sh b/scripts/check-each-commit.sh index 1dca8cca0..6ae614ff4 100755 --- a/scripts/check-each-commit.sh +++ b/scripts/check-each-commit.sh @@ -12,13 +12,4 @@ if [[ "$(git log --pretty="%H %D" | grep "^[0-9a-f]*.* $1")" = "" ]]; then echo "It seems like the current checked-out commit is not based on $1" exit 1 fi - -# Keep build cache ephemeral for this run to avoid long-term disk growth. -TMP_GOCACHE=$(mktemp -d -t lnd-check-commit-gocache.XXXXXX) -cleanup() { - rm -rf "$TMP_GOCACHE" -} -trap cleanup EXIT -export GOCACHE="$TMP_GOCACHE" - git rebase --exec scripts/check-commit.sh $1 diff --git a/scripts/comment-on-duplicates.sh b/scripts/comment-on-duplicates.sh deleted file mode 100755 index e2f2c2c4b..000000000 --- a/scripts/comment-on-duplicates.sh +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env bash -# -# Comments on a GitHub issue with a list of potential duplicates. -# Usage: ./comment-on-duplicates.sh --base-issue 123 --potential-duplicates 456 789 101 -# - -set -euo pipefail - -REPO="${GITHUB_REPOSITORY:-lightningnetwork/lnd}" -BASE_ISSUE="" -DUPLICATES=() - -# Parse arguments -while [[ $# -gt 0 ]]; do - case $1 in - --base-issue) - BASE_ISSUE="$2" - shift 2 - ;; - --potential-duplicates) - shift - while [[ $# -gt 0 && ! "$1" =~ ^-- ]]; do - DUPLICATES+=("$1") - shift - done - ;; - *) - echo "Unknown option: $1" >&2 - exit 1 - ;; - esac -done - -# Validate base issue -if [[ -z "$BASE_ISSUE" ]]; then - echo "Error: --base-issue is required" >&2 - exit 1 -fi - -if ! [[ "$BASE_ISSUE" =~ ^[0-9]+$ ]]; then - echo "Error: --base-issue must be a number, got: $BASE_ISSUE" >&2 - exit 1 -fi - -# Validate duplicates -if [[ ${#DUPLICATES[@]} -eq 0 ]]; then - echo "Error: --potential-duplicates requires at least one issue number" >&2 - exit 1 -fi - -if [[ ${#DUPLICATES[@]} -gt 3 ]]; then - echo "Error: --potential-duplicates accepts at most 3 issues" >&2 - exit 1 -fi - -for dup in "${DUPLICATES[@]}"; do - if ! [[ "$dup" =~ ^[0-9]+$ ]]; then - echo "Error: duplicate issue must be a number, got: $dup" >&2 - exit 1 - fi -done - -# Validate that base issue exists -if ! gh issue view "$BASE_ISSUE" --repo "$REPO" &>/dev/null; then - echo "Error: issue #$BASE_ISSUE does not exist in $REPO" >&2 - exit 1 -fi - -# Validate that all duplicate issues exist -for dup in "${DUPLICATES[@]}"; do - if ! gh issue view "$dup" --repo "$REPO" &>/dev/null; then - echo "Error: issue #$dup does not exist in $REPO" >&2 - exit 1 - fi -done - -# Build comment body -COUNT=${#DUPLICATES[@]} -if [[ $COUNT -eq 1 ]]; then - HEADER="Found 1 possible duplicate issue:" -else - HEADER="Found $COUNT possible duplicate issues:" -fi - -BODY="$HEADER"$'\n\n' -INDEX=1 -for dup in "${DUPLICATES[@]}"; do - BODY+="$INDEX. https://github.com/$REPO/issues/$dup"$'\n' - ((INDEX++)) -done - -BODY+=$'\n'"If this issue is a duplicate, please close it and 👍 the existing issue instead."$'\n\n' -BODY+="🤖 Generated with [Claude Code](https://claude.ai/code)" - -# Post the comment -gh issue comment "$BASE_ISSUE" --repo "$REPO" --body "$BODY" - -echo "Posted duplicate comment on issue #$BASE_ISSUE" diff --git a/scripts/install_bitcoind.sh b/scripts/install_bitcoind.sh index 0fdf576c5..8f74efa46 100755 --- a/scripts/install_bitcoind.sh +++ b/scripts/install_bitcoind.sh @@ -4,24 +4,9 @@ set -ev BITCOIND_VERSION=$1 -# The docker image tag and the install directory version don't always use the -# same format. Major-only tags like `29` or `30` install into -# `/opt/bitcoin-29.0` and `/opt/bitcoin-30.0`, while tags that already carry a -# dot (patch releases like `29.1` or release candidates like `30.0rc1`) install -# into a directory that matches the tag verbatim (`/opt/bitcoin-29.1`, -# `/opt/bitcoin-30.0rc1`). Testing an RC therefore just means passing the full -# version string as the first argument, e.g. `install_bitcoind.sh 30.0rc1`. -if [[ "$BITCOIND_VERSION" == *.* ]]; then - BITCOIND_DIR_VERSION="$BITCOIND_VERSION" -else - BITCOIND_DIR_VERSION="${BITCOIND_VERSION}.0" -fi - -# TAG_SUFFIX and DIR_SUFFIX are kept as escape hatches for one-off images that -# diverge from the conventions above (e.g. a privately pushed build); they are -# empty by default and should stay that way for normal releases. +# Useful for testing RCs: e.g. TAG_SUFFIX=.0rc1, DIR_SUFFIX=.0rc1 TAG_SUFFIX= -DIR_SUFFIX= +DIR_SUFFIX=.0 # Useful for testing against an image pushed to a different Docker repo. REPO=lightninglabs/bitcoin-core @@ -34,5 +19,5 @@ fi docker pull ${REPO}:${BITCOIND_VERSION}${TAG_SUFFIX} CONTAINER_ID=$(docker create ${REPO}:${BITCOIND_VERSION}${TAG_SUFFIX}) -sudo docker cp $CONTAINER_ID:/opt/bitcoin-${BITCOIND_DIR_VERSION}${DIR_SUFFIX}/bin/bitcoind /usr/local/bin/bitcoind +sudo docker cp $CONTAINER_ID:/opt/bitcoin-${BITCOIND_VERSION}${DIR_SUFFIX}/bin/bitcoind /usr/local/bin/bitcoind docker rm $CONTAINER_ID diff --git a/scripts/keys/boris.asc b/scripts/keys/boris.asc deleted file mode 100644 index 2d732d8a2..000000000 --- a/scripts/keys/boris.asc +++ /dev/null @@ -1,56 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- - -mQINBFkMo/sBEACizMLy5G2eWMKTvpnzbCCgc9vaxVckdDwcfU10YH3JCOjrKyoY -MufKHq862vU+iXURZrkDZI6iK6R/Gbc+yUp3dk/rXgbCubMUi37yCqaEvqM26Eik -D6Hyvfy013GXoAsMSYfPv4c/YDWYRBkNwy2zzH+Ia8nzlfWpaGHUYUUrxHnO4V0W -JBEJYBsGF9R6E/yw1ZZkAZk0UQvrjQI4jAGGzH0r7kWVWPWW0F7x767GvWpyAn9q -Qap0CSUEAKrrpQXwMOopdRYeYWtvE8E82QMap2XJ6zc+n2mmVPlTe/wGKpjCXGIh -TdBHFumHzHUQEUaC/uI4hzMhcEVpTNenLcepWggwWEUqL3l9fvUVJygWjcJjvoi4 -E7fBz7io8me28suA0CMGXhZSA04ZY65EOF6aDhu8ZJOHBi/x8p8EYOWsoEy9wjz9 -r6QqoGs+Vp750GqE7XeXGj8q9ZkMpUaJCANVnZrXw+8Z9bQJv15UgLEGqqgU/7i8 -uLz2IX+Q0d3+Lnnucbsfz/qaNx2/vyNgK95b+YmTpR808Y4ANv18QepW9a8tmamO -3aGW3zDv1U7kZGUYoFllsCzwu4ML7oPfJbk6xOxdgQAToneFCw7PFu32T8Puw9mI -oRbkVAjineLvWeUdNtVbw9lWOXUl+nH8LifC0X5mQrxK2/VmsluXhJUGoQARAQAB -tCBCb3JpcyBOYWdhZXYgPGJuYWdhZXZAZ21haWwuY29tPokCOAQTAQIAIgUCWQyj -+wIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQXqmEcDYay08ZpRAAjCXG -Y9gCkTG0WnIUCRimV72vcFyuRjqA2lEPm5SVvdZFTXf/QW1IOtIXSWvq6WZgSceB -PeHYj5hQiD51138Pxl7uNEw93kqVb4WvYiBGOevdvoKelNY3GX4Hv8lKHEH30WaT -rv35/b5yXtgE48pctrm9AEmy7xxPo42Mlvtp6fhQCWE9aFKPx+NnYGkGOr/MUULh -snvvriTIdY8YxC7yzyKFXoEsfs6WUdWObEg3tbNJ2FmRcyUJQlbzCHBye6Cz1HCX -bnwZh9tp7IZBQCC3xcrhvKiJcmIn/Cvktv34B5P5pko3ashLI/3kuhiLdYdsZR2U -Xt1CVEjb35PdQpGeSq43O/q1l0zGojXkGwNU2DRN3uKgv8e9mb/uYv6PIGPqZsma -KPZZCebSTVa5cDlh0v1E0kval+Tswv7hb6qd49m6SFfM851mS7Z5rOBRPUCNKeQ7 -dNSe9ST+GqCjBz0lYXB1jQL1dgrKceKmiscmVWTaUUl/dUxLJOSciACdpXsByVcp -Z36/wvDHi167NocScwgQRMriKniFvpAhYG4UR861hX6ZZ2ub9qA3aVhp7dx1zXtj -pJYK13FbtNtC01k9q94dAcTC2l4Ef4xGT6GVbKRwm3vC1k58Na5aDtckaNkhpev6 -bI4EfIvw0D7/35okhScLhL6Qf9lnM2jummJtJRCJARwEEAECAAYFAlkMpUMACgkQ -8BzcVabCts4bzwf+PJ4kNjoAyh7F6KgnfnymHcBeZD53sLSRX9k6++YtIdwXlmP+ -7Nud3CskqoYSaaRN4OQLGd83HAy+HHejuep0SJpGGIrUgnO7k+Cj5z8UpPbjjPzn -PDrS4bJ6qsGLv0Fhuu3cNfwFVXMd9u/pY6OsWZd8FVTO8A1sSD0q1lrYplaSK+vl -yae9o7bjRvnSNjQjzFdc9OQWSSefgFr6LSyCFda+7yw5TVtSIC2OXSpgxs/IWxhG -1WyUP0A3yVNFfZJfsSj8+R5SM9GxLMxHbD2Mp7oPTITmR+X7KYEyWVYRSmf4l0Uu -r7gPfCa5RwiA2biky1MbgpRrExsdNn0B8EoBs7kCDQRZDKP7ARAAw+rBcpYy1Bje -uUax39sBn1uZ4tB8Aw+UmebwUq41OktW/RGXdnoBigVPznN36xUZHgXqh2h6mWza -fCaPMx52pNcXwi9m8zxvi1O9BF5OyEWO0vCKTfZgieAUwgjq6EUXBMaINGtYBU/t -A+TAL4MNM+2SHfXUre4MZJfP4EUpZ5ipltgMsmZI7QThn5B1jfh67kVLtfTDJWWR -OflyJ+WHJuL6H1hih589SFVChd3qhGLX/gENtGneXNnq7SyZ/nx3owccI+CYLa8t -DiK3wLaPHfmrXCeBEwmQLi8BHSyhVR9lFuAieYKpFp4GVXdc5+0bzo5VS3FMKvnq -sTdrE003Y+YfcPps+lqr/148DN3DGSspbD9k6Ltcmt3UrtakcAnGQtj0c7pqBI6a -elR3TAW37FaYn69rIpMwTCa9eU3EwgiXOToUMdW20pj7aHDyXB/kF19NLkjUtxwh -6z0b1UFt5r3PWugRxypziLHZ+NYH09Y7vHu+dyxASJi8Zqs71X2ryq7MXJLEtQLq -hfaDYa/m2PN3d0FJYZ0rW3M35H22Xhw9hrgnrAR5TtuOoVcdwXdLbzf4V4LL5TZZ -cGQ1CF7ciDBUxQErPR3bcFVznnd2YPvNegsllWNsS8exTph5qXvkuqIZcdkhAMMK -xCkgpVMY7DyurqAgHm2tx0WaIlm7Ow0AEQEAAYkCHwQYAQIACQUCWQyj+wIbDAAK -CRBeqYRwNhrLT+V0EACVjXWyVFLfe37MGGdopixAu818ZAL1up0hFogVwttyK5lE -e+YqB40Sbr8CxZHLuDLdtr9CRdf/L7L0ycwUGqgsM+JImq1n2hMvxbZwyWrRV1ON -St74XLEs1m5mGNrOrNqbDOZ2fcPkJ3KFGngxN7NXh56gva36mic8ZblEgFmrHgFT -K/tce2YPoOoEPYq94ZLNqGkbpIJZXWRbr+5IQUb/ZD4xTKmg/LviIKltSE8Av0Of -QF9VKJ2rSG3feLVRSiVOSl0Jgm4htUsjh7QZRjwPI6z61UXZRDmdv6LkBa4dP+yT -d8bZd2OVVEmlVaN9tk13oy6wp2nH4LOEdCckq6sF2QPRb6tsE+jSQIHkginOn8FS -hRUsLjVKm0QZ7RZu9BVUJEc9LOMwVf+NIHtQiFFyvKnJuipA/B6PN8dh/T7zmfDX -Jh8I/OlqsO9Bu7TKd/ULgbBsoNQImERLGiEC0vRPCjaxCkv00qvtl869da35Ucv+ -coPgIhWr2erqGVTUw1AySsQwO0svB7IYjfdV+yd+V3IcJbgNfVfDenB/LS8+JHoa -ug4pXpJmyKwi9p51BWXonWap0/4ZmimPgiBBCihuKcVnVXzbXQ9L1pCAifsI3MQS -fYB3DxAdBWRnR0rK1ysuBN/E3tEaJJHnrtWzAW5yZrI60/kvpRpeADMbuXHJQw== -=E/zA ------END PGP PUBLIC KEY BLOCK----- diff --git a/scripts/keys/georgetsagk.asc b/scripts/keys/georgetsagk.asc deleted file mode 100644 index 803123bee..000000000 --- a/scripts/keys/georgetsagk.asc +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- - -mQINBGLF2RABEADTlKM5TvtsicbWF4WXjX/XHs/TRkp8RWXdNqkMWoIP27nWy24v -FEL6dU3FNnaPzeHfLS6+SVoOM2ku5X/KdIZoaiejXEN8WBuXz8Ydo05PKMormXDP -PUDjxdUsO5qrY/1DNQM4+9hKq1f8PrEj95DS6CPp8jlsei1W2BqaSATZNhfgu3Yb -ruQOlrz4nC1A1WmD5T/YrGWoGbJjziziVgbtLzC0P8cts7Za0cmH99ohkOodPq1N -+vg0J2Dto9S2qBsaNuSD5Vy9jQ1FQFXHE2Z3wPWLJJeHo0Ea5ewdpkWrm8QlIu6A -RyjGSBkoToOSSez4FPsublthau9ETIsOJv4c9+sJx+tsqP2UazZt4zLucpsMAXBW -RH9lCpvSK+cMdrTRgD6J3K1gtFToxnPHpVuOQn7tMowGEzSPkEO5zLeXvIm3rsKI -R9jmpZ0JOn4V5+6HrERyhnffcwRvx8Ce3+btPmHHYfVBD2e3PIODbOfC3Ppw6AXg -Pfkwr3tUt3JRzqQBhcX3wMB1kFS3G9/6l9IqhK9rFzlEu4bBEIcCLq/ljX012hXj -EjKOQ372Qo2sVRuerg247RU3RQzw4wsNgecIIgawPo/dzR14EW+K15CIYTuH/MNP -4hS605Wvdx6ZPzCPm4hBXAgGNK9j2UsxSQKqpxmAqdquF1xgZFrntUwN5QARAQAB -tDFHZW9yZ2UgVHNhZ2thcmVsaXMgPGdlb3JnZS50c2Fna2FyZWxpc0BnbWFpbC5j -b20+iQJOBBMBCgA4FiEEFYO2AbtXzHzS34qH4I3qmxK2avYFAmLF2RACGwMFCwkI -BwIGFQoJCAsCBBYCAwECHgECF4AACgkQ4I3qmxK2avZqwg/9FeJVFGtGBYx5aQIC -s+chEIx/bWM8oSxy8ruUkmHbK3tUkmzhnYgXdD+mCoN8MFWEGROEOyFip91Ay5v/ -MG1QEI1FgBiaTVODVFgDMTOfuIWq2A45m0QPK6JS0sTkxk9qeekUeyMLjcXaibLU -sGfEshGxszjWakZjDtEGbRYygWlPTX73faKCeqVxr9hF9OLBC+Ava75yhnm00GI9 -BT9udpkYxeFmFqDAgf/V84KdBV5cMWIAp2/FXl7GFA8phX9i3SfAO3TDSXfxQBQr -t39rjEQNyc58JG8QTxbgeFaAexykvvUDIjv/jhBSZivzcUAeRn4k+GscMmZEQ7Hw -tEoEgwKcHsqOwJ3CyCOEEWy9ZN0kVzxCsMLkbWZoQjeFLmIuVaNGkGWjBQJBH4kQ -WGRLd5d6PIDvHGh2rKIIS4SFL5nZHG1HQocKV001BFZeGt97nNKqDyqxqvp/7TEJ -Qx0waJOkGayvWT5NGZgGGrMMyBd8jffpzgR8YXI8VgTWJNWlLV4Ousl4p7H7iAQ3 -cODlECqzl376fzv3OAnK31DG3eWCsGairu+upxjugoImXm5QQpWEORYCR57H038i -v/gAFMIlZLnTS0Dgy0shQQb+Ygr7lqAPkKf3WGfbrt0gxlmXdo6oOoMs6T5RIFDM -/oigTX6Rla5W3cusBWEL1Gcp12e5Ag0EYsXZEAEQAMmqb9GgFe4PjEVexPh52361 -bJOSv82komNbXoWpGb45lbDFTZID1cTmi5q26AQkP+apkNcfnVTu1cQ4b/uUHj11 -AfSbn5XoYAKx4C/0TaZzSmWHuex6HkPc8eEr2ITyBZw90Z8RD5RnGFntjNsP+5EP -+wXzGTNnIXbP+arMcROv+1Ie7qAkqTXvMAAwFueG/jxJTA+JVvUEriubTcMAWjpy -5EQKF+NNiUtCdxWxwQvVdnQlUXDYWhux0IECpRXl9VKg/Arcx2vNYz1Q+TX9kPCZ -5orLfwyXg7Criw5GLHSCpqghLO8VdRuXulpDp58AIuM/+RouMkVhFTNBq69qyB08 -Kh7xM4C6PrmasLVK16fED5AWuW6VKTv4c0CxvJ5XjxpQuXsB+JHQ96Zk97wzDfcN -rBVTHXsMVQLSibExaU9PgHIZ0nw1Ipf1TA7R+t+iPxrgz9l8gtO2ddgEbpKNeQ37 -IPxx5GKsWIIi9hIJdIJZQVTLikRl6sQLQoFLwKLGu1UtOBDQKw4RyogKTmHcRFKt -ZdYUfwdhE1p9mUMxYZwloetBpG2qs7y1bzBbYNOaGECoRiSH5c1amFAnrLARXJAe -xRkgXNKwyBSIH8rizCahHQOFOvmyXyhPb2EuYPzbz8beCJXp/ALKzKSHpqcVuxHc -j/09NxZW7UtR4Jl758yZABEBAAGJAjYEGAEKACAWIQQVg7YBu1fMfNLfiofgjeqb -ErZq9gUCYsXZEAIbDAAKCRDgjeqbErZq9vLpD/4xRTqXfGfoiuJV4CH3EYvvKNFJ -Na1hSBXfmsCCoQ0kAyvFl+5gcmLvFTaII7Kt34lZsVWX0XCWgw6+ofGAZcekcXRR -swkOUMlmr56/94OrBAR0tG10KOyV3VrVY2n/4VYIymEdcMqQgwCSZ58XagOdYpBs -k/+limgo325G42LDec7VzR69UGG20QCJ9D3z2z+q5Ogg+tu9/QbzsEmzkmpc7huw -NFqIJ8TTSSramr+qYhkk9Eh7q0fSlmAuNKGmXvoQzKqAnGbluiFD8lf73G2Xz4Zv -zk+4AmU3Z70MIWrT+GgDaW5rT0FXrn9zOVafp2y2RqbFAaKSHucb42Qg1h3sgPqS -Kg/VVir7vECEpSB7ei37d1bTPe29Z6aGyFZeNuZ9J6MwY1fLsbbHwEY1RrGaiD8c -7gzBiVZl2/Nmuo6JgjnsoQDbs493MIud7dxMTb0ffsM28L0kwrD8EWHSIAY+eTBC -tNHS+Np24yd0mDNneYvR3+VIkjMWlkpoTqCC1vcCO7qB1sIBE/OEh5bNjjAlYPB2 -v5SKXC65iyoaMUO7IuTJsN+7jd7lKgqhl4OKnj48UQwXUzY9Yrk0OvttcZzXpls/ -OtzViiMG5c1Pacbuqz/PCc1D9AOudOyCYT0fSfsxrAB4GVmKr8kCVWp8BQDktLzk -Zib+Ntgdwfvr/xKO+A== -=RMmr ------END PGP PUBLIC KEY BLOCK----- diff --git a/scripts/keys/gijswijs.asc b/scripts/keys/gijswijs.asc deleted file mode 100644 index 485470bfd..000000000 --- a/scripts/keys/gijswijs.asc +++ /dev/null @@ -1,51 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- - -mQINBGXxd6kBEACX3rx9+yFbnStdm6Jax+7rfy6fizMSn709h1SXzqFGJOvFux4V -O3wO1xnnSSh0cP9AKIp6ntbppBhlq1J8VmRUu/hi73uyS7i6x9a45WT03vCEil2a -duJp6Aij5RltAxmBmJcoFa5bcUpj8ZDLxnQsF/A6V8HFQ1ijZfs5GLNVNw9sOeFT -CHK5NRFVaE2bBAj6npVJK/taGntSQzCAcD8RXHQTIVxHy62tCt5pSeQzEvJsAg5c -5uFntw/cutwSHBYpeiBeSxtUpAOl8aIEF2/xuxosymImXzyiKYxaD/LCoaw/mjjR -FAHHES33Mzkg1AS04bLuEQ0LxYEd5pcHrd41+DV2NMJ4TFISh5ecP4SHRjRjYKrO -BO+Lx0J1seg2BLbJoXa5pToJwl329yYZUayu24GENe6sNiYEDCs3cSURKKZo4olX -n0g3MdsE9GYUIpZQPQPNjWYr8ExcD5DxUahG1WBRhQDKaDDDsLjaWyX5l4cpBDtA -R4KTzLZXxeV1vxLsIY1RlF6T7MbGfJPFRluDUOUWVlypbvSYVD9PR5rpUDZBz13g -3ncqO4bdi7b9Yg4YFTfqqVz4RZAJKGSbmHedjBFiJNkNQg/5pz6LisFlpiCG3d0X -3Hdo6X/5tRXncdX2E5rZBf6gGYsI/Qr6PyF+CEohNOWYpjrBcGaxys57/QARAQAB -tClHaWpzIHZhbiBEYW0gPGdpanNAbGlnaHRuaW5nLmVuZ2luZWVyaW5nPokCOAQT -AQgALAUCZfF3qQkQAZpEhXc1/SACGwMFCR4TOAACGQEECwcJAwUVCAoCAwQWAAEC -AACYAA/+JAjIWpV1uCnE8/27ceec/8ZVoXqi6hjUny7itqnQa7de5Y4jkDDZTNBh -epHRSf0/mJmEtmqZtjON6HmBxex4LdvacqeWeVQPcohikr5ZkkuYL+QDAJutImjq -LqJA2u3nZN9u50rEHVcF2TD7X939I49WyCgdcPs2HKYODPkcBbn1Riw8Zz6BBsQW -mONXhPMGprrZulrKM/KAVwwvBUf0krnRTRi4X/n7MfXssDjHmv/LVeDmRR+6vPKv -ri+aiIFwd2mtZnx6mFS9DvMQNwOmabOxT8LPHB6+FKA9o4R/hl7wxKFNVYgtOpxv -14Sux5w7oBLRXzoxWmaT55hKEQimQEdYl/0y8TF2QNV4XEB7l9H4mTaul3T9z9fV -mYZOxXSOIywn9xu51K58KLDhyZLlP45nfgFFbgxDl3bLoneD4b8S5uTkUh9yCDZs -ufpAcGEL+17fuPX6k5KhuldMkXk++dot++NyqEfzwD9op2OBckHzhuAdKg67Y99m -ZKHNa62dI4S9ma6IHhYdVtrp4xEQtHeHZeALRwSgzNhEbU7zxcMiaBLNLKeaLjXa -usX5cKSeW7wPHl8g5+SQrDvW3ZyohMwZoO+RBE8hsier/d+wsX6NSHacbtbys/6y -Em8w4mouEa487wzAWRSO/brmr0txaUuWHQasMZ1DWTOFeP2nw8K5Ag0EZfF3qQEQ -AMPIbPjPkVcN3Dxs9yJ4B4v9VI9H2Rd/o93f1C9hhULqxX8Y2lOBXU6CPdAUTElC -baBvu16/w90hnwFjNdxP6n3mVJ4LDgF9Xo+MyvHDmJsHL3SwwNdY96UCino0l5Vd -v8kUKEWFttmvZRXDjlo8Tpu0b8pGnUATJGQasPI4YQ7qCrr48JGo81SVVf6IJol5 -3svU1z/fsDJ242EdXilWQHKNjnBJd7VZ+DtWaJjv16Zs905XKVxH7+zsyavofyLE -W1Sv0e78uSHyzXyLuIY/4OdT6LJJ9QuwKYfp4+S4SID/Def7JBMdSh0h9baG0Wzu -7hVZ6zhnJe3V0LcUcVhoRys58xF5Wpfu+8U6pz2HPN+CJsAD3XHaXBMJjaWIbZ/O -06OtjI8Y/5870YjjiPAUXo5FpVFBPTX0fft3LoBXIagS3ZOswk0j3TXwf4JgCGAW -PpIaBQ0C/ZjrOm79B020ArziCEyiiKOXre9vK4pGqz07CQfu/JxrdUdsKlnPEoPF -z2aYkNaeqa2ivONGvDbBPYIEk+LgbGfV1wtpA1FKHo3Hv66WD5qlOQX+bt9o2lHL -gZGgngjGxzq0p/VIy0hdLSKTvVfVioNofj7Vvz756k2q/d3saZSGaVzHEJ8lRaOo -hMVs8/zUuRKx6Ow2KLqKVHl3vt4lLoX74xN1zVXpUyYHABEBAAGJAjUEGAEIACkF -AmXxd6kJEAGaRIV3Nf0gAhsMBQkeEzgABAsHCQMFFQgKAgMEFgABAgAAquYP/3Xd -lJ908yJFzuBpVl05MBPGDzTiQNMGt8LDrSdmvqxgtj7+KaXDPbH3wW8GHI3GaweQ -bhHuMrty2vX5CDuK/hdvwRhZ1WBaGryPtz5rsODhMvqiiGMGBKfSYRdc2thK1L4e -T4UQa1Kbd5odszwA0Og1y483jjduqq7otJ1MsfzCOhSc6vEzZzaKjHJJPfhIt62U -4EDhZGGyZ10YiFNsdWc2twJu4ma8a2TxTQLZIPlH61BHuHfZOjf4s9wzoJBjOjPO -LmIfVfezgJI6rSM30Gr3lnchTd4sg25GUNxEyMrKapo/ztABe+coOzfqy8Kg0sQV -6s3QvTAOHrEh8oClWX+dY5j00j+pfkdi6gfPT7VEZK4Hqko8UzUy9armBPmdHfcv -Rx6HoUIZEGfYFgKOdSBoE/Q+4f7hiD5xpHx7FNMQTzI4x9zsMp+8CO4+YZHVSSBA -r5j4x3Drf86+ZuVmnho4qpHPB2jyBQ6eulYESJE/GWEzuwox7zF1Qe8PPSCkwTux -UeGCcuxiTGfsCy0dWy+4/1BzF6DkuCxVwPqcQ4r4wxRHWs3Qxii3Cw9wep4Txl1+ -DQD+EtphnXs6LSk3fxI0E50aBcYacDzJ6+NIBlNWkYJ86jAdY4b+x5/qSYWr0NIJ -ru+oOaGjswGJU1DhmQjt5D7KvgEnE6e9iYMJJBAG -=r0rf ------END PGP PUBLIC KEY BLOCK----- \ No newline at end of file diff --git a/scripts/tag-release.sh b/scripts/tag-release.sh deleted file mode 100755 index 1f1db8f18..000000000 --- a/scripts/tag-release.sh +++ /dev/null @@ -1,153 +0,0 @@ -#!/bin/bash -# -# tag-release.sh creates a signed annotated git tag for an lnd release after -# verifying (a) HEAD is in sync with the upstream lightningnetwork/lnd -# branch, and (b) build/version.go at HEAD matches the requested tag. Guards -# against tagging a commit that has not been merged upstream yet, or one -# whose embedded version disagrees with the tag. - -set -euo pipefail - -VERSION_FILE="build/version.go" - -# Match the canonical upstream URL across https / git@ / ssh:// forms, with or -# without a `.git` suffix. We identify the remote by URL because `origin` is -# conventionally the fork in a `gh repo fork` setup. The URL is lower-cased -# before matching (see below), so this pattern stays lower-case: GitHub treats -# the org/repo as case-insensitive, and `origin` is often the mixed-case -# `LightningNetwork/lnd`. -UPSTREAM_URL_REGEX='[:/]lightningnetwork/lnd(\.git)?$' - -usage() { - cat >&2 < [--branch ] - - Release tag, e.g. v0.21.0-beta.rc3. Must match the - constants defined in ${VERSION_FILE} at HEAD. - --branch Upstream branch to verify HEAD against. Defaults to - the currently checked-out branch (typically a release - branch such as v0.21.x-branch). -EOF - exit 1 -} - -TAG="" -UPSTREAM_BRANCH="" -while [[ $# -gt 0 ]]; do - case "$1" in - -h|--help) usage ;; - --branch) [[ $# -ge 2 ]] || usage; UPSTREAM_BRANCH="$2"; shift 2 ;; - --branch=*) UPSTREAM_BRANCH="${1#--branch=}"; shift ;; - -*) echo "Unknown flag: $1" >&2; usage ;; - *) [[ -z "${TAG}" ]] || usage; TAG="$1"; shift ;; - esac -done -[[ -n "${TAG}" ]] || usage - -cd "$(git rev-parse --show-toplevel)" - -if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then - echo "Error: tag ${TAG} already exists locally." >&2 - exit 1 -fi - -if [[ -z "${UPSTREAM_BRANCH}" ]]; then - UPSTREAM_BRANCH="$(git symbolic-ref --quiet --short HEAD || true)" - [[ -n "${UPSTREAM_BRANCH}" ]] \ - || { echo "Error: detached HEAD; pass --branch ." >&2; exit 1; } -fi - -# Discover the upstream remote by URL (see UPSTREAM_URL_REGEX). -UPSTREAM_REMOTES=() -while IFS= read -r line; do - UPSTREAM_REMOTES+=("$line") -done < <(git remote -v | awk -v re="${UPSTREAM_URL_REGEX}" \ - '$3 == "(fetch)" && tolower($2) ~ re { print $1 }' | sort -u) - -case "${#UPSTREAM_REMOTES[@]}" in - 0) echo "Error: no git remote points at lightningnetwork/lnd. Add one with" \ - "'git remote add upstream" \ - "https://github.com/lightningnetwork/lnd.git'." >&2 - exit 1 ;; - 1) UPSTREAM_REMOTE="${UPSTREAM_REMOTES[0]}" ;; - *) echo "Error: multiple remotes match lightningnetwork/lnd:" >&2 - printf ' %s\n' "${UPSTREAM_REMOTES[@]}" >&2 - exit 1 ;; -esac - -# Fetch first so every later check runs against confirmed-current upstream -# state. Without this, a stale local HEAD could pass the version-match check -# while still being out of sync with what's on the release branch. -echo "Fetching ${UPSTREAM_REMOTE} ${UPSTREAM_BRANCH}..." -git fetch --quiet "${UPSTREAM_REMOTE}" "${UPSTREAM_BRANCH}" - -# Catch the race where another maintainer has already published this tag. -if git ls-remote --exit-code --tags "${UPSTREAM_REMOTE}" \ - "refs/tags/${TAG}" >/dev/null 2>&1; then - echo "Error: tag ${TAG} already exists on ${UPSTREAM_REMOTE}." >&2 - exit 1 -fi - -# Compare against FETCH_HEAD rather than refs/remotes//: -# FETCH_HEAD is always written by `git fetch `, while the -# remote-tracking ref depends on the user's refspec configuration. -HEAD_SHA="$(git rev-parse HEAD)" -UP_SHA="$(git rev-parse FETCH_HEAD)" -if [[ "${HEAD_SHA}" != "${UP_SHA}" ]]; then - AHEAD="$(git rev-list --count FETCH_HEAD..HEAD)" - BEHIND="$(git rev-list --count HEAD..FETCH_HEAD)" - cat >&2 </dev/null | awk ' - /^[[:space:]]*AppMajor[[:space:]]+uint[[:space:]]*=/ { sub(/.*=[[:space:]]*/,""); sub(/[^0-9].*/,""); print } - /^[[:space:]]*AppMinor[[:space:]]+uint[[:space:]]*=/ { sub(/.*=[[:space:]]*/,""); sub(/[^0-9].*/,""); print } - /^[[:space:]]*AppPatch[[:space:]]+uint[[:space:]]*=/ { sub(/.*=[[:space:]]*/,""); sub(/[^0-9].*/,""); print } - /^[[:space:]]*AppPreRelease[[:space:]]*=/ { match($0,/"[^"]*"/); print substr($0,RSTART+1,RLENGTH-2) } - ' -) - -if [[ -z "${M}" || -z "${m}" || -z "${p}" ]]; then - echo "Error: failed to parse version constants from HEAD:${VERSION_FILE}." \ - >&2 - exit 1 -fi - -# Go treats `01` as an octal literal but %d prints it as decimal; force -# base-10 here so we match build.Version()'s output. -EXPECTED="v$((10#$M)).$((10#$m)).$((10#$p))" -[[ -n "${pre}" ]] && EXPECTED="${EXPECTED}-${pre}" - -echo "Requested: ${TAG}" -echo "Expected: ${EXPECTED} (from HEAD:${VERSION_FILE})" - -if [[ "${TAG}" != "${EXPECTED}" ]]; then - cat >&2 < "$STATUS_FILE" 2>&1 || { - echo "ERROR: Invalid signature $signature from user $USERNAME!" - echo " GPG output:" - cat "$STATUS_FILE" | sed 's/^/ /' - exit 1 - } + > "$STATUS_FILE" 2>&1 || { echo "ERROR: Invalid signature!"; exit 1; } echo "Verifying $signature of user $USERNAME against key ring $KEYRING" if grep -q "Good signature" "$STATUS_FILE"; then @@ -282,7 +262,7 @@ shift verify_version "$VERSION" # Make sure we have all tools needed for the verification. -check_command wget +check_command curl check_command jq check_command gpg diff --git a/server.go b/server.go index a0312bb01..55c7639fc 100644 --- a/server.go +++ b/server.go @@ -7,7 +7,6 @@ import ( "encoding/hex" "errors" "fmt" - "image/color" "math/big" prand "math/rand" "net" @@ -19,15 +18,14 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/connmgr" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btclog/v2" sphinx "github.com/lightningnetwork/lightning-onion" - "github.com/lightningnetwork/lnd/actor" "github.com/lightningnetwork/lnd/aliasmgr" "github.com/lightningnetwork/lnd/autopilot" "github.com/lightningnetwork/lnd/brontide" @@ -38,7 +36,6 @@ import ( "github.com/lightningnetwork/lnd/chanfitness" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channelnotifier" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/cluster" "github.com/lightningnetwork/lnd/contractcourt" @@ -63,13 +60,11 @@ import ( "github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" - chcl "github.com/lightningnetwork/lnd/lnwallet/chancloser" "github.com/lightningnetwork/lnd/lnwallet/chanfunding" "github.com/lightningnetwork/lnd/lnwallet/rpcwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/nat" "github.com/lightningnetwork/lnd/netann" - "github.com/lightningnetwork/lnd/onionmessage" paymentsdb "github.com/lightningnetwork/lnd/payments/db" "github.com/lightningnetwork/lnd/peer" "github.com/lightningnetwork/lnd/peernotifier" @@ -142,6 +137,12 @@ var ( // TODO(roasbeef): add command line param to modify. MaxFundingAmount = funding.MaxBtcFundingAmount + // EndorsementExperimentEnd is the time after which nodes should stop + // propagating experimental endorsement signals. + // + // Per blip04: January 1, 2026 12:00:00 AM UTC in unix seconds. + EndorsementExperimentEnd = time.Unix(1767225600, 0) + // ErrGossiperBan is one of the errors that can be returned when we // attempt to finalize a connection to a remote peer. ErrGossiperBan = errors.New("gossiper has banned remote's key") @@ -324,10 +325,8 @@ type server struct { fundingMgr *funding.Manager graphDB *graphdb.ChannelGraph - v1Graph *graphdb.VersionedGraph - chanStateDB chanstate.Store - linkNodeDB *channeldb.LinkNodeDB + chanStateDB *channeldb.ChannelStateDB addrSource channeldb.AddrSource @@ -380,9 +379,7 @@ type server struct { chainArb *contractcourt.ChainArbitrator - sphinxPayment *hop.OnionProcessor - - sphinxOnionMsg *sphinx.Router + sphinx *hop.OnionProcessor towerClientMgr *wtclient.Manager @@ -425,34 +422,6 @@ type server struct { customMessageServer *subscribe.Server - onionMessageServer *subscribe.Server - - // actorSystem is the actor system tasked with handling actors that are - // created for this server. - actorSystem *actor.ActorSystem - - // onionActorFactory is a factory function that spawns per-peer onion - // message actors. It captures shared dependencies and is passed to - // each peer connection. - onionActorFactory onionmessage.OnionActorFactory - - // defaultOnionActorOpts holds the default ActorOptions (backpressure - // mailbox with RED) applied to every onion peer actor. These are - // computed once during server start and returned by the per-peer - // OnionActorOpts callback. - defaultOnionActorOpts []actor.ActorOption[ - *onionmessage.Request, *onionmessage.Response, - ] - - // onionLimiter is the combined per-peer + global onion message - // ingress limiter. It hides the split between the two underlying - // buckets behind a single interface so peer.Config only needs to - // carry one field and brontide.readHandler only needs one call - // per incoming onion message. Nil means onion message rate - // limiting is disabled (e.g. when onion messaging itself is - // turned off). - onionLimiter onionmessage.IngressLimiter - // txPublisher is a publisher with fee-bumping capability. txPublisher *sweep.TxPublisher @@ -514,10 +483,6 @@ func (s *server) updatePersistentPeerAddrs() error { len(update.Addresses)) for _, addr := range update.Addresses { - if isV2OnionAddr(addr) { - continue - } - addrs = append(addrs, &lnwire.NetAddress{ IdentityKey: update.IdentityKey, @@ -588,15 +553,6 @@ func parseAddr(address string, netCfg tor.Net) (net.Addr, error) { } if tor.IsOnionHost(host) { - // Reject v2 at the operator-input boundary; the wire codec - // still round-trips v2 from peer-signed announcements. - if len(host) == tor.V2Len { - return nil, fmt.Errorf("tor v2 onion services were "+ - "retired in October 2021 and are no longer "+ - "supported; use a v3 .onion address "+ - "instead: %s", host) - } - return &tor.OnionAddr{OnionService: host, Port: port}, nil } @@ -608,42 +564,6 @@ func parseAddr(address string, netCfg tor.Net) (net.Addr, error) { return netCfg.ResolveTCPAddr("tcp", hostPort) } -// isV2OnionAddr reports whether addr is a Tor v2 .onion address. Tor stopped -// serving v2 onion services in October 2021, so callers skip these on dial -// paths. Storage and gossip re-broadcast still preserve v2 byte-for-byte to -// keep peer-signed NodeAnnouncement signatures verifiable. -// -// TODO: move this helper into the `tor` module (as `tor.IsV2Onion`) and -// remove this copy along with the duplicate in -// watchtower/wtclient/interface.go once a new `tor` module version is cut -// and the dependency is bumped. -func isV2OnionAddr(addr net.Addr) bool { - onion, ok := addr.(*tor.OnionAddr) - if !ok { - return false - } - - return len(onion.OnionService) == tor.V2Len -} - -// withoutV2Onion returns addrs with any Tor v2 .onion entries removed. See -// isV2OnionAddr for the rationale. -// -// TODO: move this helper into the `tor` module and remove this copy along -// with the duplicate in watchtower/wtclient/interface.go once a new `tor` -// module version is cut and the dependency is bumped. -func withoutV2Onion(addrs []net.Addr) []net.Addr { - filtered := make([]net.Addr, 0, len(addrs)) - for _, addr := range addrs { - if isV2OnionAddr(addr) { - continue - } - filtered = append(filtered, addr) - } - - return filtered -} - // noiseDial is a factory function which creates a connmgr compliant dialing // function by returning a closure which includes the server's identity key. func noiseDial(idKey keychain.SingleKeyECDH, @@ -687,12 +607,6 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, ) sphinxRouter := sphinx.NewRouter(nodeKeyECDH, replayLog) - // Initialize the onion message sphinx router. This router doesn't need - // replay protection. - sphinxOnionMsg := sphinx.NewRouter( - nodeKeyECDH, sphinx.NewNoOpReplayLog(), - ) - writeBufferPool := pool.NewWriteBuffer( pool.DefaultWriteBufferGCInterval, pool.DefaultWriteBufferExpiryInterval, @@ -721,41 +635,24 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, "in a standalone lnd build") } - // If taproot channels are enabled, we also enable the RBF cooperative - // close protocol, as it is required for taproot channel - // interoperability. - // - // Exception: when taproot-overlay channels are enabled we do NOT - // auto-enable RBF, because the RBF coop close state machine does not - // yet thread through the AuxCloser hook that overlay channels rely on - // to build the aux-aware close transaction. Forcing RBF on for a - // node that holds overlay channels would silently break their coop - // closes. - if cfg.ProtocolOptions.TaprootChans && - !cfg.ProtocolOptions.TaprootOverlayChans { - - cfg.ProtocolOptions.RbfCoopClose = true - } - //nolint:ll featureMgr, err := feature.NewManager(feature.Config{ - NoTLVOnion: cfg.ProtocolOptions.LegacyOnion(), - NoStaticRemoteKey: cfg.ProtocolOptions.NoStaticRemoteKey(), - NoAnchors: cfg.ProtocolOptions.NoAnchorCommitments(), - NoWumbo: !cfg.ProtocolOptions.Wumbo(), - NoScriptEnforcementLease: cfg.ProtocolOptions.NoScriptEnforcementLease(), - NoKeysend: !cfg.AcceptKeySend, - NoOptionScidAlias: !cfg.ProtocolOptions.ScidAlias(), - NoZeroConf: !cfg.ProtocolOptions.ZeroConf(), - NoAnySegwit: cfg.ProtocolOptions.NoAnySegwit(), - CustomFeatures: cfg.ProtocolOptions.CustomFeatures(), - NoTaprootChans: !cfg.ProtocolOptions.TaprootChans, - NoTaprootOverlay: !cfg.ProtocolOptions.TaprootOverlayChans, - NoRouteBlinding: cfg.ProtocolOptions.NoRouteBlinding(), - NoOnionMessages: cfg.ProtocolOptions.NoOnionMessages(), - NoExperimentalAccountability: cfg.ProtocolOptions.NoExpAccountability(), - NoQuiescence: cfg.ProtocolOptions.NoQuiescence(), - NoRbfCoopClose: !cfg.ProtocolOptions.RbfCoopClose, + NoTLVOnion: cfg.ProtocolOptions.LegacyOnion(), + NoStaticRemoteKey: cfg.ProtocolOptions.NoStaticRemoteKey(), + NoAnchors: cfg.ProtocolOptions.NoAnchorCommitments(), + NoWumbo: !cfg.ProtocolOptions.Wumbo(), + NoScriptEnforcementLease: cfg.ProtocolOptions.NoScriptEnforcementLease(), + NoKeysend: !cfg.AcceptKeySend, + NoOptionScidAlias: !cfg.ProtocolOptions.ScidAlias(), + NoZeroConf: !cfg.ProtocolOptions.ZeroConf(), + NoAnySegwit: cfg.ProtocolOptions.NoAnySegwit(), + CustomFeatures: cfg.ProtocolOptions.CustomFeatures(), + NoTaprootChans: !cfg.ProtocolOptions.TaprootChans, + NoTaprootOverlay: !cfg.ProtocolOptions.TaprootOverlayChans, + NoRouteBlinding: cfg.ProtocolOptions.NoRouteBlinding(), + NoExperimentalEndorsement: cfg.ProtocolOptions.NoExperimentalEndorsement(), + NoQuiescence: cfg.ProtocolOptions.NoQuiescence(), + NoRbfCoopClose: !cfg.ProtocolOptions.RbfCoopClose, }) if err != nil { return nil, err @@ -774,20 +671,13 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, HtlcInterceptor: invoiceHtlcModifier, } - v1Graph := graphdb.NewVersionedGraph( - dbs.GraphDB, lnwire.GossipVersion1, - ) - - addrSource := channeldb.NewMultiAddrSource(dbs.ChanStateDB, v1Graph) - chanStateDB := dbs.ChanStateDB.ChannelStateDB() + addrSource := channeldb.NewMultiAddrSource(dbs.ChanStateDB, dbs.GraphDB) s := &server{ cfg: cfg, implCfg: implCfg, graphDB: dbs.GraphDB, - v1Graph: v1Graph, - chanStateDB: chanStateDB, - linkNodeDB: chanStateDB.LinkNodeDB(), + chanStateDB: dbs.ChanStateDB.ChannelStateDB(), addrSource: addrSource, miscDB: dbs.ChanStateDB, invoicesDB: dbs.InvoiceDB, @@ -801,7 +691,9 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, blockbeatDispatcher: chainio.NewBlockbeatDispatcher( cc.ChainNotifier, ), - channelNotifier: channelnotifier.New(chanStateDB), + channelNotifier: channelnotifier.New( + dbs.ChanStateDB.ChannelStateDB(), + ), identityECDH: nodeKeyECDH, identityKeyLoc: nodeKeyDesc.KeyLocator, @@ -811,8 +703,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, // TODO(roasbeef): derive proper onion key based on rotation // schedule - sphinxPayment: hop.NewOnionProcessor(sphinxRouter), - sphinxOnionMsg: sphinxOnionMsg, + sphinx: hop.NewOnionProcessor(sphinxRouter), torController: torController, @@ -836,10 +727,6 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, customMessageServer: subscribe.NewServer(), - onionMessageServer: subscribe.NewServer(), - - actorSystem: actor.NewActorSystem(), - tlsManager: tlsManager, featureMgr: featureMgr, @@ -901,7 +788,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, }, FwdingLog: dbs.ChanStateDB.ForwardingLog(), SwitchPackager: channeldb.NewSwitchPackager(), - ExtractErrorEncrypter: s.sphinxPayment.ExtractErrorEncrypter, + ExtractErrorEncrypter: s.sphinx.ExtractErrorEncrypter, FetchLastChannelUpdate: s.fetchLastChanUpdate(), Notifier: s.cc.ChainNotifier, HtlcNotifier: s.htlcNotifier, @@ -1096,12 +983,12 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, MinProbability: routingConfig.MinRouteProbability, } - sourceNode, err := s.v1Graph.SourceNode(ctx) + sourceNode, err := dbs.GraphDB.SourceNode(ctx) if err != nil { return nil, fmt.Errorf("error getting source node: %w", err) } paymentSessionSource := &routing.SessionSource{ - GraphSessionFactory: s.v1Graph, + GraphSessionFactory: dbs.GraphDB, SourceNode: sourceNode, MissionControl: s.defaultMC, GetLink: s.htlcSwitch.GetLinkByShortID, @@ -1131,29 +1018,26 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, } s.chanRouter, err = routing.New(routing.Config{ - SelfNode: nodePubKey, - RoutingGraph: s.v1Graph, - Chain: cc.ChainIO, - Payer: s.htlcSwitch, - Control: s.controlTower, - MissionControl: s.defaultMC, - SessionSource: paymentSessionSource, - GetLink: s.htlcSwitch.GetLinkByShortID, - NextPaymentID: sequencer.NextID, - PathFindingConfig: pathFindingConfig, - Clock: clock.NewDefaultClock(), - ApplyChannelUpdate: s.graphBuilder.ApplyChannelUpdate, - ClosedSCIDs: s.fetchClosedChannelSCIDs(), - TrafficShaper: implCfg.TrafficShaper, - KeepFailedPaymentAttempts: cfg.KeepFailedPaymentAttempts, + SelfNode: nodePubKey, + RoutingGraph: dbs.GraphDB, + Chain: cc.ChainIO, + Payer: s.htlcSwitch, + Control: s.controlTower, + MissionControl: s.defaultMC, + SessionSource: paymentSessionSource, + GetLink: s.htlcSwitch.GetLinkByShortID, + NextPaymentID: sequencer.NextID, + PathFindingConfig: pathFindingConfig, + Clock: clock.NewDefaultClock(), + ApplyChannelUpdate: s.graphBuilder.ApplyChannelUpdate, + ClosedSCIDs: s.fetchClosedChannelSCIDs(), + TrafficShaper: implCfg.TrafficShaper, }) if err != nil { return nil, fmt.Errorf("can't create router: %w", err) } - chanSeries := discovery.NewChanSeries( - graphdb.NewVersionedGraph(s.graphDB, lnwire.GossipVersion1), - ) + chanSeries := discovery.NewChanSeries(s.graphDB) gossipMessageStore, err := discovery.NewMessageStore(dbs.ChanStateDB) if err != nil { return nil, err @@ -1242,8 +1126,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, *models.ChannelEdgePolicy) error, reset func()) error { - return s.v1Graph.ForEachNodeChannel( - ctx, selfVertex, + return s.graphDB.ForEachNodeChannel(ctx, selfVertex, func(c *models.ChannelEdgeInfo, e *models.ChannelEdgePolicy, _ *models.ChannelEdgePolicy) error { @@ -1393,13 +1276,11 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, outHtlcRes fn.Option[lnwallet.OutgoingHtlcResolution], inHtlcRes fn.Option[lnwallet.IncomingHtlcResolution], broadcastHeight uint32, - deadlineHeight fn.Option[int32], - opts ...contractcourt.IncubateOption) error { + deadlineHeight fn.Option[int32]) error { return s.utxoNursery.IncubateOutputs( chanPoint, outHtlcRes, inHtlcRes, broadcastHeight, deadlineHeight, - opts..., ) }, PreimageDB: s.witnessBeacon, @@ -1461,9 +1342,8 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, Sweeper: s.sweeper, Registry: s.invoices, NotifyClosedChannel: s.channelNotifier.NotifyClosedChannelEvent, - NotifyEarlyClosedChannel: s.channelNotifier.NotifyEarlyClosedChannelEvent, NotifyFullyResolvedChannel: s.channelNotifier.NotifyFullyResolvedChannelEvent, - OnionProcessor: s.sphinxPayment, + OnionProcessor: s.sphinx, PaymentsExpirationGracePeriod: cfg.PaymentsExpirationGracePeriod, IsForwardedHTLC: s.htlcSwitch.IsForwardedHTLC, Clock: clock.NewDefaultClock(), @@ -1490,12 +1370,6 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, AuxLeafStore: implCfg.AuxLeafStore, AuxSigner: implCfg.AuxSigner, AuxResolver: implCfg.AuxContractResolver, - AuxCloser: fn.MapOption( - func(c chcl.AuxChanCloser) contractcourt.AuxChanCloser { - return c - }, - )(implCfg.AuxChanCloser), - ChannelCloseConfs: s.cfg.Dev.ChannelCloseConfs(), }, dbs.ChanStateDB) // Select the configuration and funding parameters for Bitcoin. @@ -1514,7 +1388,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, *models.ChannelEdgePolicy, error) { info, e1, e2, err := s.graphDB.FetchChannelEdgesByID( - context.TODO(), scid.ToUint64(), + scid.ToUint64(), ) if errors.Is(err, graphdb.ErrEdgeNotFound) { // This is unlikely but there is a slim chance of this @@ -1542,8 +1416,8 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, return nil, fmt.Errorf("we don't have an edge") } - err = s.v1Graph.DeleteChannelEdges( - context.TODO(), false, false, scid.ToUint64(), + err = s.graphDB.DeleteChannelEdges( + false, false, scid.ToUint64(), ) return ourPolicy, err } @@ -1572,15 +1446,6 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, devCfg, reservationTimeout, zombieSweeperInterval) } - // Attempt to parse the provided upfront-shutdown address (if any). - script, err := chcl.ParseUpfrontShutdownAddress( - cfg.UpfrontShutdownAddr, cfg.ActiveNetParams.Params, - ) - if err != nil { - return nil, fmt.Errorf("error parsing upfront shutdown: %w", - err) - } - //nolint:ll s.fundingMgr, err = funding.NewFundingManager(funding.Config{ Dev: devCfg, @@ -1609,6 +1474,16 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, DefaultMinHtlcIn: cc.MinHtlcIn, NumRequiredConfs: func(chanAmt btcutil.Amount, pushAmt lnwire.MilliSatoshi) uint16 { + // For large channels we increase the number + // of confirmations we require for the + // channel to be considered open. As it is + // always the responder that gets to choose + // value, the pushAmt is value being pushed + // to us. This means we have more to lose + // in the case this gets re-orged out, and + // we will require more confirmations before + // we consider it open. + // In case the user has explicitly specified // a default value for the number of // confirmations, we use it. @@ -1617,17 +1492,29 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, return defaultConf } - // Otherwise, scale the number of confirmations based on - // the channel amount and push amount. For large - // channels we increase the number of - // confirmations we require for the channel to be - // considered open. As it is always the - // responder that gets to choose value, the - // pushAmt is value being pushed to us. This - // means we have more to lose in the case this - // gets re-orged out, and we will require more - // confirmations before we consider it open. - return lnwallet.FundingConfsForAmounts(chanAmt, pushAmt) + minConf := uint64(3) + maxConf := uint64(6) + + // If this is a wumbo channel, then we'll require the + // max amount of confirmations. + if chanAmt > MaxFundingAmount { + return uint16(maxConf) + } + + // If not we return a value scaled linearly + // between 3 and 6, depending on channel size. + // TODO(halseth): Use 1 as minimum? + maxChannelSize := uint64( + lnwire.NewMSatFromSatoshis(MaxFundingAmount)) + stake := lnwire.NewMSatFromSatoshis(chanAmt) + pushAmt + conf := maxConf * uint64(stake) / maxChannelSize + if conf < minConf { + conf = minConf + } + if conf > maxConf { + conf = maxConf + } + return uint16(conf) }, RequiredRemoteDelay: func(chanAmt btcutil.Amount) uint16 { // We scale the remote CSV delay (the time the @@ -1661,7 +1548,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, } return delay }, - WatchNewChannel: func(channel *chanstate.OpenChannel, + WatchNewChannel: func(channel *channeldb.OpenChannel, peerKey *btcec.PublicKey) error { // First, we'll mark this new peer as a persistent peer @@ -1737,7 +1624,6 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, AuxSigner: implCfg.AuxSigner, AuxResolver: implCfg.AuxContractResolver, AuxChannelNegotiator: implCfg.AuxChannelNegotiator, - ShutdownScript: peer.ChooseAddr(script), }) if err != nil { return nil, err @@ -1778,10 +1664,6 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, return s.peerNotifier.SubscribePeerEvents() }, GetOpenChannels: s.chanStateDB.FetchAllOpenChannels, - IsPeerOnline: func(peer route.Vertex) bool { - _, err := s.FindPeerByPubStr(string(peer[:])) - return err == nil - }, Clock: clock.NewDefaultClock(), ReadFlapCount: s.miscDB.ReadFlapCount, WriteFlapCount: s.miscDB.WriteFlapCounts, @@ -1822,7 +1704,9 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, commitHeight uint64) (*lnwallet.BreachRetribution, channeldb.ChannelType, error) { - channel, err := s.chanStateDB.FetchChannelByID(chanID) + channel, err := s.chanStateDB.FetchChannelByID( + nil, chanID, + ) if err != nil { return nil, 0, err } @@ -1853,13 +1737,6 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, blob.FlagTaprootChannel, ) - // Copy the policy for legacy channels and set the blob flags - // signalling support for production taproot channels. - taprootFinalPolicy := policy - taprootFinalPolicy.TxPolicy.BlobType |= blob.Type( - blob.FlagTaprootChannel | blob.FlagTaprootFinalChannel, - ) - s.towerClientMgr, err = wtclient.NewManager(&wtclient.Config{ FetchClosedChannel: fetchClosedChannel, BuildBreachRetribution: buildBreachRetribution, @@ -1890,7 +1767,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, MinBackoff: 10 * time.Second, MaxBackoff: 5 * time.Minute, MaxTasksInMemQueue: cfg.WtClient.MaxTasksInMemQueue, - }, policy, anchorPolicy, taprootPolicy, taprootFinalPolicy) + }, policy, anchorPolicy, taprootPolicy) if err != nil { return nil, err } @@ -2278,12 +2155,6 @@ func (s *server) Start(ctx context.Context) error { return } - cleanup = cleanup.add(s.onionMessageServer.Stop) - if err := s.onionMessageServer.Start(); err != nil { - startErr = err - return - } - if s.hostAnn != nil { cleanup = cleanup.add(s.hostAnn.Stop) if err := s.hostAnn.Start(); err != nil { @@ -2457,59 +2328,12 @@ func (s *server) Start(ctx context.Context) error { return } - cleanup = cleanup.add(s.sphinxPayment.Stop) - if err := s.sphinxPayment.Start(); err != nil { + cleanup = cleanup.add(s.sphinx.Stop) + if err := s.sphinx.Start(); err != nil { startErr = err return } - cleanup = cleanup.add(func() error { - s.sphinxOnionMsg.Stop() - return nil - }) - if err := s.sphinxOnionMsg.Start(); err != nil { - startErr = err - return - } - - // Create the onion message actor factory that will be used to - // spawn per-peer actors for handling onion messages. Skip if - // onion messaging is disabled via config. - if !s.cfg.ProtocolOptions.NoOnionMessages() { - resolver := onionmessage.NewGraphNodeResolver( - s.graphDB, s.identityECDH.PubKey(), - ) - s.onionActorFactory = onionmessage.NewOnionActorFactory( - s.sphinxOnionMsg, resolver, s, - s.onionMessageServer, - ) - - s.defaultOnionActorOpts = onionmessage. - DefaultOnionActorOpts() - - // Build the global and per-peer onion message rate - // limiters from the configured values, then compose - // them behind a single IngressLimiter so the peer - // package only needs to carry one field. A zero - // kbps or a zero burst-bytes disables the - // corresponding bucket; rates are expressed in - // decimal kilobits per second and bursts in bytes - // so operators can reason about onion message - // ingress in terms of bandwidth rather than raw - // message counts. - onionPeerLim := onionmessage.NewPeerRateLimiter( - s.cfg.ProtocolOptions.OnionMsgPeerKbps, - s.cfg.ProtocolOptions.OnionMsgPeerBurstBytes, - ) - onionGlobalLim := onionmessage.NewGlobalLimiter( - s.cfg.ProtocolOptions.OnionMsgGlobalKbps, - s.cfg.ProtocolOptions.OnionMsgGlobalBurstBytes, - ) - s.onionLimiter = onionmessage.NewIngressLimiter( - onionPeerLim, onionGlobalLim, - ) - } - cleanup = cleanup.add(s.chanStatusMgr.Stop) if err := s.chanStatusMgr.Start(); err != nil { startErr = err @@ -2777,9 +2601,6 @@ func (s *server) Stop() error { // Stop dispatching blocks to other systems immediately. s.blockbeatDispatcher.Stop() - // Shutdown the onion router for onion messaging. - s.sphinxOnionMsg.Stop() - // Shutdown the wallet, funding manager, and the rpc server. if err := s.chanStatusMgr.Stop(); err != nil { srvrLog.Warnf("failed to stop chanStatusMgr: %v", err) @@ -2787,7 +2608,7 @@ func (s *server) Stop() error { if err := s.htlcSwitch.Stop(); err != nil { srvrLog.Warnf("failed to stop htlcSwitch: %v", err) } - if err := s.sphinxPayment.Stop(); err != nil { + if err := s.sphinx.Stop(); err != nil { srvrLog.Warnf("failed to stop sphinx: %v", err) } if err := s.invoices.Stop(); err != nil { @@ -2919,12 +2740,6 @@ func (s *server) Stop() error { s.sigPool.Stop() s.writePool.Stop() s.readPool.Stop() - - // Shut down the actor system last so any in-flight actor work - // triggered by the subsystems above has a chance to complete. - if err := s.actorSystem.Shutdown(); err != nil { - srvrLog.Warnf("failed to stop actor system: %v", err) - } }) return nil @@ -3124,7 +2939,7 @@ func initNetworkBootstrappers(s *server) ([]discovery.NetworkPeerBootstrapper, e // First, we'll create an instance of the ChannelGraphBootstrapper as // this can be used by default if we've already partially seeded the // network. - chanGraph := autopilot.ChannelGraphFromDatabase(s.v1Graph) + chanGraph := autopilot.ChannelGraphFromDatabase(s.graphDB) graphBootstrapper, err := discovery.NewGraphBootstrapper( chanGraph, s.cfg.Bitcoin.IsLocalNetwork(), ) @@ -3439,8 +3254,8 @@ func (s *server) initialPeerBootstrap(ctx context.Context, } } -// createNewHiddenService automatically sets up a v3 onion service in order to -// listen for inbound connections over Tor. +// createNewHiddenService automatically sets up a v2 or v3 onion service in +// order to listen for inbound connections over Tor. func (s *server) createNewHiddenService(ctx context.Context) error { // Determine the different ports the server is listening on. The onion // service's virtual port will map to these ports and one will be picked @@ -3468,6 +3283,13 @@ func (s *server) createNewHiddenService(ctx context.Context) error { ), } + switch { + case s.cfg.Tor.V2: + onionCfg.Type = tor.V2 + case s.cfg.Tor.V3: + onionCfg.Type = tor.V3 + } + addr, err := s.torController.AddOnion(onionCfg) if err != nil { return err @@ -3487,17 +3309,18 @@ func (s *server) createNewHiddenService(ctx context.Context) error { // Finally, we'll update the on-disk version of our announcement so it // will eventually propagate to nodes in the network. - selfNode := models.NewV1Node( - route.NewVertex(s.identityECDH.PubKey()), &models.NodeV1Fields{ - Addresses: newNodeAnn.Addresses, - Features: newNodeAnn.Features, - AuthSigBytes: newNodeAnn.Signature.ToSignatureBytes(), - Color: newNodeAnn.RGBColor, - Alias: newNodeAnn.Alias.String(), - LastUpdate: time.Unix(int64(newNodeAnn.Timestamp), 0), - }, - ) - + selfNode := &models.Node{ + HaveNodeAnnouncement: true, + LastUpdate: time.Unix(int64(newNodeAnn.Timestamp), 0), + Addresses: newNodeAnn.Addresses, + Alias: newNodeAnn.Alias.String(), + Features: lnwire.NewFeatureVector( + newNodeAnn.Features, lnwire.Features, + ), + Color: newNodeAnn.RGBColor, + AuthSigBytes: newNodeAnn.Signature.ToSignatureBytes(), + } + copy(selfNode.PubKeyBytes[:], s.identityECDH.PubKey().SerializeCompressed()) if err := s.graphDB.SetSourceNode(ctx, selfNode); err != nil { return fmt.Errorf("can't set self node: %w", err) } @@ -3509,7 +3332,7 @@ func (s *server) createNewHiddenService(ctx context.Context) error { // optimization that is quicker than seeking for a channel given only the // ChannelID. func (s *server) findChannel(node *btcec.PublicKey, chanID lnwire.ChannelID) ( - *chanstate.OpenChannel, error) { + *channeldb.OpenChannel, error) { nodeChans, err := s.chanStateDB.FetchOpenChannels(node) if err != nil { @@ -3618,16 +3441,17 @@ func (s *server) updateAndBroadcastSelfNode(ctx context.Context, // Update the on-disk version of our announcement. // Load and modify self node istead of creating anew instance so we // don't risk overwriting any existing values. - selfNode, err := s.v1Graph.SourceNode(ctx) + selfNode, err := s.graphDB.SourceNode(ctx) if err != nil { return fmt.Errorf("unable to get current source node: %w", err) } + selfNode.HaveNodeAnnouncement = true selfNode.LastUpdate = time.Unix(int64(newNodeAnn.Timestamp), 0) selfNode.Addresses = newNodeAnn.Addresses - selfNode.Alias = fn.Some(newNodeAnn.Alias.String()) + selfNode.Alias = newNodeAnn.Alias.String() selfNode.Features = s.featureMgr.Get(feature.SetNodeAnn) - selfNode.Color = fn.Some(newNodeAnn.RGBColor) + selfNode.Color = newNodeAnn.RGBColor selfNode.AuthSigBytes = newNodeAnn.Signature.ToSignatureBytes() copy(selfNode.PubKeyBytes[:], s.identityECDH.PubKey().SerializeCompressed()) @@ -3665,7 +3489,7 @@ func (s *server) establishPersistentConnections(ctx context.Context) error { // Iterate through the list of LinkNodes to find addresses we should // attempt to connect to based on our set of previous connections. Set // the reconnection port to the default peer port. - linkNodes, err := s.linkNodeDB.FetchAllLinkNodes() + linkNodes, err := s.chanStateDB.LinkNodeDB().FetchAllLinkNodes() if err != nil && !errors.Is(err, channeldb.ErrLinkNodesNotFound) { return fmt.Errorf("failed to fetch all link nodes: %w", err) } @@ -3674,7 +3498,7 @@ func (s *server) establishPersistentConnections(ctx context.Context) error { pubStr := string(node.IdentityPub.SerializeCompressed()) nodeAddrs := &nodeAddresses{ pubKey: node.IdentityPub, - addresses: withoutV2Onion(node.Addresses), + addresses: node.Addresses, } nodeAddrsMap[pubStr] = nodeAddrs } @@ -3703,10 +3527,6 @@ func (s *server) establishPersistentConnections(ctx context.Context) error { // connect to for this peer. addrSet := make(map[string]net.Addr) for _, addr := range channelPeer.Addresses { - if isV2OnionAddr(addr) { - continue - } - switch addr.(type) { case *net.TCPAddr: addrSet[addr.String()] = addr @@ -3725,10 +3545,6 @@ func (s *server) establishPersistentConnections(ctx context.Context) error { linkNodeAddrs, ok := nodeAddrsMap[pubStr] if ok { for _, lnAddress := range linkNodeAddrs.addresses { - if isV2OnionAddr(lnAddress) { - continue - } - switch lnAddress.(type) { case *net.TCPAddr: addrSet[lnAddress.String()] = lnAddress @@ -3761,10 +3577,7 @@ func (s *server) establishPersistentConnections(ctx context.Context) error { graphAddrs[pubStr] = n return nil } - - // TODO(elle): for now, we only fetch our V1 channels. This should be - // updated to fetch channels across all versions. - err = s.v1Graph.ForEachSourceNodeChannel( + err = s.graphDB.ForEachSourceNodeChannel( ctx, forEachSrcNodeChan, func() { clear(graphAddrs) }, @@ -4412,11 +4225,6 @@ func (s *server) SubscribeCustomMessages() (*subscribe.Client, error) { return s.customMessageServer.Subscribe() } -// SubscribeOnionMessages subscribes to a stream of incoming onion messages. -func (s *server) SubscribeOnionMessages() (*subscribe.Client, error) { - return s.onionMessageServer.Subscribe() -} - // notifyOpenChannelPeerEvent updates the access manager's maps and then calls // the channelNotifier's NotifyOpenChannelEvent. func (s *server) notifyOpenChannelPeerEvent(op wire.OutPoint, @@ -4435,7 +4243,7 @@ func (s *server) notifyOpenChannelPeerEvent(op wire.OutPoint, // notifyPendingOpenChannelPeerEvent updates the access manager's maps and then // calls the channelNotifier's NotifyPendingOpenChannelEvent. func (s *server) notifyPendingOpenChannelPeerEvent(op wire.OutPoint, - pendingChan *chanstate.OpenChannel, remotePub *btcec.PublicKey) { + pendingChan *channeldb.OpenChannel, remotePub *btcec.PublicKey) { // Call newPendingOpenChan to update the access manager's maps for this // peer. @@ -4582,23 +4390,13 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq, ChainNotifier: s.cc.ChainNotifier, BestBlockView: s.cc.BestBlockTracker, RoutingPolicy: s.cc.RoutingPolicy, - SphinxPayment: s.sphinxPayment, - SpawnOnionActor: s.onionActorFactory, - OnionLimiter: s.onionLimiter, - OnionRelayAll: s.cfg.ProtocolOptions.OnionMsgRelayAll, - OnionActorOpts: func(_ [33]byte) []actor.ActorOption[ - *onionmessage.Request, *onionmessage.Response, - ] { - - return s.defaultOnionActorOpts - }, - ActorSystem: s.actorSystem, - WitnessBeacon: s.witnessBeacon, - Invoices: s.invoices, - ChannelNotifier: s.channelNotifier, - HtlcNotifier: s.htlcNotifier, - TowerClient: towerClient, - DisconnectPeer: s.DisconnectPeer, + Sphinx: s.sphinx, + WitnessBeacon: s.witnessBeacon, + Invoices: s.invoices, + ChannelNotifier: s.channelNotifier, + HtlcNotifier: s.htlcNotifier, + TowerClient: towerClient, + DisconnectPeer: s.DisconnectPeer, GenNodeAnnouncement: func(...netann.NodeAnnModifier) ( lnwire.NodeAnnouncement1, error) { @@ -4618,7 +4416,6 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq, MaxOutgoingCltvExpiry: s.cfg.MaxOutgoingCltvExpiry, MaxChannelFeeAllocation: s.cfg.MaxChannelFeeAllocation, CoopCloseTargetConfs: s.cfg.CoopCloseTargetConfs, - ChannelCloseConfs: s.cfg.Dev.ChannelCloseConfs(), MaxAnchorsCommitFeeRate: chainfee.SatPerKVByte( s.cfg.MaxCommitFeeRateAnchors * 1000).FeePerKWeight(), ChannelCommitInterval: s.cfg.ChannelCommitInterval, @@ -4640,8 +4437,14 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq, AuxResolver: s.implCfg.AuxContractResolver, AuxTrafficShaper: s.implCfg.TrafficShaper, AuxChannelNegotiator: s.implCfg.AuxChannelNegotiator, - ShouldFwdExpAccountability: func() bool { - return !s.cfg.ProtocolOptions.NoExpAccountability() + ShouldFwdExpEndorsement: func() bool { + if s.cfg.ProtocolOptions.NoExperimentalEndorsement() { + return false + } + + return clock.NewDefaultClock().Now().Before( + EndorsementExperimentEnd, + ) }, NoDisconnectOnPongFailure: s.cfg.NoDisconnectOnPongFailure, } @@ -4855,10 +4658,6 @@ func (s *server) peerTerminationWatcher(p *peer.Brontide, ready chan struct{}) { if _, ok := s.ignorePeerTermination[p]; ok { delete(s.ignorePeerTermination, p) - // Ensure the onion peer actor is stopped even if Disconnect - // hasn't been called yet due to async execution. - p.StopOnionActorIfExists() - pubKey := p.PubKey() pubStr := string(pubKey[:]) @@ -5415,17 +5214,16 @@ func (s *server) fetchNodeAdvertisedAddrs(ctx context.Context, return nil, err } - node, err := s.v1Graph.FetchNode(ctx, vertex) + node, err := s.graphDB.FetchNode(ctx, vertex) if err != nil { return nil, err } - addrs := withoutV2Onion(node.Addresses) - if len(addrs) == 0 { + if len(node.Addresses) == 0 { return nil, errNoAdvertisedAddr } - return addrs, nil + return node.Addresses, nil } // fetchLastChanUpdate returns a function which is able to retrieve our latest @@ -5468,36 +5266,34 @@ func (s *server) applyChannelUpdate(update *lnwire.ChannelUpdate1, } } - fut := s.authGossiper.ProcessLocalAnnouncement( + errChan := s.authGossiper.ProcessLocalAnnouncement( update, discovery.RemoteAlias(peerAlias), ) - - ctx, cancel := lnutils.ContextFromQuit(s.quit) - defer cancel() - - return discovery.AwaitGossipResult(ctx, fut) + select { + case err := <-errChan: + return err + case <-s.quit: + return ErrServerShuttingDown + } } // SendCustomMessage sends a custom message to the peer with the specified // pubkey. -func (s *server) SendCustomMessage(ctx context.Context, peerPub [33]byte, - msgType lnwire.MessageType, data []byte) error { +func (s *server) SendCustomMessage(peerPub [33]byte, msgType lnwire.MessageType, + data []byte) error { peer, err := s.FindPeerByPubStr(string(peerPub[:])) if err != nil { return err } - // We'll wait until the peer is active, but also listen for - // cancellation. + // We'll wait until the peer is active. select { case <-peer.ActiveSignal(): case <-peer.QuitSignal(): return fmt.Errorf("peer %x disconnected", peerPub) case <-s.quit: return ErrServerShuttingDown - case <-ctx.Done(): - return ctx.Err() } msg, err := lnwire.NewCustom(msgType, data) @@ -5510,50 +5306,6 @@ func (s *server) SendCustomMessage(ctx context.Context, peerPub [33]byte, return peer.SendMessageLazy(true, msg) } -// SendOnionMessage sends a custom message to the peer with the specified -// pubkey. -// TODO(gijs): change this message to include path finding. -func (s *server) SendOnionMessage(ctx context.Context, peerPub [33]byte, - pathKey *btcec.PublicKey, onion []byte) error { - - peer, err := s.FindPeerByPubStr(string(peerPub[:])) - if err != nil { - return err - } - - // We'll wait until the peer is active, but also listen for - // cancellation. - select { - case <-peer.ActiveSignal(): - case <-peer.QuitSignal(): - return fmt.Errorf("peer %x disconnected", peerPub) - case <-s.quit: - return ErrServerShuttingDown - case <-ctx.Done(): - return ctx.Err() - } - - msg := lnwire.NewOnionMessage(pathKey, onion) - - // Send the message as low-priority. For now we assume that all - // application-defined message are low priority. - return peer.SendMessageLazy(true, msg) -} - -// SendToPeer sends an onion message to the peer identified by the given -// compressed public key. This implements the onionmessage.PeerMessageSender -// interface and is used by the onion peer actor when forwarding messages. -func (s *server) SendToPeer(pubKey [33]byte, - msg *lnwire.OnionMessage) error { - - peer, err := s.FindPeerByPubStr(string(pubKey[:])) - if err != nil { - return err - } - - return peer.SendMessageLazy(true, msg) -} - // newSweepPkScriptGen creates closure that generates a new public key script // which should be used to sweep any funds into the on-chain wallet. // Specifically, the script generated is a version 0, pay-to-witness-pubkey-hash @@ -5697,6 +5449,74 @@ func (s *server) ChanHasRbfCoopCloser(peerPub *btcec.PublicKey, return targetPeer.ChanHasRbfCoopCloser(chanPoint) } +// attemptCoopRbfFeeBump attempts to look up the active chan closer for a +// channel given the outpoint. If found, we'll attempt to do a fee bump, +// returning channels used for updates. If the channel isn't currently active +// (p2p connection established), then his function will return an error. +func (s *server) attemptCoopRbfFeeBump(ctx context.Context, + chanPoint wire.OutPoint, feeRate chainfee.SatPerKWeight, + deliveryScript lnwire.DeliveryAddress) (*peer.CoopCloseUpdates, error) { + + // First, we'll attempt to look up the channel based on it's + // ChannelPoint. + channel, err := s.chanStateDB.FetchChannel(chanPoint) + if err != nil { + return nil, fmt.Errorf("unable to fetch channel: %w", err) + } + + // From the channel, we can now get the pubkey of the peer, then use + // that to eventually get the chan closer. + peerPub := channel.IdentityPub.SerializeCompressed() + + // Now that we have the peer pub, we can look up the peer itself. + s.mu.RLock() + targetPeer, ok := s.peersByPub[string(peerPub)] + s.mu.RUnlock() + if !ok { + return nil, fmt.Errorf("peer for ChannelPoint(%v) is "+ + "not online", chanPoint) + } + + closeUpdates, err := targetPeer.TriggerCoopCloseRbfBump( + ctx, chanPoint, feeRate, deliveryScript, + ) + if err != nil { + return nil, fmt.Errorf("unable to trigger coop rbf fee bump: "+ + "%w", err) + } + + return closeUpdates, nil +} + +// AttemptRBFCloseUpdate attempts to trigger a new RBF iteration for a co-op +// close update. This route it to be used only if the target channel in question +// is no longer active in the link. This can happen when we restart while we +// already have done a single RBF co-op close iteration. +func (s *server) AttemptRBFCloseUpdate(ctx context.Context, + chanPoint wire.OutPoint, feeRate chainfee.SatPerKWeight, + deliveryScript lnwire.DeliveryAddress) (*peer.CoopCloseUpdates, error) { + + // If the channel is present in the switch, then the request should flow + // through the switch instead. + chanID := lnwire.NewChanIDFromOutPoint(chanPoint) + if _, err := s.htlcSwitch.GetLink(chanID); err == nil { + return nil, fmt.Errorf("ChannelPoint(%v) is active in link, "+ + "invalid request", chanPoint) + } + + // At this point, we know that the channel isn't present in the link, so + // we'll check to see if we have an entry in the active chan closer map. + updates, err := s.attemptCoopRbfFeeBump( + ctx, chanPoint, feeRate, deliveryScript, + ) + if err != nil { + return nil, fmt.Errorf("unable to attempt coop rbf fee bump "+ + "ChannelPoint(%v)", chanPoint) + } + + return updates, nil +} + // calculateNodeAnnouncementTimestamp returns the timestamp to use for a node // announcement, ensuring it's at least one second after the previously // persisted timestamp. This ensures BOLT-07 compliance, which requires node @@ -5762,7 +5582,7 @@ func (s *server) setSelfNode(ctx context.Context, nodePub route.Vertex, // Parse the color from config. We will update this later if the config // color is not changed from default (#3399FF) and we have a value in // the source node. - nodeColor, err := lncfg.ParseHexColor(s.cfg.Color) + color, err := lncfg.ParseHexColor(s.cfg.Color) if err != nil { return fmt.Errorf("unable to parse color: %w", err) } @@ -5772,7 +5592,7 @@ func (s *server) setSelfNode(ctx context.Context, nodePub route.Vertex, nodeLastUpdate = time.Now() ) - srcNode, err := s.v1Graph.SourceNode(ctx) + srcNode, err := s.graphDB.SourceNode(ctx) switch { case err == nil: // If we have a source node persisted in the DB already, then we @@ -5786,25 +5606,19 @@ func (s *server) setSelfNode(ctx context.Context, nodePub route.Vertex, // didn't specify a different color in the config. We'll use the // source node's color. if s.cfg.Color == defaultColor { - srcNode.Color.WhenSome(func(rgba color.RGBA) { - nodeColor = rgba - }) + color = srcNode.Color } // If an alias is not specified in the config, we'll use the // source node's alias. if alias == "" { - srcNode.Alias.WhenSome(func(s string) { - alias = s - }) + alias = srcNode.Alias } // If the `externalip` is not specified in the config, it means // `addrs` will be empty, we'll use the source node's addresses. - // Filter out any persisted Tor v2 onion entries so an upgraded - // node never re-signs or re-broadcasts a legacy v2 address. if len(s.cfg.ExternalIPs) == 0 { - addrs = withoutV2Onion(srcNode.Addresses) + addrs = srcNode.Addresses } case errors.Is(err, graphdb.ErrSourceNodeNotSet): @@ -5828,15 +5642,16 @@ func (s *server) setSelfNode(ctx context.Context, nodePub route.Vertex, // TODO(abdulkbk): potentially find a way to use the source node's // features in the self node. - selfNode := models.NewV1Node( - nodePub, &models.NodeV1Fields{ - Alias: nodeAlias.String(), - Color: nodeColor, - LastUpdate: nodeLastUpdate, - Addresses: addrs, - Features: s.featureMgr.GetRaw(feature.SetNodeAnn), - }, - ) + selfNode := &models.Node{ + HaveNodeAnnouncement: true, + LastUpdate: nodeLastUpdate, + Addresses: addrs, + Alias: nodeAlias.String(), + Color: color, + Features: s.featureMgr.Get(feature.SetNodeAnn), + } + + copy(selfNode.PubKeyBytes[:], nodePub[:]) // Based on the disk representation of the node announcement generated // above, we'll generate a node announcement that can go out on the diff --git a/server_test.go b/server_test.go index f666b508e..0cb364318 100644 --- a/server_test.go +++ b/server_test.go @@ -1,11 +1,9 @@ package lnd import ( - "net" "testing" "time" - "github.com/lightningnetwork/lnd/tor" "github.com/stretchr/testify/require" ) @@ -141,100 +139,3 @@ func TestNodeAnnouncementTimestampComparison(t *testing.T) { }) } } - -// TestParseAddrRejectsTorV2 ensures that parseAddr rejects v2 .onion hosts at -// the operator-input boundary. This is the path used by lncli connect (via -// rpcserver.ConnectPeer) and the --addpeer config option, mirroring the -// equivalent gate in lncfg.ParseAddressString. -func TestParseAddrRejectsTorV2(t *testing.T) { - t.Parallel() - - const ( - v2Host = "3g2upl4pq6kufc4m.onion" - v3Host = "4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpb" + - "i7utijcltosqemad.onion" - ) - - netCfg := &tor.ClearNet{} - - tests := []struct { - name string - address string - expectErr bool - }{ - { - name: "v2 without port is rejected", - address: v2Host, - expectErr: true, - }, - { - name: "v2 with port is rejected", - address: v2Host + ":9735", - expectErr: true, - }, - { - name: "v3 without port is accepted", - address: v3Host, - expectErr: false, - }, - { - name: "v3 with port is accepted", - address: v3Host + ":9735", - expectErr: false, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - addr, err := parseAddr(tc.address, netCfg) - if tc.expectErr { - require.Error(t, err) - require.Contains( - t, err.Error(), "tor v2 onion", - ) - require.Nil(t, addr) - - return - } - - require.NoError(t, err) - onionAddr, ok := addr.(*tor.OnionAddr) - require.True(t, ok) - require.Equal(t, v3Host, onionAddr.OnionService) - }) - } -} - -// TestWithoutV2Onion ensures that Tor v2 onion addresses are dropped from -// reconnect/dial consumption paths (startup persistent reconnect, live -// topology updates, fetchNodeAdvertisedAddrs) while non-onion and v3 onion -// addresses pass through unchanged. Storage and gossip re-broadcast remain -// byte-faithful elsewhere. -func TestWithoutV2Onion(t *testing.T) { - t.Parallel() - - v2 := &tor.OnionAddr{ - OnionService: "3g2upl4pq6kufc4m.onion", - Port: 9735, - } - v3 := &tor.OnionAddr{ - OnionService: "4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnz" + - "jpbi7utijcltosqemad.onion", - Port: 9735, - } - tcp := &net.TCPAddr{ - IP: net.ParseIP("127.0.0.1"), - Port: 9735, - } - - require.True(t, isV2OnionAddr(v2)) - require.False(t, isV2OnionAddr(v3)) - require.False(t, isV2OnionAddr(tcp)) - - filtered := withoutV2Onion([]net.Addr{v2, v3, tcp, v2}) - require.Equal(t, []net.Addr{v3, tcp}, filtered) - - // An all-v2 input filters to an empty slice; callers such as - // fetchNodeAdvertisedAddrs treat this as "no advertised address". - require.Empty(t, withoutV2Onion([]net.Addr{v2, v2})) -} diff --git a/shachain/element.go b/shachain/element.go index 1550d0278..21f714295 100644 --- a/shachain/element.go +++ b/shachain/element.go @@ -4,7 +4,7 @@ import ( "crypto/sha256" "errors" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" ) // element represents the entity which contains the hash and index diff --git a/shachain/producer.go b/shachain/producer.go index 0d708c007..c633d8994 100644 --- a/shachain/producer.go +++ b/shachain/producer.go @@ -3,7 +3,7 @@ package shachain import ( "io" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" ) // Producer is an interface which serves as an abstraction over the data diff --git a/shachain/producer_test.go b/shachain/producer_test.go index 798cb2453..1806706e8 100644 --- a/shachain/producer_test.go +++ b/shachain/producer_test.go @@ -4,7 +4,7 @@ import ( "bytes" "testing" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" ) // TestShaChainProducerRestore checks the ability of shachain producer to be diff --git a/shachain/store.go b/shachain/store.go index 511479001..8582e62cc 100644 --- a/shachain/store.go +++ b/shachain/store.go @@ -6,7 +6,7 @@ import ( "fmt" "io" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" ) // Store is an interface which serves as an abstraction over data structure diff --git a/shachain/store_test.go b/shachain/store_test.go index 58b1504c5..d76b4949b 100644 --- a/shachain/store_test.go +++ b/shachain/store_test.go @@ -4,7 +4,7 @@ import ( "bytes" "testing" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" ) type testInsert struct { diff --git a/shachain/utils.go b/shachain/utils.go index 60036ca61..aeb3bd598 100644 --- a/shachain/utils.go +++ b/shachain/utils.go @@ -3,7 +3,7 @@ package shachain import ( "encoding/hex" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" ) // getBit return bit on index at position. diff --git a/sqldb/config.go b/sqldb/config.go index bf2a8deb8..59801dbea 100644 --- a/sqldb/config.go +++ b/sqldb/config.go @@ -31,40 +31,6 @@ type SqliteConfig struct { QueryConfig `group:"query" namespace:"query"` } -const ( - // DefaultSqliteMaxConns is the default number of maximum open - // connections for SQLite. SQLite only supports a single writer, so a - // low default reduces contention on the busy_timeout and limits - // resource usage, especially on mobile. - DefaultSqliteMaxConns = 2 - - // DefaultSqliteBusyTimeout is the default busy_timeout value used - // when no BusyTimeout is configured. - DefaultSqliteBusyTimeout = 5 * time.Second -) - -// busyTimeoutMs returns the busy_timeout value in milliseconds. If -// BusyTimeout is not set, it returns the default value. -func (s *SqliteConfig) busyTimeoutMs() int64 { - if s.BusyTimeout > 0 { - return s.BusyTimeout.Milliseconds() - } - - return DefaultSqliteBusyTimeout.Milliseconds() -} - -// MaxConns returns the effective maximum number of open connections. If -// MaxConnections is not set, it returns a default of 2. This low default is -// chosen because SQLite only supports a single writer, which helps reduce -// contention and resource usage. -func (s *SqliteConfig) MaxConns() int { - if s.MaxConnections > 0 { - return s.MaxConnections - } - - return DefaultSqliteMaxConns -} - // Validate checks that the SqliteConfig values are valid. func (p *SqliteConfig) Validate() error { if err := p.QueryConfig.Validate(true); err != nil { diff --git a/sqldb/go.mod b/sqldb/go.mod index 224be39a9..6331fa324 100644 --- a/sqldb/go.mod +++ b/sqldb/go.mod @@ -4,11 +4,12 @@ require ( github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084 github.com/davecgh/go-spew v1.1.1 github.com/golang-migrate/migrate/v4 v4.17.0 + github.com/jackc/pgconn v1.14.3 github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 - github.com/jackc/pgx/v5 v5.9.2 + github.com/jackc/pgx/v5 v5.7.4 github.com/ory/dockertest/v3 v3.10.0 github.com/pmezard/go-difflib v1.0.0 - github.com/stretchr/testify v1.11.1 + github.com/stretchr/testify v1.10.0 golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 modernc.org/sqlite v1.29.10 ) @@ -27,7 +28,7 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/go-viper/mapstructure/v2 v2.3.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect @@ -35,33 +36,36 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.3 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/sys/user v0.3.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/opencontainers/runc v1.2.8 // indirect + github.com/opencontainers/runc v1.1.14 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sirupsen/logrus v1.9.2 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect go.uber.org/atomic v1.7.0 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect modernc.org/libc v1.49.3 // indirect @@ -71,4 +75,4 @@ require ( modernc.org/token v1.1.0 // indirect ) -go 1.25.11 +go 1.24.11 diff --git a/sqldb/go.sum b/sqldb/go.sum index 0218c9d29..5dd99c404 100644 --- a/sqldb/go.sum +++ b/sqldb/go.sum @@ -42,8 +42,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= +github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-migrate/migrate/v4 v4.17.0 h1:rd40H3QXU0AA4IoLllFcEAEo9dYKRHYND2gB4p7xcaU= @@ -63,20 +63,31 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= -github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg= +github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= @@ -85,8 +96,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= -github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= @@ -97,27 +106,26 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v1.2.8 h1:RnEICeDReapbZ5lZEgHvj7E9Q3Eex9toYmaGBsbvU5Q= -github.com/opencontainers/runc v1.2.8/go.mod h1:cC0YkmZcuvr+rtBZ6T7NBoVbMGNAdLa/21vIElJDOzI= +github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w= +github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA= github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= +github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= @@ -141,41 +149,43 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -183,6 +193,7 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/sqldb/migrations.go b/sqldb/migrations.go index 241e5c0d6..38535d8b9 100644 --- a/sqldb/migrations.go +++ b/sqldb/migrations.go @@ -92,50 +92,6 @@ var ( // schema. This is optional and can be disabled by the // user if necessary. }, - { - Name: "000009_graph_v2", - Version: 11, - SchemaVersion: 9, - }, - { - Name: "000010_payments", - Version: 12, - SchemaVersion: 10, - }, - { - Name: "000011_payment_duplicates", - Version: 13, - SchemaVersion: 11, - }, - { - Name: "kv_payments_migration", - Version: 14, - SchemaVersion: 11, - // A migration function may be attached to this - // migration to migrate KV payments to the native SQL - // schema. This is optional and can be disabled by the - // user if necessary. - }, - { - Name: "000012_drop_redundant_invoice_indexes", - Version: 15, - SchemaVersion: 12, - }, - { - Name: "000013_payments_index_improvements", - Version: 16, - SchemaVersion: 13, - }, - { - Name: "000014_payments_no_fail_reason_index", - Version: 17, - SchemaVersion: 14, - }, - { - Name: "000015_chain_params", - Version: 18, - SchemaVersion: 15, - }, }, migrationAdditions...) // ErrMigrationMismatch is returned when a migrated record does not diff --git a/sqldb/migrations_dev.go b/sqldb/migrations_dev.go index 60c7b3c84..a1b25019a 100644 --- a/sqldb/migrations_dev.go +++ b/sqldb/migrations_dev.go @@ -2,9 +2,4 @@ package sqldb -// migrationAdditions is a list of migrations that are added to the -// migrationConfig slice. -// -// NOTE: This should always be empty as all migrations are now included in the -// main line (see migrations.go). var migrationAdditions []MigrationConfig diff --git a/sqldb/migrations_dev_test.go b/sqldb/migrations_dev_test.go deleted file mode 100644 index 43d1b5dcd..000000000 --- a/sqldb/migrations_dev_test.go +++ /dev/null @@ -1,52 +0,0 @@ -//go:build test_db_postgres || test_db_sqlite || test_native_sql - -package sqldb - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/require" -) - -// TestMigrationFilesAllRegistered verifies that every .up.sql file in the -// embedded migrations filesystem has a corresponding entry in migrationConfig. -// This test requires dev build tags so that any future dev-only migrations -// added to migrationAdditions are visible — without them, such entries would -// be absent and their SQL files would trigger false failures. -func TestMigrationFilesAllRegistered(t *testing.T) { - t.Parallel() - - migrations := GetMigrations() - require.NotEmpty(t, migrations) - - // Collect all schema versions referenced by any entry in migrationConfig - // (including migrationAdditions, which is only populated under dev build - // tags). - registeredSchemaVersions := make(map[int]string) - for _, m := range migrations { - registeredSchemaVersions[m.SchemaVersion] = m.Name - } - - // Read all .up.sql files from the embedded filesystem. - embeddedFiles, err := sqlSchemas.ReadDir("sqlc/migrations") - require.NoError(t, err) - - for _, f := range embeddedFiles { - if f.IsDir() { - continue - } - - var schemaVersion int - _, err := fmt.Sscanf(f.Name(), "%06d_", &schemaVersion) - require.NoError(t, err, "migration file %q has no valid "+ - "numeric prefix", f.Name()) - - _, referenced := registeredSchemaVersions[schemaVersion] - require.True(t, referenced, - "SQL migration file %q (schema version %d) has no "+ - "corresponding entry in migrationConfig — add "+ - "an entry with SchemaVersion=%d", - f.Name(), schemaVersion, schemaVersion) - } -} diff --git a/sqldb/migrations_test.go b/sqldb/migrations_test.go index 4e2b68e0f..063f810b0 100644 --- a/sqldb/migrations_test.go +++ b/sqldb/migrations_test.go @@ -4,7 +4,6 @@ import ( "database/sql" "fmt" "path/filepath" - "strings" "testing" "github.com/golang-migrate/migrate/v4" @@ -114,8 +113,8 @@ func testInvoiceExpiryMigration(t *testing.T, makeDB makeMigrationTestDB) { // AMP invoices. err = migrate(TargetVersion(4)) - invoices, err := db.FilterInvoicesByAddIndex(ctxb, sqlc.FilterInvoicesByAddIndexParams{ - AddIndexGet: 1, + invoices, err := db.FilterInvoices(ctxb, sqlc.FilterInvoicesParams{ + AddIndexGet: SQLInt64(1), NumLimit: 100, }) @@ -453,6 +452,132 @@ func TestCustomMigration(t *testing.T) { } } +// TestSchemaMigrationIdempotency tests that the our schema migrations are +// idempotent. This means that we can apply the migrations multiple times and +// the schema version will always be the same. +func TestSchemaMigrationIdempotency(t *testing.T) { + dropMigrationTrackerEntries := func(t *testing.T, db *BaseDB) { + _, err := db.Exec("DELETE FROM migration_tracker;") + require.NoError(t, err) + } + + lastMigration := migrationConfig[len(migrationConfig)-1] + + t.Run("SQLite", func(t *testing.T) { + // First instantiate the database and run the migrations + // including the custom migrations. + t.Logf("Creating new SQLite DB for testing migrations") + + dbFileName := filepath.Join(t.TempDir(), "tmp.db") + var ( + db *SqliteStore + err error + ) + + // Run the migration 3 times to test that the migrations + // are idempotent. + for i := 0; i < 3; i++ { + db, err = NewSqliteStore(&SqliteConfig{ + SkipMigrations: false, + }, dbFileName) + require.NoError(t, err) + + dbToCleanup := db.DB + t.Cleanup(func() { + require.NoError( + t, dbToCleanup.Close(), + ) + }) + + ctxb := t.Context() + require.NoError( + t, db.ApplyAllMigrations(ctxb, GetMigrations()), + ) + + version, dirty, err := db.GetSchemaVersion() + require.NoError(t, err) + + // Now reset the schema version to 0 and make sure that + // we can apply the migrations again. + require.Equal(t, lastMigration.SchemaVersion, version) + require.False(t, dirty) + + require.NoError( + t, db.SetSchemaVersion( + database.NilVersion, false, + ), + ) + dropMigrationTrackerEntries(t, db.BaseDB) + + // Make sure that we reset the schema version. + version, dirty, err = db.GetSchemaVersion() + require.NoError(t, err) + require.Equal(t, -1, version) + require.False(t, dirty) + } + }) + + t.Run("Postgres", func(t *testing.T) { + // First create a temporary Postgres database to run + // the migrations on. + fixture := NewTestPgFixture( + t, DefaultPostgresFixtureLifetime, + ) + t.Cleanup(func() { + fixture.TearDown(t) + }) + + dbName := randomDBName(t) + + // Next instantiate the database and run the migrations + // including the custom migrations. + t.Logf("Creating new Postgres DB '%s' for testing "+ + "migrations", dbName) + + _, err := fixture.db.ExecContext( + t.Context(), "CREATE DATABASE "+dbName, + ) + require.NoError(t, err) + + cfg := fixture.GetConfig(dbName) + var db *PostgresStore + + // Run the migration 3 times to test that the migrations + // are idempotent. + for i := 0; i < 3; i++ { + cfg.SkipMigrations = false + db, err = NewPostgresStore(cfg) + require.NoError(t, err) + + ctxb := t.Context() + require.NoError( + t, db.ApplyAllMigrations(ctxb, GetMigrations()), + ) + + version, dirty, err := db.GetSchemaVersion() + require.NoError(t, err) + + // Now reset the schema version to 0 and make sure that + // we can apply the migrations again. + require.Equal(t, lastMigration.SchemaVersion, version) + require.False(t, dirty) + + require.NoError( + t, db.SetSchemaVersion( + database.NilVersion, false, + ), + ) + dropMigrationTrackerEntries(t, db.BaseDB) + + // Make sure that we reset the schema version. + version, dirty, err = db.GetSchemaVersion() + require.NoError(t, err) + require.Equal(t, -1, version) + require.False(t, dirty) + } + }) +} + // TestMigrationBug19RC1 tests a bug that was present in the migration code // at the v0.19.0-rc1 release. // The bug was fixed in: https://github.com/lightningnetwork/lnd/pull/9647 @@ -617,109 +742,3 @@ func TestMigrationSucceedsAfterDirtyStateMigrationFailure19RC1(t *testing.T) { require.False(t, dirty) }) } - -// TestMigrationConfigConsistency verifies that the migration configuration in -// migrationConfig is consistent with the actual SQL schema files embedded in -// the binary. This catches version collisions (e.g. two migrations claiming -// the same schema version) and missing schema files. -func TestMigrationConfigConsistency(t *testing.T) { - t.Parallel() - - migrations := GetMigrations() - require.NotEmpty(t, migrations) - - // Build a set of schema versions that have actual .up.sql files in - // the embedded filesystem. - embeddedFiles, err := sqlSchemas.ReadDir("sqlc/migrations") - require.NoError(t, err) - - fileSchemaVersions := make(map[int]string) - for _, f := range embeddedFiles { - if f.IsDir() { - continue - } - - var version int - _, err := fmt.Sscanf(f.Name(), "%06d_", &version) - require.NoError(t, err, "schema migration file %q is "+ - "missing a valid numeric prefix (expected "+ - "format: 000XXX_name.up.sql)", f.Name()) - - // Enforce the 6-digit zero-padded naming convention - // for consistent directory listing order. - expectedPrefix := fmt.Sprintf("%06d_", version) - require.True(t, - len(f.Name()) > len(expectedPrefix) && - f.Name()[:len(expectedPrefix)] == expectedPrefix, - "schema migration file %q should use 6-digit "+ - "zero-padded prefix %q", f.Name(), - expectedPrefix) - - // Verify no two files share the same numeric prefix. - if existing, ok := fileSchemaVersions[version]; ok { - t.Fatalf("duplicate schema file version %06d: "+ - "%q and %q", version, existing, f.Name()) - } - - fileSchemaVersions[version] = f.Name() - } - - // Track seen versions to detect duplicates. - seenVersions := make(map[int]string) - seenSchemaVersions := make(map[int]string) - - for i, m := range migrations { - // 1. Verify no duplicate global versions. - if existing, ok := seenVersions[m.Version]; ok { - t.Fatalf("duplicate global version %d: %q and %q", - m.Version, existing, m.Name) - } - seenVersions[m.Version] = m.Name - - // 2. For schema migrations (those that advance the schema - // version), verify a corresponding .up.sql file exists - // and no two config entries claim the same schema version - // with different file prefixes. - prevSchema := 0 - if i > 0 { - prevSchema = migrations[i-1].SchemaVersion - } - - require.GreaterOrEqual(t, m.SchemaVersion, prevSchema, - "migration %q regresses schema version from %d to %d", - m.Name, prevSchema, m.SchemaVersion) - - // A migration advances the schema if its SchemaVersion is - // higher than the previous migration's SchemaVersion. - if m.SchemaVersion > prevSchema { - fileName, hasFile := fileSchemaVersions[m.SchemaVersion] - require.True(t, hasFile, - "migration %q (version %d) declares "+ - "SchemaVersion=%d but no %06d_*.up.sql"+ - " file exists in the embedded FS", - m.Name, m.Version, m.SchemaVersion, - m.SchemaVersion) - require.Equal(t, strings.TrimSuffix(fileName, ".up.sql"), - m.Name, "migration %q (version %d) has "+ - "SchemaVersion=%d but its name does not "+ - "match embedded file %q", - m.Name, m.Version, m.SchemaVersion, fileName) - - if existing, ok := seenSchemaVersions[m.SchemaVersion]; ok { - t.Fatalf("duplicate schema version %d: "+ - "%q and %q", m.SchemaVersion, - existing, m.Name) - } - seenSchemaVersions[m.SchemaVersion] = m.Name - } - } - - // 3. Verify versions are sequential starting from 1. - for i, m := range migrations { - require.Equal(t, i+1, m.Version, - "migration %q has version %d but expected %d "+ - "(migrations must be sequential)", - m.Name, m.Version, i+1) - } - -} diff --git a/sqldb/postgres_fixture.go b/sqldb/postgres_fixture.go index 6cae3e075..91b95d66e 100644 --- a/sqldb/postgres_fixture.go +++ b/sqldb/postgres_fixture.go @@ -155,10 +155,6 @@ func NewTestPostgresDB(t testing.TB, fixture *TestPgFixture) *PostgresStore { context.Background(), GetMigrations()), ) - t.Cleanup(func() { - require.NoError(t, store.DB.Close()) - }) - return store } @@ -186,9 +182,5 @@ func NewTestPostgresDBWithVersion(t *testing.T, fixture *TestPgFixture, err = store.ExecuteMigrations(TargetVersion(version)) require.NoError(t, err) - t.Cleanup(func() { - require.NoError(t, store.DB.Close()) - }) - return store } diff --git a/sqldb/sqlc/chain_params.sql.go b/sqldb/sqlc/chain_params.sql.go deleted file mode 100644 index bb7b44291..000000000 --- a/sqldb/sqlc/chain_params.sql.go +++ /dev/null @@ -1,33 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.29.0 -// source: chain_params.sql - -package sqlc - -import ( - "context" -) - -const getChainNetwork = `-- name: GetChainNetwork :one -SELECT network FROM chain_params -WHERE single_row = TRUE -` - -func (q *Queries) GetChainNetwork(ctx context.Context) (string, error) { - row := q.db.QueryRowContext(ctx, getChainNetwork) - var network string - err := row.Scan(&network) - return network, err -} - -const insertChainNetwork = `-- name: InsertChainNetwork :exec -INSERT INTO chain_params (single_row, network) -VALUES (TRUE, $1) -ON CONFLICT (single_row) DO NOTHING -` - -func (q *Queries) InsertChainNetwork(ctx context.Context, network string) error { - _, err := q.db.ExecContext(ctx, insertChainNetwork, network) - return err -} diff --git a/sqldb/sqlc/db_custom.go b/sqldb/sqlc/db_custom.go index b8d476616..f7bc49918 100644 --- a/sqldb/sqlc/db_custom.go +++ b/sqldb/sqlc/db_custom.go @@ -5,12 +5,6 @@ import ( "strings" ) -// GetTx returns the underlying DBTX (either *sql.DB or *sql.Tx) used by the -// Queries struct. -func (q *Queries) GetTx() DBTX { - return q.db -} - // makeQueryParams generates a string of query parameters for a SQL query. It is // meant to replace the `?` placeholders in a SQL query with numbered parameters // like `$1`, `$2`, etc. This is required for the sqlc /*SLICE:*/ @@ -167,106 +161,3 @@ func (r GetChannelsBySCIDRangeRow) Node1Pub() []byte { func (r GetChannelsBySCIDRangeRow) Node2Pub() []byte { return r.Node2PubKey } - -// PaymentAndIntent is an interface that provides access to a payment and its -// associated payment intent. -type PaymentAndIntent interface { - // GetPayment returns the Payment associated with this interface. - GetPayment() Payment - - // GetPaymentIntent returns the PaymentIntent associated with this payment. - GetPaymentIntent() PaymentIntent -} - -// GetPayment returns the Payment associated with this interface. -// -// NOTE: This method is part of the PaymentAndIntent interface. -func (r FilterPaymentsRow) GetPayment() Payment { - return r.Payment -} - -// GetPaymentIntent returns the PaymentIntent associated with this payment. -// If the payment has no intent (IntentType is NULL), this returns a zero-value -// PaymentIntent. -// -// NOTE: This method is part of the PaymentAndIntent interface. -func (r FilterPaymentsRow) GetPaymentIntent() PaymentIntent { - if !r.IntentType.Valid { - return PaymentIntent{} - } - return PaymentIntent{ - IntentType: r.IntentType.Int16, - IntentPayload: r.IntentPayload, - } -} - -// GetPayment returns the Payment associated with this interface. -// -// NOTE: This method is part of the PaymentAndIntent interface. -func (r FetchPaymentRow) GetPayment() Payment { - return r.Payment -} - -// GetPaymentIntent returns the PaymentIntent associated with this payment. -// If the payment has no intent (IntentType is NULL), this returns a zero-value -// PaymentIntent. -// -// NOTE: This method is part of the PaymentAndIntent interface. -func (r FetchPaymentRow) GetPaymentIntent() PaymentIntent { - if !r.IntentType.Valid { - return PaymentIntent{} - } - return PaymentIntent{ - IntentType: r.IntentType.Int16, - IntentPayload: r.IntentPayload, - } -} - -func (r FetchPaymentsByIDsRow) GetPayment() Payment { - return Payment{ - ID: r.ID, - AmountMsat: r.AmountMsat, - CreatedAt: r.CreatedAt, - PaymentIdentifier: r.PaymentIdentifier, - FailReason: r.FailReason, - } -} - -func (r FetchPaymentsByIDsRow) GetPaymentIntent() PaymentIntent { - if !r.IntentType.Valid { - return PaymentIntent{} - } - return PaymentIntent{ - IntentType: r.IntentType.Int16, - IntentPayload: r.IntentPayload, - } -} - -// GetPayment returns the Payment associated with this interface. -// -// NOTE: This method is part of the PaymentAndIntent interface. -func (r FetchNonTerminalPaymentsRow) GetPayment() Payment { - return Payment{ - ID: r.ID, - AmountMsat: r.AmountMsat, - CreatedAt: r.CreatedAt, - PaymentIdentifier: r.PaymentIdentifier, - FailReason: r.FailReason, - } -} - -// GetPaymentIntent returns the PaymentIntent associated with this payment. -// If the payment has no intent (IntentType is NULL), this returns a zero-value -// PaymentIntent. -// -// NOTE: This method is part of the PaymentAndIntent interface. -func (r FetchNonTerminalPaymentsRow) GetPaymentIntent() PaymentIntent { - if !r.IntentType.Valid { - return PaymentIntent{} - } - - return PaymentIntent{ - IntentType: r.IntentType.Int16, - IntentPayload: r.IntentPayload, - } -} diff --git a/sqldb/sqlc/graph.sql.go b/sqldb/sqlc/graph.sql.go index 703afd8f4..0ce7780d5 100644 --- a/sqldb/sqlc/graph.sql.go +++ b/sqldb/sqlc/graph.sql.go @@ -55,22 +55,6 @@ func (q *Queries) AddV1ChannelProof(ctx context.Context, arg AddV1ChannelProofPa ) } -const addV2ChannelProof = `-- name: AddV2ChannelProof :execresult -UPDATE graph_channels -SET signature = $2 -WHERE scid = $1 - AND version = 2 -` - -type AddV2ChannelProofParams struct { - Scid []byte - Signature []byte -} - -func (q *Queries) AddV2ChannelProof(ctx context.Context, arg AddV2ChannelProofParams) (sql.Result, error) { - return q.db.ExecContext(ctx, addV2ChannelProof, arg.Scid, arg.Signature) -} - const countZombieChannels = `-- name: CountZombieChannels :one SELECT COUNT(*) FROM graph_zombie_channels @@ -94,9 +78,9 @@ INSERT INTO graph_channels ( version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, - bitcoin_2_signature, signature, funding_pk_script, merkle_root_hash + bitcoin_2_signature ) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15 + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 ) RETURNING id ` @@ -114,9 +98,6 @@ type CreateChannelParams struct { Node2Signature []byte Bitcoin1Signature []byte Bitcoin2Signature []byte - Signature []byte - FundingPkScript []byte - MerkleRootHash []byte } func (q *Queries) CreateChannel(ctx context.Context, arg CreateChannelParams) (int64, error) { @@ -133,9 +114,6 @@ func (q *Queries) CreateChannel(ctx context.Context, arg CreateChannelParams) (i arg.Node2Signature, arg.Bitcoin1Signature, arg.Bitcoin2Signature, - arg.Signature, - arg.FundingPkScript, - arg.MerkleRootHash, ) var id int64 err := row.Scan(&id) @@ -312,7 +290,7 @@ func (q *Queries) DeleteZombieChannel(ctx context.Context, arg DeleteZombieChann const getChannelAndNodesBySCID = `-- name: GetChannelAndNodesBySCID :one SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, + c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, n1.pub_key AS node1_pub_key, n2.pub_key AS node2_pub_key FROM graph_channels c @@ -341,9 +319,6 @@ type GetChannelAndNodesBySCIDRow struct { Node2Signature []byte Bitcoin1Signature []byte Bitcoin2Signature []byte - Signature []byte - FundingPkScript []byte - MerkleRootHash []byte Node1PubKey []byte Node2PubKey []byte } @@ -365,9 +340,6 @@ func (q *Queries) GetChannelAndNodesBySCID(ctx context.Context, arg GetChannelAn &i.Node2Signature, &i.Bitcoin1Signature, &i.Bitcoin2Signature, - &i.Signature, - &i.FundingPkScript, - &i.MerkleRootHash, &i.Node1PubKey, &i.Node2PubKey, ) @@ -376,7 +348,7 @@ func (q *Queries) GetChannelAndNodesBySCID(ctx context.Context, arg GetChannelAn const getChannelByOutpointWithPolicies = `-- name: GetChannelByOutpointWithPolicies :one SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, + c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, n1.pub_key AS node1_pubkey, n2.pub_key AS node2_pubkey, @@ -397,8 +369,6 @@ SELECT cp1.message_flags AS policy_1_message_flags, cp1.channel_flags AS policy_1_channel_flags, cp1.signature AS policy_1_signature, - cp1.block_height AS policy_1_block_height, - cp1.disable_flags AS policy_1_disable_flags, -- Node 2 policy cp2.id AS policy_2_id, @@ -415,9 +385,7 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy_2_message_flags, cp2.channel_flags AS policy_2_channel_flags, - cp2.signature AS policy_2_signature, - cp2.block_height AS policy_2_block_height, - cp2.disable_flags AS policy_2_disable_flags + cp2.signature AS policy_2_signature FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id JOIN graph_nodes n2 ON c.node_id_2 = n2.id @@ -452,8 +420,6 @@ type GetChannelByOutpointWithPoliciesRow struct { Policy1MessageFlags sql.NullInt16 Policy1ChannelFlags sql.NullInt16 Policy1Signature []byte - Policy1BlockHeight sql.NullInt64 - Policy1DisableFlags sql.NullInt16 Policy2ID sql.NullInt64 Policy2NodeID sql.NullInt64 Policy2Version sql.NullInt16 @@ -469,8 +435,6 @@ type GetChannelByOutpointWithPoliciesRow struct { Policy2MessageFlags sql.NullInt16 Policy2ChannelFlags sql.NullInt16 Policy2Signature []byte - Policy2BlockHeight sql.NullInt64 - Policy2DisableFlags sql.NullInt16 } func (q *Queries) GetChannelByOutpointWithPolicies(ctx context.Context, arg GetChannelByOutpointWithPoliciesParams) (GetChannelByOutpointWithPoliciesRow, error) { @@ -490,9 +454,6 @@ func (q *Queries) GetChannelByOutpointWithPolicies(ctx context.Context, arg GetC &i.GraphChannel.Node2Signature, &i.GraphChannel.Bitcoin1Signature, &i.GraphChannel.Bitcoin2Signature, - &i.GraphChannel.Signature, - &i.GraphChannel.FundingPkScript, - &i.GraphChannel.MerkleRootHash, &i.Node1Pubkey, &i.Node2Pubkey, &i.Policy1ID, @@ -510,8 +471,6 @@ func (q *Queries) GetChannelByOutpointWithPolicies(ctx context.Context, arg GetC &i.Policy1MessageFlags, &i.Policy1ChannelFlags, &i.Policy1Signature, - &i.Policy1BlockHeight, - &i.Policy1DisableFlags, &i.Policy2ID, &i.Policy2NodeID, &i.Policy2Version, @@ -527,14 +486,12 @@ func (q *Queries) GetChannelByOutpointWithPolicies(ctx context.Context, arg GetC &i.Policy2MessageFlags, &i.Policy2ChannelFlags, &i.Policy2Signature, - &i.Policy2BlockHeight, - &i.Policy2DisableFlags, ) return i, err } const getChannelBySCID = `-- name: GetChannelBySCID :one -SELECT id, version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, bitcoin_2_signature, signature, funding_pk_script, merkle_root_hash FROM graph_channels +SELECT id, version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, bitcoin_2_signature FROM graph_channels WHERE scid = $1 AND version = $2 ` @@ -560,18 +517,15 @@ func (q *Queries) GetChannelBySCID(ctx context.Context, arg GetChannelBySCIDPara &i.Node2Signature, &i.Bitcoin1Signature, &i.Bitcoin2Signature, - &i.Signature, - &i.FundingPkScript, - &i.MerkleRootHash, ) return i, err } const getChannelBySCIDWithPolicies = `-- name: GetChannelBySCIDWithPolicies :one SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, - n1.id, n1.version, n1.pub_key, n1.alias, n1.last_update, n1.color, n1.signature, n1.block_height, - n2.id, n2.version, n2.pub_key, n2.alias, n2.last_update, n2.color, n2.signature, n2.block_height, + c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, + n1.id, n1.version, n1.pub_key, n1.alias, n1.last_update, n1.color, n1.signature, + n2.id, n2.version, n2.pub_key, n2.alias, n2.last_update, n2.color, n2.signature, -- Policy 1 cp1.id AS policy1_id, @@ -589,8 +543,6 @@ SELECT cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, - cp1.block_height AS policy1_block_height, - cp1.disable_flags AS policy1_disable_flags, -- Policy 2 cp2.id AS policy2_id, @@ -607,9 +559,7 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy_2_message_flags, cp2.channel_flags AS policy_2_channel_flags, - cp2.signature AS policy2_signature, - cp2.block_height AS policy2_block_height, - cp2.disable_flags AS policy2_disable_flags + cp2.signature AS policy2_signature FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -646,8 +596,6 @@ type GetChannelBySCIDWithPoliciesRow struct { Policy1MessageFlags sql.NullInt16 Policy1ChannelFlags sql.NullInt16 Policy1Signature []byte - Policy1BlockHeight sql.NullInt64 - Policy1DisableFlags sql.NullInt16 Policy2ID sql.NullInt64 Policy2NodeID sql.NullInt64 Policy2Version sql.NullInt16 @@ -663,8 +611,6 @@ type GetChannelBySCIDWithPoliciesRow struct { Policy2MessageFlags sql.NullInt16 Policy2ChannelFlags sql.NullInt16 Policy2Signature []byte - Policy2BlockHeight sql.NullInt64 - Policy2DisableFlags sql.NullInt16 } func (q *Queries) GetChannelBySCIDWithPolicies(ctx context.Context, arg GetChannelBySCIDWithPoliciesParams) (GetChannelBySCIDWithPoliciesRow, error) { @@ -684,9 +630,6 @@ func (q *Queries) GetChannelBySCIDWithPolicies(ctx context.Context, arg GetChann &i.GraphChannel.Node2Signature, &i.GraphChannel.Bitcoin1Signature, &i.GraphChannel.Bitcoin2Signature, - &i.GraphChannel.Signature, - &i.GraphChannel.FundingPkScript, - &i.GraphChannel.MerkleRootHash, &i.GraphNode.ID, &i.GraphNode.Version, &i.GraphNode.PubKey, @@ -694,7 +637,6 @@ func (q *Queries) GetChannelBySCIDWithPolicies(ctx context.Context, arg GetChann &i.GraphNode.LastUpdate, &i.GraphNode.Color, &i.GraphNode.Signature, - &i.GraphNode.BlockHeight, &i.GraphNode_2.ID, &i.GraphNode_2.Version, &i.GraphNode_2.PubKey, @@ -702,7 +644,6 @@ func (q *Queries) GetChannelBySCIDWithPolicies(ctx context.Context, arg GetChann &i.GraphNode_2.LastUpdate, &i.GraphNode_2.Color, &i.GraphNode_2.Signature, - &i.GraphNode_2.BlockHeight, &i.Policy1ID, &i.Policy1NodeID, &i.Policy1Version, @@ -718,8 +659,6 @@ func (q *Queries) GetChannelBySCIDWithPolicies(ctx context.Context, arg GetChann &i.Policy1MessageFlags, &i.Policy1ChannelFlags, &i.Policy1Signature, - &i.Policy1BlockHeight, - &i.Policy1DisableFlags, &i.Policy2ID, &i.Policy2NodeID, &i.Policy2Version, @@ -735,8 +674,6 @@ func (q *Queries) GetChannelBySCIDWithPolicies(ctx context.Context, arg GetChann &i.Policy2MessageFlags, &i.Policy2ChannelFlags, &i.Policy2Signature, - &i.Policy2BlockHeight, - &i.Policy2DisableFlags, ) return i, err } @@ -827,7 +764,7 @@ func (q *Queries) GetChannelFeaturesBatch(ctx context.Context, chanIds []int64) } const getChannelPolicyByChannelAndNode = `-- name: GetChannelPolicyByChannelAndNode :one -SELECT id, version, channel_id, node_id, timelock, fee_ppm, base_fee_msat, min_htlc_msat, max_htlc_msat, last_update, disabled, inbound_base_fee_msat, inbound_fee_rate_milli_msat, message_flags, channel_flags, signature, block_height, disable_flags +SELECT id, version, channel_id, node_id, timelock, fee_ppm, base_fee_msat, min_htlc_msat, max_htlc_msat, last_update, disabled, inbound_base_fee_msat, inbound_fee_rate_milli_msat, message_flags, channel_flags, signature FROM graph_channel_policies WHERE channel_id = $1 AND node_id = $2 @@ -860,8 +797,6 @@ func (q *Queries) GetChannelPolicyByChannelAndNode(ctx context.Context, arg GetC &i.MessageFlags, &i.ChannelFlags, &i.Signature, - &i.BlockHeight, - &i.DisableFlags, ) return i, err } @@ -917,7 +852,7 @@ func (q *Queries) GetChannelPolicyExtraTypesBatch(ctx context.Context, policyIds const getChannelsByIDs = `-- name: GetChannelsByIDs :many SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, + c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, -- Minimal node data. n1.id AS node1_id, @@ -941,8 +876,6 @@ SELECT cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, - cp1.block_height AS policy1_block_height, - cp1.disable_flags AS policy1_disable_flags, -- Policy 2 cp2.id AS policy2_id, @@ -959,9 +892,7 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature, - cp2.block_height AS policy2_block_height, - cp2.disable_flags AS policy2_disable_flags + cp2.signature AS policy2_signature FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -994,8 +925,6 @@ type GetChannelsByIDsRow struct { Policy1MessageFlags sql.NullInt16 Policy1ChannelFlags sql.NullInt16 Policy1Signature []byte - Policy1BlockHeight sql.NullInt64 - Policy1DisableFlags sql.NullInt16 Policy2ID sql.NullInt64 Policy2NodeID sql.NullInt64 Policy2Version sql.NullInt16 @@ -1011,8 +940,6 @@ type GetChannelsByIDsRow struct { Policy2MessageFlags sql.NullInt16 Policy2ChannelFlags sql.NullInt16 Policy2Signature []byte - Policy2BlockHeight sql.NullInt64 - Policy2DisableFlags sql.NullInt16 } func (q *Queries) GetChannelsByIDs(ctx context.Context, ids []int64) ([]GetChannelsByIDsRow, error) { @@ -1048,9 +975,6 @@ func (q *Queries) GetChannelsByIDs(ctx context.Context, ids []int64) ([]GetChann &i.GraphChannel.Node2Signature, &i.GraphChannel.Bitcoin1Signature, &i.GraphChannel.Bitcoin2Signature, - &i.GraphChannel.Signature, - &i.GraphChannel.FundingPkScript, - &i.GraphChannel.MerkleRootHash, &i.Node1ID, &i.Node1PubKey, &i.Node2ID, @@ -1070,8 +994,6 @@ func (q *Queries) GetChannelsByIDs(ctx context.Context, ids []int64) ([]GetChann &i.Policy1MessageFlags, &i.Policy1ChannelFlags, &i.Policy1Signature, - &i.Policy1BlockHeight, - &i.Policy1DisableFlags, &i.Policy2ID, &i.Policy2NodeID, &i.Policy2Version, @@ -1087,8 +1009,6 @@ func (q *Queries) GetChannelsByIDs(ctx context.Context, ids []int64) ([]GetChann &i.Policy2MessageFlags, &i.Policy2ChannelFlags, &i.Policy2Signature, - &i.Policy2BlockHeight, - &i.Policy2DisableFlags, ); err != nil { return nil, err } @@ -1105,7 +1025,7 @@ func (q *Queries) GetChannelsByIDs(ctx context.Context, ids []int64) ([]GetChann const getChannelsByOutpoints = `-- name: GetChannelsByOutpoints :many SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, + c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, n1.pub_key AS node1_pubkey, n2.pub_key AS node2_pubkey FROM graph_channels c @@ -1154,9 +1074,6 @@ func (q *Queries) GetChannelsByOutpoints(ctx context.Context, outpoints []string &i.GraphChannel.Node2Signature, &i.GraphChannel.Bitcoin1Signature, &i.GraphChannel.Bitcoin2Signature, - &i.GraphChannel.Signature, - &i.GraphChannel.FundingPkScript, - &i.GraphChannel.MerkleRootHash, &i.Node1Pubkey, &i.Node2Pubkey, ); err != nil { @@ -1173,240 +1090,11 @@ func (q *Queries) GetChannelsByOutpoints(ctx context.Context, outpoints []string return items, nil } -const getChannelsByPolicyBlockRange = `-- name: GetChannelsByPolicyBlockRange :many -SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, - n1.id, n1.version, n1.pub_key, n1.alias, n1.last_update, n1.color, n1.signature, n1.block_height, - n2.id, n2.version, n2.pub_key, n2.alias, n2.last_update, n2.color, n2.signature, n2.block_height, - - -- Policy 1 (node_id_1) - cp1.id AS policy1_id, - cp1.node_id AS policy1_node_id, - cp1.version AS policy1_version, - cp1.timelock AS policy1_timelock, - cp1.fee_ppm AS policy1_fee_ppm, - cp1.base_fee_msat AS policy1_base_fee_msat, - cp1.min_htlc_msat AS policy1_min_htlc_msat, - cp1.max_htlc_msat AS policy1_max_htlc_msat, - cp1.last_update AS policy1_last_update, - cp1.disabled AS policy1_disabled, - cp1.inbound_base_fee_msat AS policy1_inbound_base_fee_msat, - cp1.inbound_fee_rate_milli_msat AS policy1_inbound_fee_rate_milli_msat, - cp1.message_flags AS policy1_message_flags, - cp1.channel_flags AS policy1_channel_flags, - cp1.signature AS policy1_signature, - cp1.block_height AS policy1_block_height, - cp1.disable_flags AS policy1_disable_flags, - - -- Policy 2 (node_id_2) - cp2.id AS policy2_id, - cp2.node_id AS policy2_node_id, - cp2.version AS policy2_version, - cp2.timelock AS policy2_timelock, - cp2.fee_ppm AS policy2_fee_ppm, - cp2.base_fee_msat AS policy2_base_fee_msat, - cp2.min_htlc_msat AS policy2_min_htlc_msat, - cp2.max_htlc_msat AS policy2_max_htlc_msat, - cp2.last_update AS policy2_last_update, - cp2.disabled AS policy2_disabled, - cp2.inbound_base_fee_msat AS policy2_inbound_base_fee_msat, - cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, - cp2.message_flags AS policy2_message_flags, - cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature, - cp2.block_height AS policy2_block_height, - cp2.disable_flags AS policy2_disable_flags - -FROM graph_channels c - JOIN graph_nodes n1 ON c.node_id_1 = n1.id - JOIN graph_nodes n2 ON c.node_id_2 = n2.id - LEFT JOIN graph_channel_policies cp1 - ON cp1.channel_id = c.id AND cp1.node_id = c.node_id_1 AND cp1.version = c.version - LEFT JOIN graph_channel_policies cp2 - ON cp2.channel_id = c.id AND cp2.node_id = c.node_id_2 AND cp2.version = c.version -WHERE c.version = $1 - AND ( - (cp1.block_height >= $2 AND cp1.block_height < $3) - OR - (cp2.block_height >= $2 AND cp2.block_height < $3) - ) - -- Pagination using compound cursor (max_block_height, id). - -- We use COALESCE with -1 as sentinel since block heights are always positive. - AND ( - (CASE - WHEN COALESCE(cp1.block_height, 0) >= COALESCE(cp2.block_height, 0) - THEN COALESCE(cp1.block_height, 0) - ELSE COALESCE(cp2.block_height, 0) - END > COALESCE($4, -1)) - OR - (CASE - WHEN COALESCE(cp1.block_height, 0) >= COALESCE(cp2.block_height, 0) - THEN COALESCE(cp1.block_height, 0) - ELSE COALESCE(cp2.block_height, 0) - END = COALESCE($4, -1) - AND c.id > COALESCE($5, -1)) - ) -ORDER BY - CASE - WHEN COALESCE(cp1.block_height, 0) >= COALESCE(cp2.block_height, 0) - THEN COALESCE(cp1.block_height, 0) - ELSE COALESCE(cp2.block_height, 0) - END ASC, - c.id ASC -LIMIT COALESCE($6, 999999999) -` - -type GetChannelsByPolicyBlockRangeParams struct { - Version int16 - StartHeight sql.NullInt64 - EndHeight sql.NullInt64 - LastBlockHeight sql.NullInt64 - LastID sql.NullInt64 - MaxResults interface{} -} - -type GetChannelsByPolicyBlockRangeRow struct { - GraphChannel GraphChannel - GraphNode GraphNode - GraphNode_2 GraphNode - Policy1ID sql.NullInt64 - Policy1NodeID sql.NullInt64 - Policy1Version sql.NullInt16 - Policy1Timelock sql.NullInt32 - Policy1FeePpm sql.NullInt64 - Policy1BaseFeeMsat sql.NullInt64 - Policy1MinHtlcMsat sql.NullInt64 - Policy1MaxHtlcMsat sql.NullInt64 - Policy1LastUpdate sql.NullInt64 - Policy1Disabled sql.NullBool - Policy1InboundBaseFeeMsat sql.NullInt64 - Policy1InboundFeeRateMilliMsat sql.NullInt64 - Policy1MessageFlags sql.NullInt16 - Policy1ChannelFlags sql.NullInt16 - Policy1Signature []byte - Policy1BlockHeight sql.NullInt64 - Policy1DisableFlags sql.NullInt16 - Policy2ID sql.NullInt64 - Policy2NodeID sql.NullInt64 - Policy2Version sql.NullInt16 - Policy2Timelock sql.NullInt32 - Policy2FeePpm sql.NullInt64 - Policy2BaseFeeMsat sql.NullInt64 - Policy2MinHtlcMsat sql.NullInt64 - Policy2MaxHtlcMsat sql.NullInt64 - Policy2LastUpdate sql.NullInt64 - Policy2Disabled sql.NullBool - Policy2InboundBaseFeeMsat sql.NullInt64 - Policy2InboundFeeRateMilliMsat sql.NullInt64 - Policy2MessageFlags sql.NullInt16 - Policy2ChannelFlags sql.NullInt16 - Policy2Signature []byte - Policy2BlockHeight sql.NullInt64 - Policy2DisableFlags sql.NullInt16 -} - -func (q *Queries) GetChannelsByPolicyBlockRange(ctx context.Context, arg GetChannelsByPolicyBlockRangeParams) ([]GetChannelsByPolicyBlockRangeRow, error) { - rows, err := q.db.QueryContext(ctx, getChannelsByPolicyBlockRange, - arg.Version, - arg.StartHeight, - arg.EndHeight, - arg.LastBlockHeight, - arg.LastID, - arg.MaxResults, - ) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GetChannelsByPolicyBlockRangeRow - for rows.Next() { - var i GetChannelsByPolicyBlockRangeRow - if err := rows.Scan( - &i.GraphChannel.ID, - &i.GraphChannel.Version, - &i.GraphChannel.Scid, - &i.GraphChannel.NodeID1, - &i.GraphChannel.NodeID2, - &i.GraphChannel.Outpoint, - &i.GraphChannel.Capacity, - &i.GraphChannel.BitcoinKey1, - &i.GraphChannel.BitcoinKey2, - &i.GraphChannel.Node1Signature, - &i.GraphChannel.Node2Signature, - &i.GraphChannel.Bitcoin1Signature, - &i.GraphChannel.Bitcoin2Signature, - &i.GraphChannel.Signature, - &i.GraphChannel.FundingPkScript, - &i.GraphChannel.MerkleRootHash, - &i.GraphNode.ID, - &i.GraphNode.Version, - &i.GraphNode.PubKey, - &i.GraphNode.Alias, - &i.GraphNode.LastUpdate, - &i.GraphNode.Color, - &i.GraphNode.Signature, - &i.GraphNode.BlockHeight, - &i.GraphNode_2.ID, - &i.GraphNode_2.Version, - &i.GraphNode_2.PubKey, - &i.GraphNode_2.Alias, - &i.GraphNode_2.LastUpdate, - &i.GraphNode_2.Color, - &i.GraphNode_2.Signature, - &i.GraphNode_2.BlockHeight, - &i.Policy1ID, - &i.Policy1NodeID, - &i.Policy1Version, - &i.Policy1Timelock, - &i.Policy1FeePpm, - &i.Policy1BaseFeeMsat, - &i.Policy1MinHtlcMsat, - &i.Policy1MaxHtlcMsat, - &i.Policy1LastUpdate, - &i.Policy1Disabled, - &i.Policy1InboundBaseFeeMsat, - &i.Policy1InboundFeeRateMilliMsat, - &i.Policy1MessageFlags, - &i.Policy1ChannelFlags, - &i.Policy1Signature, - &i.Policy1BlockHeight, - &i.Policy1DisableFlags, - &i.Policy2ID, - &i.Policy2NodeID, - &i.Policy2Version, - &i.Policy2Timelock, - &i.Policy2FeePpm, - &i.Policy2BaseFeeMsat, - &i.Policy2MinHtlcMsat, - &i.Policy2MaxHtlcMsat, - &i.Policy2LastUpdate, - &i.Policy2Disabled, - &i.Policy2InboundBaseFeeMsat, - &i.Policy2InboundFeeRateMilliMsat, - &i.Policy2MessageFlags, - &i.Policy2ChannelFlags, - &i.Policy2Signature, - &i.Policy2BlockHeight, - &i.Policy2DisableFlags, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - const getChannelsByPolicyLastUpdateRange = `-- name: GetChannelsByPolicyLastUpdateRange :many SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, - n1.id, n1.version, n1.pub_key, n1.alias, n1.last_update, n1.color, n1.signature, n1.block_height, - n2.id, n2.version, n2.pub_key, n2.alias, n2.last_update, n2.color, n2.signature, n2.block_height, + c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, + n1.id, n1.version, n1.pub_key, n1.alias, n1.last_update, n1.color, n1.signature, + n2.id, n2.version, n2.pub_key, n2.alias, n2.last_update, n2.color, n2.signature, -- Policy 1 (node_id_1) cp1.id AS policy1_id, @@ -1424,8 +1112,6 @@ SELECT cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, - cp1.block_height AS policy1_block_height, - cp1.disable_flags AS policy1_disable_flags, -- Policy 2 (node_id_2) cp2.id AS policy2_id, @@ -1442,9 +1128,7 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature, - cp2.block_height AS policy2_block_height, - cp2.disable_flags AS policy2_disable_flags + cp2.signature AS policy2_signature FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -1513,8 +1197,6 @@ type GetChannelsByPolicyLastUpdateRangeRow struct { Policy1MessageFlags sql.NullInt16 Policy1ChannelFlags sql.NullInt16 Policy1Signature []byte - Policy1BlockHeight sql.NullInt64 - Policy1DisableFlags sql.NullInt16 Policy2ID sql.NullInt64 Policy2NodeID sql.NullInt64 Policy2Version sql.NullInt16 @@ -1530,8 +1212,6 @@ type GetChannelsByPolicyLastUpdateRangeRow struct { Policy2MessageFlags sql.NullInt16 Policy2ChannelFlags sql.NullInt16 Policy2Signature []byte - Policy2BlockHeight sql.NullInt64 - Policy2DisableFlags sql.NullInt16 } func (q *Queries) GetChannelsByPolicyLastUpdateRange(ctx context.Context, arg GetChannelsByPolicyLastUpdateRangeParams) ([]GetChannelsByPolicyLastUpdateRangeRow, error) { @@ -1564,9 +1244,6 @@ func (q *Queries) GetChannelsByPolicyLastUpdateRange(ctx context.Context, arg Ge &i.GraphChannel.Node2Signature, &i.GraphChannel.Bitcoin1Signature, &i.GraphChannel.Bitcoin2Signature, - &i.GraphChannel.Signature, - &i.GraphChannel.FundingPkScript, - &i.GraphChannel.MerkleRootHash, &i.GraphNode.ID, &i.GraphNode.Version, &i.GraphNode.PubKey, @@ -1574,7 +1251,6 @@ func (q *Queries) GetChannelsByPolicyLastUpdateRange(ctx context.Context, arg Ge &i.GraphNode.LastUpdate, &i.GraphNode.Color, &i.GraphNode.Signature, - &i.GraphNode.BlockHeight, &i.GraphNode_2.ID, &i.GraphNode_2.Version, &i.GraphNode_2.PubKey, @@ -1582,7 +1258,6 @@ func (q *Queries) GetChannelsByPolicyLastUpdateRange(ctx context.Context, arg Ge &i.GraphNode_2.LastUpdate, &i.GraphNode_2.Color, &i.GraphNode_2.Signature, - &i.GraphNode_2.BlockHeight, &i.Policy1ID, &i.Policy1NodeID, &i.Policy1Version, @@ -1598,8 +1273,6 @@ func (q *Queries) GetChannelsByPolicyLastUpdateRange(ctx context.Context, arg Ge &i.Policy1MessageFlags, &i.Policy1ChannelFlags, &i.Policy1Signature, - &i.Policy1BlockHeight, - &i.Policy1DisableFlags, &i.Policy2ID, &i.Policy2NodeID, &i.Policy2Version, @@ -1615,8 +1288,6 @@ func (q *Queries) GetChannelsByPolicyLastUpdateRange(ctx context.Context, arg Ge &i.Policy2MessageFlags, &i.Policy2ChannelFlags, &i.Policy2Signature, - &i.Policy2BlockHeight, - &i.Policy2DisableFlags, ); err != nil { return nil, err } @@ -1632,7 +1303,7 @@ func (q *Queries) GetChannelsByPolicyLastUpdateRange(ctx context.Context, arg Ge } const getChannelsBySCIDRange = `-- name: GetChannelsBySCIDRange :many -SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, +SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, n1.pub_key AS node1_pub_key, n2.pub_key AS node2_pub_key FROM graph_channels c @@ -1676,9 +1347,6 @@ func (q *Queries) GetChannelsBySCIDRange(ctx context.Context, arg GetChannelsByS &i.GraphChannel.Node2Signature, &i.GraphChannel.Bitcoin1Signature, &i.GraphChannel.Bitcoin2Signature, - &i.GraphChannel.Signature, - &i.GraphChannel.FundingPkScript, - &i.GraphChannel.MerkleRootHash, &i.Node1PubKey, &i.Node2PubKey, ); err != nil { @@ -1697,9 +1365,9 @@ func (q *Queries) GetChannelsBySCIDRange(ctx context.Context, arg GetChannelsByS const getChannelsBySCIDWithPolicies = `-- name: GetChannelsBySCIDWithPolicies :many SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, - n1.id, n1.version, n1.pub_key, n1.alias, n1.last_update, n1.color, n1.signature, n1.block_height, - n2.id, n2.version, n2.pub_key, n2.alias, n2.last_update, n2.color, n2.signature, n2.block_height, + c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, + n1.id, n1.version, n1.pub_key, n1.alias, n1.last_update, n1.color, n1.signature, + n2.id, n2.version, n2.pub_key, n2.alias, n2.last_update, n2.color, n2.signature, -- Policy 1 cp1.id AS policy1_id, @@ -1717,8 +1385,6 @@ SELECT cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, - cp1.block_height AS policy1_block_height, - cp1.disable_flags AS policy1_disable_flags, -- Policy 2 cp2.id AS policy2_id, @@ -1735,9 +1401,7 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy_2_message_flags, cp2.channel_flags AS policy_2_channel_flags, - cp2.signature AS policy2_signature, - cp2.block_height AS policy2_block_height, - cp2.disable_flags AS policy2_disable_flags + cp2.signature AS policy2_signature FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -1775,8 +1439,6 @@ type GetChannelsBySCIDWithPoliciesRow struct { Policy1MessageFlags sql.NullInt16 Policy1ChannelFlags sql.NullInt16 Policy1Signature []byte - Policy1BlockHeight sql.NullInt64 - Policy1DisableFlags sql.NullInt16 Policy2ID sql.NullInt64 Policy2NodeID sql.NullInt64 Policy2Version sql.NullInt16 @@ -1792,8 +1454,6 @@ type GetChannelsBySCIDWithPoliciesRow struct { Policy2MessageFlags sql.NullInt16 Policy2ChannelFlags sql.NullInt16 Policy2Signature []byte - Policy2BlockHeight sql.NullInt64 - Policy2DisableFlags sql.NullInt16 } func (q *Queries) GetChannelsBySCIDWithPolicies(ctx context.Context, arg GetChannelsBySCIDWithPoliciesParams) ([]GetChannelsBySCIDWithPoliciesRow, error) { @@ -1830,9 +1490,6 @@ func (q *Queries) GetChannelsBySCIDWithPolicies(ctx context.Context, arg GetChan &i.GraphChannel.Node2Signature, &i.GraphChannel.Bitcoin1Signature, &i.GraphChannel.Bitcoin2Signature, - &i.GraphChannel.Signature, - &i.GraphChannel.FundingPkScript, - &i.GraphChannel.MerkleRootHash, &i.GraphNode.ID, &i.GraphNode.Version, &i.GraphNode.PubKey, @@ -1840,7 +1497,6 @@ func (q *Queries) GetChannelsBySCIDWithPolicies(ctx context.Context, arg GetChan &i.GraphNode.LastUpdate, &i.GraphNode.Color, &i.GraphNode.Signature, - &i.GraphNode.BlockHeight, &i.GraphNode_2.ID, &i.GraphNode_2.Version, &i.GraphNode_2.PubKey, @@ -1848,7 +1504,6 @@ func (q *Queries) GetChannelsBySCIDWithPolicies(ctx context.Context, arg GetChan &i.GraphNode_2.LastUpdate, &i.GraphNode_2.Color, &i.GraphNode_2.Signature, - &i.GraphNode_2.BlockHeight, &i.Policy1ID, &i.Policy1NodeID, &i.Policy1Version, @@ -1864,8 +1519,6 @@ func (q *Queries) GetChannelsBySCIDWithPolicies(ctx context.Context, arg GetChan &i.Policy1MessageFlags, &i.Policy1ChannelFlags, &i.Policy1Signature, - &i.Policy1BlockHeight, - &i.Policy1DisableFlags, &i.Policy2ID, &i.Policy2NodeID, &i.Policy2Version, @@ -1881,8 +1534,6 @@ func (q *Queries) GetChannelsBySCIDWithPolicies(ctx context.Context, arg GetChan &i.Policy2MessageFlags, &i.Policy2ChannelFlags, &i.Policy2Signature, - &i.Policy2BlockHeight, - &i.Policy2DisableFlags, ); err != nil { return nil, err } @@ -1898,7 +1549,7 @@ func (q *Queries) GetChannelsBySCIDWithPolicies(ctx context.Context, arg GetChan } const getChannelsBySCIDs = `-- name: GetChannelsBySCIDs :many -SELECT id, version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, bitcoin_2_signature, signature, funding_pk_script, merkle_root_hash FROM graph_channels +SELECT id, version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, bitcoin_2_signature FROM graph_channels WHERE version = $1 AND scid IN (/*SLICE:scids*/?) ` @@ -1942,9 +1593,6 @@ func (q *Queries) GetChannelsBySCIDs(ctx context.Context, arg GetChannelsBySCIDs &i.Node2Signature, &i.Bitcoin1Signature, &i.Bitcoin2Signature, - &i.Signature, - &i.FundingPkScript, - &i.MerkleRootHash, ); err != nil { return nil, err } @@ -2108,7 +1756,7 @@ func (q *Queries) GetNodeAddressesBatch(ctx context.Context, ids []int64) ([]Gra } const getNodeByPubKey = `-- name: GetNodeByPubKey :one -SELECT id, version, pub_key, alias, last_update, color, signature, block_height +SELECT id, version, pub_key, alias, last_update, color, signature FROM graph_nodes WHERE pub_key = $1 AND version = $2 @@ -2130,7 +1778,6 @@ func (q *Queries) GetNodeByPubKey(ctx context.Context, arg GetNodeByPubKeyParams &i.LastUpdate, &i.Color, &i.Signature, - &i.BlockHeight, ) return i, err } @@ -2299,91 +1946,8 @@ func (q *Queries) GetNodeIDByPubKey(ctx context.Context, arg GetNodeIDByPubKeyPa return id, err } -const getNodesByBlockHeightRange = `-- name: GetNodesByBlockHeightRange :many -SELECT id, version, pub_key, alias, last_update, color, signature, block_height -FROM graph_nodes -WHERE graph_nodes.version = $1 - AND block_height >= $2 - AND block_height < $3 - -- Pagination: We use (block_height, pub_key) as a compound cursor. - -- This ensures stable ordering and allows us to resume from where we left off. - -- We use COALESCE with -1 as sentinel since block heights are always positive. - AND ( - block_height > COALESCE($4, -1) - OR - (block_height = COALESCE($4, -1) - AND pub_key > $5) - ) - -- Optional filter for public nodes only. - AND ( - COALESCE($6, FALSE) IS FALSE - OR - -- For V2 protocol, a node is public if it has at least one announced - -- v2 channel (indicated by a non-empty channel announcement signature). - EXISTS ( - SELECT 1 - FROM graph_channels c - WHERE c.version = graph_nodes.version - AND COALESCE(length(c.signature), 0) > 0 - AND (c.node_id_1 = graph_nodes.id OR c.node_id_2 = graph_nodes.id) - ) - ) -ORDER BY block_height ASC, pub_key ASC -LIMIT COALESCE($7, 999999999) -` - -type GetNodesByBlockHeightRangeParams struct { - Version int16 - StartHeight sql.NullInt64 - EndHeight sql.NullInt64 - LastBlockHeight sql.NullInt64 - LastPubKey []byte - OnlyPublic interface{} - MaxResults interface{} -} - -func (q *Queries) GetNodesByBlockHeightRange(ctx context.Context, arg GetNodesByBlockHeightRangeParams) ([]GraphNode, error) { - rows, err := q.db.QueryContext(ctx, getNodesByBlockHeightRange, - arg.Version, - arg.StartHeight, - arg.EndHeight, - arg.LastBlockHeight, - arg.LastPubKey, - arg.OnlyPublic, - arg.MaxResults, - ) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GraphNode - for rows.Next() { - var i GraphNode - if err := rows.Scan( - &i.ID, - &i.Version, - &i.PubKey, - &i.Alias, - &i.LastUpdate, - &i.Color, - &i.Signature, - &i.BlockHeight, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - const getNodesByIDs = `-- name: GetNodesByIDs :many -SELECT id, version, pub_key, alias, last_update, color, signature, block_height +SELECT id, version, pub_key, alias, last_update, color, signature FROM graph_nodes WHERE id IN (/*SLICE:ids*/?) ` @@ -2415,7 +1979,6 @@ func (q *Queries) GetNodesByIDs(ctx context.Context, ids []int64) ([]GraphNode, &i.LastUpdate, &i.Color, &i.Signature, - &i.BlockHeight, ); err != nil { return nil, err } @@ -2431,22 +1994,38 @@ func (q *Queries) GetNodesByIDs(ctx context.Context, ids []int64) ([]GraphNode, } const getNodesByLastUpdateRange = `-- name: GetNodesByLastUpdateRange :many -SELECT id, version, pub_key, alias, last_update, color, signature, block_height +SELECT id, version, pub_key, alias, last_update, color, signature FROM graph_nodes -WHERE version = 1 - AND last_update >= $1 - AND last_update < $2 +WHERE last_update >= $1 + AND last_update <= $2 -- Pagination: We use (last_update, pub_key) as a compound cursor. -- This ensures stable ordering and allows us to resume from where we left off. -- We use COALESCE with -1 as sentinel since timestamps are always positive. AND ( + -- Include rows with last_update greater than cursor (or all rows if cursor is -1) last_update > COALESCE($3, -1) - OR - (last_update = COALESCE($3, -1) + OR + -- For rows with same last_update, use pub_key as tiebreaker + (last_update = COALESCE($3, -1) AND pub_key > $4) ) + -- Optional filter for public nodes only + AND ( + -- If only_public is false or not provided, include all nodes + COALESCE($5, FALSE) IS FALSE + OR + -- For V1 protocol, a node is public if it has at least one public channel. + -- A public channel has bitcoin_1_signature set (channel announcement received). + EXISTS ( + SELECT 1 + FROM graph_channels c + WHERE c.version = 1 + AND c.bitcoin_1_signature IS NOT NULL + AND (c.node_id_1 = graph_nodes.id OR c.node_id_2 = graph_nodes.id) + ) + ) ORDER BY last_update ASC, pub_key ASC -LIMIT COALESCE($5, 999999999) +LIMIT COALESCE($6, 999999999) ` type GetNodesByLastUpdateRangeParams struct { @@ -2454,6 +2033,7 @@ type GetNodesByLastUpdateRangeParams struct { EndTime sql.NullInt64 LastUpdate sql.NullInt64 LastPubKey []byte + OnlyPublic interface{} MaxResults interface{} } @@ -2463,6 +2043,7 @@ func (q *Queries) GetNodesByLastUpdateRange(ctx context.Context, arg GetNodesByL arg.EndTime, arg.LastUpdate, arg.LastPubKey, + arg.OnlyPublic, arg.MaxResults, ) if err != nil { @@ -2480,7 +2061,6 @@ func (q *Queries) GetNodesByLastUpdateRange(ctx context.Context, arg GetNodesByL &i.LastUpdate, &i.Color, &i.Signature, - &i.BlockHeight, ); err != nil { return nil, err } @@ -2562,100 +2142,12 @@ func (q *Queries) GetPruneTip(ctx context.Context) (GraphPruneLog, error) { return i, err } -const getPublicNodesByLastUpdateRange = `-- name: GetPublicNodesByLastUpdateRange :many -SELECT id, version, pub_key, alias, last_update, color, signature, block_height -FROM graph_nodes -WHERE version = 1 - AND last_update >= $1 - AND last_update <= $2 - -- Pagination: We use (last_update, pub_key) as a compound cursor. - -- This ensures stable ordering and allows us to resume from where we left off. - -- We use COALESCE with -1 as sentinel since timestamps are always positive. - AND ( - last_update > COALESCE($3, -1) - OR - (last_update = COALESCE($3, -1) - AND pub_key > $4) - ) - AND ( - EXISTS ( - SELECT 1 - FROM graph_channels c - WHERE c.version = 1 - AND COALESCE(length(c.bitcoin_1_signature), 0) > 0 - AND c.node_id_1 = graph_nodes.id - ) - OR EXISTS ( - SELECT 1 - FROM graph_channels c - WHERE c.version = 1 - AND COALESCE(length(c.bitcoin_1_signature), 0) > 0 - AND c.node_id_2 = graph_nodes.id - ) - ) -ORDER BY last_update ASC, pub_key ASC -LIMIT COALESCE($5, 999999999) -` - -type GetPublicNodesByLastUpdateRangeParams struct { - StartTime sql.NullInt64 - EndTime sql.NullInt64 - LastUpdate sql.NullInt64 - LastPubKey []byte - MaxResults interface{} -} - -// Returns only public V1 nodes within the given last_update range. A V1 node -// is public if it has at least one channel with a bitcoin_1_signature set. The -// public check uses two separate EXISTS probes (one per node_id column) -// instead of a single OR on node_id_1/node_id_2 so the planner can use the -// channel node-id indexes directly. -func (q *Queries) GetPublicNodesByLastUpdateRange(ctx context.Context, arg GetPublicNodesByLastUpdateRangeParams) ([]GraphNode, error) { - rows, err := q.db.QueryContext(ctx, getPublicNodesByLastUpdateRange, - arg.StartTime, - arg.EndTime, - arg.LastUpdate, - arg.LastPubKey, - arg.MaxResults, - ) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GraphNode - for rows.Next() { - var i GraphNode - if err := rows.Scan( - &i.ID, - &i.Version, - &i.PubKey, - &i.Alias, - &i.LastUpdate, - &i.Color, - &i.Signature, - &i.BlockHeight, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - const getPublicV1ChannelsBySCID = `-- name: GetPublicV1ChannelsBySCID :many -SELECT id, version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, bitcoin_2_signature, signature, funding_pk_script, merkle_root_hash +SELECT id, version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, bitcoin_2_signature FROM graph_channels -WHERE version = 1 - AND COALESCE(length(node_1_signature), 0) > 0 +WHERE node_1_signature IS NOT NULL AND scid >= $1 AND scid < $2 -ORDER BY scid ASC ` type GetPublicV1ChannelsBySCIDParams struct { @@ -2686,64 +2178,6 @@ func (q *Queries) GetPublicV1ChannelsBySCID(ctx context.Context, arg GetPublicV1 &i.Node2Signature, &i.Bitcoin1Signature, &i.Bitcoin2Signature, - &i.Signature, - &i.FundingPkScript, - &i.MerkleRootHash, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getPublicV2ChannelsBySCID = `-- name: GetPublicV2ChannelsBySCID :many -SELECT id, version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, bitcoin_2_signature, signature, funding_pk_script, merkle_root_hash -FROM graph_channels -WHERE version = 2 - AND COALESCE(length(signature), 0) > 0 - AND scid >= $1 - AND scid < $2 -ORDER BY scid ASC -` - -type GetPublicV2ChannelsBySCIDParams struct { - StartScid []byte - EndScid []byte -} - -func (q *Queries) GetPublicV2ChannelsBySCID(ctx context.Context, arg GetPublicV2ChannelsBySCIDParams) ([]GraphChannel, error) { - rows, err := q.db.QueryContext(ctx, getPublicV2ChannelsBySCID, arg.StartScid, arg.EndScid) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GraphChannel - for rows.Next() { - var i GraphChannel - if err := rows.Scan( - &i.ID, - &i.Version, - &i.Scid, - &i.NodeID1, - &i.NodeID2, - &i.Outpoint, - &i.Capacity, - &i.BitcoinKey1, - &i.BitcoinKey2, - &i.Node1Signature, - &i.Node2Signature, - &i.Bitcoin1Signature, - &i.Bitcoin2Signature, - &i.Signature, - &i.FundingPkScript, - &i.MerkleRootHash, ); err != nil { return nil, err } @@ -2847,41 +2281,6 @@ func (q *Queries) GetV1DisabledSCIDs(ctx context.Context) ([][]byte, error) { return items, nil } -const getV2DisabledSCIDs = `-- name: GetV2DisabledSCIDs :many -SELECT c.scid -FROM graph_channels c - JOIN graph_channel_policies cp ON cp.channel_id = c.id -WHERE COALESCE(cp.disable_flags, 0) != 0 -AND c.version = 2 -GROUP BY c.scid -HAVING COUNT(*) > 1 -` - -// NOTE: this is V2 specific since V2 uses a disable flag -// bit vector instead of a single boolean. -func (q *Queries) GetV2DisabledSCIDs(ctx context.Context) ([][]byte, error) { - rows, err := q.db.QueryContext(ctx, getV2DisabledSCIDs) - if err != nil { - return nil, err - } - defer rows.Close() - var items [][]byte - for rows.Next() { - var scid []byte - if err := rows.Scan(&scid); err != nil { - return nil, err - } - items = append(items, scid) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - const getZombieChannel = `-- name: GetZombieChannel :one SELECT scid, version, node_key_1, node_key_2 FROM graph_zombie_channels @@ -3264,14 +2663,14 @@ SELECT EXISTS ( -- one of the signatures since we only ever set them -- together. WHERE c.version = 1 - AND COALESCE(length(c.bitcoin_1_signature), 0) > 0 + AND c.bitcoin_1_signature IS NOT NULL AND n.pub_key = $1 UNION ALL SELECT 1 FROM graph_channels c JOIN graph_nodes n ON n.id = c.node_id_2 WHERE c.version = 1 - AND COALESCE(length(c.bitcoin_1_signature), 0) > 0 + AND c.bitcoin_1_signature IS NOT NULL AND n.pub_key = $1 ) ` @@ -3283,36 +2682,6 @@ func (q *Queries) IsPublicV1Node(ctx context.Context, pubKey []byte) (bool, erro return exists, err } -const isPublicV2Node = `-- name: IsPublicV2Node :one -SELECT EXISTS ( - SELECT 1 - FROM graph_channels c - JOIN graph_nodes n ON n.id = c.node_id_1 - -- NOTE: we hard-code the version here since the clauses - -- here that determine if a node is public is specific - -- to the V2 gossip protocol. - WHERE c.version = 2 - AND COALESCE(length(c.signature), 0) > 0 - AND n.pub_key = $1 - - UNION ALL - - SELECT 1 - FROM graph_channels c - JOIN graph_nodes n ON n.id = c.node_id_2 - WHERE c.version = 2 - AND COALESCE(length(c.signature), 0) > 0 - AND n.pub_key = $1 -) -` - -func (q *Queries) IsPublicV2Node(ctx context.Context, pubKey []byte) (bool, error) { - row := q.db.QueryRowContext(ctx, isPublicV2Node, pubKey) - var exists bool - err := row.Scan(&exists) - return exists, err -} - const isZombieChannel = `-- name: IsZombieChannel :one SELECT EXISTS ( SELECT 1 @@ -3335,7 +2704,7 @@ func (q *Queries) IsZombieChannel(ctx context.Context, arg IsZombieChannelParams } const listChannelsByNodeID = `-- name: ListChannelsByNodeID :many -SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, +SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, n1.pub_key AS node1_pubkey, n2.pub_key AS node2_pubkey, @@ -3358,8 +2727,6 @@ SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, - cp1.block_height AS policy1_block_height, - cp1.disable_flags AS policy1_disable_flags, -- Policy 2 cp2.id AS policy2_id, @@ -3376,9 +2743,7 @@ SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature, - cp2.block_height AS policy2_block_height, - cp2.disable_flags AS policy2_disable_flags + cp2.signature AS policy2_signature FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -3415,8 +2780,6 @@ type ListChannelsByNodeIDRow struct { Policy1MessageFlags sql.NullInt16 Policy1ChannelFlags sql.NullInt16 Policy1Signature []byte - Policy1BlockHeight sql.NullInt64 - Policy1DisableFlags sql.NullInt16 Policy2ID sql.NullInt64 Policy2NodeID sql.NullInt64 Policy2Version sql.NullInt16 @@ -3432,8 +2795,6 @@ type ListChannelsByNodeIDRow struct { Policy2MessageFlags sql.NullInt16 Policy2ChannelFlags sql.NullInt16 Policy2Signature []byte - Policy2BlockHeight sql.NullInt64 - Policy2DisableFlags sql.NullInt16 } func (q *Queries) ListChannelsByNodeID(ctx context.Context, arg ListChannelsByNodeIDParams) ([]ListChannelsByNodeIDRow, error) { @@ -3459,9 +2820,6 @@ func (q *Queries) ListChannelsByNodeID(ctx context.Context, arg ListChannelsByNo &i.GraphChannel.Node2Signature, &i.GraphChannel.Bitcoin1Signature, &i.GraphChannel.Bitcoin2Signature, - &i.GraphChannel.Signature, - &i.GraphChannel.FundingPkScript, - &i.GraphChannel.MerkleRootHash, &i.Node1Pubkey, &i.Node2Pubkey, &i.Policy1ID, @@ -3479,8 +2837,6 @@ func (q *Queries) ListChannelsByNodeID(ctx context.Context, arg ListChannelsByNo &i.Policy1MessageFlags, &i.Policy1ChannelFlags, &i.Policy1Signature, - &i.Policy1BlockHeight, - &i.Policy1DisableFlags, &i.Policy2ID, &i.Policy2NodeID, &i.Policy2Version, @@ -3496,8 +2852,6 @@ func (q *Queries) ListChannelsByNodeID(ctx context.Context, arg ListChannelsByNo &i.Policy2MessageFlags, &i.Policy2ChannelFlags, &i.Policy2Signature, - &i.Policy2BlockHeight, - &i.Policy2DisableFlags, ); err != nil { return nil, err } @@ -3513,7 +2867,7 @@ func (q *Queries) ListChannelsByNodeID(ctx context.Context, arg ListChannelsByNo } const listChannelsForNodeIDs = `-- name: ListChannelsForNodeIDs :many -SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, +SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, n1.pub_key AS node1_pubkey, n2.pub_key AS node2_pubkey, @@ -3536,8 +2890,6 @@ SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, - cp1.block_height AS policy1_block_height, - cp1.disable_flags AS policy1_disable_flags, -- Policy 2 cp2.id AS policy2_id, @@ -3554,9 +2906,7 @@ SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature, - cp2.block_height AS policy2_block_height, - cp2.disable_flags AS policy2_disable_flags + cp2.signature AS policy2_signature FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -3595,8 +2945,6 @@ type ListChannelsForNodeIDsRow struct { Policy1MessageFlags sql.NullInt16 Policy1ChannelFlags sql.NullInt16 Policy1Signature []byte - Policy1BlockHeight sql.NullInt64 - Policy1DisableFlags sql.NullInt16 Policy2ID sql.NullInt64 Policy2NodeID sql.NullInt64 Policy2Version sql.NullInt16 @@ -3612,8 +2960,6 @@ type ListChannelsForNodeIDsRow struct { Policy2MessageFlags sql.NullInt16 Policy2ChannelFlags sql.NullInt16 Policy2Signature []byte - Policy2BlockHeight sql.NullInt64 - Policy2DisableFlags sql.NullInt16 } func (q *Queries) ListChannelsForNodeIDs(ctx context.Context, arg ListChannelsForNodeIDsParams) ([]ListChannelsForNodeIDsRow, error) { @@ -3658,9 +3004,6 @@ func (q *Queries) ListChannelsForNodeIDs(ctx context.Context, arg ListChannelsFo &i.GraphChannel.Node2Signature, &i.GraphChannel.Bitcoin1Signature, &i.GraphChannel.Bitcoin2Signature, - &i.GraphChannel.Signature, - &i.GraphChannel.FundingPkScript, - &i.GraphChannel.MerkleRootHash, &i.Node1Pubkey, &i.Node2Pubkey, &i.Policy1ID, @@ -3678,8 +3021,6 @@ func (q *Queries) ListChannelsForNodeIDs(ctx context.Context, arg ListChannelsFo &i.Policy1MessageFlags, &i.Policy1ChannelFlags, &i.Policy1Signature, - &i.Policy1BlockHeight, - &i.Policy1DisableFlags, &i.Policy2ID, &i.Policy2NodeID, &i.Policy2Version, @@ -3695,8 +3036,6 @@ func (q *Queries) ListChannelsForNodeIDs(ctx context.Context, arg ListChannelsFo &i.Policy2MessageFlags, &i.Policy2ChannelFlags, &i.Policy2Signature, - &i.Policy2BlockHeight, - &i.Policy2DisableFlags, ); err != nil { return nil, err } @@ -3760,48 +3099,6 @@ func (q *Queries) ListChannelsPaginated(ctx context.Context, arg ListChannelsPag return items, nil } -const listChannelsPaginatedV2 = `-- name: ListChannelsPaginatedV2 :many -SELECT id, outpoint, funding_pk_script -FROM graph_channels c -WHERE c.version = 2 AND c.id > $1 -ORDER BY c.id -LIMIT $2 -` - -type ListChannelsPaginatedV2Params struct { - ID int64 - Limit int32 -} - -type ListChannelsPaginatedV2Row struct { - ID int64 - Outpoint string - FundingPkScript []byte -} - -func (q *Queries) ListChannelsPaginatedV2(ctx context.Context, arg ListChannelsPaginatedV2Params) ([]ListChannelsPaginatedV2Row, error) { - rows, err := q.db.QueryContext(ctx, listChannelsPaginatedV2, arg.ID, arg.Limit) - if err != nil { - return nil, err - } - defer rows.Close() - var items []ListChannelsPaginatedV2Row - for rows.Next() { - var i ListChannelsPaginatedV2Row - if err := rows.Scan(&i.ID, &i.Outpoint, &i.FundingPkScript); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - const listChannelsWithPoliciesForCachePaginated = `-- name: ListChannelsWithPoliciesForCachePaginated :many SELECT c.id as id, @@ -3813,7 +3110,6 @@ SELECT n2.pub_key AS node2_pubkey, -- Node 1 policy - cp1.version AS policy1_version, cp1.timelock AS policy_1_timelock, cp1.fee_ppm AS policy_1_fee_ppm, cp1.base_fee_msat AS policy_1_base_fee_msat, @@ -3824,11 +3120,8 @@ SELECT cp1.inbound_fee_rate_milli_msat AS policy1_inbound_fee_rate_milli_msat, cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, - cp1.block_height AS policy1_block_height, - cp1.disable_flags AS policy1_disable_flags, -- Node 2 policy - cp2.version AS policy2_version, cp2.timelock AS policy_2_timelock, cp2.fee_ppm AS policy_2_fee_ppm, cp2.base_fee_msat AS policy_2_base_fee_msat, @@ -3838,9 +3131,7 @@ SELECT cp2.inbound_base_fee_msat AS policy2_inbound_base_fee_msat, cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, - cp2.channel_flags AS policy2_channel_flags, - cp2.block_height AS policy2_block_height, - cp2.disable_flags AS policy2_disable_flags + cp2.channel_flags AS policy2_channel_flags FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -3866,7 +3157,6 @@ type ListChannelsWithPoliciesForCachePaginatedRow struct { Capacity sql.NullInt64 Node1Pubkey []byte Node2Pubkey []byte - Policy1Version sql.NullInt16 Policy1Timelock sql.NullInt32 Policy1FeePpm sql.NullInt64 Policy1BaseFeeMsat sql.NullInt64 @@ -3877,9 +3167,6 @@ type ListChannelsWithPoliciesForCachePaginatedRow struct { Policy1InboundFeeRateMilliMsat sql.NullInt64 Policy1MessageFlags sql.NullInt16 Policy1ChannelFlags sql.NullInt16 - Policy1BlockHeight sql.NullInt64 - Policy1DisableFlags sql.NullInt16 - Policy2Version sql.NullInt16 Policy2Timelock sql.NullInt32 Policy2FeePpm sql.NullInt64 Policy2BaseFeeMsat sql.NullInt64 @@ -3890,8 +3177,6 @@ type ListChannelsWithPoliciesForCachePaginatedRow struct { Policy2InboundFeeRateMilliMsat sql.NullInt64 Policy2MessageFlags sql.NullInt16 Policy2ChannelFlags sql.NullInt16 - Policy2BlockHeight sql.NullInt64 - Policy2DisableFlags sql.NullInt16 } func (q *Queries) ListChannelsWithPoliciesForCachePaginated(ctx context.Context, arg ListChannelsWithPoliciesForCachePaginatedParams) ([]ListChannelsWithPoliciesForCachePaginatedRow, error) { @@ -3909,7 +3194,6 @@ func (q *Queries) ListChannelsWithPoliciesForCachePaginated(ctx context.Context, &i.Capacity, &i.Node1Pubkey, &i.Node2Pubkey, - &i.Policy1Version, &i.Policy1Timelock, &i.Policy1FeePpm, &i.Policy1BaseFeeMsat, @@ -3920,9 +3204,6 @@ func (q *Queries) ListChannelsWithPoliciesForCachePaginated(ctx context.Context, &i.Policy1InboundFeeRateMilliMsat, &i.Policy1MessageFlags, &i.Policy1ChannelFlags, - &i.Policy1BlockHeight, - &i.Policy1DisableFlags, - &i.Policy2Version, &i.Policy2Timelock, &i.Policy2FeePpm, &i.Policy2BaseFeeMsat, @@ -3933,8 +3214,6 @@ func (q *Queries) ListChannelsWithPoliciesForCachePaginated(ctx context.Context, &i.Policy2InboundFeeRateMilliMsat, &i.Policy2MessageFlags, &i.Policy2ChannelFlags, - &i.Policy2BlockHeight, - &i.Policy2DisableFlags, ); err != nil { return nil, err } @@ -3951,7 +3230,7 @@ func (q *Queries) ListChannelsWithPoliciesForCachePaginated(ctx context.Context, const listChannelsWithPoliciesPaginated = `-- name: ListChannelsWithPoliciesPaginated :many SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, + c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, -- Join node pubkeys n1.pub_key AS node1_pubkey, @@ -3972,8 +3251,6 @@ SELECT cp1.inbound_fee_rate_milli_msat AS policy1_inbound_fee_rate_milli_msat, cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, - cp1.block_height AS policy1_block_height, - cp1.disable_flags AS policy1_disable_flags, cp1.signature AS policy_1_signature, -- Node 2 policy @@ -3991,9 +3268,7 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy_2_signature, - cp2.block_height AS policy_2_block_height, - cp2.disable_flags AS policy_2_disable_flags + cp2.signature AS policy_2_signature FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -4031,8 +3306,6 @@ type ListChannelsWithPoliciesPaginatedRow struct { Policy1InboundFeeRateMilliMsat sql.NullInt64 Policy1MessageFlags sql.NullInt16 Policy1ChannelFlags sql.NullInt16 - Policy1BlockHeight sql.NullInt64 - Policy1DisableFlags sql.NullInt16 Policy1Signature []byte Policy2ID sql.NullInt64 Policy2NodeID sql.NullInt64 @@ -4049,8 +3322,6 @@ type ListChannelsWithPoliciesPaginatedRow struct { Policy2MessageFlags sql.NullInt16 Policy2ChannelFlags sql.NullInt16 Policy2Signature []byte - Policy2BlockHeight sql.NullInt64 - Policy2DisableFlags sql.NullInt16 } func (q *Queries) ListChannelsWithPoliciesPaginated(ctx context.Context, arg ListChannelsWithPoliciesPaginatedParams) ([]ListChannelsWithPoliciesPaginatedRow, error) { @@ -4076,9 +3347,6 @@ func (q *Queries) ListChannelsWithPoliciesPaginated(ctx context.Context, arg Lis &i.GraphChannel.Node2Signature, &i.GraphChannel.Bitcoin1Signature, &i.GraphChannel.Bitcoin2Signature, - &i.GraphChannel.Signature, - &i.GraphChannel.FundingPkScript, - &i.GraphChannel.MerkleRootHash, &i.Node1Pubkey, &i.Node2Pubkey, &i.Policy1ID, @@ -4095,8 +3363,6 @@ func (q *Queries) ListChannelsWithPoliciesPaginated(ctx context.Context, arg Lis &i.Policy1InboundFeeRateMilliMsat, &i.Policy1MessageFlags, &i.Policy1ChannelFlags, - &i.Policy1BlockHeight, - &i.Policy1DisableFlags, &i.Policy1Signature, &i.Policy2ID, &i.Policy2NodeID, @@ -4113,8 +3379,6 @@ func (q *Queries) ListChannelsWithPoliciesPaginated(ctx context.Context, arg Lis &i.Policy2MessageFlags, &i.Policy2ChannelFlags, &i.Policy2Signature, - &i.Policy2BlockHeight, - &i.Policy2DisableFlags, ); err != nil { return nil, err } @@ -4172,7 +3436,7 @@ func (q *Queries) ListNodeIDsAndPubKeys(ctx context.Context, arg ListNodeIDsAndP } const listNodesPaginated = `-- name: ListNodesPaginated :many -SELECT id, version, pub_key, alias, last_update, color, signature, block_height +SELECT id, version, pub_key, alias, last_update, color, signature FROM graph_nodes WHERE version = $1 AND id > $2 ORDER BY id @@ -4202,7 +3466,6 @@ func (q *Queries) ListNodesPaginated(ctx context.Context, arg ListNodesPaginated &i.LastUpdate, &i.Color, &i.Signature, - &i.BlockHeight, ); err != nil { return nil, err } @@ -4217,27 +3480,6 @@ func (q *Queries) ListNodesPaginated(ctx context.Context, arg ListNodesPaginated return items, nil } -const nodeExists = `-- name: NodeExists :one -SELECT EXISTS ( - SELECT 1 - FROM graph_nodes - WHERE pub_key = $1 - AND version = $2 -) AS node_exists -` - -type NodeExistsParams struct { - PubKey []byte - Version int16 -} - -func (q *Queries) NodeExists(ctx context.Context, arg NodeExistsParams) (bool, error) { - row := q.db.QueryRowContext(ctx, nodeExists, arg.PubKey, arg.Version) - var node_exists bool - err := row.Scan(&node_exists) - return node_exists, err -} - const upsertChanPolicyExtraType = `-- name: UpsertChanPolicyExtraType :exec /* ───────────────────────────────────────────── graph_channel_policy_extra_types table queries @@ -4302,9 +3544,9 @@ INSERT INTO graph_channel_policies ( base_fee_msat, min_htlc_msat, last_update, disabled, max_htlc_msat, inbound_base_fee_msat, inbound_fee_rate_milli_msat, message_flags, channel_flags, - signature, block_height, disable_flags + signature ) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17 + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15 ) ON CONFLICT (channel_id, node_id, version) -- Update the following fields if a conflict occurs on channel_id, @@ -4321,21 +3563,8 @@ ON CONFLICT (channel_id, node_id, version) inbound_fee_rate_milli_msat = EXCLUDED.inbound_fee_rate_milli_msat, message_flags = EXCLUDED.message_flags, channel_flags = EXCLUDED.channel_flags, - signature = EXCLUDED.signature, - block_height = EXCLUDED.block_height, - disable_flags = EXCLUDED.disable_flags -WHERE ( - EXCLUDED.version = 1 AND ( - graph_channel_policies.last_update IS NULL - OR EXCLUDED.last_update > graph_channel_policies.last_update - ) -) -OR ( - EXCLUDED.version = 2 AND ( - graph_channel_policies.block_height IS NULL - OR EXCLUDED.block_height >= graph_channel_policies.block_height - ) -) + signature = EXCLUDED.signature +WHERE EXCLUDED.last_update > graph_channel_policies.last_update RETURNING id ` @@ -4355,8 +3584,6 @@ type UpsertEdgePolicyParams struct { MessageFlags sql.NullInt16 ChannelFlags sql.NullInt16 Signature []byte - BlockHeight sql.NullInt64 - DisableFlags sql.NullInt16 } func (q *Queries) UpsertEdgePolicy(ctx context.Context, arg UpsertEdgePolicyParams) (int64, error) { @@ -4376,8 +3603,6 @@ func (q *Queries) UpsertEdgePolicy(ctx context.Context, arg UpsertEdgePolicyPara arg.MessageFlags, arg.ChannelFlags, arg.Signature, - arg.BlockHeight, - arg.DisableFlags, ) var id int64 err := row.Scan(&id) @@ -4391,9 +3616,9 @@ const upsertNode = `-- name: UpsertNode :one */ INSERT INTO graph_nodes ( - version, pub_key, alias, last_update, block_height, color, signature + version, pub_key, alias, last_update, color, signature ) VALUES ( - $1, $2, $3, $4, $5, $6, $7 + $1, $2, $3, $4, $5, $6 ) ON CONFLICT (pub_key, version) -- Update the following fields if a conflict occurs on pub_key @@ -4401,24 +3626,20 @@ ON CONFLICT (pub_key, version) DO UPDATE SET alias = EXCLUDED.alias, last_update = EXCLUDED.last_update, - block_height = EXCLUDED.block_height, color = EXCLUDED.color, signature = EXCLUDED.signature -WHERE (graph_nodes.last_update IS NULL - OR EXCLUDED.last_update > graph_nodes.last_update) -AND (graph_nodes.block_height IS NULL - OR EXCLUDED.block_height >= graph_nodes.block_height) +WHERE graph_nodes.last_update IS NULL + OR EXCLUDED.last_update > graph_nodes.last_update RETURNING id ` type UpsertNodeParams struct { - Version int16 - PubKey []byte - Alias sql.NullString - LastUpdate sql.NullInt64 - BlockHeight sql.NullInt64 - Color sql.NullString - Signature []byte + Version int16 + PubKey []byte + Alias sql.NullString + LastUpdate sql.NullInt64 + Color sql.NullString + Signature []byte } func (q *Queries) UpsertNode(ctx context.Context, arg UpsertNodeParams) (int64, error) { @@ -4427,7 +3648,6 @@ func (q *Queries) UpsertNode(ctx context.Context, arg UpsertNodeParams) (int64, arg.PubKey, arg.Alias, arg.LastUpdate, - arg.BlockHeight, arg.Color, arg.Signature, ) @@ -4524,9 +3744,9 @@ func (q *Queries) UpsertPruneLogEntry(ctx context.Context, arg UpsertPruneLogEnt const upsertSourceNode = `-- name: UpsertSourceNode :one INSERT INTO graph_nodes ( - version, pub_key, alias, last_update, block_height, color, signature + version, pub_key, alias, last_update, color, signature ) VALUES ( - $1, $2, $3, $4, $5, $6, $7 + $1, $2, $3, $4, $5, $6 ) ON CONFLICT (pub_key, version) -- Update the following fields if a conflict occurs on pub_key @@ -4534,24 +3754,20 @@ ON CONFLICT (pub_key, version) DO UPDATE SET alias = EXCLUDED.alias, last_update = EXCLUDED.last_update, - block_height = EXCLUDED.block_height, color = EXCLUDED.color, signature = EXCLUDED.signature WHERE graph_nodes.last_update IS NULL OR EXCLUDED.last_update >= graph_nodes.last_update -AND (graph_nodes.block_height IS NULL - OR EXCLUDED.block_height >= graph_nodes.block_height) RETURNING id ` type UpsertSourceNodeParams struct { - Version int16 - PubKey []byte - Alias sql.NullString - LastUpdate sql.NullInt64 - BlockHeight sql.NullInt64 - Color sql.NullString - Signature []byte + Version int16 + PubKey []byte + Alias sql.NullString + LastUpdate sql.NullInt64 + Color sql.NullString + Signature []byte } // We use a separate upsert for our own node since we want to be less strict @@ -4563,7 +3779,6 @@ func (q *Queries) UpsertSourceNode(ctx context.Context, arg UpsertSourceNodePara arg.PubKey, arg.Alias, arg.LastUpdate, - arg.BlockHeight, arg.Color, arg.Signature, ) diff --git a/sqldb/sqlc/invoices.sql.go b/sqldb/sqlc/invoices.sql.go index c7bb8d11c..178e70d49 100644 --- a/sqldb/sqlc/invoices.sql.go +++ b/sqldb/sqlc/invoices.sql.go @@ -64,302 +64,75 @@ func (q *Queries) DeleteInvoice(ctx context.Context, arg DeleteInvoiceParams) (s ) } -const fetchPendingInvoices = `-- name: FetchPendingInvoices :many +const filterInvoices = `-- name: FilterInvoices :many SELECT invoices.id, invoices.hash, invoices.preimage, invoices.settle_index, invoices.settled_at, invoices.memo, invoices.amount_msat, invoices.cltv_delta, invoices.expiry, invoices.payment_addr, invoices.payment_request, invoices.payment_request_hash, invoices.state, invoices.amount_paid_msat, invoices.is_amp, invoices.is_hodl, invoices.is_keysend, invoices.created_at FROM invoices -WHERE state IN (0, 3) -- 0 = ContractOpen, 3 = ContractAccepted - AND id > $1 -ORDER BY id ASC -LIMIT $2 +WHERE ( + id >= $1 OR + $1 IS NULL +) AND ( + id <= $2 OR + $2 IS NULL +) AND ( + settle_index >= $3 OR + $3 IS NULL +) AND ( + settle_index <= $4 OR + $4 IS NULL +) AND ( + state = $5 OR + $5 IS NULL +) AND ( + created_at >= $6 OR + $6 IS NULL +) AND ( + created_at < $7 OR + $7 IS NULL +) AND ( + CASE + WHEN $8 = TRUE THEN (state = 0 OR state = 3) + ELSE TRUE + END +) +ORDER BY +CASE + WHEN $9 = FALSE OR $9 IS NULL THEN id + ELSE NULL + END ASC, +CASE + WHEN $9 = TRUE THEN id + ELSE NULL +END DESC +LIMIT $11 OFFSET $10 ` -type FetchPendingInvoicesParams struct { - IDCursor int64 - NumLimit int32 -} - -// FetchPendingInvoices returns all invoices in a pending state (open or -// accepted). The invoices_state_idx index on the state column makes this a -// fast index scan rather than a full table scan. id_cursor is an exclusive -// lower bound on the primary key used for cursor-based pagination; the caller -// must supply 0 when starting from the beginning. -func (q *Queries) FetchPendingInvoices(ctx context.Context, arg FetchPendingInvoicesParams) ([]Invoice, error) { - rows, err := q.db.QueryContext(ctx, fetchPendingInvoices, arg.IDCursor, arg.NumLimit) - if err != nil { - return nil, err - } - defer rows.Close() - var items []Invoice - for rows.Next() { - var i Invoice - if err := rows.Scan( - &i.ID, - &i.Hash, - &i.Preimage, - &i.SettleIndex, - &i.SettledAt, - &i.Memo, - &i.AmountMsat, - &i.CltvDelta, - &i.Expiry, - &i.PaymentAddr, - &i.PaymentRequest, - &i.PaymentRequestHash, - &i.State, - &i.AmountPaidMsat, - &i.IsAmp, - &i.IsHodl, - &i.IsKeysend, - &i.CreatedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const filterInvoicesByAddIndex = `-- name: FilterInvoicesByAddIndex :many -SELECT - invoices.id, invoices.hash, invoices.preimage, invoices.settle_index, invoices.settled_at, invoices.memo, invoices.amount_msat, invoices.cltv_delta, invoices.expiry, invoices.payment_addr, invoices.payment_request, invoices.payment_request_hash, invoices.state, invoices.amount_paid_msat, invoices.is_amp, invoices.is_hodl, invoices.is_keysend, invoices.created_at -FROM invoices -WHERE id >= $1 -ORDER BY id ASC -LIMIT $2 -` - -type FilterInvoicesByAddIndexParams struct { - AddIndexGet int64 - NumLimit int32 -} - -// FilterInvoicesByAddIndex returns invoices whose add_index (primary key id) -// is greater than or equal to the given value, ordered by id. Because id is -// the primary key, this is always an efficient range scan on the clustered -// index. For cursor-based pagination the caller advances add_index_get to -// last_returned_id + 1 on each subsequent page. -func (q *Queries) FilterInvoicesByAddIndex(ctx context.Context, arg FilterInvoicesByAddIndexParams) ([]Invoice, error) { - rows, err := q.db.QueryContext(ctx, filterInvoicesByAddIndex, arg.AddIndexGet, arg.NumLimit) - if err != nil { - return nil, err - } - defer rows.Close() - var items []Invoice - for rows.Next() { - var i Invoice - if err := rows.Scan( - &i.ID, - &i.Hash, - &i.Preimage, - &i.SettleIndex, - &i.SettledAt, - &i.Memo, - &i.AmountMsat, - &i.CltvDelta, - &i.Expiry, - &i.PaymentAddr, - &i.PaymentRequest, - &i.PaymentRequestHash, - &i.State, - &i.AmountPaidMsat, - &i.IsAmp, - &i.IsHodl, - &i.IsKeysend, - &i.CreatedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const filterInvoicesBySettleIndex = `-- name: FilterInvoicesBySettleIndex :many -SELECT - invoices.id, invoices.hash, invoices.preimage, invoices.settle_index, invoices.settled_at, invoices.memo, invoices.amount_msat, invoices.cltv_delta, invoices.expiry, invoices.payment_addr, invoices.payment_request, invoices.payment_request_hash, invoices.state, invoices.amount_paid_msat, invoices.is_amp, invoices.is_hodl, invoices.is_keysend, invoices.created_at -FROM invoices -WHERE settle_index >= $1 - AND id > $2 -ORDER BY id ASC -LIMIT $3 -` - -type FilterInvoicesBySettleIndexParams struct { +type FilterInvoicesParams struct { + AddIndexGet sql.NullInt64 + AddIndexLet sql.NullInt64 SettleIndexGet sql.NullInt64 - IDCursor int64 + SettleIndexLet sql.NullInt64 + State sql.NullInt16 + CreatedAfter sql.NullTime + CreatedBefore sql.NullTime + PendingOnly interface{} + Reverse interface{} + NumOffset int32 NumLimit int32 } -// FilterInvoicesBySettleIndex returns settled invoices whose settle_index is -// greater than or equal to the given value, ordered by id. The caller must -// always supply a concrete lower bound so the invoices_settle_index_idx index -// can be used. id_cursor is an exclusive lower bound on the primary key used -// for cursor-based pagination; the caller must supply 0 when starting from -// the beginning. -func (q *Queries) FilterInvoicesBySettleIndex(ctx context.Context, arg FilterInvoicesBySettleIndexParams) ([]Invoice, error) { - rows, err := q.db.QueryContext(ctx, filterInvoicesBySettleIndex, arg.SettleIndexGet, arg.IDCursor, arg.NumLimit) - if err != nil { - return nil, err - } - defer rows.Close() - var items []Invoice - for rows.Next() { - var i Invoice - if err := rows.Scan( - &i.ID, - &i.Hash, - &i.Preimage, - &i.SettleIndex, - &i.SettledAt, - &i.Memo, - &i.AmountMsat, - &i.CltvDelta, - &i.Expiry, - &i.PaymentAddr, - &i.PaymentRequest, - &i.PaymentRequestHash, - &i.State, - &i.AmountPaidMsat, - &i.IsAmp, - &i.IsHodl, - &i.IsKeysend, - &i.CreatedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const filterInvoicesForward = `-- name: FilterInvoicesForward :many -SELECT - invoices.id, invoices.hash, invoices.preimage, invoices.settle_index, invoices.settled_at, invoices.memo, invoices.amount_msat, invoices.cltv_delta, invoices.expiry, invoices.payment_addr, invoices.payment_request, invoices.payment_request_hash, invoices.state, invoices.amount_paid_msat, invoices.is_amp, invoices.is_hodl, invoices.is_keysend, invoices.created_at -FROM invoices -WHERE id >= $1 - AND (NOT $2 OR state IN (0, 3)) -- 0 = ContractOpen, 3 = ContractAccepted - AND created_at >= $3 - AND created_at < $4 -ORDER BY id ASC -LIMIT $5 -` - -type FilterInvoicesForwardParams struct { - AddIndexGet int64 - PendingOnly interface{} - CreatedAfter time.Time - CreatedBefore time.Time - NumLimit int32 -} - -// FilterInvoicesForward returns invoices in ascending id order. All parameters -// are non-nullable so the planner always sees plain range predicates and can -// use the primary-key index. For cursor-based pagination the caller advances -// add_index_get to last_returned_id + 1 on each subsequent page. The caller -// is responsible for supplying Go-side defaults when a filter is not needed: -// -// add_index_get → 1 (first valid invoice id) -// created_after → time.Unix(0, 0).UTC() (epoch – before any invoice) -// created_before → time.Date(9999, …) (far future – no upper cap) -// pending_only → false (include all states) -func (q *Queries) FilterInvoicesForward(ctx context.Context, arg FilterInvoicesForwardParams) ([]Invoice, error) { - rows, err := q.db.QueryContext(ctx, filterInvoicesForward, +func (q *Queries) FilterInvoices(ctx context.Context, arg FilterInvoicesParams) ([]Invoice, error) { + rows, err := q.db.QueryContext(ctx, filterInvoices, arg.AddIndexGet, - arg.PendingOnly, - arg.CreatedAfter, - arg.CreatedBefore, - arg.NumLimit, - ) - if err != nil { - return nil, err - } - defer rows.Close() - var items []Invoice - for rows.Next() { - var i Invoice - if err := rows.Scan( - &i.ID, - &i.Hash, - &i.Preimage, - &i.SettleIndex, - &i.SettledAt, - &i.Memo, - &i.AmountMsat, - &i.CltvDelta, - &i.Expiry, - &i.PaymentAddr, - &i.PaymentRequest, - &i.PaymentRequestHash, - &i.State, - &i.AmountPaidMsat, - &i.IsAmp, - &i.IsHodl, - &i.IsKeysend, - &i.CreatedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const filterInvoicesReverse = `-- name: FilterInvoicesReverse :many -SELECT - invoices.id, invoices.hash, invoices.preimage, invoices.settle_index, invoices.settled_at, invoices.memo, invoices.amount_msat, invoices.cltv_delta, invoices.expiry, invoices.payment_addr, invoices.payment_request, invoices.payment_request_hash, invoices.state, invoices.amount_paid_msat, invoices.is_amp, invoices.is_hodl, invoices.is_keysend, invoices.created_at -FROM invoices -WHERE id <= $1 - AND (NOT $2 OR state IN (0, 3)) -- 0 = ContractOpen, 3 = ContractAccepted - AND created_at >= $3 - AND created_at < $4 -ORDER BY id DESC -LIMIT $5 -` - -type FilterInvoicesReverseParams struct { - AddIndexLet int64 - PendingOnly interface{} - CreatedAfter time.Time - CreatedBefore time.Time - NumLimit int32 -} - -// FilterInvoicesReverse is the descending counterpart of FilterInvoicesForward. -// It returns invoices in descending id order. For cursor-based pagination the -// caller advances add_index_let to last_returned_id - 1 on each subsequent -// page; pass math.MaxInt64 to start from the most recent invoice. See -// FilterInvoicesForward for the expected Go-side defaults. -func (q *Queries) FilterInvoicesReverse(ctx context.Context, arg FilterInvoicesReverseParams) ([]Invoice, error) { - rows, err := q.db.QueryContext(ctx, filterInvoicesReverse, arg.AddIndexLet, - arg.PendingOnly, + arg.SettleIndexGet, + arg.SettleIndexLet, + arg.State, arg.CreatedAfter, arg.CreatedBefore, + arg.PendingOnly, + arg.Reverse, + arg.NumOffset, arg.NumLimit, ) if err != nil { @@ -402,36 +175,84 @@ func (q *Queries) FilterInvoicesReverse(ctx context.Context, arg FilterInvoicesR return items, nil } -const getInvoiceByAddr = `-- name: GetInvoiceByAddr :one +const getInvoice = `-- name: GetInvoice :many + SELECT i.id, i.hash, i.preimage, i.settle_index, i.settled_at, i.memo, i.amount_msat, i.cltv_delta, i.expiry, i.payment_addr, i.payment_request, i.payment_request_hash, i.state, i.amount_paid_msat, i.is_amp, i.is_hodl, i.is_keysend, i.created_at FROM invoices i -WHERE i.payment_addr = $1 +LEFT JOIN amp_sub_invoices a +ON i.id = a.invoice_id +AND ( + a.set_id = $1 OR $1 IS NULL +) +WHERE ( + i.id = $2 OR + $2 IS NULL +) AND ( + i.hash = $3 OR + $3 IS NULL +) AND ( + i.payment_addr = $4 OR + $4 IS NULL +) +GROUP BY i.id +LIMIT 2 ` -func (q *Queries) GetInvoiceByAddr(ctx context.Context, paymentAddr []byte) (Invoice, error) { - row := q.db.QueryRowContext(ctx, getInvoiceByAddr, paymentAddr) - var i Invoice - err := row.Scan( - &i.ID, - &i.Hash, - &i.Preimage, - &i.SettleIndex, - &i.SettledAt, - &i.Memo, - &i.AmountMsat, - &i.CltvDelta, - &i.Expiry, - &i.PaymentAddr, - &i.PaymentRequest, - &i.PaymentRequestHash, - &i.State, - &i.AmountPaidMsat, - &i.IsAmp, - &i.IsHodl, - &i.IsKeysend, - &i.CreatedAt, +type GetInvoiceParams struct { + SetID []byte + AddIndex sql.NullInt64 + Hash []byte + PaymentAddr []byte +} + +// This method may return more than one invoice if filter using multiple fields +// from different invoices. It is the caller's responsibility to ensure that +// we bubble up an error in those cases. +func (q *Queries) GetInvoice(ctx context.Context, arg GetInvoiceParams) ([]Invoice, error) { + rows, err := q.db.QueryContext(ctx, getInvoice, + arg.SetID, + arg.AddIndex, + arg.Hash, + arg.PaymentAddr, ) - return i, err + if err != nil { + return nil, err + } + defer rows.Close() + var items []Invoice + for rows.Next() { + var i Invoice + if err := rows.Scan( + &i.ID, + &i.Hash, + &i.Preimage, + &i.SettleIndex, + &i.SettledAt, + &i.Memo, + &i.AmountMsat, + &i.CltvDelta, + &i.Expiry, + &i.PaymentAddr, + &i.PaymentRequest, + &i.PaymentRequestHash, + &i.State, + &i.AmountPaidMsat, + &i.IsAmp, + &i.IsHodl, + &i.IsKeysend, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil } const getInvoiceByHash = `-- name: GetInvoiceByHash :one @@ -473,8 +294,6 @@ INNER JOIN amp_sub_invoices a ON i.id = a.invoice_id AND a.set_id = $1 ` -// TODO(ziggie): This query can only return one invoice if the set_id is -// the primary key of amp_sub_invoices table. func (q *Queries) GetInvoiceBySetID(ctx context.Context, setID []byte) ([]Invoice, error) { rows, err := q.db.QueryContext(ctx, getInvoiceBySetID, setID) if err != nil { diff --git a/sqldb/sqlc/migrations/000009_graph_v2.down.sql b/sqldb/sqlc/migrations/000009_graph_v2.down.sql deleted file mode 100644 index f3e04d6c5..000000000 --- a/sqldb/sqlc/migrations/000009_graph_v2.down.sql +++ /dev/null @@ -1,31 +0,0 @@ --- Drop v2 block-height indexes. -DROP INDEX IF EXISTS graph_node_block_height_idx; -DROP INDEX IF EXISTS graph_channel_policy_block_height_idx; - --- Restore the original single-column last_update index. -DROP INDEX IF EXISTS graph_node_last_update_idx; -CREATE INDEX IF NOT EXISTS graph_node_last_update_idx ON graph_nodes(last_update); - --- Restore the original single-column channel node-id indexes. -DROP INDEX IF EXISTS graph_channels_node_id_1_idx; -DROP INDEX IF EXISTS graph_channels_node_id_2_idx; -CREATE INDEX IF NOT EXISTS graph_channels_node_id_1_idx ON graph_channels(node_id_1); -CREATE INDEX IF NOT EXISTS graph_channels_node_id_2_idx ON graph_channels(node_id_2); - --- Remove the block_height column from graph_nodes -ALTER TABLE graph_nodes DROP COLUMN block_height; - --- Remove the signature column from graph_channels -ALTER TABLE graph_channels DROP COLUMN signature; - --- Remove the funding_pk_script column from graph_channels -ALTER TABLE graph_channels DROP COLUMN funding_pk_script; - --- Remove the merkle_root_hash column from graph_channels -ALTER TABLE graph_channels DROP COLUMN merkle_root_hash; - --- Remove the block_height column from graph_channel_policies -ALTER TABLE graph_channel_policies DROP COLUMN block_height; - --- Remove the disable_flags column from graph_channel_policies -ALTER TABLE graph_channel_policies DROP COLUMN disable_flags; \ No newline at end of file diff --git a/sqldb/sqlc/migrations/000009_graph_v2.up.sql b/sqldb/sqlc/migrations/000009_graph_v2.up.sql deleted file mode 100644 index fa029d8b6..000000000 --- a/sqldb/sqlc/migrations/000009_graph_v2.up.sql +++ /dev/null @@ -1,57 +0,0 @@ --- The block height timestamp of this node's latest received node announcement. --- It may be zero if we have not received a node announcement yet. -ALTER TABLE graph_nodes ADD COLUMN block_height BIGINT; - --- The signature of the channel announcement. If this is null, then the channel --- belongs to the source node and the channel has not been announced yet. -ALTER TABLE graph_channels ADD COLUMN signature BLOB; - --- For v2 channels onwards, we cant necessarily derive the funding pk script --- from the other fields in the announcement, so we store it here so that --- we have easy access to it when we want to subscribe to channel closures. -ALTER TABLE graph_channels ADD COLUMN funding_pk_script BLOB; - --- The optional merkel root hash advertised in the V2 channel announcement. -ALTER TABLE graph_channels ADD COLUMN merkle_root_hash BLOB; - --- The block height timestamp of this channel's latest received channel-update --- message (for v2 channel update messages). -ALTER TABLE graph_channel_policies ADD COLUMN block_height BIGINT; - --- A bitfield describing the disabled flags for a v2 channel update. -ALTER TABLE graph_channel_policies ADD COLUMN disable_flags SMALLINT - CHECK (disable_flags >= 0 AND disable_flags <= 255); - --- Composite index for v2 node horizon queries. The query filters on --- (version, block_height) for the range scan and then paginates and orders by --- (block_height, pub_key). Including pub_key in the index lets the DB cover --- the ORDER BY without an extra sort and seek directly to the pagination --- cursor position. -CREATE INDEX IF NOT EXISTS graph_node_block_height_idx - ON graph_nodes (version, block_height, pub_key); - --- Index for v2 channel policy horizon queries which filter by gossip version --- and block-height range. The pagination cursor for channel queries uses a --- CASE expression across two joined policy rows (max of both block_heights), --- so the index cannot cover the ORDER BY — (version, block_height) is --- sufficient for the range scan. -CREATE INDEX IF NOT EXISTS graph_channel_policy_block_height_idx - ON graph_channel_policies (version, block_height); - --- Replace the old single-column last_update index with a composite index --- that matches the v1 node horizon query shape: --- WHERE version = 1 AND last_update >= ... ORDER BY last_update, pub_key -DROP INDEX IF EXISTS graph_node_last_update_idx; -CREATE INDEX IF NOT EXISTS graph_node_last_update_idx - ON graph_nodes(version, last_update, pub_key); - --- Replace the single-column channel node-id indexes with composite indexes --- that include version. This helps the version-aware public node checks --- (UNION ALL probes) for both v1 and v2, while still serving node-centric --- lookups like channel iteration and existence checks. -DROP INDEX IF EXISTS graph_channels_node_id_1_idx; -DROP INDEX IF EXISTS graph_channels_node_id_2_idx; -CREATE INDEX IF NOT EXISTS graph_channels_node_id_1_idx - ON graph_channels(node_id_1, version); -CREATE INDEX IF NOT EXISTS graph_channels_node_id_2_idx - ON graph_channels(node_id_2, version); diff --git a/sqldb/sqlc/migrations/000010_payments.down.sql b/sqldb/sqlc/migrations/000010_payments.down.sql deleted file mode 100644 index 68f19f779..000000000 --- a/sqldb/sqlc/migrations/000010_payments.down.sql +++ /dev/null @@ -1,54 +0,0 @@ --- ───────────────────────────────────────────── --- Drop custom TLV record tables first (they have no dependents). --- ───────────────────────────────────────────── - -DROP TABLE IF EXISTS payment_hop_custom_records; -DROP TABLE IF EXISTS payment_attempt_first_hop_custom_records; -DROP TABLE IF EXISTS payment_first_hop_custom_records; - --- ───────────────────────────────────────────── --- Drop per-hop payload tables before dropping the base hops table. --- ───────────────────────────────────────────── - -DROP TABLE IF EXISTS payment_route_hop_blinded; -DROP TABLE IF EXISTS payment_route_hop_amp; -DROP TABLE IF EXISTS payment_route_hop_mpp; - --- ───────────────────────────────────────────── --- Drop route hops table and its indexes. --- ───────────────────────────────────────────── - -DROP INDEX IF EXISTS idx_route_hops_htlc_attempt_index; -DROP TABLE IF EXISTS payment_route_hops; - --- ───────────────────────────────────────────── --- Drop HTLC attempt resolution table and its indexes. --- ───────────────────────────────────────────── - -DROP INDEX IF EXISTS idx_htlc_resolutions_type; -DROP INDEX IF EXISTS idx_htlc_resolutions_time; -DROP TABLE IF EXISTS payment_htlc_attempt_resolutions; - --- ───────────────────────────────────────────── --- Drop HTLC attempts table and its indexes. --- ───────────────────────────────────────────── - -DROP INDEX IF EXISTS idx_htlc_payment_id; -DROP INDEX IF EXISTS idx_htlc_attempt_index; -DROP INDEX IF EXISTS idx_htlc_payment_hash; -DROP INDEX IF EXISTS idx_htlc_attempt_time; -DROP TABLE IF EXISTS payment_htlc_attempts; - --- ───────────────────────────────────────────── --- Drop payment intents table and its indexes. --- ───────────────────────────────────────────── - -DROP INDEX IF EXISTS idx_payment_intents_type; -DROP TABLE IF EXISTS payment_intents; - --- ───────────────────────────────────────────── --- Drop payments table and its indexes. --- ───────────────────────────────────────────── - -DROP INDEX IF EXISTS idx_payments_created_at; -DROP TABLE IF EXISTS payments; \ No newline at end of file diff --git a/sqldb/sqlc/migrations/000010_payments.up.sql b/sqldb/sqlc/migrations/000010_payments.up.sql deleted file mode 100644 index 65094a15e..000000000 --- a/sqldb/sqlc/migrations/000010_payments.up.sql +++ /dev/null @@ -1,443 +0,0 @@ --- ───────────────────────────────────────────── --- Payment System Schema Migration --- ───────────────────────────────────────────── --- This migration creates the complete payment system schema including: --- - Payment intents (only BOLT 11 invoices for now) --- - Payment attempts and HTLC tracking --- - Route hops and custom TLV records --- - Resolution tracking for settled/failed payments --- ───────────────────────────────────────────── - --- ───────────────────────────────────────────── --- Payments Table --- ───────────────────────────────────────────── --- Stores all payments including all known payment types: --- - Legacy payments --- - Multi-Path Payments (MPP) --- - Atomic Multi-Path Payments (AMP) --- - Blinded payments --- - Keysend payments --- - Spontaneous AMP payments --- ───────────────────────────────────────────── - -CREATE TABLE IF NOT EXISTS payments ( - -- Primary key for the payment record - id INTEGER PRIMARY KEY, - - -- The amount of the payment in millisatoshis - amount_msat BIGINT NOT NULL, - - -- Timestamp when the payment was created - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - - -- Logical identifier for the payment - -- For legacy + MPP: matches the HTLC hash - -- For AMP: the setID - -- For future intent types: any unique payment-level key - payment_identifier BLOB NOT NULL, - - -- The reason for payment failure (only set if payment has failed) - -- Integer enum type indicating failure reason - fail_reason INTEGER, - - -- Ensure payment identifiers are unique across all payments - CONSTRAINT idx_payments_payment_identifier_unique - UNIQUE (payment_identifier) -); - --- Index for efficient querying by creation time (for chronological ordering) -CREATE INDEX IF NOT EXISTS idx_payments_created_at -ON payments(created_at); - --- ───────────────────────────────────────────── --- Payment Intents Table --- ───────────────────────────────────────────── --- Stores the descriptor of what the payment is paying for. --- Depending on the type, the payload might contain: --- - BOLT 11 invoice data --- - BOLT 12 offer data --- - NULL for legacy hash-only/keysend style payments --- ───────────────────────────────────────────── - -CREATE TABLE IF NOT EXISTS payment_intents ( - -- Primary key for the intent record - id INTEGER PRIMARY KEY, - - -- Reference to the payment this intent belongs to (one-to-one relationship) - -- When the payment is deleted, the intent is automatically deleted - payment_id BIGINT NOT NULL REFERENCES payments (id) ON DELETE CASCADE, - - -- The type of intent (e.g. 0 = bolt11_invoice, 1 = bolt12_invoice) - -- Uses SMALLINT (int16) for efficient storage of enum values - intent_type SMALLINT NOT NULL, - - -- The serialized payload for the payment intent - -- Content depends on type - could be invoice, offer, or NULL - intent_payload BLOB, - - -- Ensure one-to-one relationship: each payment has at most one intent. - -- Currently we only support one intent per payment this makes sure we do - -- not accidentally pay the same request multiple times. This currently - -- only has bolt 11 payment requests/invoices. But in the future this can - -- also include BOLT 12 offers/invoices. - CONSTRAINT idx_payment_intents_payment_id_unique - UNIQUE (payment_id) -); - --- Index for efficient querying by intent type -CREATE INDEX IF NOT EXISTS idx_payment_intents_type -ON payment_intents(intent_type); - --- ───────────────────────────────────────────── --- Payment HTLC Attempts Table --- ───────────────────────────────────────────── --- Stores all HTLC attempts for a payment. A payment can have multiple --- HTLC attempts depending on whether the payment is split and also --- if some attempts fail and need to be retried. --- ───────────────────────────────────────────── - -CREATE TABLE IF NOT EXISTS payment_htlc_attempts ( - -- Primary key for the HTLC attempt record - id INTEGER PRIMARY KEY, - - -- The index of the HTLC attempt - -- TODO: This will be removed and the primary key will be used only - attempt_index BIGINT NOT NULL, - - -- Reference to the parent payment - payment_id BIGINT NOT NULL REFERENCES payments (id) ON DELETE CASCADE, - - -- The session key of the HTLC attempt (also known as ephemeral key - -- of the Sphinx packet used for onion routing) - session_key BLOB NOT NULL, - - -- Timestamp when the HTLC attempt was created - attempt_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - - -- The payment hash for the payment attempt - -- The hash the HTLC will be locked to - this does not need to be - -- equal to the payment level identifier (e.g., for AMP payments) - payment_hash BLOB NOT NULL, - - -- First hop amount in millisatoshis of the HTLC attempt - -- Normally the same as the total amount of the route, but when using - -- custom channels this might be different - first_hop_amount_msat BIGINT NOT NULL, - - -- ───────────────────────────────────────────── - -- Route Information for the HTLC Attempt - -- ───────────────────────────────────────────── - -- Every attempt has one route, so there is a 1:1 relationship between - -- attempts and routes. The route itself can be found in the hops table. - -- ───────────────────────────────────────────── - - -- The total time lock of the route (in blocks) - route_total_time_lock INTEGER NOT NULL, - - -- The total amount of the route in millisatoshis - route_total_amount BIGINT NOT NULL, - - -- The source key of the route (our node's public key) - route_source_key BLOB NOT NULL, - - -- Ensure attempt indices are unique across all attempts - CONSTRAINT idx_htlc_attempt_index_unique - UNIQUE (attempt_index), - - -- Ensure session keys are unique (each attempt has unique session key) - CONSTRAINT idx_htlc_session_key_unique - UNIQUE (session_key) -); - --- Index for efficient querying by payment ID (find all attempts for a payment) -CREATE INDEX IF NOT EXISTS idx_htlc_payment_id -ON payment_htlc_attempts(payment_id); - --- Index for efficient querying by attempt index (for lookups and joins) -CREATE INDEX IF NOT EXISTS idx_htlc_attempt_index -ON payment_htlc_attempts(attempt_index); - --- Index for efficient querying by payment hash (for HTLC matching) -CREATE INDEX IF NOT EXISTS idx_htlc_payment_hash -ON payment_htlc_attempts(payment_hash); - --- Index for efficient querying by attempt time (for chronological ordering) -CREATE INDEX IF NOT EXISTS idx_htlc_attempt_time -ON payment_htlc_attempts(attempt_time); - --- ───────────────────────────────────────────── --- HTLC Attempt Resolutions Table --- ───────────────────────────────────────────── --- Stores resolution metadata for HTLC attempts. Rows appear once an --- attempt settles or fails, providing the final outcome and timing. --- ───────────────────────────────────────────── - -CREATE TABLE IF NOT EXISTS payment_htlc_attempt_resolutions ( - -- Primary key referencing the HTLC attempt - -- TODO: This will be removed and the primary key will be used only - attempt_index INTEGER PRIMARY KEY - REFERENCES payment_htlc_attempts (attempt_index) ON DELETE CASCADE, - - -- Timestamp when the attempt was resolved (settled or failed) - resolution_time TIMESTAMP NOT NULL, - - -- Outcome of the attempt: 1 = settled, 2 = failed - resolution_type INTEGER NOT NULL CHECK (resolution_type IN (1, 2)), - - -- Settlement payload (only populated for settled attempts) - -- Contains the preimage that proves payment completion - settle_preimage BLOB, - - -- Failure payload (only populated for failed attempts) - -- Index of the node that sent the failure - failure_source_index INTEGER, - - -- HTLC failure reason code - htlc_fail_reason INTEGER, - - -- Failure message from the failing node, this message is binary encoded - -- using the lightning wire protocol, see also lnwire/onion_error.go - failure_msg BLOB, - - -- Ensure data integrity: settled attempts must have preimage, - -- failed attempts must not have preimage - CHECK ( - (resolution_type = 1 AND settle_preimage IS NOT NULL AND - failure_source_index IS NULL AND htlc_fail_reason IS NULL AND - failure_msg IS NULL) - OR - (resolution_type = 2 AND settle_preimage IS NULL) - ) -); - --- Index for efficient querying by resolution type (settled vs failed) -CREATE INDEX IF NOT EXISTS idx_htlc_resolutions_type -ON payment_htlc_attempt_resolutions(resolution_type); - --- Index for efficient querying by resolution time (for chronological analysis) -CREATE INDEX IF NOT EXISTS idx_htlc_resolutions_time -ON payment_htlc_attempt_resolutions(resolution_time); - --- ───────────────────────────────────────────── --- Payment Route Hops Table --- ───────────────────────────────────────────── --- Stores the individual hops of a payment route. An attempt has only --- one route, but a route can consist of several hops through the network. --- ───────────────────────────────────────────── - -CREATE TABLE IF NOT EXISTS payment_route_hops ( - -- Primary key for the hop record - id INTEGER PRIMARY KEY, - - -- Reference to the HTLC attempt this hop belongs to - htlc_attempt_index BIGINT NOT NULL - REFERENCES payment_htlc_attempts (attempt_index) ON DELETE CASCADE, - - -- The order/index of this hop within the route (0-based) - hop_index INTEGER NOT NULL, - - -- The public key of the hop (node's public key) - pub_key BLOB, - - -- The short channel ID of the hop (channel identifier) - scid TEXT NOT NULL, - - -- The outgoing time lock of the hop (in blocks) - outgoing_time_lock INTEGER NOT NULL, - - -- The amount to forward to the next hop (in millisatoshis) - amt_to_forward BIGINT NOT NULL, - - -- The metadata blob transmitted to the hop (onion payload) - meta_data BLOB, - - -- Ensure each attempt can only have one hop at each hop index - -- This prevents duplicate hops in the same position - CONSTRAINT idx_route_hops_unique_hop_per_attempt - UNIQUE (htlc_attempt_index, hop_index) -); - --- Index for efficient querying by attempt index (find all hops for an attempt) -CREATE INDEX IF NOT EXISTS idx_route_hops_htlc_attempt_index -ON payment_route_hops(htlc_attempt_index); - --- ───────────────────────────────────────────── --- Per-Hop Payload Tables --- ───────────────────────────────────────────── --- These tables store specialized payload data for different payment types. --- Each table is only populated for hops that require that specific payload. --- ───────────────────────────────────────────── - --- ───────────────────────────────────────────── --- MPP (Multi-Path Payment) Payload Table --- ───────────────────────────────────────────── --- Stores MPP-specific payload data. Only present for the final hop --- of an MPP attempt, containing payment address and total amount info. --- ───────────────────────────────────────────── - -CREATE TABLE IF NOT EXISTS payment_route_hop_mpp ( - -- Primary key referencing the hop - hop_id INTEGER PRIMARY KEY - REFERENCES payment_route_hops (id) ON DELETE CASCADE, - - -- The payment address of the MPP path (for payment correlation) - payment_addr BLOB NOT NULL, - - -- The total amount of the MPP payment in millisatoshis - -- This is the sum of all parts in the multi-path payment - total_msat BIGINT NOT NULL -); - --- ───────────────────────────────────────────── --- AMP (Atomic Multi-Path Payment) Payload Table --- ───────────────────────────────────────────── --- Stores AMP-specific payload data. Only present for the final hop --- of an AMP attempt, containing share information for atomicity. --- ───────────────────────────────────────────── - -CREATE TABLE IF NOT EXISTS payment_route_hop_amp ( - -- Primary key referencing the hop - hop_id INTEGER PRIMARY KEY - REFERENCES payment_route_hops (id) ON DELETE CASCADE, - - -- The root share of the AMP path (for share reconstruction) - root_share BLOB NOT NULL, - - -- The set ID of the AMP path (groups related AMP parts) - set_id BLOB NOT NULL, - - -- The child index of the AMP path (identifies this part) - child_index INTEGER NOT NULL -); - --- ───────────────────────────────────────────── --- Blinded Route Payload Table --- ───────────────────────────────────────────── --- Stores blinded route payload data. Rows only exist for hops that --- are part of a blinded path, providing privacy-preserving routing. --- ───────────────────────────────────────────── - -CREATE TABLE IF NOT EXISTS payment_route_hop_blinded ( - -- Primary key referencing the hop - hop_id INTEGER PRIMARY KEY - REFERENCES payment_route_hops (id) ON DELETE CASCADE, - - -- The encrypted payload for the blinded hop - encrypted_data BLOB NOT NULL, - - -- Only set for the introduction point of the blinded path - -- Contains the blinding point for the introduction node - blinding_point BLOB, - - -- Only set for the final hop in the blinded path - -- Contains the total amount for the entire blinded path - blinded_path_total_amt BIGINT -); - --- ───────────────────────────────────────────── --- Custom TLV Records Tables --- ───────────────────────────────────────────── --- These tables store custom TLV (Type-Length-Value) records associated --- with payments, attempts, and hops. This is a denormalized structure --- designed to simplify cascade deletions, as each record is owned by --- a single parent entity. --- ───────────────────────────────────────────── - --- ───────────────────────────────────────────── --- Payment-Level First Hop Custom Records --- ───────────────────────────────────────────── --- Stores custom TLV records that are part of the first hop of a payment. --- These records are sent to the first hop and are payment-level data. --- --- NOTE: This relates to the custom tlv record data which is sent to the first --- hop in the wire message (UpdateAddHTLC) NOT the onion packet. --- --- TODO(ziggie): We store mostly redundant data here and on the attempt level. --- This might be improved in the future to reduce duplication. --- ───────────────────────────────────────────── - -CREATE TABLE IF NOT EXISTS payment_first_hop_custom_records ( - -- Primary key for the custom record - id INTEGER PRIMARY KEY, - - -- Reference to the parent payment - payment_id BIGINT NOT NULL REFERENCES payments (id) ON DELETE CASCADE, - - -- The TLV type identifier (must be >= 65536 for custom records) - key BIGINT NOT NULL, - - -- The TLV value data - value BLOB NOT NULL, - - -- Ensure we only store custom TLV records (not standard ones) - CHECK (key >= 65536), - - -- Ensure each payment can only have one record per TLV type - CONSTRAINT idx_payment_first_hop_custom_records_unique - UNIQUE (payment_id, key) -); - --- ───────────────────────────────────────────── --- Attempt-Level First Hop Custom Records --- ───────────────────────────────────────────── --- Stores custom TLV records for the first hop on the route level. --- These might be different from the payment-level first hop records --- in case of custom channels or route-specific modifications. --- --- NOTE: This relates to the custom tlv record data which is sent to the first --- hop in the wire message (UpdateAddHTLC) NOT the onion packet. --- --- TODO(ziggie): We store mostly redundant data here and on the payment level. --- This might be improved in the future to reduce duplication. --- ───────────────────────────────────────────── - -CREATE TABLE IF NOT EXISTS payment_attempt_first_hop_custom_records ( - -- Primary key for the custom record - id INTEGER PRIMARY KEY, - - -- Reference to the parent HTLC attempt - htlc_attempt_index BIGINT NOT NULL - REFERENCES payment_htlc_attempts (attempt_index) ON DELETE CASCADE, - - -- The TLV type identifier (must be >= 65536 for custom records) - key BIGINT NOT NULL, - - -- The TLV value data - value BLOB NOT NULL, - - -- Ensure we only store custom TLV records (not standard ones) - CHECK (key >= 65536), - - -- Ensure each attempt can only have one record per TLV type - CONSTRAINT idx_payment_attempt_first_hop_custom_records_unique - UNIQUE (htlc_attempt_index, key) -); - --- ───────────────────────────────────────────── --- Hop-Level Custom Records --- ───────────────────────────────────────────── --- Stores custom TLV records associated with a specific hop within --- a payment route. These records are sent to that specific hop --- and are hop-specific data. --- ───────────────────────────────────────────── - -CREATE TABLE IF NOT EXISTS payment_hop_custom_records ( - -- Primary key for the custom record - id INTEGER PRIMARY KEY, - - -- Reference to the parent hop - hop_id BIGINT NOT NULL REFERENCES payment_route_hops (id) ON DELETE CASCADE, - - -- The TLV type identifier (must be >= 65536 for custom records) - key BIGINT NOT NULL, - - -- The TLV value data - value BLOB NOT NULL, - - -- Ensure we only store custom TLV records (not standard ones) - CHECK (key >= 65536), - - -- Ensure each hop can only have one record per TLV type - CONSTRAINT idx_payment_hop_custom_records_unique - UNIQUE (hop_id, key) -); \ No newline at end of file diff --git a/sqldb/sqlc/migrations/000011_payment_duplicates.down.sql b/sqldb/sqlc/migrations/000011_payment_duplicates.down.sql deleted file mode 100644 index 39c4a313a..000000000 --- a/sqldb/sqlc/migrations/000011_payment_duplicates.down.sql +++ /dev/null @@ -1,2 +0,0 @@ -DROP INDEX IF EXISTS idx_payment_duplicates_payment_id; -DROP TABLE IF EXISTS payment_duplicates; diff --git a/sqldb/sqlc/migrations/000011_payment_duplicates.up.sql b/sqldb/sqlc/migrations/000011_payment_duplicates.up.sql deleted file mode 100644 index 8abaf3e09..000000000 --- a/sqldb/sqlc/migrations/000011_payment_duplicates.up.sql +++ /dev/null @@ -1,43 +0,0 @@ --- ───────────────────────────────────────────── --- Payment Duplicate Records Table --- ───────────────────────────────────────────── --- Stores duplicate payment records that were created in older versions --- of lnd. This table is intentionally minimal and is expected to be dropped --- in the future especially if no duplicates were migrated. --- ───────────────────────────────────────────── - -CREATE TABLE IF NOT EXISTS payment_duplicates ( - -- Primary key for the duplicate record. - id INTEGER PRIMARY KEY, - - -- Reference to the primary payment this duplicate belongs to. - payment_id BIGINT NOT NULL REFERENCES payments (id) ON DELETE CASCADE, - - -- Amount of the duplicate payment in millisatoshis. - amount_msat BIGINT NOT NULL, - - -- Timestamp when the duplicate payment was created. - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - - -- Failure reason for failed payments (if known). - fail_reason INTEGER, - - -- Settlement payload for succeeded payments (if known). - settle_preimage BLOB, - - -- Settlement time for succeeded payments (if known). - settle_time TIMESTAMP, - - -- Ensure we record either a failure reason or settlement data. - -- During the migration if we encounter a duplicate payment that has no - -- failure reason or settlement data, we will mark it as failed. Duplicate - -- payments were a bug in older versions of LND, so we can be sure if a - -- duplicate payment has no failure reason or settlement data, the - -- corresponding HTLC has been failed. - CONSTRAINT chk_payment_duplicates_outcome - CHECK (fail_reason IS NOT NULL OR settle_preimage IS NOT NULL) -); - --- Index for efficient lookup by primary payment. -CREATE INDEX IF NOT EXISTS idx_payment_duplicates_payment_id -ON payment_duplicates(payment_id); diff --git a/sqldb/sqlc/migrations/000012_drop_redundant_invoice_indexes.down.sql b/sqldb/sqlc/migrations/000012_drop_redundant_invoice_indexes.down.sql deleted file mode 100644 index 92e8599e0..000000000 --- a/sqldb/sqlc/migrations/000012_drop_redundant_invoice_indexes.down.sql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE INDEX IF NOT EXISTS invoices_hash_idx ON invoices(hash); -CREATE INDEX IF NOT EXISTS invoices_payment_addr_idx ON invoices(payment_addr); -CREATE INDEX IF NOT EXISTS invoices_preimage_idx ON invoices(preimage); -CREATE INDEX IF NOT EXISTS invoices_settled_at_idx ON invoices(settled_at); diff --git a/sqldb/sqlc/migrations/000012_drop_redundant_invoice_indexes.up.sql b/sqldb/sqlc/migrations/000012_drop_redundant_invoice_indexes.up.sql deleted file mode 100644 index fa6a0e2f7..000000000 --- a/sqldb/sqlc/migrations/000012_drop_redundant_invoice_indexes.up.sql +++ /dev/null @@ -1,16 +0,0 @@ --- invoices_hash_idx is redundant: The UNIQUE constraint on invoices(hash) --- already creates an implicit index. -DROP INDEX IF EXISTS invoices_hash_idx; - --- invoices_payment_addr_idx is redundant: The UNIQUE constraint on --- invoices(payment_addr) already creates an implicit index. -DROP INDEX IF EXISTS invoices_payment_addr_idx; - --- invoices_preimage_idx is useless: There are no queries that filter on --- preimage so there is no need to index it. -DROP INDEX IF EXISTS invoices_preimage_idx; - --- invoices_settled_at_idx is useless: settled_at is NULL for all pending --- invoices and is never used as a filter in any query (settle_index is used --- instead). -DROP INDEX IF EXISTS invoices_settled_at_idx; diff --git a/sqldb/sqlc/migrations/000013_payments_index_improvements.down.sql b/sqldb/sqlc/migrations/000013_payments_index_improvements.down.sql deleted file mode 100644 index 29fd14f4e..000000000 --- a/sqldb/sqlc/migrations/000013_payments_index_improvements.down.sql +++ /dev/null @@ -1,10 +0,0 @@ --- Restore the composite indexes removed in the up migration. -DROP INDEX IF EXISTS idx_htlc_payment_id_attempt_time; -DROP INDEX IF EXISTS idx_htlc_resolutions_type_attempt_index; - --- Restore the redundant indexes that were dropped in the up migration. -CREATE INDEX IF NOT EXISTS idx_htlc_attempt_index -ON payment_htlc_attempts(attempt_index); - -CREATE INDEX IF NOT EXISTS idx_route_hops_htlc_attempt_index -ON payment_route_hops(htlc_attempt_index); diff --git a/sqldb/sqlc/migrations/000013_payments_index_improvements.up.sql b/sqldb/sqlc/migrations/000013_payments_index_improvements.up.sql deleted file mode 100644 index d23ab3308..000000000 --- a/sqldb/sqlc/migrations/000013_payments_index_improvements.up.sql +++ /dev/null @@ -1,36 +0,0 @@ --- ───────────────────────────────────────────── --- Remove redundant indexes from the payments schema. --- ───────────────────────────────────────────── --- Drop two explicit indexes that duplicate indexes already created by UNIQUE --- constraints: --- --- - payment_htlc_attempts(attempt_index) duplicates UNIQUE(attempt_index) --- - payment_route_hops(htlc_attempt_index) duplicates --- UNIQUE(htlc_attempt_index, hop_index) --- --- This reduces write/index maintenance overhead without changing query --- capabilities, since the UNIQUE-backed autoindexes already satisfy these --- lookups via exact and leftmost-prefix matching. --- ───────────────────────────────────────────── - -DROP INDEX IF EXISTS idx_htlc_attempt_index; -DROP INDEX IF EXISTS idx_route_hops_htlc_attempt_index; - --- ───────────────────────────────────────────── --- Add composite indexes for hot payment query paths. --- ───────────────────────────────────────────── --- Add two composite indexes to better match high-frequency query patterns --- observed in the payment lifecycle. --- ───────────────────────────────────────────── - --- Composite index for batched attempt fetches that filter by payment_id and --- order by attempt_time. This matches FetchHtlcAttemptsForPayments: --- WHERE payment_id IN (...) ORDER BY payment_id, attempt_time. -CREATE INDEX IF NOT EXISTS idx_htlc_payment_id_attempt_time -ON payment_htlc_attempts(payment_id, attempt_time); - --- Composite index for delete paths that first filter failed resolutions by --- resolution_type and then join/delete by attempt_index. This matches --- DeleteFailedAttempts. -CREATE INDEX IF NOT EXISTS idx_htlc_resolutions_type_attempt_index -ON payment_htlc_attempt_resolutions(resolution_type, attempt_index); diff --git a/sqldb/sqlc/migrations/000014_payments_no_fail_reason_index.down.sql b/sqldb/sqlc/migrations/000014_payments_no_fail_reason_index.down.sql deleted file mode 100644 index 030107920..000000000 --- a/sqldb/sqlc/migrations/000014_payments_no_fail_reason_index.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP INDEX IF EXISTS idx_payments_no_fail_reason; diff --git a/sqldb/sqlc/migrations/000014_payments_no_fail_reason_index.up.sql b/sqldb/sqlc/migrations/000014_payments_no_fail_reason_index.up.sql deleted file mode 100644 index 97e664ba0..000000000 --- a/sqldb/sqlc/migrations/000014_payments_no_fail_reason_index.up.sql +++ /dev/null @@ -1,4 +0,0 @@ --- Partial index for startup payment recovery queries that filter on --- fail_reason IS NULL and walk payment IDs in ascending order. -CREATE INDEX IF NOT EXISTS idx_payments_no_fail_reason -ON payments(id) WHERE fail_reason IS NULL; diff --git a/sqldb/sqlc/migrations/000015_chain_params.down.sql b/sqldb/sqlc/migrations/000015_chain_params.down.sql deleted file mode 100644 index 4624cf842..000000000 --- a/sqldb/sqlc/migrations/000015_chain_params.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE IF EXISTS chain_params; diff --git a/sqldb/sqlc/migrations/000015_chain_params.up.sql b/sqldb/sqlc/migrations/000015_chain_params.up.sql deleted file mode 100644 index cf2a3f611..000000000 --- a/sqldb/sqlc/migrations/000015_chain_params.up.sql +++ /dev/null @@ -1,8 +0,0 @@ --- The chain_params table stores chain-level properties of the database. It is --- used to persist and validate chain-specific invariants across restarts, such --- as which Bitcoin network the database was initialised for. The single_row --- column is a boolean primary key that enforces exactly one row in the table. -CREATE TABLE IF NOT EXISTS chain_params ( - single_row BOOLEAN PRIMARY KEY DEFAULT TRUE CHECK (single_row), - network TEXT NOT NULL -); diff --git a/sqldb/sqlc/models.go b/sqldb/sqlc/models.go index ef9aa9006..24df0d680 100644 --- a/sqldb/sqlc/models.go +++ b/sqldb/sqlc/models.go @@ -28,11 +28,6 @@ type AmpSubInvoiceHtlc struct { Preimage []byte } -type ChainParam struct { - SingleRow bool - Network string -} - type GraphChannel struct { ID int64 Version int16 @@ -47,9 +42,6 @@ type GraphChannel struct { Node2Signature []byte Bitcoin1Signature []byte Bitcoin2Signature []byte - Signature []byte - FundingPkScript []byte - MerkleRootHash []byte } type GraphChannelExtraType struct { @@ -80,8 +72,6 @@ type GraphChannelPolicy struct { MessageFlags sql.NullInt16 ChannelFlags sql.NullInt16 Signature []byte - BlockHeight sql.NullInt64 - DisableFlags sql.NullInt16 } type GraphChannelPolicyExtraType struct { @@ -95,14 +85,13 @@ type GraphClosedScid struct { } type GraphNode struct { - ID int64 - Version int16 - PubKey []byte - Alias sql.NullString - LastUpdate sql.NullInt64 - Color sql.NullString - Signature []byte - BlockHeight sql.NullInt64 + ID int64 + Version int16 + PubKey []byte + Alias sql.NullString + LastUpdate sql.NullInt64 + Color sql.NullString + Signature []byte } type GraphNodeAddress struct { @@ -213,103 +202,3 @@ type MigrationTracker struct { Version int32 MigrationTime time.Time } - -type Payment struct { - ID int64 - AmountMsat int64 - CreatedAt time.Time - PaymentIdentifier []byte - FailReason sql.NullInt32 -} - -type PaymentAttemptFirstHopCustomRecord struct { - ID int64 - HtlcAttemptIndex int64 - Key int64 - Value []byte -} - -type PaymentDuplicate struct { - ID int64 - PaymentID int64 - AmountMsat int64 - CreatedAt time.Time - FailReason sql.NullInt32 - SettlePreimage []byte - SettleTime sql.NullTime -} - -type PaymentFirstHopCustomRecord struct { - ID int64 - PaymentID int64 - Key int64 - Value []byte -} - -type PaymentHopCustomRecord struct { - ID int64 - HopID int64 - Key int64 - Value []byte -} - -type PaymentHtlcAttempt struct { - ID int64 - AttemptIndex int64 - PaymentID int64 - SessionKey []byte - AttemptTime time.Time - PaymentHash []byte - FirstHopAmountMsat int64 - RouteTotalTimeLock int32 - RouteTotalAmount int64 - RouteSourceKey []byte -} - -type PaymentHtlcAttemptResolution struct { - AttemptIndex int64 - ResolutionTime time.Time - ResolutionType int32 - SettlePreimage []byte - FailureSourceIndex sql.NullInt32 - HtlcFailReason sql.NullInt32 - FailureMsg []byte -} - -type PaymentIntent struct { - ID int64 - PaymentID int64 - IntentType int16 - IntentPayload []byte -} - -type PaymentRouteHop struct { - ID int64 - HtlcAttemptIndex int64 - HopIndex int32 - PubKey []byte - Scid string - OutgoingTimeLock int32 - AmtToForward int64 - MetaData []byte -} - -type PaymentRouteHopAmp struct { - HopID int64 - RootShare []byte - SetID []byte - ChildIndex int32 -} - -type PaymentRouteHopBlinded struct { - HopID int64 - EncryptedData []byte - BlindingPoint []byte - BlindedPathTotalAmt sql.NullInt64 -} - -type PaymentRouteHopMpp struct { - HopID int64 - PaymentAddr []byte - TotalMsat int64 -} diff --git a/sqldb/sqlc/payments.sql.go b/sqldb/sqlc/payments.sql.go deleted file mode 100644 index 42a8fb826..000000000 --- a/sqldb/sqlc/payments.sql.go +++ /dev/null @@ -1,1389 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.29.0 -// source: payments.sql - -package sqlc - -import ( - "context" - "database/sql" - "strings" - "time" -) - -const countPayments = `-- name: CountPayments :one -SELECT COUNT(*) FROM payments -` - -func (q *Queries) CountPayments(ctx context.Context) (int64, error) { - row := q.db.QueryRowContext(ctx, countPayments) - var count int64 - err := row.Scan(&count) - return count, err -} - -const deleteFailedAttempts = `-- name: DeleteFailedAttempts :exec -DELETE FROM payment_htlc_attempts -WHERE payment_id = $1 -AND EXISTS ( - SELECT 1 FROM payment_htlc_attempt_resolutions hr - WHERE hr.attempt_index = payment_htlc_attempts.attempt_index - AND hr.resolution_type = 2 -) -` - -// Delete all failed HTLC attempts for the given payment. Resolution type 2 -// indicates a failed attempt. Uses EXISTS to scope the resolution lookup to -// only this payment's attempts, avoiding an O(N) scan of all failed -// resolutions across all payments. -func (q *Queries) DeleteFailedAttempts(ctx context.Context, paymentID int64) error { - _, err := q.db.ExecContext(ctx, deleteFailedAttempts, paymentID) - return err -} - -const deletePayment = `-- name: DeletePayment :exec -DELETE FROM payments WHERE id = $1 -` - -func (q *Queries) DeletePayment(ctx context.Context, id int64) error { - _, err := q.db.ExecContext(ctx, deletePayment, id) - return err -} - -const failAttempt = `-- name: FailAttempt :exec -INSERT INTO payment_htlc_attempt_resolutions ( - attempt_index, - resolution_time, - resolution_type, - failure_source_index, - htlc_fail_reason, - failure_msg -) -VALUES ( - $1, - $2, - $3, - $4, - $5, - $6 -) -` - -type FailAttemptParams struct { - AttemptIndex int64 - ResolutionTime time.Time - ResolutionType int32 - FailureSourceIndex sql.NullInt32 - HtlcFailReason sql.NullInt32 - FailureMsg []byte -} - -func (q *Queries) FailAttempt(ctx context.Context, arg FailAttemptParams) error { - _, err := q.db.ExecContext(ctx, failAttempt, - arg.AttemptIndex, - arg.ResolutionTime, - arg.ResolutionType, - arg.FailureSourceIndex, - arg.HtlcFailReason, - arg.FailureMsg, - ) - return err -} - -const failPayment = `-- name: FailPayment :execresult -UPDATE payments SET fail_reason = $1 WHERE payment_identifier = $2 -` - -type FailPaymentParams struct { - FailReason sql.NullInt32 - PaymentIdentifier []byte -} - -func (q *Queries) FailPayment(ctx context.Context, arg FailPaymentParams) (sql.Result, error) { - return q.db.ExecContext(ctx, failPayment, arg.FailReason, arg.PaymentIdentifier) -} - -const fetchHopLevelCustomRecords = `-- name: FetchHopLevelCustomRecords :many -SELECT - l.id, - l.hop_id, - l.key, - l.value -FROM payment_hop_custom_records l -WHERE l.hop_id IN (/*SLICE:hop_ids*/?) -ORDER BY l.hop_id ASC, l.key ASC -` - -func (q *Queries) FetchHopLevelCustomRecords(ctx context.Context, hopIds []int64) ([]PaymentHopCustomRecord, error) { - query := fetchHopLevelCustomRecords - var queryParams []interface{} - if len(hopIds) > 0 { - for _, v := range hopIds { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:hop_ids*/?", makeQueryParams(len(queryParams), len(hopIds)), 1) - } else { - query = strings.Replace(query, "/*SLICE:hop_ids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []PaymentHopCustomRecord - for rows.Next() { - var i PaymentHopCustomRecord - if err := rows.Scan( - &i.ID, - &i.HopID, - &i.Key, - &i.Value, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const fetchHopsForAttempts = `-- name: FetchHopsForAttempts :many -SELECT - h.id, - h.htlc_attempt_index, - h.hop_index, - h.pub_key, - h.scid, - h.outgoing_time_lock, - h.amt_to_forward, - h.meta_data, - m.payment_addr AS mpp_payment_addr, - m.total_msat AS mpp_total_msat, - a.root_share AS amp_root_share, - a.set_id AS amp_set_id, - a.child_index AS amp_child_index, - b.encrypted_data, - b.blinding_point, - b.blinded_path_total_amt -FROM payment_route_hops h -LEFT JOIN payment_route_hop_mpp m ON m.hop_id = h.id -LEFT JOIN payment_route_hop_amp a ON a.hop_id = h.id -LEFT JOIN payment_route_hop_blinded b ON b.hop_id = h.id -WHERE h.htlc_attempt_index IN (/*SLICE:htlc_attempt_indices*/?) -ORDER BY h.htlc_attempt_index ASC, h.hop_index ASC -` - -type FetchHopsForAttemptsRow struct { - ID int64 - HtlcAttemptIndex int64 - HopIndex int32 - PubKey []byte - Scid string - OutgoingTimeLock int32 - AmtToForward int64 - MetaData []byte - MppPaymentAddr []byte - MppTotalMsat sql.NullInt64 - AmpRootShare []byte - AmpSetID []byte - AmpChildIndex sql.NullInt32 - EncryptedData []byte - BlindingPoint []byte - BlindedPathTotalAmt sql.NullInt64 -} - -func (q *Queries) FetchHopsForAttempts(ctx context.Context, htlcAttemptIndices []int64) ([]FetchHopsForAttemptsRow, error) { - query := fetchHopsForAttempts - var queryParams []interface{} - if len(htlcAttemptIndices) > 0 { - for _, v := range htlcAttemptIndices { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:htlc_attempt_indices*/?", makeQueryParams(len(queryParams), len(htlcAttemptIndices)), 1) - } else { - query = strings.Replace(query, "/*SLICE:htlc_attempt_indices*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []FetchHopsForAttemptsRow - for rows.Next() { - var i FetchHopsForAttemptsRow - if err := rows.Scan( - &i.ID, - &i.HtlcAttemptIndex, - &i.HopIndex, - &i.PubKey, - &i.Scid, - &i.OutgoingTimeLock, - &i.AmtToForward, - &i.MetaData, - &i.MppPaymentAddr, - &i.MppTotalMsat, - &i.AmpRootShare, - &i.AmpSetID, - &i.AmpChildIndex, - &i.EncryptedData, - &i.BlindingPoint, - &i.BlindedPathTotalAmt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const fetchHtlcAttemptResolutionsForPayments = `-- name: FetchHtlcAttemptResolutionsForPayments :many -SELECT - ha.payment_id, - hr.resolution_type -FROM payment_htlc_attempts ha -LEFT JOIN payment_htlc_attempt_resolutions hr ON hr.attempt_index = ha.attempt_index -WHERE ha.payment_id IN (/*SLICE:payment_ids*/?) -` - -type FetchHtlcAttemptResolutionsForPaymentsRow struct { - PaymentID int64 - ResolutionType sql.NullInt32 -} - -// Batch query to fetch only HTLC resolution status for multiple payments. -// We don't need to order by payment_id and attempt_time because we will -// group the resolutions by payment_id in the background. -func (q *Queries) FetchHtlcAttemptResolutionsForPayments(ctx context.Context, paymentIds []int64) ([]FetchHtlcAttemptResolutionsForPaymentsRow, error) { - query := fetchHtlcAttemptResolutionsForPayments - var queryParams []interface{} - if len(paymentIds) > 0 { - for _, v := range paymentIds { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:payment_ids*/?", makeQueryParams(len(queryParams), len(paymentIds)), 1) - } else { - query = strings.Replace(query, "/*SLICE:payment_ids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []FetchHtlcAttemptResolutionsForPaymentsRow - for rows.Next() { - var i FetchHtlcAttemptResolutionsForPaymentsRow - if err := rows.Scan(&i.PaymentID, &i.ResolutionType); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const fetchHtlcAttemptsForPayments = `-- name: FetchHtlcAttemptsForPayments :many -SELECT - ha.id, - ha.attempt_index, - ha.payment_id, - ha.session_key, - ha.attempt_time, - ha.payment_hash, - ha.first_hop_amount_msat, - ha.route_total_time_lock, - ha.route_total_amount, - ha.route_source_key, - hr.resolution_type, - hr.resolution_time, - hr.failure_source_index, - hr.htlc_fail_reason, - hr.failure_msg, - hr.settle_preimage -FROM payment_htlc_attempts ha -LEFT JOIN payment_htlc_attempt_resolutions hr ON hr.attempt_index = ha.attempt_index -WHERE ha.payment_id IN (/*SLICE:payment_ids*/?) -ORDER BY ha.payment_id ASC, ha.attempt_time ASC -` - -type FetchHtlcAttemptsForPaymentsRow struct { - ID int64 - AttemptIndex int64 - PaymentID int64 - SessionKey []byte - AttemptTime time.Time - PaymentHash []byte - FirstHopAmountMsat int64 - RouteTotalTimeLock int32 - RouteTotalAmount int64 - RouteSourceKey []byte - ResolutionType sql.NullInt32 - ResolutionTime sql.NullTime - FailureSourceIndex sql.NullInt32 - HtlcFailReason sql.NullInt32 - FailureMsg []byte - SettlePreimage []byte -} - -func (q *Queries) FetchHtlcAttemptsForPayments(ctx context.Context, paymentIds []int64) ([]FetchHtlcAttemptsForPaymentsRow, error) { - query := fetchHtlcAttemptsForPayments - var queryParams []interface{} - if len(paymentIds) > 0 { - for _, v := range paymentIds { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:payment_ids*/?", makeQueryParams(len(queryParams), len(paymentIds)), 1) - } else { - query = strings.Replace(query, "/*SLICE:payment_ids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []FetchHtlcAttemptsForPaymentsRow - for rows.Next() { - var i FetchHtlcAttemptsForPaymentsRow - if err := rows.Scan( - &i.ID, - &i.AttemptIndex, - &i.PaymentID, - &i.SessionKey, - &i.AttemptTime, - &i.PaymentHash, - &i.FirstHopAmountMsat, - &i.RouteTotalTimeLock, - &i.RouteTotalAmount, - &i.RouteSourceKey, - &i.ResolutionType, - &i.ResolutionTime, - &i.FailureSourceIndex, - &i.HtlcFailReason, - &i.FailureMsg, - &i.SettlePreimage, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const fetchNonTerminalPayments = `-- name: FetchNonTerminalPayments :many -SELECT - p.id, - p.amount_msat, - p.created_at, - p.payment_identifier, - p.fail_reason, - pi.intent_type, - pi.intent_payload -FROM payments p -LEFT JOIN payment_intents pi - ON pi.payment_id = p.id -WHERE p.id > $1 -AND ( - ( - p.fail_reason IS NULL - AND NOT EXISTS ( - SELECT 1 - FROM payment_htlc_attempts ha - JOIN payment_htlc_attempt_resolutions hr - ON hr.attempt_index = ha.attempt_index - WHERE ha.payment_id = p.id - AND hr.resolution_type = 1 - ) - ) - OR EXISTS ( - SELECT 1 - FROM payment_htlc_attempts ha - WHERE ha.payment_id = p.id - AND NOT EXISTS ( - SELECT 1 - FROM payment_htlc_attempt_resolutions hr - WHERE hr.attempt_index = ha.attempt_index - ) - ) -) -ORDER BY p.id ASC -LIMIT $2 -` - -type FetchNonTerminalPaymentsParams struct { - ID int64 - Limit int32 -} - -type FetchNonTerminalPaymentsRow struct { - ID int64 - AmountMsat int64 - CreatedAt time.Time - PaymentIdentifier []byte - FailReason sql.NullInt32 - IntentType sql.NullInt16 - IntentPayload []byte -} - -// Fetch all non-terminal payments using pagination. A payment is -// non-terminal if it has an unresolved attempt, or if it has not been -// permanently failed and has no settled attempt yet. -func (q *Queries) FetchNonTerminalPayments(ctx context.Context, arg FetchNonTerminalPaymentsParams) ([]FetchNonTerminalPaymentsRow, error) { - rows, err := q.db.QueryContext(ctx, fetchNonTerminalPayments, arg.ID, arg.Limit) - if err != nil { - return nil, err - } - defer rows.Close() - var items []FetchNonTerminalPaymentsRow - for rows.Next() { - var i FetchNonTerminalPaymentsRow - if err := rows.Scan( - &i.ID, - &i.AmountMsat, - &i.CreatedAt, - &i.PaymentIdentifier, - &i.FailReason, - &i.IntentType, - &i.IntentPayload, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const fetchPayment = `-- name: FetchPayment :one -SELECT - p.id, p.amount_msat, p.created_at, p.payment_identifier, p.fail_reason, - i.intent_type AS "intent_type", - i.intent_payload AS "intent_payload" -FROM payments p -LEFT JOIN payment_intents i ON i.payment_id = p.id -WHERE p.payment_identifier = $1 -` - -type FetchPaymentRow struct { - Payment Payment - IntentType sql.NullInt16 - IntentPayload []byte -} - -func (q *Queries) FetchPayment(ctx context.Context, paymentIdentifier []byte) (FetchPaymentRow, error) { - row := q.db.QueryRowContext(ctx, fetchPayment, paymentIdentifier) - var i FetchPaymentRow - err := row.Scan( - &i.Payment.ID, - &i.Payment.AmountMsat, - &i.Payment.CreatedAt, - &i.Payment.PaymentIdentifier, - &i.Payment.FailReason, - &i.IntentType, - &i.IntentPayload, - ) - return i, err -} - -const fetchPaymentDuplicates = `-- name: FetchPaymentDuplicates :many -SELECT - id, - payment_id, - amount_msat, - created_at, - fail_reason, - settle_preimage, - settle_time -FROM payment_duplicates -WHERE payment_id = $1 -ORDER BY id ASC -` - -// Fetch all duplicate payment records from the payment_duplicates table for -// a given payment ID. -func (q *Queries) FetchPaymentDuplicates(ctx context.Context, paymentID int64) ([]PaymentDuplicate, error) { - rows, err := q.db.QueryContext(ctx, fetchPaymentDuplicates, paymentID) - if err != nil { - return nil, err - } - defer rows.Close() - var items []PaymentDuplicate - for rows.Next() { - var i PaymentDuplicate - if err := rows.Scan( - &i.ID, - &i.PaymentID, - &i.AmountMsat, - &i.CreatedAt, - &i.FailReason, - &i.SettlePreimage, - &i.SettleTime, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const fetchPaymentLevelFirstHopCustomRecords = `-- name: FetchPaymentLevelFirstHopCustomRecords :many -SELECT - l.id, - l.payment_id, - l.key, - l.value -FROM payment_first_hop_custom_records l -WHERE l.payment_id IN (/*SLICE:payment_ids*/?) -ORDER BY l.payment_id ASC, l.key ASC -` - -func (q *Queries) FetchPaymentLevelFirstHopCustomRecords(ctx context.Context, paymentIds []int64) ([]PaymentFirstHopCustomRecord, error) { - query := fetchPaymentLevelFirstHopCustomRecords - var queryParams []interface{} - if len(paymentIds) > 0 { - for _, v := range paymentIds { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:payment_ids*/?", makeQueryParams(len(queryParams), len(paymentIds)), 1) - } else { - query = strings.Replace(query, "/*SLICE:payment_ids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []PaymentFirstHopCustomRecord - for rows.Next() { - var i PaymentFirstHopCustomRecord - if err := rows.Scan( - &i.ID, - &i.PaymentID, - &i.Key, - &i.Value, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const fetchPaymentsByIDs = `-- name: FetchPaymentsByIDs :many -SELECT - p.id, - p.amount_msat, - p.created_at, - p.payment_identifier, - p.fail_reason, - pi.intent_type, - pi.intent_payload -FROM payments p -LEFT JOIN payment_intents pi ON pi.payment_id = p.id -WHERE p.id IN (/*SLICE:payment_ids*/?) -ORDER BY p.id ASC -` - -type FetchPaymentsByIDsRow struct { - ID int64 - AmountMsat int64 - CreatedAt time.Time - PaymentIdentifier []byte - FailReason sql.NullInt32 - IntentType sql.NullInt16 - IntentPayload []byte -} - -// Batch fetch payment and intent data for a set of payment IDs. -// Used to avoid fetching redundant payment data when processing multiple -// attempts for the same payment. -func (q *Queries) FetchPaymentsByIDs(ctx context.Context, paymentIds []int64) ([]FetchPaymentsByIDsRow, error) { - query := fetchPaymentsByIDs - var queryParams []interface{} - if len(paymentIds) > 0 { - for _, v := range paymentIds { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:payment_ids*/?", makeQueryParams(len(queryParams), len(paymentIds)), 1) - } else { - query = strings.Replace(query, "/*SLICE:payment_ids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []FetchPaymentsByIDsRow - for rows.Next() { - var i FetchPaymentsByIDsRow - if err := rows.Scan( - &i.ID, - &i.AmountMsat, - &i.CreatedAt, - &i.PaymentIdentifier, - &i.FailReason, - &i.IntentType, - &i.IntentPayload, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const fetchPaymentsByIDsMig = `-- name: FetchPaymentsByIDsMig :many -SELECT - p.id, - p.amount_msat, - p.created_at, - p.payment_identifier, - p.fail_reason, - COUNT(ha.id) AS htlc_attempt_count -FROM payments p -LEFT JOIN payment_htlc_attempts ha ON ha.payment_id = p.id -WHERE p.id IN (/*SLICE:payment_ids*/?) -GROUP BY p.id, p.amount_msat, p.created_at, p.payment_identifier, p.fail_reason -ORDER BY p.id ASC -` - -type FetchPaymentsByIDsMigRow struct { - ID int64 - AmountMsat int64 - CreatedAt time.Time - PaymentIdentifier []byte - FailReason sql.NullInt32 - HtlcAttemptCount int64 -} - -// Migration-specific batch fetch that returns payment data along with HTLC -// attempt counts for structural validation during KV to SQL migration. -func (q *Queries) FetchPaymentsByIDsMig(ctx context.Context, paymentIds []int64) ([]FetchPaymentsByIDsMigRow, error) { - query := fetchPaymentsByIDsMig - var queryParams []interface{} - if len(paymentIds) > 0 { - for _, v := range paymentIds { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:payment_ids*/?", makeQueryParams(len(queryParams), len(paymentIds)), 1) - } else { - query = strings.Replace(query, "/*SLICE:payment_ids*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []FetchPaymentsByIDsMigRow - for rows.Next() { - var i FetchPaymentsByIDsMigRow - if err := rows.Scan( - &i.ID, - &i.AmountMsat, - &i.CreatedAt, - &i.PaymentIdentifier, - &i.FailReason, - &i.HtlcAttemptCount, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const fetchRouteLevelFirstHopCustomRecords = `-- name: FetchRouteLevelFirstHopCustomRecords :many -SELECT - l.id, - l.htlc_attempt_index, - l.key, - l.value -FROM payment_attempt_first_hop_custom_records l -WHERE l.htlc_attempt_index IN (/*SLICE:htlc_attempt_indices*/?) -ORDER BY l.htlc_attempt_index ASC, l.key ASC -` - -func (q *Queries) FetchRouteLevelFirstHopCustomRecords(ctx context.Context, htlcAttemptIndices []int64) ([]PaymentAttemptFirstHopCustomRecord, error) { - query := fetchRouteLevelFirstHopCustomRecords - var queryParams []interface{} - if len(htlcAttemptIndices) > 0 { - for _, v := range htlcAttemptIndices { - queryParams = append(queryParams, v) - } - query = strings.Replace(query, "/*SLICE:htlc_attempt_indices*/?", makeQueryParams(len(queryParams), len(htlcAttemptIndices)), 1) - } else { - query = strings.Replace(query, "/*SLICE:htlc_attempt_indices*/?", "NULL", 1) - } - rows, err := q.db.QueryContext(ctx, query, queryParams...) - if err != nil { - return nil, err - } - defer rows.Close() - var items []PaymentAttemptFirstHopCustomRecord - for rows.Next() { - var i PaymentAttemptFirstHopCustomRecord - if err := rows.Scan( - &i.ID, - &i.HtlcAttemptIndex, - &i.Key, - &i.Value, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const filterPayments = `-- name: FilterPayments :many -/* ───────────────────────────────────────────── - fetch queries - ───────────────────────────────────────────── -*/ - -SELECT - p.id, p.amount_msat, p.created_at, p.payment_identifier, p.fail_reason, - i.intent_type AS "intent_type", - i.intent_payload AS "intent_payload" -FROM payments p -LEFT JOIN payment_intents i ON i.payment_id = p.id -WHERE p.id > COALESCE($1, -1) - AND p.id < COALESCE($2, 9223372036854775807) - -- NOTE: We use non-nullable time params with Go-side defaults instead of - -- COALESCE, because COALESCE with text fallback causes type mismatch on - -- Postgres (timestamp vs text), and OR-based optional filters can prevent - -- the planner from using the created_at index. - AND p.created_at >= $3 - AND p.created_at <= $4 - AND ( - i.intent_type = $5 OR - $5 IS NULL OR i.intent_type IS NULL - ) -ORDER BY p.id ASC -LIMIT $6 -` - -type FilterPaymentsParams struct { - IndexOffsetGet sql.NullInt64 - IndexOffsetLet sql.NullInt64 - CreatedAfter time.Time - CreatedBefore time.Time - IntentType sql.NullInt16 - NumLimit int32 -} - -type FilterPaymentsRow struct { - Payment Payment - IntentType sql.NullInt16 - IntentPayload []byte -} - -func (q *Queries) FilterPayments(ctx context.Context, arg FilterPaymentsParams) ([]FilterPaymentsRow, error) { - rows, err := q.db.QueryContext(ctx, filterPayments, - arg.IndexOffsetGet, - arg.IndexOffsetLet, - arg.CreatedAfter, - arg.CreatedBefore, - arg.IntentType, - arg.NumLimit, - ) - if err != nil { - return nil, err - } - defer rows.Close() - var items []FilterPaymentsRow - for rows.Next() { - var i FilterPaymentsRow - if err := rows.Scan( - &i.Payment.ID, - &i.Payment.AmountMsat, - &i.Payment.CreatedAt, - &i.Payment.PaymentIdentifier, - &i.Payment.FailReason, - &i.IntentType, - &i.IntentPayload, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const filterPaymentsDesc = `-- name: FilterPaymentsDesc :many -SELECT - p.id, p.amount_msat, p.created_at, p.payment_identifier, p.fail_reason, - i.intent_type AS "intent_type", - i.intent_payload AS "intent_payload" -FROM payments p -LEFT JOIN payment_intents i ON i.payment_id = p.id -WHERE p.id > COALESCE($1, -1) - AND p.id < COALESCE($2, 9223372036854775807) - -- NOTE: We use non-nullable time params with Go-side defaults instead of - -- COALESCE, because COALESCE with text fallback causes type mismatch on - -- Postgres (timestamp vs text), and OR-based optional filters can prevent - -- the planner from using the created_at index. - AND p.created_at >= $3 - AND p.created_at <= $4 - AND ( - i.intent_type = $5 OR - $5 IS NULL OR i.intent_type IS NULL - ) -ORDER BY p.id DESC -LIMIT $6 -` - -type FilterPaymentsDescParams struct { - IndexOffsetGet sql.NullInt64 - IndexOffsetLet sql.NullInt64 - CreatedAfter time.Time - CreatedBefore time.Time - IntentType sql.NullInt16 - NumLimit int32 -} - -type FilterPaymentsDescRow struct { - Payment Payment - IntentType sql.NullInt16 - IntentPayload []byte -} - -func (q *Queries) FilterPaymentsDesc(ctx context.Context, arg FilterPaymentsDescParams) ([]FilterPaymentsDescRow, error) { - rows, err := q.db.QueryContext(ctx, filterPaymentsDesc, - arg.IndexOffsetGet, - arg.IndexOffsetLet, - arg.CreatedAfter, - arg.CreatedBefore, - arg.IntentType, - arg.NumLimit, - ) - if err != nil { - return nil, err - } - defer rows.Close() - var items []FilterPaymentsDescRow - for rows.Next() { - var i FilterPaymentsDescRow - if err := rows.Scan( - &i.Payment.ID, - &i.Payment.AmountMsat, - &i.Payment.CreatedAt, - &i.Payment.PaymentIdentifier, - &i.Payment.FailReason, - &i.IntentType, - &i.IntentPayload, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const insertHtlcAttempt = `-- name: InsertHtlcAttempt :one -INSERT INTO payment_htlc_attempts ( - payment_id, - attempt_index, - session_key, - attempt_time, - payment_hash, - first_hop_amount_msat, - route_total_time_lock, - route_total_amount, - route_source_key) -VALUES ( - $1, - $2, - $3, - $4, - $5, - $6, - $7, - $8, - $9) -RETURNING id -` - -type InsertHtlcAttemptParams struct { - PaymentID int64 - AttemptIndex int64 - SessionKey []byte - AttemptTime time.Time - PaymentHash []byte - FirstHopAmountMsat int64 - RouteTotalTimeLock int32 - RouteTotalAmount int64 - RouteSourceKey []byte -} - -func (q *Queries) InsertHtlcAttempt(ctx context.Context, arg InsertHtlcAttemptParams) (int64, error) { - row := q.db.QueryRowContext(ctx, insertHtlcAttempt, - arg.PaymentID, - arg.AttemptIndex, - arg.SessionKey, - arg.AttemptTime, - arg.PaymentHash, - arg.FirstHopAmountMsat, - arg.RouteTotalTimeLock, - arg.RouteTotalAmount, - arg.RouteSourceKey, - ) - var id int64 - err := row.Scan(&id) - return id, err -} - -const insertPayment = `-- name: InsertPayment :one -INSERT INTO payments ( - amount_msat, - created_at, - payment_identifier, - fail_reason) -VALUES ( - $1, - $2, - $3, - NULL -) -RETURNING id -` - -type InsertPaymentParams struct { - AmountMsat int64 - CreatedAt time.Time - PaymentIdentifier []byte -} - -// Insert a new payment and return its ID. -// When creating a payment we don't have a fail reason because we start the -// payment process. -func (q *Queries) InsertPayment(ctx context.Context, arg InsertPaymentParams) (int64, error) { - row := q.db.QueryRowContext(ctx, insertPayment, arg.AmountMsat, arg.CreatedAt, arg.PaymentIdentifier) - var id int64 - err := row.Scan(&id) - return id, err -} - -const insertPaymentAttemptFirstHopCustomRecord = `-- name: InsertPaymentAttemptFirstHopCustomRecord :exec -INSERT INTO payment_attempt_first_hop_custom_records ( - htlc_attempt_index, - key, - value -) -VALUES ( - $1, - $2, - $3 -) -` - -type InsertPaymentAttemptFirstHopCustomRecordParams struct { - HtlcAttemptIndex int64 - Key int64 - Value []byte -} - -func (q *Queries) InsertPaymentAttemptFirstHopCustomRecord(ctx context.Context, arg InsertPaymentAttemptFirstHopCustomRecordParams) error { - _, err := q.db.ExecContext(ctx, insertPaymentAttemptFirstHopCustomRecord, arg.HtlcAttemptIndex, arg.Key, arg.Value) - return err -} - -const insertPaymentDuplicateMig = `-- name: InsertPaymentDuplicateMig :one -INSERT INTO payment_duplicates ( - payment_id, - amount_msat, - created_at, - fail_reason, - settle_preimage, - settle_time -) -VALUES ( - $1, - $2, - $3, - $4, - $5, - $6 -) -RETURNING id -` - -type InsertPaymentDuplicateMigParams struct { - PaymentID int64 - AmountMsat int64 - CreatedAt time.Time - FailReason sql.NullInt32 - SettlePreimage []byte - SettleTime sql.NullTime -} - -// Insert a duplicate payment record into the payment_duplicates table and -// return its ID. -func (q *Queries) InsertPaymentDuplicateMig(ctx context.Context, arg InsertPaymentDuplicateMigParams) (int64, error) { - row := q.db.QueryRowContext(ctx, insertPaymentDuplicateMig, - arg.PaymentID, - arg.AmountMsat, - arg.CreatedAt, - arg.FailReason, - arg.SettlePreimage, - arg.SettleTime, - ) - var id int64 - err := row.Scan(&id) - return id, err -} - -const insertPaymentFirstHopCustomRecord = `-- name: InsertPaymentFirstHopCustomRecord :exec -INSERT INTO payment_first_hop_custom_records ( - payment_id, - key, - value -) -VALUES ( - $1, - $2, - $3 -) -` - -type InsertPaymentFirstHopCustomRecordParams struct { - PaymentID int64 - Key int64 - Value []byte -} - -func (q *Queries) InsertPaymentFirstHopCustomRecord(ctx context.Context, arg InsertPaymentFirstHopCustomRecordParams) error { - _, err := q.db.ExecContext(ctx, insertPaymentFirstHopCustomRecord, arg.PaymentID, arg.Key, arg.Value) - return err -} - -const insertPaymentHopCustomRecord = `-- name: InsertPaymentHopCustomRecord :exec -INSERT INTO payment_hop_custom_records ( - hop_id, - key, - value -) -VALUES ( - $1, - $2, - $3 -) -` - -type InsertPaymentHopCustomRecordParams struct { - HopID int64 - Key int64 - Value []byte -} - -func (q *Queries) InsertPaymentHopCustomRecord(ctx context.Context, arg InsertPaymentHopCustomRecordParams) error { - _, err := q.db.ExecContext(ctx, insertPaymentHopCustomRecord, arg.HopID, arg.Key, arg.Value) - return err -} - -const insertPaymentIntent = `-- name: InsertPaymentIntent :one -INSERT INTO payment_intents ( - payment_id, - intent_type, - intent_payload) -VALUES ( - $1, - $2, - $3 -) -RETURNING id -` - -type InsertPaymentIntentParams struct { - PaymentID int64 - IntentType int16 - IntentPayload []byte -} - -// Insert a payment intent for a given payment and return its ID. -func (q *Queries) InsertPaymentIntent(ctx context.Context, arg InsertPaymentIntentParams) (int64, error) { - row := q.db.QueryRowContext(ctx, insertPaymentIntent, arg.PaymentID, arg.IntentType, arg.IntentPayload) - var id int64 - err := row.Scan(&id) - return id, err -} - -const insertPaymentMig = `-- name: InsertPaymentMig :one -/* ───────────────────────────────────────────── - Migration-specific queries - - These queries are used ONLY for the one-time migration from KV to SQL. - ───────────────────────────────────────────── -*/ - -INSERT INTO payments ( - amount_msat, - created_at, - payment_identifier, - fail_reason) -VALUES ( - $1, - $2, - $3, - $4 -) -RETURNING id -` - -type InsertPaymentMigParams struct { - AmountMsat int64 - CreatedAt time.Time - PaymentIdentifier []byte - FailReason sql.NullInt32 -} - -// Migration-specific payment insert that allows setting fail_reason. -// Normal InsertPayment forces fail_reason to NULL since new payments -// aren't failed yet. During migration, we're inserting historical data -// that may already be failed. -func (q *Queries) InsertPaymentMig(ctx context.Context, arg InsertPaymentMigParams) (int64, error) { - row := q.db.QueryRowContext(ctx, insertPaymentMig, - arg.AmountMsat, - arg.CreatedAt, - arg.PaymentIdentifier, - arg.FailReason, - ) - var id int64 - err := row.Scan(&id) - return id, err -} - -const insertRouteHop = `-- name: InsertRouteHop :one -INSERT INTO payment_route_hops ( - htlc_attempt_index, - hop_index, - pub_key, - scid, - outgoing_time_lock, - amt_to_forward, - meta_data -) -VALUES ( - $1, - $2, - $3, - $4, - $5, - $6, - $7 -) -RETURNING id -` - -type InsertRouteHopParams struct { - HtlcAttemptIndex int64 - HopIndex int32 - PubKey []byte - Scid string - OutgoingTimeLock int32 - AmtToForward int64 - MetaData []byte -} - -func (q *Queries) InsertRouteHop(ctx context.Context, arg InsertRouteHopParams) (int64, error) { - row := q.db.QueryRowContext(ctx, insertRouteHop, - arg.HtlcAttemptIndex, - arg.HopIndex, - arg.PubKey, - arg.Scid, - arg.OutgoingTimeLock, - arg.AmtToForward, - arg.MetaData, - ) - var id int64 - err := row.Scan(&id) - return id, err -} - -const insertRouteHopAmp = `-- name: InsertRouteHopAmp :exec -INSERT INTO payment_route_hop_amp ( - hop_id, - root_share, - set_id, - child_index -) -VALUES ( - $1, - $2, - $3, - $4 -) -` - -type InsertRouteHopAmpParams struct { - HopID int64 - RootShare []byte - SetID []byte - ChildIndex int32 -} - -func (q *Queries) InsertRouteHopAmp(ctx context.Context, arg InsertRouteHopAmpParams) error { - _, err := q.db.ExecContext(ctx, insertRouteHopAmp, - arg.HopID, - arg.RootShare, - arg.SetID, - arg.ChildIndex, - ) - return err -} - -const insertRouteHopBlinded = `-- name: InsertRouteHopBlinded :exec -INSERT INTO payment_route_hop_blinded ( - hop_id, - encrypted_data, - blinding_point, - blinded_path_total_amt -) -VALUES ( - $1, - $2, - $3, - $4 -) -` - -type InsertRouteHopBlindedParams struct { - HopID int64 - EncryptedData []byte - BlindingPoint []byte - BlindedPathTotalAmt sql.NullInt64 -} - -func (q *Queries) InsertRouteHopBlinded(ctx context.Context, arg InsertRouteHopBlindedParams) error { - _, err := q.db.ExecContext(ctx, insertRouteHopBlinded, - arg.HopID, - arg.EncryptedData, - arg.BlindingPoint, - arg.BlindedPathTotalAmt, - ) - return err -} - -const insertRouteHopMpp = `-- name: InsertRouteHopMpp :exec -INSERT INTO payment_route_hop_mpp ( - hop_id, - payment_addr, - total_msat -) -VALUES ( - $1, - $2, - $3 -) -` - -type InsertRouteHopMppParams struct { - HopID int64 - PaymentAddr []byte - TotalMsat int64 -} - -func (q *Queries) InsertRouteHopMpp(ctx context.Context, arg InsertRouteHopMppParams) error { - _, err := q.db.ExecContext(ctx, insertRouteHopMpp, arg.HopID, arg.PaymentAddr, arg.TotalMsat) - return err -} - -const settleAttempt = `-- name: SettleAttempt :exec -INSERT INTO payment_htlc_attempt_resolutions ( - attempt_index, - resolution_time, - resolution_type, - settle_preimage -) -VALUES ( - $1, - $2, - $3, - $4 -) -` - -type SettleAttemptParams struct { - AttemptIndex int64 - ResolutionTime time.Time - ResolutionType int32 - SettlePreimage []byte -} - -func (q *Queries) SettleAttempt(ctx context.Context, arg SettleAttemptParams) error { - _, err := q.db.ExecContext(ctx, settleAttempt, - arg.AttemptIndex, - arg.ResolutionTime, - arg.ResolutionType, - arg.SettlePreimage, - ) - return err -} diff --git a/sqldb/sqlc/querier.go b/sqldb/sqlc/querier.go index 9b95a6699..7b7b06495 100644 --- a/sqldb/sqlc/querier.go +++ b/sqldb/sqlc/querier.go @@ -13,97 +13,26 @@ import ( type Querier interface { AddSourceNode(ctx context.Context, nodeID int64) error AddV1ChannelProof(ctx context.Context, arg AddV1ChannelProofParams) (sql.Result, error) - AddV2ChannelProof(ctx context.Context, arg AddV2ChannelProofParams) (sql.Result, error) ClearKVInvoiceHashIndex(ctx context.Context) error - CountPayments(ctx context.Context) (int64, error) CountZombieChannels(ctx context.Context, version int16) (int64, error) CreateChannel(ctx context.Context, arg CreateChannelParams) (int64, error) DeleteCanceledInvoices(ctx context.Context) (sql.Result, error) DeleteChannelPolicyExtraTypes(ctx context.Context, channelPolicyID int64) error DeleteChannels(ctx context.Context, ids []int64) error DeleteExtraNodeType(ctx context.Context, arg DeleteExtraNodeTypeParams) error - // Delete all failed HTLC attempts for the given payment. Resolution type 2 - // indicates a failed attempt. Uses EXISTS to scope the resolution lookup to - // only this payment's attempts, avoiding an O(N) scan of all failed - // resolutions across all payments. - DeleteFailedAttempts(ctx context.Context, paymentID int64) error DeleteInvoice(ctx context.Context, arg DeleteInvoiceParams) (sql.Result, error) DeleteNode(ctx context.Context, id int64) error DeleteNodeAddresses(ctx context.Context, nodeID int64) error DeleteNodeByPubKey(ctx context.Context, arg DeleteNodeByPubKeyParams) (sql.Result, error) DeleteNodeFeature(ctx context.Context, arg DeleteNodeFeatureParams) error - DeletePayment(ctx context.Context, id int64) error DeletePruneLogEntriesInRange(ctx context.Context, arg DeletePruneLogEntriesInRangeParams) error DeleteUnconnectedNodes(ctx context.Context) ([][]byte, error) DeleteZombieChannel(ctx context.Context, arg DeleteZombieChannelParams) (sql.Result, error) - FailAttempt(ctx context.Context, arg FailAttemptParams) error - FailPayment(ctx context.Context, arg FailPaymentParams) (sql.Result, error) FetchAMPSubInvoiceHTLCs(ctx context.Context, arg FetchAMPSubInvoiceHTLCsParams) ([]FetchAMPSubInvoiceHTLCsRow, error) FetchAMPSubInvoices(ctx context.Context, arg FetchAMPSubInvoicesParams) ([]AmpSubInvoice, error) - FetchHopLevelCustomRecords(ctx context.Context, hopIds []int64) ([]PaymentHopCustomRecord, error) - FetchHopsForAttempts(ctx context.Context, htlcAttemptIndices []int64) ([]FetchHopsForAttemptsRow, error) - // Batch query to fetch only HTLC resolution status for multiple payments. - // We don't need to order by payment_id and attempt_time because we will - // group the resolutions by payment_id in the background. - FetchHtlcAttemptResolutionsForPayments(ctx context.Context, paymentIds []int64) ([]FetchHtlcAttemptResolutionsForPaymentsRow, error) - FetchHtlcAttemptsForPayments(ctx context.Context, paymentIds []int64) ([]FetchHtlcAttemptsForPaymentsRow, error) - // Fetch all non-terminal payments using pagination. A payment is - // non-terminal if it has an unresolved attempt, or if it has not been - // permanently failed and has no settled attempt yet. - FetchNonTerminalPayments(ctx context.Context, arg FetchNonTerminalPaymentsParams) ([]FetchNonTerminalPaymentsRow, error) - FetchPayment(ctx context.Context, paymentIdentifier []byte) (FetchPaymentRow, error) - // Fetch all duplicate payment records from the payment_duplicates table for - // a given payment ID. - FetchPaymentDuplicates(ctx context.Context, paymentID int64) ([]PaymentDuplicate, error) - FetchPaymentLevelFirstHopCustomRecords(ctx context.Context, paymentIds []int64) ([]PaymentFirstHopCustomRecord, error) - // Batch fetch payment and intent data for a set of payment IDs. - // Used to avoid fetching redundant payment data when processing multiple - // attempts for the same payment. - FetchPaymentsByIDs(ctx context.Context, paymentIds []int64) ([]FetchPaymentsByIDsRow, error) - // Migration-specific batch fetch that returns payment data along with HTLC - // attempt counts for structural validation during KV to SQL migration. - FetchPaymentsByIDsMig(ctx context.Context, paymentIds []int64) ([]FetchPaymentsByIDsMigRow, error) - // FetchPendingInvoices returns all invoices in a pending state (open or - // accepted). The invoices_state_idx index on the state column makes this a - // fast index scan rather than a full table scan. id_cursor is an exclusive - // lower bound on the primary key used for cursor-based pagination; the caller - // must supply 0 when starting from the beginning. - FetchPendingInvoices(ctx context.Context, arg FetchPendingInvoicesParams) ([]Invoice, error) - FetchRouteLevelFirstHopCustomRecords(ctx context.Context, htlcAttemptIndices []int64) ([]PaymentAttemptFirstHopCustomRecord, error) FetchSettledAMPSubInvoices(ctx context.Context, arg FetchSettledAMPSubInvoicesParams) ([]FetchSettledAMPSubInvoicesRow, error) - // FilterInvoicesByAddIndex returns invoices whose add_index (primary key id) - // is greater than or equal to the given value, ordered by id. Because id is - // the primary key, this is always an efficient range scan on the clustered - // index. For cursor-based pagination the caller advances add_index_get to - // last_returned_id + 1 on each subsequent page. - FilterInvoicesByAddIndex(ctx context.Context, arg FilterInvoicesByAddIndexParams) ([]Invoice, error) - // FilterInvoicesBySettleIndex returns settled invoices whose settle_index is - // greater than or equal to the given value, ordered by id. The caller must - // always supply a concrete lower bound so the invoices_settle_index_idx index - // can be used. id_cursor is an exclusive lower bound on the primary key used - // for cursor-based pagination; the caller must supply 0 when starting from - // the beginning. - FilterInvoicesBySettleIndex(ctx context.Context, arg FilterInvoicesBySettleIndexParams) ([]Invoice, error) - // FilterInvoicesForward returns invoices in ascending id order. All parameters - // are non-nullable so the planner always sees plain range predicates and can - // use the primary-key index. For cursor-based pagination the caller advances - // add_index_get to last_returned_id + 1 on each subsequent page. The caller - // is responsible for supplying Go-side defaults when a filter is not needed: - // add_index_get → 1 (first valid invoice id) - // created_after → time.Unix(0, 0).UTC() (epoch – before any invoice) - // created_before → time.Date(9999, …) (far future – no upper cap) - // pending_only → false (include all states) - FilterInvoicesForward(ctx context.Context, arg FilterInvoicesForwardParams) ([]Invoice, error) - // FilterInvoicesReverse is the descending counterpart of FilterInvoicesForward. - // It returns invoices in descending id order. For cursor-based pagination the - // caller advances add_index_let to last_returned_id - 1 on each subsequent - // page; pass math.MaxInt64 to start from the most recent invoice. See - // FilterInvoicesForward for the expected Go-side defaults. - FilterInvoicesReverse(ctx context.Context, arg FilterInvoicesReverseParams) ([]Invoice, error) - FilterPayments(ctx context.Context, arg FilterPaymentsParams) ([]FilterPaymentsRow, error) - FilterPaymentsDesc(ctx context.Context, arg FilterPaymentsDescParams) ([]FilterPaymentsDescRow, error) + FilterInvoices(ctx context.Context, arg FilterInvoicesParams) ([]Invoice, error) GetAMPInvoiceID(ctx context.Context, setID []byte) (int64, error) - GetChainNetwork(ctx context.Context) (string, error) GetChannelAndNodesBySCID(ctx context.Context, arg GetChannelAndNodesBySCIDParams) (GetChannelAndNodesBySCIDRow, error) GetChannelByOutpointWithPolicies(ctx context.Context, arg GetChannelByOutpointWithPoliciesParams) (GetChannelByOutpointWithPoliciesRow, error) GetChannelBySCID(ctx context.Context, arg GetChannelBySCIDParams) (GraphChannel, error) @@ -114,7 +43,6 @@ type Querier interface { GetChannelPolicyExtraTypesBatch(ctx context.Context, policyIds []int64) ([]GetChannelPolicyExtraTypesBatchRow, error) GetChannelsByIDs(ctx context.Context, ids []int64) ([]GetChannelsByIDsRow, error) GetChannelsByOutpoints(ctx context.Context, outpoints []string) ([]GetChannelsByOutpointsRow, error) - GetChannelsByPolicyBlockRange(ctx context.Context, arg GetChannelsByPolicyBlockRangeParams) ([]GetChannelsByPolicyBlockRangeRow, error) GetChannelsByPolicyLastUpdateRange(ctx context.Context, arg GetChannelsByPolicyLastUpdateRangeParams) ([]GetChannelsByPolicyLastUpdateRangeRow, error) GetChannelsBySCIDRange(ctx context.Context, arg GetChannelsBySCIDRangeParams) ([]GetChannelsBySCIDRangeRow, error) GetChannelsBySCIDWithPolicies(ctx context.Context, arg GetChannelsBySCIDWithPoliciesParams) ([]GetChannelsBySCIDWithPoliciesRow, error) @@ -122,10 +50,11 @@ type Querier interface { GetClosedChannelsSCIDs(ctx context.Context, scids [][]byte) ([][]byte, error) GetDatabaseVersion(ctx context.Context) (int32, error) GetExtraNodeTypes(ctx context.Context, nodeID int64) ([]GraphNodeExtraType, error) - GetInvoiceByAddr(ctx context.Context, paymentAddr []byte) (Invoice, error) + // This method may return more than one invoice if filter using multiple fields + // from different invoices. It is the caller's responsibility to ensure that + // we bubble up an error in those cases. + GetInvoice(ctx context.Context, arg GetInvoiceParams) ([]Invoice, error) GetInvoiceByHash(ctx context.Context, hash []byte) (Invoice, error) - // TODO(ziggie): This query can only return one invoice if the set_id is - // the primary key of amp_sub_invoices table. GetInvoiceBySetID(ctx context.Context, setID []byte) ([]Invoice, error) GetInvoiceFeatures(ctx context.Context, invoiceID int64) ([]InvoiceFeature, error) GetInvoiceHTLCCustomRecords(ctx context.Context, invoiceID int64) ([]GetInvoiceHTLCCustomRecordsRow, error) @@ -140,20 +69,12 @@ type Querier interface { GetNodeFeaturesBatch(ctx context.Context, ids []int64) ([]GraphNodeFeature, error) GetNodeFeaturesByPubKey(ctx context.Context, arg GetNodeFeaturesByPubKeyParams) ([]int32, error) GetNodeIDByPubKey(ctx context.Context, arg GetNodeIDByPubKeyParams) (int64, error) - GetNodesByBlockHeightRange(ctx context.Context, arg GetNodesByBlockHeightRangeParams) ([]GraphNode, error) GetNodesByIDs(ctx context.Context, ids []int64) ([]GraphNode, error) GetNodesByLastUpdateRange(ctx context.Context, arg GetNodesByLastUpdateRangeParams) ([]GraphNode, error) GetPruneEntriesForHeights(ctx context.Context, heights []int64) ([]GraphPruneLog, error) GetPruneHashByHeight(ctx context.Context, blockHeight int64) ([]byte, error) GetPruneTip(ctx context.Context) (GraphPruneLog, error) - // Returns only public V1 nodes within the given last_update range. A V1 node - // is public if it has at least one channel with a bitcoin_1_signature set. The - // public check uses two separate EXISTS probes (one per node_id column) - // instead of a single OR on node_id_1/node_id_2 so the planner can use the - // channel node-id indexes directly. - GetPublicNodesByLastUpdateRange(ctx context.Context, arg GetPublicNodesByLastUpdateRangeParams) ([]GraphNode, error) GetPublicV1ChannelsBySCID(ctx context.Context, arg GetPublicV1ChannelsBySCIDParams) ([]GraphChannel, error) - GetPublicV2ChannelsBySCID(ctx context.Context, arg GetPublicV2ChannelsBySCIDParams) ([]GraphChannel, error) GetSCIDByOutpoint(ctx context.Context, arg GetSCIDByOutpointParams) ([]byte, error) GetSourceNodesByVersion(ctx context.Context, version int16) ([]GetSourceNodesByVersionRow, error) // NOTE: this is V1 specific since for V1, disabled is a @@ -161,15 +82,11 @@ type Querier interface { // structure will have a more complex disabled bit vector // and so the query for V2 may differ. GetV1DisabledSCIDs(ctx context.Context) ([][]byte, error) - // NOTE: this is V2 specific since V2 uses a disable flag - // bit vector instead of a single boolean. - GetV2DisabledSCIDs(ctx context.Context) ([][]byte, error) GetZombieChannel(ctx context.Context, arg GetZombieChannelParams) (GraphZombieChannel, error) GetZombieChannelsSCIDs(ctx context.Context, arg GetZombieChannelsSCIDsParams) ([]GraphZombieChannel, error) HighestSCID(ctx context.Context, version int16) ([]byte, error) InsertAMPSubInvoice(ctx context.Context, arg InsertAMPSubInvoiceParams) error InsertAMPSubInvoiceHTLC(ctx context.Context, arg InsertAMPSubInvoiceHTLCParams) error - InsertChainNetwork(ctx context.Context, network string) error InsertChannelFeature(ctx context.Context, arg InsertChannelFeatureParams) error // NOTE: This query is only meant to be used by the graph SQL migration since // for that migration, in order to be retry-safe, we don't want to error out if @@ -184,7 +101,6 @@ type Querier interface { // UpsertEdgePolicy query is used because of the constraint in that query that // requires a policy update to have a newer last_update than the existing one). InsertEdgePolicyMig(ctx context.Context, arg InsertEdgePolicyMigParams) (int64, error) - InsertHtlcAttempt(ctx context.Context, arg InsertHtlcAttemptParams) (int64, error) InsertInvoice(ctx context.Context, arg InsertInvoiceParams) (int64, error) InsertInvoiceFeature(ctx context.Context, arg InsertInvoiceFeatureParams) error InsertInvoiceHTLC(ctx context.Context, arg InsertInvoiceHTLCParams) (int64, error) @@ -198,41 +114,17 @@ type Querier interface { // is used because of the constraint in that query that requires a node update // to have a newer last_update than the existing node). InsertNodeMig(ctx context.Context, arg InsertNodeMigParams) (int64, error) - // Insert a new payment and return its ID. - // When creating a payment we don't have a fail reason because we start the - // payment process. - InsertPayment(ctx context.Context, arg InsertPaymentParams) (int64, error) - InsertPaymentAttemptFirstHopCustomRecord(ctx context.Context, arg InsertPaymentAttemptFirstHopCustomRecordParams) error - // Insert a duplicate payment record into the payment_duplicates table and - // return its ID. - InsertPaymentDuplicateMig(ctx context.Context, arg InsertPaymentDuplicateMigParams) (int64, error) - InsertPaymentFirstHopCustomRecord(ctx context.Context, arg InsertPaymentFirstHopCustomRecordParams) error - InsertPaymentHopCustomRecord(ctx context.Context, arg InsertPaymentHopCustomRecordParams) error - // Insert a payment intent for a given payment and return its ID. - InsertPaymentIntent(ctx context.Context, arg InsertPaymentIntentParams) (int64, error) - // Migration-specific payment insert that allows setting fail_reason. - // Normal InsertPayment forces fail_reason to NULL since new payments - // aren't failed yet. During migration, we're inserting historical data - // that may already be failed. - InsertPaymentMig(ctx context.Context, arg InsertPaymentMigParams) (int64, error) - InsertRouteHop(ctx context.Context, arg InsertRouteHopParams) (int64, error) - InsertRouteHopAmp(ctx context.Context, arg InsertRouteHopAmpParams) error - InsertRouteHopBlinded(ctx context.Context, arg InsertRouteHopBlindedParams) error - InsertRouteHopMpp(ctx context.Context, arg InsertRouteHopMppParams) error IsClosedChannel(ctx context.Context, scid []byte) (bool, error) IsPublicV1Node(ctx context.Context, pubKey []byte) (bool, error) - IsPublicV2Node(ctx context.Context, pubKey []byte) (bool, error) IsZombieChannel(ctx context.Context, arg IsZombieChannelParams) (bool, error) ListChannelsByNodeID(ctx context.Context, arg ListChannelsByNodeIDParams) ([]ListChannelsByNodeIDRow, error) ListChannelsForNodeIDs(ctx context.Context, arg ListChannelsForNodeIDsParams) ([]ListChannelsForNodeIDsRow, error) ListChannelsPaginated(ctx context.Context, arg ListChannelsPaginatedParams) ([]ListChannelsPaginatedRow, error) - ListChannelsPaginatedV2(ctx context.Context, arg ListChannelsPaginatedV2Params) ([]ListChannelsPaginatedV2Row, error) ListChannelsWithPoliciesForCachePaginated(ctx context.Context, arg ListChannelsWithPoliciesForCachePaginatedParams) ([]ListChannelsWithPoliciesForCachePaginatedRow, error) ListChannelsWithPoliciesPaginated(ctx context.Context, arg ListChannelsWithPoliciesPaginatedParams) ([]ListChannelsWithPoliciesPaginatedRow, error) ListNodeIDsAndPubKeys(ctx context.Context, arg ListNodeIDsAndPubKeysParams) ([]ListNodeIDsAndPubKeysRow, error) ListNodesPaginated(ctx context.Context, arg ListNodesPaginatedParams) ([]GraphNode, error) NextInvoiceSettleIndex(ctx context.Context) (int64, error) - NodeExists(ctx context.Context, arg NodeExistsParams) (bool, error) OnAMPSubInvoiceCanceled(ctx context.Context, arg OnAMPSubInvoiceCanceledParams) error OnAMPSubInvoiceCreated(ctx context.Context, arg OnAMPSubInvoiceCreatedParams) error OnAMPSubInvoiceSettled(ctx context.Context, arg OnAMPSubInvoiceSettledParams) error @@ -241,7 +133,6 @@ type Querier interface { OnInvoiceSettled(ctx context.Context, arg OnInvoiceSettledParams) error SetKVInvoicePaymentHash(ctx context.Context, arg SetKVInvoicePaymentHashParams) error SetMigration(ctx context.Context, arg SetMigrationParams) error - SettleAttempt(ctx context.Context, arg SettleAttemptParams) error UpdateAMPSubInvoiceHTLCPreimage(ctx context.Context, arg UpdateAMPSubInvoiceHTLCPreimageParams) (sql.Result, error) UpdateAMPSubInvoiceState(ctx context.Context, arg UpdateAMPSubInvoiceStateParams) error UpdateInvoiceAmountPaid(ctx context.Context, arg UpdateInvoiceAmountPaidParams) (sql.Result, error) diff --git a/sqldb/sqlc/queries/chain_params.sql b/sqldb/sqlc/queries/chain_params.sql deleted file mode 100644 index 596ff4b23..000000000 --- a/sqldb/sqlc/queries/chain_params.sql +++ /dev/null @@ -1,8 +0,0 @@ --- name: InsertChainNetwork :exec -INSERT INTO chain_params (single_row, network) -VALUES (TRUE, sqlc.arg(network)) -ON CONFLICT (single_row) DO NOTHING; - --- name: GetChainNetwork :one -SELECT network FROM chain_params -WHERE single_row = TRUE; diff --git a/sqldb/sqlc/queries/graph.sql b/sqldb/sqlc/queries/graph.sql index a7683d1fb..a8ff040d9 100644 --- a/sqldb/sqlc/queries/graph.sql +++ b/sqldb/sqlc/queries/graph.sql @@ -5,9 +5,9 @@ -- name: UpsertNode :one INSERT INTO graph_nodes ( - version, pub_key, alias, last_update, block_height, color, signature + version, pub_key, alias, last_update, color, signature ) VALUES ( - $1, $2, $3, $4, $5, $6, $7 + $1, $2, $3, $4, $5, $6 ) ON CONFLICT (pub_key, version) -- Update the following fields if a conflict occurs on pub_key @@ -15,13 +15,10 @@ ON CONFLICT (pub_key, version) DO UPDATE SET alias = EXCLUDED.alias, last_update = EXCLUDED.last_update, - block_height = EXCLUDED.block_height, color = EXCLUDED.color, signature = EXCLUDED.signature -WHERE (graph_nodes.last_update IS NULL - OR EXCLUDED.last_update > graph_nodes.last_update) -AND (graph_nodes.block_height IS NULL - OR EXCLUDED.block_height >= graph_nodes.block_height) +WHERE graph_nodes.last_update IS NULL + OR EXCLUDED.last_update > graph_nodes.last_update RETURNING id; -- We use a separate upsert for our own node since we want to be less strict @@ -29,9 +26,9 @@ RETURNING id; -- update the record even if the last_update is the same as what we have. -- name: UpsertSourceNode :one INSERT INTO graph_nodes ( - version, pub_key, alias, last_update, block_height, color, signature + version, pub_key, alias, last_update, color, signature ) VALUES ( - $1, $2, $3, $4, $5, $6, $7 + $1, $2, $3, $4, $5, $6 ) ON CONFLICT (pub_key, version) -- Update the following fields if a conflict occurs on pub_key @@ -39,13 +36,10 @@ ON CONFLICT (pub_key, version) DO UPDATE SET alias = EXCLUDED.alias, last_update = EXCLUDED.last_update, - block_height = EXCLUDED.block_height, color = EXCLUDED.color, signature = EXCLUDED.signature WHERE graph_nodes.last_update IS NULL OR EXCLUDED.last_update >= graph_nodes.last_update -AND (graph_nodes.block_height IS NULL - OR EXCLUDED.block_height >= graph_nodes.block_height) RETURNING id; -- name: GetNodesByIDs :many @@ -59,14 +53,6 @@ FROM graph_nodes WHERE pub_key = $1 AND version = $2; --- name: NodeExists :one -SELECT EXISTS ( - SELECT 1 - FROM graph_nodes - WHERE pub_key = $1 - AND version = $2 -) AS node_exists; - -- name: GetNodeIDByPubKey :one SELECT id FROM graph_nodes @@ -101,36 +87,14 @@ SELECT EXISTS ( -- one of the signatures since we only ever set them -- together. WHERE c.version = 1 - AND COALESCE(length(c.bitcoin_1_signature), 0) > 0 + AND c.bitcoin_1_signature IS NOT NULL AND n.pub_key = $1 UNION ALL SELECT 1 FROM graph_channels c JOIN graph_nodes n ON n.id = c.node_id_2 WHERE c.version = 1 - AND COALESCE(length(c.bitcoin_1_signature), 0) > 0 - AND n.pub_key = $1 -); - --- name: IsPublicV2Node :one -SELECT EXISTS ( - SELECT 1 - FROM graph_channels c - JOIN graph_nodes n ON n.id = c.node_id_1 - -- NOTE: we hard-code the version here since the clauses - -- here that determine if a node is public is specific - -- to the V2 gossip protocol. - WHERE c.version = 2 - AND COALESCE(length(c.signature), 0) > 0 - AND n.pub_key = $1 - - UNION ALL - - SELECT 1 - FROM graph_channels c - JOIN graph_nodes n ON n.id = c.node_id_2 - WHERE c.version = 2 - AND COALESCE(length(c.signature), 0) > 0 + AND c.bitcoin_1_signature IS NOT NULL AND n.pub_key = $1 ); @@ -227,90 +191,35 @@ ORDER BY node_id, type, position; -- name: GetNodesByLastUpdateRange :many SELECT * FROM graph_nodes -WHERE version = 1 - AND last_update >= @start_time - AND last_update < @end_time - -- Pagination: We use (last_update, pub_key) as a compound cursor. - -- This ensures stable ordering and allows us to resume from where we left off. - -- We use COALESCE with -1 as sentinel since timestamps are always positive. - AND ( - last_update > COALESCE(sqlc.narg('last_update'), -1) - OR - (last_update = COALESCE(sqlc.narg('last_update'), -1) - AND pub_key > sqlc.narg('last_pub_key')) - ) -ORDER BY last_update ASC, pub_key ASC -LIMIT COALESCE(sqlc.narg('max_results'), 999999999); - --- name: GetPublicNodesByLastUpdateRange :many --- Returns only public V1 nodes within the given last_update range. A V1 node --- is public if it has at least one channel with a bitcoin_1_signature set. The --- public check uses two separate EXISTS probes (one per node_id column) --- instead of a single OR on node_id_1/node_id_2 so the planner can use the --- channel node-id indexes directly. -SELECT * -FROM graph_nodes -WHERE version = 1 - AND last_update >= @start_time +WHERE last_update >= @start_time AND last_update <= @end_time -- Pagination: We use (last_update, pub_key) as a compound cursor. -- This ensures stable ordering and allows us to resume from where we left off. -- We use COALESCE with -1 as sentinel since timestamps are always positive. AND ( + -- Include rows with last_update greater than cursor (or all rows if cursor is -1) last_update > COALESCE(sqlc.narg('last_update'), -1) - OR - (last_update = COALESCE(sqlc.narg('last_update'), -1) + OR + -- For rows with same last_update, use pub_key as tiebreaker + (last_update = COALESCE(sqlc.narg('last_update'), -1) AND pub_key > sqlc.narg('last_pub_key')) ) + -- Optional filter for public nodes only AND ( - EXISTS ( - SELECT 1 - FROM graph_channels c - WHERE c.version = 1 - AND COALESCE(length(c.bitcoin_1_signature), 0) > 0 - AND c.node_id_1 = graph_nodes.id - ) - OR EXISTS ( - SELECT 1 - FROM graph_channels c - WHERE c.version = 1 - AND COALESCE(length(c.bitcoin_1_signature), 0) > 0 - AND c.node_id_2 = graph_nodes.id - ) - ) -ORDER BY last_update ASC, pub_key ASC -LIMIT COALESCE(sqlc.narg('max_results'), 999999999); - --- name: GetNodesByBlockHeightRange :many -SELECT * -FROM graph_nodes -WHERE graph_nodes.version = @version - AND block_height >= @start_height - AND block_height < @end_height - -- Pagination: We use (block_height, pub_key) as a compound cursor. - -- This ensures stable ordering and allows us to resume from where we left off. - -- We use COALESCE with -1 as sentinel since block heights are always positive. - AND ( - block_height > COALESCE(sqlc.narg('last_block_height'), -1) - OR - (block_height = COALESCE(sqlc.narg('last_block_height'), -1) - AND pub_key > sqlc.narg('last_pub_key')) - ) - -- Optional filter for public nodes only. - AND ( + -- If only_public is false or not provided, include all nodes COALESCE(sqlc.narg('only_public'), FALSE) IS FALSE - OR - -- For V2 protocol, a node is public if it has at least one announced - -- v2 channel (indicated by a non-empty channel announcement signature). + OR + -- For V1 protocol, a node is public if it has at least one public channel. + -- A public channel has bitcoin_1_signature set (channel announcement received). EXISTS ( SELECT 1 FROM graph_channels c - WHERE c.version = graph_nodes.version - AND COALESCE(length(c.signature), 0) > 0 + WHERE c.version = 1 + AND c.bitcoin_1_signature IS NOT NULL AND (c.node_id_1 = graph_nodes.id OR c.node_id_2 = graph_nodes.id) ) ) -ORDER BY block_height ASC, pub_key ASC +ORDER BY last_update ASC, pub_key ASC LIMIT COALESCE(sqlc.narg('max_results'), 999999999); -- name: DeleteNodeAddresses :exec @@ -374,9 +283,9 @@ INSERT INTO graph_channels ( version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, - bitcoin_2_signature, signature, funding_pk_script, merkle_root_hash + bitcoin_2_signature ) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15 + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 ) RETURNING id; @@ -389,12 +298,6 @@ SET node_1_signature = $2, WHERE scid = $1 AND version = 1; --- name: AddV2ChannelProof :execresult -UPDATE graph_channels -SET signature = $2 -WHERE scid = $1 - AND version = 2; - -- name: GetChannelsBySCIDRange :many SELECT sqlc.embed(c), n1.pub_key AS node1_pub_key, @@ -462,8 +365,6 @@ SELECT cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, - cp1.block_height AS policy1_block_height, - cp1.disable_flags AS policy1_disable_flags, -- Policy 2 cp2.id AS policy2_id, @@ -480,9 +381,7 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy_2_message_flags, cp2.channel_flags AS policy_2_channel_flags, - cp2.signature AS policy2_signature, - cp2.block_height AS policy2_block_height, - cp2.disable_flags AS policy2_disable_flags + cp2.signature AS policy2_signature FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -521,8 +420,6 @@ SELECT cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, - cp1.block_height AS policy1_block_height, - cp1.disable_flags AS policy1_disable_flags, -- Policy 2 cp2.id AS policy2_id, @@ -539,9 +436,7 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature, - cp2.block_height AS policy2_block_height, - cp2.disable_flags AS policy2_disable_flags + cp2.signature AS policy2_signature FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -574,8 +469,6 @@ SELECT cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, - cp1.block_height AS policy1_block_height, - cp1.disable_flags AS policy1_disable_flags, -- Policy 2 (node_id_2) cp2.id AS policy2_id, @@ -592,9 +485,7 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature, - cp2.block_height AS policy2_block_height, - cp2.disable_flags AS policy2_disable_flags + cp2.signature AS policy2_signature FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -634,88 +525,6 @@ ORDER BY c.id ASC LIMIT COALESCE(sqlc.narg('max_results'), 999999999); --- name: GetChannelsByPolicyBlockRange :many -SELECT - sqlc.embed(c), - sqlc.embed(n1), - sqlc.embed(n2), - - -- Policy 1 (node_id_1) - cp1.id AS policy1_id, - cp1.node_id AS policy1_node_id, - cp1.version AS policy1_version, - cp1.timelock AS policy1_timelock, - cp1.fee_ppm AS policy1_fee_ppm, - cp1.base_fee_msat AS policy1_base_fee_msat, - cp1.min_htlc_msat AS policy1_min_htlc_msat, - cp1.max_htlc_msat AS policy1_max_htlc_msat, - cp1.last_update AS policy1_last_update, - cp1.disabled AS policy1_disabled, - cp1.inbound_base_fee_msat AS policy1_inbound_base_fee_msat, - cp1.inbound_fee_rate_milli_msat AS policy1_inbound_fee_rate_milli_msat, - cp1.message_flags AS policy1_message_flags, - cp1.channel_flags AS policy1_channel_flags, - cp1.signature AS policy1_signature, - cp1.block_height AS policy1_block_height, - cp1.disable_flags AS policy1_disable_flags, - - -- Policy 2 (node_id_2) - cp2.id AS policy2_id, - cp2.node_id AS policy2_node_id, - cp2.version AS policy2_version, - cp2.timelock AS policy2_timelock, - cp2.fee_ppm AS policy2_fee_ppm, - cp2.base_fee_msat AS policy2_base_fee_msat, - cp2.min_htlc_msat AS policy2_min_htlc_msat, - cp2.max_htlc_msat AS policy2_max_htlc_msat, - cp2.last_update AS policy2_last_update, - cp2.disabled AS policy2_disabled, - cp2.inbound_base_fee_msat AS policy2_inbound_base_fee_msat, - cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, - cp2.message_flags AS policy2_message_flags, - cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature, - cp2.block_height AS policy2_block_height, - cp2.disable_flags AS policy2_disable_flags - -FROM graph_channels c - JOIN graph_nodes n1 ON c.node_id_1 = n1.id - JOIN graph_nodes n2 ON c.node_id_2 = n2.id - LEFT JOIN graph_channel_policies cp1 - ON cp1.channel_id = c.id AND cp1.node_id = c.node_id_1 AND cp1.version = c.version - LEFT JOIN graph_channel_policies cp2 - ON cp2.channel_id = c.id AND cp2.node_id = c.node_id_2 AND cp2.version = c.version -WHERE c.version = @version - AND ( - (cp1.block_height >= @start_height AND cp1.block_height < @end_height) - OR - (cp2.block_height >= @start_height AND cp2.block_height < @end_height) - ) - -- Pagination using compound cursor (max_block_height, id). - -- We use COALESCE with -1 as sentinel since block heights are always positive. - AND ( - (CASE - WHEN COALESCE(cp1.block_height, 0) >= COALESCE(cp2.block_height, 0) - THEN COALESCE(cp1.block_height, 0) - ELSE COALESCE(cp2.block_height, 0) - END > COALESCE(sqlc.narg('last_block_height'), -1)) - OR - (CASE - WHEN COALESCE(cp1.block_height, 0) >= COALESCE(cp2.block_height, 0) - THEN COALESCE(cp1.block_height, 0) - ELSE COALESCE(cp2.block_height, 0) - END = COALESCE(sqlc.narg('last_block_height'), -1) - AND c.id > COALESCE(sqlc.narg('last_id'), -1)) - ) -ORDER BY - CASE - WHEN COALESCE(cp1.block_height, 0) >= COALESCE(cp2.block_height, 0) - THEN COALESCE(cp1.block_height, 0) - ELSE COALESCE(cp2.block_height, 0) - END ASC, - c.id ASC -LIMIT COALESCE(sqlc.narg('max_results'), 999999999); - -- name: GetChannelByOutpointWithPolicies :one SELECT sqlc.embed(c), @@ -739,8 +548,6 @@ SELECT cp1.message_flags AS policy_1_message_flags, cp1.channel_flags AS policy_1_channel_flags, cp1.signature AS policy_1_signature, - cp1.block_height AS policy_1_block_height, - cp1.disable_flags AS policy_1_disable_flags, -- Node 2 policy cp2.id AS policy_2_id, @@ -757,9 +564,7 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy_2_message_flags, cp2.channel_flags AS policy_2_channel_flags, - cp2.signature AS policy_2_signature, - cp2.block_height AS policy_2_block_height, - cp2.disable_flags AS policy_2_disable_flags + cp2.signature AS policy_2_signature FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id JOIN graph_nodes n2 ON c.node_id_2 = n2.id @@ -800,8 +605,6 @@ SELECT sqlc.embed(c), cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, - cp1.block_height AS policy1_block_height, - cp1.disable_flags AS policy1_disable_flags, -- Policy 2 cp2.id AS policy2_id, @@ -818,9 +621,7 @@ SELECT sqlc.embed(c), cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature, - cp2.block_height AS policy2_block_height, - cp2.disable_flags AS policy2_disable_flags + cp2.signature AS policy2_signature FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -857,8 +658,6 @@ SELECT sqlc.embed(c), cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, - cp1.block_height AS policy1_block_height, - cp1.disable_flags AS policy1_disable_flags, -- Policy 2 cp2.id AS policy2_id, @@ -875,9 +674,7 @@ SELECT sqlc.embed(c), cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature, - cp2.block_height AS policy2_block_height, - cp2.disable_flags AS policy2_disable_flags + cp2.signature AS policy2_signature FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -892,20 +689,9 @@ WHERE c.version = $1 -- name: GetPublicV1ChannelsBySCID :many SELECT * FROM graph_channels -WHERE version = 1 - AND COALESCE(length(node_1_signature), 0) > 0 +WHERE node_1_signature IS NOT NULL AND scid >= @start_scid - AND scid < @end_scid -ORDER BY scid ASC; - --- name: GetPublicV2ChannelsBySCID :many -SELECT * -FROM graph_channels -WHERE version = 2 - AND COALESCE(length(signature), 0) > 0 - AND scid >= @start_scid - AND scid < @end_scid -ORDER BY scid ASC; + AND scid < @end_scid; -- name: ListChannelsPaginated :many SELECT id, bitcoin_key_1, bitcoin_key_2, outpoint @@ -914,13 +700,6 @@ WHERE c.version = $1 AND c.id > $2 ORDER BY c.id LIMIT $3; --- name: ListChannelsPaginatedV2 :many -SELECT id, outpoint, funding_pk_script -FROM graph_channels c -WHERE c.version = 2 AND c.id > $1 -ORDER BY c.id -LIMIT $2; - -- name: ListChannelsWithPoliciesPaginated :many SELECT sqlc.embed(c), @@ -944,8 +723,6 @@ SELECT cp1.inbound_fee_rate_milli_msat AS policy1_inbound_fee_rate_milli_msat, cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, - cp1.block_height AS policy1_block_height, - cp1.disable_flags AS policy1_disable_flags, cp1.signature AS policy_1_signature, -- Node 2 policy @@ -963,9 +740,7 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy_2_signature, - cp2.block_height AS policy_2_block_height, - cp2.disable_flags AS policy_2_disable_flags + cp2.signature AS policy_2_signature FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -989,7 +764,6 @@ SELECT n2.pub_key AS node2_pubkey, -- Node 1 policy - cp1.version AS policy1_version, cp1.timelock AS policy_1_timelock, cp1.fee_ppm AS policy_1_fee_ppm, cp1.base_fee_msat AS policy_1_base_fee_msat, @@ -1000,11 +774,8 @@ SELECT cp1.inbound_fee_rate_milli_msat AS policy1_inbound_fee_rate_milli_msat, cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, - cp1.block_height AS policy1_block_height, - cp1.disable_flags AS policy1_disable_flags, -- Node 2 policy - cp2.version AS policy2_version, cp2.timelock AS policy_2_timelock, cp2.fee_ppm AS policy_2_fee_ppm, cp2.base_fee_msat AS policy_2_base_fee_msat, @@ -1014,9 +785,7 @@ SELECT cp2.inbound_base_fee_msat AS policy2_inbound_base_fee_msat, cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, - cp2.channel_flags AS policy2_channel_flags, - cp2.block_height AS policy2_block_height, - cp2.disable_flags AS policy2_disable_flags + cp2.channel_flags AS policy2_channel_flags FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -1089,9 +858,9 @@ INSERT INTO graph_channel_policies ( base_fee_msat, min_htlc_msat, last_update, disabled, max_htlc_msat, inbound_base_fee_msat, inbound_fee_rate_milli_msat, message_flags, channel_flags, - signature, block_height, disable_flags + signature ) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17 + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15 ) ON CONFLICT (channel_id, node_id, version) -- Update the following fields if a conflict occurs on channel_id, @@ -1108,21 +877,8 @@ ON CONFLICT (channel_id, node_id, version) inbound_fee_rate_milli_msat = EXCLUDED.inbound_fee_rate_milli_msat, message_flags = EXCLUDED.message_flags, channel_flags = EXCLUDED.channel_flags, - signature = EXCLUDED.signature, - block_height = EXCLUDED.block_height, - disable_flags = EXCLUDED.disable_flags -WHERE ( - EXCLUDED.version = 1 AND ( - graph_channel_policies.last_update IS NULL - OR EXCLUDED.last_update > graph_channel_policies.last_update - ) -) -OR ( - EXCLUDED.version = 2 AND ( - graph_channel_policies.block_height IS NULL - OR EXCLUDED.block_height >= graph_channel_policies.block_height - ) -) + signature = EXCLUDED.signature +WHERE EXCLUDED.last_update > graph_channel_policies.last_update RETURNING id; -- name: GetChannelPolicyByChannelAndNode :one @@ -1154,8 +910,6 @@ SELECT cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, - cp1.block_height AS policy1_block_height, - cp1.disable_flags AS policy1_disable_flags, -- Policy 2 cp2.id AS policy2_id, @@ -1172,9 +926,7 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy_2_message_flags, cp2.channel_flags AS policy_2_channel_flags, - cp2.signature AS policy2_signature, - cp2.block_height AS policy2_block_height, - cp2.disable_flags AS policy2_disable_flags + cp2.signature AS policy2_signature FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -1223,17 +975,6 @@ AND c.version = 1 GROUP BY c.scid HAVING COUNT(*) > 1; --- name: GetV2DisabledSCIDs :many -SELECT c.scid -FROM graph_channels c - JOIN graph_channel_policies cp ON cp.channel_id = c.id --- NOTE: this is V2 specific since V2 uses a disable flag --- bit vector instead of a single boolean. -WHERE COALESCE(cp.disable_flags, 0) != 0 -AND c.version = 2 -GROUP BY c.scid -HAVING COUNT(*) > 1; - -- name: DeleteChannelPolicyExtraTypes :exec DELETE FROM graph_channel_policy_extra_types WHERE channel_policy_id = $1; @@ -1434,4 +1175,4 @@ ON CONFLICT (channel_id, node_id, version) message_flags = EXCLUDED.message_flags, channel_flags = EXCLUDED.channel_flags, signature = EXCLUDED.signature -RETURNING id; +RETURNING id; \ No newline at end of file diff --git a/sqldb/sqlc/queries/invoices.sql b/sqldb/sqlc/queries/invoices.sql index 91525136a..db1f46e61 100644 --- a/sqldb/sqlc/queries/invoices.sql +++ b/sqldb/sqlc/queries/invoices.sql @@ -29,101 +29,83 @@ SELECT * FROM invoice_features WHERE invoice_id = $1; +-- This method may return more than one invoice if filter using multiple fields +-- from different invoices. It is the caller's responsibility to ensure that +-- we bubble up an error in those cases. + +-- name: GetInvoice :many +SELECT i.* +FROM invoices i +LEFT JOIN amp_sub_invoices a +ON i.id = a.invoice_id +AND ( + a.set_id = sqlc.narg('set_id') OR sqlc.narg('set_id') IS NULL +) +WHERE ( + i.id = sqlc.narg('add_index') OR + sqlc.narg('add_index') IS NULL +) AND ( + i.hash = sqlc.narg('hash') OR + sqlc.narg('hash') IS NULL +) AND ( + i.payment_addr = sqlc.narg('payment_addr') OR + sqlc.narg('payment_addr') IS NULL +) +GROUP BY i.id +LIMIT 2; + -- name: GetInvoiceByHash :one SELECT i.* FROM invoices i WHERE i.hash = $1; --- name: GetInvoiceByAddr :one -SELECT i.* -FROM invoices i -WHERE i.payment_addr = $1; - -- name: GetInvoiceBySetID :many --- TODO(ziggie): This query can only return one invoice if the set_id is --- the primary key of amp_sub_invoices table. SELECT i.* FROM invoices i INNER JOIN amp_sub_invoices a ON i.id = a.invoice_id AND a.set_id = $1; --- name: FetchPendingInvoices :many --- FetchPendingInvoices returns all invoices in a pending state (open or --- accepted). The invoices_state_idx index on the state column makes this a --- fast index scan rather than a full table scan. id_cursor is an exclusive --- lower bound on the primary key used for cursor-based pagination; the caller --- must supply 0 when starting from the beginning. +-- name: FilterInvoices :many SELECT invoices.* FROM invoices -WHERE state IN (0, 3) -- 0 = ContractOpen, 3 = ContractAccepted - AND id > @id_cursor -ORDER BY id ASC -LIMIT @num_limit; - --- name: FilterInvoicesBySettleIndex :many --- FilterInvoicesBySettleIndex returns settled invoices whose settle_index is --- greater than or equal to the given value, ordered by id. The caller must --- always supply a concrete lower bound so the invoices_settle_index_idx index --- can be used. id_cursor is an exclusive lower bound on the primary key used --- for cursor-based pagination; the caller must supply 0 when starting from --- the beginning. -SELECT - invoices.* -FROM invoices -WHERE settle_index >= @settle_index_get - AND id > @id_cursor -ORDER BY id ASC -LIMIT @num_limit; - --- name: FilterInvoicesByAddIndex :many --- FilterInvoicesByAddIndex returns invoices whose add_index (primary key id) --- is greater than or equal to the given value, ordered by id. Because id is --- the primary key, this is always an efficient range scan on the clustered --- index. For cursor-based pagination the caller advances add_index_get to --- last_returned_id + 1 on each subsequent page. -SELECT - invoices.* -FROM invoices -WHERE id >= @add_index_get -ORDER BY id ASC -LIMIT @num_limit; - --- name: FilterInvoicesForward :many --- FilterInvoicesForward returns invoices in ascending id order. All parameters --- are non-nullable so the planner always sees plain range predicates and can --- use the primary-key index. For cursor-based pagination the caller advances --- add_index_get to last_returned_id + 1 on each subsequent page. The caller --- is responsible for supplying Go-side defaults when a filter is not needed: --- add_index_get → 1 (first valid invoice id) --- created_after → time.Unix(0, 0).UTC() (epoch – before any invoice) --- created_before → time.Date(9999, …) (far future – no upper cap) --- pending_only → false (include all states) -SELECT - invoices.* -FROM invoices -WHERE id >= @add_index_get - AND (NOT @pending_only OR state IN (0, 3)) -- 0 = ContractOpen, 3 = ContractAccepted - AND created_at >= @created_after - AND created_at < @created_before -ORDER BY id ASC -LIMIT @num_limit; - --- name: FilterInvoicesReverse :many --- FilterInvoicesReverse is the descending counterpart of FilterInvoicesForward. --- It returns invoices in descending id order. For cursor-based pagination the --- caller advances add_index_let to last_returned_id - 1 on each subsequent --- page; pass math.MaxInt64 to start from the most recent invoice. See --- FilterInvoicesForward for the expected Go-side defaults. -SELECT - invoices.* -FROM invoices -WHERE id <= @add_index_let - AND (NOT @pending_only OR state IN (0, 3)) -- 0 = ContractOpen, 3 = ContractAccepted - AND created_at >= @created_after - AND created_at < @created_before -ORDER BY id DESC -LIMIT @num_limit; +WHERE ( + id >= sqlc.narg('add_index_get') OR + sqlc.narg('add_index_get') IS NULL +) AND ( + id <= sqlc.narg('add_index_let') OR + sqlc.narg('add_index_let') IS NULL +) AND ( + settle_index >= sqlc.narg('settle_index_get') OR + sqlc.narg('settle_index_get') IS NULL +) AND ( + settle_index <= sqlc.narg('settle_index_let') OR + sqlc.narg('settle_index_let') IS NULL +) AND ( + state = sqlc.narg('state') OR + sqlc.narg('state') IS NULL +) AND ( + created_at >= sqlc.narg('created_after') OR + sqlc.narg('created_after') IS NULL +) AND ( + created_at < sqlc.narg('created_before') OR + sqlc.narg('created_before') IS NULL +) AND ( + CASE + WHEN sqlc.narg('pending_only') = TRUE THEN (state = 0 OR state = 3) + ELSE TRUE + END +) +ORDER BY +CASE + WHEN sqlc.narg('reverse') = FALSE OR sqlc.narg('reverse') IS NULL THEN id + ELSE NULL + END ASC, +CASE + WHEN sqlc.narg('reverse') = TRUE THEN id + ELSE NULL +END DESC +LIMIT @num_limit OFFSET @num_offset; -- name: UpdateInvoiceState :execresult UPDATE invoices diff --git a/sqldb/sqlc/queries/payments.sql b/sqldb/sqlc/queries/payments.sql deleted file mode 100644 index 16682c83b..000000000 --- a/sqldb/sqlc/queries/payments.sql +++ /dev/null @@ -1,488 +0,0 @@ -/* ───────────────────────────────────────────── - fetch queries - ───────────────────────────────────────────── -*/ - --- name: FilterPayments :many -SELECT - sqlc.embed(p), - i.intent_type AS "intent_type", - i.intent_payload AS "intent_payload" -FROM payments p -LEFT JOIN payment_intents i ON i.payment_id = p.id -WHERE p.id > COALESCE(sqlc.narg('index_offset_get'), -1) - AND p.id < COALESCE(sqlc.narg('index_offset_let'), 9223372036854775807) - -- NOTE: We use non-nullable time params with Go-side defaults instead of - -- COALESCE, because COALESCE with text fallback causes type mismatch on - -- Postgres (timestamp vs text), and OR-based optional filters can prevent - -- the planner from using the created_at index. - AND p.created_at >= @created_after - AND p.created_at <= @created_before - AND ( - i.intent_type = sqlc.narg('intent_type') OR - sqlc.narg('intent_type') IS NULL OR i.intent_type IS NULL - ) -ORDER BY p.id ASC -LIMIT @num_limit; - --- name: FilterPaymentsDesc :many -SELECT - sqlc.embed(p), - i.intent_type AS "intent_type", - i.intent_payload AS "intent_payload" -FROM payments p -LEFT JOIN payment_intents i ON i.payment_id = p.id -WHERE p.id > COALESCE(sqlc.narg('index_offset_get'), -1) - AND p.id < COALESCE(sqlc.narg('index_offset_let'), 9223372036854775807) - -- NOTE: We use non-nullable time params with Go-side defaults instead of - -- COALESCE, because COALESCE with text fallback causes type mismatch on - -- Postgres (timestamp vs text), and OR-based optional filters can prevent - -- the planner from using the created_at index. - AND p.created_at >= @created_after - AND p.created_at <= @created_before - AND ( - i.intent_type = sqlc.narg('intent_type') OR - sqlc.narg('intent_type') IS NULL OR i.intent_type IS NULL - ) -ORDER BY p.id DESC -LIMIT @num_limit; - --- name: FetchPayment :one -SELECT - sqlc.embed(p), - i.intent_type AS "intent_type", - i.intent_payload AS "intent_payload" -FROM payments p -LEFT JOIN payment_intents i ON i.payment_id = p.id -WHERE p.payment_identifier = $1; - --- name: FetchPaymentDuplicates :many --- Fetch all duplicate payment records from the payment_duplicates table for --- a given payment ID. -SELECT - id, - payment_id, - amount_msat, - created_at, - fail_reason, - settle_preimage, - settle_time -FROM payment_duplicates -WHERE payment_id = $1 -ORDER BY id ASC; - --- name: CountPayments :one -SELECT COUNT(*) FROM payments; - --- name: FetchHtlcAttemptsForPayments :many -SELECT - ha.id, - ha.attempt_index, - ha.payment_id, - ha.session_key, - ha.attempt_time, - ha.payment_hash, - ha.first_hop_amount_msat, - ha.route_total_time_lock, - ha.route_total_amount, - ha.route_source_key, - hr.resolution_type, - hr.resolution_time, - hr.failure_source_index, - hr.htlc_fail_reason, - hr.failure_msg, - hr.settle_preimage -FROM payment_htlc_attempts ha -LEFT JOIN payment_htlc_attempt_resolutions hr ON hr.attempt_index = ha.attempt_index -WHERE ha.payment_id IN (sqlc.slice('payment_ids')/*SLICE:payment_ids*/) -ORDER BY ha.payment_id ASC, ha.attempt_time ASC; - --- name: FetchHtlcAttemptResolutionsForPayments :many --- Batch query to fetch only HTLC resolution status for multiple payments. --- We don't need to order by payment_id and attempt_time because we will --- group the resolutions by payment_id in the background. -SELECT - ha.payment_id, - hr.resolution_type -FROM payment_htlc_attempts ha -LEFT JOIN payment_htlc_attempt_resolutions hr ON hr.attempt_index = ha.attempt_index -WHERE ha.payment_id IN (sqlc.slice('payment_ids')/*SLICE:payment_ids*/); - --- name: FetchPaymentsByIDs :many --- Batch fetch payment and intent data for a set of payment IDs. --- Used to avoid fetching redundant payment data when processing multiple --- attempts for the same payment. -SELECT - p.id, - p.amount_msat, - p.created_at, - p.payment_identifier, - p.fail_reason, - pi.intent_type, - pi.intent_payload -FROM payments p -LEFT JOIN payment_intents pi ON pi.payment_id = p.id -WHERE p.id IN (sqlc.slice('payment_ids')/*SLICE:payment_ids*/) -ORDER BY p.id ASC; - --- name: FetchNonTerminalPayments :many --- Fetch all non-terminal payments using pagination. A payment is --- non-terminal if it has an unresolved attempt, or if it has not been --- permanently failed and has no settled attempt yet. -SELECT - p.id, - p.amount_msat, - p.created_at, - p.payment_identifier, - p.fail_reason, - pi.intent_type, - pi.intent_payload -FROM payments p -LEFT JOIN payment_intents pi - ON pi.payment_id = p.id -WHERE p.id > $1 -AND ( - ( - p.fail_reason IS NULL - AND NOT EXISTS ( - SELECT 1 - FROM payment_htlc_attempts ha - JOIN payment_htlc_attempt_resolutions hr - ON hr.attempt_index = ha.attempt_index - WHERE ha.payment_id = p.id - AND hr.resolution_type = 1 - ) - ) - OR EXISTS ( - SELECT 1 - FROM payment_htlc_attempts ha - WHERE ha.payment_id = p.id - AND NOT EXISTS ( - SELECT 1 - FROM payment_htlc_attempt_resolutions hr - WHERE hr.attempt_index = ha.attempt_index - ) - ) -) -ORDER BY p.id ASC -LIMIT $2; - --- name: FetchHopsForAttempts :many -SELECT - h.id, - h.htlc_attempt_index, - h.hop_index, - h.pub_key, - h.scid, - h.outgoing_time_lock, - h.amt_to_forward, - h.meta_data, - m.payment_addr AS mpp_payment_addr, - m.total_msat AS mpp_total_msat, - a.root_share AS amp_root_share, - a.set_id AS amp_set_id, - a.child_index AS amp_child_index, - b.encrypted_data, - b.blinding_point, - b.blinded_path_total_amt -FROM payment_route_hops h -LEFT JOIN payment_route_hop_mpp m ON m.hop_id = h.id -LEFT JOIN payment_route_hop_amp a ON a.hop_id = h.id -LEFT JOIN payment_route_hop_blinded b ON b.hop_id = h.id -WHERE h.htlc_attempt_index IN (sqlc.slice('htlc_attempt_indices')/*SLICE:htlc_attempt_indices*/) -ORDER BY h.htlc_attempt_index ASC, h.hop_index ASC; - - --- name: FetchPaymentLevelFirstHopCustomRecords :many -SELECT - l.id, - l.payment_id, - l.key, - l.value -FROM payment_first_hop_custom_records l -WHERE l.payment_id IN (sqlc.slice('payment_ids')/*SLICE:payment_ids*/) -ORDER BY l.payment_id ASC, l.key ASC; - --- name: FetchRouteLevelFirstHopCustomRecords :many -SELECT - l.id, - l.htlc_attempt_index, - l.key, - l.value -FROM payment_attempt_first_hop_custom_records l -WHERE l.htlc_attempt_index IN (sqlc.slice('htlc_attempt_indices')/*SLICE:htlc_attempt_indices*/) -ORDER BY l.htlc_attempt_index ASC, l.key ASC; - --- name: FetchHopLevelCustomRecords :many -SELECT - l.id, - l.hop_id, - l.key, - l.value -FROM payment_hop_custom_records l -WHERE l.hop_id IN (sqlc.slice('hop_ids')/*SLICE:hop_ids*/) -ORDER BY l.hop_id ASC, l.key ASC; - - --- name: DeletePayment :exec -DELETE FROM payments WHERE id = $1; - --- name: DeleteFailedAttempts :exec --- Delete all failed HTLC attempts for the given payment. Resolution type 2 --- indicates a failed attempt. Uses EXISTS to scope the resolution lookup to --- only this payment's attempts, avoiding an O(N) scan of all failed --- resolutions across all payments. -DELETE FROM payment_htlc_attempts -WHERE payment_id = $1 -AND EXISTS ( - SELECT 1 FROM payment_htlc_attempt_resolutions hr - WHERE hr.attempt_index = payment_htlc_attempts.attempt_index - AND hr.resolution_type = 2 -); - --- name: InsertPaymentIntent :one --- Insert a payment intent for a given payment and return its ID. -INSERT INTO payment_intents ( - payment_id, - intent_type, - intent_payload) -VALUES ( - @payment_id, - @intent_type, - @intent_payload -) -RETURNING id; - --- name: InsertPayment :one --- Insert a new payment and return its ID. --- When creating a payment we don't have a fail reason because we start the --- payment process. -INSERT INTO payments ( - amount_msat, - created_at, - payment_identifier, - fail_reason) -VALUES ( - @amount_msat, - @created_at, - @payment_identifier, - NULL -) -RETURNING id; - --- name: InsertPaymentFirstHopCustomRecord :exec -INSERT INTO payment_first_hop_custom_records ( - payment_id, - key, - value -) -VALUES ( - @payment_id, - @key, - @value -); - --- name: InsertHtlcAttempt :one -INSERT INTO payment_htlc_attempts ( - payment_id, - attempt_index, - session_key, - attempt_time, - payment_hash, - first_hop_amount_msat, - route_total_time_lock, - route_total_amount, - route_source_key) -VALUES ( - @payment_id, - @attempt_index, - @session_key, - @attempt_time, - @payment_hash, - @first_hop_amount_msat, - @route_total_time_lock, - @route_total_amount, - @route_source_key) -RETURNING id; - --- name: InsertPaymentAttemptFirstHopCustomRecord :exec -INSERT INTO payment_attempt_first_hop_custom_records ( - htlc_attempt_index, - key, - value -) -VALUES ( - @htlc_attempt_index, - @key, - @value -); - --- name: InsertRouteHop :one -INSERT INTO payment_route_hops ( - htlc_attempt_index, - hop_index, - pub_key, - scid, - outgoing_time_lock, - amt_to_forward, - meta_data -) -VALUES ( - @htlc_attempt_index, - @hop_index, - @pub_key, - @scid, - @outgoing_time_lock, - @amt_to_forward, - @meta_data -) -RETURNING id; - --- name: InsertRouteHopMpp :exec -INSERT INTO payment_route_hop_mpp ( - hop_id, - payment_addr, - total_msat -) -VALUES ( - @hop_id, - @payment_addr, - @total_msat -); - --- name: InsertRouteHopAmp :exec -INSERT INTO payment_route_hop_amp ( - hop_id, - root_share, - set_id, - child_index -) -VALUES ( - @hop_id, - @root_share, - @set_id, - @child_index -); - --- name: InsertRouteHopBlinded :exec -INSERT INTO payment_route_hop_blinded ( - hop_id, - encrypted_data, - blinding_point, - blinded_path_total_amt -) -VALUES ( - @hop_id, - @encrypted_data, - @blinding_point, - @blinded_path_total_amt -); - --- name: InsertPaymentHopCustomRecord :exec -INSERT INTO payment_hop_custom_records ( - hop_id, - key, - value -) -VALUES ( - @hop_id, - @key, - @value -); - --- name: SettleAttempt :exec -INSERT INTO payment_htlc_attempt_resolutions ( - attempt_index, - resolution_time, - resolution_type, - settle_preimage -) -VALUES ( - @attempt_index, - @resolution_time, - @resolution_type, - @settle_preimage -); - --- name: FailAttempt :exec -INSERT INTO payment_htlc_attempt_resolutions ( - attempt_index, - resolution_time, - resolution_type, - failure_source_index, - htlc_fail_reason, - failure_msg -) -VALUES ( - @attempt_index, - @resolution_time, - @resolution_type, - @failure_source_index, - @htlc_fail_reason, - @failure_msg -); - --- name: FailPayment :execresult -UPDATE payments SET fail_reason = $1 WHERE payment_identifier = $2; - -/* ───────────────────────────────────────────── - Migration-specific queries - - These queries are used ONLY for the one-time migration from KV to SQL. - ───────────────────────────────────────────── -*/ - --- name: InsertPaymentMig :one --- Migration-specific payment insert that allows setting fail_reason. --- Normal InsertPayment forces fail_reason to NULL since new payments --- aren't failed yet. During migration, we're inserting historical data --- that may already be failed. -INSERT INTO payments ( - amount_msat, - created_at, - payment_identifier, - fail_reason) -VALUES ( - @amount_msat, - @created_at, - @payment_identifier, - @fail_reason -) -RETURNING id; - --- name: FetchPaymentsByIDsMig :many --- Migration-specific batch fetch that returns payment data along with HTLC --- attempt counts for structural validation during KV to SQL migration. -SELECT - p.id, - p.amount_msat, - p.created_at, - p.payment_identifier, - p.fail_reason, - COUNT(ha.id) AS htlc_attempt_count -FROM payments p -LEFT JOIN payment_htlc_attempts ha ON ha.payment_id = p.id -WHERE p.id IN (sqlc.slice('payment_ids')/*SLICE:payment_ids*/) -GROUP BY p.id, p.amount_msat, p.created_at, p.payment_identifier, p.fail_reason -ORDER BY p.id ASC; - --- name: InsertPaymentDuplicateMig :one --- Insert a duplicate payment record into the payment_duplicates table and --- return its ID. -INSERT INTO payment_duplicates ( - payment_id, - amount_msat, - created_at, - fail_reason, - settle_preimage, - settle_time -) -VALUES ( - @payment_id, - @amount_msat, - @created_at, - @fail_reason, - @settle_preimage, - @settle_time -) -RETURNING id; diff --git a/sqldb/sqlerrors.go b/sqldb/sqlerrors.go index 5f8d998b5..59729910e 100644 --- a/sqldb/sqlerrors.go +++ b/sqldb/sqlerrors.go @@ -7,8 +7,8 @@ import ( "fmt" "strings" + "github.com/jackc/pgconn" "github.com/jackc/pgerrcode" - "github.com/jackc/pgx/v5/pgconn" "modernc.org/sqlite" sqlite3 "modernc.org/sqlite/lib" ) diff --git a/sqldb/sqlerrors_no_sqlite.go b/sqldb/sqlerrors_no_sqlite.go index 9e85c98fb..717d94152 100644 --- a/sqldb/sqlerrors_no_sqlite.go +++ b/sqldb/sqlerrors_no_sqlite.go @@ -6,8 +6,8 @@ import ( "errors" "fmt" + "github.com/jackc/pgconn" "github.com/jackc/pgerrcode" - "github.com/jackc/pgx/v5/pgconn" ) var ( diff --git a/sqldb/sqlite.go b/sqldb/sqlite.go index 1ed26810d..2b2f7be13 100644 --- a/sqldb/sqlite.go +++ b/sqldb/sqlite.go @@ -70,7 +70,7 @@ func NewSqliteStore(cfg *SqliteConfig, dbPath string) (*SqliteStore, error) { }, { name: "busy_timeout", - value: fmt.Sprintf("%d", cfg.busyTimeoutMs()), + value: "5000", }, { // With the WAL mode, this ensures that we also do an @@ -100,12 +100,6 @@ func NewSqliteStore(cfg *SqliteConfig, dbPath string) (*SqliteStore, error) { ) } - // Then we add any user specified pragma options. Note that these can - // be of the form: "key=value", "key(N)" or "key". - for _, option := range cfg.PragmaOptions { - sqliteOptions.Add(sqliteOptionPrefix, option) - } - // Construct the DSN which is just the database file name, appended // with the series of pragma options as a query URL string. For more // details on the formatting here, see the modernc.org/sqlite docs: @@ -136,8 +130,8 @@ func NewSqliteStore(cfg *SqliteConfig, dbPath string) (*SqliteStore, error) { err) } - db.SetMaxOpenConns(cfg.MaxConns()) - db.SetMaxIdleConns(cfg.MaxConns()) + db.SetMaxOpenConns(defaultMaxConns) + db.SetMaxIdleConns(defaultMaxConns) db.SetConnMaxLifetime(connIdleLifetime) queries := sqlc.New(db) diff --git a/sqldb/sqlite_bench_test.go b/sqldb/sqlite_bench_test.go deleted file mode 100644 index 9a606d5b8..000000000 --- a/sqldb/sqlite_bench_test.go +++ /dev/null @@ -1,196 +0,0 @@ -//go:build !js && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)) - -package sqldb - -import ( - "context" - "database/sql" - "fmt" - "path/filepath" - "sync" - "testing" - "time" - - "github.com/lightningnetwork/lnd/sqldb/sqlc" - "github.com/stretchr/testify/require" -) - -// getInvoiceByHashForBench fetches an invoice by hash and reports any -// error except sql.ErrNoRows (expected when the invoice doesn't exist). -func getInvoiceByHashForBench(b *testing.B, store *SqliteStore, - ctx context.Context, hash []byte) { - - _, err := store.GetInvoiceByHash(ctx, hash) - if err != nil { - require.ErrorIs(b, err, sql.ErrNoRows) - } -} - -// BenchmarkSqliteMaxConns benchmarks sequential reads against a SQLite -// database with varying MaxConnections settings. -// -// Run with: -// -// go test -bench=BenchmarkSqliteMaxConns -benchmem -run=^$ ./sqldb/ -func BenchmarkSqliteMaxConns(b *testing.B) { - const numInvoices = 500 - - // connCounts contains the MaxConnections values we want to compare. - // 0 means "use the library default" (currently 2 for SQLite). - connCounts := []int{1, 2, 4, 8, 16, 0} - - // Build a fresh SQLite database that will be shared across all - // sub-benchmarks. We insert a fixed set of invoices once and then - // execute read-only queries from multiple goroutines. - dbFileName := filepath.Join(b.TempDir(), "bench.db") - - // Open the store once with migrations so the schema is in place. - setupStore, err := NewSqliteStore(&SqliteConfig{ - SkipMigrations: false, - }, dbFileName) - require.NoError(b, err) - - require.NoError(b, setupStore.ApplyAllMigrations( - context.Background(), GetMigrations(), - )) - - ctx := context.Background() - - // Insert test invoices. We use a predictable hash per invoice so we - // can look them up deterministically during the benchmark. - hashes := make([][]byte, numInvoices) - for i := range numInvoices { - hash := make([]byte, 32) - hash[0] = byte(i) - hash[1] = byte(i >> 8) - hashes[i] = hash - - _, err := setupStore.InsertInvoice( - ctx, sqlc.InsertInvoiceParams{ - Hash: hash, - PaymentAddr: hash, - PaymentRequestHash: hash, - Expiry: 3600, - CreatedAt: time.Now(), - }, - ) - require.NoError(b, err) - } - - require.NoError(b, setupStore.DB.Close()) - - for _, maxConns := range connCounts { - name := fmt.Sprintf("MaxConns=%d", maxConns) - if maxConns == 0 { - name = fmt.Sprintf("MaxConns=default(%d)", - DefaultSqliteMaxConns) - } - - b.Run(name, func(b *testing.B) { - store, err := NewSqliteStore( - &SqliteConfig{ - SkipMigrations: true, - MaxConnections: maxConns, - }, dbFileName, - ) - require.NoError(b, err) - - b.Cleanup(func() { - require.NoError(b, store.DB.Close()) - }) - - var i int - for b.Loop() { - hash := hashes[i%numInvoices] - i++ - getInvoiceByHashForBench(b, store, ctx, hash) - } - }) - } -} - -// BenchmarkSqliteMaxConnsConcurrentReads measures aggregate read throughput -// for a fixed level of goroutine concurrency to complement the sequential -// benchmark above. Each iteration launches a fixed number of goroutines that -// all issue reads simultaneously, directly stressing the connection pool. -func BenchmarkSqliteMaxConnsConcurrentReads(b *testing.B) { - const ( - numInvoices = 500 - goroutines = 16 - ) - - connCounts := []int{1, 2, 4, 8, 16, 0} - - dbFileName := filepath.Join(b.TempDir(), "bench_conc.db") - - setupStore, err := NewSqliteStore(&SqliteConfig{ - SkipMigrations: false, - }, dbFileName) - require.NoError(b, err) - - require.NoError(b, setupStore.ApplyAllMigrations( - context.Background(), GetMigrations(), - )) - - ctx := context.Background() - - hashes := make([][]byte, numInvoices) - for i := range numInvoices { - hash := make([]byte, 32) - hash[0] = byte(i) - hash[1] = byte(i >> 8) - hashes[i] = hash - - _, err := setupStore.InsertInvoice( - ctx, sqlc.InsertInvoiceParams{ - Hash: hash, - PaymentAddr: hash, - PaymentRequestHash: hash, - Expiry: 3600, - CreatedAt: time.Now(), - }, - ) - require.NoError(b, err) - } - - require.NoError(b, setupStore.DB.Close()) - - for _, maxConns := range connCounts { - name := fmt.Sprintf("MaxConns=%d", maxConns) - if maxConns == 0 { - name = fmt.Sprintf("MaxConns=default(%d)", - DefaultSqliteMaxConns) - } - - b.Run(name, func(b *testing.B) { - store, err := NewSqliteStore( - &SqliteConfig{ - SkipMigrations: true, - MaxConnections: maxConns, - }, dbFileName, - ) - require.NoError(b, err) - - b.Cleanup(func() { - require.NoError(b, store.DB.Close()) - }) - - for b.Loop() { - var wg sync.WaitGroup - wg.Add(goroutines) - - for g := range goroutines { - hash := hashes[g%numInvoices] - go func(h []byte) { - defer wg.Done() - getInvoiceByHashForBench( - b, store, ctx, h, - ) - }(hash) - } - - wg.Wait() - } - }) - } -} diff --git a/sqldb/v2/config.go b/sqldb/v2/config.go deleted file mode 100644 index 9e6c3c015..000000000 --- a/sqldb/v2/config.go +++ /dev/null @@ -1,131 +0,0 @@ -package sqldb - -import ( - "fmt" - "net/url" - "time" -) - -const ( - // DefaultSqliteMaxConns is the default number of maximum open - // connections for SQLite. SQLite only supports a single writer, so a - // low default reduces contention on the busy_timeout and limits - // resource usage. - DefaultSqliteMaxConns = 2 - - // DefaultPostgresMaxConns is the number of permitted active and idle - // connections. We want to limit this so it isn't unlimited. We use the - // same value for the number of idle connections as, this can speed up - // queries given a new connection doesn't need to be established each - // time. - DefaultPostgresMaxConns = 25 - - // defaultMaxIdleConns is the number of permitted idle connections. - defaultMaxIdleConns = 6 - - // defaultConnMaxIdleTime is the amount of time a connection can be - // idle before it is closed. - defaultConnMaxIdleTime = 5 * time.Minute - - // defaultConnMaxLifetime is the maximum amount of time a connection can - // be reused for before it is closed. - defaultConnMaxLifetime = 10 * time.Minute -) - -// SqliteConfig holds all the config arguments needed to interact with our -// sqlite DB. -// -//nolint:ll -type SqliteConfig struct { - Timeout time.Duration `long:"timeout" description:"The time after which a database query should be timed out."` - BusyTimeout time.Duration `long:"busytimeout" description:"The maximum amount of time to wait for a database connection to become available for a query."` - MaxConnections int `long:"maxconnections" description:"The maximum number of open connections to the database."` - MaxIdleConnections int `long:"maxidleconnections" description:"Max number of idle connections to keep in the connection pool."` - ConnMaxLifetime time.Duration `long:"connmaxlifetime" description:"Max amount of time a connection can be reused for before it is closed. Valid time units are {s, m, h}."` - PragmaOptions []string `long:"pragmaoptions" description:"A list of pragma options to set on a database connection. For example, 'auto_vacuum=incremental'. Note that the flag must be specified multiple times if multiple options are to be set."` - SkipMigrations bool `long:"skipmigrations" description:"Skip applying migrations on startup."` - - // SkipMigrationDbBackup if true, then a backup of the database will not - // be created before applying migrations. - SkipMigrationDbBackup bool `long:"skipmigrationdbbackup" description:"Skip creating a backup of the database before applying migrations."` - - QueryConfig `group:"query" namespace:"query"` -} - -const ( - // DefaultSqliteBusyTimeout is the default busy_timeout value used - // when no BusyTimeout is configured. - DefaultSqliteBusyTimeout = 5 * time.Second -) - -// busyTimeoutMs returns the busy_timeout value in milliseconds. If -// BusyTimeout is not set, it returns the default value. -func (s *SqliteConfig) busyTimeoutMs() int64 { - if s.BusyTimeout > 0 { - return s.BusyTimeout.Milliseconds() - } - - return DefaultSqliteBusyTimeout.Milliseconds() -} - -// MaxConns returns the effective maximum number of SQLite connections. -func (s *SqliteConfig) MaxConns() int { - if s.MaxConnections > 0 { - return s.MaxConnections - } - - return DefaultSqliteMaxConns -} - -// MaxIdleConns returns the effective maximum number of idle SQLite -// connections. -func (s *SqliteConfig) MaxIdleConns() int { - if s.MaxIdleConnections > 0 { - return s.MaxIdleConnections - } - - return s.MaxConns() -} - -// Validate checks that the SqliteConfig values are valid. -func (p *SqliteConfig) Validate() error { - if err := p.QueryConfig.Validate(true); err != nil { - return fmt.Errorf("invalid query config: %w", err) - } - - return nil -} - -// PostgresConfig holds the postgres database configuration. -// -//nolint:ll -type PostgresConfig struct { - Dsn string `long:"dsn" description:"Database connection string."` - Timeout time.Duration `long:"timeout" description:"Database connection timeout. Set to zero to disable."` - MaxOpenConnections int `long:"maxconnections" description:"Max open connections to keep alive to the database server."` - MaxIdleConnections int `long:"maxidleconnections" description:"Max number of idle connections to keep in the connection pool."` - ConnMaxLifetime time.Duration `long:"connmaxlifetime" description:"Max amount of time a connection can be reused for before it is closed. Valid time units are {s, m, h}."` - ConnMaxIdleTime time.Duration `long:"connmaxidletime" description:"Max amount of time a connection can be idle for before it is closed. Valid time units are {s, m, h}."` - RequireSSL bool `long:"requiressl" description:"Whether to require using SSL (mode: require) when connecting to the server."` - SkipMigrations bool `long:"skipmigrations" description:"Skip applying migrations on startup."` - QueryConfig `group:"query" namespace:"query"` -} - -// Validate checks that the PostgresConfig values are valid. -func (p *PostgresConfig) Validate() error { - if p.Dsn == "" { - return fmt.Errorf("DSN is required") - } - - // Parse the DSN as a URL. - _, err := url.Parse(p.Dsn) - if err != nil { - return fmt.Errorf("invalid DSN: %w", err) - } - - if err := p.QueryConfig.Validate(false); err != nil { - return fmt.Errorf("invalid query config: %w", err) - } - - return nil -} diff --git a/sqldb/v2/config_test.go b/sqldb/v2/config_test.go deleted file mode 100644 index c6e9d2117..000000000 --- a/sqldb/v2/config_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package sqldb - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -// TestSqliteConfigMaxConns verifies that SQLite keeps the low default -// connection limit unless the caller overrides it explicitly. -func TestSqliteConfigMaxConns(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - maxConns int - expectedConn int - }{ - { - name: "default limit", - expectedConn: DefaultSqliteMaxConns, - }, - { - name: "explicit limit", - maxConns: 7, - expectedConn: 7, - }, - } - - for _, testCase := range testCases { - testCase := testCase - - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - cfg := &SqliteConfig{ - MaxConnections: testCase.maxConns, - } - - require.Equal(t, testCase.expectedConn, cfg.MaxConns()) - }) - } -} - -// TestSqliteConfigMaxIdleConns verifies that SQLite defaults its idle -// connections to the open connection limit unless the caller overrides it. -func TestSqliteConfigMaxIdleConns(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - maxConns int - maxIdleConns int - expectedIdleConn int - }{ - { - name: "default idle limit", - expectedIdleConn: DefaultSqliteMaxConns, - }, - { - name: "inherits explicit open limit", - maxConns: 4, - expectedIdleConn: 4, - }, - { - name: "explicit idle limit", - maxConns: 4, - maxIdleConns: 3, - expectedIdleConn: 3, - }, - } - - for _, testCase := range testCases { - testCase := testCase - - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - cfg := &SqliteConfig{ - MaxConnections: testCase.maxConns, - MaxIdleConnections: testCase.maxIdleConns, - } - - require.Equal(t, testCase.expectedIdleConn, - cfg.MaxIdleConns()) - }) - } -} diff --git a/sqldb/v2/go.mod b/sqldb/v2/go.mod deleted file mode 100644 index 6394bdf1d..000000000 --- a/sqldb/v2/go.mod +++ /dev/null @@ -1,73 +0,0 @@ -module github.com/lightningnetwork/lnd/sqldb/v2 - -require ( - github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b - github.com/davecgh/go-spew v1.1.1 - github.com/golang-migrate/migrate/v4 v4.19.0 - github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 - github.com/jackc/pgx/v5 v5.9.2 - github.com/lightningnetwork/lnd/fn/v2 v2.0.8 - github.com/ory/dockertest/v3 v3.10.0 - github.com/pmezard/go-difflib v1.0.0 - github.com/stretchr/testify v1.11.1 - golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b - modernc.org/sqlite v1.38.2 -) - -require ( - dario.cat/mergo v1.0.2 // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect - github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/continuity v0.3.0 // indirect - github.com/containerd/errdefs v1.0.0 // indirect - github.com/containerd/errdefs/pkg v0.3.0 // indirect - github.com/docker/cli v28.1.1+incompatible // indirect - github.com/docker/docker v28.3.3+incompatible // indirect - github.com/docker/go-connections v0.5.0 // indirect - github.com/docker/go-units v0.5.0 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/go-viper/mapstructure/v2 v2.3.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/go-cmp v0.7.0 // indirect - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/kr/pretty v0.3.1 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/sys/user v0.3.0 // indirect - github.com/moby/term v0.5.0 // indirect - github.com/ncruces/go-strftime v0.1.9 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/opencontainers/runc v1.2.8 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/sirupsen/logrus v1.9.3 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect - go.opentelemetry.io/otel/trace v1.36.0 // indirect - go.uber.org/atomic v1.10.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/text v0.29.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - modernc.org/libc v1.66.3 // indirect; indirectv - modernc.org/mathutil v1.7.1 // indirect - modernc.org/memory v1.11.0 // indirect -) - -// We are using a fork of the migration library with custom functionality that -// did not yet make it into the upstream repository. -replace github.com/golang-migrate/migrate/v4 => github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2.0.20251211093704-71c1eef09789 - -go 1.25.11 diff --git a/sqldb/v2/go.sum b/sqldb/v2/go.sum deleted file mode 100644 index 8db866873..000000000 --- a/sqldb/v2/go.sum +++ /dev/null @@ -1,218 +0,0 @@ -dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= -dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= -github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c h1:4HxD1lBUGUddhzgaNgrCPsFWd7cGYNpeFUgd9ZIgyM0= -github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ= -github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b h1:MQ+Q6sDy37V1wP1Yu79A5KqJutolqUGwA99UZWQDWZM= -github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= -github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= -github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= -github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= -github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= -github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dhui/dktest v0.4.5 h1:uUfYBIVREmj/Rw6MvgmqNAYzTiKOHJak+enB5Di73MM= -github.com/dhui/dktest v0.4.5/go.mod h1:tmcyeHDKagvlDrz7gDKq4UAJOLIfVZYkfD5OnHDwcCo= -github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= -github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v28.1.1+incompatible h1:eyUemzeI45DY7eDPuwUcmDyDj1pM98oD5MdSpiItp8k= -github.com/docker/cli v28.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= -github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= -github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= -github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= -github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= -github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2.0.20251211093704-71c1eef09789 h1:7kX7vUgHUazAHcCJ6uzBDa4/2MEGEbMEfa01GtfqmTQ= -github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2.0.20251211093704-71c1eef09789/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY= -github.com/lightningnetwork/lnd/fn/v2 v2.0.8 h1:r2SLz7gZYQPVc3IZhU82M66guz3Zk2oY+Rlj9QN5S3g= -github.com/lightningnetwork/lnd/fn/v2 v2.0.8/go.mod h1:TOzwrhjB/Azw1V7aa8t21ufcQmdsQOQMDtxVOQWNl8s= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= -github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= -github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= -github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/opencontainers/runc v1.2.8 h1:RnEICeDReapbZ5lZEgHvj7E9Q3Eex9toYmaGBsbvU5Q= -github.com/opencontainers/runc v1.2.8/go.mod h1:cC0YkmZcuvr+rtBZ6T7NBoVbMGNAdLa/21vIElJDOzI= -github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= -github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= -golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= -gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= -modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM= -modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= -modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= -modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= -modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM= -modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= -modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= -modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= -modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= -modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= -modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= -modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= -modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= -modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= -modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= -modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= -modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= -modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= -modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= -modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= -modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= -modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= -modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= -modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/sqldb/v2/interfaces.go b/sqldb/v2/interfaces.go deleted file mode 100644 index 0bf6da881..000000000 --- a/sqldb/v2/interfaces.go +++ /dev/null @@ -1,466 +0,0 @@ -package sqldb - -import ( - "context" - "database/sql" - "fmt" - "math" - "math/rand" - "time" -) - -var ( - // DefaultStoreTimeout is the default timeout used for any interaction - // with the storage/database. - DefaultStoreTimeout = time.Second * 10 -) - -const ( - // DefaultNumTxRetries is the default number of times we'll retry a - // transaction if it fails with an error that permits transaction - // repetition. - DefaultNumTxRetries = 20 - - // DefaultInitialRetryDelay is the default initial delay between - // retries. This will be used to generate a random delay between -50% - // and +50% of this value, so 20 to 60 milliseconds. The retry will be - // doubled after each attempt until we reach DefaultMaxRetryDelay. We - // start with a random value to avoid multiple goroutines that are - // created at the same time to effectively retry at the same time. - DefaultInitialRetryDelay = time.Millisecond * 40 - - // DefaultMaxRetryDelay is the default maximum delay between retries. - DefaultMaxRetryDelay = time.Second * 3 -) - -// BackendType is an enum that represents the type of database backend we're -// using. -type BackendType uint8 - -const ( - // BackendTypeUnknown indicates we're using an unknown backend. - BackendTypeUnknown BackendType = iota - - // BackendTypeSqlite indicates we're using a SQLite backend. - BackendTypeSqlite - - // BackendTypePostgres indicates we're using a Postgres backend. - BackendTypePostgres -) - -// TxOptions represents a set of options one can use to control what type of -// database transaction is created. Transaction can be either read or write. -type TxOptions interface { - // ReadOnly returns true if the transaction should be read only. - ReadOnly() bool -} - -// txOptions is a concrete implementation of the TxOptions interface. -type txOptions struct { - // readOnly indicates if the transaction should be read-only. - readOnly bool -} - -// ReadOnly returns true if the transaction should be read only. -// -// NOTE: This is part of the TxOptions interface. -func (t *txOptions) ReadOnly() bool { - return t.readOnly -} - -// WriteTxOpt returns a TxOptions that indicates that the transaction -// should be a write transaction. -func WriteTxOpt() TxOptions { - return &txOptions{ - readOnly: false, - } -} - -// ReadTxOpt returns a TxOptions that indicates that the transaction -// should be a read-only transaction. -func ReadTxOpt() TxOptions { - return &txOptions{ - readOnly: true, - } -} - -// BatchedTx is a generic interface that represents the ability to execute -// several operations to a given storage interface in a single atomic -// transaction. Typically, Q here will be some subset of the main sqlc.Querier -// interface allowing it to only depend on the routines it needs to implement -// any additional business logic. -type BatchedTx[Q any] interface { - // ExecTx will execute the passed txBody, operating upon generic - // parameter Q (usually a storage interface) in a single transaction. - // - // The set of TxOptions are passed in order to allow the caller to - // specify if a transaction should be read-only and optionally what - // type of concurrency control should be used. - ExecTx(ctx context.Context, txOptions TxOptions, - txBody func(Q) error, reset func()) error - - // Backend returns the type of the database backend used. - Backend() BackendType -} - -// Tx represents a database transaction that can be committed or rolled back. -type Tx interface { - // Commit commits the database transaction, an error should be returned - // if the commit isn't possible. - Commit() error - - // Rollback rolls back an incomplete database transaction. - // Transactions that were able to be committed can still call this as a - // noop. - Rollback() error -} - -// QueryCreator is a generic function that's used to create a Querier, which is -// a type of interface that implements storage related methods from a database -// transaction. This will be used to instantiate an object callers can use to -// apply multiple modifications to an object interface in a single atomic -// transaction. -type QueryCreator[Q any] func(*sql.Tx) Q - -// BatchedQuerier is a generic interface that allows callers to create a new -// database transaction based on an abstract type that implements the TxOptions -// interface. -type BatchedQuerier interface { - // BeginTx creates a new database transaction given the set of - // transaction options. - BeginTx(ctx context.Context, options TxOptions) (*sql.Tx, error) - - // Backend returns the type of the database backend used. - Backend() BackendType -} - -// txExecutorOptions is a struct that holds the options for the transaction -// executor. This can be used to do things like retry a transaction due to an -// error a certain amount of times. -type txExecutorOptions struct { - numRetries int - initialRetryDelay time.Duration - maxRetryDelay time.Duration -} - -// defaultTxExecutorOptions returns the default options for the transaction -// executor. -func defaultTxExecutorOptions() *txExecutorOptions { - return &txExecutorOptions{ - numRetries: DefaultNumTxRetries, - initialRetryDelay: DefaultInitialRetryDelay, - maxRetryDelay: DefaultMaxRetryDelay, - } -} - -// TxExecutorOption is a functional option that allows us to pass in optional -// argument when creating the executor. -type TxExecutorOption func(*txExecutorOptions) - -// WithTxRetries is a functional option that allows us to specify the number of -// times a transaction should be retried if it fails with a repeatable error. -func WithTxRetries(numRetries int) TxExecutorOption { - return func(o *txExecutorOptions) { - o.numRetries = numRetries - } -} - -// WithTxRetryDelay is a functional option that allows us to specify the delay -// to wait before a transaction is retried. -func WithTxRetryDelay(delay time.Duration) TxExecutorOption { - return func(o *txExecutorOptions) { - o.initialRetryDelay = delay - } -} - -// TransactionExecutor is a generic struct that abstracts away from the type of -// query a type needs to run under a database transaction, and also the set of -// options for that transaction. The QueryCreator is used to create a query -// given a database transaction created by the BatchedQuerier. -type TransactionExecutor[Query any] struct { - BatchedQuerier - - createQuery QueryCreator[Query] - - opts *txExecutorOptions -} - -// A compile-time assertion to ensure TransactionExecutor satisfies the -// batched transaction interface. -var _ BatchedTx[any] = (*TransactionExecutor[any])(nil) - -// NewTransactionExecutor creates a new instance of a TransactionExecutor given -// a Querier query object and a concrete type for the type of transactions the -// Querier understands. -func NewTransactionExecutor[Querier any](db BatchedQuerier, - createQuery QueryCreator[Querier], - opts ...TxExecutorOption) *TransactionExecutor[Querier] { - - txOpts := defaultTxExecutorOptions() - for _, optFunc := range opts { - optFunc(txOpts) - } - - return &TransactionExecutor[Querier]{ - BatchedQuerier: db, - createQuery: createQuery, - opts: txOpts, - } -} - -// Backend returns the type of database backend used by the executor. -func (t *TransactionExecutor[Q]) Backend() BackendType { - return t.BatchedQuerier.Backend() -} - -// randRetryDelay returns a random retry delay between -50% and +50% of the -// configured delay that is doubled for each attempt and capped at a max value. -func randRetryDelay(initialRetryDelay, maxRetryDelay time.Duration, - attempt int) time.Duration { - - halfDelay := initialRetryDelay / 2 - randDelay := rand.Int63n(int64(initialRetryDelay)) //nolint:gosec - - // 50% plus 0%-100% gives us the range of 50%-150%. - initialDelay := halfDelay + time.Duration(randDelay) - - // If this is the first attempt, we just return the initial delay. - if attempt == 0 { - return initialDelay - } - - // For each subsequent delay, we double the initial delay. This still - // gives us a somewhat random delay, but it still increases with each - // attempt. If we double something n times, that's the same as - // multiplying the value with 2^n. We limit the power to 32 to avoid - // overflows. - factor := time.Duration(math.Pow(2, min(float64(attempt), 32))) - actualDelay := initialDelay * factor - - // Cap the delay at the maximum configured value. - if actualDelay > maxRetryDelay { - return maxRetryDelay - } - - return actualDelay -} - -// MakeTx is a function that creates a new transaction. It returns a Tx and an -// error if the transaction cannot be created. This is used to abstract the -// creation of a transaction from the actual transaction logic in order to be -// able to reuse the transaction retry logic in other packages. -type MakeTx func() (Tx, error) - -// TxBody represents the function type for transactions. It returns an -// error to indicate success or failure. -type TxBody func(tx Tx) error - -// RollbackTx is a function that is called when a transaction needs to be rolled -// back due to a serialization error. By using this intermediate function, we -// can avoid having to return rollback errors that are not actionable by the -// caller. -type RollbackTx func(tx Tx) error - -// OnBackoff is a function that is called when a transaction is retried due to a -// serialization error. The function is called with the retry attempt number and -// the delay before the next retry. -type OnBackoff func(retry int, delay time.Duration) - -// executeTxAttempt runs a single transaction attempt and reports whether the -// caller should retry it. -func executeTxAttempt(tx Tx, txBody TxBody, rollbackTx RollbackTx, - waitBeforeRetry func(int) bool, attempt int) (bool, error) { - - // Rollback is safe to call even if the tx is already closed, so if the tx - // commits successfully, this is a no-op. - defer func() { - _ = tx.Rollback() - }() - - if bodyErr := txBody(tx); bodyErr != nil { - log.Tracef("Error in txBody: %v", bodyErr) - - // Roll back the transaction, then attempt a random backoff and try - // again if the error was a serialization error. - if err := rollbackTx(tx); err != nil { - return false, MapSQLError(err) - } - - dbErr := MapSQLError(bodyErr) - if IsSerializationOrDeadlockError(dbErr) { - return waitBeforeRetry(attempt), dbErr - } - - return false, dbErr - } - - // Commit transaction. - if commitErr := tx.Commit(); commitErr != nil { - log.Tracef("Failed to commit tx: %v", commitErr) - - // Roll back the transaction, then attempt a random backoff and try - // again if the error was a serialization error. - if err := rollbackTx(tx); err != nil { - return false, MapSQLError(err) - } - - dbErr := MapSQLError(commitErr) - if IsSerializationOrDeadlockError(dbErr) { - return waitBeforeRetry(attempt), dbErr - } - - return false, dbErr - } - - return false, nil -} - -// ExecuteSQLTransactionWithRetry is a helper function that executes a -// transaction with retry logic. It will retry the transaction if it fails with -// a serialization error. The function will return an error if the transaction -// fails with a non-retryable error, the context is cancelled or the number of -// retries is exceeded. -func ExecuteSQLTransactionWithRetry(ctx context.Context, makeTx MakeTx, - rollbackTx RollbackTx, txBody TxBody, onBackoff OnBackoff, - opts *txExecutorOptions) error { - - waitBeforeRetry := func(attemptNumber int) bool { - retryDelay := randRetryDelay( - opts.initialRetryDelay, opts.maxRetryDelay, - attemptNumber, - ) - - onBackoff(attemptNumber, retryDelay) - - select { - // Before we try again, we'll wait with a random backoff based - // on the retry delay. - case <-time.After(retryDelay): - return true - - // If the daemon is shutting down, then we'll exit early. - case <-ctx.Done(): - return false - } - } - - for i := 0; i < opts.numRetries; i++ { - tx, err := makeTx() - if err != nil { - dbErr := MapSQLError(err) - log.Tracef("Failed to makeTx: err=%v, dbErr=%v", err, - dbErr) - - if IsSerializationOrDeadlockError(dbErr) { - // Nothing to roll back here, since we haven't - // even get a transaction yet. We'll just wait - // and try again. - if waitBeforeRetry(i) { - continue - } - } - - return dbErr - } - - retry, err := executeTxAttempt( - tx, txBody, rollbackTx, waitBeforeRetry, i, - ) - if retry { - // Transient serialization error, discard this attempt and retry. - continue - } - if err != nil { - return err - } - - return nil - } - - // If we get to this point, then we weren't able to successfully commit - // a tx given the max number of retries. - return ErrTxRetriesExceeded -} - -// ExecTx is a wrapper for txBody to abstract the creation and commit of a db -// transaction. The db transaction is embedded in a `*Queries` that txBody -// needs to use when executing each one of the queries that need to be applied -// atomically. This can be used by other storage interfaces to parameterize the -// type of query and options run, in order to have access to batched operations -// related to a storage object. -func (t *TransactionExecutor[Q]) ExecTx(ctx context.Context, - txOptions TxOptions, txBody func(Q) error, reset func()) error { - - makeTx := func() (Tx, error) { - return t.BatchedQuerier.BeginTx(ctx, txOptions) - } - - execTxBody := func(tx Tx) error { - sqlTx, ok := tx.(*sql.Tx) - if !ok { - return fmt.Errorf("expected *sql.Tx, got %T", tx) - } - - reset() - return txBody(t.createQuery(sqlTx)) - } - - onBackoff := func(retry int, delay time.Duration) { - log.Tracef("Retrying transaction due to tx serialization "+ - "error, attempt_number=%v, delay=%v", retry, delay) - } - - rollbackTx := func(tx Tx) error { - sqlTx, ok := tx.(*sql.Tx) - if !ok { - return fmt.Errorf("expected *sql.Tx, got %T", tx) - } - - _ = sqlTx.Rollback() - - return nil - } - - return ExecuteSQLTransactionWithRetry( - ctx, makeTx, rollbackTx, execTxBody, onBackoff, t.opts, - ) -} - -// DB is an interface that represents a generic SQL database. It provides -// methods to apply migrations and access the underlying database connection. -type DB interface { - MigrationExecutor - - // GetBaseDB returns the underlying BaseDB instance. - GetBaseDB() *BaseDB -} - -// BaseDB is the base database struct that each implementation can embed to -// gain some common functionality. -type BaseDB struct { - *sql.DB - - // BackendType defines the type of database backend the database is. - BackendType BackendType - - // SkipMigrations can be set to true to skip running any migrations - // during the iinitialization of the database. - SkipMigrations bool -} - -// BeginTx wraps the normal sql specific BeginTx method with the TxOptions -// interface. This interface is then mapped to the concrete sql tx options -// struct. -func (s *BaseDB) BeginTx(ctx context.Context, opts TxOptions) (*sql.Tx, error) { - sqlOptions := sql.TxOptions{ - Isolation: sql.LevelSerializable, - ReadOnly: opts.ReadOnly(), - } - - return s.DB.BeginTx(ctx, &sqlOptions) -} - -// Backend returns the type of the database backend used. -func (s *BaseDB) Backend() BackendType { - return s.BackendType -} diff --git a/sqldb/v2/interfaces_test.go b/sqldb/v2/interfaces_test.go deleted file mode 100644 index 024b36746..000000000 --- a/sqldb/v2/interfaces_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package sqldb - -import ( - "context" - "database/sql" - "testing" - - "github.com/stretchr/testify/require" -) - -// testQuerier is a minimal query wrapper used to instantiate the generic -// transaction executor in tests. -type testQuerier struct { -} - -// testBatchedQuerier is a minimal BatchedQuerier implementation used to verify -// that TransactionExecutor forwards backend identity. -type testBatchedQuerier struct { - backend BackendType -} - -// BeginTx is a stub implementation used to satisfy the BatchedQuerier -// interface in tests. -func (t testBatchedQuerier) BeginTx(context.Context, - TxOptions) (*sql.Tx, error) { - - return nil, nil -} - -// Backend returns the backend type used by the test batched querier. -func (t testBatchedQuerier) Backend() BackendType { - return t.backend -} - -// TestTransactionExecutorBackend verifies that the executor forwards the -// backend type from its batched querier. -func TestTransactionExecutorBackend(t *testing.T) { - t.Parallel() - - executor := NewTransactionExecutor[testQuerier]( - testBatchedQuerier{backend: BackendTypePostgres}, - func(*sql.Tx) testQuerier { - return testQuerier{} - }, - ) - - require.Equal(t, BackendTypePostgres, executor.Backend()) -} diff --git a/sqldb/v2/log.go b/sqldb/v2/log.go deleted file mode 100644 index 19f43f701..000000000 --- a/sqldb/v2/log.go +++ /dev/null @@ -1,24 +0,0 @@ -package sqldb - -import "github.com/btcsuite/btclog/v2" - -// Subsystem defines the logging code for this subsystem. -const Subsystem = "SQL2" - -// log is a logger that is initialized with no output filters. This -// means the package will not perform any logging by default until the caller -// requests it. -var log = btclog.Disabled - -// DisableLog disables all library log output. Logging output is disabled -// by default until UseLogger is called. -func DisableLog() { - UseLogger(btclog.Disabled) -} - -// UseLogger uses a specified Logger to output package logging info. -// This should be used in preference to SetLogWriter if the caller is also -// using btclog. -func UseLogger(logger btclog.Logger) { - log = logger -} diff --git a/sqldb/v2/migrations.go b/sqldb/v2/migrations.go deleted file mode 100644 index 3a92543e1..000000000 --- a/sqldb/v2/migrations.go +++ /dev/null @@ -1,449 +0,0 @@ -package sqldb - -import ( - "bytes" - "embed" - "errors" - "fmt" - "io" - "io/fs" - "net/http" - "reflect" - "strings" - - "github.com/btcsuite/btclog/v2" - "github.com/davecgh/go-spew/spew" - "github.com/golang-migrate/migrate/v4" - "github.com/golang-migrate/migrate/v4/database" - "github.com/golang-migrate/migrate/v4/source/httpfs" - "github.com/lightningnetwork/lnd/fn/v2" - "github.com/pmezard/go-difflib/difflib" -) - -var ( - // ErrMigrationMismatch is returned when a migrated record does not - // match the original record. - ErrMigrationMismatch = fmt.Errorf("migrated record does not match " + - "original record") -) - -// MigrationDescriptor is a description struct that describes SQL migrations. -// Each migration is associated with a specific schema version and a global -// database version. Migrations are applied in the order of their global -// database version. If a migration includes a non-nil MigrationFn, it is -// executed after the SQL schema has been migrated to the corresponding schema -// version. -type MigrationDescriptor struct { - // Name is the name of the migration. - Name string - - // Version represents the "global" database version for this migration. - // Unlike the schema version tracked by golang-migrate, it encompasses - // all migrations, including those managed by golang-migrate as well - // as custom in-code migrations. - Version int - - // SchemaVersion represents the schema version tracked by golang-migrate - // at which the migration is applied. - SchemaVersion int -} - -// MigrationSet encapsulates all necessary information to manage and apply -// a series of SQL migrations, and corresponding code migrations, to a database. -type MigrationSet struct { - // TrackingTableName is the name of the table used by golang-migrate to - // track the current schema version. - TrackingTableName string - - // SQLFiles is the embedded file system containing the SQL migration - // files. - SQLFiles embed.FS - - // SQLFileDirectory is the directory containing the SQL migration files. - SQLFileDirectory string - - // MakeProgrammaticMigrations is a function that returns a map of - // ProgrammaticMigrEntry functions that can be used to execute a Golang - // based migration step. The key is the migration version and the value - // is the Golang migration function entry that should be run for the - // migration version. Note that a database version can be either an SQL - // migration or a programmatic migration, but not both at the same time. - MakeProgrammaticMigrations func( - *BaseDB) (map[uint]migrate.ProgrammaticMigrEntry, error) - - // LatestMigrationVersion is the latest migration version of the - // database. This is used to implement downgrade protection for the - // daemon. - LatestMigrationVersion uint - - // Descriptors defines a list of migrations to be applied to the - // database. Each migration is assigned a version number that documents - // and validates the expected execution order. - // The schema version, tracked by golang-migrate, ensures migrations are - // applied to the correct schema. For migrations involving only schema - // changes, the migration function can be left nil. For custom - // migrations an implemented migration function is required. - Descriptors []MigrationDescriptor -} - -// validate checks that the migration metadata is internally consistent. -func (m MigrationSet) validate() error { - if len(m.Descriptors) == 0 { - if m.LatestMigrationVersion != 0 { - return fmt.Errorf("latest migration version %d requires "+ - "at least one descriptor", - m.LatestMigrationVersion) - } - - return nil - } - - for i, descriptor := range m.Descriptors { - expectedVersion := i + 1 - if descriptor.Version != expectedVersion { - return fmt.Errorf("migration descriptor version %d is out "+ - "of order, expected %d", descriptor.Version, - expectedVersion) - } - } - - lastDescriptor := m.Descriptors[len(m.Descriptors)-1] - if uint(lastDescriptor.Version) != m.LatestMigrationVersion { - return fmt.Errorf("latest migration version %d does not "+ - "match last descriptor version %d", - m.LatestMigrationVersion, lastDescriptor.Version) - } - - return nil -} - -// MigrationTarget is a functional option that can be passed to applyMigrations -// to specify a target version to migrate to. `currentDbVersion` is the current -// (migration) version of the database, or None if unknown. -// `maxMigrationVersion` is the maximum migration version known to the driver, -// or None if unknown. -type MigrationTarget func(mig *migrate.Migrate, - currentDbVersion int, maxMigrationVersion uint) error - -// MigrationExecutor is an interface that abstracts the migration functionality. -type MigrationExecutor interface { - // ExecuteMigrations runs database migrations for the given migration - // set using the executor's default production migration target. A - // migration may include a schema change, a custom migration function, - // or both. - ExecuteMigrations(set MigrationSet) error -} - -var ( - // TargetLatest is a MigrationTarget that migrates to the latest - // version available. - TargetLatest = func(mig *migrate.Migrate, _ int, _ uint) error { - return mig.Up() - } - - // TargetVersion is a MigrationTarget that migrates to the given - // version. - TargetVersion = func(version uint) MigrationTarget { - return func(mig *migrate.Migrate, _ int, _ uint) error { - return mig.Migrate(version) - } - } - - // ErrMigrationDowngrade is returned when a database downgrade is - // detected. - ErrMigrationDowngrade = errors.New("database downgrade detected") -) - -// migrationOption is a functional option that can be passed to migrate related -// methods to modify their behavior. -type migrateOptions struct { - latestVersion fn.Option[uint] - programmaticMigrs map[uint]migrate.ProgrammaticMigrEntry -} - -// defaultMigrateOptions returns a new migrateOptions instance with default -// settings. -func defaultMigrateOptions() *migrateOptions { - return &migrateOptions{ - programmaticMigrs: make(map[uint]migrate.ProgrammaticMigrEntry), - } -} - -// MigrateOpt is a functional option that can be passed to migrate related -// methods to modify behavior. -type MigrateOpt func(*migrateOptions) - -// WithLatestVersion allows callers to override the default latest version -// setting. -func WithLatestVersion(version uint) MigrateOpt { - return func(o *migrateOptions) { - o.latestVersion = fn.Some(version) - } -} - -// WithProgrammaticMigrations is an option that can be used to set a map of -// ProgrammaticMigrEntry functions that can be used to execute a Golang based -// migration step. The key is the migration version and the value is the -// Golang migration function entry that should be run for the migration version. -func WithProgrammaticMigrations( - programmaticMigrs map[uint]migrate.ProgrammaticMigrEntry) MigrateOpt { - - return func(o *migrateOptions) { - o.programmaticMigrs = programmaticMigrs - } -} - -// migrationLogger is a logger that wraps the passed btclog.Logger so it can be -// used to log migrations. -type migrationLogger struct { - log btclog.Logger -} - -// Printf is like fmt.Printf. We map this to the target logger based on the -// current log level. -func (m *migrationLogger) Printf(format string, v ...interface{}) { - // Trim trailing newlines from the format. - format = strings.TrimRight(format, "\n") - - switch m.log.Level() { - case btclog.LevelTrace: - m.log.Tracef(format, v...) - case btclog.LevelDebug: - m.log.Debugf(format, v...) - case btclog.LevelInfo: - m.log.Infof(format, v...) - case btclog.LevelWarn: - m.log.Warnf(format, v...) - case btclog.LevelError: - m.log.Errorf(format, v...) - case btclog.LevelCritical: - m.log.Criticalf(format, v...) - case btclog.LevelOff: - } -} - -// Verbose should return true when verbose logging output is wanted -func (m *migrationLogger) Verbose() bool { - return m.log.Level() <= btclog.LevelDebug -} - -// applyMigrations executes all database migration files found in the given file -// system under the given path, using the passed database driver and database -// name. -func applyMigrations(fs fs.FS, driver database.Driver, path, - dbName string, targetVersion MigrationTarget, - opts *migrateOptions) error { - - // With the migrate instance open, we'll create a new migration source - // using the embedded file system stored in sqlSchemas. The library - // we're using can't handle a raw file system interface, so we wrap it - // in this intermediate layer. - migrateFileServer, err := httpfs.New(http.FS(fs), path) - if err != nil { - return err - } - - // Finally, we'll run the migration with our driver above based on the - // open DB, and also the migration source stored in the file system - // above. - sqlMigrate, err := migrate.NewWithInstance( - "migrations", migrateFileServer, dbName, driver, - migrate.WithProgrammaticMigrations(opts.programmaticMigrs), - ) - if err != nil { - return err - } - - migrationVersion, dirty, err := sqlMigrate.Version() - if err != nil && !errors.Is(err, migrate.ErrNilVersion) { - return fmt.Errorf("unable to determine current migration "+ - "version: %w", err) - } - - // If the migration version is dirty, we should not proceed with further - // migrations, as this indicates that a previous migration did not - // complete successfully and requires manual intervention. - if dirty { - return fmt.Errorf("database is in a dirty state at version "+ - "%v, manual intervention required", migrationVersion) - } - - // As the down migrations may end up *dropping* data, we want to - // prevent that without explicit accounting. - latestVersion, err := opts.latestVersion.UnwrapOrErr( - fmt.Errorf("latest version not set"), - ) - if err != nil { - return fmt.Errorf("unable to get latest version: %w", err) - } - if migrationVersion > latestVersion { - return fmt.Errorf("%w: database version is newer than the "+ - "latest migration version, preventing downgrade: "+ - "db_version=%v, latest_migration_version=%v", - ErrMigrationDowngrade, migrationVersion, latestVersion) - } - - // Report the current version of the database before the migration. - currentDbVersion, _, err := driver.Version() - if err != nil { - return fmt.Errorf("unable to get current db version: %w", err) - } - log.Infof("Attempting to apply migration(s) "+ - "(current_db_version=%v, latest_migration_version=%v)", - currentDbVersion, latestVersion) - - // Apply our local logger to the migration instance. - sqlMigrate.Log = &migrationLogger{log} - - // Execute the migration based on the target given. - err = targetVersion(sqlMigrate, currentDbVersion, latestVersion) - if err != nil && !errors.Is(err, migrate.ErrNoChange) { - return err - } - - // Report the current version of the database after the migration. - currentDbVersion, _, err = driver.Version() - if err != nil { - return fmt.Errorf("unable to get current db version: %w", err) - } - log.Infof("Database version after migration: %v", currentDbVersion) - - return nil -} - -// replacerFS is an implementation of a fs.FS virtual file system that wraps an -// existing file system but does a search-and-replace operation on each file -// when it is opened. -type replacerFS struct { - parentFS fs.FS - replaces map[string]string -} - -// A compile-time assertion to make sure replacerFS implements the fs.FS -// interface. -var _ fs.FS = (*replacerFS)(nil) - -// newReplacerFS creates a new replacer file system, wrapping the given parent -// virtual file system. Each file within the file system is undergoing a -// search-and-replace operation when it is opened, using the given map where the -// key denotes the search term and the value the term to replace each occurrence -// with. -func newReplacerFS(parent fs.FS, replaces map[string]string) *replacerFS { - return &replacerFS{ - parentFS: parent, - replaces: replaces, - } -} - -// Open opens a file in the virtual file system. -// -// NOTE: This is part of the fs.FS interface. -func (t *replacerFS) Open(name string) (fs.File, error) { - f, err := t.parentFS.Open(name) - if err != nil { - return nil, err - } - - stat, err := f.Stat() - if err != nil { - return nil, err - } - - if stat.IsDir() { - return f, err - } - - return newReplacerFile(f, t.replaces) -} - -type replacerFile struct { - parentFile fs.File - buf bytes.Buffer -} - -// A compile-time assertion to make sure replacerFile implements the fs.File -// interface. -var _ fs.File = (*replacerFile)(nil) - -func newReplacerFile(parent fs.File, replaces map[string]string) (*replacerFile, - error) { - - content, err := io.ReadAll(parent) - if err != nil { - return nil, err - } - - contentStr := string(content) - for from, to := range replaces { - contentStr = strings.ReplaceAll(contentStr, from, to) - } - - var buf bytes.Buffer - _, err = buf.WriteString(contentStr) - if err != nil { - return nil, err - } - - return &replacerFile{ - parentFile: parent, - buf: buf, - }, nil -} - -// Stat returns statistics/info about the file. -// -// NOTE: This is part of the fs.File interface. -func (t *replacerFile) Stat() (fs.FileInfo, error) { - return t.parentFile.Stat() -} - -// Read reads as many bytes as possible from the file into the given slice. -// -// NOTE: This is part of the fs.File interface. -func (t *replacerFile) Read(bytes []byte) (int, error) { - return t.buf.Read(bytes) -} - -// Close closes the underlying file. -// -// NOTE: This is part of the fs.File interface. -func (t *replacerFile) Close() error { - // We already fully read and then closed the file when creating this - // instance, so there's nothing to do for us here. - return nil -} - -// ApplyAllMigrations applies both the SQLC and custom in-code migrations to the -// SQLite database. -func ApplyAllMigrations(executor MigrationExecutor, sets []MigrationSet) error { - for _, set := range sets { - err := executor.ExecuteMigrations(set) - if err != nil { - return fmt.Errorf("error applying migrations: %w", err) - } - } - - return nil -} - -// CompareRecords checks if the original and migrated objects are equal. If -// they are not, it returns an error with a unified diff of the two objects. -func CompareRecords(original, migrated any, identifier string) error { - if reflect.DeepEqual(original, migrated) { - return nil - } - - diff := difflib.UnifiedDiff{ - A: difflib.SplitLines(spew.Sdump(original)), - B: difflib.SplitLines(spew.Sdump(migrated)), - FromFile: "Expected", - FromDate: "", - ToFile: "Actual", - ToDate: "", - Context: 3, - } - diffText, _ := difflib.GetUnifiedDiffString(diff) - - return fmt.Errorf("%w: %s.\n%v", ErrMigrationMismatch, identifier, - diffText) -} diff --git a/sqldb/v2/migrations_test.go b/sqldb/v2/migrations_test.go deleted file mode 100644 index 67ad1f399..000000000 --- a/sqldb/v2/migrations_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package sqldb - -import ( - "io" - "testing" - "testing/fstest" - - "github.com/stretchr/testify/require" -) - -// TestPostgresSchemaReplacements verifies that the Postgres schema -// replacements do not rewrite SQL keywords that only contain a replacement -// token as a substring. -func TestPostgresSchemaReplacements(t *testing.T) { - t.Parallel() - - postgresFS := newReplacerFS(fstest.MapFS{ - "schema.sql": &fstest.MapFile{ - Data: []byte("created_at TIMESTAMP NOT NULL DEFAULT " + - "CURRENT_TIMESTAMP"), - }, - }, postgresSchemaReplacements) - - file, err := postgresFS.Open("schema.sql") - require.NoError(t, err) - t.Cleanup(func() { - require.NoError(t, file.Close()) - }) - - content, err := io.ReadAll(file) - require.NoError(t, err) - - require.Equal(t, - "created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT "+ - "CURRENT_TIMESTAMP", string(content), - ) -} - -// TestMigrationSetValidate verifies that migration descriptors remain aligned -// with the migration stream metadata. -func TestMigrationSetValidate(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - set MigrationSet - expect string - }{ - { - name: "valid descriptors", - set: MigrationSet{ - LatestMigrationVersion: 2, - Descriptors: []MigrationDescriptor{ - {Version: 1}, - {Version: 2}, - }, - }, - }, - { - name: "descriptor gap", - set: MigrationSet{ - LatestMigrationVersion: 2, - Descriptors: []MigrationDescriptor{ - {Version: 1}, - {Version: 3}, - }, - }, - expect: "out of order", - }, - { - name: "missing descriptors for latest version", - set: MigrationSet{ - LatestMigrationVersion: 1, - }, - expect: "requires at least one descriptor", - }, - { - name: "latest version mismatch", - set: MigrationSet{ - LatestMigrationVersion: 3, - Descriptors: []MigrationDescriptor{ - {Version: 1}, - {Version: 2}, - }, - }, - expect: "does not match", - }, - } - - for _, testCase := range testCases { - testCase := testCase - - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - err := testCase.set.validate() - if testCase.expect == "" { - require.NoError(t, err) - return - } - - require.ErrorContains(t, err, testCase.expect) - }) - } -} diff --git a/sqldb/v2/no_sqlite.go b/sqldb/v2/no_sqlite.go deleted file mode 100644 index e496e3c54..000000000 --- a/sqldb/v2/no_sqlite.go +++ /dev/null @@ -1,43 +0,0 @@ -//go:build js || (windows && (arm || 386)) || (linux && (ppc64 || mips || mipsle || mips64)) - -package sqldb - -import ( - "fmt" -) - -var ( - // Make sure SqliteStore implements the DB interface. - _ DB = (*SqliteStore)(nil) -) - -// SqliteStore is a database store implementation that uses a sqlite backend. -// -// NOTE: This specific struct implementation does not implement a real sqlite -// store, and only exists to ensure that build tag environments that do not -// support sqlite database backends still contain a struct called SqliteStore, -// to ensure that the build process doesn't error. -type SqliteStore struct { - cfg *SqliteConfig - - *BaseDB -} - -// NewSqliteStore attempts to open a new sqlite database based on the passed -// config. -func NewSqliteStore(cfg *SqliteConfig, dbPath string) (*SqliteStore, error) { - return nil, fmt.Errorf("SQLite backend not supported on this platform") -} - -// GetBaseDB returns the underlying BaseDB instance for the SQLite store. -// It is a trivial helper method to comply with the sqldb.DB interface. -func (s *SqliteStore) GetBaseDB() *BaseDB { - return s.BaseDB -} - -// ExecuteMigrations returns an error because the SQLite backend is unavailable -// on this platform. -func (s *SqliteStore) ExecuteMigrations(MigrationSet) error { - - return fmt.Errorf("SQLite backend not supported on this platform") -} diff --git a/sqldb/v2/paginate.go b/sqldb/v2/paginate.go deleted file mode 100644 index 4fd2a9d4d..000000000 --- a/sqldb/v2/paginate.go +++ /dev/null @@ -1,318 +0,0 @@ -package sqldb - -import ( - "context" - "fmt" -) - -const ( - // maxSQLiteBatchSize is the maximum number of items that can be - // included in a batch query IN clause for SQLite. This was determined - // using the TestSQLSliceQueries test. - maxSQLiteBatchSize = 32766 - - // maxPostgresBatchSize is the maximum number of items that can be - // included in a batch query IN clause for Postgres. This was determined - // using the TestSQLSliceQueries test. - maxPostgresBatchSize = 65535 - - // defaultSQLitePageSize is the default page size for SQLite queries. - defaultSQLitePageSize = 100 - - // defaultPostgresPageSize is the default page size for Postgres - // queries. - defaultPostgresPageSize = 10500 - - // defaultSQLiteBatchSize is the default batch size for SQLite queries. - defaultSQLiteBatchSize = 250 - - // defaultPostgresBatchSize is the default batch size for Postgres - // queries. - defaultPostgresBatchSize = 5000 -) - -// QueryConfig holds configuration values for SQL queries. -// -//nolint:ll -type QueryConfig struct { - // MaxBatchSize is the maximum number of items included in a batch - // query IN clauses list. - MaxBatchSize uint32 `long:"max-batch-size" description:"The maximum number of items to include in a batch query IN clause. This is used for queries that fetch results based on a list of identifiers."` - - // MaxPageSize is the maximum number of items returned in a single page - // of results. This is used for paginated queries. - MaxPageSize uint32 `long:"max-page-size" description:"The maximum number of items to return in a single page of results. This is used for paginated queries."` -} - -// Validate checks that the QueryConfig values are valid. -func (c *QueryConfig) Validate(sqlite bool) error { - if c.MaxBatchSize <= 0 { - return fmt.Errorf("max batch size must be greater than "+ - "zero, got %d", c.MaxBatchSize) - } - if c.MaxPageSize <= 0 { - return fmt.Errorf("max page size must be greater than "+ - "zero, got %d", c.MaxPageSize) - } - - if sqlite { - if c.MaxBatchSize > maxSQLiteBatchSize { - return fmt.Errorf("max batch size for SQLite cannot "+ - "exceed %d, got %d", maxSQLiteBatchSize, - c.MaxBatchSize) - } - } else { - if c.MaxBatchSize > maxPostgresBatchSize { - return fmt.Errorf("max batch size for Postgres cannot "+ - "exceed %d, got %d", maxPostgresBatchSize, - c.MaxBatchSize) - } - } - - return nil -} - -// DefaultSQLiteConfig returns a default configuration for SQL queries to a -// SQLite backend. -func DefaultSQLiteConfig() *QueryConfig { - return &QueryConfig{ - MaxBatchSize: defaultSQLiteBatchSize, - MaxPageSize: defaultSQLitePageSize, - } -} - -// DefaultPostgresConfig returns a default configuration for SQL queries to a -// Postgres backend. -func DefaultPostgresConfig() *QueryConfig { - return &QueryConfig{ - MaxBatchSize: defaultPostgresBatchSize, - MaxPageSize: defaultPostgresPageSize, - } -} - -// BatchQueryFunc represents a function that takes a batch of converted items -// and returns results. -type BatchQueryFunc[T any, R any] func(context.Context, []T) ([]R, error) - -// ItemCallbackFunc represents a function that processes individual results. -type ItemCallbackFunc[R any] func(context.Context, R) error - -// ConvertFunc represents a function that converts from input type to query type -// for the batch query. -type ConvertFunc[I any, T any] func(I) T - -// ExecuteBatchQuery executes a query in batches over a slice of input items. -// It converts the input items to a query type using the provided convertFunc, -// executes the query in batches using the provided queryFunc, and applies -// the callback to each result. This is useful for queries using the -// "WHERE x IN []slice" pattern. It takes that slice, splits it into batches of -// size MaxBatchSize, and executes the query for each batch. -// -// NOTE: it is the caller's responsibility to ensure that the expected return -// results are unique across all pages. Meaning that if the input items are -// split up, a result that is returned in one page should not be expected to -// be returned in another page. -func ExecuteBatchQuery[I any, T any, R any](ctx context.Context, - cfg *QueryConfig, inputItems []I, convertFunc ConvertFunc[I, T], - queryFunc BatchQueryFunc[T, R], callback ItemCallbackFunc[R]) error { - - if len(inputItems) == 0 { - return nil - } - - // Process items in pages. - for i := 0; i < len(inputItems); i += int(cfg.MaxBatchSize) { - // Calculate the end index for this page. - end := i + int(cfg.MaxBatchSize) - if end > len(inputItems) { - end = len(inputItems) - } - - // Get the page slice of input items. - inputPage := inputItems[i:end] - - // Convert only the items needed for this page. - convertedPage := make([]T, len(inputPage)) - for j, inputItem := range inputPage { - convertedPage[j] = convertFunc(inputItem) - } - - // Execute the query for this page. - results, err := queryFunc(ctx, convertedPage) - if err != nil { - return fmt.Errorf("query failed for page "+ - "starting at %d: %w", i, err) - } - - // Apply the callback to each result. - for _, result := range results { - if err := callback(ctx, result); err != nil { - return fmt.Errorf("callback failed for "+ - "result: %w", err) - } - } - } - - return nil -} - -// PagedQueryFunc represents a function that fetches a page of results using a -// cursor. It returns the fetched items and should return an empty slice when no -// more results. -type PagedQueryFunc[C any, T any] func(context.Context, C, int32) ([]T, error) - -// CursorExtractFunc represents a function that extracts the cursor value from -// an item. This cursor will be used for the next page fetch. -type CursorExtractFunc[T any, C any] func(T) C - -// ItemProcessFunc represents a function that processes individual items. -type ItemProcessFunc[T any] func(context.Context, T) error - -// ExecutePaginatedQuery executes a cursor-based paginated query. It continues -// fetching pages until no more results are returned, processing each item with -// the provided callback. -// -// Parameters: -// - initialCursor: the starting cursor value (e.g., 0, -1, "", etc.). -// - queryFunc: function that fetches a page given cursor and limit. -// - extractCursor: function that extracts cursor from an item for next page. -// - processItem: function that processes each individual item. -// -// NOTE: it is the caller's responsibility to "undo" any processing done on -// items if the query fails on a later page. -func ExecutePaginatedQuery[C any, T any](ctx context.Context, cfg *QueryConfig, - initialCursor C, queryFunc PagedQueryFunc[C, T], - extractCursor CursorExtractFunc[T, C], - processItem ItemProcessFunc[T]) error { - - cursor := initialCursor - - for { - // Fetch the next page. - items, err := queryFunc(ctx, cursor, int32(cfg.MaxPageSize)) - if err != nil { - return fmt.Errorf("failed to fetch page with "+ - "cursor %v: %w", cursor, err) - } - - // If no items returned, we're done. - if len(items) == 0 { - break - } - - // Process each item in the page. - for _, item := range items { - if err := processItem(ctx, item); err != nil { - return fmt.Errorf("failed to process item: %w", - err) - } - - // Update cursor for next iteration. - cursor = extractCursor(item) - } - - // If the number of items is less than the max page size, - // we assume there are no more items to fetch. - if len(items) < int(cfg.MaxPageSize) { - break - } - } - - return nil -} - -// CollectAndBatchDataQueryFunc represents a function that batch loads -// additional data for collected identifiers, returning the batch data that -// applies to all items. -type CollectAndBatchDataQueryFunc[ID any, BatchData any] func(context.Context, - []ID) (BatchData, error) - -// ItemWithBatchDataProcessFunc represents a function that processes individual -// items along with shared batch data. -type ItemWithBatchDataProcessFunc[T any, BatchData any] func(context.Context, - T, BatchData) error - -// CollectFunc represents a function that extracts an identifier from a -// paginated item. -type CollectFunc[T any, ID any] func(T) (ID, error) - -// ExecuteCollectAndBatchWithSharedDataQuery implements a page-by-page -// processing pattern where each page is immediately processed with batch-loaded -// data before moving to the next page. -// -// It: -// 1. Fetches a page of items using cursor-based pagination -// 2. Collects identifiers from that page and batch loads shared data -// 3. Processes each item in the page with the shared batch data -// 4. Moves to the next page and repeats -// -// Parameters: -// - initialCursor: starting cursor for pagination -// - pageQueryFunc: fetches a page of items -// - extractPageCursor: extracts cursor from paginated item for next page -// - collectFunc: extracts identifier from paginated item -// - batchDataFunc: batch loads shared data from collected IDs for one page -// - processItem: processes each item with the shared batch data -func ExecuteCollectAndBatchWithSharedDataQuery[C any, T any, I any, D any]( - ctx context.Context, cfg *QueryConfig, initialCursor C, - pageQueryFunc PagedQueryFunc[C, T], - extractPageCursor CursorExtractFunc[T, C], - collectFunc CollectFunc[T, I], - batchDataFunc CollectAndBatchDataQueryFunc[I, D], - processItem ItemWithBatchDataProcessFunc[T, D]) error { - - cursor := initialCursor - - for { - // Step 1: Fetch the next page of items. - items, err := pageQueryFunc(ctx, cursor, int32(cfg.MaxPageSize)) - if err != nil { - return fmt.Errorf("failed to fetch page with "+ - "cursor %v: %w", cursor, err) - } - - // If no items returned, we're done. - if len(items) == 0 { - break - } - - // Step 2: Collect identifiers from this page and batch load - // data. - pageIDs := make([]I, len(items)) - for i, item := range items { - pageIDs[i], err = collectFunc(item) - if err != nil { - return fmt.Errorf("failed to collect "+ - "identifier from item: %w", err) - } - } - - // Batch load shared data for this page. - batchData, err := batchDataFunc(ctx, pageIDs) - if err != nil { - return fmt.Errorf("failed to load batch data for "+ - "page: %w", err) - } - - // Step 3: Process each item in this page with the shared batch - // data. - for _, item := range items { - err := processItem(ctx, item, batchData) - if err != nil { - return fmt.Errorf("failed to process item "+ - "with batch data: %w", err) - } - - // Update cursor for next page. - cursor = extractPageCursor(item) - } - - // If the number of items is less than the max page size, - // we assume there are no more items to fetch. - if len(items) < int(cfg.MaxPageSize) { - break - } - } - - return nil -} diff --git a/sqldb/v2/postgres.go b/sqldb/v2/postgres.go deleted file mode 100644 index 833c7445a..000000000 --- a/sqldb/v2/postgres.go +++ /dev/null @@ -1,293 +0,0 @@ -package sqldb - -import ( - "database/sql" - "fmt" - "net/url" - "path" - "strings" - "time" - - pgx_migrate "github.com/golang-migrate/migrate/v4/database/pgx/v5" - _ "github.com/golang-migrate/migrate/v4/source/file" - _ "github.com/jackc/pgx/v5" - "github.com/lightningnetwork/lnd/fn/v2" -) - -var ( - // DefaultPostgresFixtureLifetime is the default maximum time a Postgres - // test fixture is being kept alive. After that time the docker - // container will be terminated forcefully, even if the tests aren't - // fully executed yet. So this time needs to be chosen correctly to be - // longer than the longest expected individual test run time. - DefaultPostgresFixtureLifetime = 10 * time.Minute - - // postgresSchemaReplacements is a map of schema strings that need to be - // replaced for postgres. This is needed because we write the schemas to - // work with sqlite primarily but in sqlc's own dialect, and postgres - // has some differences. - postgresSchemaReplacements = map[string]string{ - "BLOB": "BYTEA", - "INTEGER PRIMARY KEY": "BIGSERIAL PRIMARY KEY", - // We need this space in front of the TIMESTAMP keyword to - // avoid replacing words which just have the word "TIMESTAMP" in - // them. - " TIMESTAMP": " TIMESTAMP WITHOUT TIME ZONE", - "UNHEX": "DECODE", - } - - // Make sure PostgresStore implements the MigrationExecutor interface. - _ MigrationExecutor = (*PostgresStore)(nil) - - // Make sure PostgresStore implements the DB interface. - _ DB = (*PostgresStore)(nil) -) - -// sslModesRequiringTLS lists sslmode values that already enforce TLS and -// therefore do not need to be rewritten when RequireSSL is set. -var sslModesRequiringTLS = map[string]struct{}{ - "require": {}, - "verify-ca": {}, - "verify-full": {}, -} - -// replacePasswordInDSN takes a DSN string and returns it with the password -// replaced by "***". -func replacePasswordInDSN(dsn string) (string, error) { - // Parse the DSN as a URL - u, err := url.Parse(dsn) - if err != nil { - return "", err - } - - // Check if the URL has a user info part - if u.User != nil { - username := u.User.Username() - - // Reconstruct user info with "***" as password - userInfo := username + ":***@" - - // Rebuild the DSN with the modified user info - sanitizeDSN := strings.Replace( - dsn, u.User.String()+"@", userInfo, 1, - ) - - return sanitizeDSN, nil - } - - // Return the original DSN if no user info is present - return dsn, nil -} - -// getDatabaseNameFromDSN extracts the database name from a DSN string. -func getDatabaseNameFromDSN(dsn string) (string, error) { - // Parse the DSN as a URL - u, err := url.Parse(dsn) - if err != nil { - return "", err - } - - // The database name is the last segment of the path. Trim leading slash - // and return the last segment. - return path.Base(u.Path), nil -} - -// ensureRequiredSSLMode rewrites the DSN to require TLS when requested. -func ensureRequiredSSLMode(dsn string, requireSSL bool) (string, error) { - if !requireSSL { - return dsn, nil - } - - u, err := url.Parse(dsn) - if err != nil { - return "", err - } - - query := u.Query() - sslMode := query.Get("sslmode") - if _, ok := sslModesRequiringTLS[sslMode]; !ok { - query.Set("sslmode", "require") - } - - u.RawQuery = query.Encode() - - return u.String(), nil -} - -// PostgresStore is a database store implementation that uses a Postgres -// backend. -type PostgresStore struct { - cfg *PostgresConfig - - *BaseDB -} - -// NewPostgresStore creates a new store that is backed by a Postgres database -// backend. -func NewPostgresStore(cfg *PostgresConfig) (*PostgresStore, error) { - if cfg == nil { - return nil, fmt.Errorf("postgres config is required") - } - - // Copy the caller config so we can enforce RequireSSL on the DSN - // without mutating a config value that may be reused elsewhere. - effectiveCfg := *cfg - - effectiveDSN, err := ensureRequiredSSLMode( - effectiveCfg.Dsn, effectiveCfg.RequireSSL, - ) - if err != nil { - return nil, err - } - effectiveCfg.Dsn = effectiveDSN - - sanitizedDSN, err := replacePasswordInDSN(effectiveCfg.Dsn) - if err != nil { - return nil, err - } - log.Infof("Using SQL database '%s'", sanitizedDSN) - - db, err := sql.Open("pgx", effectiveCfg.Dsn) - if err != nil { - return nil, err - } - - // Ensure the migration tracker table exists before running migrations. - // This table tracks migration progress and ensures compatibility with - // SQLC query generation. If the table is already created by an SQLC - // migration, this operation becomes a no-op. - migrationTrackerSQL := ` - CREATE TABLE IF NOT EXISTS migration_tracker ( - version INTEGER UNIQUE NOT NULL, - migration_time TIMESTAMP NOT NULL - );` - - _, err = db.Exec(migrationTrackerSQL) - if err != nil { - return nil, fmt.Errorf("error creating migration tracker: %w", - err) - } - - maxConns := DefaultPostgresMaxConns - if cfg.MaxOpenConnections > 0 { - maxConns = cfg.MaxOpenConnections - } - - maxIdleConns := defaultMaxIdleConns - if cfg.MaxIdleConnections > 0 { - maxIdleConns = cfg.MaxIdleConnections - } - - connMaxLifetime := defaultConnMaxLifetime - if cfg.ConnMaxLifetime > 0 { - connMaxLifetime = cfg.ConnMaxLifetime - } - - connMaxIdleTime := defaultConnMaxIdleTime - if cfg.ConnMaxIdleTime > 0 { - connMaxIdleTime = cfg.ConnMaxIdleTime - } - - db.SetMaxOpenConns(maxConns) - db.SetMaxIdleConns(maxIdleConns) - db.SetConnMaxLifetime(connMaxLifetime) - db.SetConnMaxIdleTime(connMaxIdleTime) - - return &PostgresStore{ - cfg: &effectiveCfg, - BaseDB: &BaseDB{ - DB: db, - BackendType: BackendTypePostgres, - SkipMigrations: effectiveCfg.SkipMigrations, - }, - }, nil -} - -// GetBaseDB returns the underlying BaseDB instance for the Postgres store. -// It is a trivial helper method to comply with the sqldb.DB interface. -func (s *PostgresStore) GetBaseDB() *BaseDB { - return s.BaseDB -} - -func errPostgresMigration(err error) error { - return fmt.Errorf("error creating postgres migration: %w", err) -} - -// ExecuteMigrations runs migrations for the Postgres database using the -// default production migration target. -func (s *PostgresStore) ExecuteMigrations(set MigrationSet) error { - if s.SkipMigrations { - return nil - } - - return s.executeMigrations(TargetLatest, set) -} - -// executeMigrations runs migrations for the Postgres database, depending on -// the target given, either all migrations or up to a given version. -func (s *PostgresStore) executeMigrations(target MigrationTarget, - set MigrationSet) error { - - if err := set.validate(); err != nil { - return err - } - - dbName, err := getDatabaseNameFromDSN(s.cfg.Dsn) - if err != nil { - return err - } - - driver, err := pgx_migrate.WithInstance(s.DB, &pgx_migrate.Config{ - MigrationsTable: set.TrackingTableName, - }) - if err != nil { - return errPostgresMigration(err) - } - - opts := &migrateOptions{ - latestVersion: fn.Some(set.LatestMigrationVersion), - } - - if set.MakeProgrammaticMigrations != nil { - postMigSteps, err := set.MakeProgrammaticMigrations(s.BaseDB) - if err != nil { - return errPostgresMigration(err) - } - opts.programmaticMigrs = postMigSteps - } - - // Populate the database with our set of schemas based on our embedded - // in-memory file system. - postgresFS := newReplacerFS(set.SQLFiles, postgresSchemaReplacements) - return applyMigrations( - postgresFS, driver, set.SQLFileDirectory, dbName, target, opts, - ) -} - -// GetSchemaVersion returns the current schema version of the Postgres database. -func (s *PostgresStore) GetSchemaVersion() (int, bool, error) { - driver, err := pgx_migrate.WithInstance(s.DB, &pgx_migrate.Config{}) - if err != nil { - return 0, false, errPostgresMigration(err) - - } - - version, dirty, err := driver.Version() - if err != nil { - return 0, false, err - } - - return version, dirty, nil -} - -// SetSchemaVersion sets the schema version of the Postgres database. -// -// NOTE: This alters the internal database schema tracker. USE WITH CAUTION!!! -func (s *PostgresStore) SetSchemaVersion(version int, dirty bool) error { - driver, err := pgx_migrate.WithInstance(s.DB, &pgx_migrate.Config{}) - if err != nil { - return errPostgresMigration(err) - } - - return driver.SetVersion(version, dirty) -} diff --git a/sqldb/v2/postgres_fixture.go b/sqldb/v2/postgres_fixture.go deleted file mode 100644 index 6918e3cbd..000000000 --- a/sqldb/v2/postgres_fixture.go +++ /dev/null @@ -1,235 +0,0 @@ -//go:build !js && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)) && !(netbsd || openbsd) - -package sqldb - -import ( - "context" - "crypto/rand" - "database/sql" - "encoding/hex" - "fmt" - "strconv" - "strings" - "testing" - "time" - - _ "github.com/jackc/pgx/v5" - "github.com/ory/dockertest/v3" - "github.com/ory/dockertest/v3/docker" - "github.com/stretchr/testify/require" -) - -const ( - testPgUser = "test" - testPgPass = "test" - testPgDBName = "test" - PostgresTag = "15" -) - -// TestPgFixture is a test fixture that starts a Postgres 11 instance in a -// docker container. -type TestPgFixture struct { - db *sql.DB - pool *dockertest.Pool - resource *dockertest.Resource - host string - port int -} - -// NewTestPgFixture constructs a new TestPgFixture starting up a docker -// container running Postgres 15. The started container will expire in after -// the passed duration. -func NewTestPgFixture(t testing.TB, expiry time.Duration) *TestPgFixture { - // Use a sensible default on Windows (tcp/http) and linux/osx (socket) - // by specifying an empty endpoint. - pool, err := dockertest.NewPool("") - require.NoError(t, err, "Could not connect to docker") - - // Create a Docker-safe container name and add a random suffix so - // concurrently running tests do not collide. - containerName := sanitizeDockerName( - fmt.Sprintf("%s-%s-postgresql-container", t.Name(), - RandomDBName(t)), - ) - - // Pulls an image, creates a container based on it and runs it. - resource, err := pool.RunWithOptions(&dockertest.RunOptions{ - Name: containerName, - Repository: "postgres", - Tag: PostgresTag, - Env: []string{ - fmt.Sprintf("POSTGRES_USER=%v", testPgUser), - fmt.Sprintf("POSTGRES_PASSWORD=%v", testPgPass), - fmt.Sprintf("POSTGRES_DB=%v", testPgDBName), - "listen_addresses='*'", - }, - Cmd: []string{ - "postgres", - "-c", "log_statement=all", - "-c", "log_destination=stderr", - "-c", "max_connections=5000", - }, - }, func(config *docker.HostConfig) { - // Set AutoRemove to true so that stopped container goes away - // by itself. - config.AutoRemove = true - config.RestartPolicy = docker.RestartPolicy{Name: "no"} - }) - require.NoError(t, err, "Could not start resource") - - hostAndPort := resource.GetHostPort("5432/tcp") - parts := strings.Split(hostAndPort, ":") - host := parts[0] - port, err := strconv.ParseInt(parts[1], 10, 64) - require.NoError(t, err) - - fixture := &TestPgFixture{ - host: host, - port: int(port), - } - databaseURL := fixture.GetConfig(testPgDBName).Dsn - log.Infof("Connecting to Postgres fixture: %v\n", databaseURL) - - // Tell docker to hard kill the container in "expiry" seconds. - require.NoError(t, resource.Expire(uint(expiry.Seconds()))) - - // Exponential backoff-retry, because the application in the container - // might not be ready to accept connections yet. - pool.MaxWait = 120 * time.Second - - var testDB *sql.DB - err = pool.Retry(func() error { - testDB, err = sql.Open("pgx", databaseURL) - if err != nil { - return err - } - - return testDB.Ping() - }) - require.NoError(t, err, "Could not connect to docker") - - // Now fill in the rest of the fixture. - fixture.db = testDB - fixture.pool = pool - fixture.resource = resource - - return fixture -} - -// sanitizeDockerName returns a Docker-safe container name. -func sanitizeDockerName(name string) string { - sanitized := strings.Map(func(r rune) rune { - switch { - case r >= 'a' && r <= 'z': - return r - - case r >= 'A' && r <= 'Z': - return r - - case r >= '0' && r <= '9': - return r - - case r == '_', r == '-': - return r - - default: - return '_' - } - }, name) - - sanitized = strings.Trim(sanitized, "_.-") - if sanitized == "" { - return "postgresql-container" - } - - return sanitized -} - -// GetConfig returns the full config of the Postgres node. -func (f *TestPgFixture) GetConfig(dbName string) *PostgresConfig { - return &PostgresConfig{ - Dsn: fmt.Sprintf( - "postgres://%v:%v@%v:%v/%v?sslmode=disable", - testPgUser, testPgPass, f.host, f.port, dbName, - ), - } -} - -// TearDown stops the underlying docker container. -func (f *TestPgFixture) TearDown(t testing.TB) { - err := f.pool.Purge(f.resource) - require.NoError(t, err, "Could not purge resource") -} - -func (f *TestPgFixture) DB() *sql.DB { - return f.db -} - -// RandomDBName generates a random database name. -func RandomDBName(t testing.TB) string { - randBytes := make([]byte, 8) - _, err := rand.Read(randBytes) - require.NoError(t, err) - - return "test_" + hex.EncodeToString(randBytes) -} - -// NewTestPostgresDB is a helper function that creates a Postgres database for -// testing using the given fixture. -func NewTestPostgresDB(t testing.TB, fixture *TestPgFixture, - sets []MigrationSet) *PostgresStore { - - t.Helper() - - dbName := RandomDBName(t) - - t.Logf("Creating new Postgres DB '%s' for testing", dbName) - - _, err := fixture.db.ExecContext( - context.Background(), "CREATE DATABASE "+dbName, - ) - require.NoError(t, err) - - cfg := fixture.GetConfig(dbName) - store, err := NewPostgresStore(cfg) - require.NoError(t, err) - - require.NoError(t, ApplyAllMigrations(store, sets)) - - t.Cleanup(func() { - require.NoError(t, store.DB.Close()) - }) - - return store -} - -// NewTestPostgresDBWithVersion is a helper function that creates a Postgres -// database for testing and migrates it to the given version. -func NewTestPostgresDBWithVersion(t testing.TB, fixture *TestPgFixture, - sets MigrationSet, version uint) *PostgresStore { - - t.Helper() - - t.Logf("Creating new Postgres DB for testing, migrating to version %d", - version) - - dbName := RandomDBName(t) - _, err := fixture.db.ExecContext( - context.Background(), "CREATE DATABASE "+dbName, - ) - require.NoError(t, err) - - storeCfg := fixture.GetConfig(dbName) - storeCfg.SkipMigrations = true - store, err := NewPostgresStore(storeCfg) - require.NoError(t, err) - - err = store.executeMigrations(TargetVersion(version), sets) - require.NoError(t, err) - - t.Cleanup(func() { - require.NoError(t, store.DB.Close()) - }) - - return store -} diff --git a/sqldb/v2/postgres_fixture_test.go b/sqldb/v2/postgres_fixture_test.go deleted file mode 100644 index 4cc086cc9..000000000 --- a/sqldb/v2/postgres_fixture_test.go +++ /dev/null @@ -1,53 +0,0 @@ -//go:build !js && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)) && !(netbsd || openbsd) - -package sqldb - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -// TestSanitizeDockerName verifies that invalid Docker name characters are -// normalized before the fixture creates a container. -func TestSanitizeDockerName(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - input string - expected string - }{ - { - name: "slashes and spaces", - input: "TestParent/some case", - expected: "TestParent_some_case", - }, - { - name: "leading punctuation", - input: "---fixture", - expected: "fixture", - }, - { - name: "trailing punctuation", - input: "fixture---", - expected: "fixture", - }, - { - name: "empty fallback", - input: " ", - expected: "postgresql-container", - }, - } - - for _, testCase := range testCases { - testCase := testCase - - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - result := sanitizeDockerName(testCase.input) - require.Equal(t, testCase.expected, result) - }) - } -} diff --git a/sqldb/v2/postgres_internal_test.go b/sqldb/v2/postgres_internal_test.go deleted file mode 100644 index 408a2a378..000000000 --- a/sqldb/v2/postgres_internal_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package sqldb - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -// TestEnsureRequiredSSLMode verifies that the Postgres DSN is upgraded to a -// TLS-enforcing sslmode when requested. -func TestEnsureRequiredSSLMode(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - dsn string - requireSSL bool - expected string - }{ - { - name: "ssl disabled", - dsn: "postgres://user:pass@localhost/db?sslmode=disable", - requireSSL: true, - expected: "postgres://user:pass@localhost/db?sslmode=require", - }, - { - name: "ssl not requested", - dsn: "postgres://user:pass@localhost/db?sslmode=disable", - requireSSL: false, - expected: "postgres://user:pass@localhost/db?sslmode=disable", - }, - { - name: "strict mode preserved", - dsn: "postgres://user:pass@localhost/db?sslmode=verify-full", - requireSSL: true, - expected: "postgres://user:pass@localhost/db?sslmode=verify-full", - }, - } - - for _, testCase := range testCases { - testCase := testCase - - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - result, err := ensureRequiredSSLMode( - testCase.dsn, testCase.requireSSL, - ) - require.NoError(t, err) - require.Equal(t, testCase.expected, result) - }) - } -} diff --git a/sqldb/v2/sqlerrors.go b/sqldb/v2/sqlerrors.go deleted file mode 100644 index 8f132766e..000000000 --- a/sqldb/v2/sqlerrors.go +++ /dev/null @@ -1,247 +0,0 @@ -//go:build !js && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)) - -package sqldb - -import ( - "errors" - "fmt" - "strings" - - "github.com/jackc/pgerrcode" - "github.com/jackc/pgx/v5/pgconn" - "modernc.org/sqlite" - sqlite3 "modernc.org/sqlite/lib" -) - -var ( - // ErrTxRetriesExceeded is returned when a transaction is retried more - // than the max allowed valued without a success. - ErrTxRetriesExceeded = errors.New("db tx retries exceeded") - - // postgresRetriableErrMsgs are strings that signify retriable errors - // resulting from serialization failures. - postgresRetriableErrMsgs = []string{ - "could not serialize access", - "current transaction is aborted", - "not enough elements in RWConflictPool", - "deadlock detected", - "commit unexpectedly resulted in rollback", - } -) - -// MapSQLError attempts to interpret a given error as a database agnostic SQL -// error. -func MapSQLError(err error) error { - if err == nil { - return nil - } - - // Attempt to interpret the error as a sqlite error. - var sqliteErr *sqlite.Error - if errors.As(err, &sqliteErr) { - return parseSqliteError(sqliteErr) - } - - // Attempt to interpret the error as a postgres error. - var pqErr *pgconn.PgError - if errors.As(err, &pqErr) { - return parsePostgresError(pqErr) - } - - // Sometimes the error won't be properly wrapped, so we'll need to - // inspect raw error itself to detect something we can wrap properly. - // This handles a postgres variant of the error. - for _, postgresErrMsg := range postgresRetriableErrMsgs { - if strings.Contains(err.Error(), postgresErrMsg) { - return &ErrSerializationError{ - DBError: err, - } - } - } - - // We'll also attempt to catch this for sqlite, that uses a slightly - // different error message. This is taken from: - // https://gitlab.com/cznic/sqlite/-/blob/v1.25.0/sqlite.go#L75. - const sqliteErrMsg = "SQLITE_BUSY" - if strings.Contains(err.Error(), sqliteErrMsg) { - return &ErrSerializationError{ - DBError: err, - } - } - - // Return original error if it could not be classified as a database - // specific error. - return err -} - -// parsePostgresError attempts to parse a sqlite error as a database agnostic -// SQL error. -func parseSqliteError(sqliteErr *sqlite.Error) error { - switch sqliteErr.Code() { - // Handle unique constraint violation error. - case sqlite3.SQLITE_CONSTRAINT_UNIQUE: - return &ErrSQLUniqueConstraintViolation{ - DBError: sqliteErr, - } - - case sqlite3.SQLITE_CONSTRAINT_PRIMARYKEY: - return &ErrSQLUniqueConstraintViolation{ - DBError: sqliteErr, - } - - // Database is currently busy, so we'll need to try again. - case sqlite3.SQLITE_BUSY: - return &ErrSerializationError{ - DBError: sqliteErr, - } - - // A write operation could not continue because of a conflict within the - // same database connection. - case sqlite3.SQLITE_LOCKED, sqlite3.SQLITE_BUSY_SNAPSHOT: - return &ErrDeadlockError{ - DbError: sqliteErr, - } - - // Generic error, need to parse the message further. - case sqlite3.SQLITE_ERROR: - errMsg := sqliteErr.Error() - - switch { - case strings.Contains(errMsg, "no such table"): - return &ErrSchemaError{ - DbError: sqliteErr, - } - - default: - return fmt.Errorf("unknown sqlite error: %w", sqliteErr) - } - - default: - return fmt.Errorf("unknown sqlite error: %w", sqliteErr) - } -} - -// parsePostgresError attempts to parse a postgres error as a database agnostic -// SQL error. -func parsePostgresError(pqErr *pgconn.PgError) error { - switch pqErr.Code { - // Handle unique constraint violation error. - case pgerrcode.UniqueViolation: - return &ErrSQLUniqueConstraintViolation{ - DBError: pqErr, - } - - // Unable to serialize the transaction, so we'll need to try again. - case pgerrcode.SerializationFailure: - return &ErrSerializationError{ - DBError: pqErr, - } - - // In failed SQL transaction because we didn't catch a previous - // serialization error, so return this one as a serialization error. - case pgerrcode.InFailedSQLTransaction: - return &ErrSerializationError{ - DBError: pqErr, - } - - // Deadlock detedted because of a serialization error, so return this - // one as a serialization error. - case pgerrcode.DeadlockDetected: - return &ErrSerializationError{ - DBError: pqErr, - } - - // Handle schema error. - case pgerrcode.UndefinedColumn, pgerrcode.UndefinedTable: - return &ErrSchemaError{ - DbError: pqErr, - } - - default: - return fmt.Errorf("unknown postgres error: %w", pqErr) - } -} - -// ErrSQLUniqueConstraintViolation is an error type which represents a database -// agnostic SQL unique constraint violation. -type ErrSQLUniqueConstraintViolation struct { - DBError error -} - -func (e ErrSQLUniqueConstraintViolation) Error() string { - return fmt.Sprintf("sql unique constraint violation: %v", e.DBError) -} - -// ErrSerializationError is an error type which represents a database agnostic -// error that a transaction couldn't be serialized with other concurrent db -// transactions. -type ErrSerializationError struct { - DBError error -} - -// Unwrap returns the wrapped error. -func (e ErrSerializationError) Unwrap() error { - return e.DBError -} - -// Error returns the error message. -func (e ErrSerializationError) Error() string { - return e.DBError.Error() -} - -// IsSerializationError returns true if the given error is a serialization -// error. -func IsSerializationError(err error) bool { - var serializationError *ErrSerializationError - return errors.As(err, &serializationError) -} - -// ErrDeadlockError is an error type which represents a database agnostic -// error where transactions have led to cyclic dependencies in lock acquisition. -type ErrDeadlockError struct { - DbError error -} - -// Unwrap returns the wrapped error. -func (e ErrDeadlockError) Unwrap() error { - return e.DbError -} - -// Error returns the error message. -func (e ErrDeadlockError) Error() string { - return e.DbError.Error() -} - -// IsDeadlockError returns true if the given error is a deadlock error. -func IsDeadlockError(err error) bool { - var deadlockError *ErrDeadlockError - return errors.As(err, &deadlockError) -} - -// IsSerializationOrDeadlockError returns true if the given error is either a -// deadlock error or a serialization error. -func IsSerializationOrDeadlockError(err error) bool { - return IsDeadlockError(err) || IsSerializationError(err) -} - -// ErrSchemaError is an error type which represents a database agnostic error -// that the schema of the database is incorrect for the given query. -type ErrSchemaError struct { - DbError error -} - -// Unwrap returns the wrapped error. -func (e ErrSchemaError) Unwrap() error { - return e.DbError -} - -// Error returns the error message. -func (e ErrSchemaError) Error() string { - return e.DbError.Error() -} - -// IsSchemaError returns true if the given error is a schema error. -func IsSchemaError(err error) bool { - var schemaError *ErrSchemaError - return errors.As(err, &schemaError) -} diff --git a/sqldb/v2/sqlerrors_no_sqlite.go b/sqldb/v2/sqlerrors_no_sqlite.go deleted file mode 100644 index bcc548d79..000000000 --- a/sqldb/v2/sqlerrors_no_sqlite.go +++ /dev/null @@ -1,137 +0,0 @@ -//go:build js || (windows && (arm || 386)) || (linux && (ppc64 || mips || mipsle || mips64)) - -package sqldb - -import ( - "errors" - "fmt" - - "github.com/jackc/pgerrcode" - "github.com/jackc/pgx/v5/pgconn" -) - -var ( - // ErrTxRetriesExceeded is returned when a transaction is retried more - // than the max allowed valued without a success. - ErrTxRetriesExceeded = errors.New("db tx retries exceeded") -) - -// MapSQLError attempts to interpret a given error as a database agnostic SQL -// error. -func MapSQLError(err error) error { - // Attempt to interpret the error as a postgres error. - var pqErr *pgconn.PgError - if errors.As(err, &pqErr) { - return parsePostgresError(pqErr) - } - - // Return original error if it could not be classified as a database - // specific error. - return err -} - -// parsePostgresError attempts to parse a postgres error as a database agnostic -// SQL error. -func parsePostgresError(pqErr *pgconn.PgError) error { - switch pqErr.Code { - // Handle unique constraint violation error. - case pgerrcode.UniqueViolation: - return &ErrSQLUniqueConstraintViolation{ - DBError: pqErr, - } - - // Unable to serialize the transaction, so we'll need to try again. - case pgerrcode.SerializationFailure: - return &ErrSerializationError{ - DBError: pqErr, - } - - // In failed SQL transaction because we didn't catch a previous - // serialization error, so return this one as a serialization error. - case pgerrcode.InFailedSQLTransaction: - return &ErrSerializationError{ - DBError: pqErr, - } - - // Deadlock detected because of a serialization error, so return this - // one as a serialization error. - case pgerrcode.DeadlockDetected: - return &ErrSerializationError{ - DBError: pqErr, - } - - // Handle schema error. - case pgerrcode.UndefinedColumn, pgerrcode.UndefinedTable: - return &ErrSchemaError{ - DBError: pqErr, - } - - default: - return fmt.Errorf("unknown postgres error: %w", pqErr) - } -} - -// ErrSQLUniqueConstraintViolation is an error type which represents a database -// agnostic SQL unique constraint violation. -type ErrSQLUniqueConstraintViolation struct { - DBError error -} - -func (e ErrSQLUniqueConstraintViolation) Error() string { - return fmt.Sprintf("sql unique constraint violation: %v", e.DBError) -} - -// ErrSerializationError is an error type which represents a database agnostic -// error that a transaction couldn't be serialized with other concurrent db -// transactions. -type ErrSerializationError struct { - DBError error -} - -// Unwrap returns the wrapped error. -func (e ErrSerializationError) Unwrap() error { - return e.DBError -} - -// Error returns the error message. -func (e ErrSerializationError) Error() string { - return e.DBError.Error() -} - -// IsSerializationError returns true if the given error is a serialization -// error. -func IsSerializationError(err error) bool { - var serializationError *ErrSerializationError - return errors.As(err, &serializationError) -} - -// IsSerializationOrDeadlockError returns true if the given error is either a -// deadlock error or a serialization error. -// -// DeadlockDetected errors are already mapped to ErrSerializationError above, -// so checking for serialization errors is sufficient on no-SQLite targets. -func IsSerializationOrDeadlockError(err error) bool { - return IsSerializationError(err) -} - -// ErrSchemaError is an error type which represents a database agnostic error -// that the schema of the database is incorrect for the given query. -type ErrSchemaError struct { - DBError error -} - -// Unwrap returns the wrapped error. -func (e ErrSchemaError) Unwrap() error { - return e.DBError -} - -// Error returns the error message. -func (e ErrSchemaError) Error() string { - return e.DBError.Error() -} - -// IsSchemaError returns true if the given error is a schema error. -func IsSchemaError(err error) bool { - var schemaError *ErrSchemaError - return errors.As(err, &schemaError) -} diff --git a/sqldb/v2/sqlite.go b/sqldb/v2/sqlite.go deleted file mode 100644 index 194565bbd..000000000 --- a/sqldb/v2/sqlite.go +++ /dev/null @@ -1,321 +0,0 @@ -//go:build !js && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)) - -package sqldb - -import ( - "database/sql" - "fmt" - "net/url" - "time" - - "github.com/golang-migrate/migrate/v4" - sqlite_migrate "github.com/golang-migrate/migrate/v4/database/sqlite" - "github.com/lightningnetwork/lnd/fn/v2" - _ "modernc.org/sqlite" // Register relevant drivers. -) - -const ( - // sqliteOptionPrefix is the string prefix sqlite uses to set various - // options. This is used in the following format: - // * sqliteOptionPrefix || option_name = option_value. - sqliteOptionPrefix = "_pragma" - - // sqliteTxLockImmediate is a dsn option used to ensure that write - // transactions are started immediately. - sqliteTxLockImmediate = "_txlock=immediate" -) - -var ( - // sqliteSchemaReplacements maps schema strings to their SQLite - // compatible replacements. Currently, no replacements are needed as our - // SQL schema definition files are designed for SQLite compatibility. - sqliteSchemaReplacements = map[string]string{} - - // Make sure SqliteStore implements the MigrationExecutor interface. - _ MigrationExecutor = (*SqliteStore)(nil) - - // Make sure SqliteStore implements the DB interface. - _ DB = (*SqliteStore)(nil) -) - -// pragmaOption holds a key-value pair for a SQLite pragma setting. -type pragmaOption struct { - name string - value string -} - -// SqliteStore is a database store implementation that uses a sqlite backend. -type SqliteStore struct { - DbPath string - - Config *SqliteConfig - - *BaseDB -} - -// NewSqliteStore attempts to open a new sqlite database based on the passed -// config. -func NewSqliteStore(cfg *SqliteConfig, dbPath string) (*SqliteStore, error) { - // The set of pragma options are accepted using query options. For now - // we only want to ensure that foreign key constraints are properly - // enforced. - pragmaOptions := []pragmaOption{ - { - name: "foreign_keys", - value: "on", - }, - { - name: "journal_mode", - value: "WAL", - }, - { - name: "busy_timeout", - value: fmt.Sprintf("%d", cfg.busyTimeoutMs()), - }, - { - // With the WAL mode, this ensures that we also do an - // extra WAL sync after each transaction. The normal - // sync mode skips this and gives better performance, - // but risks durability. - name: "synchronous", - value: "full", - }, - { - // This is used to ensure proper durability for users - // running on Mac OS. It uses the correct fsync system - // call to ensure items are fully flushed to disk. - name: "fullfsync", - value: "true", - }, - { - name: "auto_vacuum", - value: "incremental", - }, - } - sqliteOptions := make(url.Values) - for _, option := range pragmaOptions { - sqliteOptions.Add( - sqliteOptionPrefix, - fmt.Sprintf("%v=%v", option.name, option.value), - ) - } - - // Then we add any user specified pragma options. Note that these can - // be of the form: "key=value", "key(N)" or "key". - for _, option := range cfg.PragmaOptions { - sqliteOptions.Add(sqliteOptionPrefix, option) - } - - // Construct the DSN which is just the database file name, appended - // with the series of pragma options as a query URL string. For more - // details on the formatting here, see the modernc.org/sqlite docs: - // https://pkg.go.dev/modernc.org/sqlite#Driver.Open. - dsn := fmt.Sprintf( - "%v?%v&%v", dbPath, sqliteOptions.Encode(), - sqliteTxLockImmediate, - ) - db, err := sql.Open("sqlite", dsn) - if err != nil { - return nil, err - } - - // Create the migration tracker table before starting migrations to - // ensure it can be used to track migration progress. Note that a - // corresponding SQLC migration also creates this table, making this - // operation a no-op in that context. Its purpose is to ensure - // compatibility with SQLC query generation. - migrationTrackerSQL := ` - CREATE TABLE IF NOT EXISTS migration_tracker ( - version INTEGER UNIQUE NOT NULL, - migration_time TIMESTAMP NOT NULL - );` - - _, err = db.Exec(migrationTrackerSQL) - if err != nil { - return nil, fmt.Errorf("error creating migration tracker: %w", - err) - } - - connMaxLifetime := defaultConnMaxLifetime - if cfg.ConnMaxLifetime > 0 { - connMaxLifetime = cfg.ConnMaxLifetime - } - - db.SetMaxOpenConns(cfg.MaxConns()) - db.SetMaxIdleConns(cfg.MaxIdleConns()) - db.SetConnMaxLifetime(connMaxLifetime) - - s := &SqliteStore{ - Config: cfg, - DbPath: dbPath, - BaseDB: &BaseDB{ - DB: db, - BackendType: BackendTypeSqlite, - SkipMigrations: cfg.SkipMigrations, - }, - } - - return s, nil -} - -// GetBaseDB returns the underlying BaseDB instance for the SQLite store. -// It is a trivial helper method to comply with the sqldb.DB interface. -func (s *SqliteStore) GetBaseDB() *BaseDB { - return s.BaseDB -} - -func errSqliteMigration(err error) error { - return fmt.Errorf("error creating sqlite migration: %w", err) -} - -// backupSqliteDatabase creates a backup of the given SQLite database. -func backupSqliteDatabase(srcDB *sql.DB, dbFullFilePath string) error { - if srcDB == nil { - return fmt.Errorf("backup source database is nil") - } - - // Create a database backup file full path from the given source - // database full file path. - // - // Get the current time and format it as a Unix timestamp in - // nanoseconds. - timestamp := time.Now().UnixNano() - - // Add the timestamp to the backup name. - backupFullFilePath := fmt.Sprintf( - "%s.%d.backup", dbFullFilePath, timestamp, - ) - - log.Infof("Creating backup of database file: %v -> %v", - dbFullFilePath, backupFullFilePath) - - // Create the database backup. - vacuumIntoQuery := "VACUUM INTO ?;" - stmt, err := srcDB.Prepare(vacuumIntoQuery) - if err != nil { - return err - } - defer stmt.Close() - - _, err = stmt.Exec(backupFullFilePath) - if err != nil { - return err - } - - return nil -} - -// backupAndMigrate is a helper function that creates a database backup before -// initiating the migration, and then migrates the database to the latest -// version. -func (s *SqliteStore) backupAndMigrate(mig *migrate.Migrate, - currentDbVersion int, maxMigrationVersion uint) error { - - // Determine if a database migration is necessary given the current - // database version and the maximum migration version. - versionUpgradePending := currentDbVersion < int(maxMigrationVersion) - if !versionUpgradePending { - log.Infof("Current database version is up-to-date, skipping "+ - "migration attempt and backup creation "+ - "(current_db_version=%v, max_migration_version=%v)", - currentDbVersion, maxMigrationVersion) - return nil - } - - // At this point, we know that a database migration is necessary. - // Create a backup of the database before starting the migration. - if !s.Config.SkipMigrationDbBackup { - log.Infof("Creating database backup (before applying " + - "migration(s))") - - err := backupSqliteDatabase(s.DB, s.DbPath) - if err != nil { - return err - } - } else { - log.Infof("Skipping database backup creation before applying " + - "migration(s)") - } - - log.Infof("Applying migrations to database") - return mig.Up() -} - -// ExecuteMigrations runs migrations for the sqlite database using the default -// production migration target. -func (s *SqliteStore) ExecuteMigrations(set MigrationSet) error { - if s.SkipMigrations { - return nil - } - - return s.executeMigrations(s.backupAndMigrate, set) -} - -// executeMigrations runs migrations for the sqlite database, depending on the -// target given, either all migrations or up to a given version. -func (s *SqliteStore) executeMigrations(target MigrationTarget, - set MigrationSet) error { - - if err := set.validate(); err != nil { - return err - } - - driver, err := sqlite_migrate.WithInstance( - s.DB, &sqlite_migrate.Config{ - MigrationsTable: set.TrackingTableName, - }, - ) - if err != nil { - return errSqliteMigration(err) - } - - opts := &migrateOptions{ - latestVersion: fn.Some(set.LatestMigrationVersion), - } - - if set.MakeProgrammaticMigrations != nil { - postMigSteps, err := set.MakeProgrammaticMigrations(s.BaseDB) - if err != nil { - return errSqliteMigration(err) - } - opts.programmaticMigrs = postMigSteps - } - - // Populate the database with our set of schemas based on our embedded - // in-memory file system. - sqliteFS := newReplacerFS(set.SQLFiles, sqliteSchemaReplacements) - return applyMigrations( - sqliteFS, driver, set.SQLFileDirectory, "sqlite", target, opts, - ) -} - -// GetSchemaVersion returns the current schema version of the SQLite database. -func (s *SqliteStore) GetSchemaVersion() (int, bool, error) { - driver, err := sqlite_migrate.WithInstance( - s.DB, &sqlite_migrate.Config{}, - ) - if err != nil { - return 0, false, errSqliteMigration(err) - } - - version, dirty, err := driver.Version() - if err != nil { - return 0, dirty, err - } - - return version, dirty, nil -} - -// SetSchemaVersion sets the schema version of the SQLite database. -// -// NOTE: This alters the internal database schema tracker. USE WITH CAUTION!!! -func (s *SqliteStore) SetSchemaVersion(version int, dirty bool) error { - driver, err := sqlite_migrate.WithInstance( - s.DB, &sqlite_migrate.Config{}, - ) - if err != nil { - return errSqliteMigration(err) - } - - return driver.SetVersion(version, dirty) -} diff --git a/sqldb/v2/sqlite_internal_test.go b/sqldb/v2/sqlite_internal_test.go deleted file mode 100644 index 07fa24c66..000000000 --- a/sqldb/v2/sqlite_internal_test.go +++ /dev/null @@ -1,43 +0,0 @@ -//go:build !js && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)) - -package sqldb - -import ( - "errors" - "path/filepath" - "testing" - - "github.com/golang-migrate/migrate/v4" - "github.com/stretchr/testify/require" -) - -// TestSqliteProgrammaticMigrationError verifies that SQLite migration setup -// failures are attributed to the SQLite backend. -func TestSqliteProgrammaticMigrationError(t *testing.T) { - t.Parallel() - - store, err := NewSqliteStore( - &SqliteConfig{}, filepath.Join(t.TempDir(), "test.db"), - ) - require.NoError(t, err) - t.Cleanup(func() { - require.NoError(t, store.Close()) - }) - - boom := errors.New("boom") - err = store.ExecuteMigrations(MigrationSet{ - TrackingTableName: "migration_tracker", - LatestMigrationVersion: 1, - Descriptors: []MigrationDescriptor{{ - Name: "programmatic", - Version: 1, - }}, - MakeProgrammaticMigrations: func(*BaseDB) ( - map[uint]migrate.ProgrammaticMigrEntry, error) { - - return nil, boom - }, - }) - require.ErrorContains(t, err, "sqlite") - require.NotContains(t, err.Error(), "postgres") -} diff --git a/sqldb/v2/sqlite_test_utils.go b/sqldb/v2/sqlite_test_utils.go deleted file mode 100644 index 03ef833fe..000000000 --- a/sqldb/v2/sqlite_test_utils.go +++ /dev/null @@ -1,86 +0,0 @@ -//go:build !js && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)) - -package sqldb - -import ( - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - _ "modernc.org/sqlite" // Register relevant drivers. -) - -// NewTestSqliteDB is a helper function that creates an SQLite database for -// testing. -func NewTestSqliteDB(t testing.TB, sets []MigrationSet) *SqliteStore { - t.Helper() - - t.Logf("Creating new SQLite DB for testing") - - // TODO(roasbeef): if we pass :memory: for the file name, then we get - // an in mem version to speed up tests - dbFileName := filepath.Join(t.TempDir(), "tmp.db") - sqlDB, err := NewSqliteStore(&SqliteConfig{ - SkipMigrations: false, - }, dbFileName) - require.NoError(t, err) - - require.NoError(t, ApplyAllMigrations(sqlDB, sets)) - - t.Cleanup(func() { - require.NoError(t, sqlDB.DB.Close()) - }) - - return sqlDB -} - -// NewTestSqliteDBFromPath is a helper function that creates a SQLite database -// for testing from a given database file path. -func NewTestSqliteDBFromPath(t *testing.T, dbPath string, - sets []MigrationSet) *SqliteStore { - - t.Helper() - - t.Logf("Creating new SQLite DB for testing, using DB path %s", dbPath) - - sqlDB, err := NewSqliteStore(&SqliteConfig{ - SkipMigrations: false, - }, dbPath) - require.NoError(t, err) - - require.NoError(t, ApplyAllMigrations(sqlDB, sets)) - - t.Cleanup(func() { - require.NoError(t, sqlDB.DB.Close()) - }) - - return sqlDB -} - -// NewTestSqliteDBWithVersion is a helper function that creates an SQLite -// database for testing and migrates it to the given version. -func NewTestSqliteDBWithVersion(t *testing.T, set MigrationSet, - version uint) *SqliteStore { - - t.Helper() - - t.Logf("Creating new SQLite DB for testing, migrating to version %d", - version) - - // TODO(roasbeef): if we pass :memory: for the file name, then we get - // an in mem version to speed up tests - dbFileName := filepath.Join(t.TempDir(), "tmp.db") - sqlDB, err := NewSqliteStore(&SqliteConfig{ - SkipMigrations: true, - }, dbFileName) - require.NoError(t, err) - - err = sqlDB.executeMigrations(TargetVersion(version), set) - require.NoError(t, err) - - t.Cleanup(func() { - require.NoError(t, sqlDB.DB.Close()) - }) - - return sqlDB -} diff --git a/sqldb/v2/sqlutils.go b/sqldb/v2/sqlutils.go deleted file mode 100644 index c4bcd0e8c..000000000 --- a/sqldb/v2/sqlutils.go +++ /dev/null @@ -1,192 +0,0 @@ -package sqldb - -import ( - "database/sql" - "time" - - "github.com/lightningnetwork/lnd/fn/v2" - "golang.org/x/exp/constraints" -) - -var ( - // MaxValidSQLTime is the maximum valid time that can be rendered as a - // time string and can be used for comparisons in SQL. - MaxValidSQLTime = time.Date(9999, 12, 31, 23, 59, 59, 999999, time.UTC) -) - -// NoOpReset is a no-op function that can be used as a default -// reset function ExecTx calls. -var NoOpReset = func() {} - -// SQLInt16 turns a numerical integer type into the NullInt16 that sql/sqlc -// uses when an integer field can be permitted to be NULL. -// -// We use this constraints.Integer constraint here which maps to all signed and -// unsigned integer types. -func SQLInt16[T constraints.Integer](num T) sql.NullInt16 { - return sql.NullInt16{ - Int16: int16(num), - Valid: true, - } -} - -// SQLInt32 turns a numerical integer type into the NullInt32 that sql/sqlc -// uses when an integer field can be permitted to be NULL. -// -// We use this constraints.Integer constraint here which maps to all signed and -// unsigned integer types. -func SQLInt32[T constraints.Integer](num T) sql.NullInt32 { - return sql.NullInt32{ - Int32: int32(num), - Valid: true, - } -} - -// SQLPtrInt32 turns a pointer to a numerical integer type into the NullInt32 -// that sql/sqlc uses. -func SQLPtrInt32[T constraints.Integer](num *T) sql.NullInt32 { - if num == nil { - return sql.NullInt32{} - } - return sql.NullInt32{ - Int32: int32(*num), - Valid: true, - } -} - -// SqlOptInt32 turns an option of a numerical integer type into the NullInt32 -// that sql/sqlc uses when an integer field can be permitted to be NULL. -func SqlOptInt32[T constraints.Integer](num fn.Option[T]) sql.NullInt32 { - return fn.MapOptionZ(num, func(num T) sql.NullInt32 { - return sql.NullInt32{ - Int32: int32(num), - Valid: true, - } - }) -} - -// SQLInt64 turns a numerical integer type into the NullInt64 that sql/sqlc -// uses when an integer field can be permitted to be NULL. -// -// We use this constraints.Integer constraint here which maps to all signed and -// unsigned integer types. -func SQLInt64[T constraints.Integer](num T) sql.NullInt64 { - return sql.NullInt64{ - Int64: int64(num), - Valid: true, - } -} - -// SQLPtrInt64 turns a pointer to a numerical integer type into the NullInt64 -// that sql/sqlc uses. -func SQLPtrInt64[T constraints.Integer](num *T) sql.NullInt64 { - if num == nil { - return sql.NullInt64{} - } - return sql.NullInt64{ - Int64: int64(*num), - Valid: true, - } -} - -// SqlBool turns a boolean into the NullBool that sql/sqlc uses when a boolean -// field can be permitted to be NULL. -func SqlBool(b bool) sql.NullBool { - return sql.NullBool{ - Bool: b, - Valid: true, - } -} - -// SQLStr turns a string into the NullString that sql/sqlc uses when a string -// can be permitted to be NULL. -// -// NOTE: If the input string is empty, it returns a NullString with Valid set to -// false. If this is not the desired behavior, consider using SQLStrValid -// instead. -func SQLStr(s string) sql.NullString { - if s == "" { - return sql.NullString{} - } - - return sql.NullString{ - String: s, - Valid: true, - } -} - -// SQLStrValid turns a string into the NullString that sql/sqlc uses when a -// string can be permitted to be NULL. -// -// NOTE: Valid is always set to true, even if the input string is empty. -func SQLStrValid(s string) sql.NullString { - return sql.NullString{ - String: s, - Valid: true, - } -} - -// SQLTime turns a time.Time into the NullTime that sql/sqlc uses when a time -// can be permitted to be NULL. -func SQLTime(t time.Time) sql.NullTime { - return sql.NullTime{ - Time: t, - Valid: true, - } -} - -// ExtractSqlInt64 turns a NullInt64 into a numerical type. This can be useful -// when reading directly from the database, as this function handles extracting -// the inner value from the "option"-like struct. -func ExtractSqlInt64[T constraints.Integer](num sql.NullInt64) T { - return T(num.Int64) -} - -// ExtractSqlInt64Ptr turns a NullInt64 into a pointer to a numerical type. -func ExtractSqlInt64Ptr[T constraints.Integer](num sql.NullInt64) *T { - if !num.Valid { - return nil - } - val := T(num.Int64) - return &val -} - -// ExtractSqlInt32 turns a NullInt32 into a numerical type. This can be useful -// when reading directly from the database, as this function handles extracting -// the inner value from the "option"-like struct. -func ExtractSqlInt32[T constraints.Integer](num sql.NullInt32) T { - return T(num.Int32) -} - -// ExtractSqlInt32Ptr turns a NullInt32 into a pointer to a numerical type. -func ExtractSqlInt32Ptr[T constraints.Integer](num sql.NullInt32) *T { - if !num.Valid { - return nil - } - val := T(num.Int32) - return &val -} - -// ExtractOptSqlInt32 turns a NullInt32 into an option of a numerical type. -func ExtractOptSqlInt32[T constraints.Integer](num sql.NullInt32) fn.Option[T] { - if !num.Valid { - return fn.None[T]() - } - - result := T(num.Int32) - return fn.Some(result) -} - -// ExtractSqlInt16 turns a NullInt16 into a numerical type. This can be useful -// when reading directly from the database, as this function handles extracting -// the inner value from the "option"-like struct. -func ExtractSqlInt16[T constraints.Integer](num sql.NullInt16) T { - return T(num.Int16) -} - -// ExtractBool turns a NullBool into a boolean. This can be useful when reading -// directly from the database, as this function handles extracting the inner -// value from the "option"-like struct. -func ExtractBool(b sql.NullBool) bool { - return b.Bool -} diff --git a/sqldb/v2/test_postgres.go b/sqldb/v2/test_postgres.go deleted file mode 100644 index fc620466b..000000000 --- a/sqldb/v2/test_postgres.go +++ /dev/null @@ -1,30 +0,0 @@ -//go:build test_db_postgres && !js && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)) && !(netbsd || openbsd) - -package sqldb - -import ( - "testing" -) - -// NewTestDB is a helper function that creates a Postgres database for testing. -func NewTestDB(t *testing.T, sets []MigrationSet) *PostgresStore { - pgFixture := NewTestPgFixture(t, DefaultPostgresFixtureLifetime) - t.Cleanup(func() { - pgFixture.TearDown(t) - }) - - return NewTestPostgresDB(t, pgFixture, sets) -} - -// NewTestDBWithVersion is a helper function that creates a Postgres database -// for testing and migrates it to the given version. -func NewTestDBWithVersion(t *testing.T, set MigrationSet, - version uint) *PostgresStore { - - pgFixture := NewTestPgFixture(t, DefaultPostgresFixtureLifetime) - t.Cleanup(func() { - pgFixture.TearDown(t) - }) - - return NewTestPostgresDBWithVersion(t, pgFixture, set, version) -} diff --git a/sqldb/v2/test_sqlite.go b/sqldb/v2/test_sqlite.go deleted file mode 100644 index 1b493033f..000000000 --- a/sqldb/v2/test_sqlite.go +++ /dev/null @@ -1,20 +0,0 @@ -//go:build !test_db_postgres && !js && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)) - -package sqldb - -import ( - "testing" -) - -// NewTestDB is a helper function that creates an SQLite database for testing. -func NewTestDB(t *testing.T, sets []MigrationSet) *SqliteStore { - return NewTestSqliteDB(t, sets) -} - -// NewTestDBWithVersion is a helper function that creates an SQLite database -// for testing and migrates it to the given version. -func NewTestDBWithVersion(t *testing.T, set MigrationSet, - version uint) *SqliteStore { - - return NewTestSqliteDBWithVersion(t, set, version) -} diff --git a/subrpcserver_config.go b/subrpcserver_config.go index 8bd4b0270..d55d5a492 100644 --- a/subrpcserver_config.go +++ b/subrpcserver_config.go @@ -6,12 +6,12 @@ import ( "net" "reflect" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btclog/v2" "github.com/lightningnetwork/lnd/aliasmgr" "github.com/lightningnetwork/lnd/autopilot" "github.com/lightningnetwork/lnd/chainreg" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/htlcswitch" @@ -115,7 +115,7 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config, routerBackend *routerrpc.RouterBackend, nodeSigner *netann.NodeSigner, graphDB *graphdb.ChannelGraph, - chanStateDB chanstate.Store, + chanStateDB *channeldb.ChannelStateDB, sweeper *sweep.UtxoSweeper, tower *watchtower.Standalone, towerClientMgr *wtclient.Manager, @@ -265,9 +265,7 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config, reflect.ValueOf(defaultDelta), ) subCfgValue.FieldByName("Graph").Set( - reflect.ValueOf(graphdb.NewVersionedGraph( - graphDB, lnwire.GossipVersion1, - )), + reflect.ValueOf(graphDB), ) subCfgValue.FieldByName("ChanStateDB").Set( reflect.ValueOf(chanStateDB), diff --git a/sweep/aggregator.go b/sweep/aggregator.go index 1cb9d2953..e97ccb9a2 100644 --- a/sweep/aggregator.go +++ b/sweep/aggregator.go @@ -3,8 +3,8 @@ package sweep import ( "sort" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntypes" @@ -232,46 +232,12 @@ func (b *BudgetAggregator) filterInputs(inputs InputsMap) InputsMap { // https://github.com/lightning/bolts/blob/master/03-transactions.md#appendix-a-expected-weights wu := lntypes.VByte(input.InputSize).ToWU() + witnessSize - // If an aux sweeper is set, it may contribute an extra budget - // to any input set this input becomes part of. The input's own - // budget may be tiny (e.g. for custom channel outputs whose - // value is mostly carried off-chain), so without accounting - // for the extra budget here we'd filter such inputs out - // permanently, even though their input set could comfortably - // pay its fees. - // - // The AuxSweeper interface requires the contribution to be - // non-negative and additive across inputs, so a singleton - // call returns this input's share and per-input credits sum - // to the set-level total used at set construction. On a - // lookup error we fall back to zero extra budget rather than - // dropping the input, so a transient aux failure doesn't - // recreate the silently-stranded mode this guard is meant to - // avoid. - extraBudget, err := fn.MapOptionZ( - b.auxSweeper, - func(aux AuxSweeper) fn.Result[btcutil.Amount] { - return aux.ExtraBudgetForInputs( - []input.Input{pi.Input}, - ) - }, - ).Unpack() - if err != nil { - log.Errorf("Unable to fetch extra budget for "+ - "input=%v, falling back to own budget: %v", - op, err) - - extraBudget = 0 - } - - budget := pi.params.Budget + extraBudget - // Skip inputs that has too little budget. minFee := minFeeRate.FeeForWeight(wu) - if budget < minFee { + if pi.params.Budget < minFee { log.Warnf("Skipped input=%v: has budget=%v, but the "+ "min fee requires %v (feerate=%v), size=%v", op, - budget, minFee, + pi.params.Budget, minFee, minFeeRate.FeePerVByte(), wu.ToVB()) continue @@ -282,10 +248,10 @@ func (b *BudgetAggregator) filterInputs(inputs InputsMap) InputsMap { chainfee.SatPerKWeight(0), ) startingFee := startingFeeRate.FeeForWeight(wu) - if budget < startingFee { + if pi.params.Budget < startingFee { log.Errorf("Skipped input=%v: has budget=%v, but the "+ "starting fee requires %v (feerate=%v), "+ - "size=%v", op, budget, startingFee, + "size=%v", op, pi.params.Budget, startingFee, startingFeeRate.FeePerVByte(), wu.ToVB()) continue diff --git a/sweep/aggregator_test.go b/sweep/aggregator_test.go index 5659cf53c..2cb89bdc3 100644 --- a/sweep/aggregator_test.go +++ b/sweep/aggregator_test.go @@ -5,9 +5,9 @@ import ( "errors" "testing" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntypes" @@ -164,112 +164,6 @@ func TestBudgetAggregatorFilterInputs(t *testing.T) { require.Contains(t, result, opHigh) } -// TestBudgetAggregatorFilterInputsAuxBudget checks that the aux sweeper's -// extra budget is folded into the filter's budget check, and that an aux -// lookup failure falls back to gating on the input's own budget rather than -// silently dropping the input. -func TestBudgetAggregatorFilterInputsAuxBudget(t *testing.T) { - t.Parallel() - - const wu lntypes.WeightUnit = 100 - inpSize := lntypes.VByte(input.InputSize).ToWU() + wu - - const minFeeRate = chainfee.SatPerKWeight(1000) - minFee := minFeeRate.FeeForWeight(inpSize) - - // shortfall is how much the own budget falls short of minFee; the aux - // sweeper covers exactly this gap in the "rescue" cases. - const shortfall = btcutil.Amount(100) - auxErr := errors.New("aux failure") - - testCases := []struct { - name string - ownBudget btcutil.Amount - auxResult fn.Result[btcutil.Amount] - expectKept bool - }{ - { - // The input's own budget falls short of the min fee, - // but the aux sweeper contributes enough extra budget - // to clear it. Pre-fix this input would have been - // filtered out. - name: "aux budget rescues low-own-budget input", - ownBudget: minFee - shortfall, - auxResult: fn.Ok(shortfall), - expectKept: true, - }, - { - // The aux lookup errors but the input's own budget - // already covers the min fee, so the conservative - // fallback (extraBudget=0) keeps it in. Pre-fix this - // input would have been silently dropped. - name: "aux error keeps sufficient input", - ownBudget: minFee, - auxResult: fn.Err[btcutil.Amount](auxErr), - expectKept: true, - }, - { - // The aux lookup errors and the input cannot pay its - // own way, so it is correctly filtered. - name: "aux error drops below-min-fee input", - ownBudget: minFee - shortfall, - auxResult: fn.Err[btcutil.Amount](auxErr), - expectKept: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - estimator := &chainfee.MockEstimator{} - defer estimator.AssertExpectations(t) - estimator.On("RelayFeePerKW").Return(minFeeRate).Once() - - wt := &input.MockWitnessType{} - defer wt.AssertExpectations(t) - wt.On("SizeUpperBound").Return(wu, true, nil).Once() - - mockInput := &input.MockInput{} - defer mockInput.AssertExpectations(t) - op := wire.OutPoint{Hash: chainhash.Hash{1}} - mockInput.On("WitnessType").Return(wt) - mockInput.On("OutPoint").Return(op) - - // Stub RequiredTxOut unconditionally so a regression - // that lets the dropped case fall through to the dust - // check surfaces as a clean assertion failure rather - // than an unstubbed-mock panic. `Maybe()` is needed - // because the dropped case shouldn't actually reach - // this call. - mockInput.On("RequiredTxOut").Return(nil).Maybe() - - mockAux := &MockAuxSweeper{} - defer mockAux.AssertExpectations(t) - mockAux.On("ExtraBudgetForInputs").Return(tc.auxResult) - - inputs := InputsMap{ - op: &SweeperInput{ - Input: mockInput, - params: Params{Budget: tc.ownBudget}, - }, - } - - b := NewBudgetAggregator( - estimator, 0, - fn.Some[AuxSweeper](mockAux), - ) - result := b.filterInputs(inputs) - - if tc.expectKept { - require.Contains(t, result, op) - } else { - require.NotContains(t, result, op) - } - }) - } -} - // TestBudgetAggregatorSortInputs checks that inputs are sorted by based on // their budgets and force flag. func TestBudgetAggregatorSortInputs(t *testing.T) { @@ -476,6 +370,7 @@ func TestBudgetAggregatorCreateInputSets(t *testing.T) { // Iterate over the test cases. for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { // Setup the mocks. diff --git a/sweep/fee_bumper.go b/sweep/fee_bumper.go index 6088094ff..e0d5d7516 100644 --- a/sweep/fee_bumper.go +++ b/sweep/fee_bumper.go @@ -6,11 +6,11 @@ import ( "sync" "sync/atomic" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/chain" "github.com/lightningnetwork/lnd/chainio" "github.com/lightningnetwork/lnd/chainntnfs" diff --git a/sweep/fee_bumper_test.go b/sweep/fee_bumper_test.go index c2653d795..d697f906b 100644 --- a/sweep/fee_bumper_test.go +++ b/sweep/fee_bumper_test.go @@ -6,9 +6,9 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/chain" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/fn/v2" @@ -220,6 +220,7 @@ func TestBumpRequestMaxFeeRateAllowed(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { // Check the method under test. @@ -502,6 +503,7 @@ func TestCreateAndCheckTx(t *testing.T) { } for _, tc := range testCases { + tc := tc r := &monitorRecord{ req: tc.req, @@ -672,6 +674,7 @@ func TestCreateRBFCompliantTx(t *testing.T) { var requestCounter atomic.Uint64 for _, tc := range testCases { + tc := tc rid := requestCounter.Add(1) @@ -795,6 +798,7 @@ func TestTxPublisherBroadcast(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { tc.setupMock() @@ -927,6 +931,7 @@ func TestRemoveResult(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { requestID := tc.setupRecord() diff --git a/sweep/fee_function.go b/sweep/fee_function.go index 85ad62422..eb2ed4d6b 100644 --- a/sweep/fee_function.go +++ b/sweep/fee_function.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" - "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" diff --git a/sweep/fee_function_test.go b/sweep/fee_function_test.go index d97308444..a55ce79a7 100644 --- a/sweep/fee_function_test.go +++ b/sweep/fee_function_test.go @@ -258,6 +258,7 @@ func TestLinearFeeFunctionFeeRateAtPosition(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() diff --git a/sweep/interface.go b/sweep/interface.go index 98863e2e1..6c8c2cfad 100644 --- a/sweep/interface.go +++ b/sweep/interface.go @@ -1,9 +1,9 @@ package sweep import ( - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -88,13 +88,6 @@ type AuxSweeper interface { // should be allocated to sweep the given set of inputs. This can be // used to add extra funds to the sweep transaction, for example to // cover fees for additional outputs of custom channels. - // - // The returned amount must be non-negative, and the contribution - // must be additive across inputs: the result for a slice of inputs - // must equal the sum of the per-input results, so that callers may - // query the contribution of a single input by passing a singleton - // slice. The budget aggregator relies on this when pre-filtering - // inputs by their own budget plus their individual aux contribution. ExtraBudgetForInputs(inputs []input.Input) fn.Result[btcutil.Amount] // NotifyBroadcast is used to notify external callers of the broadcast diff --git a/sweep/mock_test.go b/sweep/mock_test.go index 88d63ef6e..e6e254e8e 100644 --- a/sweep/mock_test.go +++ b/sweep/mock_test.go @@ -1,9 +1,9 @@ package sweep import ( - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" diff --git a/sweep/store.go b/sweep/store.go index fa44830d0..e3e97908b 100644 --- a/sweep/store.go +++ b/sweep/store.go @@ -6,8 +6,8 @@ import ( "errors" "io" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/tlv" ) diff --git a/sweep/store_test.go b/sweep/store_test.go index f3868378c..e9d4db125 100644 --- a/sweep/store_test.go +++ b/sweep/store_test.go @@ -4,8 +4,8 @@ import ( "bytes" "testing" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/kvdb" "github.com/stretchr/testify/require" diff --git a/sweep/sweeper.go b/sweep/sweeper.go index 547a92c47..e2163f637 100644 --- a/sweep/sweeper.go +++ b/sweep/sweeper.go @@ -6,9 +6,9 @@ import ( "sync" "sync/atomic" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainio" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/fn/v2" @@ -747,6 +747,7 @@ func (s *UtxoSweeper) collector() { // those inputs will be removed from the wallet. func (s *UtxoSweeper) removeExclusiveGroup(group uint64, op wire.OutPoint) { for outpoint, input := range s.inputs { + outpoint := outpoint // Skip the input that caused the exclusive group to be removed. if outpoint == op { diff --git a/sweep/sweeper_test.go b/sweep/sweeper_test.go index 0d22a6dd7..d97fd9925 100644 --- a/sweep/sweeper_test.go +++ b/sweep/sweeper_test.go @@ -7,9 +7,9 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" @@ -1043,6 +1043,7 @@ func TestMonitorFeeBumpResult(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { // Setup the testing result channel. diff --git a/sweep/test_utils.go b/sweep/test_utils.go index ab46c3e92..bd4b91bee 100644 --- a/sweep/test_utils.go +++ b/sweep/test_utils.go @@ -6,8 +6,8 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" ) diff --git a/sweep/tx_input_set.go b/sweep/tx_input_set.go index 33f837894..7b533c232 100644 --- a/sweep/tx_input_set.go +++ b/sweep/tx_input_set.go @@ -5,9 +5,9 @@ import ( "math" "sort" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" diff --git a/sweep/tx_input_set_test.go b/sweep/tx_input_set_test.go index d2b222f64..159824878 100644 --- a/sweep/tx_input_set_test.go +++ b/sweep/tx_input_set_test.go @@ -5,9 +5,9 @@ import ( "math" "testing" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" diff --git a/sweep/txgenerator.go b/sweep/txgenerator.go index 0b8d78306..995949b15 100644 --- a/sweep/txgenerator.go +++ b/sweep/txgenerator.go @@ -7,9 +7,9 @@ import ( "strings" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" diff --git a/sweep/txgenerator_test.go b/sweep/txgenerator_test.go index 53e8ddb0b..3f2051646 100644 --- a/sweep/txgenerator_test.go +++ b/sweep/txgenerator_test.go @@ -3,8 +3,8 @@ package sweep import ( "testing" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/input" "github.com/stretchr/testify/require" ) @@ -134,6 +134,7 @@ func TestWeightEstimatorUnknownScript(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { testUnknownScriptInner( t, test.pkscript, test.expectFail, diff --git a/sweep/walletsweep.go b/sweep/walletsweep.go index 53771ff20..d814d3b3b 100644 --- a/sweep/walletsweep.go +++ b/sweep/walletsweep.go @@ -8,10 +8,9 @@ import ( "slices" "time" - "github.com/btcsuite/btcd/address/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/wtxmgr" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" @@ -210,7 +209,7 @@ type WalletSweepPackage struct { // paying to more than one specified address. type DeliveryAddr struct { // Addr is the address to pay to. - Addr address.Address + Addr btcutil.Address // Amt is the amount to pay to the given address. Amt btcutil.Amount @@ -224,7 +223,7 @@ type DeliveryAddr struct { // utxoSource and outputLeaser as sources for wallet funds. func CraftSweepAllTx(feeRate, maxFeeRate chainfee.SatPerKWeight, blockHeight uint32, deliveryAddrs []DeliveryAddr, - changeAddr address.Address, coinSelectLocker CoinSelectionLocker, + changeAddr btcutil.Address, coinSelectLocker CoinSelectionLocker, utxoSource UtxoSource, outputLeaser OutputLeaser, signer input.Signer, minConfs int32, selectUtxos fn.Set[wire.OutPoint]) (*WalletSweepPackage, error) { diff --git a/sweep/walletsweep_test.go b/sweep/walletsweep_test.go index 226551373..c7a5dfc22 100644 --- a/sweep/walletsweep_test.go +++ b/sweep/walletsweep_test.go @@ -7,10 +7,10 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/address/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/wtxmgr" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lntest/mock" @@ -126,6 +126,7 @@ func TestFeeEstimateInfo(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { // Setup the mockers if specified. @@ -219,7 +220,7 @@ var sweepScript = []byte{ 0xe, 0x6e, 0xf8, 0xef, } -var deliveryAddr = func() address.Address { +var deliveryAddr = func() btcutil.Address { _, addrs, _, err := txscript.ExtractPkScriptAddrs( sweepScript, &chaincfg.TestNet3Params, ) diff --git a/sweep/weight_estimator.go b/sweep/weight_estimator.go index 7e5fd3a33..429b36ee5 100644 --- a/sweep/weight_estimator.go +++ b/sweep/weight_estimator.go @@ -1,9 +1,9 @@ package sweep import ( - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwallet/chainfee" diff --git a/sweep/weight_estimator_test.go b/sweep/weight_estimator_test.go index 1ecedc957..513aa01cc 100644 --- a/sweep/weight_estimator_test.go +++ b/sweep/weight_estimator_test.go @@ -3,10 +3,10 @@ package sweep import ( "testing" - "github.com/btcsuite/btcd/address/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwallet/chainfee" @@ -129,7 +129,7 @@ func TestWeightEstimatorMaxFee(t *testing.T) { func TestWeightEstimatorAddOutput(t *testing.T) { testFeeRate := chainfee.SatPerKWeight(20000) - p2wkhAddr, err := address.NewAddressWitnessPubKeyHash( + p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash( make([]byte, 20), &chaincfg.MainNetParams, ) require.NoError(t, err) diff --git a/sweeper_wallet.go b/sweeper_wallet.go index 9f21637e2..7d429c135 100644 --- a/sweeper_wallet.go +++ b/sweeper_wallet.go @@ -1,7 +1,7 @@ package lnd import ( - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/lnwallet" ) diff --git a/ticker/go.mod b/ticker/go.mod index 51f1e267a..d78f913a3 100644 --- a/ticker/go.mod +++ b/ticker/go.mod @@ -1,3 +1,3 @@ module github.com/lightningnetwork/lnd/ticker -go 1.25.11 +go 1.24.11 diff --git a/tls_manager_test.go b/tls_manager_test.go index 9cb88c264..541b123c4 100644 --- a/tls_manager_test.go +++ b/tls_manager_test.go @@ -428,6 +428,7 @@ func TestGenerateCertPairWithPartialFiles(t *testing.T) { } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() diff --git a/tlv/go.mod b/tlv/go.mod index 80db594b4..dd1302d75 100644 --- a/tlv/go.mod +++ b/tlv/go.mod @@ -1,25 +1,25 @@ module github.com/lightningnetwork/lnd/tlv require ( - github.com/btcsuite/btcd/btcec/v2 v2.5.0 - github.com/btcsuite/btcd/wire/v2 v2.0.0 + github.com/btcsuite/btcd v0.24.2 + github.com/btcsuite/btcd/btcec/v2 v2.3.2 github.com/davecgh/go-spew v1.1.1 - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 github.com/lightningnetwork/lnd/fn/v2 v2.0.2 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.8.4 golang.org/x/exp v0.0.0-20231226003508-02704c960a9b ) require ( - github.com/btcsuite/btcd/chainhash/v2 v2.0.0 // indirect + github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect github.com/kr/pretty v0.3.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect - golang.org/x/crypto v0.40.0 // indirect + golang.org/x/crypto v0.37.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.35.0 // indirect + golang.org/x/sys v0.32.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) -go 1.25.11 +go 1.24.11 diff --git a/tlv/go.sum b/tlv/go.sum index a4dd456b1..e2005dc26 100644 --- a/tlv/go.sum +++ b/tlv/go.sum @@ -1,14 +1,15 @@ -github.com/btcsuite/btcd/btcec/v2 v2.5.0 h1:KioMXOWa76b86sTZZOmbzv/ldaQCmB8KFAyn5PbB8E8= -github.com/btcsuite/btcd/btcec/v2 v2.5.0/go.mod h1:+K/MYXcLBtHEQjRbjHuJChuybk4LCgjdjgRwil+e+Kk= -github.com/btcsuite/btcd/chainhash/v2 v2.0.0 h1:PMLlSloHJuEeB80XG9EjpXWNEKAZAMLl6YHZ6YsEuoA= -github.com/btcsuite/btcd/chainhash/v2 v2.0.0/go.mod h1:mKxcZ7oGTXE7IRV+sS9hP4EVBwc/SzfNR+52IsOP9j8= -github.com/btcsuite/btcd/wire/v2 v2.0.0 h1:mYSKzZZ0a1sK+aMhXzfDSVsSzRkWkU3x2U04TFRS2z8= -github.com/btcsuite/btcd/wire/v2 v2.0.0/go.mod h1:bGxkPkk8IiDvUo1D96wE03llBIk7p2MdWYRyAQwLmqM= +github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= +github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -24,16 +25,16 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4= golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/tlv/primitive.go b/tlv/primitive.go index 969d59033..fb8bb70c3 100644 --- a/tlv/primitive.go +++ b/tlv/primitive.go @@ -257,7 +257,7 @@ func EBytes33(w io.Writer, val interface{}, _ *[8]byte) error { // DBytes33 is a Decoder for 33-byte arrays. An error is returned if val is not // a *[33]byte. func DBytes33(r io.Reader, val interface{}, _ *[8]byte, l uint64) error { - if b, ok := val.(*[33]byte); ok && l == 33 { + if b, ok := val.(*[33]byte); ok { _, err := io.ReadFull(r, b[:]) return err } diff --git a/tlv/primitive_test.go b/tlv/primitive_test.go index c5565a8d5..ba84320e6 100644 --- a/tlv/primitive_test.go +++ b/tlv/primitive_test.go @@ -7,7 +7,6 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/tlv" - "github.com/stretchr/testify/require" ) var testPK, _ = btcec.ParsePubKey([]byte{0x02, @@ -254,89 +253,3 @@ func TestPrimitiveEncodings(t *testing.T) { prim, prim2) } } - -// TestPrimitiveWrongLength asserts that fixed-size primitive decoders fail -// with ErrTypeForDecoding when given an incorrect TLV length. -func TestPrimitiveWrongLength(t *testing.T) { - prim := primitive{ - u8: 0x01, - u16: 0x0201, - u32: 0x02000001, - u64: 0x0200000000000001, - b32: [32]byte{0x02, 0x01}, - b33: [33]byte{0x03, 0x01}, - b64: [64]byte{0x02, 0x01}, - pk: testPK, - boolean: true, - } - - type item struct { - enc fieldEncoder - dec fieldDecoder - } - - items := []item{ - { - fieldEncoder{&prim.u8, tlv.EUint8}, - fieldDecoder{new(byte), tlv.DUint8, 1}, - }, - { - fieldEncoder{&prim.u16, tlv.EUint16}, - fieldDecoder{new(uint16), tlv.DUint16, 2}, - }, - { - fieldEncoder{&prim.u32, tlv.EUint32}, - fieldDecoder{new(uint32), tlv.DUint32, 4}, - }, - { - fieldEncoder{&prim.u64, tlv.EUint64}, - fieldDecoder{new(uint64), tlv.DUint64, 8}, - }, - { - fieldEncoder{&prim.b32, tlv.EBytes32}, - fieldDecoder{new([32]byte), tlv.DBytes32, 32}, - }, - { - fieldEncoder{&prim.b33, tlv.EBytes33}, - fieldDecoder{new([33]byte), tlv.DBytes33, 33}, - }, - { - fieldEncoder{&prim.b64, tlv.EBytes64}, - fieldDecoder{new([64]byte), tlv.DBytes64, 64}, - }, - { - fieldEncoder{&prim.pk, tlv.EPubKey}, - fieldDecoder{new(*btcec.PublicKey), tlv.DPubKey, 33}, - }, - { - fieldEncoder{&prim.boolean, tlv.EBool}, - fieldDecoder{new(bool), tlv.DBool, 1}, - }, - } - - for _, it := range items { - var buf [8]byte - var b bytes.Buffer - err := it.enc.encoder(&b, it.enc.val, &buf) - require.NoError(t, err, "encode %T", it.enc.val) - data := b.Bytes() - - // Generate two wrong lengths: expected-1 (if >0) and - // expected+1. - wrongs := []uint64{it.dec.size + 1} - if it.dec.size > 0 { - wrongs = append(wrongs, it.dec.size-1) - } - - for _, l := range wrongs { - r := bytes.NewReader(data) - err := it.dec.decoder(r, it.dec.val, &buf, l) - require.ErrorAs( - t, err, &tlv.ErrTypeForDecoding{}, - "decoder %T should reject wrong length "+ - "%d (expected %d)", - it.dec.decoder, l, it.dec.size, - ) - } - } -} diff --git a/tlv/varint.go b/tlv/varint.go index 4bebd6fd6..38c7a7cd6 100644 --- a/tlv/varint.go +++ b/tlv/varint.go @@ -5,7 +5,7 @@ import ( "errors" "io" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" ) // ErrVarIntNotCanonical signals that the decoded varint was not minimally encoded. diff --git a/tools/.custom-gcl.yml b/tools/.custom-gcl.yml index f628e0d15..35895387a 100644 --- a/tools/.custom-gcl.yml +++ b/tools/.custom-gcl.yml @@ -1,15 +1,4 @@ -# We are using a custom linter which is built cloning the version of -# golangci-lint from source below(thats what `golangci-lint custom` does -# internally) and adding the custom linters defined in this file. This means -# that this is the defining version for the linter as a whole when it comes to -# linter rules for example. -# The version pinned in the tools `go.mod` file is just the version we use to -# build the custom linter which is different and does not affect the linter -# rules itself. Hence both versions should always be updated together. -# -# NOTE: We cannot use untagged versions here. Always update the linter in the -# tools `go.mod` file and here as well. -version: v2.4.0 +version: v1.64.5 plugins: - module: 'github.com/lightningnetwork/lnd/tools/linters' path: ./linters \ No newline at end of file diff --git a/tools/Dockerfile b/tools/Dockerfile index 95de054b2..7fa3270eb 100644 --- a/tools/Dockerfile +++ b/tools/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.26.4 +FROM golang:1.25.5 RUN apt-get update && apt-get install -y git ENV GOCACHE=/tmp/build/.cache @@ -10,7 +10,8 @@ RUN cd /tmp \ && mkdir -p /tmp/build/.cache \ && mkdir -p /tmp/build/.modcache \ && cd /tmp/tools \ - && CGO_ENABLED=0 go tool github.com/golangci/golangci-lint/v2/cmd/golangci-lint custom \ + && CGO_ENABLED=0 go install -trimpath github.com/golangci/golangci-lint/cmd/golangci-lint \ + && CGO_ENABLED=0 golangci-lint custom \ && mv ./custom-gcl /usr/local/bin/custom-gcl \ && chmod -R 777 /tmp/build/ \ && git config --global --add safe.directory /build diff --git a/tools/go.mod b/tools/go.mod index 8ef84de3a..5aa875a77 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -1,83 +1,71 @@ module github.com/lightningnetwork/lnd/tools -go 1.25.11 +go 1.24.11 require ( - 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect + github.com/btcsuite/btcd v0.24.2 + github.com/golangci/golangci-lint v1.64.5 + github.com/rinchsan/gosimports v0.1.5 +) + +require ( + 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.2 // indirect - codeberg.org/chavacava/garif v0.2.0 // indirect - dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect - dev.gaijin.team/go/golib v0.6.0 // indirect - github.com/4meepo/tagalign v1.4.3 // indirect - github.com/Abirdcfly/dupword v0.1.6 // indirect - github.com/AlwxSin/noinlineerr v1.0.5 // indirect - github.com/Antonboom/errname v1.1.0 // indirect - github.com/Antonboom/nilnil v1.1.0 // indirect - github.com/Antonboom/testifylint v1.6.1 // indirect - github.com/BurntSushi/toml v1.5.0 // indirect + github.com/4meepo/tagalign v1.4.1 // indirect + github.com/Abirdcfly/dupword v0.1.3 // indirect + github.com/Antonboom/errname v1.0.0 // indirect + github.com/Antonboom/nilnil v1.0.1 // indirect + github.com/Antonboom/testifylint v1.5.2 // indirect + github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect + github.com/Crocmagnon/fatcontext v0.7.1 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect - github.com/Masterminds/semver/v3 v3.3.1 // indirect - github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect + github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect github.com/aead/siphash v1.0.1 // indirect - github.com/alecthomas/chroma/v2 v2.20.0 // indirect github.com/alecthomas/go-check-sumtype v0.3.1 // indirect - github.com/alexkohler/nakedret/v2 v2.0.6 // indirect + github.com/alexkohler/nakedret/v2 v2.0.5 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect - github.com/alfatraining/structtag v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect - github.com/alingse/nilnesserr v0.2.0 // indirect - github.com/ashanbrown/forbidigo/v2 v2.1.0 // indirect - github.com/ashanbrown/makezero/v2 v2.0.1 // indirect - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/alingse/nilnesserr v0.1.2 // indirect + github.com/ashanbrown/forbidigo v1.6.0 // indirect + github.com/ashanbrown/makezero v1.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bkielbasa/cyclop v1.2.3 // indirect github.com/blizzy78/varnamelen v0.8.0 // indirect - github.com/bombsimon/wsl/v4 v4.7.0 // indirect - github.com/bombsimon/wsl/v5 v5.1.1 // indirect - github.com/breml/bidichk v0.3.3 // indirect - github.com/breml/errchkjson v0.4.1 // indirect - github.com/btcsuite/btcd v0.26.0 // indirect - github.com/btcsuite/btcd/address/v2 v2.0.0 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.5.0 // indirect - github.com/btcsuite/btcd/btcutil/v2 v2.0.0 // indirect - github.com/btcsuite/btcd/chaincfg/v2 v2.0.0 // indirect - github.com/btcsuite/btcd/chainhash/v2 v2.0.0 // indirect - github.com/btcsuite/btcd/txscript/v2 v2.0.0 // indirect - github.com/btcsuite/btcd/v2transport v1.0.1 // indirect - github.com/btcsuite/btcd/wire/v2 v2.0.0 // indirect - github.com/btcsuite/btclog v1.0.0 // indirect + github.com/bombsimon/wsl/v4 v4.5.0 // indirect + github.com/breml/bidichk v0.3.2 // indirect + github.com/breml/errchkjson v0.4.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.1.3 // indirect + github.com/btcsuite/btcd/btcutil v1.1.5 // indirect + github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect + github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect github.com/btcsuite/winsvc v1.0.0 // indirect - github.com/butuzov/ireturn v0.4.0 // indirect + github.com/butuzov/ireturn v0.3.1 // indirect github.com/butuzov/mirror v1.3.0 // indirect - github.com/catenacyber/perfsprint v0.9.1 // indirect - github.com/ccojocar/zxcvbn-go v1.0.4 // indirect + github.com/catenacyber/perfsprint v0.8.1 // indirect + github.com/ccojocar/zxcvbn-go v1.0.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charithe/durationcheck v0.0.10 // indirect - github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect - github.com/charmbracelet/lipgloss v1.1.0 // indirect - github.com/charmbracelet/x/ansi v0.8.0 // indirect - github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect - github.com/charmbracelet/x/term v0.2.1 // indirect - github.com/ckaznocha/intrange v0.3.1 // indirect + github.com/chavacava/garif v0.1.0 // indirect + github.com/ckaznocha/intrange v0.3.0 // indirect github.com/curioswitch/go-reassign v0.3.0 // indirect - github.com/daixiang0/gci v0.13.7 // indirect - github.com/dave/dst v0.27.3 // indirect + github.com/daixiang0/gci v0.13.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect - github.com/decred/dcrd/lru v1.1.3 // indirect + github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/decred/dcrd/lru v1.0.0 // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect - github.com/dlclark/regexp2 v1.11.5 // indirect github.com/ettle/strcase v0.2.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fatih/structtag v1.2.0 // indirect - github.com/firefart/nonamedreturns v1.0.6 // indirect + github.com/firefart/nonamedreturns v1.0.5 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/ghostiam/protogetter v0.3.15 // indirect - github.com/go-critic/go-critic v0.13.0 // indirect + github.com/ghostiam/protogetter v0.3.9 // indirect + github.com/go-critic/go-critic v0.12.0 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.2.0 // indirect @@ -85,26 +73,23 @@ require ( github.com/go-toolsmith/astp v1.1.0 // indirect github.com/go-toolsmith/strparse v1.1.0 // indirect github.com/go-toolsmith/typep v1.1.0 // indirect - github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/go-viper/mapstructure/v2 v2.3.0 // indirect github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gofrs/flock v0.12.1 // indirect - github.com/golang/snappy v1.0.0 // indirect - github.com/golangci/asciicheck v0.5.0 // indirect - github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect github.com/golangci/go-printf-func-name v0.1.0 // indirect github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect - github.com/golangci/golangci-lint/v2 v2.4.1-0.20250818164121-838684c5bc0c // indirect - github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 // indirect - github.com/golangci/misspell v0.7.0 // indirect - github.com/golangci/plugin-module-register v0.1.2 // indirect + github.com/golangci/misspell v0.6.0 // indirect + github.com/golangci/plugin-module-register v0.1.1 // indirect github.com/golangci/revgrep v0.8.0 // indirect - github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect - github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect - github.com/google/go-cmp v0.7.0 // indirect + github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/gordonklaus/ineffassign v0.1.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect - github.com/gostaticanalysis/comment v1.5.0 // indirect + github.com/gostaticanalysis/comment v1.4.2 // indirect github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect github.com/gostaticanalysis/nilerr v0.1.1 // indirect github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect @@ -113,130 +98,116 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jessevdk/go-flags v1.6.1 // indirect - github.com/jgautheron/goconst v1.8.2 // indirect + github.com/jessevdk/go-flags v1.4.0 // indirect + github.com/jgautheron/goconst v1.7.1 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect - github.com/jjti/go-spancheck v0.6.5 // indirect - github.com/jrick/logrotate v1.1.2 // indirect + github.com/jjti/go-spancheck v0.6.4 // indirect + github.com/jrick/logrotate v1.0.0 // indirect github.com/julz/importas v0.2.0 // indirect github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect - github.com/kcalvinalvin/anet v0.0.0-20251112173137-d8ddc1f6dbee // indirect - github.com/kisielk/errcheck v1.9.0 // indirect - github.com/kkHAIKE/contextcheck v1.1.6 // indirect - github.com/kkdai/bstream v1.0.0 // indirect + github.com/kisielk/errcheck v1.8.0 // indirect + github.com/kkHAIKE/contextcheck v1.1.5 // indirect + github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 // indirect github.com/kulti/thelper v0.6.3 // indirect - github.com/kunwardeep/paralleltest v1.0.14 // indirect + github.com/kunwardeep/paralleltest v1.0.10 // indirect github.com/lasiar/canonicalheader v1.1.2 // indirect - github.com/ldez/exptostd v0.4.4 // indirect - github.com/ldez/gomoddirectives v0.7.0 // indirect - github.com/ldez/grignotin v0.10.0 // indirect + github.com/ldez/exptostd v0.4.1 // indirect + github.com/ldez/gomoddirectives v0.6.1 // indirect + github.com/ldez/grignotin v0.9.0 // indirect github.com/ldez/tagliatelle v0.7.1 // indirect - github.com/ldez/usetesting v0.5.0 // indirect + github.com/ldez/usetesting v0.4.2 // indirect github.com/leonklingele/grouper v1.1.2 // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/macabu/inamedparam v0.2.0 // indirect + github.com/macabu/inamedparam v0.1.3 // indirect github.com/magiconair/properties v1.8.6 // indirect - github.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect - github.com/manuelarte/funcorder v0.5.0 // indirect github.com/maratori/testableexamples v1.0.0 // indirect github.com/maratori/testpackage v1.1.1 // indirect github.com/matoous/godox v1.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/mgechev/revive v1.11.0 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mgechev/revive v1.6.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moricho/tparallel v0.3.2 // indirect - github.com/muesli/termenv v0.16.0 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nakabonne/nestif v0.3.1 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.20.0 // indirect + github.com/nunnatsa/ginkgolinter v0.19.0 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/polyfloyd/go-errorlint v1.8.0 // indirect - github.com/prometheus/client_golang v1.23.2 // indirect - github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect - github.com/quasilyte/go-ruleguard v0.4.4 // indirect + github.com/polyfloyd/go-errorlint v1.7.1 // indirect + github.com/prometheus/client_golang v1.12.1 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect + github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect github.com/raeperd/recvcheck v0.2.0 // indirect - github.com/rinchsan/gosimports v0.3.8 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect - github.com/ryancurrah/gomodguard v1.4.1 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/ryancurrah/gomodguard v1.3.5 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect - github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect - github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect - github.com/securego/gosec/v2 v2.22.8 // indirect + github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect + github.com/securego/gosec/v2 v2.22.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect - github.com/sonatard/noctx v0.4.0 // indirect + github.com/sivchari/tenv v1.12.1 // indirect + github.com/sonatard/noctx v0.1.0 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect - github.com/spf13/afero v1.14.0 // indirect + github.com/spf13/afero v1.12.0 // indirect github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/cobra v1.9.1 // indirect + github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.7 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/viper v1.12.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect github.com/stretchr/objx v0.5.2 // indirect - github.com/stretchr/testify v1.11.1 // indirect + github.com/stretchr/testify v1.10.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect - github.com/tetafro/godot v1.5.1 // indirect - github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect - github.com/timonwong/loggercheck v0.11.0 // indirect - github.com/tomarrell/wrapcheck/v2 v2.11.0 // indirect + github.com/tdakkota/asciicheck v0.4.0 // indirect + github.com/tetafro/godot v1.4.20 // indirect + github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 // indirect + github.com/timonwong/loggercheck v0.10.1 // indirect + github.com/tomarrell/wrapcheck/v2 v2.10.0 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/ultraware/funlen v0.2.0 // indirect github.com/ultraware/whitespace v0.2.0 // indirect github.com/uudashr/gocognit v1.2.0 // indirect - github.com/uudashr/iface v1.4.1 // indirect - github.com/xen0n/gosmopolitan v1.3.0 // indirect - github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/uudashr/iface v1.3.1 // indirect + github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.3.0 // indirect github.com/ykadowak/zerologlint v0.1.5 // indirect gitlab.com/bosi/decorder v0.4.2 // indirect - go-simpler.org/musttag v0.14.0 // indirect - go-simpler.org/sloglint v0.11.1 // indirect - go.augendre.info/arangolint v0.2.0 // indirect - go.augendre.info/fatcontext v0.8.1 // indirect + go-simpler.org/musttag v0.13.0 // indirect + go-simpler.org/sloglint v0.9.0 // indirect + go.uber.org/atomic v1.7.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect - go.uber.org/multierr v1.10.0 // indirect - go.uber.org/zap v1.27.0 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/crypto v0.41.0 // indirect - golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.28.0 // indirect - golang.org/x/tools v0.36.0 // indirect - google.golang.org/protobuf v1.36.8 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/zap v1.24.0 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect + golang.org/x/mod v0.23.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect + golang.org/x/tools v0.30.0 // indirect + google.golang.org/protobuf v1.36.4 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - honnef.co/go/tools v0.6.1 // indirect - mvdan.cc/gofumpt v0.8.0 // indirect - mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 // indirect -) - -tool ( - github.com/btcsuite/btcd - // Once golangci-lint v2.4.1 update it here. - // Also don't forget to update the .custom-gcl.yml file. - github.com/golangci/golangci-lint/v2/cmd/golangci-lint - github.com/rinchsan/gosimports/cmd/gosimports + honnef.co/go/tools v0.6.0 // indirect + mvdan.cc/gofumpt v0.7.0 // indirect + mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect ) diff --git a/tools/go.sum b/tools/go.sum index 560d9fdfd..3fec23ed8 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -1,153 +1,189 @@ -4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A= -4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= +4d63.com/gocheckcompilerdirectives v1.2.1 h1:AHcMYuw56NPjq/2y615IGg2kYkBdTvOaojYCBcRE7MA= +4d63.com/gocheckcompilerdirectives v1.2.1/go.mod h1:yjDJSxmDTtIHHCqX0ufRYZDL6vQtMG7tJdKVeWwsqvs= 4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= 4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= -codeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY= -codeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ= -dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y= -dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI= -dev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo= -dev.gaijin.team/go/golib v0.6.0/go.mod h1:uY1mShx8Z/aNHWDyAkZTkX+uCi5PdX7KsG1eDQa2AVE= -github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8= -github.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c= -github.com/Abirdcfly/dupword v0.1.6 h1:qeL6u0442RPRe3mcaLcbaCi2/Y/hOcdtw6DE9odjz9c= -github.com/Abirdcfly/dupword v0.1.6/go.mod h1:s+BFMuL/I4YSiFv29snqyjwzDp4b65W2Kvy+PKzZ6cw= -github.com/AlwxSin/noinlineerr v1.0.5 h1:RUjt63wk1AYWTXtVXbSqemlbVTb23JOSRiNsshj7TbY= -github.com/AlwxSin/noinlineerr v1.0.5/go.mod h1:+QgkkoYrMH7RHvcdxdlI7vYYEdgeoFOVjU9sUhw/rQc= -github.com/Antonboom/errname v1.1.0 h1:A+ucvdpMwlo/myWrkHEUEBWc/xuXdud23S8tmTb/oAE= -github.com/Antonboom/errname v1.1.0/go.mod h1:O1NMrzgUcVBGIfi3xlVuvX8Q/VP/73sseCaAppfjqZw= -github.com/Antonboom/nilnil v1.1.0 h1:jGxJxjgYS3VUUtOTNk8Z1icwT5ESpLH/426fjmQG+ng= -github.com/Antonboom/nilnil v1.1.0/go.mod h1:b7sAlogQjFa1wV8jUW3o4PMzDVFLbTux+xnQdvzdcIE= -github.com/Antonboom/testifylint v1.6.1 h1:6ZSytkFWatT8mwZlmRCHkWz1gPi+q6UBSbieji2Gj/o= -github.com/Antonboom/testifylint v1.6.1/go.mod h1:k+nEkathI2NFjKO6HvwmSrbzUcQ6FAnbZV+ZRrnXPLI= -github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= -github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/4meepo/tagalign v1.4.1 h1:GYTu2FaPGOGb/xJalcqHeD4il5BiCywyEYZOA55P6J4= +github.com/4meepo/tagalign v1.4.1/go.mod h1:2H9Yu6sZ67hmuraFgfZkNcg5Py9Ch/Om9l2K/2W1qS4= +github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE= +github.com/Abirdcfly/dupword v0.1.3/go.mod h1:8VbB2t7e10KRNdwTVoxdBaxla6avbhGzb8sCTygUMhw= +github.com/Antonboom/errname v1.0.0 h1:oJOOWR07vS1kRusl6YRSlat7HFnb3mSfMl6sDMRoTBA= +github.com/Antonboom/errname v1.0.0/go.mod h1:gMOBFzK/vrTiXN9Oh+HFs+e6Ndl0eTFbtsRTSRdXyGI= +github.com/Antonboom/nilnil v1.0.1 h1:C3Tkm0KUxgfO4Duk3PM+ztPncTFlOf0b2qadmS0s4xs= +github.com/Antonboom/nilnil v1.0.1/go.mod h1:CH7pW2JsRNFgEh8B2UaPZTEPhCMuFowP/e8Udp9Nnb0= +github.com/Antonboom/testifylint v1.5.2 h1:4s3Xhuv5AvdIgbd8wOOEeo0uZG7PbDKQyKY5lGoQazk= +github.com/Antonboom/testifylint v1.5.2/go.mod h1:vxy8VJ0bc6NavlYqjZfmp6EfqXMtBgQ4+mhCojwC1P8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Crocmagnon/fatcontext v0.7.1 h1:SC/VIbRRZQeQWj/TcQBS6JmrXcfA+BU4OGSVUt54PjM= +github.com/Crocmagnon/fatcontext v0.7.1/go.mod h1:1wMvv3NXEBJucFGfwOJBxSVWcoIO6emV215SMkW9MFU= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= -github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= -github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4= -github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 h1:/fTUt5vmbkAcMBt4YQiuC23cV0kEsN1MVMNqeOW43cU= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0/go.mod h1:ONJg5sxcbsdQQ4pOW8TGdTidT2TMAUy/2Xhr8mrYaao= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= +github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw= -github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA= github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= -github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg= -github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -github.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ= -github.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alexkohler/nakedret/v2 v2.0.5 h1:fP5qLgtwbx9EJE8dGEERT02YwS8En4r9nnZ71RK+EVU= +github.com/alexkohler/nakedret/v2 v2.0.5/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= -github.com/alfatraining/structtag v1.0.0 h1:2qmcUqNcCoyVJ0up879K614L9PazjBSFruTB0GOFjCc= -github.com/alfatraining/structtag v1.0.0/go.mod h1:p3Xi5SwzTi+Ryj64DqjLWz7XurHxbGsq6y3ubePJPus= github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= -github.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEWd/w= -github.com/alingse/nilnesserr v0.2.0/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= -github.com/ashanbrown/forbidigo/v2 v2.1.0 h1:NAxZrWqNUQiDz19FKScQ/xvwzmij6BiOw3S0+QUQ+Hs= -github.com/ashanbrown/forbidigo/v2 v2.1.0/go.mod h1:0zZfdNAuZIL7rSComLGthgc/9/n2FqspBOH90xlCHdA= -github.com/ashanbrown/makezero/v2 v2.0.1 h1:r8GtKetWOgoJ4sLyUx97UTwyt2dO7WkGFHizn/Lo8TY= -github.com/ashanbrown/makezero/v2 v2.0.1/go.mod h1:kKU4IMxmYW1M4fiEHMb2vc5SFoPzXvgbMR9gIp5pjSw= -github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/alingse/nilnesserr v0.1.2 h1:Yf8Iwm3z2hUUrP4muWfW83DF4nE3r1xZ26fGWUKCZlo= +github.com/alingse/nilnesserr v0.1.2/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= +github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= +github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= +github.com/ashanbrown/makezero v1.2.0 h1:/2Lp1bypdmK9wDIq7uWBlDF1iMUpIIS4A+pF6C9IEUU= +github.com/ashanbrown/makezero v1.2.0/go.mod h1:dxlPhHbDMC6N6xICzFBSK+4njQDdK8euNO0qjQMtGY4= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo= github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= -github.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ= -github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg= -github.com/bombsimon/wsl/v5 v5.1.1 h1:cQg5KJf9FlctAH4cpL9vLKnziYknoCMCdqXl0wjl72Q= -github.com/bombsimon/wsl/v5 v5.1.1/go.mod h1:Gp8lD04z27wm3FANIUPZycXp+8huVsn0oxc+n4qfV9I= -github.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE= -github.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE= -github.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg= -github.com/breml/errchkjson v0.4.1/go.mod h1:a23OvR6Qvcl7DG/Z4o0el6BRAjKnaReoPQFciAl9U3s= -github.com/btcsuite/btcd v0.26.0 h1:yntnSshlG3+H7dTwIOR4LTFXDPojVBsFORBNN5y5c/c= -github.com/btcsuite/btcd v0.26.0/go.mod h1:7ft7+a/MoJHFouFopCb1zyiR9IWPlrcPVn6K/lJ1dcA= -github.com/btcsuite/btcd/address/v2 v2.0.0 h1:UVu8Hal6Siu4XastFe+JX5JkeBYONbDUIY5E+SVTs6I= -github.com/btcsuite/btcd/address/v2 v2.0.0/go.mod h1:htJK1AtaeK3bKNfZY63ep2oN8LbrI6qvmPGe1vekb3I= -github.com/btcsuite/btcd/btcec/v2 v2.5.0 h1:KioMXOWa76b86sTZZOmbzv/ldaQCmB8KFAyn5PbB8E8= -github.com/btcsuite/btcd/btcec/v2 v2.5.0/go.mod h1:+K/MYXcLBtHEQjRbjHuJChuybk4LCgjdjgRwil+e+Kk= -github.com/btcsuite/btcd/btcutil/v2 v2.0.0 h1:77pgf/4tjWaSBLdos8yiWVWL3rSphxWNqkLwcyONExA= -github.com/btcsuite/btcd/btcutil/v2 v2.0.0/go.mod h1:ZF8MMdsx1JGgvHJUanxbigekSO+8bN/ai34LBk/lg3c= -github.com/btcsuite/btcd/chaincfg/v2 v2.0.0 h1:M/RTtXfXA9odC1RUEOyZFXj/NXKVHPYZXVjb60xTOok= -github.com/btcsuite/btcd/chaincfg/v2 v2.0.0/go.mod h1:rHgHIXYYfn70m25a+BJ9f9z7VZAsTiDQGB2XYaippGQ= -github.com/btcsuite/btcd/chainhash/v2 v2.0.0 h1:PMLlSloHJuEeB80XG9EjpXWNEKAZAMLl6YHZ6YsEuoA= -github.com/btcsuite/btcd/chainhash/v2 v2.0.0/go.mod h1:mKxcZ7oGTXE7IRV+sS9hP4EVBwc/SzfNR+52IsOP9j8= -github.com/btcsuite/btcd/txscript/v2 v2.0.0 h1:pEmmHaC8eRx6KSB63zSVJD7qrit9/c9cLSrw++XrYP8= -github.com/btcsuite/btcd/txscript/v2 v2.0.0/go.mod h1:pZXabc11Xr9nz/18kXY3yErdAajYc3gi28Zqb3KqlFo= -github.com/btcsuite/btcd/v2transport v1.0.1 h1:pIyyyBCPwd087K3Wdb/9tIvUubAQdzTJghjPgzTQVsE= -github.com/btcsuite/btcd/v2transport v1.0.1/go.mod h1:N6H0HGSElVVJKntzaYHYVbW71DtWDLMw2yhwVRO3ZOE= -github.com/btcsuite/btcd/wire/v2 v2.0.0 h1:mYSKzZZ0a1sK+aMhXzfDSVsSzRkWkU3x2U04TFRS2z8= -github.com/btcsuite/btcd/wire/v2 v2.0.0/go.mod h1:bGxkPkk8IiDvUo1D96wE03llBIk7p2MdWYRyAQwLmqM= -github.com/btcsuite/btclog v1.0.0 h1:sEkpKJMmfGiyZjADwEIgB1NSwMyfdD1FB8v6+w1T0Ns= -github.com/btcsuite/btclog v1.0.0/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ= +github.com/bombsimon/wsl/v4 v4.5.0 h1:iZRsEvDdyhd2La0FVi5k6tYehpOR/R7qIUjmKk7N74A= +github.com/bombsimon/wsl/v4 v4.5.0/go.mod h1:NOQ3aLF4nD7N5YPXMruR6ZXDOAqLoM0GEpLwTdvmOSc= +github.com/breml/bidichk v0.3.2 h1:xV4flJ9V5xWTqxL+/PMFF6dtJPvZLPsyixAoPe8BGJs= +github.com/breml/bidichk v0.3.2/go.mod h1:VzFLBxuYtT23z5+iVkamXO386OB+/sVwZOpIj6zXGos= +github.com/breml/errchkjson v0.4.0 h1:gftf6uWZMtIa/Is3XJgibewBm2ksAQSY/kABDNFTAdk= +github.com/breml/errchkjson v0.4.0/go.mod h1:AuBOSTHyLSaaAFlWsRSuRBIroCh3eh7ZHh5YeelDIk8= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= +github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= +github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.3 h1:xM/n3yIhHAhHy04z4i43C8p4ehixJZMsnrVJkgl+MTE= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= +github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/butuzov/ireturn v0.4.0 h1:+s76bF/PfeKEdbG8b54aCocxXmi0wvYdOVsWxVO7n8E= -github.com/butuzov/ireturn v0.4.0/go.mod h1:ghI0FrCmap8pDWZwfPisFD1vEc56VKH4NpQUxDHta70= +github.com/butuzov/ireturn v0.3.1 h1:mFgbEI6m+9W8oP/oDdfA34dLisRFCj2G6o/yiI1yZrY= +github.com/butuzov/ireturn v0.3.1/go.mod h1:ZfRp+E7eJLC0NQmk1Nrm1LOrn/gQlOykv+cVPdiXH5M= github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= -github.com/catenacyber/perfsprint v0.9.1 h1:5LlTp4RwTooQjJCvGEFV6XksZvWE7wCOUvjD2z0vls0= -github.com/catenacyber/perfsprint v0.9.1/go.mod h1:q//VWC2fWbcdSLEY1R3l8n0zQCDPdE4IjZwyY1HMunM= -github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc= -github.com/ccojocar/zxcvbn-go v1.0.4/go.mod h1:3GxGX+rHmueTUMvm5ium7irpyjmm7ikxYFOSJB21Das= +github.com/catenacyber/perfsprint v0.8.1 h1:bGOHuzHe0IkoGeY831RW4aSlt1lPRd3WRAScSWOaV7E= +github.com/catenacyber/perfsprint v0.8.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= +github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= +github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= -github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= -github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= -github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= -github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= -github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= -github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= -github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= -github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= -github.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs= -github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= +github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/ckaznocha/intrange v0.3.0 h1:VqnxtK32pxgkhJgYQEeOArVidIPg+ahLP7WBOXZd5ZY= +github.com/ckaznocha/intrange v0.3.0/go.mod h1:+I/o2d2A1FBHgGELbGxzIcyd3/9l9DuwjM8FsbSS3Lo= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= -github.com/daixiang0/gci v0.13.7 h1:+0bG5eK9vlI08J+J/NWGbWPTNiXPG4WhNLJOkSxWITQ= -github.com/daixiang0/gci v0.13.7/go.mod h1:812WVN6JLFY9S6Tv76twqmNqevN0pa3SX3nih0brVzQ= -github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY= -github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc= -github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo= -github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= +github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c= +github.com/daixiang0/gci v0.13.5/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= -github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= -github.com/decred/dcrd/lru v1.1.3 h1:w9EAbvGLyzm6jTjF83UKuqZEiUtJmvRhQDOCEIvSuE0= -github.com/decred/dcrd/lru v1.1.3/go.mod h1:Tw0i0pJyiLEx/oZdHLe1Wdv/Y7EGzAX+sYftnmxBR4o= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/lru v1.0.0 h1:Kbsb1SFDsIlaupWPwsPp+dkxiBY1frcS07PCPgotKz8= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= -github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= -github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -github.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E= -github.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo= +github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= +github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -156,14 +192,24 @@ github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwV github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/ghostiam/protogetter v0.3.15 h1:1KF5sXel0HE48zh1/vn0Loiw25A9ApyseLzQuif1mLY= -github.com/ghostiam/protogetter v0.3.15/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= -github.com/go-critic/go-critic v0.13.0 h1:kJzM7wzltQasSUXtYyTl6UaPVySO6GkaR1thFnJ6afY= -github.com/go-critic/go-critic v0.13.0/go.mod h1:M/YeuJ3vOCQDnP2SU+ZhjgRzwzcBW87JqLpMJLrZDLI= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/ghostiam/protogetter v0.3.9 h1:j+zlLLWzqLay22Cz/aYwTHKQ88GE2DQ6GkWSYFOI4lQ= +github.com/ghostiam/protogetter v0.3.9/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= +github.com/go-critic/go-critic v0.12.0 h1:iLosHZuye812wnkEz1Xu3aBwn5ocCPfc9yqmFG9pa6w= +github.com/go-critic/go-critic v0.12.0/go.mod h1:DpE0P6OVc6JzVYzmM5gq5jMU31zLr4am5mB/VfFK64w= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= @@ -185,65 +231,101 @@ github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQi github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= +github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY= github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= -github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0= -github.com/golangci/asciicheck v0.5.0/go.mod h1:5RMNAInbNFw2krqN6ibBxN/zfRFa9S6tA1nPdM0l8qQ= -github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw= -github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= -github.com/golangci/golangci-lint/v2 v2.4.1-0.20250818164121-838684c5bc0c h1:1RFhewLhOV3AWgBOlLhjozyh+Q+AcYBmb4UOst1m7DI= -github.com/golangci/golangci-lint/v2 v2.4.1-0.20250818164121-838684c5bc0c/go.mod h1:UrZZ+4nTVngTvsTHDgyQjTKhtTrvGlnVg9V0Q4vC1JM= -github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 h1:AkK+w9FZBXlU/xUmBtSJN1+tAI4FIvy5WtnUnY8e4p8= -github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95/go.mod h1:k9mmcyWKSTMcPPvQUCfRWWQ9VHJ1U9Dc0R7kaXAgtnQ= -github.com/golangci/misspell v0.7.0 h1:4GOHr/T1lTW0hhR4tgaaV1WS/lJ+ncvYCoFKmqJsj0c= -github.com/golangci/misspell v0.7.0/go.mod h1:WZyyI2P3hxPY2UVHs3cS8YcllAeyfquQcKfdeE9AFVg= -github.com/golangci/plugin-module-register v0.1.2 h1:e5WM6PO6NIAEcij3B053CohVp3HIYbzSuP53UAYgOpg= -github.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw= +github.com/golangci/golangci-lint v1.64.5 h1:5omC86XFBKXZgCrVdUWU+WNHKd+CWCxNx717KXnzKZY= +github.com/golangci/golangci-lint v1.64.5/go.mod h1:WZnwq8TF0z61h3jLQ7Sk5trcP7b3kUFxLD6l1ivtdvU= +github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= +github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= +github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= +github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s= github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= -github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2bRA5htgAG9r7s3tHsfjIhN98WshBTJ9jM= -github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s= -github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM= -github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e/go.mod h1:h+wZwLjUTJnm/P2rwlbJdRPZXOzaT36/FwnPnY2inzc= +github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs= +github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a h1://KbezygeMJZCSHH+HgUZiTeSoiuFspbMg1ge+eFj18= -github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= +github.com/gostaticanalysis/comment v1.4.2 h1:hlnx5+S2fY9Zo9ePo4AhgYsYHbM2+eAv8m/s1JiCd6Q= github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= -github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8= -github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc= github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk= github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY= github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= @@ -258,6 +340,8 @@ github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -265,62 +349,72 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4= -github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc= -github.com/jgautheron/goconst v1.8.2 h1:y0XF7X8CikZ93fSNT6WBTb/NElBu9IjaY7CCYQrCMX4= -github.com/jgautheron/goconst v1.8.2/go.mod h1:A0oxgBCHy55NQn6sYpO7UdnA9p+h7cPtoOZUmvNIako= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk= +github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= -github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8= -github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU= -github.com/jrick/logrotate v1.1.2 h1:6ePk462NCX7TfKtNp5JJ7MbA2YIslkpfgP03TlTYMN0= -github.com/jrick/logrotate v1.1.2/go.mod h1:f9tdWggSVK3iqavGpyvegq5IhNois7KXmasU6/N96OQ= +github.com/jjti/go-spancheck v0.6.4 h1:Tl7gQpYf4/TMU7AT84MN83/6PutY21Nb9fuQjFTpRRc= +github.com/jjti/go-spancheck v0.6.4/go.mod h1:yAEYdKJ2lRkDA8g7X+oKUHXOWVAXSBJRv04OhF+QUjk= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= github.com/karamaru-alpha/copyloopvar v1.2.1 h1:wmZaZYIjnJ0b5UoKDjUHrikcV0zuPyyxI4SVplLd2CI= github.com/karamaru-alpha/copyloopvar v1.2.1/go.mod h1:nFmMlFNlClC2BPvNaHMdkirmTJxVCY0lhxBtlfOypMM= -github.com/kcalvinalvin/anet v0.0.0-20251112173137-d8ddc1f6dbee h1:FPP9HDkBbPyniu+u7FHZg+kKFX1WW0gxOGteJ0h3AJk= -github.com/kcalvinalvin/anet v0.0.0-20251112173137-d8ddc1f6dbee/go.mod h1:N6sz6HwJAenJ6d+/xmSl0ikfV05ZrVGmjt1ryy/WOtE= -github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M= -github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= -github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= -github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= -github.com/kkdai/bstream v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8= -github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA= +github.com/kisielk/errcheck v1.8.0 h1:ZX/URYa7ilESY19ik/vBmCn6zdGQLxACwjAcWbHlYlg= +github.com/kisielk/errcheck v1.8.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkHAIKE/contextcheck v1.1.5 h1:CdnJh63tcDe53vG+RebdpdXJTc9atMgGqdx8LXxiilg= +github.com/kkHAIKE/contextcheck v1.1.5/go.mod h1:O930cpht4xb1YQpK+1+AgoM3mFsvxr7uyFptcnWTYUA= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= -github.com/kunwardeep/paralleltest v1.0.14 h1:wAkMoMeGX/kGfhQBPODT/BL8XhK23ol/nuQ3SwFaUw8= -github.com/kunwardeep/paralleltest v1.0.14/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk= +github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCTdvWJ/lDDs= +github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= -github.com/ldez/exptostd v0.4.4 h1:58AtQjnLcT/tI5W/1KU7xE/O7zW9RAWB6c/ScQAnfus= -github.com/ldez/exptostd v0.4.4/go.mod h1:QfdzPw6oHjFVdNV7ILoPu5sw3OZ3OG1JS0I5JN3J4Js= -github.com/ldez/gomoddirectives v0.7.0 h1:EOx8Dd56BZYSez11LVgdj025lKwlP0/E5OLSl9HDwsY= -github.com/ldez/gomoddirectives v0.7.0/go.mod h1:wR4v8MN9J8kcwvrkzrx6sC9xe9Cp68gWYCsda5xvyGc= -github.com/ldez/grignotin v0.10.0 h1:NQPeh1E/Eza4F0exCeC1WkpnLvgUcQDT8MQ1vOLML0E= -github.com/ldez/grignotin v0.10.0/go.mod h1:oR4iCKUP9fwoeO6vCQeD7M5SMxCT6xdVas4vg0h1LaI= +github.com/ldez/exptostd v0.4.1 h1:DIollgQ3LWZMp3HJbSXsdE2giJxMfjyHj3eX4oiD6JU= +github.com/ldez/exptostd v0.4.1/go.mod h1:iZBRYaUmcW5jwCR3KROEZ1KivQQp6PHXbDPk9hqJKCQ= +github.com/ldez/gomoddirectives v0.6.1 h1:Z+PxGAY+217f/bSGjNZr/b2KTXcyYLgiWI6geMBN2Qc= +github.com/ldez/gomoddirectives v0.6.1/go.mod h1:cVBiu3AHR9V31em9u2kwfMKD43ayN5/XDgr+cdaFaKs= +github.com/ldez/grignotin v0.9.0 h1:MgOEmjZIVNn6p5wPaGp/0OKWyvq42KnzAt/DAb8O4Ow= +github.com/ldez/grignotin v0.9.0/go.mod h1:uaVTr0SoZ1KBii33c47O1M8Jp3OP3YDwhZCmzT9GHEk= github.com/ldez/tagliatelle v0.7.1 h1:bTgKjjc2sQcsgPiT902+aadvMjCeMHrY7ly2XKFORIk= github.com/ldez/tagliatelle v0.7.1/go.mod h1:3zjxUpsNB2aEZScWiZTHrAXOl1x25t3cRmzfK1mlo2I= -github.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc= -github.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ= +github.com/ldez/usetesting v0.4.2 h1:J2WwbrFGk3wx4cZwSMiCQQ00kjGR0+tuuyW0Lqm4lwA= +github.com/ldez/usetesting v0.4.2/go.mod h1:eEs46T3PpQ+9RgN9VjpY6qWdiw2/QmfiDeWmdZdrjIQ= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE= -github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U= +github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= +github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/manuelarte/embeddedstructfieldcheck v0.4.0 h1:3mAIyaGRtjK6EO9E73JlXLtiy7ha80b2ZVGyacxgfww= -github.com/manuelarte/embeddedstructfieldcheck v0.4.0/go.mod h1:z8dFSyXqp+fC6NLDSljRJeNQJJDWnY7RoWFzV3PC6UM= -github.com/manuelarte/funcorder v0.5.0 h1:llMuHXXbg7tD0i/LNw8vGnkDTHFpTnWqKPI85Rknc+8= -github.com/manuelarte/funcorder v0.5.0/go.mod h1:Yt3CiUQthSBMBxjShjdXMexmzpP8YGvGLjrxJNkO2hA= github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= @@ -333,40 +427,51 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mgechev/revive v1.11.0 h1:b/gLLpBE427o+Xmd8G58gSA+KtBwxWinH/A565Awh0w= -github.com/mgechev/revive v1.11.0/go.mod h1:tI0oLF/2uj+InHCBLrrqfTKfjtFTBCFFfG05auyzgdw= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mgechev/revive v1.6.1 h1:ncK0ZCMWtb8GXwVAmk+IeWF2ULIDsvRxSRfg5sTwQ2w= +github.com/mgechev/revive v1.6.1/go.mod h1:/2tfHWVO8UQi/hqJsIYNEKELi+DJy/e+PQpLgTB1v88= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= -github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= -github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.20.0 h1:OmWLkAFO2HUTYcU6mprnKud1Ey5pVdiVNYGO5HVicx8= -github.com/nunnatsa/ginkgolinter v0.20.0/go.mod h1:dCIuFlTPfQerXgGUju3VygfAFPdC5aE1mdacCDKDJcQ= +github.com/nunnatsa/ginkgolinter v0.19.0 h1:CnHRFAeBS3LdLI9h+Jidbcc5KH71GKOmaBZQk8Srnto= +github.com/nunnatsa/ginkgolinter v0.19.0/go.mod h1:jkQ3naZDmxaZMXPWaS9rblH+i+GWXQCaS/JFIWcOH2s= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= -github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= +github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= +github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.38.0 h1:c/WX+w8SLAinvuKKQFh77WEucCnPk4j2OTUr7lt7BeY= -github.com/onsi/gomega v1.38.0/go.mod h1:OcXcwId0b9QsE7Y49u+BTrL4IdKOBOKnD6VQNTJEB6o= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= @@ -376,24 +481,42 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9 github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v1.8.0 h1:DL4RestQqRLr8U4LygLw8g2DX6RN1eBJOpa2mzsrl1Q= -github.com/polyfloyd/go-errorlint v1.8.0/go.mod h1:G2W0Q5roxbLCt0ZQbdoxQxXktTjwNyDbEaj3n7jvl4s= +github.com/polyfloyd/go-errorlint v1.7.1 h1:RyLVXIbosq1gBdk/pChWA8zWYLsq9UEw7a1L5TVMCnA= +github.com/polyfloyd/go-errorlint v1.7.1/go.mod h1:aXjNb1x2TNhoLsk26iv1yl7a+zTnXPhwEMtEXukiLR8= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= -github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= -github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= -github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= -github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= -github.com/quasilyte/go-ruleguard v0.4.4 h1:53DncefIeLX3qEpjzlS1lyUmQoUEeOWPFWqaTJq9eAQ= -github.com/quasilyte/go-ruleguard v0.4.4/go.mod h1:Vl05zJ538vcEEwu16V/Hdu7IYZWyKSwIy4c88Ro1kRE= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 h1:+Wl/0aFp0hpuHM3H//KMft64WQ1yX9LdJY64Qm/gFCo= +github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= @@ -404,52 +527,55 @@ github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4l github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI= github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= -github.com/rinchsan/gosimports v0.3.8 h1:X4Pb9yFf6teHvogorT04yK/0W2Df7eHO79biCcYrA4c= -github.com/rinchsan/gosimports v0.3.8/go.mod h1:t0567k69sUHjLvJMPDsV31THZC+8UIbY1oL7NW+0I2c= +github.com/rinchsan/gosimports v0.1.5 h1:Z/l9lS79z0xgKC6fLJYmDdY44D0LFwo3MzaMtWvMKpY= +github.com/rinchsan/gosimports v0.1.5/go.mod h1:102/jU2cwf9fpa/YM9D9o4gSen2Vg8Jl80Sxctgd9N0= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g= -github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I= +github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV3oJmPU= +github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE= github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= -github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ= -github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8= -github.com/securego/gosec/v2 v2.22.8 h1:3NMpmfXO8wAVFZPNsd3EscOTa32Jyo6FLLlW53bexMI= -github.com/securego/gosec/v2 v2.22.8/go.mod h1:ZAw8K2ikuH9qDlfdV87JmNghnVfKB1XC7+TVzk6Utto= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sashamelentyev/usestdlibvars v1.28.0 h1:jZnudE2zKCtYlGzLVreNp5pmCdOxXUzwsMDBkR21cyQ= +github.com/sashamelentyev/usestdlibvars v1.28.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= +github.com/securego/gosec/v2 v2.22.1 h1:IcBt3TpI5Y9VN1YlwjSpM2cHu0i3Iw52QM+PQeg7jN8= +github.com/securego/gosec/v2 v2.22.1/go.mod h1:4bb95X4Jz7VSEPdVjC0hD7C/yR6kdeUBvCPOy9gDQ0g= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= -github.com/sonatard/noctx v0.4.0 h1:7MC/5Gg4SQ4lhLYR6mvOP6mQVSxCrdyiExo7atBs27o= -github.com/sonatard/noctx v0.4.0/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas= +github.com/sivchari/tenv v1.12.1 h1:+E0QzjktdnExv/wwsnnyk4oqZBUfuh89YMQT1cyuvSY= +github.com/sivchari/tenv v1.12.1/go.mod h1:1LjSOUCc25snIr5n3DtGGrENhX3LuWefcplwVGC24mw= +github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM= +github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c= github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= -github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= -github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= -github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= @@ -457,30 +583,39 @@ github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRk github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4= github.com/stbenjam/no-sprintf-host-port v0.2.0/go.mod h1:eL0bQ9PasS0hsyTyfTjjG+E80QIyPnBVQbYZyv20Jfk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tdakkota/asciicheck v0.4.0 h1:VZ13Itw4k1i7d+dpDSNS8Op645XgGHpkCEh/WHicgWw= +github.com/tdakkota/asciicheck v0.4.0/go.mod h1:0k7M3rCfRXb0Z6bwgvkEIMleKH3kXNz9UqJ9Xuqopr8= github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= -github.com/tetafro/godot v1.5.1 h1:PZnjCol4+FqaEzvZg5+O8IY2P3hfY9JzRBNPv1pEDS4= -github.com/tetafro/godot v1.5.1/go.mod h1:cCdPtEndkmqqrhiCfkmxDodMQJ/f3L1BCNskCUZdTwk= -github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk= -github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= -github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M= -github.com/timonwong/loggercheck v0.11.0/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= -github.com/tomarrell/wrapcheck/v2 v2.11.0 h1:BJSt36snX9+4WTIXeJ7nvHBQBcm1h2SjQMSlmQ6aFSU= -github.com/tomarrell/wrapcheck/v2 v2.11.0/go.mod h1:wFL9pDWDAbXhhPZZt+nG8Fu+h29TtnZ2MW6Lx4BRXIU= +github.com/tetafro/godot v1.4.20 h1:z/p8Ek55UdNvzt4TFn2zx2KscpW4rWqcnUrdmvWJj7E= +github.com/tetafro/godot v1.4.20/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= +github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 h1:y4mJRFlM6fUyPhoXuFg/Yu02fg/nIPFMOY8tOqppoFg= +github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= +github.com/timonwong/loggercheck v0.10.1 h1:uVZYClxQFpw55eh+PIoqM7uAOHMrhVcDoWDery9R8Lg= +github.com/timonwong/loggercheck v0.10.1/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= +github.com/tomarrell/wrapcheck/v2 v2.10.0 h1:SzRCryzy4IrAH7bVGG4cK40tNUhmVmMDuJujy4XwYDg= +github.com/tomarrell/wrapcheck/v2 v2.10.0/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI= @@ -489,12 +624,10 @@ github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSW github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA= github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU= -github.com/uudashr/iface v1.4.1 h1:J16Xl1wyNX9ofhpHmQ9h9gk5rnv2A6lX/2+APLTo0zU= -github.com/uudashr/iface v1.4.1/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg= -github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM= -github.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/uudashr/iface v1.3.1 h1:bA51vmVx1UIhiIsQFSNq6GZ6VPTk3WNMZgRiCe9R29U= +github.com/uudashr/iface v1.3.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg= +github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= +github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= @@ -502,6 +635,7 @@ github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+ github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -511,87 +645,190 @@ gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= -go-simpler.org/musttag v0.14.0 h1:XGySZATqQYSEV3/YTy+iX+aofbZZllJaqwFWs+RTtSo= -go-simpler.org/musttag v0.14.0/go.mod h1:uP8EymctQjJ4Z1kUnjX0u2l60WfUdQxCwSNKzE1JEOE= -go-simpler.org/sloglint v0.11.1 h1:xRbPepLT/MHPTCA6TS/wNfZrDzkGvCCqUv4Bdwc3H7s= -go-simpler.org/sloglint v0.11.1/go.mod h1:2PowwiCOK8mjiF+0KGifVOT8ZsCNiFzvfyJeJOIt8MQ= -go.augendre.info/arangolint v0.2.0 h1:2NP/XudpPmfBhQKX4rMk+zDYIj//qbt4hfZmSSTcpj8= -go.augendre.info/arangolint v0.2.0/go.mod h1:Vx4KSJwu48tkE+8uxuf0cbBnAPgnt8O1KWiT7bljq7w= -go.augendre.info/fatcontext v0.8.1 h1:/T4+cCjpL9g71gJpcFAgVo/K5VFpqlN+NPU7QXxD5+A= -go.augendre.info/fatcontext v0.8.1/go.mod h1:r3Qz4ZOzex66wfyyj5VZ1xUcl81vzvHQ6/GWzzlMEwA= +go-simpler.org/musttag v0.13.0 h1:Q/YAW0AHvaoaIbsPj3bvEI5/QFP7w696IMUpnKXQfCE= +go-simpler.org/musttag v0.13.0/go.mod h1:FTzIGeK6OkKlUDVpj0iQUXZLUO1Js9+mvykDQy9C5yM= +go-simpler.org/sloglint v0.9.0 h1:/40NQtjRx9txvsB/RN022KsUJU+zaaSb/9q9BSefSrE= +go-simpler.org/sloglint v0.9.0/go.mod h1:G/OrAF6uxj48sHahCzrbarVMptL2kjWTaUeC8+fOGww= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b h1:KdrhdYPDUvJTvrDK9gdjfFd6JTk8vA1WJoldYSi0kHo= -golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b/go.mod h1:LKZHyeOpPuZcMgxeHjJp4p5yvxrCX1xDvH10zYHhjjQ= +golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac h1:TSSpLIG4v+p0rPv1pNOQtl1I8knsO4S9trOxNMOLVP4= +golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -599,91 +836,224 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= -golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= -golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= -golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= -golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= +google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= -honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= -mvdan.cc/gofumpt v0.8.0 h1:nZUCeC2ViFaerTcYKstMmfysj6uhQrA2vJe+2vwGU6k= -mvdan.cc/gofumpt v0.8.0/go.mod h1:vEYnSzyGPmjvFkqJWtXkh79UwPWP9/HMxQdGEXZHjpg= -mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 h1:WjUu4yQoT5BHT1w8Zu56SP8367OuBV5jvo+4Ulppyf8= -mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4/go.mod h1:rthT7OuvRbaGcd5ginj6dA2oLE7YNlta9qhBNNdCaLE= -pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= -pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.6.0 h1:TAODvD3knlq75WCp2nyGJtT4LeRV/o7NN9nYPeVJXf8= +honnef.co/go/tools v0.6.0/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= +mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= +mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= +mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U= +mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f/go.mod h1:RSLa7mKKCNeTTMHBw5Hsy2rfJmd6O2ivt9Dw9ZqCQpQ= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/tools/linters/go.mod b/tools/linters/go.mod index 6f3b31288..3ee38851f 100644 --- a/tools/linters/go.mod +++ b/tools/linters/go.mod @@ -1,6 +1,6 @@ module github.com/lightningnetwork/lnd/tools/linters -go 1.25.11 +go 1.24.11 require ( github.com/golangci/plugin-module-register v0.1.1 diff --git a/tools/tools.go b/tools/tools.go new file mode 100644 index 000000000..dfa1260a5 --- /dev/null +++ b/tools/tools.go @@ -0,0 +1,14 @@ +//go:build tools +// +build tools + +package lnd + +// The other imports represent our build tools. Instead of defining a commit we +// want to use for those golang based tools, we use the go mod versioning system +// to unify the way we manage dependencies. So we define our build tool +// dependencies here and pin the version in go.mod. +import ( + _ "github.com/btcsuite/btcd" + _ "github.com/golangci/golangci-lint/cmd/golangci-lint" + _ "github.com/rinchsan/gosimports/cmd/gosimports" +) diff --git a/tor/README.md b/tor/README.md index 41fc8d891..f23ccf608 100644 --- a/tor/README.md +++ b/tor/README.md @@ -9,10 +9,10 @@ Tor daemon. So far, supported functions include: * Limited Tor Control functionality (synchronous messages only). So far, this includes: * Support for SAFECOOKIE, HASHEDPASSWORD, and NULL authentication methods. - * Creating v3 onion services. + * Creating v2 and v3 onion services. -In the future, the Tor Control functionality will be extended to support -asynchronous messages, etc. +In the future, the Tor Control functionality will be extended to support v3 +onion services, asynchronous messages, etc. ## Installation and Updating diff --git a/tor/cmd_onion.go b/tor/cmd_onion.go index ac43e2c75..6e60504a9 100644 --- a/tor/cmd_onion.go +++ b/tor/cmd_onion.go @@ -19,22 +19,22 @@ var ( // ErrNoPrivateKey is an error returned by the OnionStore.PrivateKey // method when a private key hasn't yet been stored. ErrNoPrivateKey = errors.New("private key not found") +) - // ErrNonV3OnionKey is returned when a restored onion private key is - // not a v3 (ED25519-V3) key. lnd no longer creates or recovers v2 - // onion services; users with an old v2 key file must remove it so - // a fresh v3 service can be generated. - ErrNonV3OnionKey = errors.New("restored onion private key is not a " + - "v3 (ED25519-V3) key; remove the old onion key file to " + - "generate a new v3 service") +// OnionType denotes the type of the onion service. +type OnionType int + +const ( + // V2 denotes that the onion service is V2. + V2 OnionType = iota + + // V3 denotes that the onion service is V3. + V3 ) const ( - // v2KeyParam is the parameter Tor used for legacy v2 onion service - // private keys. lnd no longer generates or restores v2 services, but - // the prefix is still used to detect stale on-disk keys and surface a - // clear error. - v2KeyParam = "RSA1024" + // V2KeyParam is a parameter that Tor accepts for a new V2 service. + V2KeyParam = "RSA1024" // V3KeyParam is a parameter that Tor accepts for a new V3 service. V3KeyParam = "ED25519-V3" @@ -128,17 +128,12 @@ func (f *OnionFile) PrivateKey() ([]byte, error) { return nil, err } - // If the privateKey starts with the v3 key param then it's likely - // not encrypted and we can return the data as is. - if bytes.HasPrefix(privateKeyContent, []byte(V3KeyParam)) { - return privateKeyContent, nil - } + // If the privateKey starts with either v2 or v3 key params then + // it's likely not encrypted and we can return the data as is. + if bytes.HasPrefix(privateKeyContent, []byte(V2KeyParam)) || + bytes.HasPrefix(privateKeyContent, []byte(V3KeyParam)) { - // A plaintext legacy v2 (RSA1024) key on disk is unsupported. - // Surface a dedicated error so the user can act on it instead of - // being redirected to --tor.encryptkey. - if bytes.HasPrefix(privateKeyContent, []byte(v2KeyParam)) { - return nil, ErrNonV3OnionKey + return privateKeyContent, nil } // If the privateKeyContent is encrypted but --tor.encryptkey @@ -156,13 +151,6 @@ func (f *OnionFile) PrivateKey() ([]byte, error) { return nil, err } - // Run the same validator over the decrypted contents so an encrypted - // legacy key is rejected with a clear error rather than being passed - // to Tor. - if !bytes.HasPrefix(privateKeyContent, []byte(V3KeyParam)) { - return nil, ErrNonV3OnionKey - } - return privateKeyContent, nil } @@ -174,6 +162,9 @@ func (f *OnionFile) DeletePrivateKey() error { // AddOnionConfig houses all of the required parameters in order to // successfully create a new onion service or restore an existing one. type AddOnionConfig struct { + // Type denotes the type of the onion service that should be created. + Type OnionType + // VirtualPort is the externally reachable port of the onion address. VirtualPort int @@ -201,7 +192,14 @@ func (c *Controller) prepareKeyparam(cfg AddOnionConfig) (string, error) { // create a new onion service and return its private key. Otherwise, // we'll request the server to recreate the onion server from our // private key. - keyParam := "NEW:" + V3KeyParam + var keyParam string + switch cfg.Type { + // TODO(yy): drop support for v2. + case V2: + keyParam = "NEW:" + V2KeyParam + case V3: + keyParam = "NEW:" + V3KeyParam + } if cfg.Store != nil { privateKey, err := cfg.Store.PrivateKey() @@ -210,16 +208,7 @@ func (c *Controller) prepareKeyparam(cfg AddOnionConfig) (string, error) { case ErrNoPrivateKey: // Recover the onion service with the private key found. - // Refuse to hand a non-v3 key (for example, a legacy - // RSA1024:... v2 key) to Tor; the caller must remove the old - // key file rather than silently regenerate a fresh v3 - // service, which would change the advertised onion identity. case nil: - if !bytes.HasPrefix( - privateKey, []byte(V3KeyParam+":"), - ) { - return "", ErrNonV3OnionKey - } keyParam = string(privateKey) default: @@ -284,9 +273,13 @@ func (c *Controller) prepareAddOnion(cfg AddOnionConfig) (string, string, // creating new service via `ADD_ONION`. func (c *Controller) AddOnion(cfg AddOnionConfig) (*OnionAddr, error) { // Before sending the request to create an onion service to the Tor - // server, we'll make sure that it supports V3 onion services. - if err := supportsV3(c.version); err != nil { - return nil, err + // server, we'll make sure that it supports V3 onion services if that + // was the type requested. + // TODO(yy): drop support for v2. + if cfg.Type == V3 { + if err := supportsV3(c.version); err != nil { + return nil, err + } } // Construct the cmd command. @@ -305,13 +298,13 @@ func (c *Controller) AddOnion(cfg AddOnionConfig) (*OnionAddr, error) { // If successful, the reply from the server should be of the following // format, depending on whether a private key has been requested: // - // C: ADD_ONION ED25519-V3:[Blob Redacted] Port=80,8080 - // S: 250-ServiceID=<56-char-v3-service-id> + // C: ADD_ONION RSA1024:[Blob Redacted] Port=80,8080 + // S: 250-ServiceID=testonion1234567 // S: 250 OK // - // C: ADD_ONION NEW:ED25519-V3 Port=80,8080 - // S: 250-ServiceID=<56-char-v3-service-id> - // S: 250-PrivateKey=ED25519-V3:[Blob Redacted] + // C: ADD_ONION NEW:RSA1024 Port=80,8080 + // S: 250-ServiceID=testonion1234567 + // S: 250-PrivateKey=RSA1024:[Blob Redacted] // S: 250 OK // // We're interested in retrieving the service ID, which is the public diff --git a/tor/cmd_onion_test.go b/tor/cmd_onion_test.go index 45cb7f715..0fea80dbb 100644 --- a/tor/cmd_onion_test.go +++ b/tor/cmd_onion_test.go @@ -3,7 +3,6 @@ package tor import ( "errors" "io" - "os" "path/filepath" "testing" @@ -12,8 +11,8 @@ import ( ) var ( - privateKey = []byte("ED25519-V3:hide_me_plz") - anotherKey = []byte("ED25519-V3:another_key") + privateKey = []byte("RSA1024 hide_me_plz") + anotherKey = []byte("another_key") ) // TestOnionFile tests that the File implementation of the OnionStore @@ -68,69 +67,30 @@ func TestOnionFile(t *testing.T) { require.NoError(t, err) } -// TestOnionFilePrivateKeyRejectsLegacyV2 ensures the file-backed store -// surfaces ErrNonV3OnionKey when the on-disk key is a plaintext legacy -// v2 (RSA1024) blob, rather than handing the bytes back to Tor. -func TestOnionFilePrivateKeyRejectsLegacyV2(t *testing.T) { - t.Parallel() - - privateKeyPath := filepath.Join(t.TempDir(), "secret") - require.NoError(t, os.WriteFile( - privateKeyPath, - []byte("RSA1024:legacy-v2-key-bytes"), - 0600, - )) - - onionFile := NewOnionFile(privateKeyPath, 0600, false, MockEncrypter{}) - _, err := onionFile.PrivateKey() - require.ErrorIs(t, err, ErrNonV3OnionKey) -} - -// TestOnionFilePrivateKeyRejectsEncryptedLegacyV2 ensures the file-backed -// store rejects an encrypted on-disk key that decrypts to a legacy v2 -// (RSA1024) blob, instead of forwarding the bytes to Tor. -func TestOnionFilePrivateKeyRejectsEncryptedLegacyV2(t *testing.T) { - t.Parallel() - - privateKeyPath := filepath.Join(t.TempDir(), "secret") - - // Write a ciphertext-shaped payload (no v3 or v2 prefix) so the - // reader path falls through to the decrypter. - require.NoError(t, os.WriteFile( - privateKeyPath, []byte("encrypted-blob"), 0600, - )) - - onionFile := NewOnionFile( - privateKeyPath, 0600, true, legacyV2Decrypter{}, - ) - _, err := onionFile.PrivateKey() - require.ErrorIs(t, err, ErrNonV3OnionKey) -} - // TestPrepareKeyParam checks that the key param is created as expected. func TestPrepareKeyParam(t *testing.T) { - v3Key := []byte("ED25519-V3:hide_me_plz") + testKey := []byte("hide_me_plz") dummyErr := errors.New("dummy") // Create a dummy controller. controller := NewController("", "", "") // Test that a V3 keyParam is used. - cfg := AddOnionConfig{} + cfg := AddOnionConfig{Type: V3} keyParam, err := controller.prepareKeyparam(cfg) require.Equal(t, "NEW:ED25519-V3", keyParam) require.NoError(t, err) - // Create a mock store which returns a valid v3 private key. + // Create a mock store which returns the test private key. store := &mockStore{} - store.On("PrivateKey").Return(v3Key, nil) + store.On("PrivateKey").Return(testKey, nil) - // Check that the stored v3 private key is returned. - cfg = AddOnionConfig{Store: store} + // Check that the test private is returned. + cfg = AddOnionConfig{Type: V3, Store: store} keyParam, err = controller.prepareKeyparam(cfg) - require.Equal(t, string(v3Key), keyParam) + require.Equal(t, string(testKey), keyParam) require.NoError(t, err) store.AssertExpectations(t) @@ -139,7 +99,7 @@ func TestPrepareKeyParam(t *testing.T) { store.On("PrivateKey").Return(nil, ErrNoPrivateKey) // Check that the V3 keyParam is returned. - cfg = AddOnionConfig{Store: store} + cfg = AddOnionConfig{Type: V3, Store: store} keyParam, err = controller.prepareKeyparam(cfg) require.Equal(t, "NEW:ED25519-V3", keyParam) @@ -151,27 +111,12 @@ func TestPrepareKeyParam(t *testing.T) { store.On("PrivateKey").Return(nil, dummyErr) // Check that an error is returned. - cfg = AddOnionConfig{Store: store} + cfg = AddOnionConfig{Type: V3, Store: store} keyParam, err = controller.prepareKeyparam(cfg) require.Empty(t, keyParam) require.ErrorIs(t, dummyErr, err) store.AssertExpectations(t) - - // A restored legacy v2 (RSA1024) onion key must be rejected; lnd no - // longer creates or recovers v2 services, and silently regenerating - // a fresh v3 service would change the advertised onion identity. - store = &mockStore{} - store.On("PrivateKey").Return( - []byte("RSA1024:legacy-v2-key-bytes"), nil, - ) - - cfg = AddOnionConfig{Store: store} - keyParam, err = controller.prepareKeyparam(cfg) - - require.Empty(t, keyParam) - require.ErrorIs(t, err, ErrNonV3OnionKey) - store.AssertExpectations(t) } // TestPrepareAddOnion checks that the cmd used to add onion service is created @@ -181,7 +126,7 @@ func TestPrepareAddOnion(t *testing.T) { // Create a mock store. store := &mockStore{} - testKey := []byte("ED25519-V3:hide_me_plz") + testKey := []byte("hide_me_plz") testCases := []struct { name string @@ -194,14 +139,14 @@ func TestPrepareAddOnion(t *testing.T) { name: "empty target IP and ports", targetIPAddress: "", cfg: AddOnionConfig{VirtualPort: 9735}, - expectedCmd: "ADD_ONION NEW:ED25519-V3 Port=9735,9735 ", + expectedCmd: "ADD_ONION NEW:RSA1024 Port=9735,9735 ", expectedErr: nil, }, { name: "specified target IP and empty ports", targetIPAddress: "127.0.0.1", cfg: AddOnionConfig{VirtualPort: 9735}, - expectedCmd: "ADD_ONION NEW:ED25519-V3 " + + expectedCmd: "ADD_ONION NEW:RSA1024 " + "Port=9735,127.0.0.1:9735 ", expectedErr: nil, }, @@ -212,7 +157,7 @@ func TestPrepareAddOnion(t *testing.T) { VirtualPort: 9735, TargetPorts: []int{18000, 18001}, }, - expectedCmd: "ADD_ONION NEW:ED25519-V3 " + + expectedCmd: "ADD_ONION NEW:RSA1024 " + "Port=9735,127.0.0.1:18000 " + "Port=9735,127.0.0.1:18001 ", expectedErr: nil, @@ -224,7 +169,7 @@ func TestPrepareAddOnion(t *testing.T) { VirtualPort: 9735, Store: store, }, - expectedCmd: "ADD_ONION ED25519-V3:hide_me_plz " + + expectedCmd: "ADD_ONION hide_me_plz " + "Port=9735,9735 ", expectedErr: nil, }, @@ -267,15 +212,7 @@ func (m *mockStore) StorePrivateKey(key []byte) error { func (m *mockStore) PrivateKey() ([]byte, error) { args := m.Called() - - // Allow callers to set the returned key bytes via the mock's first - // return value; fall back to a valid v3 key prefix for tests that - // only care about a successful key load. - if key, ok := args.Get(0).([]byte); ok && key != nil { - return key, args.Error(1) - } - - return []byte("ED25519-V3:hide_me_plz"), args.Error(1) + return []byte("hide_me_plz"), args.Error(1) } func (m *mockStore) DeletePrivateKey() error { @@ -292,18 +229,3 @@ func (m MockEncrypter) EncryptPayloadToWriter(_ []byte, _ io.Writer) error { func (m MockEncrypter) DecryptPayloadFromReader(_ io.Reader) ([]byte, error) { return anotherKey, nil } - -// legacyV2Decrypter is a stub encrypter whose decrypt step yields a -// legacy v2 (RSA1024) payload, used to exercise the post-decrypt -// validation branch. -type legacyV2Decrypter struct{} - -func (legacyV2Decrypter) EncryptPayloadToWriter(_ []byte, _ io.Writer) error { - return nil -} - -func (legacyV2Decrypter) DecryptPayloadFromReader(_ io.Reader) ([]byte, - error) { - - return []byte("RSA1024:legacy-v2-key-bytes"), nil -} diff --git a/tor/controller.go b/tor/controller.go index 63790a194..6facef892 100644 --- a/tor/controller.go +++ b/tor/controller.go @@ -171,35 +171,26 @@ func (c *Controller) Start() error { // Stop closes the connection between the controller and the Tor server. func (c *Controller) Stop() error { - if c.conn == nil { - return fmt.Errorf("no connection available to the tor server") - } - if !atomic.CompareAndSwapInt32(&c.stopped, 0, 1) { return nil } log.Info("Stopping tor controller") - var delOnionErr error - - // Remove the onion service if one was created successfully. - if c.activeServiceID != "" { - if err := c.DelOnion(c.activeServiceID); err != nil { - log.Errorf("DEL_ONION got error: %v", err) - delOnionErr = err - } + // Remove the onion service. + if err := c.DelOnion(c.activeServiceID); err != nil { + log.Errorf("DEL_ONION got error: %v", err) + return err } - closeErr := c.conn.Close() - if delOnionErr == nil || closeErr == nil { - // Reset service ID. If DEL_ONION failed but the control - // connection closed successfully, the ephemeral service is - // removed by Tor along with the connection. - c.activeServiceID = "" + // Reset service ID. + c.activeServiceID = "" + + if c.conn == nil { + return fmt.Errorf("no connection available to the tor server") } - return errors.Join(delOnionErr, closeErr) + return c.conn.Close() } // Reconnect makes a new socket connection between the tor controller and diff --git a/tor/controller_test.go b/tor/controller_test.go index 3937bbb07..279b4f39e 100644 --- a/tor/controller_test.go +++ b/tor/controller_test.go @@ -1,16 +1,12 @@ package tor import ( - "bufio" - "errors" "fmt" - "io" "net" "net/textproto" "os" "path/filepath" "strconv" - "strings" "sync" "testing" "time" @@ -122,26 +118,6 @@ func (tp *testProxy) cleanUp() { } } -// closeErrorConn is an in-memory control connection that returns a configured -// error from Close. -type closeErrorConn struct { - responses *strings.Reader - commands strings.Builder - closeErr error -} - -func (c *closeErrorConn) Read(p []byte) (int, error) { - return c.responses.Read(p) -} - -func (c *closeErrorConn) Write(p []byte) (int, error) { - return c.commands.Write(p) -} - -func (c *closeErrorConn) Close() error { - return c.closeErr -} - // createTestProxy creates a proxy server to listen on a random address, // creates a server and a client connection, and initializes a testProxy using // these params. @@ -326,115 +302,6 @@ func TestReconnectTCMustBeRunning(t *testing.T) { require.Equal(t, errTCStopped, c.Reconnect()) } -// TestStopWithoutConnectionDoesNotMarkStopped checks that Stop doesn't consume -// the one-way stopped flag if no control connection is available. -func TestStopWithoutConnectionDoesNotMarkStopped(t *testing.T) { - c := &Controller{} - - err := c.Stop() - require.Error(t, err) - require.Contains(t, err.Error(), "no connection available") - require.Zero(t, c.stopped) -} - -// TestStopWithoutActiveService closes the control connection without issuing a -// DEL_ONION command if ADD_ONION never completed successfully. -func TestStopWithoutActiveService(t *testing.T) { - proxy := createTestProxy(t) - t.Cleanup(proxy.cleanUp) - - c := &Controller{ - conn: proxy.clientConn, - } - - require.NoError(t, c.Stop()) - - assertControlConnClosed(t, proxy.serverConn) -} - -// TestStopClosesConnectionOnDelOnionError checks that Stop closes the control -// connection even if Tor rejects the DEL_ONION command. -func TestStopClosesConnectionOnDelOnionError(t *testing.T) { - proxy := createTestProxy(t) - t.Cleanup(proxy.cleanUp) - - const serviceID = "fakeID" - c := &Controller{ - conn: proxy.clientConn, - activeServiceID: serviceID, - } - - serverErr := make(chan error, 1) - go func() { - reader := bufio.NewReader(proxy.serverConn) - - line, err := reader.ReadString('\n') - if err != nil { - serverErr <- err - - return - } - - _, writeErr := proxy.serverConn.Write([]byte( - "512 Bad arguments\r\n", - )) - - expectedCmd := fmt.Sprintf("DEL_ONION %s\r\n", serviceID) - if line != expectedCmd { - serverErr <- fmt.Errorf("expected %q, got %q", - expectedCmd, line) - - return - } - - serverErr <- writeErr - }() - - err := c.Stop() - require.Error(t, err) - require.Contains(t, err.Error(), "invalid arguments") - require.NoError(t, <-serverErr) - require.Empty(t, c.activeServiceID) - - assertControlConnClosed(t, proxy.serverConn) -} - -// TestStopReturnsDelOnionAndCloseErrors checks that Stop preserves both -// cleanup failures and keeps the active service ID for diagnostics. -func TestStopReturnsDelOnionAndCloseErrors(t *testing.T) { - const serviceID = "fakeID" - - closeErr := errors.New("close failed") - conn := &closeErrorConn{ - responses: strings.NewReader("512 Bad arguments\r\n"), - closeErr: closeErr, - } - c := &Controller{ - conn: textproto.NewConn(conn), - activeServiceID: serviceID, - } - - err := c.Stop() - require.Error(t, err) - require.Contains(t, err.Error(), "invalid arguments") - require.ErrorIs(t, err, closeErr) - require.Equal(t, serviceID, c.activeServiceID) - require.Equal(t, "DEL_ONION fakeID\r\n", conn.commands.String()) -} - -// assertControlConnClosed asserts that the control connection was closed by -// the peer instead of timing out while waiting for data. -func assertControlConnClosed(t *testing.T, conn net.Conn) { - t.Helper() - - buf := make([]byte, 1) - require.NoError(t, conn.SetReadDeadline( - time.Now().Add(50*time.Millisecond), - )) - _, err := conn.Read(buf) - require.ErrorIs(t, err, io.EOF) -} - // TestReconnectSucceed tests a reconnection will succeed when the tor // controller is up and running. func TestReconnectSucceed(t *testing.T) { diff --git a/tor/go.mod b/tor/go.mod index 97cc3a991..a67c3ed79 100644 --- a/tor/go.mod +++ b/tor/go.mod @@ -1,28 +1,26 @@ module github.com/lightningnetwork/lnd/tor require ( - github.com/btcsuite/btcd v0.26.0 + github.com/btcsuite/btcd v0.24.2 github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084 github.com/miekg/dns v1.1.43 - github.com/stretchr/testify v1.10.0 - golang.org/x/net v0.41.0 + github.com/stretchr/testify v1.8.4 + golang.org/x/net v0.39.0 ) require ( - github.com/btcsuite/btcd/chaincfg/v2 v2.0.0 // indirect - github.com/btcsuite/btcd/chainhash/v2 v2.0.0 // indirect - github.com/btcsuite/btcd/wire/v2 v2.0.0 // indirect - github.com/btcsuite/btclog v1.0.0 // indirect + github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect + github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/kr/pretty v0.3.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect - github.com/stretchr/objx v0.5.2 // indirect - golang.org/x/crypto v0.40.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect + golang.org/x/crypto v0.37.0 // indirect golang.org/x/sync v0.2.0 // indirect - golang.org/x/sys v0.35.0 // indirect + golang.org/x/sys v0.32.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) -go 1.25.11 +go 1.24.11 diff --git a/tor/go.sum b/tor/go.sum index 2e0a1eb56..3b38259e2 100644 --- a/tor/go.sum +++ b/tor/go.sum @@ -1,16 +1,13 @@ -github.com/btcsuite/btcd v0.26.0 h1:yntnSshlG3+H7dTwIOR4LTFXDPojVBsFORBNN5y5c/c= -github.com/btcsuite/btcd v0.26.0/go.mod h1:7ft7+a/MoJHFouFopCb1zyiR9IWPlrcPVn6K/lJ1dcA= -github.com/btcsuite/btcd/chaincfg/v2 v2.0.0 h1:M/RTtXfXA9odC1RUEOyZFXj/NXKVHPYZXVjb60xTOok= -github.com/btcsuite/btcd/chaincfg/v2 v2.0.0/go.mod h1:rHgHIXYYfn70m25a+BJ9f9z7VZAsTiDQGB2XYaippGQ= -github.com/btcsuite/btcd/chainhash/v2 v2.0.0 h1:PMLlSloHJuEeB80XG9EjpXWNEKAZAMLl6YHZ6YsEuoA= -github.com/btcsuite/btcd/chainhash/v2 v2.0.0/go.mod h1:mKxcZ7oGTXE7IRV+sS9hP4EVBwc/SzfNR+52IsOP9j8= -github.com/btcsuite/btcd/wire/v2 v2.0.0 h1:mYSKzZZ0a1sK+aMhXzfDSVsSzRkWkU3x2U04TFRS2z8= -github.com/btcsuite/btcd/wire/v2 v2.0.0/go.mod h1:bGxkPkk8IiDvUo1D96wE03llBIk7p2MdWYRyAQwLmqM= -github.com/btcsuite/btclog v1.0.0 h1:sEkpKJMmfGiyZjADwEIgB1NSwMyfdD1FB8v6+w1T0Ns= -github.com/btcsuite/btclog v1.0.0/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ= +github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= +github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c h1:4HxD1lBUGUddhzgaNgrCPsFWd7cGYNpeFUgd9ZIgyM0= +github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ= github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084 h1:y3bvkt8ki0KX35eUEU8XShRHusz1S+55QwXUTmxn888= github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -28,22 +25,26 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -52,5 +53,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tor/tor.go b/tor/tor.go index ce615fb7d..37d3fc289 100644 --- a/tor/tor.go +++ b/tor/tor.go @@ -1,6 +1,7 @@ package tor import ( + "bytes" "crypto/rand" "encoding/hex" "fmt" @@ -13,31 +14,42 @@ import ( "golang.org/x/net/proxy" ) -// dnsCodes maps the DNS response codes to a friendly description. This does -// not include the BADVERS code because of duplicate keys and the underlying -// DNS (miekg/dns) package not using it. For more info, see -// https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml. -var dnsCodes = map[int]string{ - 0: "no error", - 1: "format error", - 2: "server failure", - 3: "non-existent domain", - 4: "not implemented", - 5: "query refused", - 6: "name exists when it should not", - 7: "RR set exists when it should not", - 8: "RR set that should exist does not", - 9: "server not authoritative for zone", - 10: "name not contained in zone", - 16: "TSIG signature failure", - 17: "key not recognized", - 18: "signature out of time window", - 19: "bad TKEY mode", - 20: "duplicate key name", - 21: "algorithm not supported", - 22: "bad truncation", - 23: "bad/missing server cookie", -} +var ( + // dnsCodes maps the DNS response codes to a friendly description. This + // does not include the BADVERS code because of duplicate keys and the + // underlying DNS (miekg/dns) package not using it. For more info, see + // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml. + dnsCodes = map[int]string{ + 0: "no error", + 1: "format error", + 2: "server failure", + 3: "non-existent domain", + 4: "not implemented", + 5: "query refused", + 6: "name exists when it should not", + 7: "RR set exists when it should not", + 8: "RR set that should exist does not", + 9: "server not authoritative for zone", + 10: "name not contained in zone", + 16: "TSIG signature failure", + 17: "key not recognized", + 18: "signature out of time window", + 19: "bad TKEY mode", + 20: "duplicate key name", + 21: "algorithm not supported", + 22: "bad truncation", + 23: "bad/missing server cookie", + } + + // onionPrefixBytes is a special purpose IPv6 prefix to encode Onion v2 + // addresses with. Because Neutrino uses the address manager of btcd + // which only understands net.IP addresses instead of net.Addr, we need + // to convert any .onion addresses into fake IPv6 addresses if we want + // to use a Tor hidden service as a Neutrino backend. This is the same + // range used by OnionCat, which is part part of the RFC4193 unique + // local IPv6 unicast address range. + onionPrefixBytes = []byte{0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43} +) // proxyConn is a wrapper around net.Conn that allows us to expose the actual // remote address we're dialing, rather than the proxy's address. @@ -280,3 +292,60 @@ func IsOnionHost(host string) bool { return true } + +// IsOnionFakeIP checks whether a given net.Addr is a fake IPv6 address that +// encodes an Onion v2 address. +func IsOnionFakeIP(addr net.Addr) bool { + _, err := FakeIPToOnionHost(addr) + return err == nil +} + +// OnionHostToFakeIP encodes an Onion v2 address into a fake IPv6 address that +// encodes the same information but can be used for libraries that operate on an +// IP address base only, like btcd's address manager. For example, this will +// turn the onion host ld47qlr6h2b7hrrf.onion into the ip6 address +// fd87:d87e:eb43:58f9:f82e:3e3e:83f3:c625. +func OnionHostToFakeIP(host string) (net.IP, error) { + if len(host) != V2Len { + return nil, fmt.Errorf("invalid onion v2 host: %v", host) + } + + data, err := Base32Encoding.DecodeString(host[:V2Len-OnionSuffixLen]) + if err != nil { + return nil, err + } + + ip := make([]byte, len(onionPrefixBytes)+len(data)) + copy(ip, onionPrefixBytes) + copy(ip[len(onionPrefixBytes):], data) + return ip, nil +} + +// FakeIPToOnionHost turns a fake IPv6 address that encodes an Onion v2 address +// back into its onion host address representation. For example, this will turn +// the fake tcp6 address [fd87:d87e:eb43:58f9:f82e:3e3e:83f3:c625]:8333 back +// into ld47qlr6h2b7hrrf.onion:8333. +func FakeIPToOnionHost(fakeIP net.Addr) (net.Addr, error) { + tcpAddr, ok := fakeIP.(*net.TCPAddr) + if !ok { + return nil, fmt.Errorf("invalid fake onion IP address: %v", + fakeIP) + } + + ip := tcpAddr.IP + if len(ip) != len(onionPrefixBytes)+V2DecodedLen { + return nil, fmt.Errorf("invalid fake onion IP address length: "+ + "%v", fakeIP) + } + + if !bytes.Equal(ip[:len(onionPrefixBytes)], onionPrefixBytes) { + return nil, fmt.Errorf("invalid fake onion IP address prefix: "+ + "%v", fakeIP) + } + + host := Base32Encoding.EncodeToString(ip[len(onionPrefixBytes):]) + return &OnionAddr{ + OnionService: host + ".onion", + Port: tcpAddr.Port, + }, nil +} diff --git a/tor/tor_test.go b/tor/tor_test.go new file mode 100644 index 000000000..594b77df1 --- /dev/null +++ b/tor/tor_test.go @@ -0,0 +1,36 @@ +package tor + +import ( + "fmt" + "net" + "testing" + + "github.com/stretchr/testify/require" +) + +const ( + testOnion = "ld47qlr6h2b7hrrf.onion" + testFakeIP = "fd87:d87e:eb43:58f9:f82e:3e3e:83f3:c625" +) + +// TestOnionHostToFakeIP tests that an onion host address can be converted into +// a fake tcp6 address successfully. +func TestOnionHostToFakeIP(t *testing.T) { + ip, err := OnionHostToFakeIP(testOnion) + require.NoError(t, err) + require.Equal(t, testFakeIP, ip.String()) +} + +// TestFakeIPToOnionHost tests that a fake tcp6 address can be converted back +// into its original .onion host address successfully. +func TestFakeIPToOnionHost(t *testing.T) { + tcpAddr, err := net.ResolveTCPAddr( + "tcp6", fmt.Sprintf("[%s]:8333", testFakeIP), + ) + require.NoError(t, err) + require.True(t, IsOnionFakeIP(tcpAddr)) + + onionHost, err := FakeIPToOnionHost(tcpAddr) + require.NoError(t, err) + require.Equal(t, fmt.Sprintf("%s:8333", testOnion), onionHost.String()) +} diff --git a/walletunlocker/service.go b/walletunlocker/service.go index 6de284532..bde5c7a56 100644 --- a/walletunlocker/service.go +++ b/walletunlocker/service.go @@ -8,8 +8,8 @@ import ( "os" "time" - "github.com/btcsuite/btcd/btcutil/v2/hdkeychain" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wallet" "github.com/lightningnetwork/lnd/aezeed" diff --git a/walletunlocker/service_test.go b/walletunlocker/service_test.go index 82911dc3a..3572b2224 100644 --- a/walletunlocker/service_test.go +++ b/walletunlocker/service_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcwallet/snacl" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wallet" diff --git a/watchtower/blob/commitments.go b/watchtower/blob/commitments.go index 952d76264..994c55caf 100644 --- a/watchtower/blob/commitments.go +++ b/watchtower/blob/commitments.go @@ -3,7 +3,7 @@ package blob import ( "fmt" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwallet" @@ -30,13 +30,8 @@ const ( AnchorCommitment // TaprootCommitment represents the commitment transaction of a simple - // taproot channel using staging scripts. + // taproot channel. TaprootCommitment - - // TaprootFinalCommitment represents the commitment transaction of a - // production taproot channel using final scripts with - // OP_CHECKSIGVERIFY optimizations. - TaprootFinalCommitment ) // ToLocalInput constructs the input that will be used to spend the to_local @@ -71,7 +66,7 @@ func (c CommitmentType) ToRemoteInput(info *lnwallet.BreachRetribution) ( info.LocalOutputSignDesc, 0, ), nil - case AnchorCommitment, TaprootCommitment, TaprootFinalCommitment: + case AnchorCommitment, TaprootCommitment: // Anchor and Taproot channels have a CSV-encumbered to-remote // output. We'll construct a CSV input and assign the proper CSV // delay of 1. @@ -94,9 +89,6 @@ func (c CommitmentType) ToLocalWitnessType() (input.WitnessType, error) { case TaprootCommitment: return input.TaprootCommitmentRevoke, nil - case TaprootFinalCommitment: - return input.TaprootCommitmentRevokeFinal, nil - default: return nil, fmt.Errorf("unknown commitment type: %v", c) } @@ -117,9 +109,6 @@ func (c CommitmentType) ToRemoteWitnessType() (input.WitnessType, error) { case TaprootCommitment: return input.TaprootRemoteCommitSpend, nil - case TaprootFinalCommitment: - return input.TaprootRemoteCommitSpendFinal, nil - default: return nil, fmt.Errorf("unknown commitment type: %v", c) } @@ -138,14 +127,10 @@ func (c CommitmentType) ToRemoteWitnessSize() (lntypes.WeightUnit, error) { case AnchorCommitment: return input.ToRemoteConfirmedWitnessSize, nil - // Staging taproot channels. + // Taproot channels spend a confirmed P2SH output. case TaprootCommitment: return input.TaprootToRemoteWitnessSize, nil - // Production taproot channels use slightly smaller scripts. - case TaprootFinalCommitment: - return input.TaprootToRemoteWitnessSizeFinal, nil - default: return 0, fmt.Errorf("unknown commitment type: %v", c) } @@ -168,11 +153,6 @@ func (c CommitmentType) ToLocalWitnessSize() (lntypes.WeightUnit, error) { case TaprootCommitment: return input.TaprootToLocalRevokeWitnessSize, nil - // Production taproot uses the same revoke witness size since the - // revocation script is identical between staging and production. - case TaprootFinalCommitment: - return input.TaprootToLocalRevokeWitnessSize, nil - default: return 0, fmt.Errorf("unknown commitment type: %v", c) } @@ -207,7 +187,7 @@ func (c CommitmentType) ParseRawSig(witness wire.TxWitness) (lnwire.Sig, // signature. return lnwire.NewSigFromECDSARawSignature(rawSignature) - case TaprootCommitment, TaprootFinalCommitment: + case TaprootCommitment: rawSignature := witness[0] if len(rawSignature) > 64 { rawSignature = witness[0][:len(witness[0])-1] @@ -240,7 +220,7 @@ func (c CommitmentType) NewJusticeKit(sweepScript []byte, sweepScript, breachInfo, withToRemote, ), nil - case TaprootCommitment, TaprootFinalCommitment: + case TaprootCommitment: return newTaprootJusticeKit( sweepScript, breachInfo, withToRemote, ) @@ -265,9 +245,6 @@ func (c CommitmentType) EmptyJusticeKit() (JusticeKit, error) { case TaprootCommitment: return &taprootJusticeKit{}, nil - case TaprootFinalCommitment: - return &taprootJusticeKit{isFinal: true}, nil - default: return nil, fmt.Errorf("unknown commitment type: %v", c) } diff --git a/watchtower/blob/derivation.go b/watchtower/blob/derivation.go index f4f7b5a40..fdd74de42 100644 --- a/watchtower/blob/derivation.go +++ b/watchtower/blob/derivation.go @@ -4,7 +4,7 @@ import ( "crypto/sha256" "encoding/hex" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" ) // BreachHintSize is the length of the identifier used to detect remote diff --git a/watchtower/blob/justice_kit.go b/watchtower/blob/justice_kit.go index b7d9ba291..9dc1af625 100644 --- a/watchtower/blob/justice_kit.go +++ b/watchtower/blob/justice_kit.go @@ -6,9 +6,9 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" secp "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" @@ -295,12 +295,6 @@ func (a *anchorJusticeKit) ToRemoteOutputSpendInfo() (*txscript.PkScript, // be used for backing up commitments of taproot channels. type taprootJusticeKit struct { justiceKitPacketV1 - - // isFinal indicates whether this is a production taproot channel - // using final scripts (OP_CHECKSIGVERIFY optimizations). This - // determines which script variant to use when reconstructing - // scripts for justice transactions. - isFinal bool } // A compile-time check to ensure that taprootJusticeKit implements the @@ -316,17 +310,9 @@ func newTaprootJusticeKit(sweepScript []byte, // TODO(roasbeef): aux leaf tower updates needed - // Use production scripts if this is a final taproot channel. - var scriptOpts []input.TaprootScriptOpt - isFinal := breachInfo.ChanType.IsTaprootFinal() - if isFinal { - scriptOpts = append(scriptOpts, input.WithProdScripts()) - } - tree, err := input.NewLocalCommitScriptTree( breachInfo.RemoteDelay, keyRing.ToLocalKey, keyRing.RevocationKey, fn.None[txscript.TapLeaf](), - scriptOpts..., ) if err != nil { return nil, err @@ -345,10 +331,7 @@ func newTaprootJusticeKit(sweepScript []byte, packet.commitToRemotePubKey = toBlobPubKey(keyRing.ToRemoteKey) } - return &taprootJusticeKit{ - justiceKitPacketV1: packet, - isFinal: isFinal, - }, nil + return &taprootJusticeKit{packet}, nil } // ToLocalOutputSpendInfo returns the info required to send the to-local @@ -369,14 +352,8 @@ func (t *taprootJusticeKit) ToLocalOutputSpendInfo() (*txscript.PkScript, return nil, nil, err } - // Use production scripts if this is a final taproot channel. - var scriptOpts []input.TaprootScriptOpt - if t.isFinal { - scriptOpts = append(scriptOpts, input.WithProdScripts()) - } - revokeScript, err := input.TaprootLocalCommitRevokeScript( - localDelayedPubKey, revocationPubKey, scriptOpts..., + localDelayedPubKey, revocationPubKey, ) if err != nil { return nil, nil, err @@ -442,14 +419,8 @@ func (t *taprootJusticeKit) ToRemoteOutputSpendInfo() (*txscript.PkScript, return nil, nil, 0, err } - // Use production scripts if this is a final taproot channel. - var scriptOpts []input.TaprootScriptOpt - if t.isFinal { - scriptOpts = append(scriptOpts, input.WithProdScripts()) - } - scriptTree, err := input.NewRemoteCommitScriptTree( - toRemotePk, fn.None[txscript.TapLeaf](), scriptOpts..., + toRemotePk, fn.None[txscript.TapLeaf](), ) if err != nil { return nil, nil, 0, err diff --git a/watchtower/blob/justice_kit_packet.go b/watchtower/blob/justice_kit_packet.go index 65ddc6e58..fd799cd1f 100644 --- a/watchtower/blob/justice_kit_packet.go +++ b/watchtower/blob/justice_kit_packet.go @@ -10,7 +10,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/lnwire" "golang.org/x/crypto/chacha20poly1305" ) diff --git a/watchtower/blob/justice_kit_test.go b/watchtower/blob/justice_kit_test.go index af022302a..0d23e2e0f 100644 --- a/watchtower/blob/justice_kit_test.go +++ b/watchtower/blob/justice_kit_test.go @@ -10,8 +10,8 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" @@ -326,6 +326,7 @@ func TestJusticeKitRemoteWitnessConstruction(t *testing.T) { }, } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { testJusticeKitRemoteWitnessConstruction(t, test) }) @@ -484,6 +485,7 @@ func TestJusticeKitToLocalWitnessConstruction(t *testing.T) { }, } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/watchtower/blob/type.go b/watchtower/blob/type.go index df04769ea..aee163ec0 100644 --- a/watchtower/blob/type.go +++ b/watchtower/blob/type.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/lightningnetwork/lnd/channeldb" ) // Flag represents a specify option that can be present in a Type. @@ -30,11 +30,6 @@ const ( // FlagTaprootChannel signals that this blob is meant to spend a // taproot channel and therefore must expect P2TR outputs. FlagTaprootChannel Flag = 1 << 3 - - // FlagTaprootFinalChannel signals that this blob uses production - // taproot scripts (OP_CHECKSIGVERIFY instead of OP_CHECKSIG + OP_DROP) - // as opposed to the staging variant. - FlagTaprootFinalChannel Flag = 1 << 4 ) // Type returns a Type consisting solely of this flag enabled. @@ -53,8 +48,6 @@ func (f Flag) String() string { return "FlagAnchorChannel" case FlagTaprootChannel: return "FlagTaprootChannel" - case FlagTaprootFinalChannel: - return "FlagTaprootFinalChannel" default: return "FlagUnknown" } @@ -85,22 +78,12 @@ const ( // taproot channel commitment to a sweep address controlled by the user, // and does not give the tower a reward. TypeAltruistTaprootCommit = Type(FlagCommitOutputs | FlagTaprootChannel) - - // TypeAltruistTaprootFinalCommit sweeps commitment outputs from a - // production taproot channel using final scripts with - // OP_CHECKSIGVERIFY optimizations. - TypeAltruistTaprootFinalCommit = Type( - FlagCommitOutputs | FlagTaprootChannel | - FlagTaprootFinalChannel, - ) ) // TypeFromChannel returns the appropriate blob Type for the given channel // type. -func TypeFromChannel(chanType chanstate.ChannelType) Type { +func TypeFromChannel(chanType channeldb.ChannelType) Type { switch { - case chanType.IsTaprootFinal(): - return TypeAltruistTaprootFinalCommit case chanType.IsTaproot(): return TypeAltruistTaprootCommit case chanType.HasAnchors(): @@ -121,8 +104,6 @@ func (t Type) Identifier() (string, error) { return "reward", nil case TypeAltruistTaprootCommit: return "taproot", nil - case TypeAltruistTaprootFinalCommit: - return "taproot-final", nil default: return "", fmt.Errorf("unknown blob type: %v", t) } @@ -130,13 +111,10 @@ func (t Type) Identifier() (string, error) { // CommitmentType returns the appropriate CommitmentType for the given blob Type // and channel type. -func (t Type) CommitmentType(chanType *chanstate.ChannelType) (CommitmentType, +func (t Type) CommitmentType(chanType *channeldb.ChannelType) (CommitmentType, error) { switch { - case t.Has(FlagTaprootFinalChannel): - return TaprootFinalCommitment, nil - case t.Has(FlagTaprootChannel): return TaprootCommitment, nil @@ -180,19 +158,12 @@ func (t Type) IsTaprootChannel() bool { return t.Has(FlagTaprootChannel) } -// IsTaprootFinalChannel returns true if the blob type is for a production -// taproot channel using final scripts. -func (t Type) IsTaprootFinalChannel() bool { - return t.Has(FlagTaprootFinalChannel) -} - // knownFlags maps the supported flags to their name. var knownFlags = map[Flag]struct{}{ - FlagReward: {}, - FlagCommitOutputs: {}, - FlagAnchorChannel: {}, - FlagTaprootChannel: {}, - FlagTaprootFinalChannel: {}, + FlagReward: {}, + FlagCommitOutputs: {}, + FlagAnchorChannel: {}, + FlagTaprootChannel: {}, } // String returns a human-readable description of a Type. @@ -239,11 +210,10 @@ func (t Type) String() string { // supportedTypes is the set of all configurations known to be supported by the // package. var supportedTypes = map[Type]struct{}{ - TypeAltruistCommit: {}, - TypeRewardCommit: {}, - TypeAltruistAnchorCommit: {}, - TypeAltruistTaprootCommit: {}, - TypeAltruistTaprootFinalCommit: {}, + TypeAltruistCommit: {}, + TypeRewardCommit: {}, + TypeAltruistAnchorCommit: {}, + TypeAltruistTaprootCommit: {}, } // IsSupportedType returns true if the given type is supported by the package. diff --git a/watchtower/blob/type_test.go b/watchtower/blob/type_test.go index 7baa36ce3..87d9a8af1 100644 --- a/watchtower/blob/type_test.go +++ b/watchtower/blob/type_test.go @@ -6,7 +6,7 @@ import ( "github.com/lightningnetwork/lnd/watchtower/blob" ) -var unknownFlag = blob.Flag(32) +var unknownFlag = blob.Flag(16) type typeStringTest struct { name string @@ -18,8 +18,7 @@ var typeStringTests = []typeStringTest{ { name: "commit no-reward", typ: blob.TypeAltruistCommit, - expStr: "[No-FlagTaprootFinalChannel|" + - "No-FlagTaprootChannel|" + + expStr: "[No-FlagTaprootChannel|" + "No-FlagAnchorChannel|" + "FlagCommitOutputs|" + "No-FlagReward]", @@ -27,8 +26,7 @@ var typeStringTests = []typeStringTest{ { name: "commit reward", typ: blob.TypeRewardCommit, - expStr: "[No-FlagTaprootFinalChannel|" + - "No-FlagTaprootChannel|" + + expStr: "[No-FlagTaprootChannel|" + "No-FlagAnchorChannel|" + "FlagCommitOutputs|" + "FlagReward]", @@ -36,8 +34,7 @@ var typeStringTests = []typeStringTest{ { name: "unknown flag", typ: unknownFlag.Type(), - expStr: "0000000000100000[No-FlagTaprootFinalChannel|" + - "No-FlagTaprootChannel|" + + expStr: "0000000000010000[No-FlagTaprootChannel|" + "No-FlagAnchorChannel|" + "No-FlagCommitOutputs|" + "No-FlagReward]", diff --git a/watchtower/config.go b/watchtower/config.go index 5a35f11eb..f79de9cac 100644 --- a/watchtower/config.go +++ b/watchtower/config.go @@ -5,9 +5,9 @@ import ( "net" "time" - "github.com/btcsuite/btcd/address/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/tor" "github.com/lightningnetwork/lnd/watchtower/lookout" @@ -61,7 +61,7 @@ type Config struct { // NewAddress is used to generate reward addresses, where a cut of // successfully sent funds can be received. - NewAddress func() (address.Address, error) + NewAddress func() (btcutil.Address, error) // NodeKeyECDH is the ECDH capable wrapper of the key to be used in // accepting new brontide connections. @@ -103,4 +103,8 @@ type Config struct { // KeyRing is the KeyRing to use when encrypting the Tor private key. KeyRing keychain.KeyRing + + // Type specifies the hidden service type (V2 or V3) that the watchtower + // will create. + Type tor.OnionType } diff --git a/watchtower/lookout/interface.go b/watchtower/lookout/interface.go index 552fc54bc..a84f22b03 100644 --- a/watchtower/lookout/interface.go +++ b/watchtower/lookout/interface.go @@ -1,8 +1,8 @@ package lookout import ( - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/watchtower/blob" "github.com/lightningnetwork/lnd/watchtower/wtdb" diff --git a/watchtower/lookout/justice_descriptor.go b/watchtower/lookout/justice_descriptor.go index 5895e722f..e4a095a25 100644 --- a/watchtower/lookout/justice_descriptor.go +++ b/watchtower/lookout/justice_descriptor.go @@ -5,10 +5,10 @@ import ( "fmt" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/btcutil/v2/txsort" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/txsort" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnutils" diff --git a/watchtower/lookout/justice_descriptor_test.go b/watchtower/lookout/justice_descriptor_test.go index c3cbea03f..ded2cd603 100644 --- a/watchtower/lookout/justice_descriptor_test.go +++ b/watchtower/lookout/justice_descriptor_test.go @@ -6,10 +6,10 @@ import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/btcutil/v2/txsort" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/txsort" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" secp "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" diff --git a/watchtower/lookout/lookout.go b/watchtower/lookout/lookout.go index 8d7dbea21..b40911995 100644 --- a/watchtower/lookout/lookout.go +++ b/watchtower/lookout/lookout.go @@ -7,8 +7,8 @@ import ( "sync/atomic" "time" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/watchtower/blob" ) diff --git a/watchtower/lookout/lookout_test.go b/watchtower/lookout/lookout_test.go index bcd7ac20a..64d182aa2 100644 --- a/watchtower/lookout/lookout_test.go +++ b/watchtower/lookout/lookout_test.go @@ -9,7 +9,7 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" diff --git a/watchtower/lookout/mock.go b/watchtower/lookout/mock.go index 3b633864d..84c3a004f 100644 --- a/watchtower/lookout/mock.go +++ b/watchtower/lookout/mock.go @@ -4,8 +4,8 @@ import ( "fmt" "sync" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" ) diff --git a/watchtower/lookout/punisher.go b/watchtower/lookout/punisher.go index 956a868da..0c8f8bb89 100644 --- a/watchtower/lookout/punisher.go +++ b/watchtower/lookout/punisher.go @@ -1,7 +1,7 @@ package lookout import ( - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/labels" ) diff --git a/watchtower/standalone.go b/watchtower/standalone.go index df26641de..552001209 100644 --- a/watchtower/standalone.go +++ b/watchtower/standalone.go @@ -158,8 +158,8 @@ func (w *Standalone) Stop() error { return nil } -// createNewHiddenService automatically sets up a v3 onion service in order to -// listen for inbound connections over Tor. +// createNewHiddenService automatically sets up a v2 or v3 onion service in +// order to listen for inbound connections over Tor. func (w *Standalone) createNewHiddenService() error { // Get all the ports the watchtower is listening on. These will be used to // map the hidden service's virtual port. @@ -184,6 +184,7 @@ func (w *Standalone) createNewHiddenService() error { w.cfg.WatchtowerKeyPath, 0600, w.cfg.EncryptKey, encrypter, ), + Type: w.cfg.Type, } addr, err := w.cfg.TorController.AddOnion(onionCfg) diff --git a/watchtower/wtclient/backup_task.go b/watchtower/wtclient/backup_task.go index 3cc470af7..5119a8530 100644 --- a/watchtower/wtclient/backup_task.go +++ b/watchtower/wtclient/backup_task.go @@ -4,11 +4,11 @@ import ( "fmt" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/btcutil/v2/txsort" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/txsort" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/watchtower/blob" diff --git a/watchtower/wtclient/backup_task_internal_test.go b/watchtower/wtclient/backup_task_internal_test.go index 5f725a8d5..62d760946 100644 --- a/watchtower/wtclient/backup_task_internal_test.go +++ b/watchtower/wtclient/backup_task_internal_test.go @@ -4,13 +4,12 @@ import ( "encoding/binary" "testing" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -65,7 +64,7 @@ type backupTaskTest struct { bindErr error expSweepScript []byte signer input.Signer - chanType chanstate.ChannelType + chanType channeldb.ChannelType commitType blob.CommitmentType } @@ -85,7 +84,7 @@ func genTaskTest( expSweepAmt int64, expRewardAmt int64, bindErr error, - chanType chanstate.ChannelType) backupTaskTest { + chanType channeldb.ChannelType) backupTaskTest { // Set the anchor or taproot flag in the blob type if the session needs // to support anchor or taproot channels. @@ -306,14 +305,14 @@ var ( blobTypeCommitReward = (blob.FlagCommitOutputs | blob.FlagReward).Type() - addr, _ = address.DecodeAddress( + addr, _ = btcutil.DecodeAddress( "tb1pw8gzj8clt3v5lxykpgacpju5n8xteskt7gxhmudu6pa70nwfhe6s3unsyk", &chaincfg.TestNet3Params, ) addrScript, _ = txscript.PayToAddrScript(addr) - sweepAddrScript, _ = address.DecodeAddress( + sweepAddrScript, _ = btcutil.DecodeAddress( "tb1qs3jyc9sf5kak3x0w99cav9u605aeu3t600xxx0", &chaincfg.TestNet3Params, ) @@ -331,11 +330,11 @@ var ( func TestBackupTask(t *testing.T) { t.Parallel() - chanTypes := []chanstate.ChannelType{ - chanstate.SingleFunderBit, - chanstate.SingleFunderTweaklessBit, - chanstate.AnchorOutputsBit, - chanstate.SimpleTaprootFeatureBit, + chanTypes := []channeldb.ChannelType{ + channeldb.SingleFunderBit, + channeldb.SingleFunderTweaklessBit, + channeldb.AnchorOutputsBit, + channeldb.SimpleTaprootFeatureBit, } var backupTaskTests []backupTaskTest @@ -553,6 +552,7 @@ func TestBackupTask(t *testing.T) { } for _, test := range backupTaskTests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -573,7 +573,7 @@ func testBackupTask(t *testing.T, test backupTaskTest) { // getBreachInfo is a helper closure that returns the breach retribution // info and channel type for the given channel and commit height. getBreachInfo := func(id lnwire.ChannelID, commitHeight uint64) ( - *lnwallet.BreachRetribution, chanstate.ChannelType, error) { + *lnwallet.BreachRetribution, channeldb.ChannelType, error) { return test.breachInfo, test.chanType, nil } diff --git a/watchtower/wtclient/client.go b/watchtower/wtclient/client.go index 41edb5988..8d74e9d1f 100644 --- a/watchtower/wtclient/client.go +++ b/watchtower/wtclient/client.go @@ -13,7 +13,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btclog/v2" - "github.com/lightningnetwork/lnd/chanstate" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" @@ -94,7 +94,7 @@ type RegisteredTower struct { // BreachRetribution from a channel ID and a commitment height. type BreachRetributionBuilder func(id lnwire.ChannelID, commitHeight uint64) (*lnwallet.BreachRetribution, - chanstate.ChannelType, error) + channeldb.ChannelType, error) // newTowerMsg is an internal message we'll use within the client to signal // that a new tower can be considered. @@ -303,15 +303,6 @@ func getTowerAndSessionCandidates(db DB, keyRing ECDHKeyRing, candidateSessions := make(map[wtdb.SessionID]*ClientSession) for _, dbTower := range towers { tower, err := NewTowerFromDBTower(dbTower) - if errors.Is(err, ErrTowerOnlyV2Onion) { - log.Warnf("Skipping tower %x: all persisted "+ - "addresses are Tor v2 .onion which is no "+ - "longer supported; add a fresh v3 address "+ - "to re-activate this tower", - dbTower.IdentityKey.SerializeCompressed()) - - continue - } if err != nil { return nil, err } diff --git a/watchtower/wtclient/client_test.go b/watchtower/wtclient/client_test.go index 99c0e0a18..e842876b6 100644 --- a/watchtower/wtclient/client_test.go +++ b/watchtower/wtclient/client_test.go @@ -10,16 +10,15 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channelnotifier" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -68,7 +67,7 @@ var ( } // addr is the server's reward address given to watchtower clients. - addr, _ = address.DecodeAddress( + addr, _ = btcutil.DecodeAddress( "tb1pw8gzj8clt3v5lxykpgacpju5n8xteskt7gxhmudu6pa70nwfhe6s3unsyk", &chaincfg.TestNet3Params, ) @@ -335,6 +334,7 @@ func (c *mockChannel) createRemoteCommitTx(t *testing.T) { SignMethod: input.TaprootScriptSpendSignMethod, ControlBlock: ctrlBytes, } + outputIndex++ } txid := commitTxn.TxHash() @@ -357,6 +357,7 @@ func (c *mockChannel) createRemoteCommitTx(t *testing.T) { Hash: txid, Index: uint32(outputIndex), } + outputIndex++ } commitKeyRing := &lnwallet.CommitmentKeyRing{ @@ -513,7 +514,7 @@ func newHarness(t *testing.T, cfg harnessCfg) *testHarness { }) fetchChannel := func(id lnwire.ChannelID) ( - *chanstate.ChannelCloseSummary, error) { + *channeldb.ChannelCloseSummary, error) { h.mu.Lock() defer h.mu.Unlock() @@ -523,7 +524,7 @@ func newHarness(t *testing.T, cfg harnessCfg) *testHarness { return nil, channeldb.ErrClosedChannelNotFound } - return &chanstate.ChannelCloseSummary{CloseHeight: height}, nil + return &channeldb.ChannelCloseSummary{CloseHeight: height}, nil } h.clientPolicy = cfg.policy @@ -551,11 +552,11 @@ func newHarness(t *testing.T, cfg harnessCfg) *testHarness { h.clientCfg.BuildBreachRetribution = func(id lnwire.ChannelID, commitHeight uint64) (*lnwallet.BreachRetribution, - chanstate.ChannelType, error) { + channeldb.ChannelType, error) { _, retribution := h.channelFromID(id).getState(commitHeight) - return retribution, chanstate.SimpleTaprootFeatureBit, nil + return retribution, channeldb.SimpleTaprootFeatureBit, nil } if !cfg.noServerStart { @@ -688,7 +689,7 @@ func (h *testHarness) closeChannel(id uint64, height uint32) { } h.channelEvents.sendUpdate(channelnotifier.ClosedChannelEvent{ - CloseSummary: &chanstate.ChannelCloseSummary{ + CloseSummary: &channeldb.ChannelCloseSummary{ ChanPoint: wire.OutPoint{ Hash: *chanPointHash, Index: 0, @@ -704,7 +705,7 @@ func (h *testHarness) registerChannel(id uint64) { chanID := chanIDFromInt(id) err := h.clientMgr.RegisterChannel( - chanID, chanstate.SimpleTaprootFeatureBit, + chanID, channeldb.SimpleTaprootFeatureBit, ) require.NoError(h.t, err) } @@ -957,7 +958,7 @@ func newServerHarness(t *testing.T, mockNet *mockNet, netAddr string, ReadTimeout: timeout, WriteTimeout: timeout, NodeKeyECDH: privKeyECDH, - NewAddress: func() (address.Address, error) { + NewAddress: func() (btcutil.Address, error) { return addr, nil }, } @@ -1810,10 +1811,8 @@ var clientTests = []clientTest{ require.NoError(h.t, err) cancel := make(chan struct{}) - dialStarted := make(chan struct{}) h.net.registerConnCallback( h.server.addr, func(peer wtserver.Peer) { - close(dialStarted) select { case <-h.quit: case <-cancel: @@ -1848,16 +1847,6 @@ var clientTests = []clientTest{ err = h.clientMgr.AddTower(towerAddr) require.NoError(h.t, err) - // Wait for the dial to start so that we know the - // session negotiation has begun and the address is - // locked. - select { - case <-dialStarted: - - case <-time.After(waitTime): - h.t.Fatal("timeout waiting for dial to start") - } - // Assert that if the client attempts to remove the // tower's first address, then it will error due to // address currently being locked for session @@ -2365,23 +2354,10 @@ var clientTests = []clientTest{ }, waitTime) require.NoError(h.t, err) - // Now remove the tower. We use wait.Predicate here - // because the address may still be locked by an active - // session. - err = wait.Predicate(func() bool { - err := h.clientMgr.RemoveTower( - h.server.addr.IdentityKey, nil, - ) - if err != nil { - require.ErrorIs( - h.t, err, wtclient.ErrAddrInUse, - ) - - return false - } - - return true - }, waitTime) + // Now remove the tower. + err = h.clientMgr.RemoveTower( + h.server.addr.IdentityKey, nil, + ) require.NoError(h.t, err) // Add a new tower. diff --git a/watchtower/wtclient/interface.go b/watchtower/wtclient/interface.go index 48252e2e9..1051da0d5 100644 --- a/watchtower/wtclient/interface.go +++ b/watchtower/wtclient/interface.go @@ -1,7 +1,6 @@ package wtclient import ( - "errors" "net" "github.com/btcsuite/btcd/btcec/v2" @@ -13,12 +12,6 @@ import ( "github.com/lightningnetwork/lnd/watchtower/wtserver" ) -// ErrTowerOnlyV2Onion is returned when a persisted tower has no usable -// addresses left after Tor v2 .onion entries are filtered out. The tower -// record is preserved on disk so an operator can attach a fresh v3 address -// for the same identity key. -var ErrTowerOnlyV2Onion = errors.New("tower has no non-v2-onion addresses") - // DB abstracts the required database operations required by the watchtower // client. type DB interface { @@ -191,53 +184,10 @@ type Tower struct { Addresses AddressIterator } -// isV2OnionAddr reports whether addr is a Tor v2 .onion address. Tor stopped -// serving v2 onion services in October 2021, so callers skip these on dial -// paths. Storage and gossip re-broadcast still preserve v2 byte-for-byte to -// keep peer-signed NodeAnnouncement signatures verifiable. -// -// TODO: move this helper into the `tor` module (as `tor.IsV2Onion`) and remove -// this copy along with the duplicate in the root server.go once a new `tor` -// module version is cut and the dependency is bumped. -func isV2OnionAddr(addr net.Addr) bool { - onion, ok := addr.(*tor.OnionAddr) - if !ok { - return false - } - - return len(onion.OnionService) == tor.V2Len -} - -// withoutV2Onion returns addrs with any Tor v2 .onion entries removed. See -// isV2OnionAddr for the rationale. -// -// TODO: move this helper into the `tor` module and remove this copy along -// with the duplicate in the root server.go once a new `tor` module version -// is cut and the dependency is bumped. -func withoutV2Onion(addrs []net.Addr) []net.Addr { - filtered := make([]net.Addr, 0, len(addrs)) - for _, addr := range addrs { - if isV2OnionAddr(addr) { - continue - } - filtered = append(filtered, addr) - } - - return filtered -} - // NewTowerFromDBTower converts a wtdb.Tower, which uses a static address list, -// into a Tower which uses an address iterator. Persisted Tor v2 .onion -// addresses are filtered out so an upgraded node never attempts to dial them; -// if filtering leaves zero usable addresses, ErrTowerOnlyV2Onion is returned -// and the caller is expected to skip the tower without modifying the DB. +// into a Tower which uses an address iterator. func NewTowerFromDBTower(t *wtdb.Tower) (*Tower, error) { - filtered := withoutV2Onion(t.Addresses) - if len(filtered) == 0 { - return nil, ErrTowerOnlyV2Onion - } - - addrs, err := newAddressIterator(filtered...) + addrs, err := newAddressIterator(t.Addresses...) if err != nil { return nil, err } diff --git a/watchtower/wtclient/interface_test.go b/watchtower/wtclient/interface_test.go deleted file mode 100644 index e65715fa7..000000000 --- a/watchtower/wtclient/interface_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package wtclient - -import ( - "net" - "testing" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/lightningnetwork/lnd/tor" - "github.com/lightningnetwork/lnd/watchtower/wtdb" - "github.com/stretchr/testify/require" -) - -// TestNewTowerFromDBTowerFiltersV2Onion asserts that NewTowerFromDBTower drops -// any persisted Tor v2 .onion entries before constructing the address -// iterator, that mixed lists still surface the remaining v3/tcp addresses, and -// that a tower whose addresses are exclusively v2 surfaces -// ErrTowerOnlyV2Onion so the caller can skip it without touching the DB. -func TestNewTowerFromDBTowerFiltersV2Onion(t *testing.T) { - t.Parallel() - - priv, err := btcec.NewPrivateKey() - require.NoError(t, err) - - v2 := &tor.OnionAddr{ - OnionService: "3g2upl4pq6kufc4m.onion", - Port: 9911, - } - v3 := &tor.OnionAddr{ - OnionService: "4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnz" + - "jpbi7utijcltosqemad.onion", - Port: 9911, - } - tcp := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 9911} - - t.Run("mixed addresses keep v3/tcp", func(t *testing.T) { - t.Parallel() - - tower, err := NewTowerFromDBTower(&wtdb.Tower{ - ID: 7, - IdentityKey: priv.PubKey(), - Addresses: []net.Addr{v2, v3, tcp, v2}, - }) - require.NoError(t, err) - require.Equal(t, []net.Addr{v3, tcp}, tower.Addresses.GetAll()) - }) - - t.Run("only v2 addresses returns sentinel", func(t *testing.T) { - t.Parallel() - - _, err := NewTowerFromDBTower(&wtdb.Tower{ - ID: 7, - IdentityKey: priv.PubKey(), - Addresses: []net.Addr{v2, v2}, - }) - require.ErrorIs(t, err, ErrTowerOnlyV2Onion) - }) -} diff --git a/watchtower/wtclient/manager.go b/watchtower/wtclient/manager.go index 6d9ae1849..7a39c8ff7 100644 --- a/watchtower/wtclient/manager.go +++ b/watchtower/wtclient/manager.go @@ -8,11 +8,10 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channelnotifier" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwire" @@ -68,7 +67,7 @@ type ClientManager interface { // parameters within the client. This should be called during link // startup to ensure that the client is able to support the link during // operation. - RegisterChannel(lnwire.ChannelID, chanstate.ChannelType) error + RegisterChannel(lnwire.ChannelID, channeldb.ChannelType) error // BackupState initiates a request to back up a particular revoked // state. If the method returns nil, the backup is guaranteed to be @@ -94,7 +93,7 @@ type Config struct { // channel. If the channel is not found or not yet closed then // channeldb.ErrClosedChannelNotFound will be returned. FetchClosedChannel func(cid lnwire.ChannelID) ( - *chanstate.ChannelCloseSummary, error) + *channeldb.ChannelCloseSummary, error) // ChainNotifier can be used to subscribe to block notifications. ChainNotifier chainntnfs.ChainNotifier @@ -598,7 +597,7 @@ func (m *Manager) Policy(blobType blob.Type) (wtpolicy.Policy, error) { // within the client. This should be called during link startup to ensure that // the client is able to support the link during operation. func (m *Manager) RegisterChannel(id lnwire.ChannelID, - chanType chanstate.ChannelType) error { + chanType channeldb.ChannelType) error { blobType := blob.TypeFromChannel(chanType) diff --git a/watchtower/wtclient/queue_test.go b/watchtower/wtclient/queue_test.go index 66fe20c2a..a8494c216 100644 --- a/watchtower/wtclient/queue_test.go +++ b/watchtower/wtclient/queue_test.go @@ -61,6 +61,7 @@ func TestDiskOverflowQueue(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(tt *testing.T) { tt.Parallel() diff --git a/watchtower/wtclient/session_negotiator.go b/watchtower/wtclient/session_negotiator.go index 01184657f..4cba7de62 100644 --- a/watchtower/wtclient/session_negotiator.go +++ b/watchtower/wtclient/session_negotiator.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btclog/v2" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" diff --git a/watchtower/wtclient/session_queue.go b/watchtower/wtclient/session_queue.go index 1cfd98fa8..4ee159763 100644 --- a/watchtower/wtclient/session_queue.go +++ b/watchtower/wtclient/session_queue.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btclog/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" diff --git a/watchtower/wtdb/client_db.go b/watchtower/wtdb/client_db.go index 9fa03a3c1..6e6adacc0 100644 --- a/watchtower/wtdb/client_db.go +++ b/watchtower/wtdb/client_db.go @@ -2964,7 +2964,7 @@ func getRealChannelID(chanIDIndexBkt kvdb.RBucket, } chanIDBytes := chanIDIndexBkt.Get(dbIDBytes) - if len(chanIDBytes) != 32 { + if len(chanIDBytes) != 32 { //nolint:gomnd return nil, fmt.Errorf("channel ID not found") } diff --git a/watchtower/wtdb/codec_test.go b/watchtower/wtdb/codec_test.go index e4a05eee9..3f2e59864 100644 --- a/watchtower/wtdb/codec_test.go +++ b/watchtower/wtdb/codec_test.go @@ -59,6 +59,24 @@ func randTCP6Addr(r *rand.Rand) (*net.TCPAddr, error) { return &net.TCPAddr{IP: addrIP, Port: addrPort}, nil } +func randV2OnionAddr(r *rand.Rand) (*tor.OnionAddr, error) { + var serviceID [tor.V2DecodedLen]byte + if _, err := r.Read(serviceID[:]); err != nil { + return nil, err + } + + var port [2]byte + if _, err := r.Read(port[:]); err != nil { + return nil, err + } + + onionService := tor.Base32Encoding.EncodeToString(serviceID[:]) + onionService += tor.OnionSuffix + addrPort := int(binary.BigEndian.Uint16(port[:])) + + return &tor.OnionAddr{OnionService: onionService, Port: addrPort}, nil +} + func randV3OnionAddr(r *rand.Rand) (*tor.OnionAddr, error) { var serviceID [tor.V3DecodedLen]byte if _, err := r.Read(serviceID[:]); err != nil { @@ -88,12 +106,17 @@ func randAddrs(r *rand.Rand) ([]net.Addr, error) { return nil, err } + v2OnionAddr, err := randV2OnionAddr(r) + if err != nil { + return nil, err + } + v3OnionAddr, err := randV3OnionAddr(r) if err != nil { return nil, err } - return []net.Addr{tcp4Addr, tcp6Addr, v3OnionAddr}, nil + return []net.Addr{tcp4Addr, tcp6Addr, v2OnionAddr, v3OnionAddr}, nil } // dbObject is abstract object support encoding and decoding. diff --git a/watchtower/wtdb/migration1/client_db_test.go b/watchtower/wtdb/migration1/client_db_test.go index d75503d67..acae177ad 100644 --- a/watchtower/wtdb/migration1/client_db_test.go +++ b/watchtower/wtdb/migration1/client_db_test.go @@ -94,6 +94,7 @@ func TestMigrateTowerToSessionIndex(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { // Before the migration we have a sessions bucket. diff --git a/watchtower/wtdb/migration2/client_db_test.go b/watchtower/wtdb/migration2/client_db_test.go index b74c00f11..c1436184f 100644 --- a/watchtower/wtdb/migration2/client_db_test.go +++ b/watchtower/wtdb/migration2/client_db_test.go @@ -69,6 +69,7 @@ func TestMigrateClientChannelDetails(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/watchtower/wtdb/migration3/client_db_test.go b/watchtower/wtdb/migration3/client_db_test.go index 8cd3796aa..a2fc8aedf 100644 --- a/watchtower/wtdb/migration3/client_db_test.go +++ b/watchtower/wtdb/migration3/client_db_test.go @@ -83,6 +83,7 @@ func TestMigrateChannelIDIndex(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/watchtower/wtdb/migration4/client_db_test.go b/watchtower/wtdb/migration4/client_db_test.go index 917b01dcc..267cfe17d 100644 --- a/watchtower/wtdb/migration4/client_db_test.go +++ b/watchtower/wtdb/migration4/client_db_test.go @@ -226,6 +226,7 @@ func TestMigrateAckedUpdates(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/watchtower/wtdb/migration5/client_db_test.go b/watchtower/wtdb/migration5/client_db_test.go index ec29dfdb0..a0a67e5f5 100644 --- a/watchtower/wtdb/migration5/client_db_test.go +++ b/watchtower/wtdb/migration5/client_db_test.go @@ -95,6 +95,7 @@ func TestCompleteTowerToSessionIndex(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/watchtower/wtdb/migration6/client_db_test.go b/watchtower/wtdb/migration6/client_db_test.go index 9b3880f06..c4928e2f9 100644 --- a/watchtower/wtdb/migration6/client_db_test.go +++ b/watchtower/wtdb/migration6/client_db_test.go @@ -81,6 +81,7 @@ func TestMigrateSessionIDIndex(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/watchtower/wtdb/migration7/client_db_test.go b/watchtower/wtdb/migration7/client_db_test.go index 2480b27ab..40eeec5b0 100644 --- a/watchtower/wtdb/migration7/client_db_test.go +++ b/watchtower/wtdb/migration7/client_db_test.go @@ -112,6 +112,7 @@ func TestMigrateChannelToSessionIndex(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() diff --git a/watchtower/wtdb/migration8/codec.go b/watchtower/wtdb/migration8/codec.go index 38768ba34..9c8dca1a3 100644 --- a/watchtower/wtdb/migration8/codec.go +++ b/watchtower/wtdb/migration8/codec.go @@ -7,7 +7,7 @@ import ( "fmt" "io" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/tlv" ) diff --git a/watchtower/wtdb/tower_db.go b/watchtower/wtdb/tower_db.go index 9f6bad1fe..fa43b5bdd 100644 --- a/watchtower/wtdb/tower_db.go +++ b/watchtower/wtdb/tower_db.go @@ -4,7 +4,7 @@ import ( "bytes" "errors" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/watchtower/blob" diff --git a/watchtower/wtdb/tower_db_test.go b/watchtower/wtdb/tower_db_test.go index 3b0c71de5..f829a793a 100644 --- a/watchtower/wtdb/tower_db_test.go +++ b/watchtower/wtdb/tower_db_test.go @@ -5,7 +5,7 @@ import ( "encoding/binary" "testing" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/watchtower" diff --git a/watchtower/wtmock/signer.go b/watchtower/wtmock/signer.go index b43304451..af3ebe58d 100644 --- a/watchtower/wtmock/signer.go +++ b/watchtower/wtmock/signer.go @@ -9,8 +9,8 @@ import ( "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/txscript/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" ) @@ -150,22 +150,6 @@ func (s *MockSigner) MuSig2RegisterNonces(input.MuSig2SessionID, return false, nil } -// MuSig2RegisterCombinedNonce registers a pre-aggregated combined nonce for a -// session identified by its ID. -func (s *MockSigner) MuSig2RegisterCombinedNonce(input.MuSig2SessionID, - [musig2.PubNonceSize]byte) error { - - return nil -} - -// MuSig2GetCombinedNonce retrieves the combined nonce for a session identified -// by its ID. -func (s *MockSigner) MuSig2GetCombinedNonce(input.MuSig2SessionID) ( - [musig2.PubNonceSize]byte, error) { - - return [musig2.PubNonceSize]byte{}, nil -} - // MuSig2Sign creates a partial signature using the local signing key // that was specified when the session was created. This can only be // called when all public nonces of all participants are known and have diff --git a/watchtower/wtpolicy/policy.go b/watchtower/wtpolicy/policy.go index be410544a..89804685b 100644 --- a/watchtower/wtpolicy/policy.go +++ b/watchtower/wtpolicy/policy.go @@ -4,8 +4,8 @@ import ( "errors" "fmt" - "github.com/btcsuite/btcd/btcutil/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" diff --git a/watchtower/wtserver/create_session.go b/watchtower/wtserver/create_session.go index 8b9787b34..3fd58dbd2 100644 --- a/watchtower/wtserver/create_session.go +++ b/watchtower/wtserver/create_session.go @@ -1,7 +1,7 @@ package wtserver import ( - "github.com/btcsuite/btcd/txscript/v2" + "github.com/btcsuite/btcd/txscript" "github.com/lightningnetwork/lnd/watchtower/blob" "github.com/lightningnetwork/lnd/watchtower/wtdb" "github.com/lightningnetwork/lnd/watchtower/wtpolicy" diff --git a/watchtower/wtserver/server.go b/watchtower/wtserver/server.go index 2a7f78070..01234f9d0 100644 --- a/watchtower/wtserver/server.go +++ b/watchtower/wtserver/server.go @@ -8,8 +8,8 @@ import ( "sync" "time" - "github.com/btcsuite/btcd/address/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/connmgr" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" @@ -51,7 +51,7 @@ type Config struct { // NewAddress is used to generate reward addresses, where a cut of // successfully sent funds can be received. - NewAddress func() (address.Address, error) + NewAddress func() (btcutil.Address, error) // ChainHash identifies the network that the server is watching. ChainHash chainhash.Hash diff --git a/watchtower/wtserver/server_test.go b/watchtower/wtserver/server_test.go index b1665f276..fa1dfa036 100644 --- a/watchtower/wtserver/server_test.go +++ b/watchtower/wtserver/server_test.go @@ -6,10 +6,10 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/txscript/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/watchtower/blob" "github.com/lightningnetwork/lnd/watchtower/wtdb" @@ -21,7 +21,7 @@ import ( var ( // addr is the server's reward address given to watchtower clients. - addr, _ = address.DecodeAddress( + addr, _ = btcutil.DecodeAddress( "tb1pw8gzj8clt3v5lxykpgacpju5n8xteskt7gxhmudu6pa70nwfhe6s3unsyk", &chaincfg.TestNet3Params, ) @@ -61,7 +61,7 @@ func initServer(t *testing.T, db wtserver.DB, DB: db, ReadTimeout: timeout, WriteTimeout: timeout, - NewAddress: func() (address.Address, error) { + NewAddress: func() (btcutil.Address, error) { return addr, nil }, ChainHash: testnetChainHash, diff --git a/watchtower/wtwire/init.go b/watchtower/wtwire/init.go index fa5a4033e..4d5ec34bd 100644 --- a/watchtower/wtwire/init.go +++ b/watchtower/wtwire/init.go @@ -4,7 +4,7 @@ import ( "fmt" "io" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/feature" "github.com/lightningnetwork/lnd/lnwire" ) diff --git a/watchtower/wtwire/init_test.go b/watchtower/wtwire/init_test.go index 34f8e6f68..c0b0fa751 100644 --- a/watchtower/wtwire/init_test.go +++ b/watchtower/wtwire/init_test.go @@ -3,8 +3,8 @@ package wtwire_test import ( "testing" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/feature" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/watchtower/wtwire" diff --git a/watchtower/wtwire/wtwire.go b/watchtower/wtwire/wtwire.go index d49e020a2..5b10fee33 100644 --- a/watchtower/wtwire/wtwire.go +++ b/watchtower/wtwire/wtwire.go @@ -6,8 +6,8 @@ import ( "io" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/watchtower/blob" diff --git a/watchtower/wtwire/wtwire_test.go b/watchtower/wtwire/wtwire_test.go index 785c89250..e9b37a559 100644 --- a/watchtower/wtwire/wtwire_test.go +++ b/watchtower/wtwire/wtwire_test.go @@ -8,7 +8,7 @@ import ( "testing/quick" "time" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/watchtower/wtwire" diff --git a/witness_beacon.go b/witness_beacon.go index cefeb2fd3..68c096a85 100644 --- a/witness_beacon.go +++ b/witness_beacon.go @@ -5,7 +5,6 @@ import ( "sync" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/chanstate" "github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" @@ -65,7 +64,7 @@ func newPreimageBeacon(wCache witnessCache, // SubscribeUpdates returns a channel that will be sent upon *each* time a new // preimage is discovered. func (p *preimageBeacon) SubscribeUpdates( - chanID lnwire.ShortChannelID, htlc *chanstate.HTLC, + chanID lnwire.ShortChannelID, htlc *channeldb.HTLC, payload *hop.Payload, nextHopOnionBlob []byte) (*contractcourt.WitnessSubscription, error) { @@ -107,26 +106,14 @@ func (p *preimageBeacon) SubscribeUpdates( }, } - // Report the forwarding next hop to the interceptor. A channel-ID next - // hop is reported directly; a node-ID next hop has no outgoing channel - // of its own, so outgoingChanID is hop.Exit and the requested node ID - // is exposed separately, exactly as the off-chain interceptor does. - // This is the requested next hop, not the channel that non-strict - // forwarding eventually selects, so we deliberately do not resolve it - // against the circuit map. The RPC boundary maps a node-ID hop to the - // NodeIDForwardSCID sentinel for the client. - // // Notify the htlc interceptor. There may be a client connected // and willing to supply a preimage. packet := &htlcswitch.InterceptedPacket{ - Hash: htlc.RHash, - IncomingExpiry: htlc.RefundTimeout, - IncomingAmount: htlc.Amt, - IncomingCircuit: inKey, - OutgoingChanID: payload.FwdInfo.NextHopChannel().UnwrapOr( - hop.Exit, - ), - OutgoingNodeID: payload.FwdInfo.NextHopNode(), + Hash: htlc.RHash, + IncomingExpiry: htlc.RefundTimeout, + IncomingAmount: htlc.Amt, + IncomingCircuit: inKey, + OutgoingChanID: payload.FwdInfo.NextHop, OutgoingExpiry: payload.FwdInfo.OutgoingCLTV, OutgoingAmount: payload.FwdInfo.AmountToForward, InOnionCustomRecords: payload.CustomRecords(), diff --git a/witness_beacon_test.go b/witness_beacon_test.go index dc3e0ddb5..1edbada93 100644 --- a/witness_beacon_test.go +++ b/witness_beacon_test.go @@ -4,8 +4,7 @@ import ( "errors" "testing" - "github.com/lightningnetwork/lnd/chanstate" - "github.com/lightningnetwork/lnd/fn/v2" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/htlcswitch/hop" @@ -39,7 +38,7 @@ func TestWitnessBeaconIntercept(t *testing.T) { subscription, err := p.SubscribeUpdates( lnwire.NewShortChanIDFromInt(1), - &chanstate.HTLC{ + &channeldb.HTLC{ RHash: hash, }, &hop.Payload{}, @@ -77,7 +76,7 @@ func TestWitnessBeaconInterceptErrorCancels(t *testing.T) { ) chanID := lnwire.NewShortChanIDFromInt(1) - htlc := &chanstate.HTLC{ + htlc := &channeldb.HTLC{ HtlcIndex: 2, RHash: lntypes.Hash{3}, } @@ -98,47 +97,6 @@ func TestWitnessBeaconInterceptErrorCancels(t *testing.T) { p.RUnlock() } -// TestWitnessBeaconInterceptNodeID asserts that for a node-ID next hop the -// on-chain interceptor reports the exit-hop SCID (hop.Exit) together with the -// requested next node's public key, matching the off-chain interceptor. The -// next hop is not resolved against the circuit map; the RPC boundary maps -// hop.Exit to the sentinel. -func TestWitnessBeaconInterceptNodeID(t *testing.T) { - var interceptedFwd htlcswitch.InterceptedForward - interceptor := func(fwd htlcswitch.InterceptedForward) error { - interceptedFwd = fwd - - return nil - } - - p := newPreimageBeacon( - &mockWitnessCache{}, interceptor, - func(models.CircuitKey) error { - return nil - }, - ) - - var nodeID [33]byte - nodeID[0] = 0x02 - - payload := &hop.Payload{ - FwdInfo: hop.ForwardingInfo{ - NextHop: hop.NewNodeNextHop(nodeID), - }, - } - - _, err := p.SubscribeUpdates( - lnwire.NewShortChanIDFromInt(1), - &chanstate.HTLC{RHash: lntypes.Hash{1}}, - payload, []byte{2}, - ) - require.NoError(t, err) - - packet := interceptedFwd.Packet() - require.Equal(t, hop.Exit, packet.OutgoingChanID) - require.Equal(t, fn.Some(nodeID), packet.OutgoingNodeID) -} - type mockWitnessCache struct { witnessCache } diff --git a/zpay32/decode.go b/zpay32/decode.go index 4c66e76b3..577f6a6d1 100644 --- a/zpay32/decode.go +++ b/zpay32/decode.go @@ -9,13 +9,13 @@ import ( "time" "unicode/utf8" - "github.com/btcsuite/btcd/address/v2" - "github.com/btcsuite/btcd/address/v2/bech32" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" - "github.com/btcsuite/btcd/txscript/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/bech32" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwire" ) @@ -527,15 +527,13 @@ func parseMinFinalCLTVExpiry(data []byte) (*uint64, error) { // parseFallbackAddr converts the data (encoded in base32) into a fallback // on-chain address. -func parseFallbackAddr(data []byte, - net *chaincfg.Params) (address.Address, error) { - +func parseFallbackAddr(data []byte, net *chaincfg.Params) (btcutil.Address, error) { // nolint:dupl // Checks if the data is empty or contains a version without an address. if len(data) < 2 { return nil, fmt.Errorf("empty fallback address field") } - var addr address.Address + var addr btcutil.Address version := data[0] switch version { @@ -547,13 +545,9 @@ func parseFallbackAddr(data []byte, switch len(witness) { case 20: - addr, err = address.NewAddressWitnessPubKeyHash( - witness, net, - ) + addr, err = btcutil.NewAddressWitnessPubKeyHash(witness, net) case 32: - addr, err = address.NewAddressWitnessScriptHash( - witness, net, - ) + addr, err = btcutil.NewAddressWitnessScriptHash(witness, net) default: return nil, fmt.Errorf("unknown witness program length %d", len(witness)) @@ -567,7 +561,7 @@ func parseFallbackAddr(data []byte, if err != nil { return nil, err } - addr, err = address.NewAddressTaproot(witness, net) + addr, err = btcutil.NewAddressTaproot(witness, net) if err != nil { return nil, err } @@ -577,7 +571,7 @@ func parseFallbackAddr(data []byte, return nil, err } - addr, err = address.NewAddressPubKeyHash(pubKeyHash, net) + addr, err = btcutil.NewAddressPubKeyHash(pubKeyHash, net) if err != nil { return nil, err } @@ -587,9 +581,7 @@ func parseFallbackAddr(data []byte, return nil, err } - addr, err = address.NewAddressScriptHashFromHash( - scriptHash, net, - ) + addr, err = btcutil.NewAddressScriptHashFromHash(scriptHash, net) if err != nil { return nil, err } diff --git a/zpay32/encode.go b/zpay32/encode.go index f7c643009..50f294e51 100644 --- a/zpay32/encode.go +++ b/zpay32/encode.go @@ -5,10 +5,10 @@ import ( "encoding/binary" "fmt" - "github.com/btcsuite/btcd/address/v2" - "github.com/btcsuite/btcd/address/v2/bech32" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/bech32" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwire" ) @@ -201,15 +201,15 @@ func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error { if invoice.FallbackAddr != nil { var version byte switch addr := invoice.FallbackAddr.(type) { - case *address.AddressPubKeyHash: + case *btcutil.AddressPubKeyHash: version = fallbackVersionPubkeyHash - case *address.AddressScriptHash: + case *btcutil.AddressScriptHash: version = fallbackVersionScriptHash - case *address.AddressWitnessPubKeyHash: + case *btcutil.AddressWitnessPubKeyHash: version = addr.WitnessVersion() - case *address.AddressWitnessScriptHash: + case *btcutil.AddressWitnessScriptHash: version = addr.WitnessVersion() - case *address.AddressTaproot: + case *btcutil.AddressTaproot: version = addr.WitnessVersion() default: return fmt.Errorf("unknown fallback address type") diff --git a/zpay32/fuzz_test.go b/zpay32/fuzz_test.go index c5c95e8d2..9855b8c1a 100644 --- a/zpay32/fuzz_test.go +++ b/zpay32/fuzz_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/chaincfg" ) // getPrefixAndChainParams selects network chain parameters based on the fuzzer- diff --git a/zpay32/hophint.go b/zpay32/hophint.go index dd1a2eebe..07872b0d6 100644 --- a/zpay32/hophint.go +++ b/zpay32/hophint.go @@ -12,7 +12,7 @@ const ( // We adhere to the recommendation in BOLT 02 for terminal payments. // See also: // https://github.com/lightning/bolts/blob/master/02-peer-protocol.md - DefaultAssumedFinalCLTVDelta = 24 + DefaultAssumedFinalCLTVDelta = 18 // feeRateParts is the total number of parts used to express fee rates. feeRateParts = 1e6 diff --git a/zpay32/invoice.go b/zpay32/invoice.go index 401be7db5..9c5d86ce2 100644 --- a/zpay32/invoice.go +++ b/zpay32/invoice.go @@ -5,9 +5,9 @@ import ( "fmt" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwire" ) @@ -183,7 +183,7 @@ type Invoice struct { // FallbackAddr is an on-chain address that can be used for payment in // case the Lightning payment fails. // Optional. - FallbackAddr address.Address + FallbackAddr btcutil.Address // RouteHints represents one or more different route hints. Each route // hint can be individually used to reach the destination. These usually @@ -266,7 +266,7 @@ func Expiry(expiry time.Duration) func(*Invoice) { // FallbackAddr is a functional option that allows callers of NewInvoice to set // the Invoice's fallback on-chain address that can be used for payment in case // the Lightning payment fails -func FallbackAddr(fallbackAddr address.Address) func(*Invoice) { +func FallbackAddr(fallbackAddr btcutil.Address) func(*Invoice) { return func(i *Invoice) { i.FallbackAddr = fallbackAddr } diff --git a/zpay32/invoice_internal_test.go b/zpay32/invoice_internal_test.go index 4c97a1334..22434a99b 100644 --- a/zpay32/invoice_internal_test.go +++ b/zpay32/invoice_internal_test.go @@ -7,10 +7,10 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/address/v2" - "github.com/btcsuite/btcd/address/v2/bech32" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/bech32" + "github.com/btcsuite/btcd/chaincfg" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" ) @@ -626,7 +626,7 @@ func TestParseFallbackAddr(t *testing.T) { data []byte net *chaincfg.Params valid bool - result address.Address + result btcutil.Address }{ { data: []byte{}, @@ -834,6 +834,7 @@ func TestParseTaggedFields(t *testing.T) { }, } for _, tc := range tests { + tc := tc // pin t.Run(tc.name, func(t *testing.T) { var invoice Invoice gotErr := parseTaggedFields(&invoice, tc.data, netParams) diff --git a/zpay32/invoice_test.go b/zpay32/invoice_test.go index 01318ed2f..bfa1539f3 100644 --- a/zpay32/invoice_test.go +++ b/zpay32/invoice_test.go @@ -11,11 +11,11 @@ import ( "testing" "time" - "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/chaincfg/v2" - "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwire" @@ -66,28 +66,12 @@ var ( testExpiry0 = time.Duration(0) * time.Second testExpiry60 = time.Duration(60) * time.Second - testAddrTestnet, _ = address.DecodeAddress( - "mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP", - &chaincfg.TestNet3Params, - ) - testRustyAddr, _ = address.DecodeAddress( - "1RustyRX2oai4EYYDpQGWvEL62BBGqN9T", - &chaincfg.MainNetParams, - ) - testAddrMainnetP2SH, _ = address.DecodeAddress( - "3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX", - &chaincfg.MainNetParams, - ) - testAddrMainnetP2WPKH, _ = address.DecodeAddress( - "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4", - &chaincfg.MainNetParams, - ) - testAddrMainnetP2WSH, _ = address.DecodeAddress( - "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0"+ - "gdcccefvpysxf3qccfmv3", - &chaincfg.MainNetParams, - ) - testAddrMainnetP2TR, _ = address.DecodeAddress("bc1pptdvg0d2nj99568"+ + testAddrTestnet, _ = btcutil.DecodeAddress("mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP", &chaincfg.TestNet3Params) + testRustyAddr, _ = btcutil.DecodeAddress("1RustyRX2oai4EYYDpQGWvEL62BBGqN9T", &chaincfg.MainNetParams) + testAddrMainnetP2SH, _ = btcutil.DecodeAddress("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX", &chaincfg.MainNetParams) + testAddrMainnetP2WPKH, _ = btcutil.DecodeAddress("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4", &chaincfg.MainNetParams) + testAddrMainnetP2WSH, _ = btcutil.DecodeAddress("bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3", &chaincfg.MainNetParams) + testAddrMainnetP2TR, _ = btcutil.DecodeAddress("bc1pptdvg0d2nj99568"+ "qn6ssdy4cygnwuxgw2ukmnwgwz7jpqjz2kszse2s3lm", &chaincfg.MainNetParams) @@ -917,6 +901,7 @@ func TestDecodeEncode(t *testing.T) { } for i, test := range tests { + test := test t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { t.Parallel() @@ -1065,6 +1050,7 @@ func TestNewInvoice(t *testing.T) { } for i, test := range tests { + test := test t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { t.Parallel()